# VRTheater 网络通信协议文档

## 协议概述

本文档详细说明 VRTheater 系统 Unity 客户端与 Java 后端之间的网络通信协议。该协议采用分层设计，支持不同优先级消息传输，并使用 JSON 作为序列化格式。系统采用分层的消息传输机制，静态层、上下文层和动态层的数据通过单独的消息进行传输和广播。

## 消息封装结构

所有网络消息使用 `NetworkMessage<T>` 泛型结构进行封装，确保消息传输的一致性和可扩展性。

### 消息优先级 (MessagePriority)

```java
public enum MessagePriority {
    Low(0),    // 低频：配置、状态等
    Medium(1), // 中频：场景、环境等
    High(2)    // 高频：位置、动作等
    
    private final int value;
    
    MessagePriority(int value) {
        this.value = value;
    }
    
    public int getValue() {
        return value;
    }
    
    public static MessagePriority fromValue(int value) {
        for (MessagePriority priority : values()) {
            if (priority.getValue() == value) {
                return priority;
            }
        }
        return Low; // 默认值
    }
}
```

### 消息结构

```java
public class NetworkMessage<T> {
    private int priority;     // 消息优先级
    private String type;      // 消息类型
    private T data;           // 消息数据
    
    // 构造函数和访问器略
}
```

### 非泛型消息结构 (向后兼容)

```java
public class NetworkMessage {
    private int priority;
    private String type;
    private Object data;
    
    // 构造函数和访问器略
}
```

## 数据结构定义

消息数据部分 (`data` 字段) 根据消息类型不同，采用不同的数据结构。系统采用三层数据模型：

### 1. 静态层 (StaticInfo)

包含变化频率低的用户基本信息，通常使用低优先级 (Low, 0) 传输。

```java
public class StaticInfo {
    private String id;    // 用户唯一标识符
    private String name;  // 用户名称
    private int sex;      // 用户性别
    private String role;  // 用户角色
    
    // 构造函数和访问器略
}
```

### 2. 上下文层 (ContextualInfo)

包含用户当前场景和进度信息，中频率更新，通常使用中优先级 (Medium, 1) 传输。

```java
public class ContextualInfo {
    private String id;              // 用户唯一标识符
    private String state;           // 状态机当前状态
    private String groupId;         // 小组标识符
    private int level;              // 当前关卡编号
    private float timeline;         // 当前关卡进度(时间点)
    private float timelineDuration; // 当前关卡总时长
    
    // 构造函数和访问器略
}
```

### 3. 动态层 (DynamicInfo)

包含用户实时位置和状态信息，高频率更新，通常使用高优先级 (High, 2) 传输。

```java
public class DynamicInfo {
    private String id;       // 用户唯一标识符
    
    // 位置坐标
    private float posX;
    private float posY;
    private float posZ;
    
    // 旋转角度
    private float rotX;
    private float rotY;
    private float rotZ;
    
    // 手部位置
    private float handPosX;
    private float handPosY;
    private float handPosZ;
    
    // 手部旋转
    private float handRotX;
    private float handRotY;
    private float handRotZ;
    
    // 构造函数和访问器略
}
```

## 通信模式与序列化

### JSON序列化格式

所有消息都通过JSON序列化传输。无论是客户端到服务器还是服务器到客户端，消息结构保持一致。

#### 客户端到服务器消息

**动态层示例**:

```json
{
  "priority": 2,
  "type": "DynamicInfo",
  "data": {
    "id": "user123",
    "posX": 10.5,
    "posY": 1.8,
    "posZ": -3.2,
    "rotX": 0.0,
    "rotY": 45.0,
    "rotZ": 0.0,
    "handPosX": 10.8,
    "handPosY": 2.1,
    "handPosZ": -3.0,
    "handRotX": 10.0,
    "handRotY": 20.0,
    "handRotZ": 30.0
  }
}
```

**上下文层示例**:

```json
{
  "priority": 1,
  "type": "ContextualInfo",
  "data": {
    "id": "user123",
    "state": "Playing",
    "groupId": "group1",
    "level": 2,
    "timeline": 45.5,
    "timelineDuration": 120.0
  }
}
```

**静态层示例**:

```json
{
  "priority": 0,
  "type": "StaticInfo",
  "data": {
    "id": "user123",
    "name": "用户1",
    "sex": 0,
    "role": "学生"
  }
}
```

#### 服务器到客户端消息

服务器接收来自多个客户端的更新，对同类型的数据进行聚合并分别广播。每种数据类型（静态层、上下文层、动态层）作为独立消息发送。

**静态层广播**:

