Compare commits

...

2 commits

16 changed files with 508 additions and 651 deletions

6
global.json Normal file
View file

@ -0,0 +1,6 @@
{
"sdk": {
"version": "8.0.123",
"rollForward": "disable"
}
}

View file

@ -11,6 +11,7 @@ public partial class GameManager : Node3D
[Export] public QueueManager QueueManager { get; private set; } [Export] public QueueManager QueueManager { get; private set; }
[Export] public Node3D SpaceRoot { get; private set; } [Export] public Node3D SpaceRoot { get; private set; }
[Export] public double closeDistance = 50;
[Export] public double closeTickInterval = 1; [Export] public double closeTickInterval = 1;
[Export] public double farTickInterval = 10; [Export] public double farTickInterval = 10;
[Export] public int maxFarThreads = 16; [Export] public int maxFarThreads = 16;
@ -21,6 +22,7 @@ public partial class GameManager : Node3D
public static Universe GameUniverse { get; private set; } public static Universe GameUniverse { get; private set; }
public Player MainPlayer { get; private set; } public Player MainPlayer { get; private set; }
public Dictionary<long, Player> Players { 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 closeTickTimer = 0;
private double farTickTimer = 0; private double farTickTimer = 0;
@ -29,7 +31,7 @@ public partial class GameManager : Node3D
public bool simulatingFar = false; public bool simulatingFar = false;
public bool playerReady = false; public bool playerReady = false;
private readonly Dictionary<GameObject, Node3D> spawnedObjects = []; private readonly Dictionary<GameObject, Node3D> localSpawnedObjects = [];
private readonly ConcurrentQueue<GameObject> spawnQueue = []; private readonly ConcurrentQueue<GameObject> spawnQueue = [];
public override void _EnterTree() public override void _EnterTree()
@ -51,7 +53,6 @@ public partial class GameManager : Node3D
private void OnPlayerReady() private void OnPlayerReady()
{ {
playerReady = true; playerReady = true;
SpawnClose();
} }
public override void _ExitTree() public override void _ExitTree()
@ -71,11 +72,12 @@ public partial class GameManager : Node3D
if (closeTickTimer >= closeTickInterval && !simulatingClose) if (closeTickTimer >= closeTickInterval && !simulatingClose)
{ {
SimulateClose(closeTickTimer); SimulateClose(closeTickTimer);
SyncClose();
closeTickTimer = 0; closeTickTimer = 0;
} }
farTickTimer += delta; farTickTimer += delta;
if (farTickTimer >= farTickInterval && !simulatingFar) if (farTickTimer >= farTickInterval && !simulatingFar && QueueManager.SectorReassignQueue.IsEmpty)
{ {
double taskFarTickTimer = farTickTimer; double taskFarTickTimer = farTickTimer;
Task.Run(() => Task.Run(() =>
@ -102,106 +104,122 @@ public partial class GameManager : Node3D
{ {
simulatingClose = true; simulatingClose = true;
List<Sector> sectorsClose = GetCurrentSector().GetNeighbouringSectors(); HashSet<GameObject> objectsToSimulate = [];
List<Task> tasks = [];
sectorsClose.ForEach(sector =>
{
Task simulateTask = Task.Run(() =>
{
sector.Simulate(delta);
});
tasks.Add(simulateTask);
});
Task.WaitAll([.. tasks]);
foreach (KeyValuePair<long, Player> player in Players) 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 => 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; 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) private void SimulateFar(double delta)
{ {
simulatingFar = true; simulatingFar = true;
List<Task> tasks = [];
Sector[,,] sectors = GameUniverse.Sectors; Sector[,,] sectors = GameUniverse.Sectors;
int sizeX = sectors.GetLength(0); HashSet<GameObject> allSpawnedObjects = [];
int countX = Mathf.Clamp(sizeX / maxFarThreads, 1, int.MaxValue); foreach (HashSet<GameObject> item in PlayerSpawnedObjects.Values)
for (int x = 0; x < sizeX; x += countX)
{ {
double taskDelta = delta; foreach (GameObject gameObject in item)
int taskStartX = x;
int taskCountX = countX;
Task clusteredTask = Task.Run(() =>
{ {
SimulateFarClustered(taskDelta, taskStartX, taskCountX); allSpawnedObjects.Add(gameObject);
}); }
tasks.Add(clusteredTask);
} }
Task.WaitAll([.. tasks]); int sizeX = sectors.GetLength(0);
double taskDelta = delta;
Parallel.For(0, sizeX, x =>
{
SimulateFarClustered(allSpawnedObjects, taskDelta, x);
});
simulatingFar = false; 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; Sector[,,] sectors = GameUniverse.Sectors;
int sizeX = sectors.GetLength(0);
int sizeY = sectors.GetLength(1); int sizeY = sectors.GetLength(1);
int sizeZ = sectors.GetLength(2); 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++)
{ {
Thread.Sleep(5);
for (int z = 0; z < sizeZ; z++) 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 try
{ {
sectors[x, y, z].Simulate(delta); sectors[x, y, z].Simulate(delta, ignoreObjects);
} }
catch (Exception e) catch (Exception e)
{ {
@ -210,14 +228,6 @@ public partial class GameManager : Node3D
} }
} }
} }
}
private void SpawnClose()
{
List<Sector> neighbours = GetCurrentSector().GetNeighbouringSectors();
neighbours.ForEach(sector => sector.SpawnObjects());
}
public Player SpawnPlayer(Character character, long networkId, bool isMainPlayer = false) public Player SpawnPlayer(Character character, long networkId, bool isMainPlayer = false)
{ {
@ -238,8 +248,10 @@ public partial class GameManager : Node3D
return player; return player;
} }
public void DespawnPlayer(Player player) public void DespawnPlayer(Player player, long id)
{ {
Players.Remove(id);
SpaceRoot.CallDeferred("remove_child", player); SpaceRoot.CallDeferred("remove_child", player);
player.CallDeferred("queue_free"); player.CallDeferred("queue_free");
} }
@ -257,31 +269,43 @@ public partial class GameManager : Node3D
{ {
return; return;
} }
if (spawnedObjects.ContainsKey(gameObject)) if (localSpawnedObjects.ContainsKey(gameObject))
{ {
return; return;
} }
Node3D instance = gameObject.Instantiate(GetCurrentSector()); Node3D instance = gameObject.Instantiate(GetCurrentSector());
spawnedObjects.Add(gameObject, instance); localSpawnedObjects.Add(gameObject, instance);
SpaceRoot.CallDeferred("add_child", instance); SpaceRoot.CallDeferred("add_child", instance);
} }
public void Despawn(GameObject gameObject) public void Despawn(GameObject gameObject)
{ {
if (!spawnedObjects.ContainsKey(gameObject)) if (!localSpawnedObjects.ContainsKey(gameObject))
{ {
return; return;
} }
Node3D nodeToDespawn = spawnedObjects.GetValueOrDefault(gameObject); Node3D nodeToDespawn = localSpawnedObjects.GetValueOrDefault(gameObject);
spawnedObjects.Remove(gameObject); localSpawnedObjects.Remove(gameObject);
SpaceRoot.CallDeferred("remove_child", nodeToDespawn); SpaceRoot.CallDeferred("remove_child", nodeToDespawn);
nodeToDespawn.CallDeferred("queue_free"); 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() public void ApplyOrigin()
{ {
Sector current = GetCurrentSector(); Sector current = GetCurrentSector();
@ -292,26 +316,16 @@ public partial class GameManager : Node3D
player.Value.PlayerData.UpdateNodePosition(); player.Value.PlayerData.UpdateNodePosition();
} }
List<Sector> nearby = current.GetNeighbouringSectors(); foreach (KeyValuePair<GameObject, Node3D> spawned in localSpawnedObjects)
foreach (KeyValuePair<GameObject, Node3D> spawned in spawnedObjects)
{ {
GameObject gameObject = spawned.Key; GameObject gameObject = spawned.Key;
Node3D node = spawned.Value; Node3D node = spawned.Value;
if (!nearby.Contains(gameObject.CurrentSector))
{
Despawn(gameObject);
}
else
{
gameObject.UpdateSectorOffset(current); gameObject.UpdateSectorOffset(current);
node._Process(0); node._Process(0);
} }
} }
nearby.ForEach(sector => sector.SpawnObjects());
}
public void SendUniverseToClient(long id) public void SendUniverseToClient(long id)
{ {
RpcId(id, nameof(RpcDownloadUniverse), Generator.GetUniverseSize(), Generator.GetSectorSize()); RpcId(id, nameof(RpcDownloadUniverse), Generator.GetUniverseSize(), Generator.GetSectorSize());

View file

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using Godot; using Godot;
public abstract class GameObject public abstract class GameObject
@ -103,7 +102,6 @@ public abstract class GameObject
} }
} }
public Vector3Dec GlobalCoordinates { get; protected set; }
public Vector3 SectorOffset { get; protected set; } public Vector3 SectorOffset { get; protected set; }
protected bool reassigning = false; protected bool reassigning = false;
@ -118,97 +116,65 @@ public abstract class GameObject
Velocity = new(0, 0, 1); Velocity = new(0, 0, 1);
AngularVelocity = Vector3.Zero; AngularVelocity = Vector3.Zero;
Rotation = Vector3.Zero; Rotation = Vector3.Zero;
GlobalCoordinates = CalculateGlobalCoordinates(sector.GlobalCenterCoordinates, localCoordinates);
}
public GameObject(Vector3Dec coordinates)
{
GlobalCoordinates = coordinates;
UpdateSector();
}
public GameObject(decimal x, decimal y, decimal z)
{
GlobalCoordinates = new(x, y, z);
UpdateSector();
}
public Vector3Dec CalculateGlobalCoordinates(Vector3Dec sectorCenter, Vector3 local)
{
return new
(
sectorCenter.X + (decimal)local.X,
sectorCenter.Y + (decimal)local.Y,
sectorCenter.Z + (decimal)local.Z
);
} }
public void ApplyVelocity(double delta) public void ApplyVelocity(double delta)
{ {
SetCoordinatesFromLocal(LocalCoordinates + Velocity * delta); SetCoordinates(LocalCoordinates + Velocity * delta);
Rotation += AngularVelocity * delta; Rotation += AngularVelocity * delta;
} }
public bool IsInCurrentSector() public bool IsInCurrentSector()
{ {
return Helpers.IsInsideArea(CurrentSector.Size / 2, LocalCoordinates); return Helpers.IsInsideArea(GameManager.Generator.GetSectorSize() / 2, LocalCoordinates);
} }
public void UpdateSector() public void UpdateSector()
{ {
List<Sector> neighbours = CurrentSector.GetNeighbouringSectors();
foreach (Sector sector in neighbours)
{
if (sector.IsObjectInSector(this))
{
reassigning = true;
sector.AssignObject(this);
return;
}
}
if (!GameManager.GameUniverse.IsInside(CurrentSector.Coordinates, LocalCoordinates)) if (!GameManager.GameUniverse.IsInside(CurrentSector.Coordinates, LocalCoordinates))
{ {
return; return;
} }
foreach (Sector sector in GameManager.GameUniverse.Sectors) Vector3 sectorSize = GameManager.Generator.GetSectorSize() / 2;
{
if (sector.IsObjectInSector(this)) bool? x = null;
bool? y = null;
bool? z = null;
if (LocalCoordinates.X > sectorSize.X) x = true;
else if (LocalCoordinates.X < -sectorSize.X) x = false;
if (LocalCoordinates.Y > sectorSize.Y) y = true;
else if (LocalCoordinates.Y < -sectorSize.Y) y = false;
if (LocalCoordinates.Z > sectorSize.Z) z = true;
else if (LocalCoordinates.Z < -sectorSize.Z) z = false;
Sector sector = GameManager.GameUniverse.GetNeighbouringSector(CurrentSector, x, y, z);
if (sector != null)
{ {
reassigning = true; reassigning = true;
sector.AssignObject(this); sector.AssignObject(this);
return;
}
} }
} }
public virtual void AssignSector(Sector sector) public virtual void AssignSector(Sector sector)
{ {
Vector3 sectorOffset = GetSectorOffset(sector);
CurrentSector = sector; CurrentSector = sector;
ResetLocalCoordinates(); LocalCoordinates += sectorOffset;
UpdateSectorOffsetToMainPlayer(); UpdateSectorOffsetToMainPlayer();
List<Sector> neighbours = GameManager.Instance.GetCurrentSector().GetNeighbouringSectors();
if (neighbours.Contains(sector))
{
GameManager.Instance.Spawn(this);
}
else
{
GameManager.Instance.Despawn(this);
}
reassigning = false; reassigning = false;
} }
public Vector3 GetSectorOffset(Sector sector) public Vector3 GetSectorOffset(Sector sector)
{ {
Vector3Dec relative = CurrentSector.GlobalCenterCoordinates - sector.GlobalCenterCoordinates; Vector3I relative = CurrentSector.Coordinates - sector.Coordinates;
return relative.ToVector3(); return relative * GameManager.Generator.GetSectorSize();
} }
public void UpdateSectorOffset(Sector sector) public void UpdateSectorOffset(Sector sector)
@ -221,25 +187,22 @@ public abstract class GameObject
UpdateSectorOffset(GameManager.Instance.GetCurrentSector()); UpdateSectorOffset(GameManager.Instance.GetCurrentSector());
} }
public void SetCoordinatesFromGlobal(Vector3Dec globalCoordinates) public void SetCoordinates(Vector3 localCoordinates)
{
GlobalCoordinates = globalCoordinates;
LocalCoordinates = (globalCoordinates - CurrentSector.GlobalCenterCoordinates).ToVector3();
UpdateNodePosition();
}
public void SetCoordinatesFromLocal(Vector3 localCoordinates)
{ {
LocalCoordinates = localCoordinates; LocalCoordinates = localCoordinates;
GlobalCoordinates = Vector3Dec.FromVector3(localCoordinates) + CurrentSector.GlobalCenterCoordinates;
UpdateNodePosition(); UpdateNodePosition();
} }
public void ResetLocalCoordinates() public double GetDistanceToObject(GameObject gameObject)
{ {
SetCoordinatesFromGlobal(GlobalCoordinates); Sector sector = gameObject.CurrentSector;
Vector3 sectorOffset = GetSectorOffset(sector);
Vector3 position1 = LocalCoordinates;
Vector3 position2 = gameObject.LocalCoordinates - sectorOffset;
return Helpers.GetDistance(position1, position2);
} }
public virtual void UpdateNodePosition() { } public virtual void UpdateNodePosition() { }

