forked from Rollocraft/CS2MultiplayerMod
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSyncRealizeSystem.cs
More file actions
190 lines (180 loc) · 11.6 KB
/
Copy pathSyncRealizeSystem.cs
File metadata and controls
190 lines (180 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
using System;
using System.Collections.Generic;
using Game;
using CS2MPMod.Core.Diagnostics;
using CS2MPMod.Game.Diagnostics;
using CS2MPMod.Game.Sync.Systems.Net;
namespace CS2MPMod.Game.Sync.Systems
{
/// <summary>
/// Runs remote-command realization during ToolUpdate - the only phase where definitions
/// spawn into built entities. Later creation (e.g. at ModificationEnd) drops at Cleanup.
/// </summary>
public partial class SyncRealizeSystem : GameSystemBase
{
private BuildSyncSystem _buildSync;
private NetSyncSystem _netSync;
private DeleteSyncSystem _deleteSync;
private NetReplaceSyncSystem _netReplaceSync;
private ZoneSyncSystem _zoneSync;
private TerrainSyncSystem _terrainSync;
private UpgradeSyncSystem _upgradeSync;
private MoveSyncSystem _moveSync;
private NetUpgradeSyncSystem _netUpgradeSync;
private AreaSyncSystem _areaSync;
private RouteSyncSystem _routeSync;
private TilePurchaseSyncSystem _tileSync;
private DisasterSyncSystem _disasterSync;
private GrowableSyncSystem _growableSync;
private Mods.ModStateSyncSystem _modStateSync;
protected override void OnCreate()
{
base.OnCreate();
_buildSync = World.GetOrCreateSystemManaged<BuildSyncSystem>();
_netSync = World.GetOrCreateSystemManaged<NetSyncSystem>();
_deleteSync = World.GetOrCreateSystemManaged<DeleteSyncSystem>();
_netReplaceSync = World.GetOrCreateSystemManaged<NetReplaceSyncSystem>();
_zoneSync = World.GetOrCreateSystemManaged<ZoneSyncSystem>();
_terrainSync = World.GetOrCreateSystemManaged<TerrainSyncSystem>();
_upgradeSync = World.GetOrCreateSystemManaged<UpgradeSyncSystem>();
_moveSync = World.GetOrCreateSystemManaged<MoveSyncSystem>();
_netUpgradeSync = World.GetOrCreateSystemManaged<NetUpgradeSyncSystem>();
_areaSync = World.GetOrCreateSystemManaged<AreaSyncSystem>();
_routeSync = World.GetOrCreateSystemManaged<RouteSyncSystem>();
_tileSync = World.GetOrCreateSystemManaged<TilePurchaseSyncSystem>();
_disasterSync = World.GetOrCreateSystemManaged<DisasterSyncSystem>();
_growableSync = World.GetOrCreateSystemManaged<GrowableSyncSystem>();
_modStateSync = World.GetOrCreateSystemManaged<Mods.ModStateSyncSystem>();
}
private bool _wasDeferringTerrain;
private bool _wasHoldingNetMutations;
private const int FaultReportThrottleMs = 10000;
private readonly Dictionary<string, int> _lastFaultTick = new Dictionary<string, int>();
/// <summary>
/// Run one stage in isolation. The stages are ordered but not dependent: letting a
/// throw escape costs every stage behind it that frame, and a fault that repeats
/// (a bad remote command, a torn-down prefab) silently strands whole features for
/// as long as it lasts.
/// </summary>
private void Step(string stage, Action work)
{
try
{
work();
}
catch (Exception ex)
{
int now = Environment.TickCount;
int last;
if (_lastFaultTick.TryGetValue(stage, out last) &&
unchecked(now - last) < FaultReportThrottleMs) return;
_lastFaultTick[stage] = now;
SyncLog.Error(LogTopic.Pipeline, "Realize stage '" + stage +
"' failed this frame and was skipped.", ex);
}
}
protected override void OnUpdate()
{
using (Diagnostics.SyncProfiler.Measure("Realize"))
{
// Reset the net pipeline's per-frame state (the one-preview-wipe-per-frame guard) before
// any feeder runs — DeleteSync/NetReplaceSync may hijack the frame before NetSync does.
_netSync.BeginRealizeFrame();
Step("BuildSync.ObserveLocalToolOutput", _buildSync.ObserveLocalToolOutput);
Step("BuildSync.CaptureLocalObjectApply", _buildSync.CaptureLocalObjectApply);
// The active net tool has already selected Apply, while ToolOutputSystem has not yet
// consumed its standing preview. Publish its cached native courses and remember exact
// split originals now. Object graphs are captured later at the dedicated pre-output
// hook, directly from their standing definitions and only on the Apply frame.
Step("NetSync.CaptureLocalNetApply", _netSync.CaptureLocalNetApply);
// Hold NEW net/object realizes while remote terrain edits are backlogged: a course or
// object drawn right after a terraform stroke assumes the sender's post-edit surface, and
// realizing it against this machine's not-yet-graded terrain buries/floats it and misses
// every height-gated snap. Terrain drains within frames (its capture rate is far below
// the apply budget), so the hold is frames long. In-flight net commits still finish;
// local click-replays are exempt (their Y was measured here).
Step("TerrainSync.CompletePendingHeightReadback", _terrainSync.CompletePendingHeightReadback);
bool deferTerrain = _terrainSync.HasBacklog();
_netSync.DeferForTerrain = deferTerrain;
_buildSync.DeferForTerrain = deferTerrain;
_buildSync.NetworkDependenciesHeld = deferTerrain || _netSync.HasPlacementBacklog;
_moveSync.DeferForTerrain = deferTerrain;
_deleteSync.DeferNetForTerrain = deferTerrain;
if (deferTerrain != _wasDeferringTerrain)
{
_wasDeferringTerrain = deferTerrain;
SyncLog.Trace(LogTopic.Pipeline,
deferTerrain ? "net/build realize deferred (terrain backlog)" : "terrain drained; net/build realize resumed");
}
Step("BuildSync", _buildSync.RealizePending);
// A remote placement that is still waiting for the road it anchors to must not be
// overtaken by work that can only take that road away. Delete and replace live in
// their own feeders, so wire order between them and a deferred placement was never
// enforced: in the sessions this came from, two bulldozes were applied during the
// ten seconds a placement spent waiting, and the placement then asked for a full
// world reload because its target was "missing". Bounded by the placement's own
// retry window, and a delete can never be what a placement is waiting for, so
// holding it cannot deadlock.
long nowMs = Mod.Service != null ? Mod.Service.NowMs : 0L;
bool netMutationHeld = _netSync.HasStalledNativeOperation(nowMs) ||
CS2MPMod.Game.Diagnostics.ResyncArbiter.NetMutationFrozen(nowMs);
if (netMutationHeld != _wasHoldingNetMutations)
{
_wasHoldingNetMutations = netMutationHeld;
SyncLog.Trace(LogTopic.Pipeline,
netMutationHeld ? "net delete/replace held behind a stalled placement" : "net delete/replace resumed");
}
// DeleteSync BEFORE NetSync: a remote bulldoze applied this frame tags its edge Deleted,
// and NetSync's split-target query excludes Deleted edges — so NetSync never resolves a
// split onto an edge that is being removed this same frame (a stale-reference crash in
// ApplyNetSystem). NetSync's own commit (flipping applyMode) is independent of delete order.
_deleteSync.DeferNetForPendingPlacement = netMutationHeld;
Step("DeleteSync", _deleteSync.RealizePending);
// Road-type replacements also drive NetSync's single ApplyTool commit slot, so run after
// DeleteSync and before NetSync's build: a delete armed this frame makes replace defer
// (IsCommitBusy), and an armed replace makes NetSync's build defer — only one net batch
// enters any one ApplyTool pass, never a build+replace of the same edge together.
_netReplaceSync.DeferForPendingPlacement = netMutationHeld;
if (!deferTerrain) Step("NetReplaceSync", _netReplaceSync.RealizePending);
Step("NetSync", _netSync.RealizePending);
bool deferNetworkDependents = deferTerrain || _netSync.HasPlacementBacklog;
_buildSync.NetworkDependenciesHeld = deferNetworkDependents;
// Published for the systems that only WAIT on roads, zoning and zone-grown
// buildings. They are not gated themselves, so without this they keep counting down
// retry windows for targets this pipeline is deliberately holding back.
CS2MPMod.Game.Sync.Infrastructure.RealizeGate.WorldBuildingHeld =
deferNetworkDependents;
if (!deferNetworkDependents) Step("ZoneSync", _zoneSync.RealizePending);
else _zoneSync.NotifyRealizeHeld(nowMs);
Step("TerrainSync", _terrainSync.RealizePending);
// After ZoneSync and behind the same network gate: a zoned building is grown on a lot
// that a road and its zoning produced, so realizing one before those arrive would put
// it on ground the receiver does not yet consider buildable.
_growableSync.DeferForTerrain = deferTerrain;
_growableSync.NetworkDependenciesHeld = deferNetworkDependents;
if (!deferNetworkDependents) Step("GrowableSync", _growableSync.RealizePending);
else _growableSync.NotifyRealizeHeld(nowMs);
Step("UpgradeSync", _upgradeSync.RealizePending);
Step("MoveSync", _moveSync.RealizePending);
if (!deferNetworkDependents) Step("NetUpgradeSync", _netUpgradeSync.RealizePending);
Step("AreaSync", _areaSync.RealizePending);
Step("RouteSync.FinalizePending", _routeSync.FinalizePending);
// Held rather than skipped: a route's retry window is for waiting on its own stop
// or road, not for waiting on permission to look, and expiring one unattempted
// asks for a world reload over a command that was never tried.
if (!deferNetworkDependents) Step("RouteSync", _routeSync.RealizePending);
else _routeSync.NotifyRealizeHeld(nowMs);
Step("TilePurchaseSync", _tileSync.RealizePending);
// Disaster events are plain simulation entities - no definitions, no terrain
// dependency - but they must still be created here: the game's event initialization
// runs later this frame and only ever looks at freshly Created events.
Step("DisasterSync", _disasterSync.RealizePending);
// Last: what another mod stores is stored against a road, a junction or a building,
// so everything that could still be creating one this frame has to have run. A
// closure whose carrier is genuinely still in the backlog waits in its own hold
// window rather than being gated here.
Step("ModStateSync", _modStateSync.RealizePending);
}
}
}
}