|

1. 简介
Project Wonderland 是为创建 3D 虚拟世界提供的一个软件平台,其主要作用是允许地理位置上分布的参与者相互协作。本文档将介绍 Project Wonderland 的软件架构。本文的主要读者是希望扩展或修改 Project Wonderland 的软件开发人员。Project Wonderland 本身处于发行版的雏形阶段,开发人员能够期望软件的架构以及该架构文档过一段时间后也发生了一些重大改变。
2. 主要组件:客户机服务器架构
Project Wonderland 是一个基于 Java 的客户机服务器软件平台。服务器软件建立在 Project Darkstar(为 Java SE 平台提供由多人参加“游戏”技术)基础之上。(游戏一词放在引号里是因为尽管 Project Darkstar 旨在面向游戏领域,但是它在其他上下文中也已经被采用,也就是说,它并不被严格限定在游戏领域内使用)。
Project Darkstar 管理对象的集合(称作 ManagedObjects),为对象状态的同步更新提供 API,以便响应来自多客户机的事件。对 ManagedObjects 的更新本身是事务处理性的 —— 要么在对象集合之间发生状态更新的连贯集合,要么对象集合之间根本不发生状态更新。在游戏世界或虚拟世界内的所有项目由服务器上的 ManagedObjects 表示,例如,房间、玩家、工具、武器或在世界中存在的其他被虚拟化的真实世界对象。关于 Project Darkstar 方面的详细信息可以在 http://www.projectdarkstar.com 上找到。
客户机是一个桌面 Java 应用程序,建立在呈现 3D 世界的两项技术基础之上。Java 3D 和 Looking Glass 3D (LG3D)。Java 3D 是对 Java SE 的标准扩展,如有可能,利用硬件加速将 3D 对象集合呈现到屏幕上。关于 Java 3D 方面的详细信息可以在 https://java3d.dev.java.net/ 上找到。Project Looking Glass 提供了一个 3D 桌面环境。它提供了一个 3D 窗口环境和构建 3D 应用程序的 API 集合。关于 Project Looking Glass 方面的详细信息可以在 https://lg3d-core.dev.java.net/ 上找到。
3. 虚拟 3D 世界
虚拟世界是由许多“单元”集合组成,每个单元表示世界中的一个 3D 体积。这样一个世界有一个单独的右手系 3D 笛卡尔坐标系统(以下简称“全局坐标”)从观看者的角度来看,+X 轴指向右面,+Y 轴纵向朝上,+Z 轴指向用户。该坐标系统与 Java 3D 所定义的坐标系统相同。单元可以是虚拟世界中的场景或对象。称作 Avatar 的人物也由各个单元表示。
每个单元在虚拟世界内有一个位置和界限。相对单元的中心位置的单元原点在全局坐标中给出。一个单元还有“单元坐标”,其中单元中心坐标为(0,0,0)。
一个单元可以包含零个或多个其他单元,因此对于每个单元而言存在“父单元”和“子单元”概念 —— 单个“主”单元表示整个世界并包括所有其他单元。如果一个单元跨越多个父单元的物理界限,这个单元会有多个父单元。因此,许多单元可能与一个单元重叠,且不必完全被包含在单个单元内。(这好像意味着一个单元的界限会和另一个单元界限重叠。)
4. 服务器端架构
4.1 Darkstar 引导类和数据管理
Project Darkstar 要求启动程序初始化“游戏”(可以替代 main() 方法)。org.jdesktop.lg3d.wonderland.darkstar.server.WonderlandBoot 类通过 Darkstar AppListener 接口实现来达到这一目的。initialize() 方法的实现执行引领 Wonderland 世界创建的基础任务。它们是:
- 创建维护连接到服务器上的用户(也就是客户机)列表的
org.jdesktop.lg3d.wonderland.darkstar.server.UserManager 类实例。
- 创建维护类列表(维护对于用户为可视的单元缓存)的
org.jdesktop.lg3d.wonderland.darkstar.server.cell.MasterCellCacheGLO 类实例。(还不能实际确定其含义 Bernard。)
- 创建管理一系列校验和(在世界中表示财产的各个类)的
org.jdesktop.lg3d.wonderland.darkstar.server.ChecksumManagerGLO 类实例。
- 打开称作
ChannelInfo.USER_CHANGE 的通信通道,对在客户机状态中的变化进行通信。
当一个新的客户机打开与 Wonderland 服务器的连接时,WonderlandBoot.loggedIn() 方法被调用,简易创建类 org.jdesktop.lg3d.wonderland.darkstar.server.WonderlandSessionListener 的新实例。它是一个处理在客户机中未来状态变化的类,特别是当用户登陆时,它创建网络虚拟角色(avatar)( 请参见 WonderlandSessionListener.processAvatarSetupMessage())。
4.2 单元架构
Project Wonderland 采用了服务器端上的 Project Darkstar 游戏平台,其中每个单元由管理对象表示。对于所有单元管理对象,基础抽象类是 org.jdesktop.lg3d.wonderland.darkstar.server.cell.CellGLO 类。(后缀 “GLO”是从 Project Darkstar 的早期版本中继承下来的,其中管理对象被称作“游戏逻辑对象(Game Logic Object )”(或 GLO))。
在 Wonderland 中有两种类型的单元:静态单元和可移动单元。静态单元表示不能移动的区域,而可移动单元表示移动的图形。网络虚拟角色是可移动单元的最佳示例(可能是惟一的)。所有静态单元类扩展 org.jdesktop.lg3d.wonderland.darkstar.server.cell.StationaryCellGLO,所以可移动单元类扩展 org.jdesktop.lg3d.wonderland.darkstar.server.cell.MoveableCellGLO。
在 Wonderland 架构中,用不同的类来定义各个静态单元。最基本的类是“world root”单元(参见 org.jdesktop.lg3d.wonderland.darkstar.server.cell.WorldRootCellGLO 类),它是世界中所有单元的根,还负责创建世界。目前,WorldRootCellGLO 使用代码的硬编码行创建了各个单元的结构。(例如,WorldRootCellGLO.buildFullWorld()。)将来,计划要用更为灵活的架构构建世界。
目前,下列静态类表示如下(在包 org.jdesktop.lg3d.wonderland.darkstar.server.cell 中):简单地形(SimpleTerrainCellGLO)、幻灯片放映(SlideShowCellGLO)、模型观察器(ModelViewerCellGLO)、动画(AnimatedCellGLO)、音频(AudioCellGLO)和共享的应用程序(SharedApp2DCellGLO)。现存惟一的可移动单元是网络虚拟角色单元(AvatarCellGLO )。
- 简单地形单元通过采用定义环境的几何图形的外部文件的名称来定义世界中的基本环境。
- 幻灯片放映单元采用文件名称的模式(其中百搭牌字符('%')被解压缩为桢号 )定义按顺序显示的一系列图像。按顺序显示的图像根据用户输入将被提升。
- 模型观察器单元需要进一步研究。XXX
- 动画单元通过提取构成动画的文件名称数组表示在世界中被制作成动画的一系列图像。
- 音频单元是动画单元的扩展,表示在世界中播放从某一声源处发出的音频。
- 共享应用程序单元表示在世界中被显示的基于 X Windows 的应用程序,能与网络虚拟角色发生交互。
4.3 单元功能
所有单元类扩展为各个单元提供绝大多数必需服务的 CellGLO 类。每个单元拥有中央设施所生成的惟一 ID(参见 org.jdesktop.lg3d.wonderland.darkstar.server.CellIDGenerator ),以及表达式为“CELL_ + ID”的惟一名称。(参见 CellGLO.getCellID() 和 CellGLO.getGLOName() 方法 )。单元的 ID 是被 org.jdesktop.lg3d.wonderland.darkstar.common.CellID 类实例所封装的数字。
4.3.1 单元原点和界限
世界中的每个单元位置由原点界定,例如由 4x4 矩阵确定(参见 Java 3D 的 Matrix4d 类;Matrix4d 类在 Java 3D 中被用于定义普通的变换动作,例如旋转、缩放、平移和剪切动作。单元原点不能是这些变换动作的任意组合的原因似乎没有基本限制,但是平移和旋转似乎是实际应用的惟一类型。单元的原点被存储为“cellOrigin”成员变量,并由所有单元子类直接设置。CellGLO.getOrigin() 方法以类 Matrix4d 的实例的形式返回单元原点。
一个单元还要有一个界限,由描述其物理范围的 Java 3D Bounds 类表示。CellGLO 的子类通过覆盖 CellGLO.getBounds() 方法管理单元界限。
4.3.2 父单元和子单元
单元的零个或多个子单元集合和父单元集合被存储在散列集合中。分别通过 CellGLO.addParentCell() 和 CellGLO.removeParentCell() 方法添加和移除父单元。CellGLO.getParentCellIDs() 方法返回 CellID 类的实例数组,其中每个实例确定一个父单元。方法 CellGLO.addChildCell() 和 CellGLO.removeChildCell() 分别向单元添加子单元、从单元中移除子单元。CellGLO.getChildren() 返回一个单元的子单元集合。
一些单元的子单元在任何时间内都是可视的,这取决于可视界限。CellGLO.getVisibleCells() 方法返回在给出的一定界限内可视的子单元集合。可视界限通常依赖于查看网络虚拟角色的 Frustum。
4.3.3 通过单元实现客户机通信
使用 Project Darkstar 通道机制,各个单元还提供了在客户机之间的通信机制。每个单元可以拥有单独的通信通道,该通道由惟一名称识别并通过 CellGLO.openCellChannel() 方法加以创建。CellGLO.getCellChannel() 方法返回单元的通道。客户机通过 CellGLO.addUserToCellChannel() 方法可以作为侦听器向通道添加,通过 CellGLO.removeUserFromCellChannel() 方法从通道中移除。这两种方法的任何一种将客户机会话的惟一 ID 作为参数。关于 Project Wonderland 中的客户机服务器的通信架构方面的详细信息,请参见下面的第 6 部分。
4.3.4 客户端单元类和设置
服务器上的每个 CellGLO 类在负责呈现单元的客户机上有相应的 Cell 类。通过 CellGLO.getClientCellClassName() 方法,相应客户机单元类的名称由每个单个服务器端单元的子类定义。
服务器端 CellGLO 通过实现 org.jdesktop.lg3d.wonderland.darkstar.common.CellSetup 界面的类实例对可视内容和其他设置数据进行通信。通信结果从 CellGLO.getSetupData() 方法返回,该方法通过每个单个服务器端的 CellGLO 子类得以实现。
CellSetup 界面本身是一个扩展 java.io.Serializable 界面的简单空界面,因此该界面可以在网络上发送。每个服务器端的 CellGLO 类定义了实现 CellSetup 界面的自我“设置”类。示例(在 org.jdesktop.lg3d.wonderland.darkstar.common 包中)包括 AnimatedCellSetup、AvatarCellSetup、ModelViewerCellSetup、SharedApp2DCellSetup、SimpleCellSetup 和 SlideShowCellSetup。例如,SimpleCellSetup 类被简单的地形单元所使用,它拥有返回存储场景的磁盘文件名称的方法。
4.4 服务器端单元缓存
被客户机保存在内存中的 单元 由服务器通过 org.jdesktop.lg3d.wonderland.darkstar.server.cell.UserCellCacheGLO 类实例加以管理,对于每个用户(相当于“客户机”)存在该类的实例,且当用户登录时被创建。MasterCellCacheGLO 类的实例管理由 WonderlandBoot 所创建的 UserCellCacheGLO 的所有实例(参见第 4.1 部分)。它们是在 Project Darkstar 基础设施内的管理对象。
客户端单元缓存支持对 CellGLOs 的几种状态更改。它们是:单元被创建、根单元被创建、单元被移动、单元被删除以及向单元添加父单元。目前,UserCellCacheGLO.revalidate() 方法考虑了当前可视的 CellGLOs,添加新的 CellGLOs,并从客户机内存中删除再也不会可视的单元。在 CellGLOs 状态中的更改通过与 UserCellCacheGLO 类实例有关的专用通道(称作 CELL_CACHE)进行通信。目前,在该通道上通信的消息是序列化的 org.jdesktop.lg3d.wonderland.darkstar.common.messages.CellHierarchyMessage 类实例。
可视单元通过网络虚拟角色的位置来决定。其可视域由类 org.jdesktop.lg3d.wonderland.darkstar.common.AvatarBoundsManager 的实例通过 getProximityBounds() 方法决定。该方法界定了从网络虚拟角色中心到指定半径(AvatarBoundsManager.PROXIMITY_SIZE = 200.0)构成球体范围内的可视区域。
5. 客户端架构
5.1 主要类
正如其名称所暗示的,org.jdesktop.lg3d.wonderland.Main 类实现 main() 方法以便启动客户机。该方法初始化用户界面,并从外部 XML 格式的配置文件中读取可变的配置选项。
通过称作 StartupListener(实现 Lg3dStartupListener 界面,来自 Project Looking Glass)的内部类与服务器之间进行连接的初始化 —— 该侦听器的 startupComplete() 方法在图形初始化完成时被调用。startupComplete() 方法的实现依次显示从用户接收登录信息的登录对话(参见类 org.jdesktop.lg3d.wonderland.LoginDialog)。该方法启动与 Wonderland 服务器之间的连接,并向“开机的” 客户机调用 Main.enableMainWindow() 方法。
与 Darkstar-based Wonderland 服务器之间的所有通信由类 org.jdesktop.lg3d.wonderland.darkstar.client.ChannelController 的实例进行管理。(因为管理内容并不控制通道,org.jdesktop.lg3d.wonderland.darkstar.client.ChannelController 在名称上似乎有些误导,相反它提供了客户机服务器直接通信的机制。)通过 ChannelController.initCommunications() 方法启动到服务器的新连接。要了解类 ChannelController 方面的详细信息,请参见第 6 部分。
5.2 Wonderland 域
由 org.jdesktop.lg3d.wonderland.scenemanager.WonderlandUniverseFactory 实例创建的“Wonderland 域”用于创建主要绘图画布和初始化一些基本的查看参数。这个实例由类 org.jdesktop.lg3d.wonderland.scenemanager.WonderlandPlatformConfig 的实例创建,通过 lg.platformConfigClass 属性依次加以指定。
5.3 场景管理器
Project Wonderland 定义了自己的场景管理器,等同于 Project Looking Glass 中的 X Windows 窗口管理器。类 org.jdesktop.lg3d.wonderland.scenemanager.WonderlandSceneManager 扩展实现 LG3D SceneManager 界面的 LG3D SceneManagerBase 抽象类。通过 LG3D 界面 DisplayServerManagerInterface 的实现,LG3D SceneManagerBase 类处理与LG3D 显示服务器(显然等同于 X Windows Server)之间的通信。
WonderlandSceneManager.initialize() 方法包含该类的许多功能。使用 WorldController 将所有可视化内容连接到 Java 3D BranchGroup 的类实例,该方法创建类 org.jdesktop.lg3d.wonderland.scenemanager.WorldController 的实例。它还添加 Java 3D BranchGroup 类的实例作为 LG3D 显示服务器根的子单元。
WonderlandSceneManager 的实例还使用空样式布局管理器创建 LG3D StandardAppContainer 类的实例。SceneManager.addFrame3D() 和 SceneManager.removeFrame3D() 方法的实现向类 StandardAppContainer 添加桢并从其中移除桢。此类方法的使用目前尚不清楚。
类 WonderlandSceneManger 的实例根据 LG3D 说明书进行安装,如下所示:org.jdesktop.lg3d.wonderland.scenemanager.WonderlandConfigControl 的 createSceneManager() 方法返回类 WonderlandSceneManager 的一个实例。Main 类向“org.jdesktop.lg3d.wonderland.scenemanager.WonderlandConfigControl”设置“lg.configclass”属性。(对于场景管理器的附加配置信息,还可以默认的方式通过读取配置选项的 XML 格式文件的 WonderlandConfigControl 类加以处理:"/org/jdesktop/lg3d/wonderland/scenemanager/resources/wonderland.lgcfg"。)
5.4 世界控制器
类 WorldController 的实例处理在 Wonderland 世界中的所有可视内容。作为类成员,它维护两个重要客户端类的实例。org.jdesktop.lg3d.wonderland.scenemanager.UserCellCache 和 ChannelController。WorldController 作为向 Java 3D BranchGroup 添加照明的参数:颜色 RGB = {0.7, 0.7, 0.7} 的环境光线和两个定向照明。一个定向照明是白色的,左侧朝下。第二个定向灯为 RGB = {0.2, 0.2, 0.3} ,左侧朝上。(TODO:该定向照明的表达效果是怎样的?)
关于 UserCellCache(第 5.4 部分)和 ChannelController(第 5.x 部分)的详细信息在下面的章节中可以找到。
5.5 客户端单元缓存
通过 UserCellCache 的实例可以缓存并维护可视单元。单元的加载/ 卸载以及状态的更改完全由服务器通过从服务器向客户机所发送的消息控制,格式为类 CellHierarchyMessage 的实例。UserCellCache.handleMessage() 方法处理这些服务器事件,发送通过 Project Darkstar 基础设施所维护的通信通道,并通过类 org.jdesktop.lg3d.wonderland.darkstar.client.UserCellCacheChannelListener 的实例加以处理。
特别是在客户机引导时间内,常见的操作是通过类型 CellHierarchyMessage.LOAD_CELL 的消息请求在客户端创建新的单元。使用在 CellHiearchyMessage 中嵌入的配置信息,客户端单元类的实例被创建并被初始化。新的单元被添加到各个单元的 HashMap 缓存,线程(在类 UserCellCache 内的内部类 CellUpdateThread的实例)发出信号通知更新可视单元集合以便呈现(UserCellCache.visibleCells 成员变量)。
类 UserCellCache 的实例还管理单元的列表,其中网络虚拟角色当前通过 UserCellCache.avatarInCells 成员变量(HashSet)定位。当 CellUpdateThread 发出已经更新的信号时,该集合被更新。单元进入和退出事件被生成 —— 分别由类 CellEnterEvent 和 CellExitEvent(在包 org.jdesktop.lg3d.wonderland.scenemanager.events 中)表示 —— 并被邮寄。
类 UserCellCache 目前只实现单元状态的子集:VISIBLE 和 INACTIVE。下列单元状态目前不被处理:ACTIVE、DISK 和 BOUNDS。
类 UserCellCache 还实现 org.jdesktop.lg3d.wonderland.scenemanager.UserMotionListener 界面,该界面当网络虚拟角色(通过 userMoved() 方法)移动时接收通知。目前,UserCellCache.userMoved() 方法的实现属于基本过程:通过使用被更新的网络虚拟角色位置,该方法简单地更新可视单元的状态并生成单元进入/ 退出消息。
6.客户机服务器通信
本部分概述 Wonderland 客户机和服务器之间、各个客户机之间的所有通信通道和所发送的消息。
每个客户机在服务器上通过许多对象表示。这些对象是:
UserGLO —— 基本用户信息,像名称、首选颜色。
AvatarCellGLO —— 用户的网络虚拟角色所呈现的单元。
UserCellCacheGLO —— 对特定用户可视的单元。
每个用户/ 客户机有每种对象中的一个对象。当用户在世界中移动时,服务器跟踪用户“了解”的单元。当用户移动时,通知服务器,服务器检查 UserCellCacheGLO 以便查看客户机是否已经加载了即时区域中的所有单元。如果服务器找到一个客户机还没有查看过的单元,服务器便发送一条消息,告诉客户机加载新单元。
6.1.客户机服务器直接通信
当客户机连接到服务器时,通过 Project Darkstar 自动设置客户机服务器的直接通信链接。
服务器通过该机制将下列消息传输给客户机。
- 初始消息,给出通信协议的版本(在
WonderlandBoot.loggedIn() 中)。
- 潜在错误消息,像类
org.jdesktop.lg3d.wonderland.darkstar.common.messages.ErrorMessage 的实例(与前面相同,在 WonderlandSessionListener.processFileTransferMessage() 中。)
- 类
org.jdesktop.lg3d.wonderland.darkstar.common.messages.NativeApplicationMessage 的实例,像回复“创建应用程序单元”消息的消息(在 WonderlandSessionListener.processNativeApplicationMessage() 中)。
- 类
org.jdesktop.lg3d.wonderland.darkstar.common.messages.SoftphoneMessage 的实例(在 WonderlandSessionListener 的 callStatusChanged() 和 processSoftphoneMessage() 方法中)。
由客户机处理的消息如下所示:
ChannelController.handleMessage() 处理协议版本和错误消息。
org.jdesktop.lg3d.wonderland.appshare.ServerApp2DCel 类的内部类 MessageListener 的 receivedMessage() 方法处理 NativeApplicationMessage 消息。
ChannelController.handleMessage() 处理 SoftphoneMessage 消息。
客户机通过该机制将下列消息传输给服务器。
- 类
NativeApplicationMessage 的各个实例。这些实例通过类 ServerApp2DCell 的构造器和 ChannelController 的 sendMessage() 方法发送。
org.jdesktop.lg3d.wonderland.darkstar.common.messages.SoftphoneMessage 实例。这些实例通过 ChannelController 的 connectSoftphone() 和 disconnectSoftphone() 方法发送。
org.jdesktop.lg3d.wonderland.darkstar.common.messages.AvatarSetupMessage 实例,通过类 ChannelController 中的loggedIn() 方法所启动的 。
org.jdesktop.lg3d.wonderland.darkstar.common.messages.CellMessage 的某些子类的实例。目前还没有此类实例。
服务器通过该机制从客户机接收下列消息。
- 类
NativeApplicationMessage 的各个实例。这些实例由 org.jdesktop.lg3d.wonderland.darkstar.server.WonderlandBaseSessionListener 的 receivedMessage() 方法进行处理,随后通过 processNativeApplicationMessage() 方法做进一步处理。
SoftphoneMessage 的实例。这些实例通过 WonderlandSessionListener 的 receivedMessage() 方法进行处理。
org.jdesktop.lg3d.wonderland.darkstar.common.messages.AvatarSetupMessage 消息的实例。这些实例通过 WonderlandSessionListener 的 receivedMessage() 方法进行处理。
org.jdesktop.lg3d.wonderland.darkstar.common.messages.CellMessage 消息的某些子类的其他实例,这些实例被分派到类 org.jdesktop.lg3d.wonderland.darkstar.server.CellMessageListener 的各个实例。
6.2 发布/ 订阅通信通道
Wonderland 服务器还创建发布/ 订阅通信通道,其中包括:USER_CHANGE、CELL_CACHE、AVATAR_P2P、AVATAR_CELL 和 SERVER_MANAGER_CHANNEL。通道提供客户机之间、客户机和及其服务器之间的通信机制。
6.2.1 USER_CHANGE 通道
USER_CHANGE 通信通道通过 initialize() 中的 WonderlandBoot 方法进行创建,且所有的新客户机自动被添加到其中。对于服务器到客户机的通信,USER_CHANGE 通道是专用的。
服务器通过该通道将下列消息传输给客户机。
- 类
org.jdesktop.lg3d.wonderland.darkstar.common.messages.UserChangedMessage 的实例,当用户被添加时(USER_ADD,以 WonderlandSessionListener 类的 processAvatarSetupMessage() 方法)或当用户离开时(USER_LEFT,以 WonderlandSessionListener 类的 disconnected() 方法)。
服务器不接收来自该通道上客户机的任何消息。
客户机不传输该通道上的任何消息。
客户机通过类 org.jdesktop.lg3d.wonderland.darkstar.client.UserChannelListener 的 receivedMessage() 方法处理这些消息。
6.2.2 CELL_CACHE 通道
对于每个被连接的客户机,服务器创建了 CELL_CACHE 通道。通道的名称采取以下形式:user + CELL_CACHE,其中“user”是用户的名称。为客户机提供的 CELL_CACHE 通道由对应客户机(在其构造器中)的 UserCellCacheGLO 类实例创建。每个新客户机被自动添加到这些通道中的一个通道中。
类 UserCellCacheGLO 的每个实例有一个 CELL_CACHE 通道。只有一个客户机/ 用户(缓存的用户)被订阅到 CELL_CACHE 通道。在类 UserCellCacheGLO 的 login() 方法中管理订阅内容。对于服务器到客户机的通信,CELL_CACHE 通道是专用的。
服务器通过该通道将下列消息传输给客户机。消息通过 UserCellCacheGLO 的实例发送。
- 类
CellHiearchyMessage 消息的实例可能是以下几种:LOAD_CELL、MOVE_CELL、CELL_INACTIVE、ADD_PARENT、REMOVE_PARENT、SET_WORLD_ROOT 和 DELETE_CELL。
服务器不接收来自该通道上客户机的任何消息。
客户机不传输该通道上的任何消息。
客户机通过 org.jdesktop.lg3d.wonderland.darkstar.client.UserCellCacheChannelListener 类实例的 receivedMessage() 方法处理消息。
6.2.3 AVATAR_P2P 通道
对于每个被连接的客户机,服务器创建了 AVATAR_P2P 通道。通道的名称采取以下形式:user + AVATAR_P2P,其中“user”是用户的名称。该通道在构造器内通过类 AvatarCellGLO 的实例创建,然后通过 AvatarCellGLO 的实例加以维护。另一个客户机请求通过在 AVATAR_CELL 通道上发送的 AvatarCellMessage(JOIN_P2P) 消息加入客户机的 P2P? 通道(参见下面内容)。
每个网络虚拟角色通过客户机的 AVATAR_P2P 通道发送运动更新信息,因此任何负责更新信息(通常在网络虚拟角色的可视范围内的任何其他客户机)的客户机将订阅该通道。
服务器通过该通道将下列消息传输给客户机:
- 类
org.jdesktop.lg3d.wonderland.darkstar.common.messages.AvatarP2PMessage 的实例,其中有两种:
SETUP 消息通过类 AvatarCellGLO 的 receivedMessage() 方法发送以便响应 AvatarCellMessage(JOIN_P2P) 消息。
SPEAKING 消息通过类 WonderlandSessionListener 的 callStatusChanged() 和 processAvatarCellMessage() 方法以及 AvatarCellGLO 的 setSpeakingStatus() 方法发送。
客户机通过类 org.jdesktop.lg3d.wonderland.darkstar.client.AvatarP2PChannelListener 的 receivedMessage() 方法处理消息。
客户机通过该通道传输类 AvatarP2PMessage 的实例,该类通道只有 MOVE 和 CHAT 两种:
MOVE 消息通过类 org.jdesktop.lg3d.wonderland.scenemanager.avatar.Avatar 的 userMoved() 方法发送以便响应用户的交互。
CHAT 消息通过类 Avatar 的 sendChatMessage() 方法发送。
目前服务器通过 org.jdesktop.lg3d.wonderland.darkstar.serverAvatarP2PChannelListenerGLO 类实例的 handleMessage() 方法接收该通道上的消息。(该方法只用于日志。)
因此,该通道被用于客户机间的通信(用于类型 MOVE 和 CHAT 的 AvatarP2PMessages),并用于服务器到客户机之间的通信(用于类型的 SETUP 和 SPEAKING 的 AvatarP2PMessages)。
6.2.4 AVATAR_CELL 通道
在构造器内,AVATAR_CELL 通道通过类 AvatarCellGLO 的实例创建。通道的名称采取以下形式:user + AVATAR_CELL,其中“user”是用户的名称。
服务器通过该通道将下列消息传输给客户机。
- 类
AvatarCellMessage 的实例,其中有一个基本种类:SETUP。当一个网络虚拟角色的单元(也就是 AvatarCellGLO)第一次变为可视的时候,该实例被发送。
当一个单元变为可视状态时 ,服务器逐句通过所有 CellGLOs 的列表,如果 =CellGLO= 通道存在,服务器将客户机添加到其中。目前,只有网络虚拟角色 CellGLO? 为自身创建通道。此时,SETUP 消息被发送到客户机(为未来通信保留对通道的引用)。
服务器通过该通道从客户机接收下列消息。
AvatarCellMessage 的实例。该实例通过 AvatarCellGLO 类实例中的 receivedMessage() 方法(对于 CELL_MOVE、CELL_ENTER 和 CELL_EXIT 种类的消息而言),还可以通过 WonderlandSessionListener 实例的 receivedMessage()方法(对于 AVATAR_MOVE、AVATAR_MUTE 和 JOIN_P2P 种类的消息而言)处理消息。
客户机通过该通道传输类 AvatarCellMessage 的实例(请参见类 org.jdesktop.lg3d.wonderland.darkstar.client.cell.AvatarCell)。
客户机通过该通道接收消息。客户机通过 org.jdesktop.lg3d.wonderland.darkstar.client.cell.AvatarCellChannelListener 类实例的 receivedMessage() 方法处理消息。
6.2.5 SERVER_MANAGER_CHANNEL 通道
SERVER_MANAGER_CHANNEL 通信通道通过类 org.jdesktop.lg3d.wonderland.darkstar.server.ServerManagerGLO 的实例创建,被用于服务器管理信息到客户机的通信。目前,该通道添加了服务器和特定的客户机(硬编码为“ServerManager”的名称)
服务器通过该通道向客户机传输并接收类 org.jdesktop.lg3d.wonderland.darkstar.common.messages.ServerManagerMessage 的实例,其中有四种:STATUS、FULL_STATUS、CHANGE_UPDATE_INTERVAL 和 SET_USER_LIMIT。
通过类 org.jdesktop.lg3d.wonderland.management.ManagerUI 的实例向客户机提供到 SERVER_MANAGER_CHANNEL 的通信通道,其中该类实例可以使用 ANT run-manager 目标打开。
6.3 客户机和服务器更改管理指南
在两种用户交互之间存在区别:
- 只更改客户机状态的用户交互可能以
Cell 的外观形式出现。当鼠标悬停在窗口构件上时,就是其中的一个示例(例如,参见示例模型)
- 引起服务器上的
CellGLO 状态更改的用户交互。当用户单击窗口构件时,就是其中的一个示例(例如,参见示例模型)
下面描述了两个案例。在每个案例中,假设四个客户机(C1、 C2、C3 和 C4)被连接到服务器上。
6.3.1 更改客户机状态
- 在客户机 C2 上,与
Cell 的用户交互引起在 Cell 中的局部状态更改。
Cell 更新外观
Cell 向 Cell 的 通道上的其他客户机广播消息;消息识别 Cell 的“更改种类”和最新状态。
- 其他客户机接收消息,同时其相应的
Cell 更新外观。

6.3.2 更改服务器状态
- 在客户机 C2 上与
Cell 的用户交互引起在服务器上的 CellGLO 中的状态更改。
Cell 更新局部状态(如有必要)和外观
- 客户机 C2 直接向服务器发送消息。所发送的消息描述“更改种类”和
Cell 的最新状态
- 服务器识别适当的
CellGLO。
CellGLO 更新其状态
- 除了源客户机,服务器向所有客户机广播
Cell 通道上的消息。所发送的消息描述“更改种类”和 Cell 的最新状态。
- 每个客户机接受消息并更新
Cell 的状态(如有必要)和外观。

上述控制流与常规的 MVC 在以下方面存在差异:
- 在模型和其视图之间没有明显的分离。
CellGLO 决定它的 Cell(等于说是模型决定其视图)。这意味着不可能使用相同的 CellGLO 类来通过多个 Cell 类加以呈现。例如,表示骰子的 CellGLO 类不能有提供骰子不同呈现的两个 Cell 类。
- 在视图及其控制器之间没有明显的分离。通过视图类中内部类为视图(遵循 Swing 格式)提供控制器。例如,通常通过
Cell 类的内部类为 Cell 类提供控制器。
- 由于多个客户机呈现相同的
Cell,因此有必要在客户机之间保持同步。这一要求超出 MVC 能力之外,在 Wonderland 中利用向 Cell 所拥有的通道所发送的消息加以处理。
最后,在 MVC 中的控制流与在 Wonderland 中的控制流存在微妙的差别。例如,如果 Wonderland 使用 MVC,控制流将是:
- 在客户机 C2 上与
Cell 的用户交互引起在服务器上的 CellGLO 中的状态更改。
- 客户机 C2 直接向服务器发送消息。所发送的消息描述“更改种类”和
CellGLO 的最新状态
- 服务器识别适当的
CellGLO。
CellGLO 更新其状态
- 服务器向所有客户机广播
Cell 通道上的消息。所发送的消息描述“更改种类”。
- 每个客户机接收消息并请求
CellGLO 的状态。
- 每个客户机更新
Cell 的状态(如有必要)和外观。

这种差别的原因是因为性能不同。用户尽快得到外观更改的通知是很重要的(也就是在通知服务器前)。
7. 重要代码流序列的汇总
针对 Project Wonderland 的两个方面,本部分总结了代码中的控制流:当一个客户机第一次连接时、当网络虚拟角色移动或改变观察方向时。
7.1. 客户机首次连接时的代码序列
Main 类创建了主窗口并向主窗口添加 LG3D Lg3dConnector 类的实例。这样依次启动 LG3D 显示服务器。初始化后,L3GD 显示服务器初始化宇宙、窗口系统、视图,创建根场景图,初始化场景管理器。
- Wonderland Scene Manager 创建类
WorldController 的新实例,该实例创建 ChannelController 和 UserCellCache 类的新实例。
- 在 LG3D 初始化的完成后,
StartupListener.startupComplete() 方法被调用,从而提升 Wonderland Login 对话框。
- 当用户登录确认后,到 Darkstar 服务器的连接被打开。客户机等待通过调用
WonderlandClientListener.loggedIn() 而同步发送的登录确认。
- 当登录后,客户机通过客户机—服务器通信机制使用关于网络虚拟角色的信息(例如可视化模型),向 Darkstar 服务器发送
AvatarSetupMessage 消息。
- 当登录结束后,Wonderland 客户机的主窗口被启用。
7.2 服务器初始化后的代码序列
WonderlandBoot.initialize() 方法创建了类 ServerManagerGLO、UserManager、MasterCellCacheGLO 和 ChecksumManagerGLO 的新实例。它还创建了被称作 USER_CHANGE 的发布/ 订阅通道。
MasterCellCacheGLO 实例创建了类 WorldRootCellGLO 的新实例。该过程依次调用在世界中创建所有单元的 WorldRootCellGLO.buildWorld() 方法。
- 类
WonderlandSessionListener 的实例接收来自客户机的 AvatarSetupMessage 消息。创建类 UserGLO 的新实例。
UserGLO 实例创建新的 AvatarCellGLO 实例,该实例依次创建新的 UserCellCacheGLO 实例。
UserCellCacheGLO 实例创建称作 user+CELL_CACHE(其中 “user”是用户的名称)的发布/ 订阅通道。
AvatarCelLGLO 实例随后打开称作 AVATAR_P2P 和称作“user+AVATAR_TILE”(其中 “user”是用户的名称)的发布/ 订阅通道。
WonderlandSessionListener 随后将客户机加入到 USER_CHANGE 通道并向客户机发送 UserChangedMessage(USER_ADDED) 消息。然后调用 UserGLO 实例的 login() 方法。
UserGLO.login() 方法向联系用户 ID 和用户对象引用的散列影射表添加用户的引用。并调用 AvatarCellGLO.login()。
AvatarCellGLO.login() 向 MasterCellCacheGLO 实例添加 AvatarCellGLO 的接收实例。之后,调用将客户机加入到 user+CELL_CACHE 通道的 UserCellCacheGLO.login() 并将其本身(UserCellCacheGLO)添加到 MasterCellCacheGLO 实例中,向称作 “user+CELL_CACHE” 的通道发送 CellHierarchyMessage(LOAD_CELL) 消息,然后,向称作 “user+CELL_CACHE” 的通道发送 CellHiearchyMessage(SET_WORLD_ROOT) 消息。
- 然后,
UserGLO.login() 方法调用计算可视单元列表的 UserCellCacheGLO.avatarCellMoved() 方法,并为称作“user+CELL_CACHE” 的通道上的所有可视单元发送 CellHierarchyMessage(LOAD_CELL) 消息。它还为称作 “user+CELL_CACHE” 通道上的所有新的不可视单元发送 CellHierarchyMessage(DELETE_CELL or CELL_INACTIVE) 消息。
- 最后,
WonderlandSessionListener 通过 USER_CHANGE 通道向每个客户机发送 UserChangedMessage 消息,以便通知新客户机。
7.2 当网络虚拟角色移动时的代码序列
在这个代码序列中,我们考虑到当用户按其中一个鼠标控制键来移动网络虚拟角色的位置时的情况。
- 由类
org.jdesktop.lg3d.wonderland.scenemanager.WalkBehavior 的 keyPressed() 方法处理键的事件。内部类 Stimulus 的实例状态被更新以便反映键的状态变化。
org.jdesktop.lg3d.wonderland.scenemanager.AvatarControlBehavior 类实例的 scheduleUpdate() 方法被调用以便告知对网络虚拟角色状态进行下一个更新。
AvatarControlBehavior.processStimulus() 方法唤醒并调用更新网络虚拟角色位置状态的 WalkBehavior.updateState() 方法。
- 冲突检测在 Avatar 新位置的基础上执行。摄像头的位置随后被更新。
- 界面
UserMotionListener 的所有实现通过 userMoved() 方法通知网络虚拟角色的新位置。UserMotionListener 实现的一个示例是类 UserCellCache,它的 userMoved() 方法调用 UserCellCache.updateCells() 方法。(类 AvatarCell)是界面实现的另一个示例。
updateCells() 方法在用户查看平截头体基础上计算可视单元。该方法更新每个单元的状态和可视单元的列表。对于所有的可视单元,该方法更新网络虚拟角色当前占据的单元列表。这样造成类型 CellEnterEvent 和 CellExitEvent 的事件被发送到适当的单元(在 LG3D 事件框架内)。(此时,AvatarCellMessage 类型 CELL_ENTER 和 CELL_EXIT 既不被发送到服务器中,服务器对于所接收到的这些事件也不采取任何措施。
AvatarCell.userMoved() 通过 AVATAR_TILE 通道向服务器发送 AvatarCellMessage(AVATAR_MOVE & CELLMOVE)。网络虚拟角色的位置在服务器上得到更新且 UserCellCacheGLO 被要求对自身进行重新验证(计算可视的和不可视的单元列表、更新其状态并将此类信息通知给客户机)。
8. 未来文档工作
本软件架构文档只详细说明了 Wonderland 客户机服务器软件的基本内容。关于网络虚拟角色及其配置的文档、控制行为、应用程序共享和音频的信息还需继续补充。
Project Wonderland 项目地址:https://lg3d-wonderland.dev.java.net/
|