forked from Rollocraft/CS2MultiplayerMod
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCommandObserver.cs
More file actions
77 lines (70 loc) · 3.35 KB
/
Copy pathCommandObserver.cs
File metadata and controls
77 lines (70 loc) · 3.35 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
using System;
using System.Collections.Concurrent;
using CS2MPMod.Core.Protocol.Messages;
using CS2MPMod.Core.Session;
namespace CS2MPMod.Game.Sync.Infrastructure
{
/// <summary>
/// Shared <see cref="SessionObserver"/> that funnels every command matching one of the given
/// command ids into a sync system's incoming queue. Replaces the near-identical per-system nested
/// <c>Observer</c> classes - construct one with the id(s) that system handles, e.g.
/// <c>new CommandObserver(_incoming, ObjectDeleteCommand.Id, NetDeleteCommand.Id)</c>.
/// Systems with a non-command observer keep their own bespoke observer.
/// </summary>
internal sealed class CommandObserver : SessionObserver
{
private readonly ConcurrentQueue<SimulationCommandMessage> _sink;
private readonly ushort[] _ids;
/// <summary>
/// Ceiling on a single command body. A batching command (terrain) sets this to its own
/// encoded cap so a forged oversized body is dropped on the network thread before it ever
/// reaches the queue or a decoder. Default: unlimited (bodies are already transport-capped).
/// </summary>
public int MaxBodyBytes = int.MaxValue;
/// <summary>
/// Per-system inbox ceiling. Systems that deliberately spread large, independent bursts
/// over multiple frames can opt into a larger bounded queue without weakening the default
/// limit used by dependent command streams.
/// </summary>
public int QueueCap = SyncInbox.DefaultCap;
// Backpressure warnings are throttled so a flood can't itself spam the log.
private const int WarnThrottleMs = 5000;
private int _lastWarnTick;
private bool _warnedOnce;
public CommandObserver(ConcurrentQueue<SimulationCommandMessage> sink, params ushort[] ids)
{
_sink = sink;
_ids = ids;
}
public override void OnCommandReceived(SimulationCommandMessage command)
{
for (int i = 0; i < _ids.Length; i++)
{
if (command.CommandId != _ids[i]) continue;
if (command.Body != null && command.Body.Length > MaxBodyBytes)
{
WarnThrottled("Dropping oversized command id " + command.CommandId +
" body=" + command.Body.Length + " > " + MaxBodyBytes + ".");
SyncInbox.RequestResync(CS2MPMod.Game.Diagnostics.ResyncReport
.Create("oversized sync command rejected", "stream",
CS2MPMod.Game.Diagnostics.ResyncEvidence.StreamLoss)
.About("oversized command")
.Tried("nothing - the command exceeded its size cap and was refused at the door"));
return;
}
SyncInbox.Push(_sink, command, QueueCap, "command " + command.CommandId);
return;
}
}
private void WarnThrottled(string message)
{
Action<string> warn = SyncInbox.LogWarn;
if (warn == null) return;
int now = Environment.TickCount;
if (_warnedOnce && (now - _lastWarnTick) < WarnThrottleMs) return;
_warnedOnce = true;
_lastWarnTick = now;
warn(message);
}
}
}