```json
{
  "priority": 0,
  "type": "StaticInfo",
  "data": [
    {
      "id": "user123",
      "name": "用户1",
      "sex": 0,
      "role": "学生"
    },
    {
      "id": "user456",
      "name": "用户2",
      "sex": 1,
      "role": "教师"
    }
  ]
}
```

**上下文层广播**:

```json
{
  "priority": 1,
  "type": "ContextualInfo",
  "data": [
    {
      "id": "user123",
      "state": "Playing",
      "groupId": "group1",
      "level": 2,
      "timeline": 45.5,
      "timelineDuration": 120.0
    },
    {
      "id": "user456",
      "state": "Observing",
      "groupId": "group1",
      "level": 2,
      "timeline": 45.5,
      "timelineDuration": 120.0
    }
  ]
}
```

**动态层广播**:

```json
{
  "priority": 2,
  "type": "DynamicInfo",
  "data": [
    {
      "id": "user123",
      "posX": 10.5,
      "posY": 1.8,
      "posZ": -3.2,
      "rotX": 0.0,
      "rotY": 45.0,
      "rotZ": 0.0,
      "handPosX": 10.8,
      "handPosY": 2.1,
      "handPosZ": -3.0,
      "handRotX": 10.0,
      "handRotY": 20.0,
      "handRotZ": 30.0
    },
    {
      "id": "user456",
      "posX": -5.0,
      "posY": 1.7,
      "posZ": 2.5,
      "rotX": 0.0,
      "rotY": 180.0,
      "rotZ": 0.0,
      "handPosX": -4.7,
      "handPosY": 2.0,
      "handPosZ": 2.3,
      "handRotX": 15.0,
      "handRotY": 175.0,
      "handRotZ": 5.0
    }
  ]
}
```

### 通信频率

1. **静态层**：低频率，仅在用户连接和信息变更时发送（优先级0）
2. **上下文层**：中频率，约1-2次/秒（优先级1）
3. **动态层**：高频率，实时更新（约10-30次/秒）（优先级2）

每种类型的数据通过单独的消息进行传输，便于客户端和服务器分别处理不同优先级的数据。

## 消息类型定义

系统定义了多种消息类型 (`type` 字段)，用于区分不同的通信场景：

| 消息类型             | 描述        | 数据结构           | 优先级 | 方向       |
| ---------------- | --------- | -------------- | --- | -------- |
| `StaticInfo`     | 单个静态信息更新  | StaticInfo     | 0   | 客户端->服务器 |
| `ContextualInfo` | 单个上下文信息更新 | ContextualInfo | 1   | 客户端->服务器 |
| `DynamicInfo`    | 单个动态信息更新  | DynamicInfo    | 2   | 客户端->服务器 |
| `StaticInfo`     | 静态信息列表广播  | List           | 0   | 服务器->客户端 |
| `ContextualInfo` | 上下文信息列表广播 | List           | 1   | 服务器->客户端 |
| `DynamicInfo`    | 动态信息列表广播  | List           | 2   | 服务器->客户端 |


## Java后端实现建议

### 实体类定义

```java
package com.vrtheater.model;

import java.util.ArrayList;
import java.util.List;

// 网络消息封装
public class NetworkMessage<T> {
    private int priority;
    private String type;
    private T data;
    
    // 构造函数和访问器略
}

// 静态层信息
public class StaticInfo {
    private String id;
    private String name;
    private int sex;
    private String role;
    
    // 构造函数和访问器略
}

// 上下文层信息
public class ContextualInfo {
    private String id;
    private String state;
    private String groupId;
    private int level;
    private float timeline;
    private float timelineDuration;
    
    // 构造函数和访问器略
}

// 动态层信息
public class DynamicInfo {
    private String id;
    private float posX, posY, posZ;
    private float rotX, rotY, rotZ;
    private float handPosX, handPosY, handPosZ;
    private float handRotX, handRotY, handRotZ;
    
    // 构造函数和访问器略
}
```

### 数据管理器实现

