Skip to content

Commit

Permalink
Merge pull request #214 from InvalidArgument3/live-server-triage
Browse files Browse the repository at this point in the history
anti ddos networking
  • Loading branch information
InvalidArgument3 authored Oct 27, 2024
2 parents d60a29a + e5d5a6b commit 5fb813b
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 64 deletions.
20 changes: 0 additions & 20 deletions ConfigurableGridPoints/Data/minified_output.txt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public static class AsteroidSettings
public static int NetworkMessageInterval = 60;
public static int SpawnInterval = 6;
public static int UpdateInterval = 60;
public static int NetworkUpdateInterval = 6; //this is the network metal pipe noise
public static int MaxAsteroidCount = 20000;
public static int MaxAsteroidsPerZone = 100;
public static int MaxTotalAttempts = 100;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using DynamicAsteroids.Data.Scripts.DynamicAsteroids.AsteroidEntities;
using ProtoBuf;
using Sandbox.ModAPI;
using System;
using System.Collections.Generic;
using VRageMath;

Expand Down Expand Up @@ -48,9 +49,20 @@ public AsteroidState(AsteroidEntity asteroid) {
EntityId = asteroid.EntityId;
}

private float GetQuaternionAngleDifference(Quaternion a, Quaternion b) {
float dot = a.X * b.X + a.Y * b.Y + a.Z * b.Z + a.W * b.W;
dot = MathHelper.Clamp(dot, -1f, 1f);
return 2f * (float)Math.Acos(Math.Abs(dot));
}

public bool HasChanged(AsteroidEntity asteroid) {
return Vector3D.DistanceSquared(Position, asteroid.PositionComp.GetPosition()) > 0.01
|| Vector3D.DistanceSquared(Velocity, asteroid.Physics.LinearVelocity) > 0.01;
const double POSITION_THRESHOLD = 0.01;
const double VELOCITY_THRESHOLD = 0.01;
const double ROTATION_THRESHOLD = 0.01;

return Vector3D.DistanceSquared(Position, asteroid.PositionComp.GetPosition()) > POSITION_THRESHOLD ||
Vector3D.DistanceSquared(Velocity, asteroid.Physics.LinearVelocity) > VELOCITY_THRESHOLD ||
GetQuaternionAngleDifference(Rotation, Quaternion.CreateFromRotationMatrix(asteroid.WorldMatrix)) > ROTATION_THRESHOLD;
}
}

