完成阶段 4
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-03-26
|
||||
@@ -0,0 +1,55 @@
|
||||
## Context
|
||||
|
||||
`KcpTransport` already keeps socket receive and KCP update work on background tasks, but `MessageManager` subscribes directly to `ITransport.OnReceive` and immediately parses and dispatches handlers on whichever thread raised the callback. In the current project, several registered handlers mutate Unity-facing state through `MasterManager` and UI objects inside `NetworkManager`, so the absence of an explicit main-thread handoff is the main architecture gap left after stages two and three.
|
||||
|
||||
The project already has a Unity lifecycle entry point in `Assets/Scripts/NetworkManager.cs`, and `CodeX-TODO.md` explicitly recommends adding `Assets/Scripts/Network/NetworkApplication/MainThreadNetworkDispatcher.cs`. Stage four should therefore formalize a queueing boundary without changing the reliable transport contract or mixing in later connection-state concerns.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Ensure transport receive callbacks never execute message handlers inline on background threads.
|
||||
- Introduce a thread-safe queue between transport receive and business handler execution.
|
||||
- Make Unity main thread code explicitly responsible for draining queued network messages and invoking handlers.
|
||||
- Preserve the existing `IMessageHandler` / `MessageManager.RegisterHandler` programming model so stage four remains a structural refactor rather than a gameplay rewrite.
|
||||
|
||||
**Non-Goals:**
|
||||
- Redesign KCP session management, heartbeats, reconnection, or login state handling.
|
||||
- Introduce QoS splitting for `PlayerInput` / `PlayerState`.
|
||||
- Replace the current handler registration model with a larger event bus or ECS messaging framework.
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Add a dedicated main-thread dispatcher abstraction in the network application layer
|
||||
The change will introduce a small dispatcher component, expected at `Assets/Scripts/Network/NetworkApplication/MainThreadNetworkDispatcher.cs`, that owns a thread-safe queue of received transport payloads and exposes a drain method for the Unity main thread. This keeps thread-boundary code out of `KcpTransport` and avoids coupling transport code to Unity APIs.
|
||||
|
||||
Alternative considered: enqueue directly inside `NetworkManager` with ad-hoc delegates. Rejected because it would bury the threading contract in one scene component and make edit mode testing harder.
|
||||
|
||||
### 2. `MessageManager` becomes a queueing bridge, not the final execution site for transport callbacks
|
||||
`MessageManager` will still subscribe to `ITransport.OnReceive`, parse envelopes, and resolve registered handlers, but the transport callback path will stop awaiting handlers inline. Instead it will enqueue a dispatch work item that can later be executed on the main thread. This preserves message type routing in one place while moving handler invocation to the correct thread boundary.
|
||||
|
||||
Alternative considered: push raw bytes into the dispatcher and parse envelopes later on the main thread. Rejected because malformed payload handling and message-type routing belong with the network message layer, not with the Unity host component.
|
||||
|
||||
### 3. `NetworkManager` pumps queued network work during Unity's frame loop
|
||||
The existing `NetworkManager` MonoBehaviour is the narrowest place to guarantee execution on the Unity main thread. It should own or receive the dispatcher and call its drain method from `Update`, with an optional per-frame drain limit to avoid one spike starving a frame. This keeps stage four focused and avoids introducing a second always-on host object unless later stages need it.
|
||||
|
||||
Alternative considered: capture `SynchronizationContext` and post handler work directly. Rejected because a dedicated drain step is easier to test deterministically and makes backpressure visible.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Queue growth under burst traffic] -> Add a bounded per-frame drain count and log queue length when it exceeds an expected threshold.
|
||||
- [One extra frame of dispatch latency] -> Acceptable for stage four because the goal is thread safety; later QoS work can tune batching and frame budget.
|
||||
- [Partial migration where some code still dispatches inline] -> Cover the new contract with tests that assert handlers are not run during the transport callback itself and only run after an explicit drain.
|
||||
- [Unity lifecycle coupling] -> Keep the dispatcher itself Unity-agnostic so only `NetworkManager` depends on `Update`.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Introduce the dispatcher abstraction and message work-item representation.
|
||||
2. Refactor `MessageManager` so transport callbacks enqueue dispatch work instead of invoking handlers immediately.
|
||||
3. Integrate dispatcher draining into `NetworkManager.Update`.
|
||||
4. Add or update edit mode tests for deferred dispatch, FIFO ordering, and invalid payload isolation.
|
||||
5. Run edit mode tests and update `CodeX-TODO.md` when implementation lands.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Whether stage four should enforce a hard queue capacity or only expose queue depth for diagnostics.
|
||||
- Whether login/bootstrap messages need an explicit early drain during startup before the first regular `Update`.
|
||||
@@ -0,0 +1,24 @@
|
||||
## Why
|
||||
|
||||
`MessageManager` currently handles transport receive callbacks directly on the transport's background thread, which leaves message dispatch and downstream game state updates one refactor away from touching Unity objects off the main thread. Stage four is the point where the project needs an explicit thread boundary so later connection-state and sync work can build on a safe dispatch model.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add a main-thread network dispatch capability that queues decoded transport payloads for processing on Unity's main thread.
|
||||
- Define that transport background threads are limited to socket receive, KCP session input/update, and basic transport error handling.
|
||||
- Define that message dispatch, handler execution, game object mutation, and UI-facing reactions run only when the main-thread dispatcher drains queued messages.
|
||||
- Cover the new threading boundary with architecture-focused tests and document the runtime path expected after stage four.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `network-main-thread-dispatch`: Defines the queueing and main-thread draining rules between transport receive callbacks and message handler execution.
|
||||
|
||||
### Modified Capabilities
|
||||
- None.
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected code: `Assets/Scripts/Network/NetworkApplication/MessageManager.cs`, new dispatcher code under `Assets/Scripts/Network/NetworkApplication/`, and related edit mode tests.
|
||||
- Affected runtime behavior: transport callbacks stop invoking business handlers inline and instead enqueue work for a main-thread pump.
|
||||
- Dependencies: no new external packages; uses in-process thread-safe queueing and Unity-side update integration.
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Transport callbacks enqueue network message dispatch work
|
||||
The network application layer SHALL place a thread-safe queue between `ITransport.OnReceive` callbacks and message handler execution. When a transport callback produces a valid application envelope, the callback path MUST enqueue dispatch work and return without invoking registered business handlers inline.
|
||||
|
||||
#### Scenario: Valid payload is deferred instead of dispatched inline
|
||||
- **WHEN** a transport implementation raises `OnReceive` with a valid encoded application message
|
||||
- **THEN** the message layer enqueues one dispatch work item for that message
|
||||
- **THEN** the registered handler is not executed during the transport callback itself
|
||||
|
||||
#### Scenario: Invalid payload does not block later queued messages
|
||||
- **WHEN** the transport callback receives malformed bytes followed by a valid application message
|
||||
- **THEN** the malformed payload is handled as an error without enqueuing executable work
|
||||
- **THEN** the later valid message can still be enqueued and processed normally
|
||||
|
||||
### Requirement: Main-thread drain executes queued handlers in receive order
|
||||
The runtime SHALL provide an explicit main-thread drain step that executes queued network dispatch work in FIFO order. Message handlers, gameplay state mutation, and UI-facing reactions triggered by received messages MUST run only through this main-thread drain path.
|
||||
|
||||
#### Scenario: Drain executes queued work on demand
|
||||
- **WHEN** one or more network messages have been enqueued from transport callbacks
|
||||
- **THEN** no registered handler runs until the main-thread dispatcher performs a drain step
|
||||
- **THEN** each queued handler executes during that drain step on the Unity main thread path
|
||||
|
||||
#### Scenario: Messages preserve receive order through the dispatcher
|
||||
- **WHEN** multiple valid messages are enqueued in sequence for the same runtime
|
||||
- **THEN** the main-thread dispatcher invokes their handlers in the same order they were enqueued
|
||||
|
||||
### Requirement: Runtime network host pumps the dispatcher each frame
|
||||
The Unity-side runtime network host SHALL integrate the dispatcher into its frame loop so queued network work is drained regularly while the network stack is running. The transport background thread responsibilities MUST remain limited to socket receive, KCP input/update, and transport-level error handling.
|
||||
|
||||
#### Scenario: Network host drains queued messages during runtime
|
||||
- **WHEN** the client runtime has started networking and a message is queued from the transport layer
|
||||
- **THEN** the runtime network host performs dispatcher draining during its Unity update loop
|
||||
- **THEN** the queued handler runs without the transport layer directly touching Unity objects
|
||||
|
||||
#### Scenario: Transport layer remains free of Unity object mutation
|
||||
- **WHEN** developers inspect the responsibilities of the transport receive path after stage four
|
||||
- **THEN** they find socket receive, KCP processing, and enqueue/error handling only
|
||||
- **THEN** Unity object mutation and UI updates are performed outside the transport callback path
|
||||
@@ -0,0 +1,19 @@
|
||||
## 1. Dispatcher Foundation
|
||||
|
||||
- [x] 1.1 Add a `MainThreadNetworkDispatcher` in `Assets/Scripts/Network/NetworkApplication/` that stores queued network work items in a thread-safe FIFO structure.
|
||||
- [x] 1.2 Define the dispatcher API needed by runtime code, including enqueueing from transport callbacks and draining from the Unity main thread.
|
||||
|
||||
## 2. Message Pipeline Refactor
|
||||
|
||||
- [x] 2.1 Refactor `MessageManager` so `ITransport.OnReceive` parses envelopes and enqueues dispatch work instead of invoking registered handlers inline.
|
||||
- [x] 2.2 Preserve current handler registration and invalid-payload handling while moving actual handler execution into the dispatcher drain path.
|
||||
|
||||
## 3. Unity Runtime Integration
|
||||
|
||||
- [x] 3.1 Integrate the dispatcher into `Assets/Scripts/NetworkManager.cs` so queued network messages are drained from the Unity frame loop.
|
||||
- [x] 3.2 Ensure transport-side responsibilities remain limited to receive, KCP processing, and enqueue/error handling, with Unity object mutation occurring only after main-thread drain.
|
||||
|
||||
## 4. Verification
|
||||
|
||||
- [x] 4.1 Add or update edit mode tests to verify receive callbacks defer handler execution until an explicit drain step and preserve FIFO ordering.
|
||||
- [x] 4.2 Run the relevant network edit mode tests/build and update `CodeX-TODO.md` to reflect stage four progress once the implementation is complete.
|
||||
Reference in New Issue
Block a user