- No more Rpc or Rset calls! - Custom packet handling and de/serialization - Lots of useful SendTo* methods! - Appearance change in multiplayer waits for confirmation from servermain
parent
8ee48adbd7
commit
1fa5ee9b63
7 changed files with 444 additions and 133 deletions
@ -0,0 +1,273 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.IO; |
||||||
|
using System.Linq; |
||||||
|
using System.Reflection; |
||||||
|
using System.Runtime.Serialization; |
||||||
|
using Godot; |
||||||
|
|
||||||
|
[Flags] |
||||||
|
public enum PacketDirection |
||||||
|
{ |
||||||
|
ServerToClient = 0b01, |
||||||
|
ClientToServer = 0b10, |
||||||
|
Both = ServerToClient | ClientToServer, |
||||||
|
} |
||||||
|
|
||||||
|
public enum TransferMode |
||||||
|
{ |
||||||
|
Unreliable = NetworkedMultiplayerPeer.TransferModeEnum.Unreliable, |
||||||
|
UnreliableOrdered = NetworkedMultiplayerPeer.TransferModeEnum.UnreliableOrdered, |
||||||
|
Reliable = NetworkedMultiplayerPeer.TransferModeEnum.Reliable, |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: Improve performance and type safety of de/serialization. |
||||||
|
// TODO: Support easily spawning and syncronizing objects and their properties. |
||||||
|
public 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 class PacketInfo |
||||||
|
{ |
||||||
|
public int ID { get; } |
||||||
|
public Type Type { get; } |
||||||
|
public PacketDirection Direction { get; } |
||||||
|
public TransferMode DefaultTransformMode { get; } |
||||||
|
public INetworkDeSerializer DeSerializer { get; } |
||||||
|
public Action<int, object> OnPacketReceived { get; } |
||||||
|
|
||||||
|
public PacketInfo(int id, Type type, PacketDirection direction, |
||||||
|
TransferMode defaultTransferMode, INetworkDeSerializer deSerializer, |
||||||
|
Action<int, object> onPacketReceived) |
||||||
|
{ |
||||||
|
ID = id; |
||||||
|
Type = type; |
||||||
|
Direction = direction; |
||||||
|
DefaultTransformMode = defaultTransferMode; |
||||||
|
DeSerializer = deSerializer; |
||||||
|
OnPacketReceived = onPacketReceived; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public NetworkAPI(MultiplayerAPI multiplayerAPI) |
||||||
|
{ |
||||||
|
_multiplayerAPI = multiplayerAPI; |
||||||
|
|
||||||
|
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadBoolean()); |
||||||
|
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadByte()); |
||||||
|
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadSByte()); |
||||||
|
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadInt16()); |
||||||
|
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadUInt16()); |
||||||
|
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadInt32()); |
||||||
|
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadUInt32()); |
||||||
|
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadInt64()); |
||||||
|
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadUInt64()); |
||||||
|
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadSingle()); |
||||||
|
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadDouble()); |
||||||
|
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadString()); |
||||||
|
|
||||||
|
// byte[] |
||||||
|
RegisterDeSerializer((writer, value) => { writer.Write(value.Length); writer.Write(value); }, |
||||||
|
reader => reader.ReadBytes(reader.ReadInt32())); |
||||||
|
// Vector2 |
||||||
|
RegisterDeSerializer((writer, value) => { writer.Write(value.x); writer.Write(value.y); }, |
||||||
|
reader => new Vector2(reader.ReadSingle(), reader.ReadSingle())); |
||||||
|
// Color |
||||||
|
RegisterDeSerializer((writer, value) => writer.Write(value.ToRgba32()), |
||||||
|
reader => new Color(reader.ReadInt32())); |
||||||
|
|
||||||
|
// TODO: Add handling for Array, List and Dictionary. |
||||||
|
} |
||||||
|
|
||||||
|
public void RegisterDeSerializer<T>(Action<BinaryWriter, T> serialize, Func<BinaryReader, T> deserialize) |
||||||
|
=> _deSerializers.Add(typeof(T), new SimpleNetworkDeSerializer<T>(serialize, deserialize)); |
||||||
|
public void RegisterDeSerializer<T>(INetworkDeSerializer deSerializer) |
||||||
|
=> _deSerializers.Add(typeof(T), deSerializer); |
||||||
|
// public void RegisterDeSerializer(INetworkDeSerializerMulti deSerializer) |
||||||
|
// => _multiDeSerializers.Add(deSerializer); |
||||||
|
|
||||||
|
public void RegisterS2CPacket<T>(Action<T> action, TransferMode defaultTransferMode = TransferMode.Reliable) |
||||||
|
=> RegisterPacket((int _id, T packet) => action(packet), defaultTransferMode, PacketDirection.ServerToClient); |
||||||
|
|
||||||
|
public void RegisterC2SPacket<T>(Action<int, T> action, TransferMode defaultTransferMode = TransferMode.Reliable) |
||||||
|
=> RegisterPacket(action, defaultTransferMode, PacketDirection.ClientToServer); |
||||||
|
public void RegisterC2SPacket<T>(Action<Player, T> action, TransferMode defaultTransferMode = TransferMode.Reliable) |
||||||
|
=> RegisterPacket(action, defaultTransferMode, PacketDirection.ClientToServer); |
||||||
|
|
||||||
|
public void RegisterPacket<T>(Action<Player, T> action, |
||||||
|
TransferMode defaultTransferMode = TransferMode.Reliable, |
||||||
|
PacketDirection direction = PacketDirection.Both) |
||||||
|
=> RegisterPacket((int id, T packet) => action(Network.GetPlayerOrThrow(id), packet), defaultTransferMode, direction); |
||||||
|
public void RegisterPacket<T>(Action<int, T> action, |
||||||
|
TransferMode defaultTransferMode = TransferMode.Reliable, |
||||||
|
PacketDirection direction = PacketDirection.Both) |
||||||
|
{ |
||||||
|
var deSerializer = GetDeSerializer(typeof(T), true); |
||||||
|
var info = new PacketInfo(_packetsById.Count, typeof(T), |
||||||
|
direction, defaultTransferMode, deSerializer, |
||||||
|
(id, packet) => action(id, (T)packet)); |
||||||
|
_packetsByType.Add(typeof(T), info); |
||||||
|
_packetsById.Add(info); |
||||||
|
} |
||||||
|
|
||||||
|
public void SendToServer<T>(T packet, TransferMode? transferMode = null) |
||||||
|
=> SendTo(1, packet, transferMode); |
||||||
|
|
||||||
|
public void SendTo<T>(Player player, T packet, TransferMode? transferMode = null) |
||||||
|
=> SendTo(player.NetworkID, packet, transferMode); |
||||||
|
public void SendTo<T>(int id, T packet, TransferMode? transferMode = null) |
||||||
|
=> SendTo(new []{ id }, packet, transferMode); |
||||||
|
|
||||||
|
public void SendToEveryone<T>(T packet, TransferMode? transferMode = null) |
||||||
|
=> SendTo(_multiplayerAPI.GetNetworkConnectedPeers(), packet, transferMode); |
||||||
|
public void SendToEveryoneExcept<T>(Player except, T packet, TransferMode? transferMode = null) |
||||||
|
=> SendToEveryoneExcept(except?.NetworkID ?? 0, packet, transferMode); |
||||||
|
public void SendToEveryoneExcept<T>(int except, T packet, TransferMode? transferMode = null) |
||||||
|
=> SendTo(_multiplayerAPI.GetNetworkConnectedPeers().Where(id => id != except), packet, transferMode); |
||||||
|
|
||||||
|
public void SendTo<T>(IEnumerable<Player> players, T packet, TransferMode? transferMode = null) |
||||||
|
=> SendTo(players.Select(p => p.NetworkID), packet, transferMode); |
||||||
|
public void SendTo<T>(IEnumerable<int> ids, T packet, TransferMode? transferMode = null) |
||||||
|
{ |
||||||
|
var info = GetPacketInfoAndVerifyDirection<T>(); |
||||||
|
var mode = ToPeerTransferMode(info, transferMode); |
||||||
|
byte[] bytes = null; |
||||||
|
foreach (var id in ids) { |
||||||
|
// Only serialize the packet if sending to at least 1 player. |
||||||
|
bytes = bytes ?? PacketToBytes(info, packet); |
||||||
|
_multiplayerAPI.SendBytes(bytes, id, mode); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private PacketInfo GetPacketInfoAndVerifyDirection<T>() |
||||||
|
{ |
||||||
|
if (!_packetsByType.TryGetValue(typeof(T), out var info)) |
||||||
|
throw new InvalidOperationException($"No packet of type {typeof(T)} has been registered"); |
||||||
|
|
||||||
|
var direction = Network.IsServer ? PacketDirection.ServerToClient : PacketDirection.ClientToServer; |
||||||
|
if ((direction & info.Direction) == 0) throw new InvalidOperationException( |
||||||
|
$"Attempting to send packet {typeof(T)} in invalid direction {direction}"); |
||||||
|
|
||||||
|
return info; |
||||||
|
} |
||||||
|
private byte[] PacketToBytes(PacketInfo info, object packet) |
||||||
|
{ |
||||||
|
using (var stream = new MemoryStream()) { |
||||||
|
using (var writer = new BinaryWriter(stream)) { |
||||||
|
writer.Write((ushort)info.ID); |
||||||
|
info.DeSerializer.Serialize(writer, packet); |
||||||
|
} |
||||||
|
return stream.ToArray(); |
||||||
|
} |
||||||
|
} |
||||||
|
private NetworkedMultiplayerPeer.TransferModeEnum ToPeerTransferMode(PacketInfo info, TransferMode? transferMode) |
||||||
|
=> (NetworkedMultiplayerPeer.TransferModeEnum)(transferMode ?? info.DefaultTransformMode); |
||||||
|
|
||||||
|
|
||||||
|
internal void OnPacketReceived(int id, byte[] bytes) |
||||||
|
{ |
||||||
|
if (!Network.IsServer && (id != 1)) |
||||||
|
throw new Exception($"Received packet from other player (ID {id})"); |
||||||
|
|
||||||
|
|
||||||
|
using (var stream = new MemoryStream(bytes)) { |
||||||
|
using (var reader = new BinaryReader(stream)) { |
||||||
|
var packetId = reader.ReadUInt16(); |
||||||
|
if (packetId >= _packetsById.Count) throw new Exception( |
||||||
|
$"Received packet with invalid ID {packetId}"); |
||||||
|
var info = _packetsById[packetId]; |
||||||
|
|
||||||
|
var direction = Network.IsServer ? PacketDirection.ClientToServer : PacketDirection.ServerToClient; |
||||||
|
if ((direction & info.Direction) == 0) throw new Exception( |
||||||
|
$"Received packet {info.Type} on invalid side {(_multiplayerAPI.IsNetworkServer() ? "server" : "client")}"); |
||||||
|
|
||||||
|
var packet = info.DeSerializer.Deserialize(reader); |
||||||
|
var playerID = Network.IsServer ? id : Network.LocalNetworkID; |
||||||
|
info.OnPacketReceived(id, packet); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private INetworkDeSerializer GetDeSerializer(Type type, bool createIfMissing) |
||||||
|
{ |
||||||
|
if (!_deSerializers.TryGetValue(type, out var value)) { |
||||||
|
if (!createIfMissing) throw new InvalidOperationException( |
||||||
|
$"No DeSerializer for type {type} found"); |
||||||
|
|
||||||
|
value = new ComplexNetworkDeSerializer(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); |
||||||
|
} |
Loading…
Reference in new issue