Merge pull request #44 from TwoTenPvP/command-messages

Command messages
This commit is contained in:
Albin Corén 2018-04-20 12:58:48 +02:00 committed by GitHub
commit c8f0e74894
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 569 additions and 6 deletions

View File

@ -0,0 +1,10 @@
using System;
namespace MLAPI.Attributes
{
[AttributeUsage(AttributeTargets.Method)]
public class ClientRpc : Attribute
{
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace MLAPI.Attributes
{
[AttributeUsage(AttributeTargets.Method)]
public class Command : Attribute
{
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace MLAPI.Attributes
{
[AttributeUsage(AttributeTargets.Method)]
public class TargetRpc : Attribute
{
}
}

51
MLAPI/Data/Cache.cs Normal file
View File

@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
namespace MLAPI.Data
{
internal static class Cache
{
internal static Dictionary<string, ulong> messageAttributeHashes = new Dictionary<string, ulong>();
internal static Dictionary<ulong, string> messageAttributeNames = new Dictionary<ulong, string>();
internal static ulong GetMessageAttributeHash(string name)
{
if (messageAttributeHashes.ContainsKey(name))
return messageAttributeHashes[name];
using (SHA256Managed sha = new SHA256Managed())
{
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(name));
ulong value = hash[0] | ((ulong)hash[1] << 8) | ((ulong)hash[2] << 16) | ((ulong)hash[3] << 24) | ((ulong)hash[4] << 32) | ((ulong)hash[5] << 40) | ((ulong)hash[6] << 48) | ((ulong)hash[7] << 56);
//ulong value = hash[0] | ((uint)hash[1] << 8) | ((uint)hash[2] << 16) | ((uint)hash[3] << 24);
messageAttributeHashes.Add(name, value);
messageAttributeNames.Add(value, name);
return value;
}
}
internal static string GetAttributeMethodName(ulong hash)
{
if (messageAttributeNames.ContainsKey(hash))
return messageAttributeNames[hash];
else
return string.Empty;
}
internal static void RegisterMessageAttributeName(string name)
{
if (messageAttributeHashes.ContainsKey(name))
return;
using (SHA256Managed sha = new SHA256Managed())
{
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(name));
ulong value = hash[0] | ((ulong)hash[1] << 8) | ((ulong)hash[2] << 16) | ((ulong)hash[3] << 24) | ((ulong)hash[4] << 32) | ((ulong)hash[5] << 40) | ((ulong)hash[6] << 48) | ((ulong)hash[7] << 56);
//ulong value = hash[0] | ((uint)hash[1] << 8) | ((uint)hash[2] << 16) | ((uint)hash[3] << 24);
messageAttributeHashes.Add(name, value);
messageAttributeNames.Add(value, name);
}
}
}
}

View File

@ -45,6 +45,137 @@ namespace MLAPI.Data
internal static class FieldTypeHelper
{
internal static void WriteFieldType(BitWriter writer, object value, FieldType fieldType)
{
switch (fieldType)
{
case FieldType.Bool:
writer.WriteBool((bool)value);
break;
case FieldType.Byte:
writer.WriteByte((byte)value);
break;
case FieldType.Double:
writer.WriteDouble((double)value);
break;
case FieldType.Single:
writer.WriteFloat((float)value);
break;
case FieldType.Int:
writer.WriteInt((int)value);
break;
case FieldType.Long:
writer.WriteLong((long)value);
break;
case FieldType.SByte:
writer.WriteSByte((sbyte)value);
break;
case FieldType.Short:
writer.WriteShort((short)value);
break;
case FieldType.UInt:
writer.WriteUInt((uint)value);
break;
case FieldType.ULong:
writer.WriteULong((ulong)value);
break;
case FieldType.UShort:
writer.WriteUShort((ushort)value);
break;
case FieldType.String:
writer.WriteString((string)value);
break;
case FieldType.Vector3:
Vector3 vector3 = (Vector3)value;
writer.WriteFloat(vector3.x);
writer.WriteFloat(vector3.y);
writer.WriteFloat(vector3.z);
break;
case FieldType.Vector2:
Vector2 vector2 = (Vector2)value;
writer.WriteFloat(vector2.x);
writer.WriteFloat(vector2.y);
break;
case FieldType.Quaternion:
Vector3 euler = ((Quaternion)value).eulerAngles;
writer.WriteFloat(euler.x);
writer.WriteFloat(euler.y);
writer.WriteFloat(euler.z);
break;
case FieldType.BoolArray:
bool[] bools = (bool[])value;
writer.WriteUShort((ushort)bools.Length);
for (int j = 0; j < bools.Length; j++)
writer.WriteBool(bools[j]);
break;
case FieldType.ByteArray:
writer.WriteByteArray((byte[])value);
break;
case FieldType.DoubleArray:
writer.WriteDoubleArray((double[])value);
break;
case FieldType.SingleArray:
writer.WriteFloatArray((float[])value);
break;
case FieldType.IntArray:
writer.WriteIntArray((int[])value);
break;
case FieldType.LongArray:
writer.WriteLongArray((long[])value);
break;
case FieldType.SByteArray:
writer.WriteSByteArray((sbyte[])value);
break;
case FieldType.ShortArray:
writer.WriteShortArray((short[])value);
break;
case FieldType.UIntArray:
writer.WriteUIntArray((uint[])value);
break;
case FieldType.ULongArray:
writer.WriteULongArray((ulong[])value);
break;
case FieldType.UShortArray:
writer.WriteUShortArray((ushort[])value);
break;
case FieldType.StringArray:
string[] strings = (string[])value;
writer.WriteUShort((ushort)strings.Length);
for (int j = 0; j < strings.Length; j++)
writer.WriteString(strings[j]);
break;
case FieldType.Vector3Array:
Vector3[] vector3s = (Vector3[])value;
writer.WriteUShort((ushort)vector3s.Length);
for (int j = 0; j < vector3s.Length; j++)
{
writer.WriteFloat(vector3s[j].x);
writer.WriteFloat(vector3s[j].y);
writer.WriteFloat(vector3s[j].z);
}
break;
case FieldType.Vector2Array:
Vector2[] vector2s = (Vector2[])value;
writer.WriteUShort((ushort)vector2s.Length);
for (int j = 0; j < vector2s.Length; j++)
{
writer.WriteFloat(vector2s[j].x);
writer.WriteFloat(vector2s[j].y);
}
break;
case FieldType.QuaternionArray:
Quaternion[] quaternions = (Quaternion[])value;
writer.WriteUShort((ushort)quaternions.Length);
for (int j = 0; j < quaternions.Length; j++)
{
writer.WriteFloat(quaternions[j].eulerAngles.x);
writer.WriteFloat(quaternions[j].eulerAngles.y);
writer.WriteFloat(quaternions[j].eulerAngles.z);
}
break;
}
}
internal static void WriteFieldType(BitWriter writer, FieldInfo field, object fieldInstance, FieldType fieldType)
{
switch (fieldType)
@ -241,5 +372,155 @@ namespace MLAPI.Data
else
return FieldType.Invalid;
}
internal static object[] ReadObjects(BitReader reader, byte paramCount)
{
object[] returnVal = new object[paramCount];
for (int i = 0; i < paramCount; i++)
{
FieldType fieldType = (FieldType)reader.ReadBits(5);
switch (fieldType)
{
case FieldType.Bool:
returnVal[i] = reader.ReadBool();
break;
case FieldType.Byte:
returnVal[i] = reader.ReadByte();
break;
case FieldType.Double:
returnVal[i] = reader.ReadDouble();
break;
case FieldType.Single:
returnVal[i] = reader.ReadFloat();
break;
case FieldType.Int:
returnVal[i] = reader.ReadInt();
break;
case FieldType.Long:
returnVal[i] = reader.ReadLong();
break;
case FieldType.SByte:
returnVal[i] = reader.ReadSByte();
break;
case FieldType.Short:
returnVal[i] = reader.ReadShort();
break;
case FieldType.UInt:
returnVal[i] = reader.ReadUInt();
break;
case FieldType.ULong:
returnVal[i] = reader.ReadULong();
break;
case FieldType.UShort:
returnVal[i] = reader.ReadUShort();
break;
case FieldType.String:
returnVal[i] = reader.ReadString();
break;
case FieldType.Vector3:
Vector3 vector3 = Vector3.zero;
vector3.x = reader.ReadFloat();
vector3.y = reader.ReadFloat();
vector3.z = reader.ReadFloat();
returnVal[i] = vector3;
break;
case FieldType.Vector2:
Vector2 vector2 = Vector2.zero;
vector2.x = reader.ReadFloat();
vector2.y = reader.ReadFloat();
returnVal[i] = vector2;
break;
case FieldType.Quaternion:
Vector3 eulerAngle = Vector3.zero;
eulerAngle.x = reader.ReadFloat();
eulerAngle.y = reader.ReadFloat();
eulerAngle.z = reader.ReadFloat();
returnVal[i] = Quaternion.Euler(eulerAngle);
break;
case FieldType.BoolArray:
ushort boolCount = reader.ReadUShort();
for (int j = 0; j < boolCount; j++)
returnVal[i] = reader.ReadBool();
break;
case FieldType.ByteArray:
returnVal[i] = reader.ReadByteArray();
break;
case FieldType.DoubleArray:
returnVal[i] = reader.ReadDoubleArray();
break;
case FieldType.SingleArray:
returnVal[i] = reader.ReadFloatArray();
break;
case FieldType.IntArray:
returnVal[i] = reader.ReadIntArray();
break;
case FieldType.LongArray:
returnVal[i] = reader.ReadLongArray();
break;
case FieldType.SByteArray:
returnVal[i] = reader.ReadSByteArray();
break;
case FieldType.ShortArray:
returnVal[i] = reader.ReadShortArray();
break;
case FieldType.UIntArray:
returnVal[i] = reader.ReadUIntArray();
break;
case FieldType.ULongArray:
returnVal[i] = reader.ReadULongArray();
break;
case FieldType.UShortArray:
returnVal[i] = reader.ReadUShortArray();
break;
case FieldType.StringArray:
ushort stringCount = reader.ReadUShort();
string[] strings = new string[stringCount];
for (int j = 0; j < stringCount; j++)
strings[j] = reader.ReadString();
returnVal[i] = strings;
break;
case FieldType.Vector3Array:
ushort vector3Count = reader.ReadUShort();
Vector3[] vector3s = new Vector3[vector3Count];
for (int j = 0; j < vector3Count; j++)
{
Vector3 vec3 = Vector3.zero;
vec3.x = reader.ReadFloat();
vec3.y = reader.ReadFloat();
vec3.z = reader.ReadFloat();
vector3s[j] = vec3;
}
returnVal[i] = vector3s;
break;
case FieldType.Vector2Array:
ushort vector2Count = reader.ReadUShort();
Vector2[] vector2s = new Vector2[vector2Count];
for (int j = 0; j < vector2Count; j++)
{
Vector2 vec2 = Vector2.zero;
vec2.x = reader.ReadFloat();
vec2.y = reader.ReadFloat();
vector2s[j] = vec2;
}
returnVal[i] = vector2s;
break;
case FieldType.QuaternionArray:
ushort quaternionCount = reader.ReadUShort();
Quaternion[] quaternions = new Quaternion[quaternionCount];
for (int j = 0; j < quaternionCount; j++)
{
Vector3 vec3 = Vector3.zero;
vec3.x = reader.ReadFloat();
vec3.y = reader.ReadFloat();
vec3.z = reader.ReadFloat();
quaternions[j] = Quaternion.Euler(vec3);
}
returnVal[i] = quaternions;
break;
}
}
return returnVal;
}
}
}

View File

@ -67,6 +67,10 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Attributes\BinaryIgnore.cs" />
<Compile Include="Attributes\ClientRpc.cs" />
<Compile Include="Attributes\Command.cs" />
<Compile Include="Attributes\TargetRpc.cs" />
<Compile Include="Data\Cache.cs" />
<Compile Include="Data\Channel.cs" />
<Compile Include="Data\FieldType.cs" />
<Compile Include="Attributes\SyncedVar.cs" />

View File

@ -76,7 +76,7 @@ namespace MLAPI.MonoBehaviours.Core
{
get
{
if(_networkedObject == null)
if (_networkedObject == null)
{
_networkedObject = GetComponentInParent<NetworkedObject>();
}
@ -111,9 +111,9 @@ namespace MLAPI.MonoBehaviours.Core
private void OnEnable()
{
if (_networkedObject == null)
{
_networkedObject = GetComponentInParent<NetworkedObject>();
}
CacheAttributedMethods();
NetworkedObject.NetworkedBehaviours.Add(this);
}
@ -139,6 +139,121 @@ namespace MLAPI.MonoBehaviours.Core
{
}
internal Dictionary<string, MethodInfo> cachedMethods = new Dictionary<string, MethodInfo>();
private void CacheAttributedMethods()
{
MethodInfo[] methods = GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
for (int i = 0; i < methods.Length; i++)
{
if (methods[i].IsDefined(typeof(Command), true) || methods[i].IsDefined(typeof(ClientRpc), true) || methods[i].IsDefined(typeof(TargetRpc), true))
{
Data.Cache.RegisterMessageAttributeName(methods[i].Name);
if (!cachedMethods.ContainsKey(methods[i].Name))
cachedMethods.Add(methods[i].Name, methods[i]);
}
}
}
protected void InvokeCommand(string methodName, params object[] methodParams)
{
if (NetworkingManager.singleton.isServer)
{
Debug.LogWarning("MLAPI: Cannot invoke commands from server");
return;
}
if (ownerClientId != NetworkingManager.singleton.MyClientId)
{
Debug.LogWarning("MLAPI: Cannot invoke command for object without ownership");
return;
}
if (!methodName.StartsWith("Cmd"))
{
Debug.LogWarning("MLAPI: Invalid Command name. Command methods have to start with Cmd");
return;
}
ulong hash = Data.Cache.GetMessageAttributeHash(methodName);
using (BitWriter writer = new BitWriter())
{
writer.WriteUInt(networkId);
writer.WriteUShort(networkedObject.GetOrderIndex(this));
writer.WriteULong(hash);
writer.WriteBits((byte)methodParams.Length, 5);
for (int i = 0; i < methodParams.Length; i++)
{
FieldType fieldType = FieldTypeHelper.GetFieldType(methodParams[i].GetType());
writer.WriteBits((byte)fieldType, 5);
FieldTypeHelper.WriteFieldType(writer, methodParams[i], fieldType);
}
InternalMessageHandler.Send(NetId.ServerNetId.GetClientId(), "MLAPI_COMMAND", "MLAPI_INTERNAL", writer.Finalize());
}
}
protected void InvokeClientRpc(string methodName, params object[] methodParams)
{
if (!NetworkingManager.singleton.isServer)
{
Debug.LogWarning("MLAPI: Cannot invoke ClientRpc from client");
return;
}
if (!methodName.StartsWith("Rpc"))
{
Debug.LogWarning("MLAPI: Invalid Command name. Command methods have to start with Cmd");
return;
}
ulong hash = Data.Cache.GetMessageAttributeHash(methodName);
using (BitWriter writer = new BitWriter())
{
writer.WriteUInt(networkId);
writer.WriteUShort(networkedObject.GetOrderIndex(this));
writer.WriteULong(hash);
writer.WriteBits((byte)methodParams.Length, 5);
for (int i = 0; i < methodParams.Length; i++)
{
FieldType fieldType = FieldTypeHelper.GetFieldType(methodParams[i].GetType());
writer.WriteBits((byte)fieldType, 5);
FieldTypeHelper.WriteFieldType(writer, methodParams[i], fieldType);
}
InternalMessageHandler.Send("MLAPI_RPC", "MLAPI_INTERNAL", writer.Finalize());
}
}
protected void InvokeTargetRpc(string methodName, params object[] methodParams)
{
if (!NetworkingManager.singleton.isServer)
{
Debug.LogWarning("MLAPI: Cannot invoke ClientRpc from client");
return;
}
if (!methodName.StartsWith("Target"))
{
Debug.LogWarning("MLAPI: Invalid Command name. Command methods have to start with Cmd");
return;
}
ulong hash = Data.Cache.GetMessageAttributeHash(methodName);
using (BitWriter writer = new BitWriter())
{
writer.WriteUInt(networkId);
writer.WriteUShort(networkedObject.GetOrderIndex(this));
writer.WriteULong(hash);
writer.WriteBits((byte)methodParams.Length, 5);
for (int i = 0; i < methodParams.Length; i++)
{
FieldType fieldType = FieldTypeHelper.GetFieldType(methodParams[i].GetType());
writer.WriteBits((byte)fieldType, 5);
FieldTypeHelper.WriteFieldType(writer, methodParams[i], fieldType);
}
InternalMessageHandler.Send(ownerClientId, "MLAPI_RPC", "MLAPI_INTERNAL", writer.Finalize());
}
}
/// <summary>
/// Registers a message handler
/// </summary>

View File

@ -219,6 +219,8 @@ namespace MLAPI.MonoBehaviours.Core
connectedClients = new Dictionary<uint, NetworkedClient>();
messageBuffer = new byte[NetworkConfig.MessageBufferSize];
diffieHellmanPublicKeys = new Dictionary<uint, byte[]>();
Data.Cache.messageAttributeHashes = new Dictionary<string, ulong>();
Data.Cache.messageAttributeNames = new Dictionary<ulong, string>();
MessageManager.channels = new Dictionary<string, int>();
MessageManager.messageTypes = new Dictionary<string, ushort>();
MessageManager.messageCallbacks = new Dictionary<ushort, Dictionary<int, Action<uint, byte[]>>>();
@ -378,6 +380,9 @@ namespace MLAPI.MonoBehaviours.Core
MessageManager.messageTypes.Add("MLAPI_SYNC_VAR_UPDATE", 9);
MessageManager.messageTypes.Add("MLAPI_ADD_OBJECTS", 10);
MessageManager.messageTypes.Add("MLAPI_TIME_SYNC", 11);
MessageManager.messageTypes.Add("MLAPI_COMMAND", 12);
MessageManager.messageTypes.Add("MLAPI_RPC", 13);
MessageManager.messageTypes.Add("MLAPI_TARGET", 14);
List<MessageType> messageTypes = new List<MessageType>(NetworkConfig.MessageTypes)
{
@ -1000,6 +1005,24 @@ namespace MLAPI.MonoBehaviours.Core
InternalMessageHandler.HandleTimeSync(clientId, incommingData, channelId);
}
break;
case 12:
if (isServer)
{
InternalMessageHandler.HandleCommand(clientId, incommingData, channelId);
}
break;
case 13:
if (isClient)
{
InternalMessageHandler.HandleRpc(clientId, incommingData, channelId);
}
break;
case 14:
if (isClient)
{
InternalMessageHandler.HandleTargetRpc(clientId, incommingData, channelId);
}
break;
}
#endregion
}

View File

@ -54,6 +54,13 @@ namespace MLAPI.NetworkingManagerComponents.Binary
public int[] ReadIntArray(int known = -1) => ReadArray(ReadInt, known);
public long[] ReadLongArray(int known = -1) => ReadArray(ReadLong, known);
public string ReadString() => Encoding.UTF8.GetString(ReadByteArray());
public byte ReadBits(int bits)
{
byte b = 0;
for (int i = 0; --bits >= 0; ++i)
b |= (byte)((ReadBool() ? 1 : 0) << i);
return b;
}
public ulong ReadULong()
{

View File

@ -105,6 +105,10 @@ namespace MLAPI.NetworkingManagerComponents.Binary
public void WriteShortArray(short[] s, bool known = false) => PushArray(s, known);
public void WriteIntArray(int[] i, bool known = false) => PushArray(i, known);
public void WriteLongArray(long[] l, bool known = false) => PushArray(l, known);
public void WriteBits(byte value, int bits)
{
for (int i = 0; i < bits; ++i) WriteBool((value & (1 << i)) != 0);
}
private void PushArray<T>(T[] t, bool knownSize = false)
{
@ -268,7 +272,7 @@ namespace MLAPI.NetworkingManagerComponents.Binary
WriteByte(writeTo, (value >> 32) & 255, bitOffset + 40, isAligned);
if (value > 1099511627775)
{
WriteByte(writeTo, (value >> 40) & 55, bitOffset + 48, isAligned);
WriteByte(writeTo, (value >> 40) & 255, bitOffset + 48, isAligned);
if (value > 281474976710655)
{
WriteByte(writeTo, (value >> 48) & 255, bitOffset + 56, isAligned);

View File

@ -1,4 +1,5 @@
using System.Security.Cryptography;
using System.Reflection;
using System.Security.Cryptography;
using MLAPI.Data;
using MLAPI.MonoBehaviours.Core;
using MLAPI.NetworkingManagerComponents.Binary;
@ -384,6 +385,53 @@ namespace MLAPI.NetworkingManagerComponents.Core
if ((NetworkError)error != NetworkError.Ok)
msDelay = 0;
netManager.networkTime = netTime + (msDelay / 1000f);
}
}
internal static void HandleCommand(uint clientId, byte[] incommingData, int channelId)
{
BitReader reader = new BitReader(incommingData);
uint networkId = reader.ReadUInt();
ushort orderId = reader.ReadUShort();
ulong hash = reader.ReadULong();
NetworkedBehaviour behaviour = SpawnManager.spawnedObjects[networkId].GetBehaviourAtOrderIndex(orderId);
if (clientId != behaviour.ownerClientId)
return; // Not owner
MethodInfo targetMethod = null;
if (behaviour.cachedMethods.ContainsKey(Data.Cache.GetAttributeMethodName(hash)))
targetMethod = behaviour.cachedMethods[Data.Cache.GetAttributeMethodName(hash)];
byte paramCount = reader.ReadBits(5);
object[] methodParams = FieldTypeHelper.ReadObjects(reader, paramCount);
targetMethod.Invoke(behaviour, methodParams);
}
internal static void HandleRpc(uint clientId, byte[] incommingData, int channelId)
{
BitReader reader = new BitReader(incommingData);
uint networkId = reader.ReadUInt();
ushort orderId = reader.ReadUShort();
ulong hash = reader.ReadULong();
NetworkedBehaviour behaviour = SpawnManager.spawnedObjects[networkId].GetBehaviourAtOrderIndex(orderId);
MethodInfo targetMethod = null;
if (behaviour.cachedMethods.ContainsKey(Data.Cache.GetAttributeMethodName(hash)))
targetMethod = behaviour.cachedMethods[Data.Cache.GetAttributeMethodName(hash)];
byte paramCount = reader.ReadBits(5);
object[] methodParams = FieldTypeHelper.ReadObjects(reader, paramCount);
targetMethod.Invoke(behaviour, methodParams);
}
internal static void HandleTargetRpc(uint clientId, byte[] incommingData, int channelId)
{
BitReader reader = new BitReader(incommingData);
uint networkId = reader.ReadUInt();
ushort orderId = reader.ReadUShort();
ulong hash = reader.ReadULong();
NetworkedBehaviour behaviour = SpawnManager.spawnedObjects[networkId].GetBehaviourAtOrderIndex(orderId);
MethodInfo targetMethod = null;
if (behaviour.cachedMethods.ContainsKey(Data.Cache.GetAttributeMethodName(hash)))
targetMethod = behaviour.cachedMethods[Data.Cache.GetAttributeMethodName(hash)];
byte paramCount = reader.ReadBits(5);
object[] methodParams = FieldTypeHelper.ReadObjects(reader, paramCount);
targetMethod.Invoke(behaviour, methodParams);
}
}
}