Refactor; Optimize simulation

This commit is contained in:
Aslan 2026-02-22 14:43:43 -05:00
parent 8581cf6fb8
commit 0ef5652cea
16 changed files with 508 additions and 651 deletions

View file

@ -11,6 +11,7 @@ public partial class GameManager : Node3D
[Export] public QueueManager QueueManager { get; private set; }
[Export] public Node3D SpaceRoot { get; private set; }
[Export] public double closeDistance = 50;
[Export] public double closeTickInterval = 1;
[Export] public double farTickInterval = 10;
[Export] public int maxFarThreads = 16;
@ -21,6 +22,7 @@ public partial class GameManager : Node3D
public static Universe GameUniverse { get; private set; }
public Player MainPlayer { get; private set; }
public Dictionary<long, Player> Players { get; private set; } = [];
public Dictionary<long, HashSet<GameObject>> PlayerSpawnedObjects { get; private set; } = [];
private double closeTickTimer = 0;
private double farTickTimer = 0;
@ -29,7 +31,7 @@ public partial class GameManager : Node3D
public bool simulatingFar = false;
public bool playerReady = false;
private readonly Dictionary<GameObject, Node3D> spawnedObjects = [];
private readonly Dictionary<GameObject, Node3D> localSpawnedObjects = [];
private readonly ConcurrentQueue<GameObject> spawnQueue = [];
public override void _EnterTree()
@ -51,7 +53,6 @@ public partial class GameManager : Node3D
private void OnPlayerReady()
{
playerReady = true;
SpawnClose();
}
public override void _ExitTree()
@ -71,11 +72,12 @@ public partial class GameManager : Node3D
if (closeTickTimer >= closeTickInterval && !simulatingClose)
{
SimulateClose(closeTickTimer);
SyncClose();
closeTickTimer = 0;
}
farTickTimer += delta;
if (farTickTimer >= farTickInterval && !simulatingFar)
if (farTickTimer >= farTickInterval && !simulatingFar && QueueManager.SectorReassignQueue.IsEmpty)
{
double taskFarTickTimer = farTickTimer;
Task.Run(() =>
@ -102,123 +104,131 @@ public partial class GameManager : Node3D
{
simulatingClose = true;
List<Sector> sectorsClose = GetCurrentSector().GetNeighbouringSectors();
List<Task> tasks = [];
sectorsClose.ForEach(sector =>
{
Task simulateTask = Task.Run(() =>
{
sector.Simulate(delta);
});
tasks.Add(simulateTask);
});
Task.WaitAll([.. tasks]);
HashSet<GameObject> objectsToSimulate = [];
foreach (KeyValuePair<long, Player> player in Players)
{
List<Sector> playerSectors = player.Value.PlayerData.CurrentSector.GetNeighbouringSectors();
Character character = player.Value.PlayerData;
List<Sector> playerSectors = character.CurrentSector.GetNeighbouringSectors();
playerSectors.ForEach(sector =>
{
if (player.Key != 1)
sector.GameObjects.ForEach(gameObject =>
{
sector.NetworkSync(player.Key);
}
double distance = character.GetDistanceToObject(gameObject);
if (distance <= closeDistance)
{
objectsToSimulate.Add(gameObject);
}
});
});
}
foreach (GameObject gameObject in objectsToSimulate)
{
gameObject.Simulate(delta);
}
simulatingClose = false;
}
private void SyncClose()
{
foreach (KeyValuePair<long, Player> player in Players)
{
bool isHostPlayer = player.Key == 1;
long playerKey = player.Key;
Character character = player.Value.PlayerData;
PlayerSpawnedObjects.TryGetValue(playerKey, out HashSet<GameObject> playerSpawnedObjects);
playerSpawnedObjects ??= [];
HashSet<GameObject> playerNewSpawnedObjects = [];
List<Sector> playerSectors = character.CurrentSector.GetNeighbouringSectors();
// Spawn/Send object
playerSectors.ForEach(sector =>
{
sector.GameObjects.ForEach(gameObject =>
{
double distance = character.GetDistanceToObject(gameObject);
if (distance <= closeDistance)
{
if (isHostPlayer)
Spawn(gameObject);
else
gameObject.NetworkWrite(playerKey, !playerSpawnedObjects.Contains(gameObject));
playerNewSpawnedObjects.Add(gameObject);
}
});
});
// Despawn object
foreach (GameObject gameObject in playerSpawnedObjects)
{
if (!playerNewSpawnedObjects.Contains(gameObject))
{
if (isHostPlayer)
Despawn(gameObject);
else
RPCNode.Instance.RpcId(playerKey, nameof(RPCNode.RpcDespawnGameObject), gameObject.UUID.ToString());
}
}
PlayerSpawnedObjects[playerKey] = playerNewSpawnedObjects;
}
}
private void SimulateFar(double delta)
{
simulatingFar = true;
List<Task> tasks = [];
Sector[,,] sectors = GameUniverse.Sectors;
int sizeX = sectors.GetLength(0);
int countX = Mathf.Clamp(sizeX / maxFarThreads, 1, int.MaxValue);
for (int x = 0; x < sizeX; x += countX)
HashSet<GameObject> allSpawnedObjects = [];
foreach (HashSet<GameObject> item in PlayerSpawnedObjects.Values)
{
double taskDelta = delta;
int taskStartX = x;
int taskCountX = countX;
Task clusteredTask = Task.Run(() =>
foreach (GameObject gameObject in item)
{
SimulateFarClustered(taskDelta, taskStartX, taskCountX);
});
tasks.Add(clusteredTask);
allSpawnedObjects.Add(gameObject);
}
}
Task.WaitAll([.. tasks]);
int sizeX = sectors.GetLength(0);
double taskDelta = delta;
Parallel.For(0, sizeX, x =>
{
SimulateFarClustered(allSpawnedObjects, taskDelta, x);
});
simulatingFar = false;
}
private void SimulateFarClustered(double delta, int startX, int countX)
private static void SimulateFarClustered(HashSet<GameObject> ignoreObjects, double delta, int x)
{
Vector3I currentCoordinates = GetCurrentSector().Coordinates;
Sector[,,] sectors = GameUniverse.Sectors;
int sizeX = sectors.GetLength(0);
int sizeY = sectors.GetLength(1);
int sizeZ = sectors.GetLength(2);
int startSkipX = Mathf.Clamp(currentCoordinates.X - 1, 0, sizeX);
int startSkipY = Mathf.Clamp(currentCoordinates.Y - 1, 0, sizeY);
int startSkipZ = Mathf.Clamp(currentCoordinates.Z - 1, 0, sizeZ);
int endSkipX = Mathf.Clamp(currentCoordinates.X + 1, 0, sizeX);
int endSkipY = Mathf.Clamp(currentCoordinates.Y + 1, 0, sizeY);
int endSkipZ = Mathf.Clamp(currentCoordinates.Z + 1, 0, sizeZ);
int endX = Mathf.Clamp(startX + countX, 0, sizeX);
for (int x = startX; x < endX; x++)
for (int y = 0; y < sizeY; y++)
{
for (int y = 0; y < sizeY; y++)
for (int z = 0; z < sizeZ; z++)
{
Thread.Sleep(5);
for (int z = 0; z < sizeZ; z++)
try
{
if (Helpers.IsBetweenInclusive(x, startSkipX, endSkipX))
{
if (Helpers.IsBetweenInclusive(y, startSkipY, endSkipY))
{
if (Helpers.IsBetweenInclusive(z, startSkipZ, endSkipZ))
{
continue;
}
}
}
try
{
sectors[x, y, z].Simulate(delta);
}
catch (Exception e)
{
QueueManager.LogQueue.Enqueue("EXCEPTION: " + e.Message);
}
sectors[x, y, z].Simulate(delta, ignoreObjects);
}
catch (Exception e)
{
QueueManager.LogQueue.Enqueue("EXCEPTION: " + e.Message);
}
}
}
}
private void SpawnClose()
{
List<Sector> neighbours = GetCurrentSector().GetNeighbouringSectors();
neighbours.ForEach(sector => sector.SpawnObjects());
}
public Player SpawnPlayer(Character character, long networkId, bool isMainPlayer = false)
{
Player player = character.InstantiatePlayer();
@ -238,8 +248,10 @@ public partial class GameManager : Node3D
return player;
}
public void DespawnPlayer(Player player)
public void DespawnPlayer(Player player, long id)
{
Players.Remove(id);
SpaceRoot.CallDeferred("remove_child", player);
player.CallDeferred("queue_free");
}
@ -257,31 +269,43 @@ public partial class GameManager : Node3D
{
return;
}
if (spawnedObjects.ContainsKey(gameObject))
if (localSpawnedObjects.ContainsKey(gameObject))
{
return;
}
Node3D instance = gameObject.Instantiate(GetCurrentSector());
spawnedObjects.Add(gameObject, instance);
localSpawnedObjects.Add(gameObject, instance);
SpaceRoot.CallDeferred("add_child", instance);
}
public void Despawn(GameObject gameObject)
{
if (!spawnedObjects.ContainsKey(gameObject))
if (!localSpawnedObjects.ContainsKey(gameObject))
{
return;
}
Node3D nodeToDespawn = spawnedObjects.GetValueOrDefault(gameObject);
spawnedObjects.Remove(gameObject);
Node3D nodeToDespawn = localSpawnedObjects.GetValueOrDefault(gameObject);
localSpawnedObjects.Remove(gameObject);
SpaceRoot.CallDeferred("remove_child", nodeToDespawn);
nodeToDespawn.CallDeferred("queue_free");
}
public void Despawn(Guid uuid)
{
foreach (GameObject gameObject in localSpawnedObjects.Keys)
{
if (gameObject.UUID == uuid)
{
Despawn(gameObject);
break;
}
}
}
public void ApplyOrigin()
{
Sector current = GetCurrentSector();
@ -292,24 +316,14 @@ public partial class GameManager : Node3D
player.Value.PlayerData.UpdateNodePosition();
}
List<Sector> nearby = current.GetNeighbouringSectors();
foreach (KeyValuePair<GameObject, Node3D> spawned in spawnedObjects)
foreach (KeyValuePair<GameObject, Node3D> spawned in localSpawnedObjects)
{
GameObject gameObject = spawned.Key;
Node3D node = spawned.Value;
if (!nearby.Contains(gameObject.CurrentSector))
{
Despawn(gameObject);
}
else
{
gameObject.UpdateSectorOffset(current);
node._Process(0);
}
gameObject.UpdateSectorOffset(current);
node._Process(0);
}
nearby.ForEach(sector => sector.SpawnObjects());
}
public void SendUniverseToClient(long id)