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 closeDistance = 50; [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; } = []; public Dictionary> PlayerSpawnedObjects { 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 localSpawnedObjects = []; 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; } 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); SyncClose(); closeTickTimer = 0; } farTickTimer += delta; if (farTickTimer >= farTickInterval && !simulatingFar && QueueManager.SectorReassignQueue.IsEmpty) { 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; HashSet objectsToSimulate = []; foreach (KeyValuePair player in Players) { Character character = player.Value.PlayerData; List playerSectors = character.CurrentSector.GetNeighbouringSectors(); playerSectors.ForEach(sector => { sector.GameObjects.ForEach(gameObject => { 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 player in Players) { bool isHostPlayer = player.Key == 1; long playerKey = player.Key; Character character = player.Value.PlayerData; PlayerSpawnedObjects.TryGetValue(playerKey, out HashSet playerSpawnedObjects); playerSpawnedObjects ??= []; HashSet playerNewSpawnedObjects = []; List 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; Sector[,,] sectors = GameUniverse.Sectors; HashSet allSpawnedObjects = []; foreach (HashSet item in PlayerSpawnedObjects.Values) { foreach (GameObject gameObject in item) { allSpawnedObjects.Add(gameObject); } } int sizeX = sectors.GetLength(0); double taskDelta = delta; Parallel.For(0, sizeX, x => { SimulateFarClustered(allSpawnedObjects, taskDelta, x); }); simulatingFar = false; } private static void SimulateFarClustered(HashSet ignoreObjects, double delta, int x) { Sector[,,] sectors = GameUniverse.Sectors; int sizeY = sectors.GetLength(1); int sizeZ = sectors.GetLength(2); for (int y = 0; y < sizeY; y++) { for (int z = 0; z < sizeZ; z++) { try { sectors[x, y, z].Simulate(delta, ignoreObjects); } catch (Exception e) { QueueManager.LogQueue.Enqueue("EXCEPTION: " + e.Message); } } } } 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, long id) { Players.Remove(id); 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 (localSpawnedObjects.ContainsKey(gameObject)) { return; } Node3D instance = gameObject.Instantiate(GetCurrentSector()); localSpawnedObjects.Add(gameObject, instance); SpaceRoot.CallDeferred("add_child", instance); } public void Despawn(GameObject gameObject) { if (!localSpawnedObjects.ContainsKey(gameObject)) { return; } 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(); foreach (KeyValuePair player in Players) { player.Value.PlayerData.UpdateSectorOffset(current); player.Value.PlayerData.UpdateNodePosition(); } foreach (KeyValuePair spawned in localSpawnedObjects) { GameObject gameObject = spawned.Key; Node3D node = spawned.Value; gameObject.UpdateSectorOffset(current); node._Process(0); } } 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; } }