Expand Down
136 changes: 95 additions & 41 deletions Dynamic Asteroids/Data/Scripts/DynamicAsteroids/AsteroidSpawner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,8 @@ private void CleanupOrphanedAsteroids() {
private DateTime _lastCleanupTime = DateTime.MinValue;
private bool _isCleanupRunning = false;
private const double CLEANUP_COOLDOWN_SECONDS = 10.0; // Minimum time between cleanups



public void UpdateTick() {
if (!MyAPIGateway.Session.IsServer)
return;
Expand All @@ -997,6 +998,11 @@ public void UpdateTick() {
}
}

_networkMessageTimer++;
if (_networkMessageTimer >= AsteroidSettings.NetworkUpdateInterval) {
SendNetworkMessages();
_networkMessageTimer = 0;
}
// Normal update logic...
if (_updateIntervalTimer <= 0) {
UpdateAsteroids(playerZones.Values.ToList());
Expand Down Expand Up @@ -1025,7 +1031,6 @@ public void UpdateTick() {
Log.Exception(ex, typeof(AsteroidSpawner), "Error triggering validation");
}
}
//CleanupZones();

// Check if cleanup should run
if (!_isCleanupRunning && (DateTime.UtcNow - _lastCleanupTime).TotalSeconds >= CLEANUP_COOLDOWN_SECONDS) {
Expand Down Expand Up @@ -1111,28 +1116,36 @@ private void UpdateAsteroids(List<AsteroidZone> zones) {
}

#endregion

#region Network Management

private int _networkMessageTimer = 0;

public void SendNetworkMessages() {
if (!MyAPIGateway.Session.IsServer || !MyAPIGateway.Utilities.IsDedicated)
return; // Skip in single-player or client-hosted games
return;

try {
int messagesSent = 0;
// Process immediate messages first
int immediateMessagesSent = 0;
_messageCache.ProcessMessages(message => {
if (message.EntityId == 0) {
Log.Warning("Attempted to send message for asteroid with ID 0");
return;
}

// Get the actual asteroid to ensure it still exists
AsteroidEntity asteroid = MyEntities.GetEntityById(message.EntityId) as AsteroidEntity;
if (asteroid == null) {
Log.Warning($"Attempted to send update for non-existent asteroid {message.EntityId}");
return;
}

// Create fresh message with current data
// Only send if the asteroid has changed significantly
var state = _stateCache.GetState(asteroid.EntityId);
if (state != null && !state.HasChanged(asteroid)) {
return;
}

AsteroidNetworkMessage updateMessage = new AsteroidNetworkMessage(
asteroid.PositionComp.GetPosition(),
asteroid.Properties.Diameter,
Expand All @@ -1142,73 +1155,84 @@ public void SendNetworkMessages() {
false,
asteroid.EntityId,
false,
false, // This is an update, not initial creation
false,
Quaternion.CreateFromRotationMatrix(asteroid.WorldMatrix)
);

byte[] messageBytes = MyAPIGateway.Utilities.SerializeToBinary(updateMessage);

if (messageBytes == null || messageBytes.Length == 0) {
Log.Warning("Failed to serialize network message");
return;
}

Log.Info($"Server: Sending position update for asteroid ID {updateMessage.EntityId}, " +
$"Position: {updateMessage.GetPosition()}, " +
$"Velocity: {updateMessage.GetVelocity()}, " +
$"Data size: {messageBytes.Length} bytes");

MyAPIGateway.Multiplayer.SendMessageToOthers(32000, messageBytes);
messagesSent++;
immediateMessagesSent++;
});

if (messagesSent > 0) {
Log.Info($"Server: Successfully sent {messagesSent} asteroid updates");
// Process batched updates if it's time
if (_networkMessageTimer >= AsteroidSettings.NetworkUpdateInterval) {
var batchPacket = new AsteroidBatchUpdatePacket();
List<IMyPlayer> players = new List<IMyPlayer>();
MyAPIGateway.Players.GetPlayers(players);

foreach (AsteroidEntity asteroid in _asteroids) {
if (asteroid == null || asteroid.MarkedForClose) continue;

bool shouldUpdate = false;
foreach (IMyPlayer player in players) {
if (ShouldUpdateAsteroid(asteroid, player.GetPosition())) {
shouldUpdate = true;
break;
}
}

if (shouldUpdate) {
_stateCache.UpdateState(asteroid);
}
}

var dirtyAsteroids = _stateCache.GetDirtyAsteroids();
if (dirtyAsteroids.Count > 0) {
SendBatchedUpdates(dirtyAsteroids);
}

_stateCache.ClearDirtyStates();
}

if (immediateMessagesSent > 0) {
Log.Info($"Server: Successfully sent {immediateMessagesSent} immediate asteroid updates");
}
}
catch (Exception ex) {
Log.Exception(ex, typeof(AsteroidSpawner), "Error sending network messages");
}
}
public void SendPositionUpdates() {
if (!MyAPIGateway.Session.IsServer)
return;
if (!MyAPIGateway.Session.IsServer) return;

try {
var batchPacket = new AsteroidBatchUpdatePacket();
List<IMyPlayer> players = new List<IMyPlayer>();
MyAPIGateway.Players.GetPlayers(players);

foreach (AsteroidEntity asteroid in _asteroids) {
if (asteroid == null || asteroid.MarkedForClose)
continue;

bool isNearPlayer = false;
Vector3D asteroidPos = asteroid.PositionComp.GetPosition();
if (asteroid == null || asteroid.MarkedForClose) continue;

bool shouldUpdate = false;
foreach (IMyPlayer player in players) {
double distSquared = Vector3D.DistanceSquared(player.GetPosition(), asteroidPos);
if (distSquared <= AsteroidSettings.ZoneRadius * AsteroidSettings.ZoneRadius) {
isNearPlayer = true;
if (ShouldUpdateAsteroid(asteroid, player.GetPosition())) {
shouldUpdate = true;
break;
}
}

if (!isNearPlayer)
continue;

_stateCache.UpdateState(asteroid);
if (shouldUpdate) {
_stateCache.UpdateState(asteroid);
}
}

var dirtyAsteroids = _stateCache.GetDirtyAsteroids();
foreach (AsteroidState state in dirtyAsteroids) {
batchPacket.Updates.Add(state);
}

if (batchPacket.Updates.Count > 0) {
byte[] messageBytes = MyAPIGateway.Utilities.SerializeToBinary(batchPacket);
MyAPIGateway.Multiplayer.SendMessageToOthers(32000, messageBytes);
Log.Info($"Server: Sent batch update for {batchPacket.Updates.Count} changed asteroids");
if (dirtyAsteroids.Count > 0) {
SendBatchedUpdates(dirtyAsteroids);
}

_stateCache.ClearDirtyStates();
Expand Down Expand Up @@ -1254,6 +1278,35 @@ public void ProcessAsteroidUpdates() {
updatesProcessed++;
}
}
private void SendBatchedUpdates(List<AsteroidState> updates) {
const int MAX_UPDATES_PER_PACKET = 50;

// Sort updates by priority (distance to closest player)
updates.Sort((a, b) =>
GetDistanceToClosestPlayer(a.Position)
.CompareTo(GetDistanceToClosestPlayer(b.Position)));

// Split into smaller batches
for (int i = 0; i < updates.Count; i += MAX_UPDATES_PER_PACKET) {
var batch = updates.Skip(i).Take(MAX_UPDATES_PER_PACKET);
var packet = new AsteroidBatchUpdatePacket();
packet.Updates.AddRange(batch);

byte[] data = MyAPIGateway.Utilities.SerializeToBinary(packet);
MyAPIGateway.Multiplayer.SendMessageToOthers(32000, data);

Log.Info($"Sent batch update containing {batch.Count()} asteroids");
}
}
private bool ShouldUpdateAsteroid(AsteroidEntity asteroid, Vector3D playerPos) {
double distance = Vector3D.Distance(asteroid.PositionComp.GetPosition(), playerPos);

// Update closer asteroids more frequently
if (distance < 1000) return _networkMessageTimer % 2 == 0;
if (distance < 5000) return _networkMessageTimer % 10 == 0;
if (distance < 10000) return _networkMessageTimer % 20 == 0;
return _networkMessageTimer % 30 == 0;
}
#endregion


Expand Down Expand Up @@ -1351,7 +1404,7 @@ public double GetSmoothedSpeed() {
}
}
#endregion

#region Utility Methods
private double GetDistanceToClosestPlayer(Vector3D position) {
double minDistance = double.MaxValue;
Expand All @@ -1369,6 +1422,7 @@ private Vector3D RandVector() {
return Math.Pow(rand.NextDouble(), 1 / 3d) * new Vector3D(sinPhi * Math.Cos(theta), sinPhi * Math.Sin(theta), Math.Cos(phi));
}
#endregion

private const int VALIDATION_BATCH_SIZE = 100; // Only check this many entities per validation
private const double VALIDATION_MAX_TIME_MS = 16.0; // Max milliseconds to spend on validation (1 frame at 60fps)
private int _lastValidatedIndex = 0; // Track where we left off
Expand Down
1 change: 0 additions & 1 deletion Dynamic Asteroids/Data/Scripts/minified_output.txt

This file was deleted.

0 comments on commit 5fb813b

Please sign in to comment.