fix: Fixes tick count wrap-around in movement throttle, and eliminates more allocations in NetState (#2603)
## Summary Removes the per-tick allocation in the movement throttle, fixes tick-count wrap-around bugs in the throttle and RTT probe state, and trims per-connection allocations and dead fields in `NetState`. ## Movement throttle - **No more per-tick `List<NetState>` snapshot.** `ProcessAllQueues()` iterates the `HashSet` directly and removes drained or disconnected states in place. `HashSet<T>.Remove` does not invalidate enumerators on .NET Core 3.0+ (verified on 10.0.11); only inserting a *new* member does, and the only `Add` is in the packet handler, which never nests with `Slice()`. The eager `Remove` calls in `RejectAndReset`, `ClearQueue`, and `ProcessMovementQueue` are gone; membership is reconciled once per tick from `_hasQueuedMovements`. - **Debug logging** is now gated solely by the per-connection `NetState.MovementLogging` flag. The global `movementThrottle.debugLogging` setting is removed. - **New settings**: `movementThrottle.maxRttBonus`, `movementThrottle.maxChainGap`, and `movementThrottle.speedHackNotificationCooldown` were fields with no config binding. ## Tick-count wrap-around All comparisons are now in subtraction form and no tick field uses zero as a sentinel: - `now < _nextMovementTime` in the queue drain loop → `now - _nextMovementTime < 0`. - `_lastMovementRecordTime > 0`, `_lastSpeedHackNotification`, `_rttProbeTime > 0`, and `_nextRttProbe == 0` sentinels replaced with `_hasMovementRecord`, `_speedHackNotified`, `_rttProbePending`, and a seeded `_nextRttProbe`. - `_lastQueueDepthCheck` and `_movementWindowStart` are seeded from `Core.TickCount` at construction and on reset instead of zero. User-visible effects of the old code: on hosts with pass-through counters (GCP) movement history never recorded and speed hack detection was silently off; on every host, staff speed hack notifications were suppressed until `Core.TickCount` exceeded the five-minute cooldown. ## NetState - `Instances` returns `HashSet<NetState>` again so engine-internal `foreach` uses the struct enumerator instead of boxing through `IReadOnlySet<T>`. - Removed `_sustainedQueueDepth` (declared and zeroed since #2266, never read), `_lastRtt` (now derived as `LastRtt` from the newest history slot), and `_rttProbeTimestampHiRes` (only fed one debug log line). 20 bytes per connection. - `HuePickers`, `Menus`, and `Trades` are lazily created instead of allocating three lists per connection, including every login-server connection that dies on shard select. `Trades` is released when it empties. All helpers and the `HuePickerResponse` / `MenuResponse` handlers are null-tolerant; the trade cancel loops keep their `i < Count` guards because `SecureTrade.Cancel()` runs virtual item hooks that can re-enter the same list. ## Testing - `dotnet build -c Release` clean. - All MovementThrottle tests pass (27), plus the Trade / Menu / HuePicker / NetState tests (32).
This commit is contained in:
parent
c9875e7f64
commit
547c2ea0fa
5 changed files with 153 additions and 120 deletions
|
|
@ -86,11 +86,17 @@ public static class IncomingPlayerPackets
|
|||
public static void HuePickerResponse(NetState state, SpanReader reader)
|
||||
{
|
||||
var serial = reader.ReadUInt32();
|
||||
_ = reader.ReadInt16(); // Item ID
|
||||
reader.ReadInt16(); // Item ID
|
||||
var hue = Utility.ClipDyedHue(reader.ReadInt16() & 0x3FFF);
|
||||
|
||||
foreach (var huePicker in state.HuePickers)
|
||||
if (state.HuePickers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < state.HuePickers.Count; i++)
|
||||
{
|
||||
var huePicker = state.HuePickers[i];
|
||||
if (huePicker.Serial == serial)
|
||||
{
|
||||
state.RemoveHuePicker(huePicker);
|
||||
|
|
@ -287,13 +293,18 @@ public static class IncomingPlayerPackets
|
|||
public static void MenuResponse(NetState state, SpanReader reader)
|
||||
{
|
||||
var serial = reader.ReadUInt32();
|
||||
int menuID = reader.ReadInt16();
|
||||
reader.ReadInt16(); // menu id
|
||||
int index = reader.ReadInt16();
|
||||
int itemID = reader.ReadInt16();
|
||||
int hue = reader.ReadInt16();
|
||||
reader.ReadInt16(); // item id
|
||||
reader.ReadInt16(); // hue
|
||||
|
||||
index -= 1; // convert from 1-based to 0-based
|
||||
|
||||
if (state.Menus == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < state.Menus.Count; i++)
|
||||
{
|
||||
var menu = state.Menus[i];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue