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"]
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"]

@ -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<Node2D>();
block.Position = new Vector2(x * 16, 48);
block.Modulate = Color.FromHsv(GD.Randf(), 0.1F, 1.0F);
AddChild(block);
BlockContainer.AddChild(block);
}
}
}

@ -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<ClientAuthPacket>(OnClientAuthPacket);
API.RegisterS2CPacket<SpawnPlayerPacket>(OnSpawnPlayerPacket);
API.RegisterS2CPacket<SpawnBlockPacket>(OnSpawnBlockPacket);
API.RegisterS2CPacket<SpawnBlocksPacket>(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<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)
{

@ -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<PacketInfo> _packetsById = new List<PacketInfo>();
private readonly Dictionary<Type, PacketInfo> _packetsByType = new Dictionary<Type, PacketInfo>();
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
{
@ -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<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)
=> _deSerializers.Add(typeof(T), deSerializer);
// public void RegisterDeSerializer(INetworkDeSerializerMulti deSerializer)
// => _multiDeSerializers.Add(deSerializer);
public void RegisterDeSerializerGenerator(INetworkDeSerializerGenerator deSerializerGenerator)
=> _deSerializerGenerators.Add(deSerializerGenerator);
public void RegisterS2CPacket<T>(Action<T> 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<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