139 lines
7.9 KiB
Markdown
139 lines
7.9 KiB
Markdown
# Chat System Flow — Luồng hệ thống chat
|
|
|
|
Tài liệu tổng hợp luồng chat từ mạng → UI (TMP), đồng bộ main thread, và xử lý emoji — **English summary inline where useful**.
|
|
|
|
---
|
|
|
|
## 1. Tổng quan (Overview)
|
|
|
|
- Gói chat S2C được xử lý trong `GameSession` (luồng nhận của `NetworkManager`, **không phải** main thread của Unity).
|
|
- Mọi thao tác dùng API Unity (`UnityEngine.Object.name`, TMP, `EventBus` → UI, v.v.) phải chạy trên **main thread**.
|
|
- Hai lớp bảo vệ:
|
|
1. **Marshal UI chat** qua `EnqueueChatUI` → `SynchronizationContext` → `ChatThreadDispatcher` (cùng hàng đợi với `ChatSystemlUI` / `MiniChatUI`).
|
|
2. **Cache tên TMP sprite asset** trong dữ liệu SO (`TmpSpriteAssetName`) để `TryGetSprite` không gọi `GetName()` từ luồng lạ.
|
|
|
|
---
|
|
|
|
## 2. Luồng mạng → hiển thị (Network → display)
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
subgraph net [Network thread]
|
|
NM[NetworkManager.ProcessReceivedData]
|
|
GS[GameSession.OnProtocolReceived]
|
|
H[Chat handlers decode + PolicyResolver]
|
|
end
|
|
subgraph main [Main thread]
|
|
Ctx[SynchronizationContext.Post]
|
|
CTD[ChatThreadDispatcher.Update]
|
|
ACM[AddChatMessage / EventBus]
|
|
TMP[TMP / Chat panel]
|
|
end
|
|
NM --> GS --> H
|
|
H -->|EnqueueChatUI| Ctx --> CTD --> ACM --> TMP
|
|
```
|
|
|
|
### 2.1 Điểm vào gói chat (Protocol entry points)
|
|
|
|
| Gói / hàm | File | Ghi chú |
|
|
|-----------|------|---------|
|
|
| `PROTOCOL_CHATMESSAGE` | `GameSession.OnPrtcChatMessage` | Decode + filter + `PolicyResolver` có thể **giữ trên luồng mạng** khi cần `return false` (pending tên). |
|
|
| `PROTOCOL_WORLDCHAT` | `GameSession.OnPrtcWorldChat` | Copy chuỗi → `EnqueueChatUI` → `OnPrtcWorldChat_Apply`. |
|
|
| `PROTOCOL_PRIVATECHAT` | `GameSession.OnPrtcPrivateChat` | Copy chuỗi → `EnqueueChatUI` → `OnPrtcPrivateChat_Apply`. |
|
|
| Echo local sau gửi | `GameSession.SendChatData` | Sau `SendProtocol` → `EnqueueChatUI` → `SendChatData_ApplyLocalEchoToChatPanel`. |
|
|
|
|
### 2.2 `EnqueueChatUI` — cơ chế đồng bộ (Marshaling)
|
|
|
|
**File:** `Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs`
|
|
|
|
1. **`GameSession.Context`** được gán trong `UnityGameSession.Awake` từ `SynchronizationContext.Current` (main thread).
|
|
2. `EnqueueChatUI(action)`:
|
|
- Nếu `Context == null` → log lỗi, **không** chạy UI (tránh hỏng thread).
|
|
- Ngược lại: `Context.Post(_ => ChatThreadDispatcher.Instance.Post(action), null)`.
|
|
|
|
**Vì sao hai bước (Context rồi ChatThreadDispatcher)?**
|
|
|
|
- Luồng mạng **không** được gọi `ChatThreadDispatcher.Instance` trực tiếp nếu singleton chưa tồn tại: `MonoSingleton` có thể `FindFirstObjectByType` / tạo `GameObject` — đó là API chỉ an toàn trên main thread.
|
|
- `Context.Post` đưa lệnh lên main thread; từ đó `Instance.Post` an toàn và **cùng queue** với các UI chat khác đã dùng `ChatThreadDispatcher`.
|
|
|
|
**Khởi tạo sớm singleton (warmup):**
|
|
|
|
**File:** `Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs`
|
|
|
|
- Trong `Awake`, sau khi gán `GameSession.Context`: `_ = ChatThreadDispatcher.Instance;`
|
|
— Đảm bảo dispatcher đã tạo trên main **trước** khi luồng mạng bắt đầu post.
|
|
|
|
### 2.3 Việc vẫn chạy trên luồng mạng (Stays on network thread)
|
|
|
|
Cần giữ **đồng bộ** để `return false` / pending đúng nghĩa:
|
|
|
|
- `Chat_GameSession.ShouldBlockByLevel`
|
|
- `AUICommon.FilterInvalidTags` (sau khi decode; nếu sau này có Unity API trong đây cần xem lại)
|
|
- `Chat_GameSession.PolicyResolver` — khi trả về `false` (chờ tên / policy), thêm `pProtocol` vào pending trên luồng xử lý gói
|
|
- Nhánh battle: `OnBattleChatMessage` có thể `return false` (giữ nguyên logic cũ)
|
|
|
|
### 2.4 Việc chạy trên main thread (qua `EnqueueChatUI`)
|
|
|
|
- `EventBus.Publish(ChatMessageEvent(...))` cho system / broadcast / auction (sau khi copy `strTemp`, `channel`, `emotion` vào closure).
|
|
- `EC_Game.GetGameRun().AddChatMessage(...)` (player có tên, NPC, echo local).
|
|
- `CECGameUIMan.AddChatMessage`, `ConvertWireBodyForChatPanel`, `ConvertWireBodyForHeadBubble`, bubble `EventChatMessageOnTopPlayer`.
|
|
|
|
Các helper apply: `OnPrtcWorldChat_Apply`, `OnPrtcPrivateChat_Apply`, `OnPrtcChatMessage_ApplyNpcBranch`, `SendChatData_ApplyLocalEchoToChatPanel`.
|
|
|
|
### 2.5 `ChatThreadDispatcher` — vai trò (Role)
|
|
|
|
**File:** `Assets/PerfectWorld/Scripts/Chat/UI/ChatThreadDispatcher.cs`
|
|
|
|
- `ConcurrentQueue<Action>` + `Update()` dequeue trên main thread.
|
|
- Các màn như `ChatSystemlUI`, `MiniChatUI`, `UIPlayer` đã dùng `ChatThreadDispatcher.Instance.Post` từ **main**; luồng chat từ `GameSession` giờ **đồng nhất** queue sau khi qua `Context.Post`.
|
|
|
|
---
|
|
|
|
## 3. Wire → TMP trong game (In-game pipeline)
|
|
|
|
- **`CECGameUIMan.AddChatMessage`** (`EC_GameUIMan.cs`): filter kênh emotion → bad words (kênh player) → `ChatEmotionDisplayPipeline.ConvertInlineItemsToTmp` → màu / kênh → `EventBus` (`ChatMessageEvent`) và bubble nếu kênh hỗ trợ.
|
|
- **`ConvertWireBodyForChatPanel`**: gọi `CECGameUIMan.ConvertWireChatBodyForDisplay` — **chỉ** nên gọi từ code đã chạy trên main (hiện được gọi trong lambda đã `EnqueueChatUI`).
|
|
|
|
---
|
|
|
|
## 4. Emoji / TMP — tránh `GetName()` ngoài main thread
|
|
|
|
**Vấn đề:** `TMP_SpriteAsset.name` (và mọi `UnityEngine.Object.name`) gọi native `GetName()` — **chỉ main thread**. Trước đây `EmotionLibrarySpriteMap.TryGetSprite` đọc `set.TmpSpriteAsset.name` khi xử lý chat, dễ lỗi nếu vẫn còn path nào đó gọi pipeline ngoài main.
|
|
|
|
**Cách xử lý:**
|
|
|
|
| Thành phần | File | Mô tả |
|
|
|------------|------|--------|
|
|
| `EmotionSetSnapshot.TmpSpriteAssetName` | `EmotionLibrarySO.cs` | Chuỗi cache khớp `TmpSpriteAsset.name`. |
|
|
| `EmotionLibrarySO.SyncCachedTmpSpriteAssetNamesFromObjects()` | cùng file | Ghi cache từ reference (chỉ gọi trên main / editor `OnValidate`). |
|
|
| `EmotionLibrarySpriteMap.EnsureCachedTmpSpriteAssetNames()` | `EmotionLibrarySpriteMap.cs` | Gọi sync trên library. |
|
|
| `CECGameUIMan.SetEmotionSpriteMap` | `EC_GameUIMan.cs` | Nếu map là `EmotionLibrarySpriteMap` → `EnsureCachedTmpSpriteAssetNames()` trước khi gán pipeline. |
|
|
| `ChatInputHandler.Awake` | `ChatInputHandler.cs` | `_spriteMap?.EnsureCachedTmpSpriteAssetNames()` nếu map gán riêng trên input. |
|
|
|
|
`TryGetSprite` chỉ đọc `set.TmpSpriteAssetName`, **không** đọc `TmpSpriteAsset.name` tại runtime lookup.
|
|
|
|
---
|
|
|
|
## 5. Đường dẫn file tham chiếu nhanh (Quick file index)
|
|
|
|
| Nội dung | Đường dẫn |
|
|
|----------|-----------|
|
|
| Marshal chat UI, handlers | `Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs` |
|
|
| Gán `Context`, warmup dispatcher | `Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs` |
|
|
| Hàng đợi chat UI | `Assets/PerfectWorld/Scripts/Chat/UI/ChatThreadDispatcher.cs` |
|
|
| AddChatMessage + pipeline TMP | `Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs` |
|
|
| Policy / pending chat | `Assets/PerfectWorld/Scripts/Chat/Chat_GameSession.cs` |
|
|
| SO emotion + cache tên | `Assets/PerfectWorld/Scripts/Chat/EmotionData/EmotionLibrarySO.cs`, `EmotionLibrarySpriteMap.cs` |
|
|
|
|
---
|
|
|
|
## 6. Lưu ý vận hành (Operational notes)
|
|
|
|
- **Độ trễ:** Tin có thể tới UI chậm thêm khoảng **một vài frame** so với xử lý trực tiếp trên main (Context post + queue `ChatThreadDispatcher` + `Update`).
|
|
- **Mobile / IL2CPP:** `SynchronizationContext`, `ConcurrentQueue`, và cache `string` trên SO là an toàn cho build thiết bị.
|
|
- **Nếu rút gọn một hop:** Có thể chỉ `Context.Post(action)` trực tiếp (bỏ `ChatThreadDispatcher` ở giữa) để giảm trễ, nhưng sẽ **không** cùng queue với `ChatSystemlUI` / `MiniChatUI` trừ khi refactor thêm.
|
|
|
|
---
|
|
|
|
*Tài liệu phản ánh trạng thái codebase tại thời điểm thêm luồng `EnqueueChatUI` + cache `TmpSpriteAssetName`. Cập nhật khi thay đổi handler hoặc kiến trúc thread.*
|