```java
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

// 用户数据管理器
public class UserDataManager {
    // 存储当前所有用户数据
    private ConcurrentHashMap<String, StaticInfo> staticData = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, ContextualInfo> contextualData = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, DynamicInfo> dynamicData = new ConcurrentHashMap<>();
    
    // 更新时间戳
    private ConcurrentHashMap<String, Long> staticLastUpdate = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, Long> contextualLastUpdate = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, Long> dynamicLastUpdate = new ConcurrentHashMap<>();
    
    private ObjectMapper mapper = new ObjectMapper();
    
    // 更新静态数据
    public synchronized void updateStaticInfo(StaticInfo info) {
        String userId = info.getId();
        staticData.put(userId, info);
        staticLastUpdate.put(userId, System.currentTimeMillis());
    }
    
    // 更新上下文数据
    public synchronized void updateContextualInfo(ContextualInfo info) {
        String userId = info.getId();
        contextualData.put(userId, info);
        contextualLastUpdate.put(userId, System.currentTimeMillis());
    }
    
    // 更新动态数据
    public synchronized void updateDynamicInfo(DynamicInfo info) {
        String userId = info.getId();
        dynamicData.put(userId, info);
        dynamicLastUpdate.put(userId, System.currentTimeMillis());
    }
    
    // 移除用户数据
    public synchronized void removeUser(String userId) {
        staticData.remove(userId);
        contextualData.remove(userId);
        dynamicData.remove(userId);
        
        staticLastUpdate.remove(userId);
        contextualLastUpdate.remove(userId);
        dynamicLastUpdate.remove(userId);
    }
    
    // 获取静态数据列表 (可选增量更新)
    public synchronized List<StaticInfo> getStaticInfoList(boolean incrementalOnly, long timeThreshold) {
        List<StaticInfo> result = new ArrayList<>();
        long currentTime = System.currentTimeMillis();
        
        for (String userId : staticData.keySet()) {
            if (!incrementalOnly || 
                (staticLastUpdate.get(userId) != null && 
                 currentTime - staticLastUpdate.get(userId) < timeThreshold)) {
                result.add(staticData.get(userId));
            }
        }
        
        return result;
    }
    
    // 获取上下文数据列表 (可选增量更新)
    public synchronized List<ContextualInfo> getContextualInfoList(boolean incrementalOnly, long timeThreshold) {
        List<ContextualInfo> result = new ArrayList<>();
        long currentTime = System.currentTimeMillis();
        
        for (String userId : contextualData.keySet()) {
            if (!incrementalOnly || 
                (contextualLastUpdate.get(userId) != null && 
                 currentTime - contextualLastUpdate.get(userId) < timeThreshold)) {
                result.add(contextualData.get(userId));
            }
        }
        
        return result;
    }
    
    // 获取动态数据列表 (可选增量更新)
    public synchronized List<DynamicInfo> getDynamicInfoList(boolean incrementalOnly, long timeThreshold) {
        List<DynamicInfo> result = new ArrayList<>();
        long currentTime = System.currentTimeMillis();
        
        for (String userId : dynamicData.keySet()) {
            if (!incrementalOnly || 
                (dynamicLastUpdate.get(userId) != null && 
                 currentTime - dynamicLastUpdate.get(userId) < timeThreshold)) {
                result.add(dynamicData.get(userId));
            }
        }
        
        return result;
    }
    
    // 创建广播消息
    public String createStaticInfoListMessage(List<StaticInfo> infoList) {
        try {
            NetworkMessage<List<StaticInfo>> message = 
                new NetworkMessage<>("StaticInfoList", infoList, 0);
            return mapper.writeValueAsString(message);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    public String createContextualInfoListMessage(List<ContextualInfo> infoList) {
        try {
            NetworkMessage<List<ContextualInfo>> message = 
                new NetworkMessage<>("ContextualInfoList", infoList, 1);
            return mapper.writeValueAsString(message);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    public String createDynamicInfoListMessage(List<DynamicInfo> infoList) {
        try {
            NetworkMessage<List<DynamicInfo>> message = 
                new NetworkMessage<>("DynamicInfoList", infoList, 2);
            return mapper.writeValueAsString(message);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
```

### 消息处理器实现

