Files
2026-04-13 14:05:43 +07:00

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ủ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 EnqueueChatUISynchronizationContextChatThreadDispatcher (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)

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 → EnqueueChatUIOnPrtcWorldChat_Apply.
PROTOCOL_PRIVATECHAT GameSession.OnPrtcPrivateChat Copy chuỗi → EnqueueChatUIOnPrtcPrivateChat_Apply.
Echo local sau gửi GameSession.SendChatData Sau SendProtocolEnqueueChatUISendChatData_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.ConvertWireChatBodyForDisplaychỉ 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à EmotionLibrarySpriteMapEnsureCachedTmpSpriteAssetNames() 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.