View file

@ -8,9 +8,14 @@ public class Character(Sector sector, Vector3 localCoordinates) : GameObject(sec
public override void AssignSector(Sector sector) public override void AssignSector(Sector sector)
{ {
CurrentSector = sector; Vector3 sectorOffset = GetSectorOffset(sector);
CurrentSector = sector;
if (IsMainPlayer())
{
LocalCoordinates += sectorOffset;
}
ResetLocalCoordinates();
UpdateSectorOffsetToMainPlayer(); UpdateSectorOffsetToMainPlayer();
UpdateNodePosition(); UpdateNodePosition();
@ -47,14 +52,7 @@ public class Character(Sector sector, Vector3 localCoordinates) : GameObject(sec
} }
public override void UpdateNodePosition() public override void UpdateNodePosition()
{
if (IsMainPlayer())
{
player.GlobalPosition = LocalCoordinates;
}
else
{ {
player.GlobalPosition = LocalCoordinates + SectorOffset; player.GlobalPosition = LocalCoordinates + SectorOffset;
} }
} }
}

View file

@ -25,7 +25,7 @@ public class Star(Sector sector, Vector3 localCoordinates) : GameObject(sector,
if (gameObjectData != null) if (gameObjectData != null)
{ {
QueueManager.NetworkSyncQueue.Enqueue((id, gameObjectData)); QueueManager.NetworkSyncQueue.Enqueue((id, full, gameObjectData));
} }
DirtyBits = DirtyFlags.None; DirtyBits = DirtyFlags.None;

View file

@ -7,12 +7,12 @@ public interface IGenerator
public Universe InitializeEmptyUniverse(Vector3I universeSize, Vector3 sectorSize); public Universe InitializeEmptyUniverse(Vector3I universeSize, Vector3 sectorSize);
public Universe GenerateUniverse(); public Universe GenerateUniverse();
public Sector GenerateSector(Vector3I coordinates); public Sector GenerateSector(Vector3I coordinates, RandomNumberGenerator rng);
public Star GenerateStar(Sector sector, Vector3 localCoordinates); public Star GenerateStar(Sector sector, Vector3 localCoordinates);
public Star GenerateStar(Sector sector); public Star GenerateStar(Sector sector, RandomNumberGenerator rng);
public Vessel GenerateShip(Sector sector, Vector3 localCoordinates); public Vessel GenerateShip(Sector sector, Vector3 localCoordinates);
public Vessel GenerateShip(Sector sector); public Vessel GenerateShip(Sector sector, RandomNumberGenerator rng);
public Vessel GenerateStation(Sector sector, Vector3 localCoordinates); public Vessel GenerateStation(Sector sector, Vector3 localCoordinates);
public Vessel GenerateStation(Sector sector); public Vessel GenerateStation(Sector sector, RandomNumberGenerator rng);
} }

