压测工具
This commit is contained in:
parent
ac1b31f33a
commit
6162094162
92
.codex/skills/th1-network-sync/SKILL.md
Normal file
92
.codex/skills/th1-network-sync/SKILL.md
Normal file
@ -0,0 +1,92 @@
|
||||
---
|
||||
name: th1-network-sync
|
||||
description: TH1 project-specific network synchronization guide for Unity C# multiplayer, Steam P2P, GameNetSender/GameNetReceiver, SteamLobbyManager, SimpleP2P, MapData/NetData recovery, action sync, host start/resume, ForceUpdate, heartbeat, ordered delivery, large message chunking, send-failure handling, and lobby UI rollback. Use whenever Codex works on TH1 networking, multiplayer saves, deterministic sync, P2P queueing, MapData broadcasts, ActionConfirm/ActionExecute, reconnect, Timer-driven network callbacks, or any bug that may affect multiplayer reliability or ordering.
|
||||
---
|
||||
|
||||
# TH1 Network Sync
|
||||
|
||||
## Core Rule
|
||||
|
||||
Treat multiplayer changes as state-machine changes, not only message sends. Before editing, trace the full path:
|
||||
|
||||
`GameNetSender -> SteamLobbyManager -> SimpleP2P -> GameNetReceiver -> Main/ActionLogic/MapData`.
|
||||
|
||||
Do not let one peer advance local game state unless the matching network send/receive contract succeeded.
|
||||
|
||||
## First Files To Read
|
||||
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Steam/SimpleP2P.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Steam/SteamLobbyManager.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Steam/GameNetSender.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Steam/GameNetReceiver.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Data/NetData.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Data/MapData.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Core/Main.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Logic/Action/ActionLogic.cs`
|
||||
- `Unity/Assets/Scripts/TH1_Instance/Timer.cs`
|
||||
|
||||
For the current network contract summary, read `references/network-contract.md`.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Classify the message.
|
||||
- Critical: `GameStart`, `ForceUpdate`, `ActionConfirm`, `ActionExecute`, full `MapData`, reconnect/restore messages.
|
||||
- Health/status: heartbeat, map confirm, lobby state, chat.
|
||||
- Critical messages must return `bool` or otherwise expose failure to the caller before local state advances.
|
||||
|
||||
2. Preserve ordering.
|
||||
- Keep game messages on `SimpleP2P` per-peer outgoing queues.
|
||||
- Do not bypass ordered envelopes for normal game messages.
|
||||
- Do not send direct raw Steam messages for gameplay sync unless you also preserve FIFO and failure semantics.
|
||||
|
||||
3. Preserve all-or-nothing broadcast for critical messages.
|
||||
- Broadcast through `SteamLobbyManager.BroadcastMessage`.
|
||||
- Preflight all lobby targets before enqueueing any target.
|
||||
- If any target cannot queue, return failure and avoid local progression.
|
||||
- Never accept "some peers got the critical message" as a valid state.
|
||||
|
||||
4. Gate local state on send success.
|
||||
- Host `GameStart` must succeed before `SaveMapData`, `RefreshTurn`, room UI close, or final success return.
|
||||
- Owner `ActionExecute` broadcast must succeed before owner local execute.
|
||||
- Client `ActionConfirm` must succeed before client local execute; `TurnEnd` may send and then stop local execution.
|
||||
- Heartbeat send timestamps should only update after the send was accepted.
|
||||
|
||||
5. Validate `MapData` before sending or applying.
|
||||
- Reject null maps, `DeserializedMissingCriticalData`, wrong `NetMode`, and missing core maps.
|
||||
- Call `Net.RefreshPlayerNet(mapData)` and respect its `bool` result.
|
||||
- Ensure every current lobby member maps to a valid `PlayerId`.
|
||||
- Do not repair missing critical data silently during deserialization.
|
||||
|
||||
6. Roll back failed start/resume.
|
||||
- Snapshot `MapData`, `InputLogic`, `MapInteractionLogic`, and `MapGeneratorLogic` before host start/resume mutation.
|
||||
- On failure or exception, restore the snapshot, dispose/reinitialize render state as appropriate, cancel pending start timers, and keep lobby UI open.
|
||||
- UI code must only invoke room-close/start callbacks after the start method returns `true`.
|
||||
|
||||
7. Keep receiver failure atomic.
|
||||
- Deserialize and dispatch inside try/catch.
|
||||
- If incoming `GameStart` or `ForceUpdate` validation fails, do not hide room UI and do not leave the game in `ForceUpdating`.
|
||||
- Restore previous game state if `NetResumeMatch` fails.
|
||||
|
||||
## Checks Before Finishing
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
dotnet build Unity/Assembly-CSharp.csproj --no-restore
|
||||
```
|
||||
|
||||
For network-heavy changes, inspect these risks explicitly:
|
||||
|
||||
- Could a critical broadcast partially enqueue?
|
||||
- Could a caller ignore a failed send and still mutate game state?
|
||||
- Could `MapData` deserialize with missing core fields and still be used?
|
||||
- Could a timer callback fire after the target UI/object state is gone?
|
||||
- Could a retry loop or ordered gap wait forever?
|
||||
|
||||
## What Not To Do
|
||||
|
||||
- Do not add a new direct send path around `SimpleP2P` queues for gameplay sync.
|
||||
- Do not silently swallow `SendMessageToPeer` or `BroadcastMessage` failure.
|
||||
- Do not call `GameNetReceiver` from a partial large-message chunk.
|
||||
- Do not close the multiplayer room UI before host start returns success.
|
||||
- Do not call `GC.Collect()` in match entry paths as a networking fix.
|
||||
4
.codex/skills/th1-network-sync/agents/openai.yaml
Normal file
4
.codex/skills/th1-network-sync/agents/openai.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "TH1 Network Sync"
|
||||
short_description: "TH1 multiplayer sync and Steam P2P reliability rules"
|
||||
default_prompt: "Use the TH1 Network Sync skill for TH1 multiplayer, P2P, GameNet, save resume, reconnect, or deterministic sync work."
|
||||
@ -0,0 +1,67 @@
|
||||
# TH1 Network Contract
|
||||
|
||||
This reference summarizes the multiplayer contract after the May 2026 pre-release network audit.
|
||||
|
||||
## SimpleP2P
|
||||
|
||||
- All normal game messages use per-peer outgoing queues.
|
||||
- Per-peer FIFO is required.
|
||||
- Ordered envelopes are used for game payloads; do not bypass them for gameplay sync.
|
||||
- Outgoing sequence is committed only after enqueue succeeds.
|
||||
- Large payloads are chunked after ordered wrapping.
|
||||
- Incoming large chunks must validate magic, version, message id, chunk index, chunk count, total length, and payload length.
|
||||
- Large incoming messages and outgoing queues have per-peer and global byte budgets.
|
||||
- Ordered gaps and large-message receives must timeout and disconnect/clean state rather than wait forever.
|
||||
- Steam message pointers must be released in `finally`.
|
||||
|
||||
## SteamLobbyManager
|
||||
|
||||
- `SendMessageToPeer` returns `bool`; precheck failures must call the same failure path used by P2P send failures.
|
||||
- `BroadcastMessage` returns `bool`.
|
||||
- Critical broadcast must gather current lobby members, skip self, preflight all targets through `SimpleP2P.CanQueueMessages`, and only then enqueue.
|
||||
- Missing connection, non-lobby target, invalid data, or queue budget failure must surface through `OnLobbyErrorEvent` / send-failure logging.
|
||||
|
||||
## GameNetSender
|
||||
|
||||
- Sender methods that gate local state must return `bool`.
|
||||
- `GameStart` must validate `MapData` and return broadcast success.
|
||||
- `ActionConfirm` must return send success.
|
||||
- `ActionExecute` must return broadcast success.
|
||||
- `ForceUpdate` and full `MapData` sends must validate multiplayer map data before sending.
|
||||
- Heartbeat send timestamps should only update after send acceptance.
|
||||
|
||||
## GameNetReceiver
|
||||
|
||||
- Wrap deserialization and dispatch in try/catch.
|
||||
- Validate incoming `GameStart` and `ForceUpdate` maps before applying.
|
||||
- `NetStartGame` and `NetResumeMatch` return `bool`; UI should only close/hide after success.
|
||||
- `ForceUpdate` should restore previous game state if resume fails.
|
||||
- `MapConfirm` must guard null maps, missing actions, and null action payloads.
|
||||
|
||||
## MapData And NetData
|
||||
|
||||
- `MapData.DeserializedMissingCriticalData` means the save/network map must not be used.
|
||||
- `OnAfterMemoryPackDeserialize` may coalesce non-critical containers to avoid callback NREs but must not silently accept missing core data.
|
||||
- `NetData.RefreshPlayerNet(MapData)` returns `bool`.
|
||||
- In multiplayer mode, every current lobby member must have a valid, non-duplicate `PlayerId`.
|
||||
- Host resume/start must fail before assigning global `MapData` if player network mapping is invalid.
|
||||
|
||||
## Main And UI
|
||||
|
||||
- Host start/resume snapshots `MapData`, `InputLogic`, `MapInteractionLogic`, and `MapGeneratorLogic`.
|
||||
- Host `GameStart` failure must roll back and must not save, refresh turn, or report success.
|
||||
- Custom map load must check `mapRecord == null` before `RegenerateMap`.
|
||||
- `UIOutsideMultiplayView.ShowLoadingAndStartGame` must only invoke `OnStartGame` after the host start/resume method returns `true`.
|
||||
- Timer callbacks for start announcements should be cancelled during abort paths.
|
||||
|
||||
## ActionLogic
|
||||
|
||||
- Client `ActionConfirm` failure aborts local action execution.
|
||||
- Owner `ActionExecute` broadcast failure aborts owner local action execution.
|
||||
- `TurnEnd` can send confirmation and stop local execution as designed.
|
||||
|
||||
## Timer/Event Notes
|
||||
|
||||
- Timer callbacks should guard destroyed Unity targets.
|
||||
- Timer mutation during callback must not remove the wrong task.
|
||||
- Event publish should isolate listener exceptions so one bad listener does not block others.
|
||||
1338
Unity/Assets/Scripts/TH1_Logic/Editor/NetworkStressEditorWindow.cs
Normal file
1338
Unity/Assets/Scripts/TH1_Logic/Editor/NetworkStressEditorWindow.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a0b645d31ea4e8aaee2ffadbe47b7cb
|
||||
timeCreated: 1778849300
|
||||
@ -22,6 +22,7 @@ namespace TH1_Logic.Steam
|
||||
{
|
||||
var message = MemoryPack.MemoryPackSerializer.Deserialize<BaseMessage>(data);
|
||||
if (message == null) return;
|
||||
if (message.MessageType == P2PMsgType.NetworkStress) return;
|
||||
|
||||
if (message.MessageType == P2PMsgType.String) OnReceivedString((StringMessage)message);
|
||||
if (message.MessageType == P2PMsgType.GameStart) OnReceivedGameStart((GameStartMessage)message);
|
||||
|
||||
@ -57,6 +57,17 @@ namespace TH1_Logic.Steam
|
||||
ChatMessage = 15,
|
||||
// 邀请
|
||||
InviteMessage = 16,
|
||||
// 网络压测工具消息
|
||||
NetworkStress = 17,
|
||||
}
|
||||
|
||||
public enum NetworkStressMessageKind : byte
|
||||
{
|
||||
Probe = 0,
|
||||
Ack = 1,
|
||||
ControlStart = 2,
|
||||
ControlStop = 3,
|
||||
Report = 4,
|
||||
}
|
||||
|
||||
|
||||
@ -77,6 +88,7 @@ namespace TH1_Logic.Steam
|
||||
[MemoryPackUnion(14, typeof(HeartbeatReplyMessage))]
|
||||
[MemoryPackUnion(15, typeof(ChatMessage))]
|
||||
[MemoryPackUnion(16, typeof(InviteMessage))]
|
||||
[MemoryPackUnion(17, typeof(NetworkStressMessage))]
|
||||
public abstract partial class BaseMessage
|
||||
{
|
||||
public abstract P2PMsgType MessageType { get; }
|
||||
@ -217,4 +229,37 @@ namespace TH1_Logic.Steam
|
||||
public override P2PMsgType MessageType => P2PMsgType.InviteMessage;
|
||||
public LobbyListInfo LobbyInfo { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class NetworkStressMessage : BaseMessage
|
||||
{
|
||||
public override P2PMsgType MessageType => P2PMsgType.NetworkStress;
|
||||
public NetworkStressMessageKind StressKind { get; set; }
|
||||
public string SessionId { get; set; }
|
||||
public string ScenarioName { get; set; }
|
||||
public ulong SenderMemberId { get; set; }
|
||||
public ulong TargetMemberId { get; set; }
|
||||
public long Sequence { get; set; }
|
||||
public long AckSequence { get; set; }
|
||||
public long SentUtcTicks { get; set; }
|
||||
public int PayloadBytes { get; set; }
|
||||
public int PayloadHash { get; set; }
|
||||
public bool Reliable { get; set; }
|
||||
public bool RequiresAck { get; set; }
|
||||
public float DurationSeconds { get; set; }
|
||||
public float MessagesPerSecond { get; set; }
|
||||
public int SmallPayloadBytes { get; set; }
|
||||
public int LargePayloadBytes { get; set; }
|
||||
public int LargeEveryMessages { get; set; }
|
||||
public float DropPercent { get; set; }
|
||||
public float UnreliablePercent { get; set; }
|
||||
public int JitterMinMs { get; set; }
|
||||
public int JitterMaxMs { get; set; }
|
||||
public int BurstPauseEveryMessages { get; set; }
|
||||
public int BurstPauseMs { get; set; }
|
||||
public int RandomSeed { get; set; }
|
||||
public byte[] Payload { get; set; }
|
||||
public string Text { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user