Keep track of game object by UniqueID

. Add ObjectHolder which does just that
- ObjectHolder is accessible though Game.Objects
- UniqueID replaces "sync ID"
- Move DeSerializer classes to IO folder
main
copygirl 4 years ago
parent e62d7f5ee6
commit 7861e524ab
  1. 4
      src/EscapeMenuMultiplayer.cs
  2. 14
      src/IO/DeSerializer.Impl.cs
  3. 0
      src/IO/DeSerializer.Interfaces.cs
  4. 2
      src/IO/DeSerializerRegistry.cs
  5. 7
      src/IO/Save.cs
  6. 32
      src/Network/Sync.cs
  7. 15
      src/Network/SyncClient.cs
  8. 15
      src/Network/SyncServer.cs
  9. 49
      src/Objects/ObjectHolder.cs
  10. 1
      src/Scenes/Game.cs
  11. 2
      src/Scenes/Server.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();
}
}

@ -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<SyncObjectAttribute>() == 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<TObj>
private class NodeDeSerializer<TObj>
: DeSerializer<TObj>
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;
}

@ -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<T>(Action<BinaryWriter, T> serialize, Func<BinaryReader, T> deserialize)

@ -0,0 +1,7 @@
using System;
[AttributeUsage(AttributeTargets.Property)]
public class SaveAttribute : Attribute
{
}

@ -9,17 +9,17 @@ using Godot;
public class Sync
{
protected Game Game { get; }
protected Dictionary<uint, SyncStatus> StatusBySyncID { get; } = new Dictionary<uint, SyncStatus>();
protected Dictionary<UniqueID, SyncStatus> StatusByID { get; } = new Dictionary<UniqueID, SyncStatus>();
protected Dictionary<Node, SyncStatus> StatusByObject { get; } = new Dictionary<Node, SyncStatus>();
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);
}
}

@ -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<Node>();
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);

@ -6,7 +6,6 @@ using Godot;
public class SyncServer : Sync
{
private static readonly HashSet<SyncStatus> _dirtyObjects = new HashSet<SyncStatus>();
private static uint _syncIDCounter = 1;
protected Server Server => (Server)Game;
@ -18,10 +17,13 @@ public class SyncServer : Sync
{
var info = SyncRegistry.Get<T>();
var obj = info.Scene.Init<T>();
var status = new SyncStatus(_syncIDCounter++, obj, info){ Mode = SyncMode.Spawn };
StatusBySyncID.Add(status.SyncID, status);
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;
}
}

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using Godot;
public class ObjectHolder
{
private readonly Dictionary<UniqueID, Node> _nodeByID = new Dictionary<UniqueID, Node>();
private readonly Dictionary<Node, UniqueID> _idByNode = new Dictionary<Node, UniqueID>();
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<UniqueID>
{
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);
}

@ -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.

@ -155,7 +155,7 @@ public readonly struct NetworkID : IEquatable<NetworkID>
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);
}

Loading…
Cancel
Save