Add collection de/serializers and sync blocks

- Allow registering de/serializer "generators"
- Add generators for arrays, lists and dictionaries
- Move de/serialization code into extra file
  (still part of NetworkAPI thanks to "partial")
- Synchronize existing blocks to new players
main
copygirl 5 years ago
parent 4f0d5a537e
commit 9b53e6b2d3
  1. 3
      scene/GameScene.tscn
  2. 18
      src/Game.cs
  3. 74
      src/Network.cs
  4. 85
      src/NetworkAPI.cs
  5. 198
      src/NetworkAPIDeSerializers.cs

@ -15,6 +15,7 @@
[node name="Game" type="Node"] [node name="Game" type="Node"]
pause_mode = 2 pause_mode = 2
script = ExtResource( 3 ) script = ExtResource( 3 )
BlockContainerPath = NodePath("Blocks")
BlockScene = ExtResource( 6 ) BlockScene = ExtResource( 6 )
[node name="Viewport" type="Node" parent="."] [node name="Viewport" type="Node" parent="."]
@ -44,6 +45,8 @@ pause_mode = 1
[node name="LocalPlayer" parent="Players" instance=ExtResource( 5 )] [node name="LocalPlayer" parent="Players" instance=ExtResource( 5 )]
position = Vector2( 0, -2 ) position = Vector2( 0, -2 )
[node name="Blocks" type="Node" parent="."]
[node name="HUD" type="CanvasLayer" parent="."] [node name="HUD" type="CanvasLayer" parent="."]
[node name="Cursor" type="Node2D" parent="HUD"] [node name="Cursor" type="Node2D" parent="HUD"]