View file

@ -1,21 +1,16 @@
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Godot; using Godot;
public class TestGenerator : IGenerator public class TestGenerator : IGenerator
{ {
public static Vector3I UNIVERSE_SIZE = new(10, 10, 10); public readonly static Vector3I UNIVERSE_SIZE = new(10, 10, 10);
public static Vector3 SECTOR_SIZE = new(50, 50, 50); public readonly static Vector3 SECTOR_SIZE = new(50, 50, 50);
public static int STARS_PER_SECTOR = 1; public readonly static int STARS_PER_SECTOR = 1;
public static int STARS_PER_SECTOR_VARIANCE = 0; public readonly static int STARS_PER_SECTOR_VARIANCE = 0;
public static int SHIPS_PER_SECTOR = 0; public readonly static int SHIPS_PER_SECTOR = 0;
public static int SHIPS_PER_SECTOR_VARIANCE = 0; public readonly static int SHIPS_PER_SECTOR_VARIANCE = 0;
public static int STATIONS_PER_SECTOR = 0; public readonly static int STATIONS_PER_SECTOR = 0;
public static int STATIONS_PER_SECTOR_VARIANCE = 0; public readonly static int STATIONS_PER_SECTOR_VARIANCE = 0;
private readonly int threads = 16;
private RandomNumberGenerator rng;
public Vector3I GetUniverseSize() public Vector3I GetUniverseSize()
{ {
@ -27,7 +22,7 @@ public class TestGenerator : IGenerator
return SECTOR_SIZE; return SECTOR_SIZE;
} }
public Vector3I GetSectorOffset(Vector3I universeSize) public static Vector3I GetSectorOffset(Vector3I universeSize)
{ {
return new( return new(
(int)Mathf.Floor(universeSize.X / 2), (int)Mathf.Floor(universeSize.X / 2),
@ -36,7 +31,7 @@ public class TestGenerator : IGenerator
); );
} }
public Vector3I GetSectorOffset() public static Vector3I GetSectorOffset()
{ {
return GetSectorOffset(UNIVERSE_SIZE); return GetSectorOffset(UNIVERSE_SIZE);
} }
@ -52,9 +47,7 @@ public class TestGenerator : IGenerator
for (int z = 0; z < universeSize.Z; z++) for (int z = 0; z < universeSize.Z; z++)
{ {
universe.Sectors[x, y, z] = new Sector( universe.Sectors[x, y, z] = new Sector(
new(x, y, z), new(x, y, z)
GetSectorOffset(universeSize),
sectorSize
); );
} }
} }
@ -65,50 +58,33 @@ public class TestGenerator : IGenerator
public Universe GenerateUniverse() public Universe GenerateUniverse()
{ {
rng = new();
List<Task> tasks = [];
Universe universe = new(UNIVERSE_SIZE); Universe universe = new(UNIVERSE_SIZE);
int countX = Mathf.Clamp(UNIVERSE_SIZE.X / threads, 1, int.MaxValue);
for (int x = 0; x < UNIVERSE_SIZE.X; x += countX) Parallel.For(0, UNIVERSE_SIZE.X, x =>
{ {
int taskStartX = x; GenerateClustered(universe, x);
int taskCountX = countX;
Task clusteredTask = Task.Run(() =>
{
GenerateClustered(universe, taskStartX, taskCountX);
}); });
tasks.Add(clusteredTask);
}
Task.WaitAll([.. tasks]);
return universe; return universe;
} }
private void GenerateClustered(Universe universe, int startX, int countX) private void GenerateClustered(Universe universe, int x)
{ {
int endX = Mathf.Clamp(startX + countX, 0, UNIVERSE_SIZE.X); RandomNumberGenerator rng = new();
rng.Randomize();
for (int x = startX; x < endX; x++)
{
for (int y = 0; y < UNIVERSE_SIZE.Y; y++) for (int y = 0; y < UNIVERSE_SIZE.Y; y++)
{ {
for (int z = 0; z < UNIVERSE_SIZE.Z; z++) for (int z = 0; z < UNIVERSE_SIZE.Z; z++)
{ {
universe.Sectors[x, y, z] = GenerateSector(new(x, y, z)); universe.Sectors[x, y, z] = GenerateSector(new(x, y, z), rng);
}
} }
} }
} }
public Sector GenerateSector(Vector3I coordinates) public Sector GenerateSector(Vector3I coordinates, RandomNumberGenerator rng)
{ {
Sector sector = new(coordinates, GetSectorOffset(), SECTOR_SIZE); Sector sector = new(coordinates);
int starCount = rng.RandiRange( int starCount = rng.RandiRange(
STARS_PER_SECTOR - STARS_PER_SECTOR_VARIANCE, STARS_PER_SECTOR - STARS_PER_SECTOR_VARIANCE,
@ -120,7 +96,7 @@ public class TestGenerator : IGenerator
{ {
if (coordinates.X == 5 && coordinates.Y == 5 && coordinates.Z == 5) if (coordinates.X == 5 && coordinates.Y == 5 && coordinates.Z == 5)
{ {
Vector3 localCoordinates = GenerateLocalCoordinates(); Vector3 localCoordinates = GenerateLocalCoordinates(rng);
Star star = GenerateStar(sector, localCoordinates); Star star = GenerateStar(sector, localCoordinates);
sector.GameObjects.Add(star); sector.GameObjects.Add(star);
@ -135,7 +111,7 @@ public class TestGenerator : IGenerator
for (int i = 0; i < shipCount; i++) for (int i = 0; i < shipCount; i++)
{ {
Vector3 localCoordinates = GenerateLocalCoordinates(); Vector3 localCoordinates = GenerateLocalCoordinates(rng);
Vessel ship = GenerateShip(sector, localCoordinates); Vessel ship = GenerateShip(sector, localCoordinates);
sector.GameObjects.Add(ship); sector.GameObjects.Add(ship);
@ -149,7 +125,7 @@ public class TestGenerator : IGenerator
for (int i = 0; i < stationCount; i++) for (int i = 0; i < stationCount; i++)
{ {
Vector3 localCoordinates = GenerateLocalCoordinates(); Vector3 localCoordinates = GenerateLocalCoordinates(rng);
Vessel station = GenerateStation(sector, localCoordinates); Vessel station = GenerateStation(sector, localCoordinates);
sector.GameObjects.Add(station); sector.GameObjects.Add(station);
@ -163,9 +139,9 @@ public class TestGenerator : IGenerator
return new Star(sector, localCoordinates); return new Star(sector, localCoordinates);
} }
public Star GenerateStar(Sector sector) public Star GenerateStar(Sector sector, RandomNumberGenerator rng)
{ {
return GenerateStar(sector, GenerateLocalCoordinates()); return GenerateStar(sector, GenerateLocalCoordinates(rng));
} }
public Vessel GenerateShip(Sector sector, Vector3 localCoordinates) public Vessel GenerateShip(Sector sector, Vector3 localCoordinates)
@ -173,9 +149,9 @@ public class TestGenerator : IGenerator
return new Vessel(sector, localCoordinates); return new Vessel(sector, localCoordinates);
} }
public Vessel GenerateShip(Sector sector) public Vessel GenerateShip(Sector sector, RandomNumberGenerator rng)
{ {
return GenerateShip(sector, GenerateLocalCoordinates()); return GenerateShip(sector, GenerateLocalCoordinates(rng));
} }
public Vessel GenerateStation(Sector sector, Vector3 localCoordinates) public Vessel GenerateStation(Sector sector, Vector3 localCoordinates)
@ -183,12 +159,12 @@ public class TestGenerator : IGenerator
return new Vessel(sector, localCoordinates); return new Vessel(sector, localCoordinates);
} }
public Vessel GenerateStation(Sector sector) public Vessel GenerateStation(Sector sector, RandomNumberGenerator rng)
{ {
return GenerateStation(sector, GenerateLocalCoordinates()); return GenerateStation(sector, GenerateLocalCoordinates(rng));
} }
public Vector3 GenerateLocalCoordinates() public static Vector3 GenerateLocalCoordinates(RandomNumberGenerator rng)
{ {
double x = (rng.Randf() - 0.5) * SECTOR_SIZE.X; double x = (rng.Randf() - 0.5) * SECTOR_SIZE.X;
double y = (rng.Randf() - 0.5) * SECTOR_SIZE.Y; double y = (rng.Randf() - 0.5) * SECTOR_SIZE.Y;

View file

@ -1,3 +1,4 @@
using System;
using Godot; using Godot;
public static class Helpers public static class Helpers
@ -27,18 +28,12 @@ public static class Helpers
return true; return true;
} }
public static bool IsInsideGlobalArea(Vector3Dec areaStart, Vector3Dec areaEnd, Vector3Dec coordinates) public static double GetDistance(Vector3 position1, Vector3 position2)
{ {
if (coordinates.X >= areaEnd.X || coordinates.Y >= areaEnd.Y || coordinates.Z >= areaEnd.Z) double diffX = Math.Abs(position1.X - position2.X);
{ double diffY = Math.Abs(position1.Y - position2.Y);
return false; double diffZ = Math.Abs(position1.Z - position2.Z);
}
if (coordinates.X < areaStart.X || coordinates.Y < areaStart.Y || coordinates.Z < areaStart.Z) return Math.Sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ);
{
return false;
}
return true;
} }
} }

View file

@ -73,7 +73,7 @@ public partial class NetworkManager : Node
public void OnPlayerDisconnected(long id) public void OnPlayerDisconnected(long id)
{ {
Player player = GetNode<Player>($"/root/Game/Space/Player-{id}"); Player player = GetNode<Player>($"/root/Game/Space/Player-{id}");
GameManager.Instance.DespawnPlayer(player); GameManager.Instance.DespawnPlayer(player, id);
} }
private async Task SpawnNetPlayer(long id) private async Task SpawnNetPlayer(long id)

View file

@ -51,7 +51,7 @@ public partial class Player : CharacterBody3D
rpc.Rpc(nameof(rpc.RpcSyncPlayer), GetMultiplayerAuthority(), GlobalPosition, PlayerData.CurrentSector.Coordinates); rpc.Rpc(nameof(rpc.RpcSyncPlayer), GetMultiplayerAuthority(), GlobalPosition, PlayerData.CurrentSector.Coordinates);
} }
PlayerData.SetCoordinatesFromLocal(GlobalPosition); PlayerData.SetCoordinates(GlobalPosition);
} }
PlayerData.Simulate(delta); PlayerData.Simulate(delta);

