From dc6afc13313ee43e33988953b8481c9d88572afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= <2108U9@gmail.com> Date: Wed, 7 Mar 2018 20:56:24 +0100 Subject: [PATCH] Added Reflection based SyncVar system --- MLAPI/Attributes/FieldType.cs | 19 ++ MLAPI/Attributes/SyncedVar.cs | 10 + MLAPI/MLAPI.csproj | 5 +- .../MonoBehaviours/Core/NetworkedBehaviour.cs | 296 ++++++++++++++++++ MLAPI/MonoBehaviours/Core/NetworkedObject.cs | 28 ++ .../MonoBehaviours/Core/NetworkingManager.cs | 72 ++++- .../SyncedVarManager.cs | 35 +++ 7 files changed, 463 insertions(+), 2 deletions(-) create mode 100644 MLAPI/Attributes/FieldType.cs create mode 100644 MLAPI/Attributes/SyncedVar.cs create mode 100644 MLAPI/NetworkingManagerComponents/SyncedVarManager.cs diff --git a/MLAPI/Attributes/FieldType.cs b/MLAPI/Attributes/FieldType.cs new file mode 100644 index 0000000..916c77f --- /dev/null +++ b/MLAPI/Attributes/FieldType.cs @@ -0,0 +1,19 @@ +namespace MLAPI.Attributes +{ + internal enum FieldType + { + Bool, + Byte, + Char, + Double, + Single, + Int, + Long, + SByte, + Short, + UInt, + ULong, + UShort, + String + } +} diff --git a/MLAPI/Attributes/SyncedVar.cs b/MLAPI/Attributes/SyncedVar.cs new file mode 100644 index 0000000..f01705c --- /dev/null +++ b/MLAPI/Attributes/SyncedVar.cs @@ -0,0 +1,10 @@ +using System; + +namespace MLAPI.Attributes +{ + [AttributeUsage(AttributeTargets.Field)] + public class SyncedVar : Attribute + { + + } +} diff --git a/MLAPI/MLAPI.csproj b/MLAPI/MLAPI.csproj index 63b2af0..948bd46 100644 --- a/MLAPI/MLAPI.csproj +++ b/MLAPI/MLAPI.csproj @@ -41,7 +41,7 @@ AnyCPU prompt MinimumRecommendedRules.ruleset - Off + Auto @@ -56,6 +56,8 @@ + + @@ -71,6 +73,7 @@ + diff --git a/MLAPI/MonoBehaviours/Core/NetworkedBehaviour.cs b/MLAPI/MonoBehaviours/Core/NetworkedBehaviour.cs index 8c1f608..64f342e 100644 --- a/MLAPI/MonoBehaviours/Core/NetworkedBehaviour.cs +++ b/MLAPI/MonoBehaviours/Core/NetworkedBehaviour.cs @@ -2,11 +2,16 @@ using System.Collections.Generic; using UnityEngine; using MLAPI.NetworkingManagerComponents; +using System.Reflection; +using MLAPI.Attributes; +using System.Linq; +using System.IO; namespace MLAPI { public abstract class NetworkedBehaviour : MonoBehaviour { + public float SyncVarSyncDelay = 0.1f; public bool isLocalPlayer { get @@ -79,6 +84,7 @@ namespace MLAPI { _networkedObject = GetComponentInParent(); } + NetworkedObject.networkedBehaviours.Add(this); } internal bool networkedStartInvoked = false; @@ -109,6 +115,11 @@ namespace MLAPI MessageManager.RemoveIncomingMessageHandler(name, counter, networkId); } + private void OnDisable() + { + NetworkedObject.networkedBehaviours.Remove(this); + } + private void OnDestroy() { foreach (KeyValuePair pair in registeredMessageHandlers) @@ -117,6 +128,291 @@ namespace MLAPI } } + #region SYNC_VAR + private List syncedFields = new List(); + internal List syncedFieldTypes = new List(); + private List syncedFieldValues = new List(); + //A dirty field is a field that's not synced. + public bool[] dirtyFields; + protected static ushort networkedBehaviourId; + //This is just for the unity editor. if you turn the editor to DEBUG mode you can see what the networkedBehaviourId is. + private ushort _networkedBehaviourId = networkedBehaviourId; + internal void SyncVarInit() + { + FieldInfo[] sortedFields = GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance).OrderBy(x => x.Name).ToArray(); + for (byte i = 0; i < sortedFields.Length; i++) + { + if(sortedFields[i].IsDefined(typeof(SyncedVar), true)) + { + if (sortedFields[i].FieldType == typeof(bool)) + { + syncedFields.Add(sortedFields[i]); + syncedFieldValues.Add(sortedFields[i].GetValue(this)); + syncedFieldTypes.Add(FieldType.Bool); + } + else if(sortedFields[i].FieldType == typeof(byte)) + { + syncedFields.Add(sortedFields[i]); + syncedFieldValues.Add(sortedFields[i].GetValue(this)); + syncedFieldTypes.Add(FieldType.Byte); + } + else if (sortedFields[i].FieldType == typeof(char)) + { + syncedFields.Add(sortedFields[i]); + syncedFieldValues.Add(sortedFields[i].GetValue(this)); + syncedFieldTypes.Add(FieldType.Char); + } + else if (sortedFields[i].FieldType == typeof(double)) + { + syncedFields.Add(sortedFields[i]); + syncedFieldValues.Add(sortedFields[i].GetValue(this)); + syncedFieldTypes.Add(FieldType.Double); + } + else if (sortedFields[i].FieldType == typeof(float)) + { + syncedFields.Add(sortedFields[i]); + syncedFieldValues.Add(sortedFields[i].GetValue(this)); + syncedFieldTypes.Add(FieldType.Single); + } + else if (sortedFields[i].FieldType == typeof(int)) + { + syncedFields.Add(sortedFields[i]); + syncedFieldValues.Add(sortedFields[i].GetValue(this)); + syncedFieldTypes.Add(FieldType.Int); + } + else if (sortedFields[i].FieldType == typeof(long)) + { + syncedFields.Add(sortedFields[i]); + syncedFieldValues.Add(sortedFields[i].GetValue(this)); + syncedFieldTypes.Add(FieldType.Long); + } + else if (sortedFields[i].FieldType == typeof(sbyte)) + { + syncedFields.Add(sortedFields[i]); + syncedFieldValues.Add(sortedFields[i].GetValue(this)); + syncedFieldTypes.Add(FieldType.SByte); + } + else if (sortedFields[i].FieldType == typeof(short)) + { + syncedFields.Add(sortedFields[i]); + syncedFieldValues.Add(sortedFields[i].GetValue(this)); + syncedFieldTypes.Add(FieldType.Short); + } + else if (sortedFields[i].FieldType == typeof(uint)) + { + syncedFields.Add(sortedFields[i]); + syncedFieldValues.Add(sortedFields[i].GetValue(this)); + syncedFieldTypes.Add(FieldType.UInt); + } + else if (sortedFields[i].FieldType == typeof(ulong)) + { + syncedFields.Add(sortedFields[i]); + syncedFieldValues.Add(sortedFields[i].GetValue(this)); + syncedFieldTypes.Add(FieldType.ULong); + } + else if (sortedFields[i].FieldType == typeof(ushort)) + { + syncedFields.Add(sortedFields[i]); + syncedFieldValues.Add(sortedFields[i].GetValue(this)); + syncedFieldTypes.Add(FieldType.UShort); + } + else if(sortedFields[i].FieldType == typeof(string)) + { + syncedFields.Add(sortedFields[i]); + syncedFieldValues.Add(sortedFields[i].GetValue(this)); + syncedFieldTypes.Add(FieldType.String); + } + else + { + Debug.LogError("MLAPI: The type " + sortedFields[i].FieldType.ToString() + " can not be used as a syncvar"); + } + } + } + if(dirtyFields.Length > 255) + { + Debug.LogError("MLAPI: You can not have more than 255 SyncVar's per NetworkedBehaviour!"); + } + dirtyFields = new bool[syncedFields.Count]; + } + + internal void OnSyncVarUpdate(object value, byte fieldIndex) + { + if (isServer) + return; + syncedFields[fieldIndex].SetValue(this, value); + } + + private float lastSyncTime = 0f; + internal void SyncvarUpdate() + { + if (!isServer) + return; + SetDirtyness(); + if(Time.time - lastSyncTime >= SyncVarSyncDelay) + { + byte dirtyCount = (byte)dirtyFields.Count(x => x == true); + if (dirtyCount == 0) + return; //All up to date! + //It's sync time! + using (MemoryStream stream = new MemoryStream()) + { + using(BinaryWriter writer = new BinaryWriter(stream)) + { + //Write all indexes + writer.Write(dirtyCount); + for (byte i = 0; i < dirtyFields.Length; i++) + { + //Writes all the indexes of the dirty syncvars. + if (dirtyFields[i] == true) + { + writer.Write(networkId); + writer.Write(networkedObject.GetOrderIndex(this)); + writer.Write(networkedBehaviourId); + writer.Write(i); //Index + switch (syncedFieldTypes[i]) + { + case FieldType.Bool: + writer.Write((bool)syncedFields[i].GetValue(this)); + break; + case FieldType.Byte: + writer.Write((byte)syncedFields[i].GetValue(this)); + break; + case FieldType.Char: + writer.Write((char)syncedFields[i].GetValue(this)); + break; + case FieldType.Double: + writer.Write((double)syncedFields[i].GetValue(this)); + break; + case FieldType.Single: + writer.Write((float)syncedFields[i].GetValue(this)); + break; + case FieldType.Int: + writer.Write((int)syncedFields[i].GetValue(this)); + break; + case FieldType.Long: + writer.Write((long)syncedFields[i].GetValue(this)); + break; + case FieldType.SByte: + writer.Write((sbyte)syncedFields[i].GetValue(this)); + break; + case FieldType.Short: + writer.Write((short)syncedFields[i].GetValue(this)); + break; + case FieldType.UInt: + writer.Write((uint)syncedFields[i].GetValue(this)); + break; + case FieldType.ULong: + writer.Write((ulong)syncedFields[i].GetValue(this)); + break; + case FieldType.UShort: + writer.Write((ushort)syncedFields[i].GetValue(this)); + break; + case FieldType.String: + writer.Write((string)syncedFields[i].GetValue(this)); + break; + } + syncedFieldValues[i] = syncedFields[i].GetValue(this); + dirtyFields[i] = false; + } + } + NetworkingManager.singleton.Send("MLAPI_SYNC_VAR_UPDATE", "MLAPI_RELIABLE_FRAGMENTED_SEQUENCED", stream.ToArray(), ownerClientId); + } + } + lastSyncTime = Time.time; + } + } + + private void SetDirtyness() + { + if (!isServer) + return; + for (int i = 0; i < syncedFields.Count; i++) + { + switch (syncedFieldTypes[i]) + { + case FieldType.Bool: + if ((bool)syncedFields[i].GetValue(this) != (bool)syncedFieldValues[i]) + dirtyFields[i] = true; //This fields value is out of sync! + else + dirtyFields[i] = false; //Up to date + break; + case FieldType.Byte: + if ((byte)syncedFields[i].GetValue(this) != (byte)syncedFieldValues[i]) + dirtyFields[i] = true; //This fields value is out of sync! + else + dirtyFields[i] = false; //Up to date + break; + case FieldType.Char: + if ((char)syncedFields[i].GetValue(this) != (char)syncedFieldValues[i]) + dirtyFields[i] = true; //This fields value is out of sync! + else + dirtyFields[i] = false; //Up to date + break; + case FieldType.Double: + if ((double)syncedFields[i].GetValue(this) != (double)syncedFieldValues[i]) + dirtyFields[i] = true; //This fields value is out of sync! + else + dirtyFields[i] = false; //Up to date + break; + case FieldType.Single: + if ((float)syncedFields[i].GetValue(this) != (float)syncedFieldValues[i]) + dirtyFields[i] = true; //This fields value is out of sync! + else + dirtyFields[i] = false; //Up to date + break; + case FieldType.Int: + if ((int)syncedFields[i].GetValue(this) != (int)syncedFieldValues[i]) + dirtyFields[i] = true; //This fields value is out of sync! + else + dirtyFields[i] = false; //Up to date + break; + case FieldType.Long: + if ((long)syncedFields[i].GetValue(this) != (long)syncedFieldValues[i]) + dirtyFields[i] = true; //This fields value is out of sync! + else + dirtyFields[i] = false; //Up to date + break; + case FieldType.SByte: + if ((sbyte)syncedFields[i].GetValue(this) != (sbyte)syncedFieldValues[i]) + dirtyFields[i] = true; //This fields value is out of sync! + else + dirtyFields[i] = false; //Up to date + break; + case FieldType.Short: + if ((short)syncedFields[i].GetValue(this) != (short)syncedFieldValues[i]) + dirtyFields[i] = true; //This fields value is out of sync! + else + dirtyFields[i] = false; //Up to date + break; + case FieldType.UInt: + if ((uint)syncedFields[i].GetValue(this) != (uint)syncedFieldValues[i]) + dirtyFields[i] = true; //This fields value is out of sync! + else + dirtyFields[i] = false; //Up to date + break; + case FieldType.ULong: + if ((ulong)syncedFields[i].GetValue(this) != (ulong)syncedFieldValues[i]) + dirtyFields[i] = true; //This fields value is out of sync! + else + dirtyFields[i] = false; //Up to date + break; + case FieldType.UShort: + if ((ushort)syncedFields[i].GetValue(this) != (ushort)syncedFieldValues[i]) + dirtyFields[i] = true; //This fields value is out of sync! + else + dirtyFields[i] = false; //Up to date + break; + case FieldType.String: + if ((string)syncedFields[i].GetValue(this) != (string)syncedFieldValues[i]) + dirtyFields[i] = true; //This fields value is out of sync! + else + dirtyFields[i] = false; //Up to date + break; + } + } + } + #endregion + protected void SendToServer(string messageType, string channelName, byte[] data) { if(MessageManager.messageTypes[messageType] < 32) diff --git a/MLAPI/MonoBehaviours/Core/NetworkedObject.cs b/MLAPI/MonoBehaviours/Core/NetworkedObject.cs index d7ba9ee..aaf4332 100644 --- a/MLAPI/MonoBehaviours/Core/NetworkedObject.cs +++ b/MLAPI/MonoBehaviours/Core/NetworkedObject.cs @@ -1,4 +1,5 @@ using MLAPI.NetworkingManagerComponents; +using System.Collections.Generic; using UnityEngine; namespace MLAPI @@ -96,8 +97,35 @@ namespace MLAPI if(netBehaviours[i].networkedObject == this && !netBehaviours[i].networkedStartInvoked) { netBehaviours[i].NetworkStart(); + netBehaviours[i].SyncVarInit(); } } } + + internal static List networkedBehaviours = new List(); + internal static void InvokeSyncvarUpdate() + { + for (int i = 0; i < networkedBehaviours.Count; i++) + { + networkedBehaviours[i].SyncvarUpdate(); + } + } + + internal ushort GetOrderIndex(NetworkedBehaviour instance) + { + NetworkedBehaviour[] behaviours = GetComponentsInChildren(); + for (ushort i = 0; i < behaviours.Length; i++) + { + if (behaviours[i].networkedObject == this && behaviours[i] == instance) + return i; + } + return 0; + } + + internal NetworkedBehaviour GetBehaviourAtOrderIndex(ushort index) + { + NetworkedBehaviour[] behaviours = GetComponentsInChildren(); + return behaviours[index]; + } } } diff --git a/MLAPI/MonoBehaviours/Core/NetworkingManager.cs b/MLAPI/MonoBehaviours/Core/NetworkingManager.cs index 3e7dd18..8b98885 100644 --- a/MLAPI/MonoBehaviours/Core/NetworkingManager.cs +++ b/MLAPI/MonoBehaviours/Core/NetworkingManager.cs @@ -1,4 +1,5 @@ -using MLAPI.NetworkingManagerComponents; +using MLAPI.Attributes; +using MLAPI.NetworkingManagerComponents; using System; using System.Collections; using System.Collections.Generic; @@ -73,6 +74,7 @@ namespace MLAPI { NetworkConfig = netConfig; + SyncedVarManager.Init(); pendingClients = new HashSet(); connectedClients = new Dictionary(); messageBuffer = new byte[NetworkConfig.MessageBufferSize]; @@ -117,6 +119,7 @@ namespace MLAPI MessageManager.messageTypes.Add("MLAPI_SPAWN_POOL_OBJECT", 6); MessageManager.messageTypes.Add("MLAPI_DESTROY_POOL_OBJECT", 7); MessageManager.messageTypes.Add("MLAPI_CHANGE_OWNER", 8); + MessageManager.messageTypes.Add("MLAPI_SYNC_VAR_UPDATE", 9); NetworkConfig.MessageTypes.Add("MLAPI_OnRecieveTransformFromClient"); NetworkConfig.MessageTypes.Add("MLAPI_OnRecieveTransformFromServer"); NetworkConfig.MessageTypes.Add("MLAPI_HandleAnimationMessage"); @@ -361,6 +364,8 @@ namespace MLAPI } if (isServer) LagCompensationManager.AddFrames(); + + NetworkedObject.InvokeSyncvarUpdate(); } private IEnumerator ApprovalTimeout(int clientId) @@ -677,6 +682,71 @@ namespace MLAPI } } break; + case 9: + if (isClient) + { + using (MemoryStream messageReadStream = new MemoryStream(incommingData)) + { + using (BinaryReader messageReader = new BinaryReader(messageReadStream)) + { + byte dirtyCount = messageReader.ReadByte(); + if(dirtyCount > 0) + { + for (int i = 0; i < dirtyCount; i++) + { + uint netId = messageReader.ReadUInt32(); //NetId the syncvar is from + ushort orderIndex = messageReader.ReadUInt16(); + ushort networkBehaviourId = messageReader.ReadUInt16(); + byte fieldIndex = messageReader.ReadByte(); + FieldType type = SpawnManager.spawnedObjects[netId].GetBehaviourAtOrderIndex(orderIndex).syncedFieldTypes[fieldIndex]; + switch (type) + { + case FieldType.Bool: + SpawnManager.spawnedObjects[netId].GetBehaviourAtOrderIndex(orderIndex).OnSyncVarUpdate(messageReader.ReadBoolean(), fieldIndex); + break; + case FieldType.Byte: + SpawnManager.spawnedObjects[netId].GetBehaviourAtOrderIndex(orderIndex).OnSyncVarUpdate(messageReader.ReadByte(), fieldIndex); + break; + case FieldType.Char: + SpawnManager.spawnedObjects[netId].GetBehaviourAtOrderIndex(orderIndex).OnSyncVarUpdate(messageReader.ReadChar(), fieldIndex); + break; + case FieldType.Double: + SpawnManager.spawnedObjects[netId].GetBehaviourAtOrderIndex(orderIndex).OnSyncVarUpdate(messageReader.ReadDouble(), fieldIndex); + break; + case FieldType.Single: + SpawnManager.spawnedObjects[netId].GetBehaviourAtOrderIndex(orderIndex).OnSyncVarUpdate(messageReader.ReadSingle(), fieldIndex); + break; + case FieldType.Int: + SpawnManager.spawnedObjects[netId].GetBehaviourAtOrderIndex(orderIndex).OnSyncVarUpdate(messageReader.ReadInt32(), fieldIndex); + break; + case FieldType.Long: + SpawnManager.spawnedObjects[netId].GetBehaviourAtOrderIndex(orderIndex).OnSyncVarUpdate(messageReader.ReadInt64(), fieldIndex); + break; + case FieldType.SByte: + SpawnManager.spawnedObjects[netId].GetBehaviourAtOrderIndex(orderIndex).OnSyncVarUpdate(messageReader.ReadSByte(), fieldIndex); + break; + case FieldType.Short: + SpawnManager.spawnedObjects[netId].GetBehaviourAtOrderIndex(orderIndex).OnSyncVarUpdate(messageReader.ReadInt16(), fieldIndex); + break; + case FieldType.UInt: + SpawnManager.spawnedObjects[netId].GetBehaviourAtOrderIndex(orderIndex).OnSyncVarUpdate(messageReader.ReadUInt32(), fieldIndex); + break; + case FieldType.ULong: + SpawnManager.spawnedObjects[netId].GetBehaviourAtOrderIndex(orderIndex).OnSyncVarUpdate(messageReader.ReadUInt64(), fieldIndex); + break; + case FieldType.UShort: + SpawnManager.spawnedObjects[netId].GetBehaviourAtOrderIndex(orderIndex).OnSyncVarUpdate(messageReader.ReadUInt16(), fieldIndex); + break; + case FieldType.String: + SpawnManager.spawnedObjects[netId].GetBehaviourAtOrderIndex(orderIndex).OnSyncVarUpdate(messageReader.ReadString(), fieldIndex); + break; + } + } + } + } + } + } + break; } } } diff --git a/MLAPI/NetworkingManagerComponents/SyncedVarManager.cs b/MLAPI/NetworkingManagerComponents/SyncedVarManager.cs new file mode 100644 index 0000000..a9dcf6d --- /dev/null +++ b/MLAPI/NetworkingManagerComponents/SyncedVarManager.cs @@ -0,0 +1,35 @@ +using MLAPI.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace MLAPI.NetworkingManagerComponents +{ + internal static class SyncedVarManager + { + internal static void Init() + { + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + Assembly assembly = Assembly.GetExecutingAssembly(); + for (int i = 0; i < assemblies.Length; i++) + { + if (assemblies[i].FullName == "Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null") + { + assembly = assemblies[i]; + break; + } + } + IEnumerable types = from t in assembly.GetTypes() + where t.IsClass && t.IsSubclassOf(typeof(NetworkedBehaviour)) + select t; + List behaviourTypes = types.OrderBy(x => x.FullName).ToList(); + for (ushort i = 0; i < behaviourTypes.Count; i++) + { + FieldInfo networkedBehaviourId = behaviourTypes[i].GetField("networkedBehaviourId", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy); + networkedBehaviourId.SetValue(null, i); + } + } + } +}