@ -4,8 +4,11 @@ public class Game : Node
{ {
public static Game Instance { get; private set; } public static Game Instance { get; private set; }
[Export] public NodePath BlockContainerPath { get; set; }
[Export] public PackedScene BlockScene { get; set; } [Export] public PackedScene BlockScene { get; set; }
public Node BlockContainer { get; private set; }
public Game() => Instance = this; public Game() => Instance = this;
// Using _EnterTree to make sure this code runs before any other. // Using _EnterTree to make sure this code runs before any other.
@ -13,15 +16,24 @@ public class Game : Node
=> GD.Randomize(); => GD.Randomize();
public override void _Ready() 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++) { for (var x = -6; x <= 6; x++) {
var block = BlockScene.Init<Node2D>(); var block = BlockScene.Init<Node2D>();
block.Position = new Vector2(x * 16, 48); block.Position = new Vector2(x * 16, 48);
block.Modulate = Color.FromHsv(GD.Randf(), 0.1F, 1.0F); block.Modulate = Color.FromHsv(GD.Randf(), 0.1F, 1.0F);
AddChild(block); BlockContainer.AddChild(block);
} }
} }
} }

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Godot; using Godot;
public enum NetworkStatus public enum NetworkStatus
@ -43,10 +44,7 @@ public class Network : Node
public Node PlayerContainer { get; private set; } public Node PlayerContainer { get; private set; }
public Network() public Network() =>Instance = this;
{
Instance = this;
}
public override void _Ready() public override void _Ready()
{ {
@ -65,6 +63,8 @@ public class Network : Node
API.RegisterC2SPacket<ClientAuthPacket>(OnClientAuthPacket); API.RegisterC2SPacket<ClientAuthPacket>(OnClientAuthPacket);
API.RegisterS2CPacket<SpawnPlayerPacket>(OnSpawnPlayerPacket); API.RegisterS2CPacket<SpawnPlayerPacket>(OnSpawnPlayerPacket);
API.RegisterS2CPacket<SpawnBlockPacket>(OnSpawnBlockPacket);
API.RegisterS2CPacket<SpawnBlocksPacket>(OnSpawnBlocksPacket);
Player.RegisterPackets(); Player.RegisterPackets();
} }
@ -73,13 +73,17 @@ public class Network : Node
=> API.OnPacketReceived(id, bytes); => API.OnPacketReceived(id, bytes);
public void ClearPlayers() public void ResetGame()
{ {
LocalPlayer.Instance.NetworkID = -1; LocalPlayer.Instance.NetworkID = -1;
// Clear other players.
foreach (var player in _playersById.Values) foreach (var player in _playersById.Values)
if (!player.IsLocal) if (!player.IsLocal) player.QueueFree();
player.QueueFree();
_playersById.Clear(); _playersById.Clear();
// Game.Instance.ClearBlocks();
// Game.Instance.SpawnDefaultBlocks();
} }
private void ChangeStatus(NetworkStatus status) private void ChangeStatus(NetworkStatus status)
@ -116,7 +120,7 @@ public class Network : Node
((NetworkedMultiplayerENet)GetTree().NetworkPeer).CloseConnection(); ((NetworkedMultiplayerENet)GetTree().NetworkPeer).CloseConnection();
GetTree().NetworkPeer = null; GetTree().NetworkPeer = null;
ClearPlayers(); ResetGame();
ChangeStatus(NetworkStatus.NoConnection); ChangeStatus(NetworkStatus.NoConnection);
} }
@ -142,10 +146,7 @@ public class Network : Node
LocalPlayer.Instance.NetworkID = id; LocalPlayer.Instance.NetworkID = id;
_playersById.Add(id, LocalPlayer.Instance); _playersById.Add(id, LocalPlayer.Instance);
API.SendToServer(new ClientAuthPacket { API.SendToServer(new ClientAuthPacket(LocalPlayer.Instance));
DisplayName = LocalPlayer.Instance.DisplayName,
Color = LocalPlayer.Instance.Color
});
} }
public void DisconnectFromServer() public void DisconnectFromServer()
@ -156,7 +157,7 @@ public class Network : Node
GetTree().NetworkPeer = null; GetTree().NetworkPeer = null;
ChangeStatus(NetworkStatus.NoConnection); ChangeStatus(NetworkStatus.NoConnection);
ClearPlayers(); ResetGame();
} }
@ -176,27 +177,31 @@ public class Network : Node
private class ClientAuthPacket private class ClientAuthPacket
{ {
public string DisplayName { get; set; } public string DisplayName { get; }
public Color Color { get; set; } public Color Color { get; }
public ClientAuthPacket(Player player)
{ DisplayName = player.DisplayName; Color = player.Color; }
} }
private void OnClientAuthPacket(int networkID, ClientAuthPacket packet) private void OnClientAuthPacket(int networkID, ClientAuthPacket packet)
{ {
// Authentication message is only sent once, so once the Player object exists, ignore this message. // Authentication message is only sent once, so once the Player object exists, ignore this message.
if (GetPlayer(networkID) != null) return; if (GetPlayer(networkID) != null) return;
API.SendTo(networkID, new SpawnBlocksPacket());
foreach (var player in _playersById.Values) foreach (var player in _playersById.Values)
API.SendTo(networkID, new SpawnPlayerPacket(player)); API.SendTo(networkID, new SpawnPlayerPacket(player));
var newPlayer = SpawnOtherPlayer(networkID, Vector2.Zero, packet.DisplayName, packet.Color); var newPlayer = SpawnOtherPlayer(networkID, Vector2.Zero, packet.DisplayName, packet.Color);
API.SendToEveryone(new SpawnPlayerPacket(newPlayer)); API.SendToEveryone(new SpawnPlayerPacket(newPlayer));
} }
private class SpawnPlayerPacket private class SpawnPlayerPacket
{ {
public int NetworkID { get; set; } public int NetworkID { get; }
public Vector2 Position { get; set; } public Vector2 Position { get; }
public string DisplayName { get; set; } public string DisplayName { get; }
public Color Color { get; set; } public Color Color { get; }
public SpawnPlayerPacket(Player player) public SpawnPlayerPacket(Player player)
{ {
@ -216,6 +221,35 @@ public class Network : Node
} else SpawnOtherPlayer(packet.NetworkID, packet.Position, packet.DisplayName, packet.Color); } 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<Node2D>();
block.Position = packet.Position;
block.Modulate = packet.Color;
Game.Instance.BlockContainer.AddChild(block);
}
private class SpawnBlocksPacket
{
public List<SpawnBlockPacket> Blocks { get; }
public SpawnBlocksPacket()
=> Blocks = Game.Instance.BlockContainer.GetChildren().OfType<Node2D>()
.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) private void OnPeerConnected(int id)
{ {

@ -2,8 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Godot; using Godot;
[Flags] [Flags]
@ -23,13 +21,13 @@ public enum TransferMode
// TODO: Improve performance and type safety of de/serialization. // TODO: Improve performance and type safety of de/serialization.
// TODO: Support easily spawning and syncronizing objects and their properties. // TODO: Support easily spawning and syncronizing objects and their properties.
public class NetworkAPI public partial class NetworkAPI
{ {
private readonly MultiplayerAPI _multiplayerAPI; private readonly MultiplayerAPI _multiplayerAPI;
private readonly List<PacketInfo> _packetsById = new List<PacketInfo>(); private readonly List<PacketInfo> _packetsById = new List<PacketInfo>();
private readonly Dictionary<Type, PacketInfo> _packetsByType = new Dictionary<Type, PacketInfo>(); private readonly Dictionary<Type, PacketInfo> _packetsByType = new Dictionary<Type, PacketInfo>();
private readonly Dictionary<Type, INetworkDeSerializer> _deSerializers = new Dictionary<Type, INetworkDeSerializer>(); private readonly Dictionary<Type, INetworkDeSerializer> _deSerializers = new Dictionary<Type, INetworkDeSerializer>();
// private readonly List<INetworkDeSerializerMulti> _multiDeSerializers = new List<INetworkDeSerializerMulti>(); private readonly List<INetworkDeSerializerGenerator> _deSerializerGenerators = new List<INetworkDeSerializerGenerator>();
private class PacketInfo private class PacketInfo
{ {
@ -80,15 +78,17 @@ public class NetworkAPI
RegisterDeSerializer((writer, value) => writer.Write(value.ToRgba32()), RegisterDeSerializer((writer, value) => writer.Write(value.ToRgba32()),
reader => new Color(reader.ReadInt32())); 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<T>(Action<BinaryWriter, T> serialize, Func<BinaryReader, T> deserialize) public void RegisterDeSerializer<T>(Action<BinaryWriter, T> serialize, Func<BinaryReader, T> deserialize)
=> _deSerializers.Add(typeof(T), new SimpleNetworkDeSerializer<T>(serialize, deserialize)); => _deSerializers.Add(typeof(T), new SimpleDeSerializer<T>(serialize, deserialize));
public void RegisterDeSerializer<T>(INetworkDeSerializer deSerializer) public void RegisterDeSerializer<T>(INetworkDeSerializer deSerializer)
=> _deSerializers.Add(typeof(T), deSerializer); => _deSerializers.Add(typeof(T), deSerializer);
// public void RegisterDeSerializer(INetworkDeSerializerMulti deSerializer) public void RegisterDeSerializerGenerator(INetworkDeSerializerGenerator deSerializerGenerator)
// => _multiDeSerializers.Add(deSerializer); => _deSerializerGenerators.Add(deSerializerGenerator);
public void RegisterS2CPacket<T>(Action<T> action, TransferMode defaultTransferMode = TransferMode.Reliable) public void RegisterS2CPacket<T>(Action<T> action, TransferMode defaultTransferMode = TransferMode.Reliable)
=> RegisterPacket((int _id, T packet) => action(packet), defaultTransferMode, PacketDirection.ServerToClient); => RegisterPacket((int _id, T packet) => action(packet), defaultTransferMode, PacketDirection.ServerToClient);
@ -199,75 +199,10 @@ public class NetworkAPI
if (!createIfMissing) throw new InvalidOperationException( if (!createIfMissing) throw new InvalidOperationException(
$"No DeSerializer for type {type} found"); $"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); _deSerializers.Add(type, value);
} }
return value; return value;
} }
private class SimpleNetworkDeSerializer<T>
: INetworkDeSerializer
{
private readonly Action<BinaryWriter, T> _serialize;
private readonly Func<BinaryReader, T> _deserialize;
public SimpleNetworkDeSerializer(Action<BinaryWriter, T> serialize, Func<BinaryReader, T> 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<BinaryWriter, object> OnSerialize;
private event Action<BinaryReader, object> 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);
} }

@ -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<T>
: INetworkDeSerializer
{
private readonly Action<BinaryWriter, T> _serialize;
private readonly Func<BinaryReader, T> _deserialize;
public SimpleDeSerializer(Action<BinaryWriter, T> serialize, Func<BinaryReader, T> 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<T>
: 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<TCollection, TElement>
: INetworkDeSerializer
where TCollection : ICollection<TElement>, 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<TDictionary, TKey, TValue>
: INetworkDeSerializer
where TDictionary : IDictionary<TKey, TValue>, 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<BinaryWriter, object> OnSerialize;
private event Action<BinaryReader, object> 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;
}
}
}
Loading…
Cancel
Save