View file

@ -4,13 +4,13 @@ using Godot;
public partial class QueueManager : Node public partial class QueueManager : Node
{ {
public static ConcurrentQueue<string> LogQueue = new(); public static readonly ConcurrentQueue<string> LogQueue = new();
public static ConcurrentQueue<Action> ActionQueue = new(); public static readonly ConcurrentQueue<Action> ActionQueue = new();
public static ConcurrentQueue<(Sector, GameObject)> SectorReassignQueue = new(); public static readonly ConcurrentQueue<(Sector, GameObject)> SectorReassignQueue = new();
public static ConcurrentQueue<(long, Godot.Collections.Dictionary)> NetworkSyncQueue = new(); public static readonly ConcurrentQueue<(long, bool, Godot.Collections.Dictionary)> NetworkSyncQueue = new();
private readonly int sectorReassignQueueRateLimit = 500; private readonly int sectorReassignQueueRateLimit = 5000;
private readonly int networkSyncQueueRateLimit = 10; private readonly int networkSyncQueueRateLimit = 10;
public override void _Process(double delta) public override void _Process(double delta)
@ -51,8 +51,11 @@ public partial class QueueManager : Node
&& NetworkSyncQueue.TryDequeue(out var item) && NetworkSyncQueue.TryDequeue(out var item)
) )
{ {
var (clientId, gameObjectData) = item; var (clientId, full, gameObjectData) = item;
if (full)
RPCNode.Instance.RpcId(clientId, nameof(RPCNode.RpcSpawnGameObject), gameObjectData);
else
RPCNode.Instance.RpcId(clientId, nameof(RPCNode.RpcSyncGameObject), gameObjectData); RPCNode.Instance.RpcId(clientId, nameof(RPCNode.RpcSyncGameObject), gameObjectData);
} }
} }