```java
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

// 消息处理器
public class MessageHandler {
    private ObjectMapper mapper = new ObjectMapper();
    private UserDataManager dataManager;
    private WebSocketManager websocketManager;
    
    // 更新调度时间间隔 (毫秒)
    private static final long STATIC_UPDATE_INTERVAL = 10000;    // 10秒
    private static final long CONTEXTUAL_UPDATE_INTERVAL = 1000; // 1秒
    private static final long DYNAMIC_UPDATE_INTERVAL = 100;     // 0.1秒
    
    // 最后一次广播时间
    private long lastStaticBroadcast = 0;
    private long lastContextualBroadcast = 0;
    private long lastDynamicBroadcast = 0;
    
    public MessageHandler(UserDataManager dataManager, WebSocketManager websocketManager) {
        this.dataManager = dataManager;
        this.websocketManager = websocketManager;
    }
    
    // 处理接收到的消息
    public void handleMessage(String messageJson, String sessionId) {
        try {
            // 解析消息
            JsonNode node = mapper.readTree(messageJson);
            int priority = node.get("priority").asInt();
            String type = node.get("type").asText();
            
            switch (type) {
                case "StaticInfo":
                    StaticInfo staticInfo = mapper.treeToValue(node.get("data"), StaticInfo.class);
                    handleStaticInfo(staticInfo, sessionId);
                    break;
                    
                case "ContextualInfo":
                    ContextualInfo contextInfo = mapper.treeToValue(node.get("data"), ContextualInfo.class);
                    handleContextualInfo(contextInfo, sessionId);
                    break;
                    
                case "DynamicInfo":
                    DynamicInfo dynamicInfo = mapper.treeToValue(node.get("data"), DynamicInfo.class);
                    handleDynamicInfo(dynamicInfo, sessionId);
                    break;
                    
                case "Connect":
                    handleConnect(node.get("data").asText(), sessionId);
                    break;
                    
                case "Disconnect":
                    handleDisconnect(node.get("data").asText());
                    break;
                    
                default:
                    System.out.println("未知消息类型: " + type);
            }
            
            // 检查是否需要广播更新
            long currentTime = System.currentTimeMillis();
            
            // 静态数据更新 (低频)
            if (currentTime - lastStaticBroadcast > STATIC_UPDATE_INTERVAL) {
                broadcastStaticUpdates();
                lastStaticBroadcast = currentTime;
            }
            
            // 上下文数据更新 (中频)
            if (currentTime - lastContextualBroadcast > CONTEXTUAL_UPDATE_INTERVAL) {
                broadcastContextualUpdates();
                lastContextualBroadcast = currentTime;
            }
            
            // 动态数据更新 (高频)
            if (currentTime - lastDynamicBroadcast > DYNAMIC_UPDATE_INTERVAL) {
                broadcastDynamicUpdates();
                lastDynamicBroadcast = currentTime;
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    // 处理静态信息更新
    private void handleStaticInfo(StaticInfo info, String sessionId) {
        dataManager.updateStaticInfo(info);
    }
    
    // 处理上下文信息更新
    private void handleContextualInfo(ContextualInfo info, String sessionId) {
        dataManager.updateContextualInfo(info);
    }
    
    // 处理动态信息更新
    private void handleDynamicInfo(DynamicInfo info, String sessionId) {
        dataManager.updateDynamicInfo(info);
    }
    
    // 处理连接请求
    private void handleConnect(String userId, String sessionId) {
        websocketManager.registerUser(userId, sessionId);
        
        // 发送当前完整数据给新连接的用户
        sendFullDataToUser(userId);
    }
    
    // 处理断开连接
    private void handleDisconnect(String userId) {
        websocketManager.unregisterUser(userId);
        dataManager.removeUser(userId);
        
        // 广播用户离开信息
        broadcastStaticUpdates();
    }
    
    // 向单个用户发送完整数据
    private void sendFullDataToUser(String userId) {
        // 静态数据
        List<StaticInfo> staticList = dataManager.getStaticInfoList(false, 0);
        String staticMessage = dataManager.createStaticInfoListMessage(staticList);
        websocketManager.sendToUser(userId, staticMessage);
        
        // 上下文数据
        List<ContextualInfo> contextualList = dataManager.getContextualInfoList(false, 0);
        String contextualMessage = dataManager.createContextualInfoListMessage(contextualList);
        websocketManager.sendToUser(userId, contextualMessage);
        
        // 动态数据
        List<DynamicInfo> dynamicList = dataManager.getDynamicInfoList(false, 0);
        String dynamicMessage = dataManager.createDynamicInfoListMessage(dynamicList);
        websocketManager.sendToUser(userId, dynamicMessage);
    }
    
    // 广播静态数据更新
    private void broadcastStaticUpdates() {
        // 获取所有静态数据
        List<StaticInfo> staticList = dataManager.getStaticInfoList(false, 0);
        if (!staticList.isEmpty()) {
            String message = dataManager.createStaticInfoListMessage(staticList);
            websocketManager.broadcastToAll(message);
        }
    }
    
    // 广播上下文数据更新
    private void broadcastContextualUpdates() {
        // 获取增量上下文数据 (最近5秒更新的)
        List<ContextualInfo> contextualList = dataManager.getContextualInfoList(true, 5000);
        if (!contextualList.isEmpty()) {
            String message = dataManager.createContextualInfoListMessage(contextualList);
            websocketManager.broadcastToAll(message);
        }
    }
    
    // 广播动态数据更新
    private void broadcastDynamicUpdates() {
        // 获取增量动态数据 (最近200毫秒更新的)
        List<DynamicInfo> dynamicList = dataManager.getDynamicInfoList(true, 200);
        if (!dynamicList.isEmpty()) {
            String message = dataManager.createDynamicInfoListMessage(dynamicList);
            websocketManager.broadcastToAll(message);
        }
    }
}
```
