diff --git a/MLAPI/Configuration/MLAPIConstants.cs b/MLAPI/Configuration/MLAPIConstants.cs index 6970d2b637..0021151b87 100644 --- a/MLAPI/Configuration/MLAPIConstants.cs +++ b/MLAPI/Configuration/MLAPIConstants.cs @@ -1,4 +1,4 @@ -namespace MLAPI.Configuration +namespace MLAPI.Configuration { /// /// A static class containing MLAPI constants @@ -30,6 +30,7 @@ internal static class MLAPIConstants internal const byte MLAPI_UNNAMED_MESSAGE = 20; internal const byte MLAPI_DESTROY_OBJECTS = 21; internal const byte MLAPI_NAMED_MESSAGE = 22; + internal const byte MLAPI_SYNCED_VAR = 23; internal const byte INVALID = 32; internal static readonly string[] MESSAGE_NAMES = { @@ -56,7 +57,7 @@ internal static class MLAPIConstants "MLAPI_UNNAMED_MESSAGE", "MLAPI_DESTROY_OBJECTS", "MLAPI_NAMED_MESSAGE", - "", + "MLAPI_SYNCED_VAR", "", "", "", diff --git a/MLAPI/Core/NetworkedBehaviour.cs b/MLAPI/Core/NetworkedBehaviour.cs index 8469e4193c..e0f030a499 100644 --- a/MLAPI/Core/NetworkedBehaviour.cs +++ b/MLAPI/Core/NetworkedBehaviour.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using UnityEngine; @@ -16,6 +16,7 @@ using MLAPI.Serialization.Pooled; using MLAPI.Spawning; using BitStream = MLAPI.Serialization.BitStream; +using MLAPI.Serialization; namespace MLAPI { @@ -151,23 +152,23 @@ internal void InternalNetworkStart() rpcDefinition = RpcTypeDefinition.Get(GetType()); rpcDelegates = rpcDefinition.CreateTargetedDelegates(this); - NetworkedVarInit(); + InitializeVars(); } - - /// - /// Gets called when SyncedVars gets updated - /// + + /// + /// Gets called when SyncedVars gets updated + /// public virtual void OnSyncVarUpdate() - { - - } - /// - /// Gets called when the local client gains ownership of this object - /// - public virtual void OnGainedOwnership() - { - - } + { + + } + /// + /// Gets called when the local client gains ownership of this object + /// + public virtual void OnGainedOwnership() + { + + } /// /// Gets called when we loose ownership of this object /// @@ -197,25 +198,30 @@ protected NetworkedBehaviour GetBehaviour(ushort id) #region NetworkedVar - private bool networkedVarInit = false; - private readonly List> channelMappedVarIndexes = new List>(); - private readonly List channelsForVarGroups = new List(); + private bool varInit = false; + + private readonly List> channelMappedNetworkedVarIndexes = new List>(); + private readonly List channelsForNetworkedVarGroups = new List(); internal readonly List networkedVarFields = new List(); + private static readonly Dictionary fieldTypes = new Dictionary(); - + private readonly List> channelMappedSyncedVarIndexes = new List>(); + private readonly List channelsForSyncedVarGroups = new List(); + internal readonly List syncedVars = new List(); + + private static FieldInfo[] GetFieldInfoForType(Type type) { if (!fieldTypes.ContainsKey(type)) fieldTypes.Add(type, GetFieldInfoForTypeRecursive(type)); - + return fieldTypes[type]; } - - - private static FieldInfo[] GetFieldInfoForTypeRecursive(Type type, List list = null) + + private static FieldInfo[] GetFieldInfoForTypeRecursive(Type type, List list = null) { - if (list == null) + if (list == null) { list = new List(); list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)); @@ -235,68 +241,237 @@ private static FieldInfo[] GetFieldInfoForTypeRecursive(Type type, List firstLevelIndex = new Dictionary(); - int secondLevelCounter = 0; - for (int i = 0; i < networkedVarFields.Count; i++) + + { + // Create index map for channels + Dictionary firstLevelIndex = new Dictionary(); + int secondLevelCounter = 0; + + for (int i = 0; i < syncedVars.Count; i++) + { + string channel = syncedVars[i].attribute.Channel; + + if (!firstLevelIndex.ContainsKey(channel)) + { + firstLevelIndex.Add(channel, secondLevelCounter); + channelsForSyncedVarGroups.Add(channel); + secondLevelCounter++; + } + + if (firstLevelIndex[channel] >= channelMappedSyncedVarIndexes.Count) + { + channelMappedSyncedVarIndexes.Add(new HashSet()); + } + + channelMappedSyncedVarIndexes[firstLevelIndex[channel]].Add(i); + } + } + + { + // Create index map for channels + Dictionary firstLevelIndex = new Dictionary(); + int secondLevelCounter = 0; + + for (int i = 0; i < networkedVarFields.Count; i++) + { + string channel = networkedVarFields[i].GetChannel(); // Cache this here. Some developers are stupid. You don't know what shit they will do in their methods + + if (!firstLevelIndex.ContainsKey(channel)) + { + firstLevelIndex.Add(channel, secondLevelCounter); + channelsForNetworkedVarGroups.Add(channel); + secondLevelCounter++; + } + + if (firstLevelIndex[channel] >= channelMappedNetworkedVarIndexes.Count) + { + channelMappedNetworkedVarIndexes.Add(new HashSet()); + } + + channelMappedNetworkedVarIndexes[firstLevelIndex[channel]].Add(i); + } + } + } + + internal void VarUpdate() + { + if (!varInit) + InitializeVars(); + + SyncedVarUpdate(); + NetworkedVarUpdate(); + } + + private readonly List syncedVarIndexesToReset = new List(); + private readonly HashSet syncedVarIndexesToResetSet = new HashSet(); + + private void SyncedVarUpdate() + { + if (!IsServer || !CouldHaveDirtySyncedVars()) + return; + + syncedVarIndexesToReset.Clear(); + syncedVarIndexesToResetSet.Clear(); + + for (int i = 0; i < NetworkingManager.Singleton.ConnectedClientsList.Count; i++) { - string channel = networkedVarFields[i].GetChannel(); //Cache this here. Some developers are stupid. You don't know what shit they will do in their methods - if (!firstLevelIndex.ContainsKey(channel)) + // Do this check here to prevent doing all the expensive dirty checks + if (this.NetworkedObject.observers.Contains(NetworkingManager.Singleton.ConnectedClientsList[i].ClientId)) { - firstLevelIndex.Add(channel, secondLevelCounter); - channelsForVarGroups.Add(channel); - secondLevelCounter++; + // This iterates over every "channel group". + for (int j = 0; j < channelMappedSyncedVarIndexes.Count; j++) + { + using (PooledBitStream stream = PooledBitStream.Get()) + { + using (PooledBitWriter writer = PooledBitWriter.Get(stream)) + { + writer.WriteUInt64Packed(NetworkId); + writer.WriteUInt16Packed(NetworkedObject.GetOrderIndex(this)); + + ulong clientId = NetworkingManager.Singleton.ConnectedClientsList[i].ClientId; + bool writtenAny = false; + for (int k = 0; k < syncedVars.Count; k++) + { + if (!channelMappedSyncedVarIndexes[j].Contains(k)) + { + // This var does not belong to the currently iterating channel group. + if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) + { + writer.WriteUInt16Packed(0); + } + else + { + writer.WriteBool(false); + } + continue; + } + + bool isDirty = syncedVars[k].IsDirty(); // cache this here. You never know what operations users will do in the dirty methods + + if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) + { + if (!isDirty) + { + writer.WriteUInt16Packed(0); + } + } + else + { + writer.WriteBool(isDirty); + } + + if (isDirty) + { + writtenAny = true; + + if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) + { + using (PooledBitStream varStream = PooledBitStream.Get()) + { + syncedVars[k].WriteValue(varStream, false); + varStream.PadStream(); + + writer.WriteUInt16Packed((ushort)varStream.Length); + stream.CopyFrom(varStream); + } + } + else + { + syncedVars[k].WriteValue(stream, false); + } + + if (!syncedVarIndexesToResetSet.Contains(k)) + { + syncedVarIndexesToResetSet.Add(k); + syncedVarIndexesToReset.Add(k); + } + } + } + + if (writtenAny) + { + InternalMessageSender.Send(clientId, MLAPIConstants.MLAPI_SYNCED_VAR, channelsForSyncedVarGroups[j], stream, SecuritySendFlags.None, this.NetworkedObject); + } + } + } + } } - if (firstLevelIndex[channel] >= channelMappedVarIndexes.Count) - channelMappedVarIndexes.Add(new HashSet()); - channelMappedVarIndexes[firstLevelIndex[channel]].Add(i); + } + + for (int i = 0; i < syncedVarIndexesToReset.Count; i++) + { + syncedVars[syncedVarIndexesToReset[i]].ResetDirty(); } } - + private readonly List networkedVarIndexesToReset = new List(); private readonly HashSet networkedVarIndexesToResetSet = new HashSet(); - internal void NetworkedVarUpdate() - { - if (!networkedVarInit) - NetworkedVarInit(); - if (!CouldHaveDirtyVars()) + private void NetworkedVarUpdate() + { + if (!CouldHaveDirtyNetworkedVars()) return; networkedVarIndexesToReset.Clear(); networkedVarIndexesToResetSet.Clear(); - + for (int i = 0; i < (IsServer ? NetworkingManager.Singleton.ConnectedClientsList.Count : 1); i++) { // Do this check here to prevent doing all the expensive dirty checks if (!IsServer || this.NetworkedObject.observers.Contains(NetworkingManager.Singleton.ConnectedClientsList[i].ClientId)) { - //This iterates over every "channel group". - for (int j = 0; j < channelMappedVarIndexes.Count; j++) + // This iterates over every "channel group". + for (int j = 0; j < channelMappedNetworkedVarIndexes.Count; j++) { using (PooledBitStream stream = PooledBitStream.Get()) { @@ -309,9 +484,9 @@ internal void NetworkedVarUpdate() bool writtenAny = false; for (int k = 0; k < networkedVarFields.Count; k++) { - if (!channelMappedVarIndexes[j].Contains(k)) + if (!channelMappedNetworkedVarIndexes[j].Contains(k)) { - //This var does not belong to the currently iterating channel group. + // This var does not belong to the currently iterating channel group. if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) { writer.WriteUInt16Packed(0); @@ -323,10 +498,9 @@ internal void NetworkedVarUpdate() continue; } - bool isDirty = networkedVarFields[k].IsDirty(); //cache this here. You never know what operations users will do in the dirty methods + bool isDirty = networkedVarFields[k].IsDirty(); // cache this here. You never know what operations users will do in the dirty methods bool shouldWrite = isDirty && (!IsServer || networkedVarFields[k].CanClientRead(clientId)); - - + if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) { if (!shouldWrite) @@ -349,7 +523,7 @@ internal void NetworkedVarUpdate() { networkedVarFields[k].WriteDelta(varStream); varStream.PadStream(); - + writer.WriteUInt16Packed((ushort)varStream.Length); stream.CopyFrom(varStream); } @@ -370,9 +544,9 @@ internal void NetworkedVarUpdate() if (writtenAny) { if (IsServer) - InternalMessageSender.Send(clientId, MLAPIConstants.MLAPI_NETWORKED_VAR_DELTA, channelsForVarGroups[j], stream, SecuritySendFlags.None, this.NetworkedObject); + InternalMessageSender.Send(clientId, MLAPIConstants.MLAPI_NETWORKED_VAR_DELTA, channelsForNetworkedVarGroups[j], stream, SecuritySendFlags.None, this.NetworkedObject); else - InternalMessageSender.Send(NetworkingManager.Singleton.ServerClientId, MLAPIConstants.MLAPI_NETWORKED_VAR_DELTA, channelsForVarGroups[j], stream, SecuritySendFlags.None, null); + InternalMessageSender.Send(NetworkingManager.Singleton.ServerClientId, MLAPIConstants.MLAPI_NETWORKED_VAR_DELTA, channelsForNetworkedVarGroups[j], stream, SecuritySendFlags.None, null); } } } @@ -386,17 +560,71 @@ internal void NetworkedVarUpdate() } } - private bool CouldHaveDirtyVars() + private bool CouldHaveDirtyNetworkedVars() { for (int i = 0; i < networkedVarFields.Count; i++) { - if (networkedVarFields[i].IsDirty()) + if (networkedVarFields[i].IsDirty()) return true; } return false; } + private bool CouldHaveDirtySyncedVars() + { + for (int i = 0; i < syncedVars.Count; i++) + { + if (syncedVars[i].IsDirty()) + return true; + } + + return false; + } + + internal static void HandleSyncedVarValue(List syncedVarList, Stream stream, ulong clientId, NetworkedBehaviour logInstance) + { + using (PooledBitReader reader = PooledBitReader.Get(stream)) + { + for (int i = 0; i < syncedVarList.Count; i++) + { + ushort varSize = 0; + + if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) + { + varSize = reader.ReadUInt16Packed(); + + if (varSize == 0) + continue; + } + else + { + if (!reader.ReadBool()) + continue; + } + + long readStartPos = stream.Position; + + syncedVarList[i].ReadValue(stream); + + if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) + { + (stream as BitStream).SkipPadBits(); + + if (stream.Position > (readStartPos + varSize)) + { + if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("SyncedVar read too far. " + (stream.Position - (readStartPos + varSize)) + " bytes." + (logInstance != null ? ("NetworkId: " + logInstance.NetworkId + " BehaviourIndex: " + logInstance.NetworkedObject.GetOrderIndex(logInstance) + " VariableIndex: " + i) : string.Empty)); + stream.Position = readStartPos + varSize; + } + else if (stream.Position < (readStartPos + varSize)) + { + if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("SyncedVar read too little. " + ((readStartPos + varSize) - stream.Position) + " bytes." + (logInstance != null ? ("NetworkId: " + logInstance.NetworkId + " BehaviourIndex: " + logInstance.NetworkedObject.GetOrderIndex(logInstance) + " VariableIndex: " + i) : string.Empty)); + stream.Position = readStartPos + varSize; + } + } + } + } + } internal static void HandleNetworkedVarDeltas(List networkedVarList, Stream stream, ulong clientId, NetworkedBehaviour logInstance) { @@ -405,18 +633,18 @@ internal static void HandleNetworkedVarDeltas(List networkedVarLi for (int i = 0; i < networkedVarList.Count; i++) { ushort varSize = 0; - + if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) { varSize = reader.ReadUInt16Packed(); - + if (varSize == 0) continue; } else { if (!reader.ReadBool()) - continue; + continue; } if (NetworkingManager.Singleton.IsServer && !networkedVarList[i].CanClientWrite(clientId)) @@ -431,25 +659,25 @@ internal static void HandleNetworkedVarDeltas(List networkedVarLi { //This client wrote somewhere they are not allowed. This is critical //We can't just skip this field. Because we don't actually know how to dummy read - //That is, we don't know how many bytes to skip. Because the interface doesn't have a + //That is, we don't know how many bytes to skip. Because the interface doesn't have a //Read that gives us the value. Only a Read that applies the value straight away //A dummy read COULD be added to the interface for this situation, but it's just being too nice. //This is after all a developer fault. A critical error should be fine. // - TwoTen if (LogHelper.CurrentLogLevel <= LogLevel.Error) LogHelper.LogError("Client wrote to NetworkedVar without permission. No more variables can be read. This is critical. " + (logInstance != null ? ("NetworkId: " + logInstance.NetworkId + " BehaviourIndex: " + logInstance.NetworkedObject.GetOrderIndex(logInstance) + " VariableIndex: " + i) : string.Empty)); - return; + return; } } long readStartPos = stream.Position; - + networkedVarList[i].ReadDelta(stream, NetworkingManager.Singleton.IsServer); if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) { (stream as BitStream).SkipPadBits(); - + if (stream.Position > (readStartPos + varSize)) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("Var delta read too far. " + (stream.Position - (readStartPos + varSize)) + " bytes." + (logInstance != null ? ("NetworkId: " + logInstance.NetworkId + " BehaviourIndex: " + logInstance.NetworkedObject.GetOrderIndex(logInstance) + " VariableIndex: " + i) : string.Empty)); @@ -472,20 +700,20 @@ internal static void HandleNetworkedVarUpdate(List networkedVarLi for (int i = 0; i < networkedVarList.Count; i++) { ushort varSize = 0; - + if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) { varSize = reader.ReadUInt16Packed(); - + if (varSize == 0) continue; } else { if (!reader.ReadBool()) - continue; + continue; } - + if (NetworkingManager.Singleton.IsServer && !networkedVarList[i].CanClientWrite(clientId)) { if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) @@ -498,27 +726,27 @@ internal static void HandleNetworkedVarUpdate(List networkedVarLi { //This client wrote somewhere they are not allowed. This is critical //We can't just skip this field. Because we don't actually know how to dummy read - //That is, we don't know how many bytes to skip. Because the interface doesn't have a + //That is, we don't know how many bytes to skip. Because the interface doesn't have a //Read that gives us the value. Only a Read that applies the value straight away //A dummy read COULD be added to the interface for this situation, but it's just being too nice. //This is after all a developer fault. A critical error should be fine. // - TwoTen if (LogHelper.CurrentLogLevel <= LogLevel.Error) LogHelper.LogError("Client wrote to NetworkedVar without permission. No more variables can be read. This is critical. " + (logInstance != null ? ("NetworkId: " + logInstance.NetworkId + " BehaviourIndex: " + logInstance.NetworkedObject.GetOrderIndex(logInstance) + " VariableIndex: " + i) : string.Empty)); - return; + return; } } long readStartPos = stream.Position; - + networkedVarList[i].ReadField(stream); - + if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) { if (stream is BitStream bitStream) { bitStream.SkipPadBits(); } - + if (stream.Position > (readStartPos + varSize)) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("Var update read too far. " + (stream.Position - (readStartPos + varSize)) + " bytes." + (logInstance != null ? ("NetworkId: " + logInstance.NetworkId + " BehaviourIndex: " + logInstance.NetworkedObject.GetOrderIndex(logInstance) + " VariableIndex: " + i) : string.Empty)); @@ -534,6 +762,89 @@ internal static void HandleNetworkedVarUpdate(List networkedVarLi } } + internal static void WriteSyncedVarData(List syncedVarList, Stream stream, ulong clientId) + { + if (syncedVarList.Count == 0) + return; + + using (PooledBitWriter writer = PooledBitWriter.Get(stream)) + { + for (int j = 0; j < syncedVarList.Count; j++) + { + if (!NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) + { + writer.WriteBool(true); + } + + if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) + { + using (PooledBitStream varStream = PooledBitStream.Get()) + { + syncedVarList[j].WriteValue(varStream); + varStream.PadStream(); + + writer.WriteUInt16Packed((ushort)varStream.Length); + varStream.CopyTo(stream); + } + } + else + { + syncedVarList[j].WriteValue(stream); + } + } + } + } + + internal static void SetSyncedVarData(List syncedVarList, Stream stream) + { + if (syncedVarList.Count == 0) + return; + + using (PooledBitReader reader = PooledBitReader.Get(stream)) + { + for (int j = 0; j < syncedVarList.Count; j++) + { + ushort varSize = 0; + + if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) + { + varSize = reader.ReadUInt16Packed(); + + if (varSize == 0) + continue; + } + else + { + if (!reader.ReadBool()) + continue; + } + + long readStartPos = stream.Position; + + syncedVarList[j].ReadValue(stream); + + if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) + { + if (stream is BitStream bitStream) + { + bitStream.SkipPadBits(); + } + + if (stream.Position > (readStartPos + varSize)) + { + if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("SyncedVar data read too far. " + (stream.Position - (readStartPos + varSize)) + " bytes."); + stream.Position = readStartPos + varSize; + } + else if (stream.Position < (readStartPos + varSize)) + { + if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("SyncedVar data read too little. " + ((readStartPos + varSize) - stream.Position) + " bytes."); + stream.Position = readStartPos + varSize; + } + } + } + } + } + internal static void WriteNetworkedVarData(List networkedVarList, Stream stream, ulong clientId) { if (networkedVarList.Count == 0) @@ -565,7 +876,7 @@ internal static void WriteNetworkedVarData(List networkedVarList, { networkedVarList[j].WriteField(varStream); varStream.PadStream(); - + writer.WriteUInt16Packed((ushort)varStream.Length); varStream.CopyTo(stream); } @@ -575,7 +886,7 @@ internal static void WriteNetworkedVarData(List networkedVarList, networkedVarList[j].WriteField(stream); } } - } + } } } @@ -589,31 +900,31 @@ internal static void SetNetworkedVarData(List networkedVarList, S for (int j = 0; j < networkedVarList.Count; j++) { ushort varSize = 0; - + if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) { varSize = reader.ReadUInt16Packed(); - + if (varSize == 0) continue; } else { if (!reader.ReadBool()) - continue; + continue; } long readStartPos = stream.Position; - + networkedVarList[j].ReadField(stream); - + if (NetworkingManager.Singleton.NetworkConfig.EnsureNetworkedVarLengthSafety) { if (stream is BitStream bitStream) { bitStream.SkipPadBits(); } - + if (stream.Position > (readStartPos + varSize)) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("Var data read too far. " + (stream.Position - (readStartPos + varSize)) + " bytes."); @@ -625,11 +936,9 @@ internal static void SetNetworkedVarData(List networkedVarList, S stream.Position = readStartPos + varSize; } } - } + } } } - - #endregion #region MESSAGING_SYSTEM @@ -691,7 +1000,7 @@ internal object OnRemoteServerRPC(ulong hash, ulong senderClientId, Stream strea return InvokeServerRPCLocal(hash, senderClientId, stream); } - + internal object OnRemoteClientRPC(ulong hash, ulong senderClientId, Stream stream) { if (!rpcDefinition.clientMethods.ContainsKey(hash)) @@ -699,7 +1008,7 @@ internal object OnRemoteClientRPC(ulong hash, ulong senderClientId, Stream strea if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("ClientRPC request method not found"); return null; } - + return InvokeClientRPCLocal(hash, senderClientId, stream); } @@ -722,7 +1031,7 @@ private object InvokeClientRPCLocal(ulong hash, ulong senderClientId, Stream str return null; } - + //Technically boxed writes are not needed. But save LOC for the non performance sends. internal void SendServerRPCBoxed(ulong hash, string channel, SecuritySendFlags security, params object[] parameters) { @@ -735,12 +1044,12 @@ internal void SendServerRPCBoxed(ulong hash, string channel, SecuritySendFlags s { writer.WriteObjectPacked(parameters[i]); } - + SendServerRPCPerformance(hash, stream, channel, security); } } } - + internal RpcResponse SendServerRPCBoxedResponse(ulong hash, string channel, SecuritySendFlags security, params object[] parameters) { using (PooledBitStream stream = PooledBitStream.Get()) @@ -751,12 +1060,12 @@ internal RpcResponse SendServerRPCBoxedResponse(ulong hash, string channel { writer.WriteObjectPacked(parameters[i]); } - + return SendServerRPCPerformanceResponse(hash, stream, channel, security); } } } - + internal void SendClientRPCBoxedToClient(ulong hash, ulong clientId, string channel, SecuritySendFlags security, params object[] parameters) { using (PooledBitStream stream = PooledBitStream.Get()) @@ -771,7 +1080,7 @@ internal void SendClientRPCBoxedToClient(ulong hash, ulong clientId, string chan } } } - + internal RpcResponse SendClientRPCBoxedResponse(ulong hash, ulong clientId, string channel, SecuritySendFlags security, params object[] parameters) { using (PooledBitStream stream = PooledBitStream.Get()) @@ -782,12 +1091,12 @@ internal RpcResponse SendClientRPCBoxedResponse(ulong hash, ulong clientId { writer.WriteObjectPacked(parameters[i]); } - + return SendClientRPCPerformanceResponse(hash, clientId, stream, channel, security); } } } - + internal void SendClientRPCBoxed(ulong hash, List clientIds, string channel, SecuritySendFlags security, params object[] parameters) { using (PooledBitStream stream = PooledBitStream.Get()) @@ -849,7 +1158,7 @@ internal void SendServerRPCPerformance(ulong hash, Stream messageStream, string } } } - + internal RpcResponse SendServerRPCPerformanceResponse(ulong hash, Stream messageStream, string channel, SecuritySendFlags security) { if (!IsClient && IsRunning) @@ -868,7 +1177,7 @@ internal RpcResponse SendServerRPCPerformanceResponse(ulong hash, Stream m writer.WriteUInt64Packed(NetworkId); writer.WriteUInt16Packed(NetworkedObject.GetOrderIndex(this)); writer.WriteUInt64Packed(hash); - + if (!IsHost) writer.WriteUInt64Packed(responseId); stream.CopyFrom(messageStream); @@ -877,7 +1186,7 @@ internal RpcResponse SendServerRPCPerformanceResponse(ulong hash, Stream m { messageStream.Position = 0; object result = InvokeServerRPCLocal(hash, NetworkingManager.Singleton.LocalClientId, messageStream); - + return new RpcResponse() { Id = responseId, @@ -889,7 +1198,7 @@ internal RpcResponse SendServerRPCPerformanceResponse(ulong hash, Stream m }; } else - { + { RpcResponse response = new RpcResponse() { Id = responseId, @@ -898,9 +1207,9 @@ internal RpcResponse SendServerRPCPerformanceResponse(ulong hash, Stream m Type = typeof(T), ClientId = NetworkingManager.Singleton.ServerClientId }; - + ResponseMessageManager.Add(response.Id, response); - + InternalMessageSender.Send(NetworkingManager.Singleton.ServerClientId, MLAPIConstants.MLAPI_SERVER_RPC_REQUEST, string.IsNullOrEmpty(channel) ? "MLAPI_DEFAULT_MESSAGE" : channel, stream, security, null); return response; @@ -910,7 +1219,7 @@ internal RpcResponse SendServerRPCPerformanceResponse(ulong hash, Stream m } internal void SendClientRPCPerformance(ulong hash, List clientIds, Stream messageStream, string channel, SecuritySendFlags security) - { + { if (!IsServer && IsRunning) { //We are NOT a server. @@ -937,7 +1246,7 @@ internal void SendClientRPCPerformance(ulong hash, List clientIds, Strea if (LogHelper.CurrentLogLevel <= LogLevel.Developer) LogHelper.LogWarning("Silently suppressed ClientRPC because a target in the bulk list was not an observer"); continue; } - + if (IsHost && NetworkingManager.Singleton.ConnectedClientsList[i].ClientId == NetworkingManager.Singleton.LocalClientId) { messageStream.Position = 0; @@ -958,7 +1267,7 @@ internal void SendClientRPCPerformance(ulong hash, List clientIds, Strea if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("Cannot send ClientRPC to client without visibility to the object"); continue; } - + if (IsHost && clientIds[i] == NetworkingManager.Singleton.LocalClientId) { messageStream.Position = 0; @@ -1028,7 +1337,7 @@ internal void SendClientRPCPerformance(ulong hash, ulong clientId, Stream messag if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("Only clients and host can invoke ClientRPC"); return; } - + if (!this.NetworkedObject.observers.Contains(clientId)) { if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("Cannot send ClientRPC to client without visibility to the object"); @@ -1057,7 +1366,7 @@ internal void SendClientRPCPerformance(ulong hash, ulong clientId, Stream messag } } } - + internal RpcResponse SendClientRPCPerformanceResponse(ulong hash, ulong clientId, Stream messageStream, string channel, SecuritySendFlags security) { if (!IsServer && IsRunning) @@ -1072,9 +1381,9 @@ internal RpcResponse SendClientRPCPerformanceResponse(ulong hash, ulong cl if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("Cannot send ClientRPC to client without visibility to the object"); return null; } - + ulong responseId = ResponseMessageManager.GenerateMessageId(); - + using (PooledBitStream stream = PooledBitStream.Get()) { using (PooledBitWriter writer = PooledBitWriter.Get(stream)) @@ -1082,7 +1391,7 @@ internal RpcResponse SendClientRPCPerformanceResponse(ulong hash, ulong cl writer.WriteUInt64Packed(NetworkId); writer.WriteUInt16Packed(NetworkedObject.GetOrderIndex(this)); writer.WriteUInt64Packed(hash); - + if (!(IsHost && clientId == NetworkingManager.Singleton.LocalClientId)) writer.WriteUInt64Packed(responseId); stream.CopyFrom(messageStream); @@ -1091,7 +1400,7 @@ internal RpcResponse SendClientRPCPerformanceResponse(ulong hash, ulong cl { messageStream.Position = 0; object result = InvokeClientRPCLocal(hash, NetworkingManager.Singleton.LocalClientId, messageStream); - + return new RpcResponse() { Id = responseId, @@ -1112,11 +1421,11 @@ internal RpcResponse SendClientRPCPerformanceResponse(ulong hash, ulong cl Type = typeof(T), ClientId = clientId }; - + ResponseMessageManager.Add(response.Id, response); - + InternalMessageSender.Send(clientId, MLAPIConstants.MLAPI_CLIENT_RPC_REQUEST, string.IsNullOrEmpty(channel) ? "MLAPI_DEFAULT_MESSAGE" : channel, stream, security, null); - + return response; } } diff --git a/MLAPI/Core/NetworkedObject.cs b/MLAPI/Core/NetworkedObject.cs index 85bb27b63b..2fb9b409de 100644 --- a/MLAPI/Core/NetworkedObject.cs +++ b/MLAPI/Core/NetworkedObject.cs @@ -546,21 +546,43 @@ internal static void NetworkedBehaviourUpdate() for (int i = 0; i < amountToProcess; i++) { if (_lastProcessedObject >= SpawnManager.SpawnedObjectsList.Count) + { _lastProcessedObject = 0; + } // Sync all vars for (int j = 0; j < SpawnManager.SpawnedObjectsList[_lastProcessedObject].childNetworkedBehaviours.Count; j++) - SpawnManager.SpawnedObjectsList[_lastProcessedObject].childNetworkedBehaviours[j].NetworkedVarUpdate(); + { + SpawnManager.SpawnedObjectsList[_lastProcessedObject].childNetworkedBehaviours[j].VarUpdate(); + } _lastProcessedObject++; } } + internal void WriteSyncedVarData(Stream stream, ulong clientId) + { + for (int i = 0; i < childNetworkedBehaviours.Count; i++) + { + childNetworkedBehaviours[i].InitializeVars(); + NetworkedBehaviour.WriteSyncedVarData(childNetworkedBehaviours[i].syncedVars, stream, clientId); + } + } + + internal void SetSyncedVarData(Stream stream) + { + for (int i = 0; i < childNetworkedBehaviours.Count; i++) + { + childNetworkedBehaviours[i].InitializeVars(); + NetworkedBehaviour.SetSyncedVarData(childNetworkedBehaviours[i].syncedVars, stream); + } + } + internal void WriteNetworkedVarData(Stream stream, ulong clientId) { for (int i = 0; i < childNetworkedBehaviours.Count; i++) { - childNetworkedBehaviours[i].NetworkedVarInit(); + childNetworkedBehaviours[i].InitializeVars(); NetworkedBehaviour.WriteNetworkedVarData(childNetworkedBehaviours[i].networkedVarFields, stream, clientId); } } @@ -569,7 +591,7 @@ internal void SetNetworkedVarData(Stream stream) { for (int i = 0; i < childNetworkedBehaviours.Count; i++) { - childNetworkedBehaviours[i].NetworkedVarInit(); + childNetworkedBehaviours[i].InitializeVars(); NetworkedBehaviour.SetNetworkedVarData(childNetworkedBehaviours[i].networkedVarFields, stream); } } diff --git a/MLAPI/Core/NetworkingManager.cs b/MLAPI/Core/NetworkingManager.cs index 126adecf6d..b32c912507 100644 --- a/MLAPI/Core/NetworkingManager.cs +++ b/MLAPI/Core/NetworkingManager.cs @@ -591,7 +591,7 @@ private void OnEnable() else { Singleton = this; - if (OnSingletonReady != null) + if (OnSingletonReady != null) OnSingletonReady(); if (DontDestroy) DontDestroyOnLoad(gameObject); @@ -990,6 +990,9 @@ private void HandleIncomingData(ulong clientId, string channelName, ArraySegment case MLAPIConstants.MLAPI_CLIENT_SWITCH_SCENE_COMPLETED: if (IsServer && NetworkConfig.EnableSceneManagement) InternalMessageHandler.HandleClientSwitchSceneCompleted(clientId, messageStream); break; + case MLAPIConstants.MLAPI_SYNCED_VAR: + if (IsClient) InternalMessageHandler.HandleSyncedVar(clientId, messageStream); + break; default: if (LogHelper.CurrentLogLevel <= LogLevel.Error) LogHelper.LogError("Read unrecognized messageType " + messageType); break; @@ -1226,6 +1229,7 @@ internal void HandleApproval(ulong clientId, bool createPlayerObject, ulong? pla if (NetworkConfig.EnableNetworkedVar) { observedObject.WriteNetworkedVarData(stream, clientId); + observedObject.WriteSyncedVarData(stream, clientId); } } @@ -1286,6 +1290,7 @@ internal void HandleApproval(ulong clientId, bool createPlayerObject, ulong? pla if (NetworkConfig.EnableNetworkedVar) { ConnectedClients[clientId].PlayerObject.WriteNetworkedVarData(stream, clientPair.Key); + ConnectedClients[clientId].PlayerObject.WriteSyncedVarData(stream, clientPair.Key); } InternalMessageSender.Send(clientPair.Key, MLAPIConstants.MLAPI_ADD_OBJECT, "MLAPI_INTERNAL", stream, SecuritySendFlags.None, null); diff --git a/MLAPI/Messaging/InternalMessageHandler.cs b/MLAPI/Messaging/InternalMessageHandler.cs index abb475bab8..d929c479cb 100644 --- a/MLAPI/Messaging/InternalMessageHandler.cs +++ b/MLAPI/Messaging/InternalMessageHandler.cs @@ -51,7 +51,7 @@ internal static void HandleHailRequest(ulong clientId, Stream stream) // Read the ECDH // Allocation justification: This runs on client and only once, at initial connection serverDiffieHellmanPublicPart = reader.ReadByteArray(); - + // Verify the key exchange if (NetworkingManager.Singleton.NetworkConfig.SignKeyExchange) { @@ -151,7 +151,7 @@ internal static void HandleHailResponse(ulong clientId, Stream stream) NetworkingManager.Singleton.PendingClients[clientId].ConnectionState = PendingClient.State.PendingConnection; NetworkingManager.Singleton.PendingClients[clientId].KeyExchange = null; // Give to GC - + // Send greetings, they have passed all the handshakes using (PooledBitStream outStream = PooledBitStream.Get()) { @@ -186,7 +186,7 @@ internal static void HandleConnectionRequest(ulong clientId, Stream stream) if (NetworkingManager.Singleton.NetworkConfig.ConnectionApproval) { byte[] connectionBuffer = reader.ReadByteArray(); - NetworkingManager.Singleton.ConnectionApprovalCallback(connectionBuffer, clientId, (createPlayerObject, playerPrefabHash, approved, position, rotation) => + NetworkingManager.Singleton.ConnectionApprovalCallback(connectionBuffer, clientId, (createPlayerObject, playerPrefabHash, approved, position, rotation) => { NetworkingManager.Singleton.HandleApproval(clientId, createPlayerObject, playerPrefabHash, approved, position, rotation); }); @@ -308,7 +308,7 @@ void OnSceneLoadComplete() } onSceneLoaded = (oldScene, newScene) => { OnSceneLoadComplete(); }; - + SceneManager.activeSceneChanged += onSceneLoaded; NetworkSceneManager.OnFirstSceneSwitchSync(sceneIndex, sceneSwitchProgressGuid); @@ -371,7 +371,7 @@ internal static void HandleAddObject(ulong clientId, Stream stream) bool hasPayload = reader.ReadBool(); int payLoadLength = hasPayload ? reader.ReadInt32Packed() : 0; - + NetworkedObject netObject = SpawnManager.CreateLocalNetworkedObject(softSync, instanceId, prefabHash, parentNetworkId, pos, rot); SpawnManager.SpawnNetworkedObjectLocally(netObject, networkId, softSync, isPlayerObject, ownerId, stream, hasPayload, payLoadLength, true, false); } @@ -392,18 +392,18 @@ internal static void HandleSwitchScene(ulong clientId, Stream stream) { uint sceneIndex = reader.ReadUInt32Packed(); Guid switchSceneGuid = new Guid(reader.ReadByteArray()); - + Serialization.BitStream objectStream = new Serialization.BitStream(); objectStream.CopyUnreadFrom(stream); objectStream.Position = 0; - + NetworkSceneManager.OnSceneSwitch(sceneIndex, switchSceneGuid, objectStream); } } internal static void HandleClientSwitchSceneCompleted(ulong clientId, Stream stream) { - using (PooledBitReader reader = PooledBitReader.Get(stream)) + using (PooledBitReader reader = PooledBitReader.Get(stream)) { NetworkSceneManager.OnClientSwitchSceneCompleted(clientId, new Guid(reader.ReadByteArray())); } @@ -415,7 +415,7 @@ internal static void HandleChangeOwner(ulong clientId, Stream stream) { ulong networkId = reader.ReadUInt64Packed(); ulong ownerClientId = reader.ReadUInt64Packed(); - + if (SpawnManager.SpawnedObjects[networkId].OwnerClientId == NetworkingManager.Singleton.LocalClientId) { //We are current owner. @@ -435,20 +435,20 @@ internal static void HandleAddObjects(ulong clientId, Stream stream) using (PooledBitReader reader = PooledBitReader.Get(stream)) { ushort objectCount = reader.ReadUInt16Packed(); - + for (int i = 0; i < objectCount; i++) { HandleAddObject(clientId, stream); } } } - + internal static void HandleDestroyObjects(ulong clientId, Stream stream) { using (PooledBitReader reader = PooledBitReader.Get(stream)) { ushort objectCount = reader.ReadUInt16Packed(); - + for (int i = 0; i < objectCount; i++) { HandleDestroyObject(clientId, stream); @@ -472,7 +472,7 @@ internal static void HandleNetworkedVarDelta(ulong clientId, Stream stream) if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("NetworkedVar delta received but EnableNetworkedVar is false"); return; } - + using (PooledBitReader reader = PooledBitReader.Get(stream)) { ulong networkId = reader.ReadUInt64Packed(); @@ -503,7 +503,7 @@ internal static void HandleNetworkedVarUpdate(ulong clientId, Stream stream) if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("NetworkedVar update received but EnableNetworkedVar is false"); return; } - + using (PooledBitReader reader = PooledBitReader.Get(stream)) { ulong networkId = reader.ReadUInt64Packed(); @@ -526,7 +526,32 @@ internal static void HandleNetworkedVarUpdate(ulong clientId, Stream stream) } } } - + + internal static void HandleSyncedVar(ulong clientId, Stream stream) + { + using (PooledBitReader reader = PooledBitReader.Get(stream)) + { + ulong networkId = reader.ReadUInt64Packed(); + ushort orderIndex = reader.ReadUInt16Packed(); + + if (SpawnManager.SpawnedObjects.ContainsKey(networkId)) + { + NetworkedBehaviour instance = SpawnManager.SpawnedObjects[networkId].GetBehaviourAtOrderIndex(orderIndex); + if (instance == null) + { + if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("SyncedVar message recieved for a non existant behaviour"); + return; + } + NetworkedBehaviour.HandleSyncedVarValue(instance.syncedVars, stream, clientId, instance); + } + else + { + if (LogHelper.CurrentLogLevel <= LogLevel.Normal) LogHelper.LogWarning("SyncedVar message recieved for a non existant object with id: " + networkId); + return; + } + } + } + internal static void HandleServerRPC(ulong clientId, Stream stream) { using (PooledBitReader reader = PooledBitReader.Get(stream)) @@ -535,8 +560,8 @@ internal static void HandleServerRPC(ulong clientId, Stream stream) ushort behaviourId = reader.ReadUInt16Packed(); ulong hash = reader.ReadUInt64Packed(); - if (SpawnManager.SpawnedObjects.ContainsKey(networkId)) - { + if (SpawnManager.SpawnedObjects.ContainsKey(networkId)) + { NetworkedBehaviour behaviour = SpawnManager.SpawnedObjects[networkId].GetBehaviourAtOrderIndex(behaviourId); if (behaviour != null) { @@ -545,7 +570,7 @@ internal static void HandleServerRPC(ulong clientId, Stream stream) } } } - + internal static void HandleServerRPCRequest(ulong clientId, Stream stream, string channelName, SecuritySendFlags security) { using (PooledBitReader reader = PooledBitReader.Get(stream)) @@ -555,8 +580,8 @@ internal static void HandleServerRPCRequest(ulong clientId, Stream stream, strin ulong hash = reader.ReadUInt64Packed(); ulong responseId = reader.ReadUInt64Packed(); - if (SpawnManager.SpawnedObjects.ContainsKey(networkId)) - { + if (SpawnManager.SpawnedObjects.ContainsKey(networkId)) + { NetworkedBehaviour behaviour = SpawnManager.SpawnedObjects[networkId].GetBehaviourAtOrderIndex(behaviourId); if (behaviour != null) { @@ -569,14 +594,14 @@ internal static void HandleServerRPCRequest(ulong clientId, Stream stream, strin responseWriter.WriteUInt64Packed(responseId); responseWriter.WriteObjectPacked(result); } - + InternalMessageSender.Send(clientId, MLAPIConstants.MLAPI_SERVER_RPC_RESPONSE, channelName, responseStream, security, SpawnManager.SpawnedObjects[networkId]); } } } } } - + internal static void HandleServerRPCResponse(ulong clientId, Stream stream) { using (PooledBitReader reader = PooledBitReader.Get(stream)) @@ -586,16 +611,16 @@ internal static void HandleServerRPCResponse(ulong clientId, Stream stream) if (ResponseMessageManager.ContainsKey(responseId)) { RpcResponseBase responseBase = ResponseMessageManager.GetByKey(responseId); - + ResponseMessageManager.Remove(responseId); - + responseBase.IsDone = true; responseBase.Result = reader.ReadObjectPacked(responseBase.Type); responseBase.IsSuccessful = true; } } } - + internal static void HandleClientRPC(ulong clientId, Stream stream) { using (PooledBitReader reader = PooledBitReader.Get(stream)) @@ -603,8 +628,8 @@ internal static void HandleClientRPC(ulong clientId, Stream stream) ulong networkId = reader.ReadUInt64Packed(); ushort behaviourId = reader.ReadUInt16Packed(); ulong hash = reader.ReadUInt64Packed(); - - if (SpawnManager.SpawnedObjects.ContainsKey(networkId)) + + if (SpawnManager.SpawnedObjects.ContainsKey(networkId)) { NetworkedBehaviour behaviour = SpawnManager.SpawnedObjects[networkId].GetBehaviourAtOrderIndex(behaviourId); if (behaviour != null) @@ -614,7 +639,7 @@ internal static void HandleClientRPC(ulong clientId, Stream stream) } } } - + internal static void HandleClientRPCRequest(ulong clientId, Stream stream, string channelName, SecuritySendFlags security) { using (PooledBitReader reader = PooledBitReader.Get(stream)) @@ -623,14 +648,14 @@ internal static void HandleClientRPCRequest(ulong clientId, Stream stream, strin ushort behaviourId = reader.ReadUInt16Packed(); ulong hash = reader.ReadUInt64Packed(); ulong responseId = reader.ReadUInt64Packed(); - - if (SpawnManager.SpawnedObjects.ContainsKey(networkId)) + + if (SpawnManager.SpawnedObjects.ContainsKey(networkId)) { NetworkedBehaviour behaviour = SpawnManager.SpawnedObjects[networkId].GetBehaviourAtOrderIndex(behaviourId); if (behaviour != null) { object result = behaviour.OnRemoteClientRPC(hash, clientId, stream); - + using (PooledBitStream responseStream = PooledBitStream.Get()) { using (PooledBitWriter responseWriter = PooledBitWriter.Get(responseStream)) @@ -638,14 +663,14 @@ internal static void HandleClientRPCRequest(ulong clientId, Stream stream, strin responseWriter.WriteUInt64Packed(responseId); responseWriter.WriteObjectPacked(result); } - + InternalMessageSender.Send(clientId, MLAPIConstants.MLAPI_CLIENT_RPC_RESPONSE, channelName, responseStream, security, null); } } } } } - + internal static void HandleClientRPCResponse(ulong clientId, Stream stream) { using (PooledBitReader reader = PooledBitReader.Get(stream)) @@ -655,18 +680,18 @@ internal static void HandleClientRPCResponse(ulong clientId, Stream stream) if (ResponseMessageManager.ContainsKey(responseId)) { RpcResponseBase responseBase = ResponseMessageManager.GetByKey(responseId); - + if (responseBase.ClientId != clientId) return; - + ResponseMessageManager.Remove(responseId); - + responseBase.IsDone = true; responseBase.Result = reader.ReadObjectPacked(responseBase.Type); responseBase.IsSuccessful = true; } } } - + internal static void HandleUnnamedMessage(ulong clientId, Stream stream) { CustomMessagingManager.InvokeUnnamedMessage(clientId, stream); diff --git a/MLAPI/NetworkedVar/SyncedVarAttribute.cs b/MLAPI/NetworkedVar/SyncedVarAttribute.cs new file mode 100644 index 0000000000..6e0c81f186 --- /dev/null +++ b/MLAPI/NetworkedVar/SyncedVarAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace MLAPI.NetworkedVar +{ + /// + /// SyncedVar attribute. Use this to automatically syncronize fields from the server to clients. + /// + [AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)] + public class SyncedVarAttribute : Attribute + { + /// + /// The channel to send changes on. + /// + public string Channel = "MLAPI_DEFAULT_MESSAGE"; + + /// + /// The maximum times per second this var will be synced. + /// A value of 0 will cause the variable to sync as soon as possible after being changed. + /// A value of less than 0 will cause the variable to sync only at once at spawn and not update again. + /// + public float SendTickrate = 0; + } +} diff --git a/MLAPI/NetworkedVar/SyncedVarContainer.cs b/MLAPI/NetworkedVar/SyncedVarContainer.cs new file mode 100644 index 0000000000..5cfbe3c9ce --- /dev/null +++ b/MLAPI/NetworkedVar/SyncedVarContainer.cs @@ -0,0 +1,68 @@ +using System.IO; +using System.Reflection; +using MLAPI.Serialization.Pooled; + +namespace MLAPI.NetworkedVar +{ + internal class SyncedVarContainer + { + internal SyncedVarAttribute attribute; + internal FieldInfo field; + internal object fieldInstance; + internal object value; + internal bool isDirty; + internal float lastSyncedTime; + + internal bool IsDirty() + { + if (attribute.SendTickrate >= 0 && (attribute.SendTickrate == 0 || NetworkingManager.Singleton.NetworkTime - lastSyncedTime >= (1f / attribute.SendTickrate))) + { + lastSyncedTime = NetworkingManager.Singleton.NetworkTime; + + object newValue = field.GetValue(fieldInstance); + object oldValue = value; + + if (newValue != oldValue || isDirty) + { + isDirty = true; + + value = newValue; + + return true; + } + } + + return false; + } + + internal void ResetDirty() + { + value = field.GetValue(fieldInstance); + isDirty = false; + } + + internal void WriteValue(Stream stream, bool checkDirty = true) + { + using (PooledBitWriter writer = PooledBitWriter.Get(stream)) + { + if (checkDirty) + { + // Trigger a value update + IsDirty(); + } + + writer.WriteObjectPacked(value); + } + } + + internal void ReadValue(Stream stream) + { + using (PooledBitReader reader = PooledBitReader.Get(stream)) + { + value = reader.ReadObjectPacked(field.FieldType); + + field.SetValue(fieldInstance, value); + } + } + } +} diff --git a/MLAPI/SceneManagement/NetworkSceneManager.cs b/MLAPI/SceneManagement/NetworkSceneManager.cs index b66b7f0a07..8e79272c1c 100644 --- a/MLAPI/SceneManagement/NetworkSceneManager.cs +++ b/MLAPI/SceneManagement/NetworkSceneManager.cs @@ -285,6 +285,7 @@ private static void OnSceneUnloadServer(Guid switchSceneGuid) if (NetworkingManager.Singleton.NetworkConfig.EnableNetworkedVar) { newSceneObjects[i].WriteNetworkedVarData(stream, NetworkingManager.Singleton.ConnectedClientsList[j].ClientId); + newSceneObjects[i].WriteSyncedVarData(stream, NetworkingManager.Singleton.ConnectedClientsList[j].ClientId); } } } diff --git a/MLAPI/Spawning/SpawnManager.cs b/MLAPI/Spawning/SpawnManager.cs index 1bac759839..06b0c298e8 100644 --- a/MLAPI/Spawning/SpawnManager.cs +++ b/MLAPI/Spawning/SpawnManager.cs @@ -323,8 +323,11 @@ internal static void SpawnNetworkedObjectLocally(NetworkedObject netObject, ulon throw new SpawnStateException("Object is already spawned"); } - - if (readNetworkedVar && NetworkingManager.Singleton.NetworkConfig.EnableNetworkedVar) netObject.SetNetworkedVarData(dataStream); + if (readNetworkedVar && NetworkingManager.Singleton.NetworkConfig.EnableNetworkedVar) + { + netObject.SetNetworkedVarData(dataStream); + netObject.SetSyncedVarData(dataStream); + } netObject.IsSpawned = true; @@ -465,6 +468,7 @@ internal static void WriteSpawnCallForObject(Serialization.BitStream stream, ulo if (NetworkingManager.Singleton.NetworkConfig.EnableNetworkedVar) { netObject.WriteNetworkedVarData(stream, clientId); + netObject.WriteSyncedVarData(stream, clientId); } if (payload != null) stream.CopyFrom(payload); diff --git a/docs/_data/docs.yml b/docs/_data/docs.yml index cb2f313632..9a73f0056e 100644 --- a/docs/_data/docs.yml +++ b/docs/_data/docs.yml @@ -13,6 +13,7 @@ - object-behaviour-relation - modularity - networkedvar + - syncedvar - scene-management - object-ownership - object-visibility diff --git a/docs/_docs/the-basics/syncedvar.md b/docs/_docs/the-basics/syncedvar.md new file mode 100644 index 0000000000..f8a36e5a3a --- /dev/null +++ b/docs/_docs/the-basics/syncedvar.md @@ -0,0 +1,23 @@ +--- +title: SyncedVar +permalink: /wiki/syncedvar/ +--- + +SyncedVars are simple ways to have syncronized fields in NetworkedBehaviours. SyncedVars are similar to NetworkedVars but have a few differences: + +1. They are slower +2. They are less customizable +3. They only sync from server to client +4. They require less code +5. They support serialization + +### Example +Creating a SyncedVar is as easy as creating an attribute on the field. Note that properties are not supported. + +```csharp +[SyncedVar] +public float mySyncedFloat = 5f; +``` + +### Single Sync Values +If you want values to be synced only once (at spawn), the send rate can be set to a negative value. \ No newline at end of file