diff --git a/src/EscapeMenuMultiplayer.cs b/src/EscapeMenuMultiplayer.cs index 20d5f4c..b7c6990 100644 --- a/src/EscapeMenuMultiplayer.cs +++ b/src/EscapeMenuMultiplayer.cs @@ -135,12 +135,15 @@ public class EscapeMenuMultiplayer : Container if (IntegratedServer != null) { IntegratedServer.Server.Stop(); + // TODO: Have a single method to "reset" the state? + IntegratedServer.Server.Objects.Clear(); IntegratedServer.Server.Sync.Clear(); IntegratedServer.GetParent().RemoveChild(IntegratedServer); IntegratedServer.QueueFree(); IntegratedServer = null; client.Disconnect(); + client.Objects.Clear(); client.Sync.Clear(); } @@ -156,6 +159,7 @@ public class EscapeMenuMultiplayer : Container client.Connect(address, port); } else { client.Disconnect(); + client.Objects.Clear(); client.Sync.Clear(); } } diff --git a/src/Network/DeSerializer.Impl.cs b/src/IO/DeSerializer.Impl.cs similarity index 95% rename from src/Network/DeSerializer.Impl.cs rename to src/IO/DeSerializer.Impl.cs index 7eda306..ddafc51 100644 --- a/src/Network/DeSerializer.Impl.cs +++ b/src/IO/DeSerializer.Impl.cs @@ -205,26 +205,26 @@ public class DictionaryDeSerializerGenerator } } -public class SyncedObjectDeSerializerGenerator +public class NodeDeSerializerGenerator : IDeSerializerGenerator { public IDeSerializer GenerateFor(Type type) { - if (!typeof(Node).IsAssignableFrom(type) || (type.GetCustomAttribute() == null)) return null; - var deSerializerType = typeof(SyncedObjectDeSerializer<>).MakeGenericType(type); + if (!typeof(Node).IsAssignableFrom(type)) return null; + var deSerializerType = typeof(NodeDeSerializer<>).MakeGenericType(type); return (IDeSerializer)Activator.CreateInstance(deSerializerType); } - private class SyncedObjectDeSerializer + private class NodeDeSerializer : DeSerializer where TObj : Node { public override void Serialize(Game game, BinaryWriter writer, TObj value) - => writer.Write(game.Sync.GetStatusOrThrow(value).SyncID); + => writer.Write(game.Objects.GetSyncID(value).Value); public override TObj Deserialize(Game game, BinaryReader reader) { - var id = reader.ReadUInt32(); - var value = (TObj)game.Sync.GetStatusOrThrow(id).Object; + var id = new UniqueID(reader.ReadUInt32()); + var value = (TObj)game.Objects.GetNodeByID(id); if (value == null) throw new Exception($"Could not find synced object of type {typeof(TObj)} with ID {id}"); return value; } diff --git a/src/Network/DeSerializer.Interfaces.cs b/src/IO/DeSerializer.Interfaces.cs similarity index 100% rename from src/Network/DeSerializer.Interfaces.cs rename to src/IO/DeSerializer.Interfaces.cs diff --git a/src/Network/DeSerializerRegistry.cs b/src/IO/DeSerializerRegistry.cs similarity index 97% rename from src/Network/DeSerializerRegistry.cs rename to src/IO/DeSerializerRegistry.cs index f51e7f7..eaa344c 100644 --- a/src/Network/DeSerializerRegistry.cs +++ b/src/IO/DeSerializerRegistry.cs @@ -38,7 +38,7 @@ public static class DeSerializerRegistry RegisterGenerator(new ArrayDeSerializerGenerator()); RegisterGenerator(new CollectionDeSerializerGenerator()); RegisterGenerator(new DictionaryDeSerializerGenerator()); - RegisterGenerator(new SyncedObjectDeSerializerGenerator()); + RegisterGenerator(new NodeDeSerializerGenerator()); } public static void Register(Action serialize, Func deserialize) diff --git a/src/IO/Save.cs b/src/IO/Save.cs new file mode 100644 index 0000000..6eef1dd --- /dev/null +++ b/src/IO/Save.cs @@ -0,0 +1,7 @@ +using System; + +[AttributeUsage(AttributeTargets.Property)] +public class SaveAttribute : Attribute +{ + +} diff --git a/src/Network/Sync.cs b/src/Network/Sync.cs index a4b364f..9b89cad 100644 --- a/src/Network/Sync.cs +++ b/src/Network/Sync.cs @@ -9,17 +9,17 @@ using Godot; public class Sync { protected Game Game { get; } - protected Dictionary StatusBySyncID { get; } = new Dictionary(); + protected Dictionary StatusByID { get; } = new Dictionary(); protected Dictionary StatusByObject { get; } = new Dictionary(); static Sync() => DeSerializerRegistry.Register(new SyncPacketObjectDeSerializer()); public Sync(Game game) => Game = game; - public SyncStatus GetStatusOrNull(uint syncID) - => StatusBySyncID.TryGetValue(syncID, out var value) ? value : null; - public SyncStatus GetStatusOrThrow(uint syncID) - => GetStatusOrNull(syncID) ?? throw new Exception( - $"No {nameof(SyncStatus)} found for ID {syncID}"); + public SyncStatus GetStatusOrNull(UniqueID id) + => StatusByID.TryGetValue(id, out var value) ? value : null; + public SyncStatus GetStatusOrThrow(UniqueID id) + => GetStatusOrNull(id) ?? throw new Exception( + $"No {nameof(SyncStatus)} found for ID {id}"); public SyncStatus GetStatusOrNull(Node obj) { @@ -39,23 +39,23 @@ public class Sync node.QueueFree(); } + StatusByID.Clear(); StatusByObject.Clear(); - StatusBySyncID.Clear(); } } public class SyncStatus { - public uint SyncID { get; } + public UniqueID ID { get; } public Node Object { get; } public SyncObjectInfo Info { get; } public int DirtyProperties { get; set; } public SyncMode Mode { get; set; } - public SyncStatus(uint syncID, Node obj, SyncObjectInfo info) - { SyncID = syncID; Object = obj; Info = info; } + public SyncStatus(UniqueID id, Node obj, SyncObjectInfo info) + { ID = id; Object = obj; Info = info; } } public enum SyncMode @@ -73,11 +73,11 @@ public class SyncPacket public class Object { public ushort InfoID { get; } - public uint SyncID { get; } + public UniqueID ID { get; } public SyncMode Mode { get; } public List<(byte, object)> Values { get; } - public Object(ushort infoID, uint syncID, SyncMode mode, List<(byte, object)> values) - { InfoID = infoID; SyncID = syncID; Mode = mode; Values = values; } + public Object(ushort infoID, UniqueID id, SyncMode mode, List<(byte, object)> values) + { InfoID = infoID; ID = id; Mode = mode; Values = values; } } } @@ -87,7 +87,7 @@ internal class SyncPacketObjectDeSerializer public override void Serialize(Game game, BinaryWriter writer, SyncPacket.Object value) { writer.Write(value.InfoID); - writer.Write(value.SyncID); + writer.Write(value.ID.Value); writer.Write((byte)value.Mode); writer.Write((byte)value.Values.Count); @@ -103,7 +103,7 @@ internal class SyncPacketObjectDeSerializer public override SyncPacket.Object Deserialize(Game game, BinaryReader reader) { var infoID = reader.ReadUInt16(); - var syncID = reader.ReadUInt32(); + var id = new UniqueID(reader.ReadUInt32()); var mode = (SyncMode)reader.ReadByte(); var count = reader.ReadByte(); @@ -124,6 +124,6 @@ internal class SyncPacketObjectDeSerializer values.Add((propID, deSerializer.Deserialize(game, reader))); } - return new SyncPacket.Object(infoID, syncID, mode, values); + return new SyncPacket.Object(infoID, id, mode, values); } } diff --git a/src/Network/SyncClient.cs b/src/Network/SyncClient.cs index f6b27a1..ecce7cf 100644 --- a/src/Network/SyncClient.cs +++ b/src/Network/SyncClient.cs @@ -14,25 +14,28 @@ public class SyncClient : Sync { foreach (var packetObj in packet.Changes) { var info = SyncRegistry.Get(packetObj.InfoID); - var status = GetStatusOrNull(packetObj.SyncID); + var status = GetStatusOrNull(packetObj.ID); if (status == null) { if (packetObj.Mode != SyncMode.Spawn) throw new Exception( - $"Unknown synced object {info.Name} (ID {packetObj.SyncID})"); + $"Unknown synced object {info.Name} (ID {packetObj.ID})"); var obj = info.Scene.Init(); - status = new SyncStatus(packetObj.SyncID, obj, info); - StatusBySyncID.Add(status.SyncID, status); + Client.Objects.Add(packetObj.ID, obj); + + status = new SyncStatus(packetObj.ID, obj, info); + StatusByID.Add(status.ID, status); StatusByObject.Add(status.Object, status); + Client.GetNode("World").AddChild(obj); } else { if (packetObj.Mode == SyncMode.Spawn) throw new Exception( - $"Spawning object {info.Name} with ID {packetObj.SyncID}, but it already exists"); + $"Spawning object {info.Name} with ID {packetObj.ID}, but it already exists"); if (info != status.Info) throw new Exception( $"Info of synced object being modified doesn't match ({info.Name} != {status.Info.Name})"); if (packetObj.Mode == SyncMode.Destroy) { - StatusBySyncID.Remove(status.SyncID); + StatusByID.Remove(status.ID); StatusByObject.Remove(status.Object); status.Object.GetParent().RemoveChild(status.Object); diff --git a/src/Network/SyncServer.cs b/src/Network/SyncServer.cs index 577c9ab..c0eae6d 100644 --- a/src/Network/SyncServer.cs +++ b/src/Network/SyncServer.cs @@ -6,7 +6,6 @@ using Godot; public class SyncServer : Sync { private static readonly HashSet _dirtyObjects = new HashSet(); - private static uint _syncIDCounter = 1; protected Server Server => (Server)Game; @@ -16,12 +15,15 @@ public class SyncServer : Sync public T Spawn() where T : Node { - var info = SyncRegistry.Get(); - var obj = info.Scene.Init(); - var status = new SyncStatus(_syncIDCounter++, obj, info){ Mode = SyncMode.Spawn }; - StatusBySyncID.Add(status.SyncID, status); + var info = SyncRegistry.Get(); + var obj = info.Scene.Init(); + var id = Server.Objects.Add(obj); + + var status = new SyncStatus(id, obj, info){ Mode = SyncMode.Spawn }; + StatusByID.Add(status.ID, status); StatusByObject.Add(status.Object, status); _dirtyObjects.Add(status); + Server.GetNode("World").AddChild(obj); return obj; @@ -33,7 +35,7 @@ public class SyncServer : Sync var status = GetStatusOrThrow(obj); status.Mode = SyncMode.Destroy; - StatusBySyncID.Remove(status.SyncID); + StatusByID.Remove(status.ID); StatusByObject.Remove(status.Object); _dirtyObjects.Add(status); @@ -63,7 +65,7 @@ public class SyncServer : Sync foreach (var prop in status.Info.PropertiesByID) if ((status.DirtyProperties & (1 << prop.ID)) != 0) values.Add((prop.ID, prop.Getter(status.Object))); - packet.Changes.Add(new SyncPacket.Object(status.Info.ID, status.SyncID, status.Mode, values)); + packet.Changes.Add(new SyncPacket.Object(status.Info.ID, status.ID, status.Mode, values)); // If the object has been newly spawned, now is the time to remove the "Spawn" flag. if (status.Mode == SyncMode.Spawn) status.Mode = SyncMode.Default; } @@ -80,7 +82,7 @@ public class SyncServer : Sync var values = new List<(byte, object)>(); foreach (var prop in status.Info.PropertiesByID) values.Add((prop.ID, prop.Getter(status.Object))); - packet.Changes.Add(new SyncPacket.Object(status.Info.ID, status.SyncID, SyncMode.Spawn, values)); + packet.Changes.Add(new SyncPacket.Object(status.Info.ID, status.ID, SyncMode.Spawn, values)); } NetworkPackets.Send(server, new []{ networkID }, packet); } @@ -89,6 +91,5 @@ public class SyncServer : Sync { base.Clear(); _dirtyObjects.Clear(); - _syncIDCounter = 1; } } diff --git a/src/Objects/ObjectHolder.cs b/src/Objects/ObjectHolder.cs new file mode 100644 index 0000000..cdb2720 --- /dev/null +++ b/src/Objects/ObjectHolder.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using Godot; + +public class ObjectHolder +{ + private readonly Dictionary _nodeByID = new Dictionary(); + private readonly Dictionary _idByNode = new Dictionary(); + private uint _newIDCounter = 1; + + public UniqueID Add(Node obj) + { + UniqueID id; + // Keep going until we find an unused UniqueID. + while (_nodeByID.TryGetValue(id = new UniqueID(_newIDCounter++), out _)) { } + Add(id, obj); + return id; + } + public void Add(UniqueID id, Node obj) + { + _nodeByID.Add(id, obj); + _idByNode.Add(obj, id); + } + + public UniqueID GetSyncID(Node obj) + => _idByNode.TryGetValue(obj, out var value) ? value : throw new Exception( + $"The specified object '{obj}' does not have a UniqueID"); + public Node GetNodeByID(UniqueID id) + => _nodeByID.TryGetValue(id, out var value) ? value : throw new Exception( + $"No object associated with {id}"); + + public void Clear() + { + _nodeByID.Clear(); + _idByNode.Clear(); + } +} + +public readonly struct UniqueID : IEquatable +{ + public uint Value { get; } + public UniqueID(uint value) => Value = value; + public override bool Equals(object obj) => (obj is UniqueID other) && Equals(other); + public bool Equals(UniqueID other) => Value == other.Value; + public override int GetHashCode() => Value.GetHashCode(); + public override string ToString() => $"{nameof(UniqueID)}({Value})"; + public static bool operator ==(UniqueID left, UniqueID right) => left.Equals(right); + public static bool operator !=(UniqueID left, UniqueID right) => !left.Equals(right); +} diff --git a/src/Scenes/Game.cs b/src/Scenes/Game.cs index 54836d8..9bd383c 100644 --- a/src/Scenes/Game.cs +++ b/src/Scenes/Game.cs @@ -4,6 +4,7 @@ using Godot.Collections; public abstract class Game : Node2D { + public ObjectHolder Objects { get; } = new ObjectHolder(); public Sync Sync { get; protected set; } // Using _EnterTree to make sure this code runs before any other. diff --git a/src/Scenes/Server.cs b/src/Scenes/Server.cs index ff6362b..b20f75b 100644 --- a/src/Scenes/Server.cs +++ b/src/Scenes/Server.cs @@ -155,7 +155,7 @@ public readonly struct NetworkID : IEquatable public override bool Equals(object obj) => (obj is NetworkID other) && Equals(other); public bool Equals(NetworkID other) => Value == other.Value; public override int GetHashCode() => Value.GetHashCode(); - public override string ToString() => $"NetworkID({Value})"; + public override string ToString() => $"{nameof(NetworkID)}({Value})"; public static bool operator ==(NetworkID left, NetworkID right) => left.Equals(right); public static bool operator !=(NetworkID left, NetworkID right) => !left.Equals(right); }