using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Godot; public partial class GameManager : Node3D { [Export] public Control GameMenu { get; private set; } [Export] public QueueManager QueueManager { get; private set; } [Export] public Node3D SpaceRoot { get; private set; } [Export] public double closeTickInterval = 1; [Export] public double farTickInterval = 10; [Export] public int maxFarThreads = 16; public static bool Loading { get; private set; } = true; public static GameManager Instance { get; private set; } public static IGenerator Generator { get; private set; } public static Universe GameUniverse { get; private set; } public Player MainPlayer { get; private set; } public Dictionary Players { get; private set; } = []; private double closeTickTimer = 0; private double farTickTimer = 0; public bool simulatingClose = false; public bool simulatingFar = false; public bool playerReady = false; private readonly Dictionary spawnedObjects = []; private readonly ConcurrentQueue spawnQueue = []; public override void _EnterTree() { Instance = this; } public override void _Ready() { Generator = new TestGenerator(); if (Global.IsGameHost) { GameUniverse = Generator.GenerateUniverse(); Loading = false; } } private void OnPlayerReady() { playerReady = true; SpawnClose(); } public override void _ExitTree() { Loading = true; Instance = null; } public override void _Process(double delta) { if (!playerReady || !Global.IsGameHost) { return; } closeTickTimer += delta; if (closeTickTimer >= closeTickInterval && !simulatingClose) { SimulateClose(closeTickTimer); closeTickTimer = 0; } farTickTimer += delta; if (farTickTimer >= farTickInterval && !simulatingFar) { double taskFarTickTimer = farTickTimer; Task.Run(() => { SimulateFar(taskFarTickTimer); }); farTickTimer = 0; } } public Sector GetCurrentSector() { return MainPlayer.PlayerData.CurrentSector; } public Player GetPlayer(long id) { Players.TryGetValue(id, out Player player); return player; } private void SimulateClose(double delta) { simulatingClose = true; List sectorsClose = GetCurrentSector().GetNeighbouringSectors(); List tasks = []; sectorsClose.ForEach(sector => { Task simulateTask = Task.Run(() => { sector.Simulate(delta); }); tasks.Add(simulateTask); }); Task.WaitAll([.. tasks]); foreach (KeyValuePair player in Players) { List playerSectors = player.Value.PlayerData.CurrentSector.GetNeighbouringSectors(); playerSectors.ForEach(sector => { if (player.Key != 1) { sector.NetworkSync(player.Key); } }); } simulatingClose = false; } private void SimulateFar(double delta) { simulatingFar = true; List 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) { double taskDelta = delta; int taskStartX = x; int taskCountX = countX; Task clusteredTask = Task.Run(() => { SimulateFarClustered(taskDelta, taskStartX, taskCountX); }); tasks.Add(clusteredTask); } Task.WaitAll([.. tasks]); simulatingFar = false; } private void SimulateFarClustered(double delta, int startX, int countX) { 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++) { Thread.Sleep(5); for (int z = 0; z < sizeZ; z++) { 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); } } } } } private void SpawnClose() { List neighbours = GetCurrentSector().GetNeighbouringSectors(); neighbours.ForEach(sector => sector.SpawnObjects()); } public Player SpawnPlayer(Character character, long networkId, bool isMainPlayer = false) { Player player = character.InstantiatePlayer(); player.GameMenu = GameMenu; if (isMainPlayer) { MainPlayer = player; OnPlayerReady(); } Players.Add(networkId, player); character.UpdateSector(); SpaceRoot.CallDeferred("add_child", player); return player; } public void DespawnPlayer(Player player) { SpaceRoot.CallDeferred("remove_child", player); player.CallDeferred("queue_free"); } public void Spawn(GameObject gameObject) { spawnQueue.Enqueue(gameObject); CallDeferred(nameof(ProcessSpawnQueue)); } private void ProcessSpawnQueue() { spawnQueue.TryDequeue(out GameObject gameObject); if (gameObject == null) { return; } if (spawnedObjects.ContainsKey(gameObject)) { return; } Node3D instance = gameObject.Instantiate(GetCurrentSector()); spawnedObjects.Add(gameObject, instance); SpaceRoot.CallDeferred("add_child", instance); } public void Despawn(GameObject gameObject) { if (!spawnedObjects.ContainsKey(gameObject)) { return; } Node3D nodeToDespawn = spawnedObjects.GetValueOrDefault(gameObject); spawnedObjects.Remove(gameObject); SpaceRoot.CallDeferred("remove_child", nodeToDespawn); nodeToDespawn.CallDeferred("queue_free"); } public void ApplyOrigin() { Sector current = GetCurrentSector(); foreach (KeyValuePair player in Players) { player.Value.PlayerData.UpdateSectorOffset(current); player.Value.PlayerData.UpdateNodePosition(); } List nearby = current.GetNeighbouringSectors(); foreach (KeyValuePair spawned in spawnedObjects) { GameObject gameObject = spawned.Key; Node3D node = spawned.Value; if (!nearby.Contains(gameObject.CurrentSector)) { Despawn(gameObject); } else { gameObject.UpdateSectorOffset(current); node._Process(0); } } nearby.ForEach(sector => sector.SpawnObjects()); } public void SendUniverseToClient(long id) { RpcId(id, nameof(RpcDownloadUniverse), Generator.GetUniverseSize(), Generator.GetSectorSize()); } [Rpc(MultiplayerApi.RpcMode.Authority)] public void RpcDownloadUniverse(Vector3I universeSize, Vector3 sectorSize) { GameUniverse = Generator.InitializeEmptyUniverse(universeSize, sectorSize); Loading = false; } }