完成阶段 4

This commit is contained in:
SepComet
2026-03-27 08:04:25 +08:00
parent f053c9ad0d
commit ff9ee1291f
47 changed files with 2481 additions and 460 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-03-26
@@ -0,0 +1,55 @@
## Context
The project already has a reusable transport contract (`ITransport`), a KCP-based reliable transport (`KcpTransport`), and a message layer (`MessageManager`) that parses envelopes and routes handlers. However, the current runtime shape still hard-codes a Unity-oriented hosting model: `NetworkManager` is the only real host, `MessageManager` defaults to `MainThreadNetworkDispatcher`, and the main-thread pumping behavior is bundled into the same client-facing assembly that owns reusable transport code.
That coupling is now the main blocker to sharing one networking stack across client and server. The transport and protocol code itself is already environment-agnostic, but the hosting and dispatch assumptions are not. If a server is added without first separating those concerns, the codebase will either fork into client/server variants or introduce conditional logic in classes that should remain host-neutral.
## Goals / Non-Goals
**Goals:**
- Define a shared network core that both client and server hosts can use without depending on Unity runtime types.
- Introduce an explicit dispatcher abstraction so message execution policy is supplied by the host instead of being baked into `MessageManager`.
- Preserve KCP transport behavior and the existing envelope/handler programming model while separating reusable core from host-specific orchestration.
- Keep Unity main-thread dispatch as a supported client strategy rather than regressing stage four.
**Non-Goals:**
- Build the full dedicated server application, deployment pipeline, or gameplay authority model.
- Redesign connection/login/heartbeat state machines from stage five.
- Change protocol formats or replace KCP with a different transport.
## Decisions
### 1. Split networking into shared core vs host-specific adapters
The refactor will treat transport/session/message-routing code as a shared foundation and move runtime bootstrapping into host adapters. The shared layer owns `ITransport`, `KcpTransport`, envelope parsing, handler registration, and dispatch abstractions. The client host retains Unity frame-loop integration and gameplay/UI handlers; the future server host will provide its own startup and ticking model.
Alternative considered: keep one assembly and rely on naming conventions only. Rejected because soft boundaries will erode quickly once server-specific code starts landing.
### 2. Replace hard-coded main-thread dispatch with an injected dispatcher contract
`MessageManager` should depend on an interface such as `INetworkMessageDispatcher` that can enqueue and execute handler work according to host policy. The Unity client can implement it with a queued main-thread dispatcher; a single-threaded server can implement it with immediate execution or a dedicated server loop. This keeps message parsing shared while making execution policy explicit.
Alternative considered: let `MessageManager` keep constructing `MainThreadNetworkDispatcher` by default and override only on the server. Rejected because a Unity default still leaks client assumptions into shared code and makes tests less honest.
### 3. Keep Unity main-thread dispatch as a client-host requirement, not a shared-core requirement
Stage four's thread-safety guarantee remains valid, but it belongs to the Unity client host rather than the shared message layer. The shared capability will state that hosts provide a dispatch strategy; the existing Unity dispatch capability will be narrowed to define how the client host pumps a main-thread dispatcher implementation.
Alternative considered: remove the `network-main-thread-dispatch` capability entirely and fold everything into the shared-core spec. Rejected because Unity-specific frame-loop guarantees are still valuable and testable on their own.
## Risks / Trade-offs
- [Boundary churn across files and assemblies] -> Move in small slices and keep tests running after each structural step.
- [Client regressions while introducing host abstraction] -> Preserve current Unity behavior behind a client-specific dispatcher adapter and verify with existing edit mode tests.
- [Server host semantics chosen too early] -> Specify only the abstraction and one minimal non-Unity host path; leave richer server lifecycle work for a later change.
- [Over-generalizing the dispatcher contract] -> Keep the interface minimal: register work, drain or execute work, and expose only what shared message routing actually needs.
## Migration Plan
1. Introduce shared host-dispatch abstractions and move message routing to depend on them.
2. Re-home or reorganize reusable network core code so it no longer depends on Unity host classes.
3. Rebuild the Unity client host on top of the shared core plus a main-thread dispatcher adapter.
4. Add a minimal non-Unity host path or tests that prove the same core can run without Unity-specific pumping.
5. Update docs and TODO status once the shared foundation is in place.
## Open Questions
- Whether the shared core should be split by folder only or by asmdef/project boundary in the first pass.
- Whether the initial server-facing host should use immediate dispatch or a queued single-thread loop for parity with future lifecycle work.
@@ -0,0 +1,24 @@
## Why
The project's transport and message pipeline are now strong enough to serve both client and server, but the current runtime assembly still mixes reusable networking code with Unity-specific hosting concerns such as `NetworkManager` and main-thread pumping. If the client and server continue to evolve separately, transport, protocol handling, and dispatch behavior will drift and the same bugs will be fixed twice.
## What Changes
- Extract the reusable transport, session, protocol-envelope, and message-routing core into a shared client/server networking foundation.
- Introduce a host-side dispatcher abstraction so `MessageManager` depends on an injected dispatch strategy rather than a hard-coded Unity main-thread implementation.
- Keep Unity-specific hosting, frame-loop pumping, and gameplay/UI handlers in the client host layer while enabling a non-Unity server host to use the same networking core.
- Add tests and documentation that prove the same shared networking layer can run under both client-style and server-style hosting paths.
## Capabilities
### New Capabilities
- `shared-network-foundation`: Defines the shared transport/message infrastructure that both client and server hosts use without depending on Unity-specific runtime classes.
### Modified Capabilities
- `network-main-thread-dispatch`: Refine the threading requirement so Unity main-thread dispatch is a host-specific strategy layered on top of a host-injected dispatcher abstraction, not the only message execution model.
## Impact
- Affected code: `Assets/Scripts/Network/NetworkTransport/`, `Assets/Scripts/Network/NetworkApplication/`, `Assets/Scripts/NetworkManager.cs`, and new host/dispatcher abstraction code.
- Affected architecture: shared networking core becomes independent from Unity `MonoBehaviour` hosting; client and server each provide their own runtime host and dispatch policy.
- Dependencies: no new external packages expected, but assembly boundaries and tests will need to be reorganized to support shared code reuse.
@@ -0,0 +1,14 @@
## MODIFIED Requirements
### Requirement: Runtime network host pumps the dispatcher each frame
The Unity-side runtime network host SHALL integrate a client-specific main-thread dispatcher implementation into its frame loop so queued network work is drained regularly while the network stack is running. That Unity dispatcher implementation MUST satisfy the shared host-dispatch abstraction used by the shared message-routing layer, while the transport background thread responsibilities remain limited to socket receive, KCP input/update, and transport-level error handling.
#### Scenario: Network host drains queued messages during runtime
- **WHEN** the Unity client runtime has started networking and a message is queued from the transport layer
- **THEN** the Unity client 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 the shared networking refactor
- **THEN** they find socket receive, KCP processing, and enqueue/error handling only
- **THEN** Unity object mutation and UI updates are performed through the Unity host's main-thread dispatcher implementation rather than the transport callback path
@@ -0,0 +1,35 @@
## ADDED Requirements
### Requirement: Shared network core is host-agnostic
The project SHALL provide a shared network core that contains transport, session, envelope parsing, and message-routing behavior without depending on Unity-specific runtime host classes such as `MonoBehaviour` or frame-loop callbacks. Both client and server networking hosts MUST be able to use this shared core.
#### Scenario: Client host uses shared network core
- **WHEN** the Unity client constructs its runtime networking stack
- **THEN** it uses the shared transport and message-routing core for transport startup, sending, receiving, and handler registration
- **THEN** Unity-specific logic remains in the client host adapter rather than in the shared core classes
#### Scenario: Server host can use the same core without Unity types
- **WHEN** a server-side host constructs the runtime networking stack
- **THEN** it can use the same shared transport and message-routing core without depending on Unity host classes
- **THEN** server-specific startup and lifetime control are provided by a separate host adapter
### Requirement: Message routing uses a host-provided dispatcher strategy
The shared message-routing layer SHALL execute received business handlers through a host-provided dispatcher abstraction rather than constructing a Unity-specific dispatcher internally. The host MUST be able to choose the dispatch strategy that matches its runtime model.
#### Scenario: Unity client injects a queued main-thread dispatcher
- **WHEN** the Unity client constructs the shared message-routing layer
- **THEN** it supplies a dispatcher implementation that queues work for later execution on the Unity main thread
- **THEN** received handlers run according to that injected client dispatch strategy
#### Scenario: Server host injects a non-Unity dispatch strategy
- **WHEN** a non-Unity server host constructs the shared message-routing layer
- **THEN** it supplies a dispatcher implementation that does not rely on Unity frame-loop semantics
- **THEN** the shared message-routing layer still processes received messages correctly through that host-selected strategy
### Requirement: Shared core preserves current transport and message contracts
The shared client/server foundation SHALL preserve the existing `ITransport` send/receive contract and the envelope-based `MessageManager` routing model so client and server hosts exchange the same business payload format through the same transport abstractions.
#### Scenario: Shared hosts exchange the same envelope format
- **WHEN** a client host sends a business message through the shared core to a server host using the shared core
- **THEN** the message is encoded using the same envelope contract on the client side
- **THEN** the server host decodes and routes it through the shared message-routing layer without a host-specific protocol fork
@@ -0,0 +1,19 @@
## 1. Shared Core Boundary
- [x] 1.1 Introduce a host-dispatch abstraction for the message layer so shared networking code no longer constructs `MainThreadNetworkDispatcher` internally.
- [x] 1.2 Reorganize the reusable transport/message-routing code into a shared client/server network core boundary that does not depend on Unity host classes.
## 2. Client Host Refactor
- [x] 2.1 Update the Unity client host to build the networking stack from the shared core and inject a Unity main-thread dispatcher implementation explicitly.
- [x] 2.2 Keep current client gameplay/UI handler behavior intact while moving Unity-specific frame-loop pumping and host lifecycle logic out of the shared core.
## 3. Server-Oriented Reuse Path
- [x] 3.1 Add a minimal non-Unity host path or test-only server host that constructs the same shared networking core with a non-Unity dispatcher strategy.
- [x] 3.2 Verify that the shared client/server path preserves the existing envelope-based protocol and `ITransport` contract without introducing a protocol fork.
## 4. Verification And Documentation
- [x] 4.1 Add or update tests to cover injected dispatcher behavior, Unity host pumping, and non-Unity host reuse of the same core.
- [x] 4.2 Run the relevant build/tests and update `CodeX-TODO.md` or related docs to reflect that the network foundation is now shared between client and server hosts.