View file

@ -6,8 +6,6 @@ public partial class RPCNode : Node
{ {
public static RPCNode Instance { get; private set; } public static RPCNode Instance { get; private set; }
private readonly HashSet<Guid> requestedFullGameObjects = [];
public override void _EnterTree() public override void _EnterTree()
{ {
Instance = this; Instance = this;
@ -18,6 +16,31 @@ public partial class RPCNode : Node
Instance = null; Instance = null;
} }
[Rpc(MultiplayerApi.RpcMode.Authority)]
public void RpcDespawnGameObject(string uuidData)
{
GD.Print("DESPAWNING: " + uuidData);
if (!GameManager.Instance.playerReady)
{
return;
}
Guid uuid = Guid.Parse(uuidData);
List<Sector> sectors = GameManager.Instance.GetCurrentSector().GetNeighbouringSectors();
foreach (Sector sector in sectors)
{
bool removed = sector.RemoveObjectById(uuid);
if (removed)
{
break;
}
}
GameManager.Instance.Despawn(uuid);
}
[Rpc(MultiplayerApi.RpcMode.Authority)] [Rpc(MultiplayerApi.RpcMode.Authority)]
public void RpcSyncGameObject(Godot.Collections.Dictionary gameObjectData) public void RpcSyncGameObject(Godot.Collections.Dictionary gameObjectData)
{ {
@ -43,28 +66,22 @@ public partial class RPCNode : Node
return; return;
} }
} }
if (!TrySyncFullGameObject(gameObjectData))
{
if (requestedFullGameObjects.Contains(uuid))
{
return;
}
requestedFullGameObjects.Add(uuid);
RpcId(1, nameof(RequestFullGameObject), NetworkManager.Instance.LocalNetId, (string)uuidData);
}
} }
public bool TrySyncFullGameObject(Godot.Collections.Dictionary gameObjectData)
[Rpc(MultiplayerApi.RpcMode.Authority)]
public void RpcSpawnGameObject(Godot.Collections.Dictionary gameObjectData)
{ {
GD.Print("SPAWNING: " + gameObjectData);
if (!gameObjectData.TryGetValue("type", out var typeData)) if (!gameObjectData.TryGetValue("type", out var typeData))
return false; return;
if (!gameObjectData.TryGetValue("sectorCoordinates", out var sectorCoordinatesData)) if (!gameObjectData.TryGetValue("sectorCoordinates", out var sectorCoordinatesData))
return false; return;
if (!gameObjectData.TryGetValue("localCoordinates", out var localCoordinatesData)) if (!gameObjectData.TryGetValue("localCoordinates", out var localCoordinatesData))
return false; return;
if (!gameObjectData.TryGetValue("uuid", out var uuidData)) if (!gameObjectData.TryGetValue("uuid", out var uuidData))
return false; return;
string type = (string)typeData; string type = (string)typeData;
Vector3I sectorCoordinates = (Vector3I)sectorCoordinatesData; Vector3I sectorCoordinates = (Vector3I)sectorCoordinatesData;
@ -73,7 +90,7 @@ public partial class RPCNode : Node
Sector sector = GameManager.GameUniverse.GetSector(sectorCoordinates); Sector sector = GameManager.GameUniverse.GetSector(sectorCoordinates);
if (sector == null) if (sector == null)
return false; return;
GameObject gameObject; GameObject gameObject;
switch (type) switch (type)
@ -83,38 +100,15 @@ public partial class RPCNode : Node
break; break;
default: default:
return false; return;
} }
gameObject.UUID = uuid; gameObject.UUID = uuid;
gameObject.NetworkRead(gameObjectData); gameObject.NetworkRead(gameObjectData);
requestedFullGameObjects.Remove(uuid);
sector.AssignObject(gameObject); sector.AssignObject(gameObject);
return true; GameManager.Instance.Spawn(gameObject);
}
[Rpc(MultiplayerApi.RpcMode.AnyPeer)]
public void RequestFullGameObject(long id, string uuidString)
{
if (!Global.IsGameHost)
{
return;
}
Guid uuid = Guid.Parse(uuidString);
List<Sector> sectors = GameManager.Instance.GetPlayer(id).PlayerData.CurrentSector.GetNeighbouringSectors();
foreach (Sector sector in sectors)
{
GameObject gameObject = sector.GetObjectById(uuid);
if (gameObject != null)
{
gameObject.NetworkWrite(id, true);
return;
}
}
} }
[Rpc(MultiplayerApi.RpcMode.AnyPeer)] [Rpc(MultiplayerApi.RpcMode.AnyPeer)]
@ -138,8 +132,7 @@ public partial class RPCNode : Node
return; return;
} }
Vector3Dec newGlobal = playerData.CalculateGlobalCoordinates(sector.GlobalCenterCoordinates, position); playerData.SetCoordinates(position);
playerData.SetCoordinatesFromGlobal(newGlobal);
if (playerData.CurrentSector.Coordinates != sectorCoordinates) if (playerData.CurrentSector.Coordinates != sectorCoordinates)
{ {

View file

@ -5,45 +5,22 @@ using Godot;
public class Sector public class Sector
{ {
public Vector3I Coordinates; public Vector3I Coordinates;
public Vector3Dec GlobalStartCoordinates;
public Vector3Dec GlobalCenterCoordinates;
public Vector3Dec GlobalEndCoordinates;
public Vector3 Size;
public FastUniqueList<GameObject> GameObjects = new(); public FastUniqueList<GameObject> GameObjects = new();
public Sector(Vector3I coordinates, Vector3I offset, Vector3 size) public Sector(Vector3I coordinates)
{ {
Coordinates = coordinates; Coordinates = coordinates;
Vector3Dec sizeDec = Vector3Dec.FromVector3(size);
decimal startX = (coordinates.X - offset.X) * sizeDec.X;
decimal startY = (coordinates.Y - offset.Y) * sizeDec.Y;
decimal startZ = (coordinates.Z - offset.Z) * sizeDec.Z;
GlobalStartCoordinates = new(startX, startY, startZ);
GlobalCenterCoordinates = new(
startX + sizeDec.X / 2,
startY + sizeDec.Y / 2,
startZ + sizeDec.Z / 2
);
GlobalEndCoordinates = new(
startX + sizeDec.X,
startY + sizeDec.Y,
startZ + sizeDec.Z
);
Size = size;
} }
public void Simulate(double delta) public void Simulate(double delta, HashSet<GameObject> ignoreObjects = null)
{ {
GameObjects.ForEach(gameObject => GameObjects.ForEach(gameObject =>
{
if (ignoreObjects == null || !ignoreObjects.Contains(gameObject))
{ {
gameObject.Simulate(delta); gameObject.Simulate(delta);
}
}); });
} }
@ -55,11 +32,6 @@ public class Sector
}); });
} }
public bool IsObjectInSector(GameObject gameObject)
{
return Helpers.IsInsideGlobalArea(GlobalStartCoordinates, GlobalEndCoordinates, gameObject.GlobalCoordinates);
}
public void AssignObject(GameObject gameObject) public void AssignObject(GameObject gameObject)
{ {
QueueManager.SectorReassignQueue.Enqueue((this, gameObject)); QueueManager.SectorReassignQueue.Enqueue((this, gameObject));
@ -83,6 +55,20 @@ public class Sector
return null; return null;
} }
public bool RemoveObjectById(Guid id)
{
foreach (GameObject gameObject in GameObjects)
{
if (gameObject.UUID.Equals(id))
{
GameObjects.Remove(gameObject);
return true;
}
}
return false;
}
public List<Sector> GetNeighbouringSectors() public List<Sector> GetNeighbouringSectors()
{ {
List<Sector> neighbours = []; List<Sector> neighbours = [];
@ -114,4 +100,15 @@ public class Sector
return neighbours; return neighbours;
} }
public override bool Equals(object obj)
{
Sector sector = (Sector)obj;
return sector.Coordinates == Coordinates;
}
public override int GetHashCode()
{
return Coordinates.GetHashCode();
}
} }

