feat: Adds a movement throttle system. Removes Fastwalk system. (#1511)
### Summary
- Removes Fastwalk system
- Removed the following settings:
- `movement.enableFastWalkPrevention`
- `movement.fastwalkExemptionLevel`
- Adds movement throttle system.
- Adds the following settings:
- `movement.throttleReset` - Default value is `1000` (1 second).
- `movement.throttleThreshold` - Default value is `400` (400ms).
### Movement Throttling
This new system will trigger if a player requests 400ms (configurable) worth of movements quicker than wall clock time. When this happens, the player is throttled (all incoming packets to the server are halted) until wall clock time catches up with the requests. Upon each throttle, the player receives enough credit to handle up to 400ms of "lag" as a grace/catch-up.
### Developer Notes
We use two throttle queues to prevent an infinite loop.
This commit is contained in:
parent
27df6344d9
commit
146abad36e
11 changed files with 106 additions and 322 deletions
71
Projects/Server/Network/MovementThrottle.cs
Normal file
71
Projects/Server/Network/MovementThrottle.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2023 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: MovementThrottle.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
|
||||
namespace Server.Network;
|
||||
|
||||
public static class MovementThrottle
|
||||
{
|
||||
private static long _movementThrottleReset; // 1 second
|
||||
private static long _throttleThreshold; // 400 milliseconds
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
_movementThrottleReset = ServerConfiguration.GetOrUpdateSetting("movement.throttleReset", 1000);
|
||||
_throttleThreshold = ServerConfiguration.GetOrUpdateSetting("movement.throttleThreshold", 400);
|
||||
}
|
||||
|
||||
public static unsafe void Initialize()
|
||||
{
|
||||
IncomingPackets.RegisterThrottler(0x02, &Throttle);
|
||||
}
|
||||
|
||||
public static bool Throttle(int packetId, NetState ns, out bool drop)
|
||||
{
|
||||
drop = false;
|
||||
|
||||
var from = ns.Mobile;
|
||||
|
||||
if (from?.Deleted != false || from.AccessLevel > AccessLevel.Player)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
long now = Core.TickCount;
|
||||
long credit = ns._movementCredit;
|
||||
long nextMove = ns._nextMovementTime;
|
||||
|
||||
// Reset system if idle for more than 1 second
|
||||
if (now - nextMove + _movementThrottleReset > 0)
|
||||
{
|
||||
ns._movementCredit = 0;
|
||||
ns._nextMovementTime = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
long cost = nextMove - now;
|
||||
|
||||
if (credit < cost)
|
||||
{
|
||||
// Not enough credit, therefore throttled
|
||||
return false;
|
||||
}
|
||||
|
||||
// On the next event loop, the player receives up to 400ms in grace latency
|
||||
ns._movementCredit = Math.Min(_throttleThreshold, credit - cost);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2023 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: NetState.Fastwalk.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using CalcMoves = Server.Movement.Movement;
|
||||
|
||||
namespace Server.Network;
|
||||
|
||||
public partial class NetState
|
||||
{
|
||||
// The next step
|
||||
private int _stepIndex;
|
||||
// The last index to expire
|
||||
private int _expiredIndex;
|
||||
|
||||
private long[] _steps;
|
||||
|
||||
public bool AddStep(Direction d)
|
||||
{
|
||||
if (Mobile == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_steps ??= new long[CalcMoves.MaxSteps + 1]; // Extra index as a sentinel
|
||||
var stepsLength = _steps.Length;
|
||||
|
||||
var now = Core.TickCount;
|
||||
|
||||
var lastIndex = -1;
|
||||
|
||||
// Expire old steps
|
||||
while (_expiredIndex != _stepIndex)
|
||||
{
|
||||
var step = _steps[_expiredIndex];
|
||||
|
||||
// Is the step ahead of us, or the next step rolled over and we didn't yet
|
||||
if (step - now > 0 || lastIndex > -1 && _steps[lastIndex] > step)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
lastIndex = _expiredIndex++;
|
||||
|
||||
if (_expiredIndex == stepsLength)
|
||||
{
|
||||
_expiredIndex -= stepsLength;
|
||||
}
|
||||
}
|
||||
|
||||
var stepsTaken = (_stepIndex < _expiredIndex ? _stepIndex + stepsLength : _stepIndex) - _expiredIndex;
|
||||
var maxSteps = _steps.Length - 1;
|
||||
|
||||
// Can we take a step?
|
||||
if (stepsTaken >= maxSteps)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var delay = Mobile.ComputeMovementSpeed(d);
|
||||
|
||||
var prev = _stepIndex - 1;
|
||||
if (prev < 0)
|
||||
{
|
||||
prev += stepsLength;
|
||||
}
|
||||
|
||||
// Give a 5% buffer on the first step
|
||||
_steps[_stepIndex++] = stepsTaken > 0 ? _steps[prev] + delay : now + delay * 950 / 1000;
|
||||
|
||||
if (_stepIndex == stepsLength)
|
||||
{
|
||||
_stepIndex -= stepsLength;
|
||||
}
|
||||
|
||||
// If CalcMoves.MaxSteps is modified, we need to adjust accordingly
|
||||
AdjustSteps(CalcMoves.MaxSteps);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AdjustSteps(int maxSteps)
|
||||
{
|
||||
var stepsLength = maxSteps + 1;
|
||||
|
||||
if (_steps.Length == stepsLength)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var oldSteps = _steps;
|
||||
_steps = new long[stepsLength];
|
||||
|
||||
var expiredIndex = _expiredIndex;
|
||||
var newStepIndex = 0;
|
||||
while (newStepIndex < maxSteps && expiredIndex != _stepIndex)
|
||||
{
|
||||
_steps[newStepIndex++] = oldSteps[expiredIndex++];
|
||||
if (expiredIndex >= oldSteps.Length)
|
||||
{
|
||||
expiredIndex -= oldSteps.Length;
|
||||
}
|
||||
}
|
||||
|
||||
_expiredIndex = 0;
|
||||
_stepIndex = newStepIndex;
|
||||
}
|
||||
}
|
||||
|
|
@ -76,6 +76,10 @@ public partial class NetState : IComparable<NetState>
|
|||
|
||||
public GCHandle Handle => _handle;
|
||||
|
||||
// Speed Hack Prevention
|
||||
internal long _movementCredit;
|
||||
internal long _nextMovementTime;
|
||||
|
||||
internal enum ParserState
|
||||
{
|
||||
AwaitingNextPacket,
|
||||
|
|
|
|||
|
|
@ -60,43 +60,6 @@ public static class OutgoingMovementPackets
|
|||
ns.Send(writer.Span);
|
||||
}
|
||||
|
||||
public static void SendInitialFastwalkStack(this NetState ns, uint[] keys)
|
||||
{
|
||||
if (ns.CannotSendPackets())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var writer = new SpanWriter(stackalloc byte[29]);
|
||||
writer.Write((byte)0xBF); // Packet ID
|
||||
writer.Write((ushort)29);
|
||||
writer.Write((ushort)0x1); // Subpacket
|
||||
writer.Write(keys[0]);
|
||||
writer.Write(keys[1]);
|
||||
writer.Write(keys[2]);
|
||||
writer.Write(keys[3]);
|
||||
writer.Write(keys[4]);
|
||||
writer.Write(keys[5]);
|
||||
|
||||
ns.Send(writer.Span);
|
||||
}
|
||||
|
||||
public static void SendFastwalkStackKey(this NetState ns, uint key = 0)
|
||||
{
|
||||
if (ns.CannotSendPackets())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var writer = new SpanWriter(stackalloc byte[9]);
|
||||
writer.Write((byte)0xBF); // Packet ID
|
||||
writer.Write((ushort)9);
|
||||
writer.Write((ushort)0x2); // Subpacket
|
||||
writer.Write(key);
|
||||
|
||||
ns.Send(writer.Span);
|
||||
}
|
||||
|
||||
public static void SendTimeSyncResponse(this NetState ns)
|
||||
{
|
||||
if (ns.CannotSendPackets())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue