diff --git a/scene/GameScene.tscn b/scene/GameScene.tscn index 29264eb..9d569b4 100644 --- a/scene/GameScene.tscn +++ b/scene/GameScene.tscn @@ -15,6 +15,7 @@ [node name="Game" type="Node"] pause_mode = 2 script = ExtResource( 3 ) +BlockContainerPath = NodePath("Blocks") BlockScene = ExtResource( 6 ) [node name="Viewport" type="Node" parent="."] @@ -44,6 +45,8 @@ pause_mode = 1 [node name="LocalPlayer" parent="Players" instance=ExtResource( 5 )] position = Vector2( 0, -2 ) +[node name="Blocks" type="Node" parent="."] + [node name="HUD" type="CanvasLayer" parent="."] [node name="Cursor" type="Node2D" parent="HUD"] diff --git a/src/Game.cs b/src/Game.cs index fdfd24c..d499fe7 100644 --- a/src/Game.cs +++ b/src/Game.cs @@ -4,8 +4,11 @@ public class Game : Node { public static Game Instance { get; private set; } + [Export] public NodePath BlockContainerPath { get; set; } [Export] public PackedScene BlockScene { get; set; } + public Node BlockContainer { get; private set; } + public Game() => Instance = this; // Using _EnterTree to make sure this code runs before any other. @@ -13,15 +16,24 @@ public class Game : Node => GD.Randomize(); public override void _Ready() - => SpawnBlocks(); + { + BlockContainer = GetNode(BlockContainerPath); + SpawnDefaultBlocks(); + } + + public void ClearBlocks() + { + foreach (var block in BlockContainer.GetChildren()) + ((Node)block).Free(); + } - private void SpawnBlocks() + public void SpawnDefaultBlocks() { for (var x = -6; x <= 6; x++) { var block = BlockScene.Init(); block.Position = new Vector2(x * 16, 48); block.Modulate = Color.FromHsv(GD.Randf(), 0.1F, 1.0F); - AddChild(block); + BlockContainer.AddChild(block); } } } diff --git a/src/Network.cs b/src/Network.cs index 28c8158..406ab23 100644 --- a/src/Network.cs +++ b/src/Network.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Godot; public enum NetworkStatus @@ -43,10 +44,7 @@ public class Network : Node public Node PlayerContainer { get; private set; } - public Network() - { - Instance = this; - } + public Network() =>Instance = this; public override void _Ready() { @@ -65,6 +63,8 @@ public class Network : Node API.RegisterC2SPacket(OnClientAuthPacket); API.RegisterS2CPacket(OnSpawnPlayerPacket); + API.RegisterS2CPacket(OnSpawnBlockPacket); + API.RegisterS2CPacket(OnSpawnBlocksPacket); Player.RegisterPackets(); } @@ -73,13 +73,17 @@ public class Network : Node => API.OnPacketReceived(id, bytes); - public void ClearPlayers() + public void ResetGame() { LocalPlayer.Instance.NetworkID = -1; + + // Clear other players. foreach (var player in _playersById.Values) - if (!player.IsLocal) - player.QueueFree(); + if (!player.IsLocal) player.QueueFree(); _playersById.Clear(); + + // Game.Instance.ClearBlocks(); + // Game.Instance.SpawnDefaultBlocks(); } private void ChangeStatus(NetworkStatus status) @@ -116,7 +120,7 @@ public class Network : Node ((NetworkedMultiplayerENet)GetTree().NetworkPeer).CloseConnection(); GetTree().NetworkPeer = null; - ClearPlayers(); + ResetGame(); ChangeStatus(NetworkStatus.NoConnection); } @@ -142,10 +146,7 @@ public class Network : Node LocalPlayer.Instance.NetworkID = id; _playersById.Add(id, LocalPlayer.Instance); - API.SendToServer(new ClientAuthPacket { - DisplayName = LocalPlayer.Instance.DisplayName, - Color = LocalPlayer.Instance.Color - }); + API.SendToServer(new ClientAuthPacket(LocalPlayer.Instance)); } public void DisconnectFromServer() @@ -156,7 +157,7 @@ public class Network : Node GetTree().NetworkPeer = null; ChangeStatus(NetworkStatus.NoConnection); - ClearPlayers(); + ResetGame(); } @@ -176,27 +177,31 @@ public class Network : Node private class ClientAuthPacket { - public string DisplayName { get; set; } - public Color Color { get; set; } + public string DisplayName { get; } + public Color Color { get; } + public ClientAuthPacket(Player player) + { DisplayName = player.DisplayName; Color = player.Color; } } private void OnClientAuthPacket(int networkID, ClientAuthPacket packet) { // Authentication message is only sent once, so once the Player object exists, ignore this message. if (GetPlayer(networkID) != null) return; + API.SendTo(networkID, new SpawnBlocksPacket()); + foreach (var player in _playersById.Values) API.SendTo(networkID, new SpawnPlayerPacket(player)); + var newPlayer = SpawnOtherPlayer(networkID, Vector2.Zero, packet.DisplayName, packet.Color); API.SendToEveryone(new SpawnPlayerPacket(newPlayer)); } - private class SpawnPlayerPacket { - public int NetworkID { get; set; } - public Vector2 Position { get; set; } - public string DisplayName { get; set; } - public Color Color { get; set; } + public int NetworkID { get; } + public Vector2 Position { get; } + public string DisplayName { get; } + public Color Color { get; } public SpawnPlayerPacket(Player player) { @@ -216,6 +221,35 @@ public class Network : Node } else SpawnOtherPlayer(packet.NetworkID, packet.Position, packet.DisplayName, packet.Color); } + private struct SpawnBlockPacket + { + public Vector2 Position { get; } + public Color Color { get; } + public SpawnBlockPacket(Node2D block) + { Position = block.Position; Color = block.Modulate; } + } + private void OnSpawnBlockPacket(SpawnBlockPacket packet) + { + var block = Game.Instance.BlockScene.Init(); + block.Position = packet.Position; + block.Modulate = packet.Color; + Game.Instance.BlockContainer.AddChild(block); + } + + private class SpawnBlocksPacket + { + public List Blocks { get; } + public SpawnBlocksPacket() + => Blocks = Game.Instance.BlockContainer.GetChildren().OfType() + .Select(block => new SpawnBlockPacket(block)).ToList(); + } + private void OnSpawnBlocksPacket(SpawnBlocksPacket packet) + { + Game.Instance.ClearBlocks(); + foreach (var block in packet.Blocks) + OnSpawnBlockPacket(block); + } + private void OnPeerConnected(int id) { diff --git a/src/NetworkAPI.cs b/src/NetworkAPI.cs index 5874939..99dd274 100644 --- a/src/NetworkAPI.cs +++ b/src/NetworkAPI.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; using Godot; [Flags] @@ -23,13 +21,13 @@ public enum TransferMode // TODO: Improve performance and type safety of de/serialization. // TODO: Support easily spawning and syncronizing objects and their properties. -public class NetworkAPI +public partial class NetworkAPI { private readonly MultiplayerAPI _multiplayerAPI; private readonly List _packetsById = new List(); private readonly Dictionary _packetsByType = new Dictionary(); private readonly Dictionary _deSerializers = new Dictionary(); - // private readonly List _multiDeSerializers = new List(); + private readonly List _deSerializerGenerators = new List(); private class PacketInfo { @@ -80,15 +78,17 @@ public class NetworkAPI RegisterDeSerializer((writer, value) => writer.Write(value.ToRgba32()), reader => new Color(reader.ReadInt32())); - // TODO: Add handling for Array, List and Dictionary. + RegisterDeSerializerGenerator(new DictionaryDeSerializerGenerator()); + RegisterDeSerializerGenerator(new CollectionDeSerializerGenerator()); + RegisterDeSerializerGenerator(new ArrayDeSerializerGenerator()); } public void RegisterDeSerializer(Action serialize, Func deserialize) - => _deSerializers.Add(typeof(T), new SimpleNetworkDeSerializer(serialize, deserialize)); + => _deSerializers.Add(typeof(T), new SimpleDeSerializer(serialize, deserialize)); public void RegisterDeSerializer(INetworkDeSerializer deSerializer) => _deSerializers.Add(typeof(T), deSerializer); - // public void RegisterDeSerializer(INetworkDeSerializerMulti deSerializer) - // => _multiDeSerializers.Add(deSerializer); + public void RegisterDeSerializerGenerator(INetworkDeSerializerGenerator deSerializerGenerator) + => _deSerializerGenerators.Add(deSerializerGenerator); public void RegisterS2CPacket(Action action, TransferMode defaultTransferMode = TransferMode.Reliable) => RegisterPacket((int _id, T packet) => action(packet), defaultTransferMode, PacketDirection.ServerToClient); @@ -199,75 +199,10 @@ public class NetworkAPI if (!createIfMissing) throw new InvalidOperationException( $"No DeSerializer for type {type} found"); - value = new ComplexNetworkDeSerializer(type); + value = _deSerializerGenerators.Select(g => g.GenerateFor(type)).FirstOrDefault(x => x != null); + if (value == null) value = new ComplexDeSerializer(type); _deSerializers.Add(type, value); } return value; } - - private class SimpleNetworkDeSerializer - : INetworkDeSerializer - { - private readonly Action _serialize; - private readonly Func _deserialize; - public SimpleNetworkDeSerializer(Action serialize, Func deserialize) - { _serialize = serialize; _deserialize = deserialize; } - public void Serialize(BinaryWriter writer, object value) => _serialize(writer, (T)value); - public object Deserialize(BinaryReader reader) => _deserialize(reader); - } - - // private class ArrayNetworkDeSerializer - // : INetworkDeSerializerMulti - // { - // public bool Handles(Type type) => type.IsArray; - - // public void Serialize(BinaryWriter writer, object value) - // { - // var array = (Array)value; - // writer.Write(array.Length); - // var deSerializer = Network.API.GetOrCreateDeserializer(array.GetType().GetElementType()); - // foreach (var element in array) deSerializer.Serialize(writer, element); - // } - - // public object Deserialize(BinaryReader reader) - // { - // TODO: This doesn't work. We need the type to initialize the array. - // We may want to generate a new INetworkDeSerializer for each array type..? - // } - // } - - private class ComplexNetworkDeSerializer - : INetworkDeSerializer - { - private readonly Type _type; - private event Action OnSerialize; - private event Action OnDeserialize; - - public ComplexNetworkDeSerializer(Type type) - { - _type = type; - foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { - var deSerializer = Network.API.GetDeSerializer(field.FieldType, false); - OnSerialize += (writer, value) => deSerializer.Serialize(writer, field.GetValue(value)); - OnDeserialize += (reader, instance) => field.SetValue(instance, deSerializer.Deserialize(reader)); - } - if (OnSerialize == null) throw new InvalidOperationException( - $"Unable to create serializer for type {type}"); - } - - public void Serialize(BinaryWriter writer, object value) - => OnSerialize(writer, value); - public object Deserialize(BinaryReader reader) - { - var instance = FormatterServices.GetUninitializedObject(_type); - OnDeserialize(reader, instance); - return instance; - } - } -} - -public interface INetworkDeSerializer -{ - void Serialize(BinaryWriter writer, object value); - object Deserialize(BinaryReader reader); } diff --git a/src/NetworkAPIDeSerializers.cs b/src/NetworkAPIDeSerializers.cs new file mode 100644 index 0000000..0beb425 --- /dev/null +++ b/src/NetworkAPIDeSerializers.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; + +public interface INetworkDeSerializer +{ + void Serialize(BinaryWriter writer, object value); + object Deserialize(BinaryReader reader); +} + +public interface INetworkDeSerializerGenerator +{ + INetworkDeSerializer GenerateFor(Type type); +} + +public partial class NetworkAPI +{ + private class SimpleDeSerializer + : INetworkDeSerializer + { + private readonly Action _serialize; + private readonly Func _deserialize; + public SimpleDeSerializer(Action serialize, Func deserialize) + { _serialize = serialize; _deserialize = deserialize; } + public void Serialize(BinaryWriter writer, object value) => _serialize(writer, (T)value); + public object Deserialize(BinaryReader reader) => _deserialize(reader); + } + + private class ArrayDeSerializerGenerator + : INetworkDeSerializerGenerator + { + public INetworkDeSerializer GenerateFor(Type type) + { + if (!type.IsArray) return null; + var deSerializerType = typeof(ArrayDeSerializer<>).MakeGenericType(type.GetElementType()); + return (INetworkDeSerializer)Activator.CreateInstance(deSerializerType); + } + } + private class ArrayDeSerializer + : INetworkDeSerializer + { + private readonly INetworkDeSerializer _elementDeSerializer = + Network.API.GetDeSerializer(typeof(T), true); + + public void Serialize(BinaryWriter writer, object value) + { + var array = (T[])value; + writer.Write(array.Length); + foreach (var element in array) _elementDeSerializer.Serialize(writer, element); + } + + public object Deserialize(BinaryReader reader) + { + var length = reader.ReadInt32(); + var array = new T[length]; + for (var i = 0; i < length; i++) + array[i] = (T)_elementDeSerializer.Deserialize(reader); + return array; + } + } + + private class CollectionDeSerializerGenerator + : INetworkDeSerializerGenerator + { + public INetworkDeSerializer GenerateFor(Type type) + { + Type elementType; + if (type.IsInterface) { + if (!type.IsGenericType) return null; + elementType = type.GetGenericArguments()[0]; + var typeDef = type.GetGenericTypeDefinition(); + if (typeDef == typeof(ICollection<>)) type = typeof(List<>).MakeGenericType(elementType); + else if (typeDef == typeof(IList<>)) type = typeof(List<>).MakeGenericType(elementType); + else if (typeDef == typeof(ISet<>)) type = typeof(HashSet<>).MakeGenericType(elementType); + else return null; + } else { + if (type.GetConstructor(Type.EmptyTypes) == null) return null; + elementType = type.GetInterfaces() + .Where(i => i.IsGenericType && (i.GetGenericTypeDefinition() == typeof(ICollection<>))) + .Select(i => i.GetGenericArguments()[0]) + .FirstOrDefault(); + if (elementType == null) return null; + } + var deSerializerType = typeof(CollectionDeSerializer<,>).MakeGenericType(type, elementType); + return (INetworkDeSerializer)Activator.CreateInstance(deSerializerType); + } + } + private class CollectionDeSerializer + : INetworkDeSerializer + where TCollection : ICollection, new() + { + private readonly INetworkDeSerializer _elementDeSerializer = + Network.API.GetDeSerializer(typeof(TElement), true); + + public void Serialize(BinaryWriter writer, object value) + { + var collection = (TCollection)value; + writer.Write(collection.Count); + foreach (var element in collection) + _elementDeSerializer.Serialize(writer, element); + } + + public object Deserialize(BinaryReader reader) + { + var count = reader.ReadInt32(); + var collection = new TCollection(); + for (var i = 0; i < count; i++) + collection.Add((TElement)_elementDeSerializer.Deserialize(reader)); + return collection; + } + } + + private class DictionaryDeSerializerGenerator + : INetworkDeSerializerGenerator + { + public INetworkDeSerializer GenerateFor(Type type) + { + Type keyType, valueType; + if (type.IsInterface) { + if (!type.IsGenericType || (type.GetGenericTypeDefinition() != typeof(IDictionary<,>))) return null; + keyType = type.GetGenericArguments()[0]; + valueType = type.GetGenericArguments()[1]; + type = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); + } else { + if (type.GetConstructor(Type.EmptyTypes) == null) return null; + (keyType, valueType) = type.GetInterfaces() + .Where(i => i.IsGenericType && (i.GetGenericTypeDefinition() == typeof(IDictionary<,>))) + .Select(i => (i.GetGenericArguments()[0], i.GetGenericArguments()[1])) + .FirstOrDefault(); + if (keyType == null) return null; + } + var deSerializerType = typeof(DictionaryDeSerializer<,,>).MakeGenericType(type, keyType, valueType); + return (INetworkDeSerializer)Activator.CreateInstance(deSerializerType); + } + } + private class DictionaryDeSerializer + : INetworkDeSerializer + where TDictionary : IDictionary, new() + { + private readonly INetworkDeSerializer _keyDeSerializer = + Network.API.GetDeSerializer(typeof(TKey), true); + private readonly INetworkDeSerializer _valueDeSerializer = + Network.API.GetDeSerializer(typeof(TKey), true); + + public void Serialize(BinaryWriter writer, object value) + { + var dictionary = (TDictionary)value; + writer.Write(dictionary.Count); + foreach (var element in dictionary) { + _keyDeSerializer.Serialize(writer, element.Key); + _valueDeSerializer.Serialize(writer, element.Value); + } + } + + public object Deserialize(BinaryReader reader) + { + var count = reader.ReadInt32(); + var dictionary = new TDictionary(); + for (var i = 0; i < count; i++) + dictionary.Add((TKey)_keyDeSerializer.Deserialize(reader), + (TValue)_valueDeSerializer.Deserialize(reader)); + return dictionary; + } + } + + // TODO: Replace this with something that will generate code at runtime for improved performance. + private class ComplexDeSerializer + : INetworkDeSerializer + { + private readonly Type _type; + private event Action OnSerialize; + private event Action OnDeserialize; + + public ComplexDeSerializer(Type type) + { + _type = type; + foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { + var deSerializer = Network.API.GetDeSerializer(field.FieldType, true); + OnSerialize += (writer, value) => deSerializer.Serialize(writer, field.GetValue(value)); + OnDeserialize += (reader, instance) => field.SetValue(instance, deSerializer.Deserialize(reader)); + } + if (OnSerialize == null) throw new InvalidOperationException( + $"Unable to create serializer for type {type}"); + } + + public void Serialize(BinaryWriter writer, object value) + => OnSerialize(writer, value); + public object Deserialize(BinaryReader reader) + { + var instance = FormatterServices.GetUninitializedObject(_type); + OnDeserialize(reader, instance); + return instance; + } + } +}