Skip to content

Commit

Permalink
Merge pull request #221 from InvalidArgument3/live-server-triage
Browse files Browse the repository at this point in the history
summary of changes for tonight
  • Loading branch information
InvalidArgument3 authored Oct 29, 2024
2 parents 0c16c5f + ac546e9 commit aa7a0c8
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

namespace DynamicAsteroids.Data.Scripts.DynamicAsteroids {
public class AsteroidZone {
public long PlayerId { get; set; }
public Vector3D Center { get; set; }
public double Radius { get; set; }
public int AsteroidCount { get; set; }
Expand Down Expand Up @@ -86,6 +87,7 @@ public bool IsDisabledDueToSpeed() {

public class AsteroidSpawner {
private ConcurrentBag<AsteroidEntity> _asteroids;
private ConcurrentDictionary<long, AsteroidEntity> _asteroidLookup = new ConcurrentDictionary<long, AsteroidEntity>();
private bool _canSpawnAsteroids = false;
private DateTime _worldLoadTime;
private Random rand;
Expand Down Expand Up @@ -263,7 +265,7 @@ public AsteroidSpawner(RealGasGiantsApi realGasGiantsApi) {
}

public bool IsAsteroidTracked(long entityId) {
return _asteroids.Any(a => a.EntityId == entityId);
return _asteroidLookup.ContainsKey(entityId);
}

public void Init(int seed) {
Expand Down Expand Up @@ -756,38 +758,38 @@ public IEnumerable<AsteroidEntity> GetAsteroids() {
}
public void AddAsteroid(AsteroidEntity asteroid) {
_asteroids.Add(asteroid);
_asteroidLookup[asteroid.EntityId] = asteroid;
_newAsteroidTimestamps[asteroid.EntityId] = DateTime.UtcNow;
Log.Info($"Added new asteroid {asteroid.EntityId} with grace period");
}
public bool TryRemoveAsteroid(AsteroidEntity asteroid) {
return _asteroids.TryTake(out asteroid);
}

private void RemoveAsteroid(AsteroidEntity asteroid) {
if (asteroid == null) return;

try {
if (MyAPIGateway.Session.IsServer) {
_pendingRemovals.Enqueue(asteroid.EntityId);
}

// Remove from all zone tracking
foreach (var zone in playerZones.Values) {
zone.ContainedAsteroids.Remove(asteroid.EntityId);
zone.TransferredFromOtherZone.Remove(asteroid.EntityId);
}

MyEntities.Remove(asteroid);
asteroid.Close();

AsteroidEntity removed;
_asteroids.TryTake(out removed);

AsteroidEntity removedFromLookup;
_asteroidLookup.TryRemove(asteroid.EntityId, out removedFromLookup);
Log.Info($"Successfully removed asteroid {asteroid.EntityId}");
}
catch (Exception ex) {
Log.Exception(ex, typeof(AsteroidSpawner), $"Error removing asteroid {asteroid?.EntityId}");
}
}

public void SpawnAsteroids(List<AsteroidZone> zones) {
if (!MyAPIGateway.Session.IsServer) return;
if (!AreAsteroidsEnabled()) return;
Expand Down
80 changes: 37 additions & 43 deletions Dynamic Asteroids/Data/Scripts/DynamicAsteroids/MainSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -932,69 +932,63 @@ public void UpdateClientZones(Dictionary<long, AsteroidZone> serverZones) {
private void ProcessZoneMessage(byte[] message) {
try {
var zonePacket = MyAPIGateway.Utilities.SerializeFromBinary<ZoneUpdatePacket>(message);
if (zonePacket?.Zones == null || zonePacket.Zones.Count == 0) return;
if (zonePacket == null || zonePacket.Zones == null || zonePacket.Zones.Count == 0) return;

// Clear all existing known asteroid IDs when zones change significantly
bool significantZoneChange = false;
bool zonesChanged = false;
foreach (var zoneData in zonePacket.Zones) {
Vector3D lastPos;
if (_lastProcessedZonePositions.TryGetValue(zoneData.PlayerId, out lastPos)) {
if (Vector3D.DistanceSquared(lastPos, zoneData.Center) > AsteroidSettings.ZoneRadius * AsteroidSettings.ZoneRadius) {
significantZoneChange = true;
break;
}
if (!_lastProcessedZonePositions.TryGetValue(zoneData.PlayerId, out lastPos) ||
Vector3D.DistanceSquared(lastPos, zoneData.Center) > 1) {
zonesChanged = true;
break;
}
else {
significantZoneChange = true;
}

if (!zonesChanged) return;

if (MyAPIGateway.Session.IsServer && !MyAPIGateway.Utilities.IsDedicated) {
if (_spawner != null) {
UpdateClientZones(_spawner.playerZones.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
}
return;
}

if (significantZoneChange) {
Log.Info("Significant zone change detected, clearing client state");
_knownAsteroidIds.Clear();
_serverPositions.Clear();
_serverRotations.Clear();
var previousZones = new Dictionary<long, AsteroidZone>(_clientZones);
_clientZones.Clear();

foreach (var zoneData in zonePacket.Zones) {
var newZone = new AsteroidZone(zoneData.Center, zoneData.Radius) {
IsMarkedForRemoval = !zoneData.IsActive,
IsMerged = zoneData.IsMerged,
CurrentSpeed = zoneData.CurrentSpeed,
PlayerId = zoneData.PlayerId
};
_clientZones[zoneData.PlayerId] = newZone;
previousZones.Remove(zoneData.PlayerId);
}

if (!MyAPIGateway.Session.IsServer) {
_clientZones.Clear();
foreach (var zoneData in zonePacket.Zones) {
var newZone = new AsteroidZone(zoneData.Center, zoneData.Radius) {
IsMarkedForRemoval = !zoneData.IsActive,
IsMerged = zoneData.IsMerged,
CurrentSpeed = zoneData.CurrentSpeed,
LastActiveTime = DateTime.UtcNow
};
_clientZones[zoneData.PlayerId] = newZone;
_lastProcessedZonePositions[zoneData.PlayerId] = zoneData.Center;
foreach (var removedZone in previousZones.Values) {
_lastRemovedZones.Enqueue(removedZone);
while (_lastRemovedZones.Count > 5) {
_lastRemovedZones.Dequeue();
}

// Clean up asteroids that are no longer in any zone
var entities = new HashSet<IMyEntity>();
MyAPIGateway.Entities.GetEntities(entities);
foreach (var entity in entities) {
var asteroid = entity as AsteroidEntity;
if (asteroid == null) continue;

bool inAnyZone = false;
foreach (var zone in _clientZones.Values) {
if (zone.IsPointInZone(asteroid.PositionComp.GetPosition())) {
inAnyZone = true;
break;
if (!MyAPIGateway.Session.IsServer) {
var entities = new HashSet<IMyEntity>();
MyAPIGateway.Entities.GetEntities(entities);
foreach (var entity in entities) {
var asteroid = entity as AsteroidEntity;
if (asteroid != null && removedZone.IsPointInZone(asteroid.PositionComp.GetPosition())) {
RemoveAsteroidOnClient(asteroid.EntityId);
}
}

if (!inAnyZone) {
RemoveAsteroidOnClient(asteroid.EntityId);
}
}
}
}
catch (Exception ex) {
Log.Exception(ex, typeof(MainSession), "Error processing zone packet");
}
}

private void OnSettingsSyncReceived(ushort handlerId, byte[] data, ulong steamId, bool isFromServer) {
if (!isFromServer) return;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,76 +8,105 @@
using VRage.Game.ModAPI;
using VRageMath;

namespace DynamicAsteroids.Data.Scripts.DynamicAsteroids {
public class ZoneNetworkManager {
namespace DynamicAsteroids.Data.Scripts.DynamicAsteroids
{
public class ZoneNetworkManager
{
private Dictionary<long, HashSet<long>> _playerZoneAwareness = new Dictionary<long, HashSet<long>>();
private const double ZONE_AWARENESS_RADIUS = 25000; // Only sync zones within this range
private const double ZONE_AWARENESS_RADIUS = 25000;

public void UpdateZoneAwareness(
Dictionary<long, Vector3D> playerPositions,
ConcurrentDictionary<long, AsteroidZone> zones) {
foreach (var player in playerPositions) {
if (!_playerZoneAwareness.ContainsKey(player.Key)) {
_playerZoneAwareness[player.Key] = new HashSet<long>();
}
public void UpdateZoneAwareness(Dictionary<long, Vector3D> playerPositions,
ConcurrentDictionary<long, AsteroidZone> zones)
{
// Clear old awareness data
_playerZoneAwareness.Clear();

// Build new awareness data
foreach (var playerKvp in playerPositions)
{
var playerPos = playerKvp.Value;
var playerid = playerKvp.Key;

// First, add player's own zone
var ownZone = zones.FirstOrDefault(z => z.Value.IsPointInZone(playerPos));
if (ownZone.Value != null)
{
if (!_playerZoneAwareness.ContainsKey(playerid))
{
_playerZoneAwareness[playerid] = new HashSet<long>();
}

_playerZoneAwareness[playerid].Add(ownZone.Key);

// Find zones this player should be aware of
var relevantZones = zones.Where(z =>
Vector3D.Distance(player.Value, z.Value.Center) <= ZONE_AWARENESS_RADIUS);
// Then check for merged zones
foreach (var otherZone in zones)
{
if (otherZone.Key == ownZone.Key) continue;

_playerZoneAwareness[player.Key] = new HashSet<long>(relevantZones.Select(z => z.Key));
double distance = Vector3D.Distance(ownZone.Value.Center, otherZone.Value.Center);
if (distance <= ownZone.Value.Radius + otherZone.Value.Radius)
{
_playerZoneAwareness[playerid].Add(otherZone.Key);
}
}
}
}
}

public void SendBatchedUpdates(List<AsteroidState> updates, ConcurrentDictionary<long, AsteroidZone> zones) {
if (updates == null || updates.Count == 0 || zones == null || zones.Count == 0) {
public void SendBatchedUpdates(List<AsteroidState> updates, ConcurrentDictionary<long, AsteroidZone> zones)
{
if (updates == null || updates.Count == 0 || zones == null || zones.Count == 0)
{
return;
}

// Group updates by zone
var updatesByZone = updates.GroupBy(u => {
var updatesByZone = updates.GroupBy(u =>
{
var zone = zones.FirstOrDefault(z => z.Value.IsPointInZone(u.Position));
return zone.Key;
}).Where(g => g.Key != 0); // Filter out updates with no zone

if (!updatesByZone.Any()) return;
}).Where(g => g.Key != 0);

foreach (var zoneGroup in updatesByZone) {
// Find players who should receive these updates
foreach (var zoneGroup in updatesByZone)
{
// Find players who should receive updates for this zone
var relevantPlayers = _playerZoneAwareness
.Where(p => p.Value.Contains(zoneGroup.Key))
.Select(p => p.Key)
.ToList();

if (relevantPlayers.Count == 0) continue;

// Send batched updates only to relevant players
// Send updates in batches
const int MAX_UPDATES_PER_PACKET = 25;
for (int i = 0; i < zoneGroup.Count(); i += MAX_UPDATES_PER_PACKET) {
for (int i = 0; i < zoneGroup.Count(); i += MAX_UPDATES_PER_PACKET)
{
var batch = zoneGroup.Skip(i).Take(MAX_UPDATES_PER_PACKET).ToList();
if (batch.Count == 0) continue;

var packet = new AsteroidBatchUpdatePacket();
packet.Updates.AddRange(batch);

byte[] data = MyAPIGateway.Utilities.SerializeToBinary(packet);

// Send only to players who care about this zone
foreach (var playerId in relevantPlayers) {
var steamId = GetSteamId(playerId);
if (steamId != 0) {
foreach (var playerId in relevantPlayers)
{
var steamId = NetworkUtils.GetSteamId(playerId);
if (steamId != 0)
{
MyAPIGateway.Multiplayer.SendMessageTo(32000, data, steamId);
}
}
}
}
}
}

private ulong GetSteamId(long playerId) {
public static class NetworkUtils {
public static ulong GetSteamId(long playerId) {
var players = new List<IMyPlayer>();
MyAPIGateway.Players.GetPlayers(players);
var player = players.FirstOrDefault(p => p.IdentityId == playerId);
return player?.SteamUserId ?? 0;
return player != null ? player.SteamUserId : 0;
}
}
}
}

0 comments on commit aa7a0c8

Please sign in to comment.