View file

@ -28,10 +28,29 @@ public class Universe
return Sectors[x, y, z]; return Sectors[x, y, z];
} }
public Sector GetNeighbouringSector(Sector sector, bool? x, bool? y, bool? z)
{
Vector3I coordinates = sector.Coordinates;
if (x == true) coordinates.X++;
else if (x == false) coordinates.X--;
if (y == true) coordinates.Y++;
else if (y == false) coordinates.Y--;
if (z == true) coordinates.Z++;
else if (z == false) coordinates.Z--;
coordinates.X = Math.Clamp(coordinates.X, 0, Size.X);
coordinates.Y = Math.Clamp(coordinates.Y, 0, Size.Y);
coordinates.Z = Math.Clamp(coordinates.Z, 0, Size.Z);
return GetSector(coordinates);
}
public bool IsInside(Vector3I sectorCoordinates, Vector3 localCoordinates) public bool IsInside(Vector3I sectorCoordinates, Vector3 localCoordinates)
{ {
Vector3 sectorSize = GameManager.Generator.GetSectorSize() / 2; Vector3 sectorSize = GameManager.Generator.GetSectorSize() / 2;
Vector3I universeSize = GameManager.GameUniverse.Size;
if ( if (
sectorCoordinates.X == 0 && localCoordinates.X < -sectorSize.X || sectorCoordinates.X == 0 && localCoordinates.X < -sectorSize.X ||
@ -43,9 +62,9 @@ public class Universe
} }
if ( if (
sectorCoordinates.X == universeSize.X - 1 && localCoordinates.X >= sectorSize.X || sectorCoordinates.X == Size.X - 1 && localCoordinates.X >= sectorSize.X ||
sectorCoordinates.Y == universeSize.Y - 1 && localCoordinates.Y >= sectorSize.Y || sectorCoordinates.Y == Size.Y - 1 && localCoordinates.Y >= sectorSize.Y ||
sectorCoordinates.Z == universeSize.Z - 1 && localCoordinates.Z >= sectorSize.Z sectorCoordinates.Z == Size.Z - 1 && localCoordinates.Z >= sectorSize.Z
) )
{ {
return false; return false;

View file

@ -1,106 +0,0 @@
using System;
using Godot;
public readonly struct Vector3Dec(decimal x, decimal y, decimal z)
{
public static Vector3Dec Zero { get; } = new(0, 0, 0);
public decimal X { get; } = x;
public decimal Y { get; } = y;
public decimal Z { get; } = z;
public Vector3 ToVector3()
{
return new((double)X, (double)Y, (double)Z);
}
public static Vector3Dec FromVector3(Vector3 vec)
{
return new((decimal)vec.X, (decimal)vec.Y, (decimal)vec.Z);
}
public static Vector3Dec operator +(Vector3Dec vec1, Vector3Dec vec2)
{
return new(vec1.X + vec2.X, vec1.Y + vec2.Y, vec1.Z + vec2.Z);
}
public static Vector3Dec operator -(Vector3Dec vec1, Vector3Dec vec2)
{
return new(vec1.X - vec2.X, vec1.Y - vec2.Y, vec1.Z - vec2.Z);
}
public static Vector3Dec operator *(Vector3Dec vec1, Vector3Dec vec2)
{
return new(vec1.X * vec2.X, vec1.Y * vec2.Y, vec1.Z * vec2.Z);
}
public static Vector3Dec operator /(Vector3Dec vec1, Vector3Dec vec2)
{
return new(vec1.X / vec2.X, vec1.Y / vec2.Y, vec1.Z / vec2.Z);
}
public static Vector3Dec operator +(Vector3Dec vec, decimal value)
{
return new(vec.X + value, vec.Y + value, vec.Z + value);
}
public static Vector3Dec operator +(decimal value, Vector3Dec vec)
{
return vec + value;
}
public static Vector3Dec operator -(Vector3Dec vec, decimal value)
{
return new(vec.X - value, vec.Y - value, vec.Z - value);
}
public static Vector3Dec operator -(decimal value, Vector3Dec vec)
{
return vec - value;
}
public static Vector3Dec operator *(Vector3Dec vec, decimal value)
{
return new(vec.X * value, vec.Y * value, vec.Z * value);
}
public static Vector3Dec operator *(decimal value, Vector3Dec vec)
{
return vec * value;
}
public static Vector3Dec operator /(Vector3Dec vec, decimal value)
{
return new(vec.X / value, vec.Y / value, vec.Z / value);
}
public static Vector3Dec operator /(decimal value, Vector3Dec vec)
{
return vec / value;
}
public static Vector3Dec operator -(Vector3Dec vec)
{
return new(-vec.X, -vec.Y, -vec.Z);
}
public static bool operator ==(Vector3Dec vec1, Vector3Dec vec2)
{
return vec1.X == vec2.X && vec1.Y == vec2.Y && vec1.Z == vec2.Z;
}
public static bool operator !=(Vector3Dec vec1, Vector3Dec vec2)
{
return !(vec1 == vec2);
}
public override bool Equals(object obj)
{
return obj != null && obj is Vector3Dec vec && this == vec;
}
public override int GetHashCode()
{
return HashCode.Combine(X, Y, Z);
}
}

View file

@ -1 +0,0 @@
uid://def7cpbdn6gjm