7.9 KiB
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ủaNetworkManager, 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ệ:
- Marshal UI chat qua
EnqueueChatUI→SynchronizationContext→ChatThreadDispatcher(cùng hàng đợi vớiChatSystemlUI/MiniChatUI). - Cache tên TMP sprite asset trong dữ liệu SO (
TmpSpriteAssetName) đểTryGetSpritekhông gọiGetName()từ luồng lạ.
- Marshal UI chat qua
2. Luồng mạng → hiển thị (Network → display)
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
GameSession.Contextđược gán trongUnityGameSession.AwaketừSynchronizationContext.Current(main thread).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).
- Nếu
Vì sao hai bước (Context rồi ChatThreadDispatcher)?
- Luồng mạng không được gọi
ChatThreadDispatcher.Instancetrực tiếp nếu singleton chưa tồn tại:MonoSingletoncó thểFindFirstObjectByType/ tạoGameObject— đó là API chỉ an toàn trên main thread. Context.Postđưa lệnh lên main thread; từ đóInstance.Postan toàn và cùng queue với các UI chat khác đã dùngChatThreadDispatcher.
Khởi tạo sớm singleton (warmup):
File: Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs
- Trong
Awake, sau khi gánGameSession.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.ShouldBlockByLevelAUICommon.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êmpProtocolvào pending trên luồng xử lý gói- Nhánh battle:
OnBattleChatMessagecó 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 copystrTemp,channel,emotionvào closure).EC_Game.GetGameRun().AddChatMessage(...)(player có tên, NPC, echo local).CECGameUIMan.AddChatMessage,ConvertWireBodyForChatPanel,ConvertWireBodyForHeadBubble, bubbleEventChatMessageOnTopPlayer.
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ùngChatThreadDispatcher.Instance.Posttừ main; luồng chat từGameSessiongiờ đồng nhất queue sau khi quaContext.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ọiCECGameUIMan.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à cachestringtrê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ớiChatSystemlUI/MiniChatUItrừ 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.