From dfd696557ca9e8a235a1b00f0fc070f83b432dc4 Mon Sep 17 00:00:00 2001 From: Ruslan Pyrch Date: Fri, 6 Jun 2025 23:06:58 +0300 Subject: [PATCH 01/22] update to latest LES: 4303e6b00eea3614dd2bed67e819b0451544420c --- .../Internal/RemoteCallPacket.cs | 19 ++++++++----------- .../Internal/ServerStateData.cs | 16 +++++++++++----- .../LiteEntitySystem/ServerEntityManager.cs | 13 ++++++++----- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs b/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs index e5cbcad..8ab74a7 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs @@ -18,12 +18,15 @@ internal sealed class RemoteCallPacket public NetPlayer OnlyForPlayer; public ExecuteFlags ExecuteFlags; - public unsafe int TotalSize => sizeof(RPCHeader) + Header.ByteCount; + public int TotalSize => RpcDeltaCompressor.MaxDeltaSize + Header.ByteCount; public const int ReserverdRPCsCount = 3; public const ushort NewRPCId = 0; public const ushort ConstructRPCId = 1; public const ushort DestroyRPCId = 2; + + //can be static because doesnt use any buffers + private static DeltaCompressor RpcDeltaCompressor = new(Utils.SizeOfStruct()); public static void InitReservedRPCs(List rpcCache) { @@ -42,21 +45,15 @@ public bool AllowToSendForPlayer(byte forPlayerId, byte entityOwnerId) return false; } - - public unsafe void WriteTo(byte* resultData, ref int position) - { - *(RPCHeader*)(resultData + position) = Header; - fixed (byte* rpcData = Data) - RefMagic.CopyBlock(resultData + sizeof(RPCHeader) + position, rpcData, Header.ByteCount); - position += TotalSize; - } - public unsafe void WriteToDeltaCompressed(ref DeltaCompressor deltaCompressor, byte* resultData, ref int position, RPCHeader prevHeader) + public unsafe int WriteTo(byte* resultData, ref int position, ref RPCHeader prevHeader) { - int headerEncodedSize = deltaCompressor.Encode(ref prevHeader, ref Header, new Span(resultData + position, sizeof(RPCHeader))); + int headerEncodedSize = RpcDeltaCompressor.Encode(ref prevHeader, ref Header, new Span(resultData + position, RpcDeltaCompressor.MaxDeltaSize)); fixed (byte* rpcData = Data) RefMagic.CopyBlock(resultData + headerEncodedSize + position, rpcData, Header.ByteCount); position += headerEncodedSize + Header.ByteCount; + prevHeader = Header; + return headerEncodedSize + Header.ByteCount; } public void Init(NetPlayer targetPlayer, InternalEntity entity, ushort tick, ushort byteCount, ushort rpcId, ExecuteFlags executeFlags) diff --git a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs index 8a9db16..cbaf90d 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs @@ -67,6 +67,7 @@ internal class ServerStateData public int DataSize => _dataSize; private readonly HashSet _syncablesSet = new(); + private DeltaCompressor _rpcDeltaCompressor = new (Utils.SizeOfStruct()); public unsafe void GetDiagnosticData( InternalEntity[] entityDict, @@ -235,13 +236,17 @@ public unsafe void ExecuteRpcs(ClientEntityManager entityManager, ushort minimal { while (_rpcReadPos < _rpcEndPos) { - if (_rpcEndPos - _rpcReadPos < sizeof(RPCHeader)) + if (_rpcEndPos - _rpcReadPos < _rpcDeltaCompressor.MinDeltaSize) { Logger.LogError("Broken rpcs sizes?"); break; } - var header = *(RPCHeader*)(rawData + _rpcReadPos); + RPCHeader header = new(); + int encodedSize = _rpcDeltaCompressor.Decode( + new ReadOnlySpan(rawData + _rpcReadPos, _rpcDeltaCompressor.MaxDeltaSize), + new Span(&header, sizeof(RPCHeader))); + if (!firstSync) { if (Utils.SequenceDiff(header.Tick, entityManager.ServerTick) > 0) @@ -252,14 +257,14 @@ public unsafe void ExecuteRpcs(ClientEntityManager entityManager, ushort minimal if (Utils.SequenceDiff(header.Tick, minimalTick) <= 0) { - _rpcReadPos += header.ByteCount + sizeof(RPCHeader); + _rpcReadPos += header.ByteCount + encodedSize; //Logger.Log($"Skip rpc. Entity: {header.EntityId}. Tick {header.Tick} <= MinimalTick: {minimalTick}. Id: {header.Id}. StateATick: {entityManager.RawServerTick}. StateBTick: {entityManager.RawTargetServerTick}"); continue; } } - int rpcDataStart = _rpcReadPos + sizeof(RPCHeader); - _rpcReadPos += header.ByteCount + sizeof(RPCHeader); + int rpcDataStart = _rpcReadPos + encodedSize; + _rpcReadPos += encodedSize + header.ByteCount; //Logger.Log($"Executing rpc. Entity: {header.EntityId}. Tick {header.Tick}. Id: {header.Id}"); var entity = entityManager.EntitiesDict[header.EntityId]; @@ -339,6 +344,7 @@ public void Reset(ushort tick) _partMtu = 0; _nullEntitiesCount = 0; _rpcReadPos = 0; + _rpcDeltaCompressor.Init(); } public unsafe bool ReadBaseline(BaselineDataHeader header, byte* rawData, int fullSize) diff --git a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs index d65ec5f..6ae0aa3 100644 --- a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs @@ -34,7 +34,7 @@ public sealed class ServerEntityManager : EntityManager private readonly byte[] _inputDecodeBuffer = new byte[NetConstants.MaxUnreliableDataSize]; private readonly NetDataReader _requestsReader = new(); private readonly Queue _pendingRPCs = new(); - + private NetPlayer _syncForPlayer; private int _maxDataSize; @@ -598,6 +598,7 @@ protected override unsafe void OnLogicTick() var player = _netPlayers.GetByIndex(pidx); _syncForPlayer = null; int rpcSize = 0; + RPCHeader prevRpcHeader = new(); if (player.State == NetPlayerState.RequestBaseline) { int originalLength = 0; @@ -622,7 +623,7 @@ protected override unsafe void OnLogicTick() continue; var entity = EntitiesDict[rpcNode.Header.EntityId]; if(rpcNode.AllowToSendForPlayer(player.Id, entity.OwnerId)) - rpcNode.WriteTo(packetBuffer, ref originalLength); + rpcNode.WriteTo(packetBuffer, ref originalLength, ref prevRpcHeader); } rpcSize = originalLength; } @@ -630,7 +631,7 @@ protected override unsafe void OnLogicTick() { foreach (var rpcNode in _pendingRPCs) if(ShouldSendRPC(rpcNode, player)) - rpcNode.WriteTo(packetBuffer, ref originalLength); + rpcNode.WriteTo(packetBuffer, ref originalLength, ref prevRpcHeader); rpcSize = originalLength; foreach (var e in GetEntities()) @@ -686,8 +687,9 @@ protected override unsafe void OnLogicTick() if(!ShouldSendRPC(rpcNode, player)) continue; - rpcSize += rpcNode.TotalSize; - rpcNode.WriteTo(packetBuffer, ref writePosition); + //use another approach to sum size because writePosition can be edited by CheckOverflow + //!!! + rpcSize += rpcNode.WriteTo(packetBuffer, ref writePosition, ref prevRpcHeader); CheckOverflowAndSend(player, header, packetBuffer, ref writePosition, maxPartSize); if(player.State == NetPlayerState.RequestBaseline) break; @@ -761,6 +763,7 @@ protected override unsafe void OnLogicTick() _netPlayers.GetByIndex(0).Peer.TriggerSend(); //Logger.Log($"ServerSendTick: {_tick}"); + //Logger.Log($"SendTotal: {RemoteCallPacket.TotalWriteSizeNormal}, SendDelta: {RemoteCallPacket.TotalWriteSizeDelta}"); return; void CheckOverflowAndSend(NetPlayer player, DiffPartHeader *header, byte* packetBuffer, ref int writePosition, int maxPartSize) { From e438e70581c2c8939f7c9697f8e14f4ec8dba8a5 Mon Sep 17 00:00:00 2001 From: Ruslan Pyrch Date: Wed, 11 Jun 2025 17:23:38 +0300 Subject: [PATCH 02/22] mini update --- .../Internal/ServerStateData.cs | 5 +++++ .../LiteEntitySystem/RPCRegistrator.cs | 2 -- Assets/Plugins/LiteEntitySystem/SpanReader.cs | 11 ++++++++++ Assets/Plugins/LiteEntitySystem/SpanWriter.cs | 21 +++++++++++++++++++ .../Plugins/LiteEntitySystem/SyncableField.cs | 1 - 5 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs index cbaf90d..7a899d4 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs @@ -68,6 +68,11 @@ internal class ServerStateData private readonly HashSet _syncablesSet = new(); private DeltaCompressor _rpcDeltaCompressor = new (Utils.SizeOfStruct()); + + public ServerStateData() + { + _rpcDeltaCompressor.Init(); + } public unsafe void GetDiagnosticData( InternalEntity[] entityDict, diff --git a/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs b/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs index e42f400..5e45c9c 100644 --- a/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs +++ b/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs @@ -116,7 +116,6 @@ internal static void CheckTarget(object ent, object target) /// /// Variable to bind /// Action that will be called when variable changes by sync - /// order of execution public void BindOnChange(ref SyncVar syncVar, Action onChangedAction) where T : unmanaged where TEntity : InternalEntity { _fields[syncVar.FieldId].OnSync = RemoteCall.CreateMCD(onChangedAction); @@ -128,7 +127,6 @@ public void BindOnChange(ref SyncVar syncVar, Action /// Target entity for binding /// Variable to bind /// Action that will be called when variable changes by sync - /// order of execution public void BindOnChange(TEntity self, ref SyncVar syncVar, Action onChangedAction) where T : unmanaged where TEntity : InternalEntity { CheckTarget(self, onChangedAction.Target); diff --git a/Assets/Plugins/LiteEntitySystem/SpanReader.cs b/Assets/Plugins/LiteEntitySystem/SpanReader.cs index a67fc01..74fe0c5 100644 --- a/Assets/Plugins/LiteEntitySystem/SpanReader.cs +++ b/Assets/Plugins/LiteEntitySystem/SpanReader.cs @@ -27,6 +27,17 @@ public void Get(out T result, Func constructor) where T : class, ISpanSeri result = constructor(); result.Deserialize(ref this); } + + public void GetStruct(out T result) where T : unmanaged => result = GetStruct(); + + public unsafe T GetStruct() where T : unmanaged + { + T value; + fixed (byte* rawData = RawData) + value = *(T*)(rawData+Position); + Position += sizeof(T); + return value; + } public void Get(out byte result) => result = GetByte(); public void Get(out sbyte result) => result = (sbyte)GetByte(); diff --git a/Assets/Plugins/LiteEntitySystem/SpanWriter.cs b/Assets/Plugins/LiteEntitySystem/SpanWriter.cs index b5dbb47..319a793 100644 --- a/Assets/Plugins/LiteEntitySystem/SpanWriter.cs +++ b/Assets/Plugins/LiteEntitySystem/SpanWriter.cs @@ -99,6 +99,12 @@ public void Put(byte[] data) new ReadOnlySpan(data).CopyTo(RawData.Slice(Position, data.Length)); Position += data.Length; } + + public void Put(ReadOnlySpan data) + { + data.CopyTo(RawData.Slice(Position, data.Length)); + Position += data.Length; + } public void PutSBytesWithLength(sbyte[] data, int offset, ushort length) { @@ -113,6 +119,21 @@ public void PutBytesWithLength(byte[] data, int offset, ushort length) new ReadOnlySpan(data, offset, length).CopyTo(RawData.Slice(Position + 2, length)); Position += 2 + length; } + + public unsafe void PutStruct(T data) where T : unmanaged + { + Put(sizeof(T)); + fixed (byte* rawData = RawData) + *(T*) (rawData + Position) = data; + Position += sizeof(T); + } + + public void PutBytesWithLength(ReadOnlySpan data) + { + Put(data.Length); + data.CopyTo(RawData.Slice(Position + 2, data.Length)); + Position += 2 + data.Length; + } public void PutArray(T[] arr, int sz) where T : unmanaged { diff --git a/Assets/Plugins/LiteEntitySystem/SyncableField.cs b/Assets/Plugins/LiteEntitySystem/SyncableField.cs index fbbaef5..2cbd0a8 100644 --- a/Assets/Plugins/LiteEntitySystem/SyncableField.cs +++ b/Assets/Plugins/LiteEntitySystem/SyncableField.cs @@ -30,7 +30,6 @@ public abstract class SyncableField : InternalBaseClass private InternalEntity _parentEntity; private ExecuteFlags _executeFlags; - internal int FieldId; internal ushort RPCOffset; /// From 115507b5b5373a1359aba14916642ad9714254ce Mon Sep 17 00:00:00 2001 From: RevenantX Date: Fri, 13 Jun 2025 14:39:16 +0300 Subject: [PATCH 03/22] fixes --- .../LiteEntitySystem/ClientEntityManager.cs | 49 +++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 86af30b..82a9858 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -140,7 +140,6 @@ public sealed class ClientEntityManager : EntityManager private readonly HashSet _changedEntities = new(); private readonly CircularBuffer _storedInputHeaders = new(InputBufferSize); private InternalEntity[] _entitiesToRemove = new InternalEntity[64]; - private readonly AVLTree _tempEntityTree = new(); private int _entitiesToRemoveCount; private ServerSendRate _serverSendRate; @@ -580,6 +579,23 @@ private unsafe void GoToNextState() } } UpdateMode = UpdateMode.Normal; + + //update local interpolated position + foreach (var entity in AliveEntities) + { + if(entity.IsLocal || !entity.IsLocalControlled) + continue; + + ref var classData = ref ClassDataDict[entity.ClassId]; + for(int i = 0; i < classData.InterpolatedCount; i++) + { + fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(entity)) + { + ref var field = ref classData.Fields[i]; + field.TypeProcessor.WriteTo(entity, field.Offset, currentDataPtr + field.FixedOffset); + } + } + } } internal override void OnEntityDestroyed(InternalEntity e) @@ -601,25 +617,24 @@ protected override unsafe void OnLogicTick() ServerTick = Utils.LerpSequence(_stateA.Tick, _stateB.Tick, (float)(_timer/_lerpTime)); //restore actual field values - _tempEntityTree.Clear(); foreach (var entity in AliveEntities) { + if (!entity.IsLocal && !entity.IsLocalControlled) + continue; + ref var classData = ref ClassDataDict[entity.ClassId]; if(classData.InterpolatedCount == 0) continue; - if (entity.IsLocal || entity.IsLocalControlled) + + //save data for interpolation before update + fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(entity)) { - //save data for interpolation before update - fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(entity)) + //restore previous + for (int i = 0; i < classData.InterpolatedCount; i++) { - //restore previous - for (int i = 0; i < classData.InterpolatedCount; i++) - { - var field = classData.Fields[i]; - field.TypeProcessor.SetFrom(entity, field.Offset, currentDataPtr + field.FixedOffset); - } + ref var field = ref classData.Fields[i]; + field.TypeProcessor.SetFrom(entity, field.Offset, currentDataPtr + field.FixedOffset); } - _tempEntityTree.Add(entity); } } } @@ -647,16 +662,22 @@ protected override unsafe void OnLogicTick() if (_stateB != null) { //save interpolation data - foreach (var entity in _tempEntityTree) + foreach (var entity in AliveEntities) { + if (!entity.IsLocal && !entity.IsLocalControlled) + continue; + ref var classData = ref ClassDataDict[entity.ClassId]; + if(classData.InterpolatedCount == 0) + continue; + fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(entity), prevDataPtr = classData.ClientInterpolatedPrevData(entity)) { RefMagic.CopyBlock(prevDataPtr, currentDataPtr, (uint)classData.InterpolatedFieldsSize); for(int i = 0; i < classData.InterpolatedCount; i++) { - var field = classData.Fields[i]; + ref var field = ref classData.Fields[i]; field.TypeProcessor.WriteTo(entity, field.Offset, currentDataPtr + field.FixedOffset); } } From a67ad80dfab7acd760c99120f05647f7e75518e7 Mon Sep 17 00:00:00 2001 From: Ruslan Pyrch Date: Fri, 13 Jun 2025 22:06:46 +0300 Subject: [PATCH 04/22] upd --- .../LiteEntitySystem/ClientEntityManager.cs | 4 +- .../Plugins/LiteEntitySystem/EntityManager.cs | 10 ++- .../LiteEntitySystem/ILPart/RefMagic.dll | Bin 2560 -> 3584 bytes .../Internal/EntityFieldInfo.cs | 2 +- .../Internal/InternalEntity.cs | 4 +- .../Internal/ServerStateData.cs | 2 +- .../Internal/StateSerializer.cs | 3 +- .../Internal/ValueTypeProcessor.cs | 81 +++++++----------- Assets/Plugins/LiteEntitySystem/SyncVar.cs | 36 ++++---- Assets/Plugins/LiteEntitySystem/Utils.cs | 2 +- 10 files changed, 70 insertions(+), 74 deletions(-) diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 82a9858..8b353a0 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -546,7 +546,7 @@ private unsafe void GoToNextState() ref var field = ref rollbackFields[i]; if (field.FieldType == FieldType.SyncableSyncVar) { - var syncableField = RefMagic.RefFieldValue(entity, field.Offset); + var syncableField = RefMagic.GetFieldValue(entity, field.Offset); field.TypeProcessor.SetFrom(syncableField, field.SyncableSyncVarOffset, predictedData + field.PredictedOffset); } else @@ -556,7 +556,7 @@ private unsafe void GoToNextState() } } for (int i = 0; i < classData.SyncableFields.Length; i++) - RefMagic.RefFieldValue(entity, classData.SyncableFields[i].Offset).OnRollback(); + RefMagic.GetFieldValue(entity, classData.SyncableFields[i].Offset).OnRollback(); entity.OnRollback(); } diff --git a/Assets/Plugins/LiteEntitySystem/EntityManager.cs b/Assets/Plugins/LiteEntitySystem/EntityManager.cs index e57911b..555259a 100644 --- a/Assets/Plugins/LiteEntitySystem/EntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/EntityManager.cs @@ -91,9 +91,15 @@ public abstract class EntityManager /// /// Maximum synchronized (without LocalOnly) entities /// - public const int MaxSyncedEntityCount = 32767; + public const int MaxSyncedEntityCount = 64000; - public const int MaxEntityCount = MaxSyncedEntityCount * 2; + //because 0 - invalid id + public const int MaxEntityCount = ushort.MaxValue - 1; + + /// + /// Maximum count of predicted local entities + /// + public const int MaxLocalEntityCount = MaxEntityCount - MaxSyncedEntityCount; public const byte ServerPlayerId = 0; diff --git a/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.dll b/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.dll index 5c12d9f11201f270c1f65a7244fffa43290145db..92da363c18253963e3f19ac6f8ea968cbe9314c4 100644 GIT binary patch literal 3584 zcmeHJU1%It6#nk)Pj@#q-6U;nY)q#nl^WxaG!=hnB)?5lk{Yv{SVS?ivp2~!voq_= z?6zB>rVrABAQqwcqFCvppbr&_Aow7K()Ou+Q4spj7oP+ld{9U{_s(o~o5l*IFG6o} z&pqcm=bky|+;eAk>a9-@0T89Rwgz0GRg6JNA52zA-MIVvMqEzb+;fFZ-rSR2GCj5E zI*YDePz~L-9be6H)h*eoX{)2JXVij|=i1h-sjkrV^cXP76kJ=LT&h0p4wO9&tN}<7 zq6Fv{`)E~Z4$=hDk|&*rTa4<8c@n}s5e$APOvQg~4MhRbI81&s0lPUJ1fJMj^I%j3 z5_J{{k`LF(n$J)ABwrFnq^r-BBso-IUURwS7-0;91dH4@1Vr#Kp$|1yD1R|TYzkty zqXJ7?04BhP7{jYc;MQuA;n;iBSG&4rYQSnz!5Il2zZYC>jgzTEB-MRtgDM$8sHUh= z-J9>EBBr|0(i;K!kVAUCa&p3$25o2Kvq&VjlknHx zezXR)6vs^NLDdnE>7jTyyqnhcV^f#Nw7L-FNshAYu`B3UqTx`X+p){zRMiXv?(wuS*qC5<@Wn8!E z{ww$;EkOVSo5kOX-=k8PA^D|uGYi5?t8bVX!W zKd#G6YuFKd779Bli))fkhHHadcT!!iQE!O#DavCfJtdI0g>q9UH<8RJjwsC-nrXHY z7pF7ppxGv4OyU{pxwasrgHukvWbxOKsdzpwXfq|-Hw#=FaSBD#;%a)9&zR$`NABp}A*;KK+&@I$5PrgO zm+#w>;YP_NU)nTx3#KQwd8mRl>|gg`)-H3`Trl;V#YgCaw9yiCx>e%YN|A4@#Dwj0 z*D0>GLrd}o>41BUTZuCIS#!N~8==kqW2?O<6Yomn=E z-TWf^oo{VIZD^ENo-N8dsH$l?vqC+$;X0MYjQnT{k>7c-W_q=QOlarO&bNz npew;;*9(iaz2ry!daKz#<{oJU)ZpjjmoGn3uaBMmPb%;aJOc&o delta 607 zcmX|8&r2Io5dLO2iMvVANHjzYve5)2>Y{|w9$JZ2YO6@CKZ2kltJz2-Bx2HwmJ;cu zcrrW)1@RvcJd}Vfz16dx3cVG2>(O3%=^;9AO&oYL-+bS^@6EE6NTuueWB>YPw~c1} zh7$FW5AZOd+>_(+>RH4+E_wya|gw2wj?YqIhH;<4W7roOn$Xv-=7z2KDZ1bO*KFgv};!f z!Q1PRUvIPkDSEzT-2B0EQBz{08VxB%TA#`sXxkl4 C9$x?e diff --git a/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs b/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs index 549b7ea..c43a406 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs @@ -72,7 +72,7 @@ public unsafe bool ReadField( RefMagic.CopyBlock(predictedData + PredictedOffset, rawData, Size); if (FieldType == FieldType.SyncableSyncVar) { - var syncableField = RefMagic.RefFieldValue(entity, Offset); + var syncableField = RefMagic.GetFieldValue(entity, Offset); TypeProcessor.SetFrom(syncableField, SyncableSyncVarOffset, rawData); } else diff --git a/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs b/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs index 4f22cef..21bf3f6 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs @@ -211,7 +211,7 @@ internal void RegisterRpcInternal() } else { - var syncableField = RefMagic.RefFieldValue(this, field.Offset); + var syncableField = RefMagic.GetFieldValue(this, field.Offset); field.TypeProcessor.InitSyncVar(syncableField, field.SyncableSyncVarOffset, onChangeTarget, (ushort)i); } } @@ -231,7 +231,7 @@ internal void RegisterRpcInternal() for (int i = 0; i < classData.SyncableFields.Length; i++) { ref var syncFieldInfo = ref classData.SyncableFields[i]; - var syncField = RefMagic.RefFieldValue(this, syncFieldInfo.Offset); + var syncField = RefMagic.GetFieldValue(this, syncFieldInfo.Offset); syncField.Init(this, syncFieldInfo.Flags); if (rpcCache == null) //classData.RemoteCallsClient != null { diff --git a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs index 7a899d4..579d8b3 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs @@ -317,7 +317,7 @@ public unsafe void ExecuteRpcs(ClientEntityManager entityManager, ushort minimal } else { - var syncableField = RefMagic.RefFieldValue(entity, rpcFieldInfo.SyncableOffset); + var syncableField = RefMagic.GetFieldValue(entity, rpcFieldInfo.SyncableOffset); if (_syncablesSet.Add(syncableField)) { syncableField.BeforeReadRPC(); diff --git a/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs b/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs index 4ce8e74..0bcc191 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs @@ -140,8 +140,7 @@ public void MakeConstructedRPC(NetPlayer player) { var syncableFields = _entity.ClassData.SyncableFields; for (int i = 0; i < syncableFields.Length; i++) - RefMagic.RefFieldValue(_entity, syncableFields[i].Offset) - .OnSyncRequested(); + RefMagic.GetFieldValue(_entity, syncableFields[i].Offset).OnSyncRequested(); _entity.OnSyncRequested(); } catch (Exception e) diff --git a/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs b/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs index a15a9a9..e348fb3 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs @@ -30,82 +30,70 @@ public ValueTypeProcessor() : base(sizeof(T)) { } internal override void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => throw new Exception($"This type: {typeof(T)} can't be interpolated"); - internal override void InitSyncVar(InternalBaseClass obj, int offset, InternalEntity entity, ushort fieldId) => - RefMagic.RefFieldValue>(obj, offset).Init(entity, fieldId); - - internal override void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) + internal override void InitSyncVar(InternalBaseClass obj, int offset, InternalEntity entity, ushort fieldId) { - ref var a = ref RefMagic.RefFieldValue>(obj, offset); - *(T*)tempHistory = a; - a.SetDirect(*(T*)historyA); + var sv = RefMagic.GetFieldValue>(obj, offset); + sv.Init(entity, fieldId); + RefMagic.SetFieldValue(obj, offset, sv); } + internal override void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => + RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, *(T*)historyA, out *(T*)tempHistory); + internal override void SetFrom(InternalBaseClass obj, int offset, byte* data) => - RefMagic.RefFieldValue>(obj, offset).SetDirect(*(T*)data); + RefMagic.SyncVarSetDirect>(obj, offset, *(T*)data); internal override bool SetFromAndSync(InternalBaseClass obj, int offset, byte* data) => - RefMagic.RefFieldValue>(obj, offset).SetFromAndSync(data); + RefMagic.SyncVarSetFromAndSync>(obj, offset, ref *(T*)data); internal override void WriteTo(InternalBaseClass obj, int offset, byte* data) => - *(T*)data = RefMagic.RefFieldValue>(obj, offset); + *(T*)data = RefMagic.GetFieldValue>(obj, offset); internal override int GetHashCode(InternalBaseClass obj, int offset) => - RefMagic.RefFieldValue>(obj, offset).GetHashCode(); + RefMagic.GetFieldValue>(obj, offset).GetHashCode(); internal override string ToString(InternalBaseClass obj, int offset) => - RefMagic.RefFieldValue>(obj, offset).ToString(); + RefMagic.GetFieldValue>(obj, offset).ToString(); } internal class ValueTypeProcessorInt : ValueTypeProcessor { internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.RefFieldValue>(obj, offset).SetDirect(Utils.Lerp(*(int*)prev, *(int*)current, fTimer)); + RefMagic.SyncVarSetDirect>(obj, offset, Utils.Lerp(*(int*)prev, *(int*)current, fTimer)); - internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) - { - ref var a = ref RefMagic.RefFieldValue>(obj, offset); - *(int*)tempHistory = a; - a.SetDirect(Utils.Lerp(*(int*)historyA, *(int*)historyB, lerpTime)); - } + internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => + RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, + Utils.Lerp(*(int*)historyA, *(int*)historyB, lerpTime), out *(int*)tempHistory); } internal class ValueTypeProcessorLong : ValueTypeProcessor { internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.RefFieldValue>(obj, offset).SetDirect(Utils.Lerp(*(long*)prev, *(long*)current, fTimer)); + RefMagic.SyncVarSetDirect>(obj, offset, Utils.Lerp(*(long*)prev, *(long*)current, fTimer)); - internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) - { - ref var a = ref RefMagic.RefFieldValue>(obj, offset); - *(long*)tempHistory = a; - a.SetDirect(Utils.Lerp(*(long*)historyA, *(long*)historyB, lerpTime)); - } + internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => + RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, + Utils.Lerp(*(long*)historyA, *(long*)historyB, lerpTime), out *(long*)tempHistory); } internal class ValueTypeProcessorFloat : ValueTypeProcessor { internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.RefFieldValue>(obj, offset).SetDirect(Utils.Lerp(*(float*)prev, *(float*)current, fTimer)); + RefMagic.SyncVarSetDirect>(obj, offset, Utils.Lerp(*(float*)prev, *(float*)current, fTimer)); - internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) - { - ref var a = ref RefMagic.RefFieldValue>(obj, offset); - *(float*)tempHistory = a; - a.SetDirect(Utils.Lerp(*(float*)historyA, *(float*)historyB, lerpTime)); - } + internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => + RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, + Utils.Lerp(*(float*)historyA, *(float*)historyB, lerpTime), out *(float*)tempHistory); } internal class ValueTypeProcessorDouble : ValueTypeProcessor { internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.RefFieldValue>(obj, offset).SetDirect(Utils.Lerp(*(double*)prev, *(double*)current, fTimer)); + RefMagic.SyncVarSetDirect>(obj, offset, Utils.Lerp(*(double*)prev, *(double*)current, fTimer)); - internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) - { - ref var a = ref RefMagic.RefFieldValue>(obj, offset); - *(double*)tempHistory = a; - a.SetDirect(Utils.Lerp(*(double*)historyA, *(double*)historyB, lerpTime)); - } + internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => + RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, + Utils.Lerp(*(double*)historyA, *(double*)historyB, lerpTime), out *(double*)tempHistory); } internal unsafe class UserTypeProcessor : ValueTypeProcessor where T : unmanaged @@ -113,14 +101,11 @@ internal unsafe class UserTypeProcessor : ValueTypeProcessor where T : unm private readonly InterpolatorDelegateWithReturn _interpDelegate; internal override void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.RefFieldValue>(obj, offset).SetDirect(_interpDelegate?.Invoke(*(T*)prev, *(T*)current, fTimer) ?? *(T*)prev); - - internal override void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) - { - ref var a = ref RefMagic.RefFieldValue>(obj, offset); - *(T*)tempHistory = a; - a.SetDirect(_interpDelegate?.Invoke(*(T*)historyA, *(T*)historyB, lerpTime) ?? *(T*)historyA); - } + RefMagic.SyncVarSetDirect>(obj, offset, _interpDelegate?.Invoke(*(T*)prev, *(T*)current, fTimer) ?? *(T*)prev); + + internal override void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => + RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, + _interpDelegate?.Invoke(*(T*)historyA, *(T*)historyB, lerpTime) ?? *(T*)historyA, out *(T*)tempHistory); public UserTypeProcessor(InterpolatorDelegateWithReturn interpolationDelegate) => _interpDelegate = interpolationDelegate; diff --git a/Assets/Plugins/LiteEntitySystem/SyncVar.cs b/Assets/Plugins/LiteEntitySystem/SyncVar.cs index 114883e..3826174 100644 --- a/Assets/Plugins/LiteEntitySystem/SyncVar.cs +++ b/Assets/Plugins/LiteEntitySystem/SyncVar.cs @@ -69,13 +69,32 @@ public class SyncVarFlags : Attribute } [StructLayout(LayoutKind.Sequential)] - public struct SyncVar : IEquatable, IEquatable> where T : unmanaged + public struct SyncVar : ISyncVar, IEquatable, IEquatable> where T : unmanaged { private T _value; internal ushort FieldId; internal InternalEntity Container; - internal void SetDirect(T value) => _value = value; + void ISyncVar.SvSetDirect(T value) => _value = value; + + void ISyncVar.SvSetDirectAndStorePrev(T value, out T prevValue) + { + prevValue = _value; + _value = value; + } + + bool ISyncVar.SvSetFromAndSync(ref T value) + { + if (!Utils.FastEquals(ref _value, ref value)) + { + // ReSharper disable once SwapViaDeconstruction + var tmp = _value; + _value = value; + value = tmp; + return true; + } + return false; + } public T Value { @@ -95,19 +114,6 @@ internal void Init(InternalEntity container, ushort fieldId) Container?.EntityManager.EntityFieldChanged(Container, FieldId, ref _value); } - internal unsafe bool SetFromAndSync(byte* data) - { - if (!Utils.FastEquals(ref _value, data)) - { - var temp = _value; - _value = *(T*)data; - *(T*)data = temp; - return true; - } - _value = *(T*)data; - return false; - } - public static implicit operator T(SyncVar sv) => sv._value; public override string ToString() => _value.ToString(); diff --git a/Assets/Plugins/LiteEntitySystem/Utils.cs b/Assets/Plugins/LiteEntitySystem/Utils.cs index 73ca22c..81ef3c3 100644 --- a/Assets/Plugins/LiteEntitySystem/Utils.cs +++ b/Assets/Plugins/LiteEntitySystem/Utils.cs @@ -305,7 +305,7 @@ static Utils() var field = typeof(TestOffset).GetField("TestValue"); int offset = GetFieldOffset(field); var to = new TestOffset(); - if (RefMagic.RefFieldValue(to, offset) != to.TestValue) + if (RefMagic.GetFieldValue(to, offset) != to.TestValue) Logger.LogError("Unknown native field offset"); } } From 715296c321075888e5957f9fceeb5787162e9e0c Mon Sep 17 00:00:00 2001 From: Ruslan Pyrch Date: Mon, 16 Jun 2025 20:28:58 +0300 Subject: [PATCH 05/22] some smoothness improvs --- .../LiteEntitySystem/ClientEntityManager.cs | 97 +++++++++++-------- .../Internal/StateSerializer.cs | 13 ++- .../LiteEntitySystem/ServerEntityManager.cs | 11 ++- 3 files changed, 70 insertions(+), 51 deletions(-) diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 8b353a0..5ba4855 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -140,6 +140,7 @@ public sealed class ClientEntityManager : EntityManager private readonly HashSet _changedEntities = new(); private readonly CircularBuffer _storedInputHeaders = new(InputBufferSize); private InternalEntity[] _entitiesToRemove = new InternalEntity[64]; + private readonly Queue _entitiesToRollback = new(); private int _entitiesToRemoveCount; private ServerSendRate _serverSendRate; @@ -889,8 +890,54 @@ internal unsafe void ReadNewRPC(ushort entityId, byte* rawData) } } - private void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData) + private unsafe void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData) { + if (_entitiesToRollback.Count > 0) + { + //fast forward new but predicted entities + UpdateMode = UpdateMode.PredictionRollback; + for(int cmdNum = Utils.SequenceDiff(ServerTick, _stateA.Tick); cmdNum < _storedInputHeaders.Count; cmdNum++) + { + //reapply input data + var storedInput = _storedInputHeaders[cmdNum]; + _localPlayer.LoadInputInfo(storedInput.Header); + RollBackTick = storedInput.Tick; + foreach (var controller in GetEntities()) + controller.ReadStoredInput(cmdNum); + + foreach (var e in _entitiesToRollback) + { + ref var classData = ref ClassDataDict[e.ClassId]; + if (cmdNum == _storedInputHeaders.Count - 1) + { + for(int i = 0; i < classData.InterpolatedCount; i++) + { + fixed (byte* prevDataPtr = classData.ClientInterpolatedPrevData(e)) + { + ref var field = ref classData.Fields[i]; + field.TypeProcessor.WriteTo(e, field.Offset, prevDataPtr + field.FixedOffset); + } + } + e.Update(); + for (int i = 0; i < classData.InterpolatedCount; i++) + { + fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(e)) + { + ref var field = ref classData.Fields[i]; + field.TypeProcessor.WriteTo(e, field.Offset, currentDataPtr + field.FixedOffset); + } + } + } + else + { + e.Update(); + } + } + } + UpdateMode = UpdateMode.Normal; + _entitiesToRollback.Clear(); + } + ExecuteLateConstruct(); for (int i = 0; i < _syncCallsCount; i++) _syncCalls[i].Execute(stateData); @@ -939,49 +986,15 @@ internal unsafe void ReadConstructRPC(ushort entityId, byte* rawData, int reader if (ConstructEntity(entity) == false) return; - if (entity is not EntityLogic entityLogic) - return; - - //Logger.Log($"ConstructedEntity: {entityId}, pid: {entityLogic.PredictedId}"); - - entityLogic.RefreshOwnerInfo(null); - - if (entityLogic.IsLocalControlled && entityLogic.IsPredicted && AliveEntities.Contains(entity)) + if (entity is EntityLogic entityLogic) { - UpdateMode = UpdateMode.PredictionRollback; - for(int cmdNum = Utils.SequenceDiff(ServerTick, _stateA.Tick); cmdNum < _storedInputHeaders.Count; cmdNum++) - { - //reapply input data - var storedInput = _storedInputHeaders[cmdNum]; - _localPlayer.LoadInputInfo(storedInput.Header); - RollBackTick = storedInput.Tick; - foreach (var controller in GetEntities()) - controller.ReadStoredInput(cmdNum); - - if (cmdNum == _storedInputHeaders.Count - 1) - { - for(int i = 0; i < classData.InterpolatedCount; i++) - { - fixed (byte* prevDataPtr = classData.ClientInterpolatedPrevData(entity)) - { - ref var field = ref classData.Fields[i]; - field.TypeProcessor.WriteTo(entity, field.Offset, prevDataPtr + field.FixedOffset); - } - } - } - entity.Update(); - } - for (int i = 0; i < classData.InterpolatedCount; i++) - { - fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(entity)) - { - ref var field = ref classData.Fields[i]; - field.TypeProcessor.WriteTo(entity, field.Offset, currentDataPtr + field.FixedOffset); - } - } - - UpdateMode = UpdateMode.Normal; + entityLogic.RefreshOwnerInfo(null); + if(entity.IsLocal || !entity.IsLocalControlled || !entityLogic.IsPredicted || !AliveEntities.Contains(entity)) + return; + _entitiesToRollback.Enqueue(entityLogic); } + + //Logger.Log($"ConstructedEntity: {entityId}, pid: {entityLogic.PredictedId}"); } private unsafe void ReadDiff(ref int readerPosition) diff --git a/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs b/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs index 0bcc191..6a66192 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs @@ -103,16 +103,15 @@ public void MakeNewRPC() } //refresh construct rpc with latest values (old behaviour) - public unsafe void RefreshConstructedRPC(RemoteCallPacket packet, NetPlayer player) + public unsafe void RefreshConstructedRPC(RemoteCallPacket packet) { - RefreshSyncGroupsVariable(player); fixed (byte* sourceData = _latestEntityData, rawData = packet.Data) { RefMagic.CopyBlock(rawData, sourceData + HeaderSize, (uint)(_fullDataSize - HeaderSize)); } } - - private SyncGroup RefreshSyncGroupsVariable(NetPlayer player) + + public SyncGroup RefreshSyncGroupsVariable(NetPlayer player, Span target) { SyncGroup enabledGroups = SyncGroup.All; if (_entity is EntityLogic el) @@ -127,7 +126,7 @@ private SyncGroup RefreshSyncGroupsVariable(NetPlayer player) //if no data it "never" changed _fieldChangeTicks[el.IsSyncEnabledFieldId] = _versionChangedTick; } - _latestEntityData[HeaderSize + _fields[el.IsSyncEnabledFieldId].FixedOffset] = (byte)enabledGroups; + target[HeaderSize + _fields[el.IsSyncEnabledFieldId].FixedOffset] = (byte)enabledGroups; } return enabledGroups; @@ -150,7 +149,7 @@ public void MakeConstructedRPC(NetPlayer player) //it can be null on entity creation if(player != null) - RefreshSyncGroupsVariable(player); + RefreshSyncGroupsVariable(player, new Span(_latestEntityData)); //actual on constructed rpc _entity.ServerManager.AddRemoteCall( @@ -223,7 +222,7 @@ public unsafe bool MakeDiff(NetPlayer player, ushort minimalTick, byte* resultDa : player.CurrentServerTick; //overwrite IsSyncEnabled for each player - SyncGroup enabledSyncGroups = RefreshSyncGroupsVariable(player); + SyncGroup enabledSyncGroups = RefreshSyncGroupsVariable(player, new Span(_latestEntityData)); fixed (byte* lastEntityData = _latestEntityData) //make diff { diff --git a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs index 6ae0aa3..dc927a4 100644 --- a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs @@ -560,8 +560,15 @@ protected override unsafe void OnLogicTick() if(!aliveEntity.IsDestroyed) aliveEntity.Update(); } - + ExecuteLateConstruct(); + + //refresh construct rpc with latest updates to entity at creation tick + foreach (var rpcNode in _pendingRPCs) + { + if (rpcNode.Header.Id == RemoteCallPacket.ConstructRPCId && rpcNode.Header.Tick == _tick) + _stateSerializers[rpcNode.Header.EntityId].RefreshConstructedRPC(rpcNode); + } foreach (var lagCompensatedEntity in LagCompensatedEntities) ClassDataDict[lagCompensatedEntity.ClassId].WriteHistory(lagCompensatedEntity, _tick); @@ -828,7 +835,7 @@ bool ShouldSendRPC(RemoteCallPacket rpcNode, NetPlayer player) } if (rpcNode.Header.Id == RemoteCallPacket.ConstructRPCId) - stateSerializer.RefreshConstructedRPC(rpcNode, player); + stateSerializer.RefreshSyncGroupsVariable(player, new Span(rpcNode.Data)); return true; } From a7a681cf86ca435ae119e60238ed3e7851b2d3e8 Mon Sep 17 00:00:00 2001 From: Ruslan Pyrch Date: Thu, 19 Jun 2025 18:41:34 +0300 Subject: [PATCH 06/22] update LES to 0ab6eed - more safe update in one place --- .../LiteEntitySystem/ClientEntityManager.cs | 15 ++++++++++----- .../LiteEntitySystem/ILPart/RefMagic.dll | Bin 3584 -> 3584 bytes .../Internal/StateSerializer.cs | 10 ++++++---- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 5ba4855..96c71a8 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -107,7 +107,7 @@ public sealed class ClientEntityManager : EntityManager /// Preferred input and incoming states buffer length in seconds lowest bound /// Buffer automatically increases to Jitter time + PreferredBufferTimeLowest /// - public float PreferredBufferTimeLowest = 0.01f; + public float PreferredBufferTimeLowest = 0.025f; /// /// Preferred input and incoming states buffer length in seconds lowest bound @@ -122,6 +122,7 @@ public sealed class ClientEntityManager : EntityManager private const float TimeSpeedChangeFadeTime = 0.1f; private const float MaxJitter = 0.2f; + private const float MinJitter = 0.001f; /// /// Maximum stored inputs count @@ -449,8 +450,8 @@ private bool PreloadNextState() //limit jitter for pause scenarios if (NetworkJitter > MaxJitter) NetworkJitter = MaxJitter; - float lowestBound = NetworkJitter + PreferredBufferTimeLowest; - float upperBound = NetworkJitter + PreferredBufferTimeHighest; + float lowestBound = NetworkJitter * 1.5f + PreferredBufferTimeLowest; + float upperBound = NetworkJitter * 1.5f + PreferredBufferTimeHighest; //tune buffer playing speed _lerpTime = Utils.SequenceDiff(_stateB.Tick, _stateA.Tick) * DeltaTimeF; @@ -692,7 +693,11 @@ protected override unsafe void OnLogicTick() } if (NetworkJitter > _jitterMiddle) + { NetworkJitter -= DeltaTimeF * 0.1f; + if (NetworkJitter < MinJitter) + NetworkJitter = MinJitter; + } } /// @@ -918,7 +923,7 @@ private unsafe void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData) field.TypeProcessor.WriteTo(e, field.Offset, prevDataPtr + field.FixedOffset); } } - e.Update(); + e.SafeUpdate(); for (int i = 0; i < classData.InterpolatedCount; i++) { fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(e)) @@ -930,7 +935,7 @@ private unsafe void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData) } else { - e.Update(); + e.SafeUpdate(); } } } diff --git a/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.dll b/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.dll index 92da363c18253963e3f19ac6f8ea968cbe9314c4..26d63867cdef9b130123fe0a2221a50a8061f67c 100644 GIT binary patch delta 866 zcmZuvO=wd=5dLQ0dr4lZP11@<&9hCYf+iA;ITU}PMOv#AqiKtX6{?1ipthJ)RHR8n zq#&quK@T28di0`1JcuWeTw$oep$0#faC}NfXs?*2LupJTxi+ATFWL}Y}o>JT`c`uZ0)qJU|G` zE}IgMu>(rdx)!!hg0KAHVpadmSYSg}HnHLMZ(Yf>H>f7Tn%l?y-&)||1j-x@+?N|> zU)6zn0S9Vwhu>%rDRko)r4Yp>jvY8m86= pDLgJOzxsX#W{k+ki=;rO*|)o^r}5eMl=y$XD@Xn5#zX(S@dqZ6gjfIo delta 846 zcmaJ;O=wd=5dLOgUXu5!wrM0LCdO!r)TR`RLQyQ(DyYRnHKg>QSbI?8zpeGqLz7-K zP!Pid5yXSyQBaBAi%1~UTYK`{yP)7j2!6X+5K$bMneUsOH~a0pYNncbTyt92&(v2I z+*AEPF&LIiut&bTS0jJ~@BEQqQf(P@fb_t*wg;@YJ;8L_Q&={|YkwBoiEvO;*-V%p z+~x>>;2#Yi)%f961>~0iJ-VuK@!nkd)|GkCUb{e)IbSN`J#ml5CrhVKFnNkKl4U<-1`*u1%u{9Rs8=k_t2u1NTg#5ACB*RAvME)=c4$c( zM%acZUirmhUH?X`@WWJoW5@RYuTs%Xikds){%)P&;}mW%I+#};CU{+pF^m+WkNpOZ znb-%tn?C5B^g-`p2yu+c5Yjl$*o8wpDWHhJ#B~(1ByrKDC`Z=O&6g%PI)^+4aWuH= z#w%;)bqv-f@YIqdg|`g$Z0<5S$z8TYpz}^jPjYUDcUvB_JVq|OIYf8SOHUAm`J=M* zlvWjjhrNwR_@Xp!M*8IdRX8~# target) //if no data it "never" changed _fieldChangeTicks[el.IsSyncEnabledFieldId] = _versionChangedTick; } - target[HeaderSize + _fields[el.IsSyncEnabledFieldId].FixedOffset] = (byte)enabledGroups; + target[_fields[el.IsSyncEnabledFieldId].FixedOffset] = (byte)enabledGroups; } return enabledGroups; @@ -148,13 +148,15 @@ public void MakeConstructedRPC(NetPlayer player) } //it can be null on entity creation + var entityDataSpan = new Span(_latestEntityData, HeaderSize, (int)(_fullDataSize - HeaderSize)); + if(player != null) - RefreshSyncGroupsVariable(player, new Span(_latestEntityData)); + RefreshSyncGroupsVariable(player, entityDataSpan); //actual on constructed rpc _entity.ServerManager.AddRemoteCall( _entity, - new ReadOnlySpan(_latestEntityData, HeaderSize, (int)(_fullDataSize - HeaderSize)), + (ReadOnlySpan)entityDataSpan, RemoteCallPacket.ConstructRPCId, ExecuteFlags.SendToAll); //Logger.Log($"Added constructed RPC: {_entity}"); @@ -222,7 +224,7 @@ public unsafe bool MakeDiff(NetPlayer player, ushort minimalTick, byte* resultDa : player.CurrentServerTick; //overwrite IsSyncEnabled for each player - SyncGroup enabledSyncGroups = RefreshSyncGroupsVariable(player, new Span(_latestEntityData)); + SyncGroup enabledSyncGroups = RefreshSyncGroupsVariable(player, new Span(_latestEntityData, HeaderSize, (int)(_fullDataSize - HeaderSize))); fixed (byte* lastEntityData = _latestEntityData) //make diff { From f328272fc61dd0b8ffa0aabb85b9a92dcfbbb5e6 Mon Sep 17 00:00:00 2001 From: Ruslan Pyrch Date: Mon, 30 Jun 2025 15:21:08 +0300 Subject: [PATCH 07/22] wip --- .../LiteEntitySystem/ClientEntityManager.cs | 220 +++++------------- .../Collections/SequenceBinaryHeap.cs | 5 + .../Plugins/LiteEntitySystem/EntityManager.cs | 6 - .../LiteEntitySystem/ILPart/RefMagic.dll | Bin 3584 -> 3584 bytes .../LiteEntitySystem/ILPart/RefMagic.il | 145 ++++++++++++ .../LiteEntitySystem/ILPart/RefMagic.il.meta | 7 + .../Internal/EntityClassData.cs | 13 +- .../Internal/EntityFieldInfo.cs | 12 +- .../Internal/InternalEntity.cs | 5 +- .../Internal/ServerStateData.cs | 58 +---- .../Internal/ValueTypeProcessor.cs | 41 ++-- .../LiteEntitySystem/ServerEntityManager.cs | 17 ++ Assets/Plugins/LiteEntitySystem/SyncVar.cs | 9 + Assets/Scripts/Shared/BasePlayer.cs | 8 +- .../{BasePlayerView.cs => PlayerProxy.cs} | 2 +- ...PlayerView.cs.meta => PlayerProxy.cs.meta} | 0 Assets/Scripts/Shared/SimpleProjectile.cs | 4 +- 17 files changed, 292 insertions(+), 260 deletions(-) create mode 100644 Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il create mode 100644 Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il.meta rename Assets/Scripts/Shared/{BasePlayerView.cs => PlayerProxy.cs} (85%) rename Assets/Scripts/Shared/{BasePlayerView.cs.meta => PlayerProxy.cs.meta} (100%) diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 96c71a8..651f529 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -149,9 +149,26 @@ public sealed class ClientEntityManager : EntityManager private ServerStateData _stateB; private float _lerpTime; private double _timer; - private int _syncCallsCount; private readonly IdGeneratorUShort _localIdQueue = new(MaxSyncedEntityCount, MaxEntityCount); + + private SyncCallInfo[] _syncCalls; + private int _syncCallsCount; + + private ushort _lastReceivedInputTick; + private float _remoteLerpMsec; + private ushort _lastReadyTick; + + //time manipulation + private readonly float[] _jitterSamples = new float[50]; + private int _jitterSampleIdx; + private readonly Stopwatch _jitterTimer = new(); + private float _jitterPrevTime; + private float _jitterMiddle; + private float _jitterSum; + + //local player + private NetPlayer _localPlayer; private readonly struct SyncCallInfo { @@ -180,22 +197,6 @@ public void Execute(ServerStateData state) } } } - private SyncCallInfo[] _syncCalls; - - private ushort _lastReceivedInputTick; - private float _logicLerpMsec; - private ushort _lastReadyTick; - - //time manipulation - private readonly float[] _jitterSamples = new float[50]; - private int _jitterSampleIdx; - private readonly Stopwatch _jitterTimer = new(); - private float _jitterPrevTime; - private float _jitterMiddle; - private float _jitterSum; - - //local player - private NetPlayer _localPlayer; /// /// Return client controller if exist @@ -236,7 +237,10 @@ public ClientEntityManager( public override void Reset() { base.Reset(); + _predictedEntities.Clear(); _localIdQueue.Reset(); + _readyStates.Clear(); + _syncCallsCount = 0; _entitiesToRemoveCount = 0; } @@ -252,7 +256,8 @@ internal T AddLocalEntity(EntityLogic parent, Action initMethod) where T : Logger.LogError("Max local entities count reached"); return null; } - + + ref var classData = ref ClassDataDict[EntityClassInfo.ClassId]; var entity = AddEntity(new EntityParams( _localIdQueue.GetNewId(), new EntityDataHeader( @@ -260,7 +265,7 @@ internal T AddLocalEntity(EntityLogic parent, Action initMethod) where T : 0, 0), this, - ClassDataDict[EntityClassInfo.ClassId].AllocateDataCache())); + classData.AllocateDataCache())); //Logger.Log($"AddPredicted, tick: {_tick}, rb: {InRollBackState}, id: {entity.Id}"); @@ -270,6 +275,12 @@ internal T AddLocalEntity(EntityLogic parent, Action initMethod) where T : ConstructEntity(entity); _spawnPredictedEntities.Enqueue((_tick, entity)); + for(int i = 0; i < classData.InterpolatedCount; i++) + { + ref var field = ref classData.Fields[i]; + field.TypeProcessor.SetInterpValueFromCurrentValue(entity, field.Offset); + } + return entity; } @@ -289,21 +300,6 @@ internal override void EntityFieldChanged(InternalEntity entity, ushort field { //currently nothing } - - protected override unsafe void OnAliveEntityAdded(InternalEntity entity) - { - ref var classData = ref ClassDataDict[entity.ClassId]; - fixed (byte* interpDataPtr = classData.ClientInterpolatedNextData(entity), - prevDataPtr = classData.ClientInterpolatedPrevData(entity)) - { - for (int i = 0; i < classData.InterpolatedCount; i++) - { - var field = classData.Fields[i]; - field.TypeProcessor.WriteTo(entity, field.Offset, interpDataPtr + field.FixedOffset); - field.TypeProcessor.WriteTo(entity, field.Offset, prevDataPtr + field.FixedOffset); - } - } - } /// Read incoming data /// Incoming data including header byte @@ -419,7 +415,6 @@ public unsafe DeserializeResult Deserialize(ReadOnlySpan inData) _timer = _lerpTime; //fast-forward GoToNextState(); - PreloadNextState(); } _readyStates.Add(serverState, serverState.Tick); @@ -532,7 +527,7 @@ private unsafe void GoToNextState() _timer -= _lerpTime; //================== Rollback part =========================== - //reset owned entities + //reset predicted entities foreach (var entity in _predictedEntities) { ref var classData = ref ClassDataDict[entity.ClassId]; @@ -581,23 +576,6 @@ private unsafe void GoToNextState() } } UpdateMode = UpdateMode.Normal; - - //update local interpolated position - foreach (var entity in AliveEntities) - { - if(entity.IsLocal || !entity.IsLocalControlled) - continue; - - ref var classData = ref ClassDataDict[entity.ClassId]; - for(int i = 0; i < classData.InterpolatedCount; i++) - { - fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(entity)) - { - ref var field = ref classData.Fields[i]; - field.TypeProcessor.WriteTo(entity, field.Offset, currentDataPtr + field.FixedOffset); - } - } - } } internal override void OnEntityDestroyed(InternalEntity e) @@ -612,31 +590,23 @@ internal override void OnEntityDestroyed(InternalEntity e) base.OnEntityDestroyed(e); } - protected override unsafe void OnLogicTick() + protected override void OnLogicTick() { if (_stateB != null) { ServerTick = Utils.LerpSequence(_stateA.Tick, _stateB.Tick, (float)(_timer/_lerpTime)); - //restore actual field values foreach (var entity in AliveEntities) { if (!entity.IsLocal && !entity.IsLocalControlled) continue; ref var classData = ref ClassDataDict[entity.ClassId]; - if(classData.InterpolatedCount == 0) - continue; - //save data for interpolation before update - fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(entity)) + for (int i = 0; i < classData.InterpolatedCount; i++) { - //restore previous - for (int i = 0; i < classData.InterpolatedCount; i++) - { - ref var field = ref classData.Fields[i]; - field.TypeProcessor.SetFrom(entity, field.Offset, currentDataPtr + field.FixedOffset); - } + ref var field = ref classData.Fields[i]; + field.TypeProcessor.SetInterpValueFromCurrentValue(entity, field.Offset); } } } @@ -649,7 +619,7 @@ protected override unsafe void OnLogicTick() { StateA = _stateA.Tick, StateB = RawTargetServerTick, - LerpMsec = _logicLerpMsec + LerpMsec = _remoteLerpMsec })); foreach (var controller in humanControllers) { @@ -663,28 +633,6 @@ protected override unsafe void OnLogicTick() if (_stateB != null) { - //save interpolation data - foreach (var entity in AliveEntities) - { - if (!entity.IsLocal && !entity.IsLocalControlled) - continue; - - ref var classData = ref ClassDataDict[entity.ClassId]; - if(classData.InterpolatedCount == 0) - continue; - - fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(entity), - prevDataPtr = classData.ClientInterpolatedPrevData(entity)) - { - RefMagic.CopyBlock(prevDataPtr, currentDataPtr, (uint)classData.InterpolatedFieldsSize); - for(int i = 0; i < classData.InterpolatedCount; i++) - { - ref var field = ref classData.Fields[i]; - field.TypeProcessor.WriteTo(entity, field.Offset, currentDataPtr + field.FixedOffset); - } - } - } - //execute rpcs and spawn entities IsExecutingRPC = true; _stateB.ExecuteRpcs(this, _stateA.Tick, false); @@ -703,7 +651,7 @@ protected override unsafe void OnLogicTick() /// /// Update method, call this every frame /// - public override unsafe void Update() + public override void Update() { //skip update until receive first sync and tickrate if (Tickrate == 0) @@ -718,9 +666,10 @@ public override unsafe void Update() if (_tick != prevTick) SendBufferedInput(); - if (PreloadNextState()) + if (_stateB != null) { _timer += VisualDeltaTime; + while(_timer >= _lerpTime) { GoToNextState(); @@ -730,31 +679,8 @@ public override unsafe void Update() if (_stateB != null) { - _logicLerpMsec = (float)(_timer/_lerpTime); - _stateB.RemoteInterpolation(EntitiesDict, _logicLerpMsec); - } - } - - //local interpolation - float localLerpT = LerpFactor; - foreach (var entity in AliveEntities) - { - if (!entity.IsLocalControlled && !entity.IsLocal) - continue; - - ref var classData = ref ClassDataDict[entity.ClassId]; - fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(entity), prevDataPtr = classData.ClientInterpolatedPrevData(entity)) - { - for(int i = 0; i < classData.InterpolatedCount; i++) - { - ref var field = ref classData.Fields[i]; - field.TypeProcessor.SetInterpolation( - entity, - field.Offset, - prevDataPtr + field.FixedOffset, - currentDataPtr + field.FixedOffset, - localLerpT); - } + _remoteLerpMsec = (float)(_timer/_lerpTime); + _stateB.PreloadInterpolationForNewEntities(EntitiesDict); } } @@ -765,6 +691,14 @@ public override unsafe void Update() } } + internal T GetInterpolatedValue(ref SyncVar syncVar, T interpValue) where T : unmanaged + { + var typeProcessor = (ValueTypeProcessor)ClassDataDict[syncVar.Container.ClassId].Fields[syncVar.FieldId].TypeProcessor; + return syncVar.Container.IsLocalControlled + ? typeProcessor.GetInterpolatedValue(interpValue, syncVar.Value, LerpFactor) + : typeProcessor.GetInterpolatedValue(syncVar.Value, interpValue, _remoteLerpMsec); + } + private unsafe void SendBufferedInput() { if (_storedInputHeaders.Count == 0) @@ -774,7 +708,7 @@ private unsafe void SendBufferedInput() *(ushort*)(sendBuffer + 2) = Tick; *(InputPacketHeader*)(sendBuffer + 4) = new InputPacketHeader { - LerpMsec = _logicLerpMsec, + LerpMsec = _remoteLerpMsec, StateA = _stateA.Tick, StateB = RawTargetServerTick }; @@ -895,7 +829,7 @@ internal unsafe void ReadNewRPC(ushort entityId, byte* rawData) } } - private unsafe void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData) + private void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData) { if (_entitiesToRollback.Count > 0) { @@ -912,31 +846,16 @@ private unsafe void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData) foreach (var e in _entitiesToRollback) { - ref var classData = ref ClassDataDict[e.ClassId]; if (cmdNum == _storedInputHeaders.Count - 1) { + ref var classData = ref ClassDataDict[e.ClassId]; for(int i = 0; i < classData.InterpolatedCount; i++) { - fixed (byte* prevDataPtr = classData.ClientInterpolatedPrevData(e)) - { - ref var field = ref classData.Fields[i]; - field.TypeProcessor.WriteTo(e, field.Offset, prevDataPtr + field.FixedOffset); - } - } - e.SafeUpdate(); - for (int i = 0; i < classData.InterpolatedCount; i++) - { - fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(e)) - { - ref var field = ref classData.Fields[i]; - field.TypeProcessor.WriteTo(e, field.Offset, currentDataPtr + field.FixedOffset); - } + ref var field = ref classData.Fields[i]; + field.TypeProcessor.SetInterpValueFromCurrentValue(e, field.Offset); } } - else - { - e.SafeUpdate(); - } + e.SafeUpdate(); } } UpdateMode = UpdateMode.Normal; @@ -965,22 +884,16 @@ internal unsafe void ReadConstructRPC(ushort entityId, byte* rawData, int reader ref var classData = ref entity.ClassData; Utils.ResizeOrCreate(ref _syncCalls, _syncCallsCount + classData.FieldsCount); - fixed (byte* interpDataPtr = classData.ClientInterpolatedNextData(entity), - prevDataPtr = classData.ClientInterpolatedPrevData(entity), - predictedData = classData.ClientPredictedData(entity)) + fixed (byte* predictedData = classData.ClientPredictedData(entity)) { for (int i = 0; i < classData.FieldsCount; i++) { ref var field = ref classData.Fields[i]; - if (field.ReadField( - entity, - rawData + readerPosition, - predictedData, - writeInterpolationData ? interpDataPtr : null, - writeInterpolationData ? prevDataPtr : null)) - { + if (writeInterpolationData && field.Flags.HasFlagFast(SyncFlags.Interpolated)) + field.TypeProcessor.SetInterpValue(entity, field.Offset, rawData + readerPosition); + + if (field.ReadField(entity, rawData + readerPosition, predictedData)) _syncCalls[_syncCallsCount++] = new SyncCallInfo(field.OnSync, entity, readerPosition, field.IntSize); - } //Logger.Log($"E {entity.Id} Field updated: {field.Name}"); readerPosition += field.IntSize; @@ -1023,30 +936,21 @@ private unsafe void ReadDiff(ref int readerPosition) } ref var classData = ref entity.ClassData; - bool writeInterpolationData = entity.IsRemoteControlled; readerPosition += classData.FieldsFlagsSize; _changedEntities.Add(entity); Utils.ResizeOrCreate(ref _syncCalls, _syncCallsCount + classData.FieldsCount); int fieldsFlagsOffset = readerPosition - classData.FieldsFlagsSize; - fixed (byte* interpDataPtr = classData.ClientInterpolatedNextData(entity), - predictedData = classData.ClientPredictedData(entity)) + fixed (byte* predictedData = classData.ClientPredictedData(entity)) { for (int i = 0; i < classData.FieldsCount; i++) { if (!Utils.IsBitSet(rawData + fieldsFlagsOffset, i)) continue; ref var field = ref classData.Fields[i]; - if (field.ReadField( - entity, - rawData + readerPosition, - predictedData, - writeInterpolationData ? interpDataPtr : null, - null)) - { + if (field.ReadField(entity, rawData + readerPosition, predictedData)) _syncCalls[_syncCallsCount++] = new SyncCallInfo(field.OnSync, entity, readerPosition, field.IntSize); - } //Logger.Log($"E {entity.Id} Field updated: {field.Name}"); readerPosition += field.IntSize; diff --git a/Assets/Plugins/LiteEntitySystem/Collections/SequenceBinaryHeap.cs b/Assets/Plugins/LiteEntitySystem/Collections/SequenceBinaryHeap.cs index 7d77466..6e8d334 100644 --- a/Assets/Plugins/LiteEntitySystem/Collections/SequenceBinaryHeap.cs +++ b/Assets/Plugins/LiteEntitySystem/Collections/SequenceBinaryHeap.cs @@ -21,6 +21,11 @@ public SequenceBinaryHeap(int capacity) _data = new SequenceHeapNode[capacity]; _count = 0; } + + public void Clear() + { + _count = 0; + } public void Add(T item, ushort sequence) { diff --git a/Assets/Plugins/LiteEntitySystem/EntityManager.cs b/Assets/Plugins/LiteEntitySystem/EntityManager.cs index 555259a..3575f50 100644 --- a/Assets/Plugins/LiteEntitySystem/EntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/EntityManager.cs @@ -553,17 +553,11 @@ protected bool ConstructEntity(InternalEntity e) if (IsEntityAlive(classData.Flags, e)) { AliveEntities.Add(e); - OnAliveEntityAdded(e); } _entitiesToLateConstruct.Enqueue(e); return true; } - - protected virtual void OnAliveEntityAdded(InternalEntity e) - { - - } protected static bool IsEntityLagCompensated(InternalEntity e) => !e.IsLocal && e is EntityLogic && e.ClassData.LagCompensatedCount > 0; diff --git a/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.dll b/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.dll index 26d63867cdef9b130123fe0a2221a50a8061f67c..b56b5ea75213712a61381b10b5740abc26475d4f 100644 GIT binary patch delta 912 zcmb7@O=uHQ5Xb-XHoMs*ZPGN2`7nmWMldbZqzX3p0THDw6dQ@H9<&l6NI-+FwPFuV ztssgPnmp`9Z(c-lsOIWf4?TDg1TRAAMX(@sTGi^Ndmfmxk__{}3*kn4uT9@Pq z)#ho4x6mH%^ahQ<@rNpu;>{HjoS;?g&}A4XDGG=(uRW(IDXP3`UC}WnFPe(R#2xst zuBamJBZ#kxYBjNpFs$OX>=L&~a5+De-p#wYMgcE-!mifTTmPRCyiIGBNiVz4?DgVmt{1aOK5uoorv9^~jS`f(A`_8)B@AS+X}y~%xBg%3RlA&w;a zkVRfj>w(%PmmqjczsES%6ok3J4O@Fu?6hN;=h-0Qrui_)6^6ODZT2e;C=MtNDh@Ie zSK?sP`FwPAr&t$l4&&dIzP zk`3n@qn(yH^HPMnWt}RpvYi?#e;z*5_%=28Bh^ZgPdhBFcPPwBZ alddOw4Rd$1}VB4T4bn9!et*h(*kc#vvDM5Nh@ ziXi^11A0@TJ*kI6L@yqt7kdcx;GrOT^5DU<2gNs=5k-B=eDC|-&d&SZte5xl_ih@8 zN;CegLGh^IKbl%0Q(n4sN~}fYRa(w@TvSozC1B#C11v=Vl8f=ZvL-6>x#*{e zye<08C=e^k&tl)fHv$-12F48Fn&Q(J&Nt6CTI!@1Ccrx}G&_yg%u|X_&K^C?d6V^; zoHs^CELIs_YA3e?{gD+{?_<1x`|_1>+c1Vh*e1ap zKlOM!NZ%L*zW>Dy{lV|I|E~zP+&k{yoeiE%pv7q5vam44Hqwk?d3$4jJIP0Q*Q)qI|$zBzZ0yyWygU{RnxzKf$Bw*b2sC&{4%> zEwXI68*mHViGaj|LYA+wO9Yi&cAe!WJ)P7#t#ul%tcM4ZxxkFYHY}bAo$KLqBqPxc z`5{`j4JT+l+Waw|Ts?DS)t-?jAHJQERjb_btQPIORQP2MxTCn^yBXmXWHJ6;I*E0Q R4@hwN$wVie_)7j* +{ + .param type T + .custom instance void [netstandard]System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = (01 00 00 00 ) + .method public hidebysig virtual newslot abstract instance void SvSetDirect(!T 'value') cil managed {} + .method public hidebysig virtual newslot abstract instance void SvSetDirectAndStorePrev(!T 'value', [out] !T& prevValue) cil managed {} + .method public hidebysig virtual newslot abstract instance bool SvSetFromAndSync(!T& 'value') cil managed {} + .method public hidebysig virtual newslot abstract instance void SvSetInterpValue(!T 'value') cil managed {} +} + +.class public abstract auto ansi sealed beforefieldinit LiteEntitySystem.Internal.RefMagic extends [netstandard]System.Object +{ + .method public hidebysig static !!T GetFieldValue(object obj, int32 offs) cil managed aggressiveinlining + { + ldarg obj + conv.i + ldarg offs + add + ldobj !!T + ret + } + + .method public hidebysig static void SetFieldValue(object obj, int32 offs, !!T 'value') cil managed aggressiveinlining + { + ldarg obj + conv.i + ldarg offs + add + ldarg 'value' + stobj !!T + ret + } + + .method public hidebysig static void SyncVarSetDirect< + valuetype .ctor (class [netstandard]System.ValueType modreq ([netstandard]System.Runtime.InteropServices.UnmanagedType)) T, + valuetype .ctor (class LiteEntitySystem.Internal.ISyncVar`1, class [netstandard]System.ValueType) TSyncVar> + (object obj, int32 offs, !!T 'value') cil managed aggressiveinlining + { + .param type T + .custom instance void [netstandard]System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = (01 00 00 00 ) + + ldarg obj + conv.i + ldarg offs + add + ldarg 'value' + constrained. !!TSyncVar + callvirt instance void class LiteEntitySystem.Internal.ISyncVar`1::SvSetDirect(!0) + ret + } + + .method public hidebysig static void SyncVarSetInterp< + valuetype .ctor (class [netstandard]System.ValueType modreq ([netstandard]System.Runtime.InteropServices.UnmanagedType)) T, + valuetype .ctor (class LiteEntitySystem.Internal.ISyncVar`1, class [netstandard]System.ValueType) TSyncVar> + (object obj, int32 offs, !!T 'value') cil managed aggressiveinlining + { + .param type T + .custom instance void [netstandard]System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = (01 00 00 00 ) + + ldarg obj + conv.i + ldarg offs + add + ldarg 'value' + constrained. !!TSyncVar + callvirt instance void class LiteEntitySystem.Internal.ISyncVar`1::SvSetInterpValue(!0) + ret + } + + .method public hidebysig static void SyncVarSetDirectAndStorePrev< + valuetype .ctor (class [netstandard]System.ValueType modreq([netstandard]System.Runtime.InteropServices.UnmanagedType)) T, + valuetype .ctor (class LiteEntitySystem.Internal.ISyncVar`1, [netstandard]System.ValueType) TSyncVar> + (object obj, int32 offs, !!T 'value', [out] !!T& prevValue) cil managed aggressiveinlining + { + .param type T + .custom instance void [netstandard]System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = ( 01 00 00 00 ) + + ldarg obj + conv.i + ldarg offs + add + ldarg 'value' + ldarg prevValue + constrained. !!TSyncVar + callvirt instance void class LiteEntitySystem.Internal.ISyncVar`1::SvSetDirectAndStorePrev(!0, !0&) + ret + } + + .method public hidebysig static bool SyncVarSetFromAndSync< + valuetype .ctor (class [netstandard]System.ValueType modreq([netstandard]System.Runtime.InteropServices.UnmanagedType)) T, + valuetype .ctor (class LiteEntitySystem.Internal.ISyncVar`1, [netstandard]System.ValueType) TSyncVar> + (object obj, int32 offs, !!T& 'value') cil managed aggressiveinlining + { + .param type T + .custom instance void [netstandard]System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = (01 00 00 00 ) + + ldarg obj + conv.i + ldarg offs + add + ldarg 'value' + constrained. !!TSyncVar + callvirt instance bool class LiteEntitySystem.Internal.ISyncVar`1::SvSetFromAndSync(!0&) + ret + } + + .method public hidebysig static void CopyBlock(void* destination, void* source, unsigned int32 byteCount) cil managed aggressiveinlining + { + .maxstack 3 + ldarg.0 + ldarg.1 + ldarg.2 + cpblk + ret + } +} \ No newline at end of file diff --git a/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il.meta b/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il.meta new file mode 100644 index 0000000..e597eb6 --- /dev/null +++ b/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ed546e403437eb249b60a62a394b4da0 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs b/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs index a5d32b5..75a6621 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs @@ -63,7 +63,6 @@ internal struct EntityClassData public readonly int PredictedSize; public readonly EntityFieldInfo[] Fields; public readonly SyncableFieldInfo[] SyncableFields; - public readonly int InterpolatedFieldsSize; public readonly int InterpolatedCount; public readonly EntityFieldInfo[] LagCompensatedFields; public readonly int LagCompensatedSize; @@ -86,10 +85,8 @@ internal struct EntityClassData private readonly int _dataCacheSize; private readonly int _maxHistoryCount; private readonly int _historyStart; - - public Span ClientInterpolatedPrevData(InternalEntity e) => new (e.IOBuffer, 0, InterpolatedFieldsSize); - public Span ClientInterpolatedNextData(InternalEntity e) => new (e.IOBuffer, InterpolatedFieldsSize, InterpolatedFieldsSize); - public Span ClientPredictedData(InternalEntity e) => new (e.IOBuffer, InterpolatedFieldsSize*2, PredictedSize); + + public Span ClientPredictedData(InternalEntity e) => new (e.IOBuffer, 0, PredictedSize); public EntityFieldInfo[] GetRollbackFields(bool isOwned) => isOwned ? _ownedRollbackFields : _remoteRollbackFields; @@ -174,7 +171,6 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp FixedFieldsSize = 0; LagCompensatedSize = 0; InterpolatedCount = 0; - InterpolatedFieldsSize = 0; RemoteCallsClient = null; ClassId = typeInfo.ClassId; ClassEnumName = typeInfo.ClassName; @@ -231,7 +227,6 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp int fieldSize = valueTypeProcessor.Size; if (syncFlags.HasFlagFast(SyncFlags.Interpolated) && !ft.IsEnum) { - InterpolatedFieldsSize += fieldSize; InterpolatedCount++; } var fieldInfo = new EntityFieldInfo($"{baseType.Name}-{field.Name}", valueTypeProcessor, offset, syncVarFlags); @@ -339,8 +334,8 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp } else { - _dataCacheSize = InterpolatedFieldsSize * 2 + PredictedSize + historySize; - _historyStart = InterpolatedFieldsSize * 2 + PredictedSize; + _dataCacheSize = PredictedSize + historySize; + _historyStart = PredictedSize; } Type = entType; diff --git a/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs b/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs index c43a406..3789595 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs @@ -64,9 +64,7 @@ private EntityFieldInfo( public unsafe bool ReadField( InternalEntity entity, byte* rawData, - byte* predictedData, - byte* nextInterpDataPtr, - byte* prevInterpDataPtr) + byte* predictedData) { if (IsPredicted) RefMagic.CopyBlock(predictedData + PredictedOffset, rawData, Size); @@ -77,14 +75,6 @@ public unsafe bool ReadField( } else { - if (Flags.HasFlagFast(SyncFlags.Interpolated)) - { - if(nextInterpDataPtr != null) - RefMagic.CopyBlock(nextInterpDataPtr + FixedOffset, rawData, Size); - if(prevInterpDataPtr != null) - RefMagic.CopyBlock(prevInterpDataPtr + FixedOffset, rawData, Size); - } - if (OnSync != null) { if (TypeProcessor.SetFromAndSync(entity, Offset, rawData)) diff --git a/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs b/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs index 21bf3f6..053bb30 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs @@ -201,18 +201,17 @@ internal void RegisterRpcInternal() ref var classData = ref EntityManager.ClassDataDict[ClassId]; //setup field ids for BindOnChange and pass on server this for OnChangedEvent to StateSerializer - var onChangeTarget = EntityManager.IsServer && !IsLocal ? this : null; for (int i = 0; i < classData.FieldsCount; i++) { ref var field = ref classData.Fields[i]; if (field.FieldType == FieldType.SyncVar) { - field.TypeProcessor.InitSyncVar(this, field.Offset, onChangeTarget, (ushort)i); + field.TypeProcessor.InitSyncVar(this, field.Offset, this, (ushort)i); } else { var syncableField = RefMagic.GetFieldValue(this, field.Offset); - field.TypeProcessor.InitSyncVar(syncableField, field.SyncableSyncVarOffset, onChangeTarget, (ushort)i); + field.TypeProcessor.InitSyncVar(syncableField, field.SyncableSyncVarOffset, this, (ushort)i); } } diff --git a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs index 579d8b3..b98cc58 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs @@ -46,9 +46,6 @@ internal class ServerStateData public ushort LastReceivedTick; public byte BufferedInputsCount; - private int _interpolatedCachesCount; - private InterpolatedCache[] _interpolatedCaches = new InterpolatedCache[DefaultCacheSize]; - private int _totalPartsCount; private int _receivedPartsCount; private byte _maxReceivedPart; @@ -69,10 +66,8 @@ internal class ServerStateData private readonly HashSet _syncablesSet = new(); private DeltaCompressor _rpcDeltaCompressor = new (Utils.SizeOfStruct()); - public ServerStateData() - { + public ServerStateData() => _rpcDeltaCompressor.Init(); - } public unsafe void GetDiagnosticData( InternalEntity[] entityDict, @@ -118,22 +113,6 @@ public unsafe void GetDiagnosticData( ? classDatas[classId].ClassEnumName : "Unknown"; - /* - if (classId >= 0) - { - ref var classData = ref classDatas[classId]; - int entityFieldsOffset = initialReaderPosition + StateSerializer.DiffHeaderSize; - //interpolated fields goes first so can skip some checks - for (int i = 0; i < classData.FieldsCount; i++) - { - if (!Utils.IsBitSet(Data, entityFieldsOffset, i)) - continue; - ref var field = ref classData.Fields[i]; - Logger.Log($"{field.Name}({i}) - {field.Size}"); - } - } - */ - if(!diagnosticDataDict.TryGetValue(classId, out LESDiagnosticDataEntry entry)) { entry = new LESDiagnosticDataEntry { Count = 1, Name = name, Size = totalSize }; @@ -175,7 +154,7 @@ public void Preload(InternalEntity[] entityDict) } } - private void PreloadInterpolation(InternalEntity entity, int offset) + private unsafe void PreloadInterpolation(InternalEntity entity, int offset) { if (!entity.IsRemoteControlled) return; @@ -186,22 +165,22 @@ private void PreloadInterpolation(InternalEntity entity, int offset) int entityFieldsOffset = offset + StateSerializer.DiffHeaderSize; int stateReaderOffset = entityFieldsOffset + classData.FieldsFlagsSize; - - //preload interpolation info - Utils.ResizeIfFull(ref _interpolatedCaches, _interpolatedCachesCount + classData.InterpolatedCount); //interpolated fields goes first so can skip some checks - for (int i = 0; i < classData.InterpolatedCount; i++) + fixed (byte* rawData = Data) { - if (!Utils.IsBitSet(Data, entityFieldsOffset, i)) - continue; - ref var field = ref classData.Fields[i]; - _interpolatedCaches[_interpolatedCachesCount++] = new InterpolatedCache(entity, ref field, stateReaderOffset); - stateReaderOffset += field.IntSize; + for (int i = 0; i < classData.InterpolatedCount; i++) + { + if (!Utils.IsBitSet(Data, entityFieldsOffset, i)) + continue; + ref var field = ref classData.Fields[i]; + field.TypeProcessor.SetInterpValue(entity, field.Offset, rawData + stateReaderOffset); + stateReaderOffset += field.IntSize; + } } } - public unsafe void RemoteInterpolation(InternalEntity[] entityDict, float logicLerpMsec) + public void PreloadInterpolationForNewEntities(InternalEntity[] entityDict) { for (int i = 0; i < _nullEntitiesCount; i++) { @@ -218,18 +197,6 @@ public unsafe void RemoteInterpolation(InternalEntity[] entityDict, float logicL _nullEntitiesData[i] = _nullEntitiesData[_nullEntitiesCount]; i--; } - - for(int i = 0; i < _interpolatedCachesCount; i++) - { - ref var interpolatedCache = ref _interpolatedCaches[i]; - fixed (byte* initialDataPtr = interpolatedCache.Entity.ClassData.ClientInterpolatedNextData(interpolatedCache.Entity), nextDataPtr = Data) - interpolatedCache.TypeProcessor.SetInterpolation( - interpolatedCache.Entity, - interpolatedCache.FieldOffset, - initialDataPtr + interpolatedCache.FieldFixedOffset, - nextDataPtr + interpolatedCache.StateReaderOffset, - logicLerpMsec); - } } public unsafe void ExecuteRpcs(ClientEntityManager entityManager, ushort minimalTick, bool firstSync) @@ -341,7 +308,6 @@ public void Reset(ushort tick) { Tick = tick; _receivedParts.SetAll(false); - _interpolatedCachesCount = 0; _maxReceivedPart = 0; _receivedPartsCount = 0; _totalPartsCount = 0; diff --git a/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs b/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs index e348fb3..4e87213 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs @@ -16,8 +16,9 @@ internal abstract unsafe class ValueTypeProcessor internal abstract void InitSyncVar(InternalBaseClass obj, int offset, InternalEntity entity, ushort fieldId); internal abstract void SetFrom(InternalBaseClass obj, int offset, byte* data); internal abstract bool SetFromAndSync(InternalBaseClass obj, int offset, byte* data); + internal abstract void SetInterpValue(InternalBaseClass obj, int offset, byte* data); + internal abstract void SetInterpValueFromCurrentValue(InternalBaseClass obj, int offset); internal abstract void WriteTo(InternalBaseClass obj, int offset, byte* data); - internal abstract void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer); internal abstract void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime); internal abstract int GetHashCode(InternalBaseClass obj, int offset); internal abstract string ToString(InternalBaseClass obj, int offset); @@ -27,10 +28,9 @@ internal unsafe class ValueTypeProcessor : ValueTypeProcessor where T : unman { public ValueTypeProcessor() : base(sizeof(T)) { } - internal override void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - throw new Exception($"This type: {typeof(T)} can't be interpolated"); + internal virtual T GetInterpolatedValue(T prev, T current, float t) => current; - internal override void InitSyncVar(InternalBaseClass obj, int offset, InternalEntity entity, ushort fieldId) + internal sealed override void InitSyncVar(InternalBaseClass obj, int offset, InternalEntity entity, ushort fieldId) { var sv = RefMagic.GetFieldValue>(obj, offset); sv.Init(entity, fieldId); @@ -40,26 +40,31 @@ internal override void InitSyncVar(InternalBaseClass obj, int offset, InternalEn internal override void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, *(T*)historyA, out *(T*)tempHistory); - internal override void SetFrom(InternalBaseClass obj, int offset, byte* data) => + internal sealed override void SetFrom(InternalBaseClass obj, int offset, byte* data) => RefMagic.SyncVarSetDirect>(obj, offset, *(T*)data); - internal override bool SetFromAndSync(InternalBaseClass obj, int offset, byte* data) => + internal sealed override bool SetFromAndSync(InternalBaseClass obj, int offset, byte* data) => RefMagic.SyncVarSetFromAndSync>(obj, offset, ref *(T*)data); - internal override void WriteTo(InternalBaseClass obj, int offset, byte* data) => + internal sealed override void SetInterpValue(InternalBaseClass obj, int offset, byte* data) => + RefMagic.SyncVarSetInterp>(obj, offset, *(T*)data); + + internal sealed override void SetInterpValueFromCurrentValue(InternalBaseClass obj, int offset) => + RefMagic.SyncVarSetInterp>(obj, offset, RefMagic.GetFieldValue>(obj, offset)); + + internal sealed override void WriteTo(InternalBaseClass obj, int offset, byte* data) => *(T*)data = RefMagic.GetFieldValue>(obj, offset); - internal override int GetHashCode(InternalBaseClass obj, int offset) => + internal sealed override int GetHashCode(InternalBaseClass obj, int offset) => RefMagic.GetFieldValue>(obj, offset).GetHashCode(); - internal override string ToString(InternalBaseClass obj, int offset) => + internal sealed override string ToString(InternalBaseClass obj, int offset) => RefMagic.GetFieldValue>(obj, offset).ToString(); } internal class ValueTypeProcessorInt : ValueTypeProcessor { - internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.SyncVarSetDirect>(obj, offset, Utils.Lerp(*(int*)prev, *(int*)current, fTimer)); + internal override int GetInterpolatedValue(int prev, int current, float t) => Utils.Lerp(prev, current, t); internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, @@ -68,8 +73,7 @@ internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byt internal class ValueTypeProcessorLong : ValueTypeProcessor { - internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.SyncVarSetDirect>(obj, offset, Utils.Lerp(*(long*)prev, *(long*)current, fTimer)); + internal override long GetInterpolatedValue(long prev, long current, float t) => Utils.Lerp(prev, current, t); internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, @@ -78,8 +82,7 @@ internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byt internal class ValueTypeProcessorFloat : ValueTypeProcessor { - internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.SyncVarSetDirect>(obj, offset, Utils.Lerp(*(float*)prev, *(float*)current, fTimer)); + internal override float GetInterpolatedValue(float prev, float current, float t) => Utils.Lerp(prev, current, t); internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, @@ -88,8 +91,7 @@ internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byt internal class ValueTypeProcessorDouble : ValueTypeProcessor { - internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.SyncVarSetDirect>(obj, offset, Utils.Lerp(*(double*)prev, *(double*)current, fTimer)); + internal override double GetInterpolatedValue(double prev, double current, float t) => Utils.Lerp(prev, current, t); internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, @@ -100,9 +102,8 @@ internal unsafe class UserTypeProcessor : ValueTypeProcessor where T : unm { private readonly InterpolatorDelegateWithReturn _interpDelegate; - internal override void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.SyncVarSetDirect>(obj, offset, _interpDelegate?.Invoke(*(T*)prev, *(T*)current, fTimer) ?? *(T*)prev); - + internal override T GetInterpolatedValue(T prev, T current, float t) => _interpDelegate?.Invoke(prev, current, t) ?? current; + internal override void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, _interpDelegate?.Invoke(*(T*)historyA, *(T*)historyB, lerpTime) ?? *(T*)historyA, out *(T*)tempHistory); diff --git a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs index dc927a4..3788849 100644 --- a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs @@ -9,10 +9,24 @@ namespace LiteEntitySystem { + /// + /// Rate at which server sends state packets + /// public enum ServerSendRate : byte { + /// + /// Send rate is equal to Tickrate + /// EqualToFPS = 1, + + /// + /// Send rate is Tickrate / 2 + /// HalfOfFPS = 2, + + /// + /// Send rate is Tickrate / 3 + /// ThirdOfFPS = 3 } @@ -848,6 +862,9 @@ internal override void EntityFieldChanged(InternalEntity entity, ushort field //old freed entity return; } + if(entity is AiControllerLogic) + return; + _changedEntities.Add(entity); _stateSerializers[entity.Id].UpdateFieldValue(fieldId, _minimalTick, _tick, ref newValue); } diff --git a/Assets/Plugins/LiteEntitySystem/SyncVar.cs b/Assets/Plugins/LiteEntitySystem/SyncVar.cs index 3826174..d593d2a 100644 --- a/Assets/Plugins/LiteEntitySystem/SyncVar.cs +++ b/Assets/Plugins/LiteEntitySystem/SyncVar.cs @@ -72,8 +72,17 @@ public class SyncVarFlags : Attribute public struct SyncVar : ISyncVar, IEquatable, IEquatable> where T : unmanaged { private T _value; + private T _interpValue; + internal ushort FieldId; internal InternalEntity Container; + + public T InterpolatedValue => Container == null || Container.IsServer + ? _value + : Container.ClientManager.GetInterpolatedValue(ref this, _interpValue); + + //for interpolation + void ISyncVar.SvSetInterpValue(T value) => _interpValue = value; void ISyncVar.SvSetDirect(T value) => _value = value; diff --git a/Assets/Scripts/Shared/BasePlayer.cs b/Assets/Scripts/Shared/BasePlayer.cs index cb5e247..a5d8fb7 100644 --- a/Assets/Scripts/Shared/BasePlayer.cs +++ b/Assets/Scripts/Shared/BasePlayer.cs @@ -35,8 +35,8 @@ public class BasePlayer : PawnLogic private bool _projectileFire; public byte Health => _health; - public Vector2 Position => _position; - public float Rotation => _rotation.Value; + public Vector2 Position => _position.InterpolatedValue; + public float Rotation => _rotation.InterpolatedValue; public GameObject UnityObject; public GameObject View; @@ -63,7 +63,7 @@ protected override void OnConstructed() _rigidbody.isKinematic = true; _collider.isTrigger = true; _collider.size = Vector2.one * 0.66f; - UnityObject.AddComponent().AttachedPlayer = this; + UnityObject.AddComponent().AttachedPlayer = this; Debug.Log($"Player joined: {Name.Value}"); } @@ -177,7 +177,7 @@ protected override void Update() for (int i = 0; i < hitsCount; i++) { ref var hit = ref RaycastHits[i]; - if (hit.transform.TryGetComponent(out var playerProxy) && playerProxy.AttachedPlayer != this ) + if (hit.transform.TryGetComponent(out var playerProxy) && playerProxy.AttachedPlayer != this ) { if (EntityManager.InNormalState) ExecuteRPC(_hitRemoteCall, new HitPacket { Position = hit.point }); diff --git a/Assets/Scripts/Shared/BasePlayerView.cs b/Assets/Scripts/Shared/PlayerProxy.cs similarity index 85% rename from Assets/Scripts/Shared/BasePlayerView.cs rename to Assets/Scripts/Shared/PlayerProxy.cs index 1a2281b..f23a3ee 100644 --- a/Assets/Scripts/Shared/BasePlayerView.cs +++ b/Assets/Scripts/Shared/PlayerProxy.cs @@ -2,7 +2,7 @@ namespace Code.Shared { - public class BasePlayerView : MonoBehaviour + public class PlayerProxy : MonoBehaviour { public BasePlayer AttachedPlayer; diff --git a/Assets/Scripts/Shared/BasePlayerView.cs.meta b/Assets/Scripts/Shared/PlayerProxy.cs.meta similarity index 100% rename from Assets/Scripts/Shared/BasePlayerView.cs.meta rename to Assets/Scripts/Shared/PlayerProxy.cs.meta diff --git a/Assets/Scripts/Shared/SimpleProjectile.cs b/Assets/Scripts/Shared/SimpleProjectile.cs index 31912bb..4128aa1 100644 --- a/Assets/Scripts/Shared/SimpleProjectile.cs +++ b/Assets/Scripts/Shared/SimpleProjectile.cs @@ -87,7 +87,7 @@ protected override void Update() for (int i = 0; i < hitsCount; i++) { ref var hit = ref RaycastHits[i]; - if (hit.transform.TryGetComponent(out var playerProxy) && playerProxy.AttachedPlayer.SharedReference != ShooterPlayer ) + if (hit.transform.TryGetComponent(out var playerProxy) && playerProxy.AttachedPlayer.SharedReference != ShooterPlayer ) { playerProxy.AttachedPlayer.Damage(25); if (EntityManager.IsClient && EntityManager.InNormalState) @@ -107,7 +107,7 @@ protected override void Update() protected override void VisualUpdate() { - UnityObject.transform.position = Position.Value; + UnityObject.transform.position = Position.InterpolatedValue; } } } \ No newline at end of file From 1134fc4d6a1c407091c403b3714c0b2d26b2aa6a Mon Sep 17 00:00:00 2001 From: Ruslan Pyrch Date: Mon, 30 Jun 2025 15:36:36 +0300 Subject: [PATCH 08/22] upd --- .../Plugins/LiteEntitySystem/ClientEntityManager.cs | 13 ++++++------- Assets/Plugins/LiteEntitySystem/EntityLogic.cs | 8 ++++---- .../LiteEntitySystem/Internal/InternalBaseClass.cs | 7 +++++++ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 651f529..95e8c8e 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -17,11 +17,6 @@ public sealed class ClientEntityManager : EntityManager /// Current interpolated server tick /// public ushort ServerTick { get; private set; } - - /// - /// Current rollback tick (valid only in Rollback state) - /// - public ushort RollBackTick { get; private set; } /// /// Tick of currently executing rpc (check only in client RPC methods) @@ -559,12 +554,13 @@ private unsafe void GoToNextState() //reapply input UpdateMode = UpdateMode.PredictionRollback; + ushort targetTick = _tick; for(int cmdNum = 0; cmdNum < _storedInputHeaders.Count; cmdNum++) { //reapply input data var storedInput = _storedInputHeaders[cmdNum]; _localPlayer.LoadInputInfo(storedInput.Header); - RollBackTick = storedInput.Tick; + _tick = storedInput.Tick; foreach (var controller in GetEntities()) controller.ReadStoredInput(cmdNum); //simple update @@ -575,6 +571,7 @@ private unsafe void GoToNextState() entity.Update(); } } + _tick = targetTick; UpdateMode = UpdateMode.Normal; } @@ -835,12 +832,13 @@ private void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData) { //fast forward new but predicted entities UpdateMode = UpdateMode.PredictionRollback; + ushort targetTick = _tick; for(int cmdNum = Utils.SequenceDiff(ServerTick, _stateA.Tick); cmdNum < _storedInputHeaders.Count; cmdNum++) { //reapply input data var storedInput = _storedInputHeaders[cmdNum]; _localPlayer.LoadInputInfo(storedInput.Header); - RollBackTick = storedInput.Tick; + _tick = storedInput.Tick; foreach (var controller in GetEntities()) controller.ReadStoredInput(cmdNum); @@ -858,6 +856,7 @@ private void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData) e.SafeUpdate(); } } + _tick = targetTick; UpdateMode = UpdateMode.Normal; _entitiesToRollback.Clear(); } diff --git a/Assets/Plugins/LiteEntitySystem/EntityLogic.cs b/Assets/Plugins/LiteEntitySystem/EntityLogic.cs index fe726f8..8578479 100644 --- a/Assets/Plugins/LiteEntitySystem/EntityLogic.cs +++ b/Assets/Plugins/LiteEntitySystem/EntityLogic.cs @@ -174,7 +174,7 @@ public void DisableLagCompensationForOwner() => /// current tick depending on entity manager state (IsExecutingRPC and InRollBackState) public int GetFrameSeed() => EntityManager.IsClient - ? (EntityManager.InRollBackState ? ClientManager.RollBackTick : (ClientManager.IsExecutingRPC ? ClientManager.CurrentRPCTick : EntityManager.Tick)) + ? (ClientManager.IsExecutingRPC ? ClientManager.CurrentRPCTick : EntityManager.Tick) : (InternalOwnerId.Value == EntityManager.ServerPlayerId ? EntityManager.Tick : ServerManager.GetPlayer(InternalOwnerId).LastProcessedTick); /// @@ -212,12 +212,12 @@ public T AddPredictedEntity(Action initMethod = null) where T : EntityLogi //local counter here should be reset ushort potentialId = _localPredictedIdCounter.Value++; - var origEnt = ClientManager.FindEntityByPredictedId(ClientManager.RollBackTick, Id, potentialId); + var origEnt = ClientManager.FindEntityByPredictedId(ClientManager.Tick, Id, potentialId); entity = origEnt as T; if (entity == null) { - Logger.LogWarning($"Requested RbTick{ClientManager.RollBackTick}, ParentId: {Id}, potentialId: {potentialId}, requestedType: {typeof(T)}, foundType: {origEnt?.GetType()}"); - Logger.LogWarning($"Misspredicted entity add? RbTick: {ClientManager.RollBackTick}, potentialId: {potentialId}"); + Logger.LogWarning($"Requested RbTick{ClientManager.Tick}, ParentId: {Id}, potentialId: {potentialId}, requestedType: {typeof(T)}, foundType: {origEnt?.GetType()}"); + Logger.LogWarning($"Misspredicted entity add? RbTick: {ClientManager.Tick}, potentialId: {potentialId}"); } else { diff --git a/Assets/Plugins/LiteEntitySystem/Internal/InternalBaseClass.cs b/Assets/Plugins/LiteEntitySystem/Internal/InternalBaseClass.cs index 3b0d9b0..e9f820b 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/InternalBaseClass.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/InternalBaseClass.cs @@ -1,7 +1,14 @@ namespace LiteEntitySystem.Internal { + /// + /// Base class for SyncableFields and Entities + /// public abstract class InternalBaseClass { + /// + /// Method for executing RPCs containing initial sync data that need to be sent after entity creation + /// to existing players or when new player connected + /// protected internal virtual void OnSyncRequested() { From 1f1973d219a67800a2ba8a93b5951dbed71152cf Mon Sep 17 00:00:00 2001 From: RevenantX Date: Tue, 1 Jul 2025 11:50:47 +0300 Subject: [PATCH 09/22] upd --- .../LiteEntitySystem/ClientEntityManager.cs | 80 +++++++++++------- .../Plugins/LiteEntitySystem/EntityManager.cs | 7 +- .../LiteEntitySystem/Extensions/SyncDict.cs | 8 +- .../Extensions/SyncHashSet.cs | 7 +- .../LiteEntitySystem/Extensions/SyncList.cs | 44 ++++++---- .../LiteEntitySystem/Extensions/SyncQueue.cs | 12 ++- .../Extensions/SyncStateMachine.cs | 2 - .../LiteEntitySystem/Extensions/SyncTimer.cs | 6 +- .../LiteEntitySystem/ILPart/RefMagic.dll | Bin 3584 -> 4096 bytes .../LiteEntitySystem/ILPart/RefMagic.il | 18 ++++ .../Internal/EntityClassData.cs | 14 ++- .../Internal/ServerStateData.cs | 6 +- .../Internal/ValueTypeProcessor.cs | 2 +- Assets/Plugins/LiteEntitySystem/SyncChilds.cs | 4 +- Assets/Plugins/LiteEntitySystem/SyncVar.cs | 13 ++- .../Plugins/LiteEntitySystem/SyncableField.cs | 41 ++++----- 16 files changed, 170 insertions(+), 94 deletions(-) diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 95e8c8e..39b4d90 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -125,7 +125,7 @@ public sealed class ClientEntityManager : EntityManager public const int InputBufferSize = 64; //predicted entities that should use rollback - private readonly AVLTree _predictedEntities = new(); + private readonly AVLTree _modifiedEntitiesToRollback = new(); private readonly AbstractNetPeer _netPeer; private readonly Queue _statesPool = new(MaxSavedStateDiff); @@ -136,7 +136,7 @@ public sealed class ClientEntityManager : EntityManager private readonly HashSet _changedEntities = new(); private readonly CircularBuffer _storedInputHeaders = new(InputBufferSize); private InternalEntity[] _entitiesToRemove = new InternalEntity[64]; - private readonly Queue _entitiesToRollback = new(); + private readonly Queue _entitiesToRollback = new(); private int _entitiesToRemoveCount; private ServerSendRate _serverSendRate; @@ -232,7 +232,7 @@ public ClientEntityManager( public override void Reset() { base.Reset(); - _predictedEntities.Clear(); + _modifiedEntitiesToRollback.Clear(); _localIdQueue.Reset(); _readyStates.Clear(); _syncCallsCount = 0; @@ -290,11 +290,6 @@ internal EntityLogic FindEntityByPredictedId(ushort tick, ushort parentId, ushor } return null; } - - internal override void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue) - { - //currently nothing - } /// Read incoming data /// Incoming data including header byte @@ -351,7 +346,9 @@ public unsafe DeserializeResult Deserialize(ReadOnlySpan inData) IsExecutingRPC = false; int readerPosition = _stateA.DataOffset; ReadDiff(ref readerPosition); - ExecuteSyncCallsAndWriteHistory(_stateA); + ExecuteSyncCalls(_stateA); + foreach (var lagCompensatedEntity in LagCompensatedEntities) + ClassDataDict[lagCompensatedEntity.ClassId].WriteHistory(lagCompensatedEntity, ServerTick); Logger.Log($"[CEM] Got baseline sync. Assigned player id: {header.PlayerId}, Original: {_stateA.Size}, Tick: {header.Tick}, SendRate: {_serverSendRate}"); } @@ -497,7 +494,9 @@ private unsafe void GoToNextState() IsExecutingRPC = false; int readerPosition = _stateA.DataOffset; ReadDiff(ref readerPosition); - ExecuteSyncCallsAndWriteHistory(_stateA); + ExecuteSyncCalls(_stateA); + foreach (var lagCompensatedEntity in LagCompensatedEntities) + ClassDataDict[lagCompensatedEntity.ClassId].WriteHistory(lagCompensatedEntity, ServerTick); for(int i = 0; i < _entitiesToRemoveCount; i++) { @@ -505,8 +504,6 @@ private unsafe void GoToNextState() var entityToRemove = _entitiesToRemove[i]; if (_changedEntities.Contains(entityToRemove)) continue; - - _predictedEntities.Remove(entityToRemove); //Logger.Log($"[CLI] RemovingEntity: {_entitiesToRemove[i].Id}"); RemoveEntity(entityToRemove); @@ -523,12 +520,14 @@ private unsafe void GoToNextState() //================== Rollback part =========================== //reset predicted entities - foreach (var entity in _predictedEntities) + _entitiesToRollback.Clear(); + foreach (var entity in _modifiedEntitiesToRollback) + _entitiesToRollback.Enqueue(entity); + + foreach (var entity in _entitiesToRollback) { ref var classData = ref ClassDataDict[entity.ClassId]; var rollbackFields = classData.GetRollbackFields(entity.IsLocalControlled); - if(rollbackFields == null || rollbackFields.Length == 0) - continue; entity.OnBeforeRollback(); fixed (byte* predictedData = classData.ClientPredictedData(entity)) @@ -547,10 +546,13 @@ private unsafe void GoToNextState() } } } - for (int i = 0; i < classData.SyncableFields.Length; i++) - RefMagic.GetFieldValue(entity, classData.SyncableFields[i].Offset).OnRollback(); + for (int i = 0; i < classData.SyncableFieldsCustomRollback.Length; i++) + RefMagic.GetFieldValue(entity, classData.SyncableFieldsCustomRollback[i].Offset).OnRollback(); entity.OnRollback(); } + + //clear modified here to readd changes after RollbackUpdate + _modifiedEntitiesToRollback.Clear(); //reapply input UpdateMode = UpdateMode.PredictionRollback; @@ -564,15 +566,35 @@ private unsafe void GoToNextState() foreach (var controller in GetEntities()) controller.ReadStoredInput(cmdNum); //simple update - foreach (var entity in AliveEntities) + //foreach (var entity in AliveEntities) + foreach (var entity in _entitiesToRollback) { - if (entity.IsLocal || !entity.IsLocalControlled) + if (!entity.IsLocalControlled || !AliveEntities.Contains(entity)) continue; entity.Update(); } } _tick = targetTick; UpdateMode = UpdateMode.Normal; + _entitiesToRollback.Clear(); + } + + internal void MarkEntityChanged(InternalEntity entity) + { + if (entity.IsLocal || entity.IsDestroyed) + return; + _modifiedEntitiesToRollback.Add(entity); + } + + internal override void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue) + { + if (entity.IsLocal || entity.IsDestroyed) + return; + + ref var classData = ref ClassDataDict[entity.ClassId]; + var rollbackFields = classData.GetRollbackFields(entity.IsLocalControlled); + if (rollbackFields != null && rollbackFields.Length > 0 && classData.Fields[fieldId].IsPredicted) + _modifiedEntitiesToRollback.Add(entity); } internal override void OnEntityDestroyed(InternalEntity e) @@ -582,6 +604,8 @@ internal override void OnEntityDestroyed(InternalEntity e) if(e.IsLocalControlled && e is EntityLogic eLogic) RemoveOwned(eLogic); Utils.AddToArrayDynamic(ref _entitiesToRemove, ref _entitiesToRemoveCount, e); + + _modifiedEntitiesToRollback.Remove(e); } base.OnEntityDestroyed(e); @@ -634,7 +658,7 @@ protected override void OnLogicTick() IsExecutingRPC = true; _stateB.ExecuteRpcs(this, _stateA.Tick, false); IsExecutingRPC = false; - ExecuteSyncCallsAndWriteHistory(_stateB); + ExecuteSyncCalls(_stateB); } if (NetworkJitter > _jitterMiddle) @@ -690,6 +714,9 @@ public override void Update() internal T GetInterpolatedValue(ref SyncVar syncVar, T interpValue) where T : unmanaged { + if (IsLagCompensationEnabled && IsEntityLagCompensated(syncVar.Container)) + return syncVar.Value; + var typeProcessor = (ValueTypeProcessor)ClassDataDict[syncVar.Container.ClassId].Fields[syncVar.FieldId].TypeProcessor; return syncVar.Container.IsLocalControlled ? typeProcessor.GetInterpolatedValue(interpValue, syncVar.Value, LerpFactor) @@ -810,23 +837,16 @@ internal unsafe void ReadNewRPC(ushort entityId, byte* rawData) Logger.Log($"[CEM] Replace entity by new: {entityDataHeader.Version}. Class: {entityDataHeader.ClassId}. Id: {entityId}"); entity.DestroyInternal(); RemoveEntity(entity); - _predictedEntities.Remove(entity); entity = null; } if (entity == null) //create new { ref var classData = ref ClassDataDict[entityDataHeader.ClassId]; - entity = AddEntity(new EntityParams(entityId, entityDataHeader, this, classData.AllocateDataCache())); - - if (classData.PredictedSize > 0 || classData.SyncableFields.Length > 0) - { - _predictedEntities.Add(entity); - //Logger.Log($"Add predicted: {entity.GetType()}"); - } + AddEntity(new EntityParams(entityId, entityDataHeader, this, classData.AllocateDataCache())); } } - private void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData) + private void ExecuteSyncCalls(ServerStateData stateData) { if (_entitiesToRollback.Count > 0) { @@ -865,8 +885,6 @@ private void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData) for (int i = 0; i < _syncCallsCount; i++) _syncCalls[i].Execute(stateData); _syncCallsCount = 0; - foreach (var lagCompensatedEntity in LagCompensatedEntities) - ClassDataDict[lagCompensatedEntity.ClassId].WriteHistory(lagCompensatedEntity, ServerTick); } internal unsafe void ReadConstructRPC(ushort entityId, byte* rawData, int readerPosition) diff --git a/Assets/Plugins/LiteEntitySystem/EntityManager.cs b/Assets/Plugins/LiteEntitySystem/EntityManager.cs index 3575f50..794bb09 100644 --- a/Assets/Plugins/LiteEntitySystem/EntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/EntityManager.cs @@ -216,6 +216,11 @@ public abstract class EntityManager /// IsRunning - sets to false after Reset() call /// public bool IsRunning => _stopwatch.IsRunning; + + /// + /// Is lag compensation currently enabled + /// + public bool IsLagCompensationEnabled => _lagCompensationEnabled; /// /// Register custom field type with interpolation @@ -639,7 +644,7 @@ protected void ExecuteLateConstruct() } protected abstract void OnLogicTick(); - + internal abstract void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue) where T : unmanaged; diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncDict.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncDict.cs index 232112d..5e45273 100644 --- a/Assets/Plugins/LiteEntitySystem/Extensions/SyncDict.cs +++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncDict.cs @@ -4,7 +4,7 @@ namespace LiteEntitySystem.Extensions { - public class SyncDict : SyncableField, IEnumerable> where TKey : unmanaged where TValue : unmanaged + public class SyncDict : SyncableFieldCustomRollback, IEnumerable> where TKey : unmanaged where TValue : unmanaged { private struct KeyValue { @@ -31,8 +31,6 @@ public KeyValue(TKey key, TValue value) public Dictionary.KeyCollection Keys => _data.Keys; public Dictionary.ValueCollection Values => _data.Values; - - public override bool IsRollbackSupported => true; protected internal override void RegisterRPC(ref SyncableRPCRegistrator r) { @@ -85,18 +83,21 @@ private void InitAction(ReadOnlySpan data) private void AddAction(KeyValue kv) { _data[kv.Key] = kv.Value; + MarkAsChanged(); } public void Add(TKey key, TValue value) { _data.Add(key, value); ExecuteRPC(_addAction, new KeyValue(key,value)); + MarkAsChanged(); } public void Clear() { _data.Clear(); ExecuteRPC(_clearAction); + MarkAsChanged(); } public bool TryGetValue(TKey key, out TValue value) @@ -124,6 +125,7 @@ public bool Remove(TKey key) if (!_data.Remove(key)) return false; ExecuteRPC(_removeAction, key); + MarkAsChanged(); return true; } diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncHashSet.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncHashSet.cs index 3ed1c83..be9d8e4 100644 --- a/Assets/Plugins/LiteEntitySystem/Extensions/SyncHashSet.cs +++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncHashSet.cs @@ -4,7 +4,7 @@ namespace LiteEntitySystem.Extensions { - public class SyncHashSet : SyncableField, IEnumerable where T : unmanaged + public class SyncHashSet : SyncableFieldCustomRollback, IEnumerable where T : unmanaged { public int Count => _data.Count; @@ -16,8 +16,6 @@ public class SyncHashSet : SyncableField, IEnumerable where T : unmanaged private static RemoteCall _clearAction; private static RemoteCall _removeAction; private static RemoteCallSpan _initAction; - - public override bool IsRollbackSupported => true; protected internal override void RegisterRPC(ref SyncableRPCRegistrator r) { @@ -86,12 +84,14 @@ public void Add(T x) { _data.Add(x); ExecuteRPC(_addAction, x); + MarkAsChanged(); } public void Clear() { _data.Clear(); ExecuteRPC(_clearAction); + MarkAsChanged(); } public bool Contains(T x) => _data.Contains(x); @@ -103,6 +103,7 @@ public bool Remove(T key) if (!_data.Remove(key)) return false; ExecuteRPC(_removeAction, key); + MarkAsChanged(); return true; } diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncList.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncList.cs index 2263eb4..ef56c9b 100644 --- a/Assets/Plugins/LiteEntitySystem/Extensions/SyncList.cs +++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncList.cs @@ -5,8 +5,28 @@ namespace LiteEntitySystem.Extensions { - public class SyncList : SyncableField, ICollection, IReadOnlyList where T : unmanaged + public class SyncList : SyncableFieldCustomRollback, ICollection, IReadOnlyList where T : unmanaged { + public struct SyncListEnumerator : IEnumerator + { + private readonly T[] _data; + private readonly int _count; + private int _index; + + public SyncListEnumerator(T[] data, int count) + { + _data = data; + _count = count; + _index = -1; + } + + public bool MoveNext() => ++_index < _count; + public void Reset() => _index = -1; + public T Current => _index >= 0 && _index < _data.Length ? _data[_index] : default; + object IEnumerator.Current => Current; + public void Dispose() {} + } + struct SetValueData { public int Index; @@ -28,8 +48,6 @@ struct SetValueData private static RemoteCallSpan _initAction; private static RemoteCall _setAction; - public override bool IsRollbackSupported => true; - protected internal override void BeforeReadRPC() { _serverData ??= new T[_data.Length]; @@ -106,12 +124,14 @@ public void Add(T item) _data[_count] = item; _count++; ExecuteRPC(_addAction, item); + MarkAsChanged(); } public void Clear() { _count = 0; ExecuteRPC(_clearAction); + MarkAsChanged(); } public bool Contains(T item) @@ -158,6 +178,7 @@ public void RemoveAt(int index) _data[_count - 1] = default; _count--; ExecuteRPC(_removeAtAction, index); + MarkAsChanged(); } public T this[int index] @@ -170,19 +191,10 @@ public T this[int index] } } - public IEnumerator GetEnumerator() - { - int index = 0; - while (index < _count) - { - yield return _data[index]; - index++; - } - } + public SyncListEnumerator GetEnumerator() => new(_data, _count); - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } \ No newline at end of file diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncQueue.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncQueue.cs index 56a7187..bc695ae 100644 --- a/Assets/Plugins/LiteEntitySystem/Extensions/SyncQueue.cs +++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncQueue.cs @@ -4,7 +4,7 @@ namespace LiteEntitySystem.Extensions { - public class SyncQueue : SyncableField, IReadOnlyCollection, ICollection where T : unmanaged + public class SyncQueue : SyncableFieldCustomRollback, IReadOnlyCollection, ICollection where T : unmanaged { // TODO: implement ring buffer instead of using .net's Queue. @@ -20,9 +20,7 @@ public class SyncQueue : SyncableField, IReadOnlyCollection, ICollection w public int Count => _data.Count; public bool IsSynchronized => false; public object SyncRoot => throw new NotImplementedException("The SyncQueue Collection isn't thread-safe."); - - public override bool IsRollbackSupported => true; - + protected internal override void RegisterRPC(ref SyncableRPCRegistrator r) { base.RegisterRPC(ref r); @@ -74,6 +72,7 @@ public void Enqueue(T item) { _data.Enqueue(item); ExecuteRPC(_enqueueAction, item); + MarkAsChanged(); } private void EnqueueClientAction(T item) => _data.Enqueue(item); @@ -82,6 +81,7 @@ public T Dequeue() { var value = _data.Dequeue(); ExecuteRPC(_dequeueAction); + MarkAsChanged(); return value; } @@ -89,7 +89,10 @@ public bool TryDequeue(out T item) { bool hasValue = _data.TryDequeue(out item); if (hasValue) + { ExecuteRPC(_dequeueAction); + MarkAsChanged(); + } return hasValue; } @@ -105,6 +108,7 @@ public void Clear() { _data.Clear(); ExecuteRPC(_clearAction); + MarkAsChanged(); } private void ClearClientAction() => _data.Clear(); diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncStateMachine.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncStateMachine.cs index 7ac74c6..9ce3b4f 100644 --- a/Assets/Plugins/LiteEntitySystem/Extensions/SyncStateMachine.cs +++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncStateMachine.cs @@ -15,8 +15,6 @@ public class SyncStateMachine : SyncableField where T : unmanaged, Enum public T CurrentState => _state; - public override bool IsRollbackSupported => true; - private readonly StateCalls[] _data; public SyncStateMachine() diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncTimer.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncTimer.cs index 1d62cb9..8a85aae 100644 --- a/Assets/Plugins/LiteEntitySystem/Extensions/SyncTimer.cs +++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncTimer.cs @@ -1,6 +1,4 @@ -using LiteEntitySystem.Internal; - -namespace LiteEntitySystem.Extensions +namespace LiteEntitySystem.Extensions { public class SyncTimer : SyncableField { @@ -13,8 +11,6 @@ public class SyncTimer : SyncableField private SyncVar _time; private SyncVar _maxTime; - public override bool IsRollbackSupported => true; - public SyncTimer(float maxTime) { _maxTime.Value = maxTime; diff --git a/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.dll b/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.dll index b56b5ea75213712a61381b10b5740abc26475d4f..8d3b14be6be06ae56d3d38f28d34d0c987a9f26a 100644 GIT binary patch delta 1043 zcmaJ=-%FEG7=GTf?eqKG)Y;T+ZpoHqI6+e{62ZDC{LzI@h8j_b(n!H(tQpjWvmZhT zk>h|jT?JkgL^p%%&LWU5j4sxNH^C_Avi^abz31DBt((rrdEfVW&X4y!&v(vRcrCK| zIEsz=1?X0Uh`PxQc$g4nvXpx{s9u-<6UD6iOE1a;e1ygqBVDLbQ=3pNLk2_pFUU{t$yh83)k5 z3KZEFxs3KiCcT)taa(Qw&H#82uLPq-I>|>Zw>{TA zGGHJcVN6>Hi0h2zH0lvcT=yM~hQtco$Z0ggs2;C0S`e#f#0QNYWyLyt_@?1=k%J## zZB>dL@eHjhgvl}~E4b8p9as1cW&~n3Ux^)h)&H5X^466}gi5To5UfWz<-fd2|K3jg ziLC=)WWv>xHTb_MMZ-`^cC!j2)XfKsvkhUH`q711NZS@{>m$V^Y%6VRvaZ``Kod*R z##+SCivhlP&NY>@x*ahTRorw6`+7!z%RaQ@I_(>5A7JqhiJ<&A7ari^%GjrIv&PLD z`!)7!9Mm|7aXD$64jtobXz)Q#a1Zj+GWhy+a1R3cb>jiGjHNQk^sVW+rIqAxdimys zm2^6p%1AQ5HkOB~*@Bt$&U>f*=N@K9pUU5peH81Lqt<}DYYo>JULh!K=69`rs@vBu lQVH@?k~<`dow;a}>~nw4M?7(|BISpU!F;3V7v;{>`~h#}m8$>% delta 908 zcmZuwOK4L;6g_v+7nAqWCLswXX^=cq(KgVgsbK0CMQN2{BC%A&Diw-mVZlGTFtLKD zC^Q}H%3U`K1(kp+cZIre(S<8F#n_Et7Ah{fDB_)$(FXKA?&q92bLY;5sU$0jnFsd$ zOXn{_f1e<1i!8uoLNv+a)fZ#N&mF*+?5BjZ>704e%S%Z%Q$C_J`Q#fq7Rz)u^}`$l z46L(YZ4*E;=-VX=;*4Araq`F&5jS|h*&~06-TRU*Aaw_L%<(y&m?~boQkuGORn^+* z20R>7qeblHJfQNS(W8esjxb)3*Ij*S?iO)2)ss?FOH{d9Z)XGM<+3YZeoOC|CjReI z0IQ6)ac(#YV96M#E~kbfmWYrj9Bxc#6cUr%%?*ug zF~xmU6)LlI55o>D=*1Cn6G6P!s3h(of-f4C#S=t%7op)a-5|m3{LuLZZ}R8_I@l97 za+luK%H*4cTB-jrg74Wp=3nJQ=dh>jU3ey?(O1qP0X3Umgm8#@(TNH676xemX`F&| z{0GN}NHImndw61V@FR@P*a90p=*K~{Rn#?Fzg32!h0EiW7DTzvdcgq}>P~ETVwB6) ziFkT`WVzKS&$x+$8izCvX&liwf~wOZ=*aBXv)Pqf_6gbW=49GDP`hjnkRM3s_n_{vU%s|FMW(xIhvjE$*_#A>Ocw@g LLEks3X57C4LCJsU diff --git a/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il b/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il index 0648754..896ad8c 100644 --- a/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il +++ b/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il @@ -35,6 +35,7 @@ .method public hidebysig virtual newslot abstract instance void SvSetDirectAndStorePrev(!T 'value', [out] !T& prevValue) cil managed {} .method public hidebysig virtual newslot abstract instance bool SvSetFromAndSync(!T& 'value') cil managed {} .method public hidebysig virtual newslot abstract instance void SvSetInterpValue(!T 'value') cil managed {} + .method public hidebysig virtual newslot abstract instance void SvSetInterpValueFromCurrent() cil managed {} } .class public abstract auto ansi sealed beforefieldinit LiteEntitySystem.Internal.RefMagic extends [netstandard]System.Object @@ -96,6 +97,23 @@ ret } + .method public hidebysig static void SyncVarSetInterpFromCurrent< + valuetype .ctor (class [netstandard]System.ValueType modreq ([netstandard]System.Runtime.InteropServices.UnmanagedType)) T, + valuetype .ctor (class LiteEntitySystem.Internal.ISyncVar`1, class [netstandard]System.ValueType) TSyncVar> + (object obj, int32 offs) cil managed aggressiveinlining + { + .param type T + .custom instance void [netstandard]System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = (01 00 00 00 ) + + ldarg obj + conv.i + ldarg offs + add + constrained. !!TSyncVar + callvirt instance void class LiteEntitySystem.Internal.ISyncVar`1::SvSetInterpValueFromCurrent() + ret + } + .method public hidebysig static void SyncVarSetDirectAndStorePrev< valuetype .ctor (class [netstandard]System.ValueType modreq([netstandard]System.Runtime.InteropServices.UnmanagedType)) T, valuetype .ctor (class LiteEntitySystem.Internal.ISyncVar`1, [netstandard]System.ValueType) TSyncVar> diff --git a/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs b/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs index 75a6621..e63a59e 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs @@ -63,6 +63,7 @@ internal struct EntityClassData public readonly int PredictedSize; public readonly EntityFieldInfo[] Fields; public readonly SyncableFieldInfo[] SyncableFields; + public readonly SyncableFieldInfo[] SyncableFieldsCustomRollback; public readonly int InterpolatedCount; public readonly EntityFieldInfo[] LagCompensatedFields; public readonly int LagCompensatedSize; @@ -186,6 +187,7 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp var fields = new List(); var syncableFields = new List(); + var syncableFieldsWithCustomRollback = new List(); var lagCompensatedFields = new List(); var ownedRollbackFields = new List(); var remoteRollbackFields = new List(); @@ -248,6 +250,11 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp throw new Exception($"Syncable fields should be readonly! (Class: {entType} Field: {field.Name})"); syncableFields.Add(new SyncableFieldInfo(offset, syncFlags)); + + //add custom rollbacked separately + if (ft.IsSubclassOf(typeof(SyncableFieldCustomRollback))) + syncableFieldsWithCustomRollback.Add(new SyncableFieldInfo(offset, syncFlags)); + var syncableFieldTypesWithBase = Utils.GetBaseTypes(ft, SyncableFieldType, true, true); while(syncableFieldTypesWithBase.Count > 0) { @@ -295,6 +302,7 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp }); Fields = fields.ToArray(); SyncableFields = syncableFields.ToArray(); + SyncableFieldsCustomRollback = syncableFieldsWithCustomRollback.ToArray(); FieldsCount = Fields.Length; FieldsFlagsSize = (FieldsCount-1) / 8 + 1; LagCompensatedFields = lagCompensatedFields.ToArray(); @@ -311,7 +319,11 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp { field.PredictedOffset = predictedOffset; predictedOffset += field.IntSize; - ownedRollbackFields.Add(field); + + //singletons can't be owned + if (!IsSingleton) + ownedRollbackFields.Add(field); + if(field.Flags.HasFlagFast(SyncFlags.AlwaysRollback)) remoteRollbackFields.Add(field); } diff --git a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs index b98cc58..270ff8d 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs @@ -63,7 +63,7 @@ internal class ServerStateData public int DataOffset => _dataOffset; public int DataSize => _dataSize; - private readonly HashSet _syncablesSet = new(); + private readonly HashSet _syncablesSet = new(); private DeltaCompressor _rpcDeltaCompressor = new (Utils.SizeOfStruct()); public ServerStateData() => @@ -285,9 +285,9 @@ public unsafe void ExecuteRpcs(ClientEntityManager entityManager, ushort minimal else { var syncableField = RefMagic.GetFieldValue(entity, rpcFieldInfo.SyncableOffset); - if (_syncablesSet.Add(syncableField)) + if (syncableField is SyncableFieldCustomRollback sf && _syncablesSet.Add(sf)) { - syncableField.BeforeReadRPC(); + sf.BeforeReadRPC(); } try { diff --git a/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs b/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs index 4e87213..bf1e015 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs @@ -50,7 +50,7 @@ internal sealed override void SetInterpValue(InternalBaseClass obj, int offset, RefMagic.SyncVarSetInterp>(obj, offset, *(T*)data); internal sealed override void SetInterpValueFromCurrentValue(InternalBaseClass obj, int offset) => - RefMagic.SyncVarSetInterp>(obj, offset, RefMagic.GetFieldValue>(obj, offset)); + RefMagic.SyncVarSetInterpFromCurrent>(obj, offset); internal sealed override void WriteTo(InternalBaseClass obj, int offset, byte* data) => *(T*)data = RefMagic.GetFieldValue>(obj, offset); diff --git a/Assets/Plugins/LiteEntitySystem/SyncChilds.cs b/Assets/Plugins/LiteEntitySystem/SyncChilds.cs index f84b71b..a0ccc06 100644 --- a/Assets/Plugins/LiteEntitySystem/SyncChilds.cs +++ b/Assets/Plugins/LiteEntitySystem/SyncChilds.cs @@ -4,7 +4,7 @@ namespace LiteEntitySystem { - public sealed class SyncChilds : SyncableField, IEnumerable + public sealed class SyncChilds : SyncableFieldCustomRollback, IEnumerable { class EqualityComparer : IEqualityComparer { @@ -27,8 +27,6 @@ class EqualityComparer : IEqualityComparer private static RemoteCall _removeAction; private static RemoteCallSpan _initAction; - public override bool IsRollbackSupported => true; - protected internal override void RegisterRPC(ref SyncableRPCRegistrator r) { r.CreateClientAction(this, Add, ref _addAction); diff --git a/Assets/Plugins/LiteEntitySystem/SyncVar.cs b/Assets/Plugins/LiteEntitySystem/SyncVar.cs index d593d2a..f6c69ab 100644 --- a/Assets/Plugins/LiteEntitySystem/SyncVar.cs +++ b/Assets/Plugins/LiteEntitySystem/SyncVar.cs @@ -68,6 +68,10 @@ public class SyncVarFlags : Attribute public SyncVarFlags(SyncFlags flags) => Flags = flags; } + /// + /// Synchronized variable + /// + /// Variable type [StructLayout(LayoutKind.Sequential)] public struct SyncVar : ISyncVar, IEquatable, IEquatable> where T : unmanaged { @@ -77,12 +81,16 @@ public struct SyncVar : ISyncVar, IEquatable, IEquatable> wh internal ushort FieldId; internal InternalEntity Container; + /// + /// Interpolated value on client (on server equals to Value) + /// public T InterpolatedValue => Container == null || Container.IsServer ? _value : Container.ClientManager.GetInterpolatedValue(ref this, _interpValue); //for interpolation void ISyncVar.SvSetInterpValue(T value) => _interpValue = value; + void ISyncVar.SvSetInterpValueFromCurrent() => _interpValue = _value; void ISyncVar.SvSetDirect(T value) => _value = value; @@ -105,6 +113,9 @@ bool ISyncVar.SvSetFromAndSync(ref T value) return false; } + /// + /// Actual logical value + /// public T Value { get => _value; @@ -120,7 +131,7 @@ internal void Init(InternalEntity container, ushort fieldId) { Container = container; FieldId = fieldId; - Container?.EntityManager.EntityFieldChanged(Container, FieldId, ref _value); + Container.EntityManager.EntityFieldChanged(Container, FieldId, ref _value); } public static implicit operator T(SyncVar sv) => sv._value; diff --git a/Assets/Plugins/LiteEntitySystem/SyncableField.cs b/Assets/Plugins/LiteEntitySystem/SyncableField.cs index 2cbd0a8..6c32c6a 100644 --- a/Assets/Plugins/LiteEntitySystem/SyncableField.cs +++ b/Assets/Plugins/LiteEntitySystem/SyncableField.cs @@ -42,11 +42,6 @@ public abstract class SyncableField : InternalBaseClass /// protected internal bool IsServer => _parentEntity != null && _parentEntity.IsServer; - /// - /// Is supported rollback by this syncable field - /// - public virtual bool IsRollbackSupported => false; - /// /// Owner of this syncable field /// @@ -67,21 +62,6 @@ internal void Init(InternalEntity parentEntity, SyncFlags fieldFlags) /// Owner of this syncable field casted to EntityLogic /// protected EntityLogic ParentEntityLogic => _parentEntity as EntityLogic; - - protected internal virtual void BeforeReadRPC() - { - - } - - protected internal virtual void AfterReadRPC() - { - - } - - protected internal virtual void OnRollback() - { - - } protected internal virtual void RegisterRPC(ref SyncableRPCRegistrator r) { @@ -119,4 +99,25 @@ protected void ExecuteRPC(in RemoteCallSerializable rpc, T value) where T } } } + + /// + /// Syncable fields with custom rollback notifications and implementation + /// + public abstract class SyncableFieldCustomRollback : SyncableField + { + /// + /// Marks that SyncableField was modified on client and add parent entity to Rollback list + /// + protected void MarkAsChanged() + { + if(ParentEntity != null && ParentEntity.IsClient) + ParentEntity.ClientManager.MarkEntityChanged(ParentEntity); + } + + protected internal abstract void BeforeReadRPC(); + + protected internal abstract void AfterReadRPC(); + + protected internal abstract void OnRollback(); + } } \ No newline at end of file From 552ad4e433621432db2644eaabcfd2ee8d58f704 Mon Sep 17 00:00:00 2001 From: RevenantX Date: Tue, 1 Jul 2025 13:29:33 +0300 Subject: [PATCH 10/22] fix --- Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 39b4d90..f0a09b3 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -407,6 +407,9 @@ public unsafe DeserializeResult Deserialize(ReadOnlySpan inData) _timer = _lerpTime; //fast-forward GoToNextState(); + + //to add space to _readyStates + PreloadNextState(); } _readyStates.Add(serverState, serverState.Tick); From 7734f605ab7fef9b16051bc3568f0913d671e3ab Mon Sep 17 00:00:00 2001 From: RevenantX Date: Fri, 11 Jul 2025 07:33:38 +0300 Subject: [PATCH 11/22] update LES --- .../LiteEntitySystem/ClientEntityManager.cs | 230 +++++++-------- .../Plugins/LiteEntitySystem/EntityLogic.cs | 51 +++- .../Plugins/LiteEntitySystem/EntityManager.cs | 27 +- .../LiteEntitySystem/HumanControllerLogic.cs | 13 +- .../Internal/EntityClassData.cs | 2 +- .../Internal/EntityFieldInfo.cs | 4 +- .../Internal/InternalEntity.cs | 42 ++- .../Internal/RemoteCallPacket.cs | 10 +- .../Internal/ServerStateData.cs | 277 +++++++++++------- .../LiteEntitySystem/RPCRegistrator.cs | 31 +- .../LiteEntitySystem/ServerEntityManager.cs | 29 +- Assets/Plugins/LiteEntitySystem/SyncVar.cs | 9 +- 12 files changed, 446 insertions(+), 279 deletions(-) diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index f0a09b3..4177d86 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -18,15 +18,10 @@ public sealed class ClientEntityManager : EntityManager /// public ushort ServerTick { get; private set; } - /// - /// Tick of currently executing rpc (check only in client RPC methods) - /// - public ushort CurrentRPCTick { get; internal set; } - /// /// Is server->client rpc currently executing /// - public bool IsExecutingRPC { get; private set; } + public bool IsExecutingRPC { get; internal set; } /// /// Current state server tick @@ -142,8 +137,10 @@ public sealed class ClientEntityManager : EntityManager private ServerSendRate _serverSendRate; private ServerStateData _stateA; private ServerStateData _stateB; - private float _lerpTime; - private double _timer; + private float _remoteInterpolationTotalTime; + private float _remoteInterpolationTimer; + private float _remoteLerpFactor; + private ushort _prevTick; private readonly IdGeneratorUShort _localIdQueue = new(MaxSyncedEntityCount, MaxEntityCount); @@ -151,7 +148,6 @@ public sealed class ClientEntityManager : EntityManager private int _syncCallsCount; private ushort _lastReceivedInputTick; - private float _remoteLerpMsec; private ushort _lastReadyTick; //time manipulation @@ -226,7 +222,7 @@ public ClientEntityManager( _sendBuffer[1] = InternalPackets.ClientInput; for (int i = 0; i < MaxSavedStateDiff; i++) - _statesPool.Enqueue(new ServerStateData()); + _statesPool.Enqueue(new ServerStateData(this)); } public override void Reset() @@ -336,14 +332,14 @@ public unsafe DeserializeResult Deserialize(ReadOnlySpan inData) _localPlayer = new NetPlayer(_netPeer, InternalPlayerId); ServerTick = _stateA.Tick; _lastReadyTick = ServerTick; + _remoteInterpolationTimer = 0f; + _remoteLerpFactor = 0f; foreach (var controller in GetEntities()) controller.ClearClientStoredInputs(); _storedInputHeaders.Clear(); _jitterTimer.Reset(); - IsExecutingRPC = true; - _stateA.ExecuteRpcs(this, 0, true); - IsExecutingRPC = false; + _stateA.ExecuteRpcs((ushort)(_stateA.Tick - 1),RPCExecuteMode.FirstSync); int readerPosition = _stateA.DataOffset; ReadDiff(ref readerPosition); ExecuteSyncCalls(_stateA); @@ -380,7 +376,7 @@ public unsafe DeserializeResult Deserialize(ReadOnlySpan inData) { if (_statesPool.Count == 0) { - serverState = new ServerStateData { Tick = diffHeader.Tick }; + serverState = new ServerStateData(this) { Tick = diffHeader.Tick }; } else { @@ -404,7 +400,7 @@ public unsafe DeserializeResult Deserialize(ReadOnlySpan inData) if (_readyStates.Count == MaxSavedStateDiff) { //one state should be already preloaded - _timer = _lerpTime; + _remoteInterpolationTimer = _remoteInterpolationTotalTime; //fast-forward GoToNextState(); @@ -444,8 +440,8 @@ private bool PreloadNextState() float upperBound = NetworkJitter * 1.5f + PreferredBufferTimeHighest; //tune buffer playing speed - _lerpTime = Utils.SequenceDiff(_stateB.Tick, _stateA.Tick) * DeltaTimeF; - _lerpTime *= 1 - GetSpeedMultiplier(LerpBufferTimeLength)*TimeSpeedChangeCoef; + _remoteInterpolationTotalTime = Utils.SequenceDiff(_stateB.Tick, _stateA.Tick) * DeltaTimeF; + _remoteInterpolationTotalTime *= 1 - GetSpeedMultiplier(LerpBufferTimeLength)*TimeSpeedChangeCoef; //tune game prediction and input generation speed SpeedMultiplier = GetSpeedMultiplier(_stateB.BufferedInputsCount * DeltaTimeF); @@ -486,15 +482,19 @@ private unsafe void GoToNextState() _statesPool.Enqueue(_stateA); _stateA = _stateB; _stateB = null; + ServerTick = _stateA.Tick; //Logger.Log($"GotoState: IST: {ServerTick}, TST:{_stateA.Tick}}"); + _remoteInterpolationTimer -= _remoteInterpolationTotalTime; + + _entitiesToRollback.Clear(); + foreach (var entity in _modifiedEntitiesToRollback) + _entitiesToRollback.Enqueue(entity); + //================== ReadEntityStates BEGIN ================== _changedEntities.Clear(); - ServerTick = _stateA.Tick; - IsExecutingRPC = true; - _stateA.ExecuteRpcs(this, minimalTick, false); - IsExecutingRPC = false; + _stateA.ExecuteRpcs(minimalTick, RPCExecuteMode.OnNextState); int readerPosition = _stateA.DataOffset; ReadDiff(ref readerPosition); ExecuteSyncCalls(_stateA); @@ -516,24 +516,18 @@ private unsafe void GoToNextState() _entitiesToRemove[_entitiesToRemoveCount] = null; i--; } - //================== ReadEntityStates END ==================== - _timer -= _lerpTime; - //================== Rollback part =========================== + //reset predicted entities - _entitiesToRollback.Clear(); - foreach (var entity in _modifiedEntitiesToRollback) - _entitiesToRollback.Enqueue(entity); - foreach (var entity in _entitiesToRollback) { ref var classData = ref ClassDataDict[entity.ClassId]; var rollbackFields = classData.GetRollbackFields(entity.IsLocalControlled); entity.OnBeforeRollback(); - fixed (byte* predictedData = classData.ClientPredictedData(entity)) + fixed (byte* lastServerData = classData.GetLastServerData(entity)) { for (int i = 0; i < rollbackFields.Length; i++) { @@ -541,11 +535,11 @@ private unsafe void GoToNextState() if (field.FieldType == FieldType.SyncableSyncVar) { var syncableField = RefMagic.GetFieldValue(entity, field.Offset); - field.TypeProcessor.SetFrom(syncableField, field.SyncableSyncVarOffset, predictedData + field.PredictedOffset); + field.TypeProcessor.SetFrom(syncableField, field.SyncableSyncVarOffset, lastServerData + field.PredictedOffset); } else { - field.TypeProcessor.SetFrom(entity, field.Offset, predictedData + field.PredictedOffset); + field.TypeProcessor.SetFrom(entity, field.Offset, lastServerData + field.PredictedOffset); } } } @@ -569,11 +563,22 @@ private unsafe void GoToNextState() foreach (var controller in GetEntities()) controller.ReadStoredInput(cmdNum); //simple update - //foreach (var entity in AliveEntities) foreach (var entity in _entitiesToRollback) { if (!entity.IsLocalControlled || !AliveEntities.Contains(entity)) continue; + + //update interpolation data + if (cmdNum == _storedInputHeaders.Count - 1) + { + ref var classData = ref ClassDataDict[entity.ClassId]; + for(int i = 0; i < classData.InterpolatedCount; i++) + { + ref var field = ref classData.Fields[i]; + field.TypeProcessor.SetInterpValueFromCurrentValue(entity, field.Offset); + } + } + entity.Update(); } } @@ -589,14 +594,25 @@ internal void MarkEntityChanged(InternalEntity entity) _modifiedEntitiesToRollback.Add(entity); } - internal override void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue) + internal override unsafe void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue, ref T oldValue) { - if (entity.IsLocal || entity.IsDestroyed) + if (entity.IsRemoved) return; ref var classData = ref ClassDataDict[entity.ClassId]; + ref var fieldInfo = ref classData.Fields[fieldId]; + + if ((fieldInfo.OnSyncFlags & BindOnChangeFlags.ExecuteOnPrediction) != 0) + { + T value = oldValue; + fieldInfo.OnSync(entity, new ReadOnlySpan(&value, sizeof(T))); + } + + if (entity.IsLocal) + return; + var rollbackFields = classData.GetRollbackFields(entity.IsLocalControlled); - if (rollbackFields != null && rollbackFields.Length > 0 && classData.Fields[fieldId].IsPredicted) + if (rollbackFields != null && rollbackFields.Length > 0 && fieldInfo.IsPredicted) _modifiedEntitiesToRollback.Add(entity); } @@ -616,25 +632,6 @@ internal override void OnEntityDestroyed(InternalEntity e) protected override void OnLogicTick() { - if (_stateB != null) - { - ServerTick = Utils.LerpSequence(_stateA.Tick, _stateB.Tick, (float)(_timer/_lerpTime)); - - foreach (var entity in AliveEntities) - { - if (!entity.IsLocal && !entity.IsLocalControlled) - continue; - - ref var classData = ref ClassDataDict[entity.ClassId]; - //save data for interpolation before update - for (int i = 0; i < classData.InterpolatedCount; i++) - { - ref var field = ref classData.Fields[i]; - field.TypeProcessor.SetInterpValueFromCurrentValue(entity, field.Offset); - } - } - } - //apply input var humanControllers = GetEntities(); if (humanControllers.Count > 0) @@ -643,7 +640,7 @@ protected override void OnLogicTick() { StateA = _stateA.Tick, StateB = RawTargetServerTick, - LerpMsec = _remoteLerpMsec + LerpMsec = _remoteLerpFactor })); foreach (var controller in humanControllers) { @@ -653,22 +650,20 @@ protected override void OnLogicTick() //local only and UpdateOnClient foreach (var entity in AliveEntities) - entity.Update(); - - if (_stateB != null) { - //execute rpcs and spawn entities - IsExecutingRPC = true; - _stateB.ExecuteRpcs(this, _stateA.Tick, false); - IsExecutingRPC = false; - ExecuteSyncCalls(_stateB); - } - - if (NetworkJitter > _jitterMiddle) - { - NetworkJitter -= DeltaTimeF * 0.1f; - if (NetworkJitter < MinJitter) - NetworkJitter = MinJitter; + //first logic tick in Update + if (_tick == _prevTick && (entity.IsLocal || entity.IsLocalControlled)) + { + ref var classData = ref ClassDataDict[entity.ClassId]; + //save data for interpolation before update + for (int i = 0; i < classData.InterpolatedCount; i++) + { + ref var field = ref classData.Fields[i]; + field.TypeProcessor.SetInterpValueFromCurrentValue(entity, field.Offset); + } + } + + entity.Update(); } } @@ -682,30 +677,39 @@ public override void Update() return; //logic update - ushort prevTick = _tick; - + _prevTick = _tick; + ServerTick = Utils.LerpSequence(_stateA.Tick, _stateB?.Tick ?? _stateA.Tick, _remoteLerpFactor); + base.Update(); + //reduce max jitter to middle + float dt = (float)VisualDeltaTime; + if (NetworkJitter > _jitterMiddle) + { + NetworkJitter -= dt * 0.1f; + if (NetworkJitter < MinJitter) + NetworkJitter = MinJitter; + } + //send buffered input - if (_tick != prevTick) + if (_tick != _prevTick) SendBufferedInput(); if (_stateB != null) { - _timer += VisualDeltaTime; + //execute rpcs and spawn entities + _stateB.ExecuteRpcs(_stateA.Tick, RPCExecuteMode.BetweenStates); + ExecuteSyncCalls(_stateB); - while(_timer >= _lerpTime) + while(_remoteInterpolationTimer >= _remoteInterpolationTotalTime) { GoToNextState(); if (!PreloadNextState()) break; } - - if (_stateB != null) - { - _remoteLerpMsec = (float)(_timer/_lerpTime); - _stateB.PreloadInterpolationForNewEntities(EntitiesDict); - } + _stateB?.PreloadInterpolationForNewEntities(); + _remoteLerpFactor = _remoteInterpolationTimer / _remoteInterpolationTotalTime; + _remoteInterpolationTimer += dt; } //local only and UpdateOnClient @@ -723,7 +727,7 @@ internal T GetInterpolatedValue(ref SyncVar syncVar, T interpValue) where var typeProcessor = (ValueTypeProcessor)ClassDataDict[syncVar.Container.ClassId].Fields[syncVar.FieldId].TypeProcessor; return syncVar.Container.IsLocalControlled ? typeProcessor.GetInterpolatedValue(interpValue, syncVar.Value, LerpFactor) - : typeProcessor.GetInterpolatedValue(syncVar.Value, interpValue, _remoteLerpMsec); + : typeProcessor.GetInterpolatedValue(syncVar.Value, interpValue, _remoteLerpFactor); } private unsafe void SendBufferedInput() @@ -735,7 +739,7 @@ private unsafe void SendBufferedInput() *(ushort*)(sendBuffer + 2) = Tick; *(InputPacketHeader*)(sendBuffer + 4) = new InputPacketHeader { - LerpMsec = _remoteLerpMsec, + LerpMsec = _remoteLerpFactor, StateA = _stateA.Tick, StateB = RawTargetServerTick }; @@ -832,10 +836,12 @@ internal unsafe void ReadNewRPC(ushort entityId, byte* rawData) } var entity = EntitiesDict[entityId]; - //Logger.Log($"[CEM] ReadBaseline Entity: {entityId} pos: {bytesRead}"); + //Logger.Log($"[CEM] New Entity: {entityId}"); //remove old entity if (entity != null && entity.Version != entityDataHeader.Version) { + //this should be impossible now? + //this can be only on logics (not on singletons) Logger.Log($"[CEM] Replace entity by new: {entityDataHeader.Version}. Class: {entityDataHeader.ClassId}. Id: {entityId}"); entity.DestroyInternal(); @@ -851,39 +857,6 @@ internal unsafe void ReadNewRPC(ushort entityId, byte* rawData) private void ExecuteSyncCalls(ServerStateData stateData) { - if (_entitiesToRollback.Count > 0) - { - //fast forward new but predicted entities - UpdateMode = UpdateMode.PredictionRollback; - ushort targetTick = _tick; - for(int cmdNum = Utils.SequenceDiff(ServerTick, _stateA.Tick); cmdNum < _storedInputHeaders.Count; cmdNum++) - { - //reapply input data - var storedInput = _storedInputHeaders[cmdNum]; - _localPlayer.LoadInputInfo(storedInput.Header); - _tick = storedInput.Tick; - foreach (var controller in GetEntities()) - controller.ReadStoredInput(cmdNum); - - foreach (var e in _entitiesToRollback) - { - if (cmdNum == _storedInputHeaders.Count - 1) - { - ref var classData = ref ClassDataDict[e.ClassId]; - for(int i = 0; i < classData.InterpolatedCount; i++) - { - ref var field = ref classData.Fields[i]; - field.TypeProcessor.SetInterpValueFromCurrentValue(e, field.Offset); - } - } - e.SafeUpdate(); - } - } - _tick = targetTick; - UpdateMode = UpdateMode.Normal; - _entitiesToRollback.Clear(); - } - ExecuteLateConstruct(); for (int i = 0; i < _syncCallsCount; i++) _syncCalls[i].Execute(stateData); @@ -904,7 +877,7 @@ internal unsafe void ReadConstructRPC(ushort entityId, byte* rawData, int reader ref var classData = ref entity.ClassData; Utils.ResizeOrCreate(ref _syncCalls, _syncCallsCount + classData.FieldsCount); - fixed (byte* predictedData = classData.ClientPredictedData(entity)) + fixed (byte* predictedData = classData.GetLastServerData(entity)) { for (int i = 0; i < classData.FieldsCount; i++) { @@ -920,17 +893,12 @@ internal unsafe void ReadConstructRPC(ushort entityId, byte* rawData, int reader } } - //Construct and fast forward predicted entities - if (ConstructEntity(entity) == false) - return; - - if (entity is EntityLogic entityLogic) - { - entityLogic.RefreshOwnerInfo(null); - if(entity.IsLocal || !entity.IsLocalControlled || !entityLogic.IsPredicted || !AliveEntities.Contains(entity)) - return; - _entitiesToRollback.Enqueue(entityLogic); - } + //add owned entities to rollback queue + if(!entity.IsConstructed && entity.InternalOwnerId.Value == InternalPlayerId) + _entitiesToRollback.Enqueue(entity); + + //Construct + ConstructEntity(entity); //Logger.Log($"ConstructedEntity: {entityId}, pid: {entityLogic.PredictedId}"); } @@ -961,7 +929,7 @@ private unsafe void ReadDiff(ref int readerPosition) Utils.ResizeOrCreate(ref _syncCalls, _syncCallsCount + classData.FieldsCount); int fieldsFlagsOffset = readerPosition - classData.FieldsFlagsSize; - fixed (byte* predictedData = classData.ClientPredictedData(entity)) + fixed (byte* predictedData = classData.GetLastServerData(entity)) { for (int i = 0; i < classData.FieldsCount; i++) { @@ -995,7 +963,7 @@ private static bool IsEntityIdValid(ushort id) public void GetDiagnosticData(Dictionary diagnosticDataDict) { diagnosticDataDict.Clear(); - _stateA?.GetDiagnosticData(EntitiesDict, ClassDataDict, diagnosticDataDict); + _stateA?.GetDiagnosticData(diagnosticDataDict); } } } \ No newline at end of file diff --git a/Assets/Plugins/LiteEntitySystem/EntityLogic.cs b/Assets/Plugins/LiteEntitySystem/EntityLogic.cs index 8578479..7128b08 100644 --- a/Assets/Plugins/LiteEntitySystem/EntityLogic.cs +++ b/Assets/Plugins/LiteEntitySystem/EntityLogic.cs @@ -169,17 +169,46 @@ public void DisableLagCompensationForOwner() => EntityManager.DisableLagCompensation(); /// - /// Get synchronized seed for random generators based on current tick. Can be used for rollback or inside RPCs + /// Gets owner tick on client if entity is owned, and on server it returns owner tick. + /// Useful as seed for random generators based on current tick. /// - /// current tick depending on entity manager state (IsExecutingRPC and InRollBackState) - public int GetFrameSeed() => - EntityManager.IsClient - ? (ClientManager.IsExecutingRPC ? ClientManager.CurrentRPCTick : EntityManager.Tick) - : (InternalOwnerId.Value == EntityManager.ServerPlayerId ? EntityManager.Tick : ServerManager.GetPlayer(InternalOwnerId).LastProcessedTick); + /// true if owner tick accessible, false if you trying get remote player tick + public bool TryGetOwnerTick(out ushort tick) + { + if (EntityManager.IsClient) + { + if (ClientManager.InternalPlayerId == InternalOwnerId.Value) + { + tick = EntityManager.Tick; + return true; + } + + if (InternalOwnerId.Value == EntityManager.ServerPlayerId) + { + tick = ClientManager.ServerTick; + return true; + } + + //else + tick = EntityManager.Tick; + return false; + } + + //Server + if(InternalOwnerId.Value == EntityManager.ServerPlayerId) + { + tick = EntityManager.Tick; + return true; + } + + tick = ServerManager.GetPlayer(InternalOwnerId).LastProcessedTick; + return true; + } /// /// Create predicted entity (like projectile) that will be replaced by server entity if prediction is successful /// Should be called also in rollback mode + /// Don't call this method inside Server->Client RPC! This will break many things. /// /// Entity type /// Method that will be called after entity constructed @@ -406,11 +435,9 @@ protected virtual void OnChildRemoved(EntityLogic child) } - internal void RefreshOwnerInfo(EntityLogic oldOwner) + private void OnOwnerChanged(byte oldPlayerId) { - if (EntityManager.IsServer || IsLocal) - return; - if(oldOwner != null && oldOwner.InternalOwnerId.Value == EntityManager.InternalPlayerId) + if(oldPlayerId == EntityManager.InternalPlayerId) ClientManager.RemoveOwned(this); if(InternalOwnerId.Value == EntityManager.InternalPlayerId) ClientManager.AddOwned(this); @@ -420,7 +447,8 @@ protected override void RegisterRPC(ref RPCRegistrator r) { base.RegisterRPC(ref r); - r.BindOnChange(ref _isSyncEnabled, (e, _) => e.OnSyncGroupsChanged(e._isSyncEnabled.Value)); + r.BindOnChange(ref _isSyncEnabled, (e, _) => e.OnSyncGroupsChanged(e._isSyncEnabled.Value)); + r.BindOnChange(this, ref InternalOwnerId, OnOwnerChanged); r.CreateRPCAction( (e, data) => @@ -429,7 +457,6 @@ protected override void RegisterRPC(ref RPCRegistrator r) { case ChangeType.Parent: var oldOwner = e.EntityManager.GetEntityById(data.Ref); - e.RefreshOwnerInfo(oldOwner); e.OnParentChanged(oldOwner); break; case ChangeType.ChildAdd: diff --git a/Assets/Plugins/LiteEntitySystem/EntityManager.cs b/Assets/Plugins/LiteEntitySystem/EntityManager.cs index 794bb09..51ed628 100644 --- a/Assets/Plugins/LiteEntitySystem/EntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/EntityManager.cs @@ -101,6 +101,9 @@ public abstract class EntityManager /// public const int MaxLocalEntityCount = MaxEntityCount - MaxSyncedEntityCount; + /// + /// Server player Id - always 0 and reserved + /// public const byte ServerPlayerId = 0; /// @@ -168,6 +171,9 @@ public abstract class EntityManager /// public byte PlayerId => InternalPlayerId; + /// + /// EntityManager packets header byte (can be used to distinguish LES packets from custom made) + /// public readonly byte HeaderByte; public bool InRollBackState => UpdateMode == UpdateMode.PredictionRollback; @@ -177,7 +183,16 @@ public abstract class EntityManager internal const int MaxParts = 256; private const int MaxTicksPerUpdate = 5; + /// + /// Delta time between visual Updates + /// public double VisualDeltaTime { get; private set; } + + /// + /// Delta time between visual Updates (float) + /// + public float VisualDeltaTimeF => (float)VisualDeltaTime; + public const int MaxPlayers = byte.MaxValue-1; protected ushort _tick; @@ -529,11 +544,11 @@ protected T AddEntity(EntityParams entityParams) where T : InternalEntity return entity; } - protected bool ConstructEntity(InternalEntity e) + protected void ConstructEntity(InternalEntity e) { if (e.IsConstructed) - return false; - e.IsConstructed = true; + return; + e.ConstructInternal(); ref var classData = ref ClassDataDict[e.ClassId]; if (classData.IsSingleton) @@ -560,8 +575,6 @@ protected bool ConstructEntity(InternalEntity e) AliveEntities.Add(e); } _entitiesToLateConstruct.Enqueue(e); - - return true; } protected static bool IsEntityLagCompensated(InternalEntity e) @@ -605,7 +618,7 @@ protected void RemoveEntity(InternalEntity e) EntitiesDict[e.Id] = null; EntitiesCount--; ClassDataDict[e.ClassId].ReleaseDataCache(e); - e.IsRemoved = true; + e.Remove(); //Logger.Log($"{Mode} - RemoveEntity: {e.Id}"); } @@ -645,7 +658,7 @@ protected void ExecuteLateConstruct() protected abstract void OnLogicTick(); - internal abstract void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue) + internal abstract void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue, ref T oldValue) where T : unmanaged; /// diff --git a/Assets/Plugins/LiteEntitySystem/HumanControllerLogic.cs b/Assets/Plugins/LiteEntitySystem/HumanControllerLogic.cs index bb386fe..8f39138 100644 --- a/Assets/Plugins/LiteEntitySystem/HumanControllerLogic.cs +++ b/Assets/Plugins/LiteEntitySystem/HumanControllerLogic.cs @@ -240,6 +240,12 @@ public InputCommand(ushort tick, TInput data) //server part internal readonly SequenceBinaryHeap AvailableInput; + /// + /// Returns default new input for custom cases where not all values should be 0 + /// + /// New clean input + protected virtual TInput GetDefaultInput() => default; + /// /// Get pending input reference for modifications /// Pending input resets to default after it was assigned to CurrentInput inside logic tick @@ -249,7 +255,7 @@ protected ref TInput ModifyPendingInput() { if (_shouldResetInput) { - _pendingInput = default; + _pendingInput = GetDefaultInput(); _shouldResetInput = false; } return ref _pendingInput; @@ -276,6 +282,9 @@ protected HumanControllerLogic(EntityParams entityParams) : base(entityParams, U { AvailableInput = new(ServerEntityManager.MaxStoredInputs); } + + // ReSharper disable once VirtualMemberCallInConstructor + _currentInput = GetDefaultInput(); } internal override void RemoveClientProcessedInputs(ushort processedTick) @@ -320,7 +329,7 @@ internal override void ApplyIncomingInput(ushort tick) else if (seqDiff == 0) { //Set input if tick equals - CurrentInput = AvailableInput.ExtractMin(); + _currentInput = AvailableInput.ExtractMin(); break; } } diff --git a/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs b/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs index e63a59e..ad12b1b 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs @@ -87,7 +87,7 @@ internal struct EntityClassData private readonly int _maxHistoryCount; private readonly int _historyStart; - public Span ClientPredictedData(InternalEntity e) => new (e.IOBuffer, 0, PredictedSize); + public Span GetLastServerData(InternalEntity e) => new (e.IOBuffer, 0, PredictedSize); public EntityFieldInfo[] GetRollbackFields(bool isOwned) => isOwned ? _ownedRollbackFields : _remoteRollbackFields; diff --git a/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs b/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs index 3789595..bd31d0b 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs @@ -19,6 +19,7 @@ internal struct EntityFieldInfo public readonly bool IsPredicted; public MethodCallDelegate OnSync; + public BindOnChangeFlags OnSyncFlags; public int FixedOffset; public int PredictedOffset; @@ -44,6 +45,7 @@ private EntityFieldInfo( SyncVarFlags flags, FieldType fieldType) { + OnSyncFlags = 0; Name = name; TypeProcessor = valueTypeProcessor; SyncableSyncVarOffset = syncableSyncVarOffset; @@ -75,7 +77,7 @@ public unsafe bool ReadField( } else { - if (OnSync != null) + if (OnSync != null && (OnSyncFlags & BindOnChangeFlags.ExecuteOnSync) != 0) { if (TypeProcessor.SetFromAndSync(entity, Offset, rawData)) return true; //create sync call diff --git a/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs b/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs index 053bb30..0ac23bf 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs @@ -4,11 +4,21 @@ namespace LiteEntitySystem.Internal { + public enum EntityState + { + New = 0, + Constructed = 1, + Destroyed = 2, + Removed = 3 + } + public abstract class InternalEntity : InternalBaseClass, IComparable { [SyncVarFlags(SyncFlags.NeverRollBack)] internal SyncVar InternalOwnerId; + private EntityState _entityState; + internal byte[] IOBuffer; internal readonly int UpdateOrderNum; @@ -22,7 +32,6 @@ public abstract class InternalEntity : InternalBaseClass, IComparable public readonly ushort Id; - /// /// Entity manager @@ -44,7 +53,7 @@ public abstract class InternalEntity : InternalBaseClass, IComparable public readonly byte Version; - internal EntityDataHeader DataHeader => new EntityDataHeader + internal EntityDataHeader DataHeader => new ( ClassId, Version, @@ -54,7 +63,7 @@ public abstract class InternalEntity : InternalBaseClass, IComparable /// Is entity is destroyed /// - public bool IsDestroyed { get; private set; } + public bool IsDestroyed => _entityState >= EntityState.Destroyed; /// /// Is entity local controlled @@ -103,12 +112,17 @@ public abstract class InternalEntity : InternalBaseClass, IComparable /// Is entity constructed (OnConstruct called) /// - public bool IsConstructed { get; internal set; } + public bool IsConstructed => _entityState >= EntityState.Constructed; /// /// Is entity released and not used after destroy. /// - public bool IsRemoved { get; internal set; } + public bool IsRemoved => _entityState == EntityState.Removed; + + /// + /// Entity state. New, Constructed, Destroyed, Removed + /// + public EntityState State => _entityState; /// /// Destroy entity @@ -132,11 +146,27 @@ internal virtual void DestroyInternal() { if (IsDestroyed) return; - IsDestroyed = true; + _entityState = EntityState.Destroyed; EntityManager.OnEntityDestroyed(this); OnDestroy(); } + internal void ConstructInternal() + { + if (_entityState != EntityState.New) + Logger.LogError($"Error! Calling construct on not new entity: {this}"); + + _entityState = EntityState.Constructed; + } + + internal void Remove() + { + if (_entityState != EntityState.Destroyed) + Logger.LogError($"Error! Calling remove on not destroyed entity: {this}"); + + _entityState = EntityState.Removed; + } + internal void SafeUpdate() { try diff --git a/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs b/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs index 8ab74a7..084f71d 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs @@ -20,17 +20,19 @@ internal sealed class RemoteCallPacket public int TotalSize => RpcDeltaCompressor.MaxDeltaSize + Header.ByteCount; - public const int ReserverdRPCsCount = 3; + public const int ReservedRPCsCount = 4; + public const ushort NewRPCId = 0; - public const ushort ConstructRPCId = 1; - public const ushort DestroyRPCId = 2; + public const ushort NewOwnedRPCId = 1; + public const ushort ConstructRPCId = 2; + public const ushort DestroyRPCId = 3; //can be static because doesnt use any buffers private static DeltaCompressor RpcDeltaCompressor = new(Utils.SizeOfStruct()); public static void InitReservedRPCs(List rpcCache) { - for(int i = 0; i < ReserverdRPCsCount; i++) + for(int i = 0; i < ReservedRPCsCount; i++) rpcCache.Add(new RpcFieldInfo(-1, null)); } diff --git a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs index 270ff8d..14bf47c 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs @@ -5,24 +5,13 @@ namespace LiteEntitySystem.Internal { - internal readonly struct InterpolatedCache + internal enum RPCExecuteMode { - public readonly InternalEntity Entity; - public readonly int FieldOffset; - public readonly int FieldFixedOffset; - public readonly ValueTypeProcessor TypeProcessor; - public readonly int StateReaderOffset; - - public InterpolatedCache(InternalEntity entity, ref EntityFieldInfo field, int offset) - { - Entity = entity; - FieldOffset = field.Offset; - FieldFixedOffset = field.FixedOffset; - TypeProcessor = field.TypeProcessor; - StateReaderOffset = offset; - } + FirstSync, + BetweenStates, + OnNextState } - + internal readonly struct EntityDataCache { public readonly ushort EntityId; @@ -35,6 +24,20 @@ public EntityDataCache(ushort entityId, int offset) } } + internal readonly struct RemoteCallInfo + { + public readonly RPCHeader Header; + public readonly int DataOffset; + public readonly bool ExecuteOnNextState; + + public RemoteCallInfo(RPCHeader header, int dataOffset, bool executeOnNextState) + { + Header = header; + DataOffset = dataOffset; + ExecuteOnNextState = executeOnNextState; + } + } + internal class ServerStateData { private const int DefaultCacheSize = 32; @@ -54,52 +57,57 @@ internal class ServerStateData private int _dataOffset; private int _dataSize; - private int _rpcReadPos; - private int _rpcEndPos; private EntityDataCache[] _nullEntitiesData = new EntityDataCache[DefaultCacheSize]; + private RemoteCallInfo[] _remoteCallInfos = new RemoteCallInfo[DefaultCacheSize]; + private int _nullEntitiesCount; + private int _remoteCallsCount; + private int _rpcIndex; public int DataOffset => _dataOffset; public int DataSize => _dataSize; - private readonly HashSet _syncablesSet = new(); + [ThreadStatic] + private static HashSet SyncablesBufferSet; + + [ThreadStatic] + private static HashSet LocalEntitiesBuffer; + private DeltaCompressor _rpcDeltaCompressor = new (Utils.SizeOfStruct()); - public ServerStateData() => - _rpcDeltaCompressor.Init(); + private readonly ClientEntityManager _entityManager; + + public ServerStateData(ClientEntityManager entityManager) + { + _entityManager = entityManager; + } - public unsafe void GetDiagnosticData( - InternalEntity[] entityDict, - EntityClassData[] classDatas, - Dictionary diagnosticDataDict) + public unsafe void GetDiagnosticData(Dictionary diagnosticDataDict) { - fixed (byte* rawData = Data) + for (int i = 0; i < _remoteCallsCount; i++) { - int readPos = 0; - while (readPos < _rpcEndPos) - { - if (_rpcEndPos - readPos < sizeof(RPCHeader)) - break; - var header = *(RPCHeader*)(rawData + readPos); - int rpcSize = header.ByteCount + sizeof(RPCHeader); - readPos += rpcSize; + var header = _remoteCallInfos[i].Header; + int rpcSize = header.ByteCount + sizeof(RPCHeader); + int dictId = ushort.MaxValue + header.Id; - int dictId = ushort.MaxValue + header.Id; - - if(!diagnosticDataDict.TryGetValue(dictId, out LESDiagnosticDataEntry entry)) - { - entry = new LESDiagnosticDataEntry { IsRPC = true, Count = 1, Name = $"{header.Id}", Size = rpcSize }; - } - else - { - entry.Count++; - entry.Size += rpcSize; - } - diagnosticDataDict[dictId] = entry; + if (!diagnosticDataDict.TryGetValue(dictId, out LESDiagnosticDataEntry entry)) + { + entry = new LESDiagnosticDataEntry + { IsRPC = true, Count = 1, Name = $"{header.Id}", Size = rpcSize }; } + else + { + entry.Count++; + entry.Size += rpcSize; + } + + diagnosticDataDict[dictId] = entry; } + var entityDict = _entityManager.EntitiesDict; + var classDatas = _entityManager.ClassDataDict; + for (int bytesRead = _dataOffset; bytesRead < _dataOffset + _dataSize;) { int initialReaderPosition = bytesRead; @@ -180,11 +188,11 @@ private unsafe void PreloadInterpolation(InternalEntity entity, int offset) } } - public void PreloadInterpolationForNewEntities(InternalEntity[] entityDict) + public void PreloadInterpolationForNewEntities() { for (int i = 0; i < _nullEntitiesCount; i++) { - var entity = entityDict[_nullEntitiesData[i].EntityId]; + var entity = _entityManager.EntitiesDict[_nullEntitiesData[i].EntityId]; if (entity == null) continue; @@ -199,82 +207,93 @@ public void PreloadInterpolationForNewEntities(InternalEntity[] entityDict) } } - public unsafe void ExecuteRpcs(ClientEntityManager entityManager, ushort minimalTick, bool firstSync) + public unsafe void ExecuteRpcs(ushort minimalTick, RPCExecuteMode executeMode) { - _syncablesSet.Clear(); + if (SyncablesBufferSet == null) + SyncablesBufferSet = new HashSet(); + else + SyncablesBufferSet.Clear(); + //if(_remoteCallsCount > 0) // Logger.Log($"Executing rpcs (ST: {Tick}) for tick: {entityManager.ServerTick}, Min: {minimalTick}, Count: {_remoteCallsCount}"); + + int initialRpcIndex = _rpcIndex; + if (executeMode == RPCExecuteMode.OnNextState) + _rpcIndex = 0; + + _entityManager.IsExecutingRPC = true; + fixed (byte* rawData = Data) { - while (_rpcReadPos < _rpcEndPos) + for(;_rpcIndex < _remoteCallsCount; _rpcIndex++) { - if (_rpcEndPos - _rpcReadPos < _rpcDeltaCompressor.MinDeltaSize) - { - Logger.LogError("Broken rpcs sizes?"); - break; - } + var remoteCallInfo = _remoteCallInfos[_rpcIndex]; + var header = remoteCallInfo.Header; - RPCHeader header = new(); - int encodedSize = _rpcDeltaCompressor.Decode( - new ReadOnlySpan(rawData + _rpcReadPos, _rpcDeltaCompressor.MaxDeltaSize), - new Span(&header, sizeof(RPCHeader))); - - if (!firstSync) + if (executeMode != RPCExecuteMode.FirstSync) { - if (Utils.SequenceDiff(header.Tick, entityManager.ServerTick) > 0) + if (executeMode == RPCExecuteMode.BetweenStates) + { + if (remoteCallInfo.ExecuteOnNextState) + { + continue; + } + if (Utils.SequenceDiff(header.Tick, _entityManager.ServerTick) > 0) + { + //Logger.Log($"Skip rpc. Entity: {header.EntityId}. Tick {header.Tick} > ServerTick: {entityManager.ServerTick}. Id: {header.Id}."); + break; + } + } + //skip executed inside interpolation + else if (executeMode == RPCExecuteMode.OnNextState && remoteCallInfo.ExecuteOnNextState == false && _rpcIndex < initialRpcIndex) { - //Logger.Log($"Skip rpc. Entity: {header.EntityId}. Tick {header.Tick} > ServerTick: {entityManager.ServerTick}. Id: {header.Id}."); - break; + //Logger.Log($"Skip rpc. Entity: {header.EntityId}. _rpcIndex {_rpcIndex} < initialRpcIndex: {initialRpcIndex}. Id: {header.Id}."); + continue; } if (Utils.SequenceDiff(header.Tick, minimalTick) <= 0) { - _rpcReadPos += header.ByteCount + encodedSize; //Logger.Log($"Skip rpc. Entity: {header.EntityId}. Tick {header.Tick} <= MinimalTick: {minimalTick}. Id: {header.Id}. StateATick: {entityManager.RawServerTick}. StateBTick: {entityManager.RawTargetServerTick}"); continue; } } - - int rpcDataStart = _rpcReadPos + encodedSize; - _rpcReadPos += encodedSize + header.ByteCount; + if (header.Id == RemoteCallPacket.NewRPCId || + header.Id == RemoteCallPacket.NewOwnedRPCId) + { + _entityManager.ReadNewRPC(header.EntityId, rawData + remoteCallInfo.DataOffset); + continue; + } + //Logger.Log($"Executing rpc. Entity: {header.EntityId}. Tick {header.Tick}. Id: {header.Id}"); - var entity = entityManager.EntitiesDict[header.EntityId]; + var entity = _entityManager.EntitiesDict[header.EntityId]; if (entity == null) { - if (header.Id == RemoteCallPacket.NewRPCId) - { - entityManager.ReadNewRPC(header.EntityId, rawData + rpcDataStart); - continue; - } - Logger.LogError($"Entity is null: {header.EntityId}. RPCId: {header.Id}"); continue; } - entityManager.CurrentRPCTick = header.Tick; - - var rpcFieldInfo = entityManager.ClassDataDict[entity.ClassId].RemoteCallsClient[header.Id]; + var rpcFieldInfo = _entityManager.ClassDataDict[entity.ClassId].RemoteCallsClient[header.Id]; if (rpcFieldInfo.SyncableOffset == -1) { try { - if (header.Id == RemoteCallPacket.NewRPCId) - { - entityManager.ReadNewRPC(header.EntityId, rawData + rpcDataStart); - } - else if (header.Id == RemoteCallPacket.ConstructRPCId) + switch (header.Id) { - //Logger.Log($"ConstructRPC for entity: {header.EntityId}, RpcReadPos: {_rpcReadPos}, Tick: {header.Tick}"); - entityManager.ReadConstructRPC(header.EntityId, rawData, rpcDataStart); - } - else if (header.Id == RemoteCallPacket.DestroyRPCId) - { - entity.DestroyInternal(); - } - else - { - rpcFieldInfo.Method(entity, new ReadOnlySpan(rawData + rpcDataStart, header.ByteCount)); + case RemoteCallPacket.ConstructRPCId: + //Logger.Log($"ConstructRPC for entity: {header.EntityId}, Size: {header.ByteCount}, RpcReadPos: {remoteCallInfo.DataOffset}, Tick: {header.Tick}"); + //Logger.Log($"CRPCData: {Utils.BytesToHexString(new ReadOnlySpan(rawData + remoteCallInfo.DataOffset, header.ByteCount))}"); + _entityManager.ReadConstructRPC(header.EntityId, rawData, remoteCallInfo.DataOffset); + break; + + case RemoteCallPacket.DestroyRPCId: + //Logger.Log($"DestroyRPC for {header.EntityId}"); + entity.DestroyInternal(); + break; + + default: + rpcFieldInfo.Method(entity, new ReadOnlySpan(rawData + remoteCallInfo.DataOffset, header.ByteCount)); + break; } } catch (Exception e) @@ -285,13 +304,12 @@ public unsafe void ExecuteRpcs(ClientEntityManager entityManager, ushort minimal else { var syncableField = RefMagic.GetFieldValue(entity, rpcFieldInfo.SyncableOffset); - if (syncableField is SyncableFieldCustomRollback sf && _syncablesSet.Add(sf)) - { + if (syncableField is SyncableFieldCustomRollback sf && SyncablesBufferSet.Add(sf)) sf.BeforeReadRPC(); - } + try { - rpcFieldInfo.Method(syncableField, new ReadOnlySpan(rawData + rpcDataStart, header.ByteCount)); + rpcFieldInfo.Method(syncableField, new ReadOnlySpan(rawData + remoteCallInfo.DataOffset, header.ByteCount)); } catch (Exception e) { @@ -300,8 +318,11 @@ public unsafe void ExecuteRpcs(ClientEntityManager entityManager, ushort minimal } } } - foreach (var syncableField in _syncablesSet) + + foreach (var syncableField in SyncablesBufferSet) syncableField.AfterReadRPC(); + + _entityManager.IsExecutingRPC = false; } public void Reset(ushort tick) @@ -314,8 +335,58 @@ public void Reset(ushort tick) Size = 0; _partMtu = 0; _nullEntitiesCount = 0; - _rpcReadPos = 0; + } + + private unsafe void PreloadRPCs(int rpcsSize) + { + if (LocalEntitiesBuffer == null) + LocalEntitiesBuffer = new HashSet(); + else + LocalEntitiesBuffer.Clear(); + + //parse and preload rpc infos + int rpcReadPos = 0; + _rpcIndex = 0; + _remoteCallsCount = 0; _rpcDeltaCompressor.Init(); + while (rpcReadPos < rpcsSize) + { + if (rpcsSize - rpcReadPos < _rpcDeltaCompressor.MinDeltaSize) + { + Logger.LogError("Broken rpcs sizes?"); + break; + } + + RPCHeader header = new(); + int encodedHeaderSize = _rpcDeltaCompressor.Decode( + new ReadOnlySpan(Data, rpcReadPos, rpcsSize - rpcReadPos), + new Span(&header, sizeof(RPCHeader))); + + //Logger.Log($"ReadRPC: EID: {header.EntityId}, RPCID: {header.Id}, BC: {header.ByteCount}"); + + rpcReadPos += encodedHeaderSize; + + bool executeOnNextState; + if (LocalEntitiesBuffer.Contains(header.EntityId)) + { + executeOnNextState = true; + } + else if (header.Id == RemoteCallPacket.NewOwnedRPCId || + _entityManager.EntitiesDict[header.EntityId]?.InternalOwnerId.Value == _entityManager.InternalPlayerId) + { + LocalEntitiesBuffer.Add(header.EntityId); + executeOnNextState = true; + } + else + { + executeOnNextState = false; + } + + Utils.ResizeIfFull(ref _remoteCallInfos, _remoteCallsCount); + _remoteCallInfos[_remoteCallsCount++] = new RemoteCallInfo(header, rpcReadPos, executeOnNextState); + + rpcReadPos += header.ByteCount; + } } public unsafe bool ReadBaseline(BaselineDataHeader header, byte* rawData, int fullSize) @@ -325,8 +396,6 @@ public unsafe bool ReadBaseline(BaselineDataHeader header, byte* rawData, int fu Data = new byte[header.OriginalLength]; _dataOffset = header.EventsSize; _dataSize = header.OriginalLength - header.EventsSize; - _rpcEndPos = header.EventsSize; - _rpcReadPos = 0; fixed (byte* stateData = Data) { int decodedBytes = LZ4Codec.Decode( @@ -340,6 +409,7 @@ public unsafe bool ReadBaseline(BaselineDataHeader header, byte* rawData, int fu return false; } } + PreloadRPCs(header.EventsSize); return true; } @@ -360,7 +430,6 @@ public unsafe bool ReadPart(DiffPartHeader partHeader, byte* rawData, int partSi ProcessedTick = lastPartData.LastProcessedTick; BufferedInputsCount = lastPartData.BufferedInputsCount; _dataOffset = lastPartData.EventsSize; - _rpcEndPos = lastPartData.EventsSize; //Logger.Log($"TPC: {partHeader.Part} {_partMtu}, LastReceivedTick: {LastReceivedTick}, LastProcessedTick: {ProcessedTick}"); } partSize -= sizeof(DiffPartHeader); @@ -379,6 +448,8 @@ public unsafe bool ReadPart(DiffPartHeader partHeader, byte* rawData, int partSi if (_receivedPartsCount == _totalPartsCount) { _dataSize = Size - _dataOffset; + //rpc before data - so data offset equals size + PreloadRPCs(_dataOffset); return true; } return false; diff --git a/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs b/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs index 5e45c9c..3e6c352 100644 --- a/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs +++ b/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs @@ -5,6 +5,30 @@ namespace LiteEntitySystem { + [Flags] + public enum BindOnChangeFlags + { + /// + /// Execute in OnSync stage. Mostly useful for RemoteControlled entities to trigger change notification + /// + ExecuteOnSync = 1 << 0, + + /// + /// Execute on local prediction on Client + /// + ExecuteOnPrediction = 1 << 2, + + /// + /// Execute when value changed on server + /// + ExecuteOnServer = 1 << 3, + + /// + /// Combines ExecuteOnSync, ExecuteOnPrediction and ExecuteOnServer flags + /// + ExecuteAlways = ExecuteOnSync | ExecuteOnPrediction | ExecuteOnServer + } + public delegate void SpanAction(ReadOnlySpan data); public delegate void SpanAction(TCaller caller, ReadOnlySpan data); internal delegate void MethodCallDelegate(object classPtr, ReadOnlySpan buffer); @@ -116,9 +140,10 @@ internal static void CheckTarget(object ent, object target) /// /// Variable to bind /// Action that will be called when variable changes by sync - public void BindOnChange(ref SyncVar syncVar, Action onChangedAction) where T : unmanaged where TEntity : InternalEntity + public void BindOnChange(ref SyncVar syncVar, Action onChangedAction, BindOnChangeFlags flags = BindOnChangeFlags.ExecuteOnSync) where T : unmanaged where TEntity : InternalEntity { _fields[syncVar.FieldId].OnSync = RemoteCall.CreateMCD(onChangedAction); + _fields[syncVar.FieldId].OnSyncFlags = flags; } /// @@ -127,10 +152,10 @@ public void BindOnChange(ref SyncVar syncVar, Action /// Target entity for binding /// Variable to bind /// Action that will be called when variable changes by sync - public void BindOnChange(TEntity self, ref SyncVar syncVar, Action onChangedAction) where T : unmanaged where TEntity : InternalEntity + public void BindOnChange(TEntity self, ref SyncVar syncVar, Action onChangedAction, BindOnChangeFlags flags = BindOnChangeFlags.ExecuteOnSync) where T : unmanaged where TEntity : InternalEntity { CheckTarget(self, onChangedAction.Target); - BindOnChange(ref syncVar, onChangedAction.Method.CreateDelegateHelper>()); + BindOnChange(ref syncVar, onChangedAction.Method.CreateDelegateHelper>(), flags); } /// diff --git a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs index 3788849..752026b 100644 --- a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs @@ -832,11 +832,11 @@ bool ShouldSendRPC(RemoteCallPacket rpcNode, NetPlayer player) return false; var entity = EntitiesDict[rpcNode.Header.EntityId]; - if (!rpcNode.AllowToSendForPlayer(player.Id, entity.OwnerId)) + if (!rpcNode.AllowToSendForPlayer(player.Id, entity.InternalOwnerId.Value)) return false; //check sync groups - if (entity.OwnerId != player.Id) + if (entity.InternalOwnerId.Value != player.Id) { if (entity is EntityLogic el && player.EntitySyncInfo.TryGetValue(el, out var syncGroups) && @@ -847,15 +847,25 @@ bool ShouldSendRPC(RemoteCallPacket rpcNode, NetPlayer player) return false; } } - - if (rpcNode.Header.Id == RemoteCallPacket.ConstructRPCId) - stateSerializer.RefreshSyncGroupsVariable(player, new Span(rpcNode.Data)); + + switch (rpcNode.Header.Id) + { + case RemoteCallPacket.NewOwnedRPCId when entity.InternalOwnerId.Value != player.Id: + rpcNode.Header.Id = RemoteCallPacket.NewRPCId; + break; + case RemoteCallPacket.NewRPCId when entity.InternalOwnerId.Value == player.Id: + rpcNode.Header.Id = RemoteCallPacket.NewOwnedRPCId; + break; + case RemoteCallPacket.ConstructRPCId: + stateSerializer.RefreshSyncGroupsVariable(player, new Span(rpcNode.Data)); + break; + } return true; } } - internal override void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue) + internal override unsafe void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue, ref T oldValue) { if (entity.IsRemoved) { @@ -867,6 +877,13 @@ internal override void EntityFieldChanged(InternalEntity entity, ushort field _changedEntities.Add(entity); _stateSerializers[entity.Id].UpdateFieldValue(fieldId, _minimalTick, _tick, ref newValue); + + ref var fieldInfo = ref entity.ClassData.Fields[fieldId]; + if ((fieldInfo.OnSyncFlags & BindOnChangeFlags.ExecuteOnServer) != 0) + { + T value = oldValue; + fieldInfo.OnSync(entity, new ReadOnlySpan(&value, sizeof(T))); + } } internal void MarkFieldsChanged(InternalEntity entity, SyncFlags onlyWithFlags) diff --git a/Assets/Plugins/LiteEntitySystem/SyncVar.cs b/Assets/Plugins/LiteEntitySystem/SyncVar.cs index f6c69ab..d0c6f52 100644 --- a/Assets/Plugins/LiteEntitySystem/SyncVar.cs +++ b/Assets/Plugins/LiteEntitySystem/SyncVar.cs @@ -121,9 +121,10 @@ public T Value get => _value; set { - if (Container != null && !Utils.FastEquals(ref value, ref _value)) - Container.EntityManager.EntityFieldChanged(Container, FieldId, ref value); + var oldValue = _value; _value = value; + if (Container != null && !Utils.FastEquals(ref value, ref oldValue)) + Container.EntityManager.EntityFieldChanged(Container, FieldId, ref value, ref oldValue); } } @@ -131,7 +132,9 @@ internal void Init(InternalEntity container, ushort fieldId) { Container = container; FieldId = fieldId; - Container.EntityManager.EntityFieldChanged(Container, FieldId, ref _value); + T defaultValue = default; + if(!Utils.FastEquals(ref _value, ref defaultValue)) + Container.EntityManager.EntityFieldChanged(Container, FieldId, ref _value, ref defaultValue); } public static implicit operator T(SyncVar sv) => sv._value; From 4ecf5f20de136ebcc3885e53da6df7338dab0682 Mon Sep 17 00:00:00 2001 From: RevenantX Date: Fri, 11 Jul 2025 08:30:55 +0300 Subject: [PATCH 12/22] upd --- .../LiteEntitySystem/ClientEntityManager.cs | 61 +++++++++---------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 4177d86..76ee207 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -340,8 +340,7 @@ public unsafe DeserializeResult Deserialize(ReadOnlySpan inData) _jitterTimer.Reset(); _stateA.ExecuteRpcs((ushort)(_stateA.Tick - 1),RPCExecuteMode.FirstSync); - int readerPosition = _stateA.DataOffset; - ReadDiff(ref readerPosition); + ReadDiff(); ExecuteSyncCalls(_stateA); foreach (var lagCompensatedEntity in LagCompensatedEntities) ClassDataDict[lagCompensatedEntity.ClassId].WriteHistory(lagCompensatedEntity, ServerTick); @@ -488,38 +487,10 @@ private unsafe void GoToNextState() _remoteInterpolationTimer -= _remoteInterpolationTotalTime; + //================== Rollback part =========================== _entitiesToRollback.Clear(); foreach (var entity in _modifiedEntitiesToRollback) _entitiesToRollback.Enqueue(entity); - - //================== ReadEntityStates BEGIN ================== - _changedEntities.Clear(); - _stateA.ExecuteRpcs(minimalTick, RPCExecuteMode.OnNextState); - int readerPosition = _stateA.DataOffset; - ReadDiff(ref readerPosition); - ExecuteSyncCalls(_stateA); - foreach (var lagCompensatedEntity in LagCompensatedEntities) - ClassDataDict[lagCompensatedEntity.ClassId].WriteHistory(lagCompensatedEntity, ServerTick); - - for(int i = 0; i < _entitiesToRemoveCount; i++) - { - //skip changed - var entityToRemove = _entitiesToRemove[i]; - if (_changedEntities.Contains(entityToRemove)) - continue; - - //Logger.Log($"[CLI] RemovingEntity: {_entitiesToRemove[i].Id}"); - RemoveEntity(entityToRemove); - - _entitiesToRemoveCount--; - _entitiesToRemove[i] = _entitiesToRemove[_entitiesToRemoveCount]; - _entitiesToRemove[_entitiesToRemoveCount] = null; - i--; - } - //================== ReadEntityStates END ==================== - - //================== Rollback part =========================== - //reset predicted entities foreach (var entity in _entitiesToRollback) { @@ -548,6 +519,31 @@ private unsafe void GoToNextState() entity.OnRollback(); } + //================== ReadEntityStates BEGIN ================== + _changedEntities.Clear(); + _stateA.ExecuteRpcs(minimalTick, RPCExecuteMode.OnNextState); + ReadDiff(); + ExecuteSyncCalls(_stateA); + foreach (var lagCompensatedEntity in LagCompensatedEntities) + ClassDataDict[lagCompensatedEntity.ClassId].WriteHistory(lagCompensatedEntity, ServerTick); + + for(int i = 0; i < _entitiesToRemoveCount; i++) + { + //skip changed + var entityToRemove = _entitiesToRemove[i]; + if (_changedEntities.Contains(entityToRemove)) + continue; + + //Logger.Log($"[CLI] RemovingEntity: {_entitiesToRemove[i].Id}"); + RemoveEntity(entityToRemove); + + _entitiesToRemoveCount--; + _entitiesToRemove[i] = _entitiesToRemove[_entitiesToRemoveCount]; + _entitiesToRemove[_entitiesToRemoveCount] = null; + i--; + } + //================== ReadEntityStates END ==================== + //clear modified here to readd changes after RollbackUpdate _modifiedEntitiesToRollback.Clear(); @@ -903,8 +899,9 @@ internal unsafe void ReadConstructRPC(ushort entityId, byte* rawData, int reader //Logger.Log($"ConstructedEntity: {entityId}, pid: {entityLogic.PredictedId}"); } - private unsafe void ReadDiff(ref int readerPosition) + private unsafe void ReadDiff() { + int readerPosition = _stateA.DataOffset; fixed (byte* rawData = _stateA.Data) { while (readerPosition < _stateA.DataOffset + _stateA.DataSize) From d69c29c19afd37ccb4c195edd845857eed36c958 Mon Sep 17 00:00:00 2001 From: RevenantX Date: Fri, 11 Jul 2025 09:42:08 +0300 Subject: [PATCH 13/22] upd --- .../LiteEntitySystem/ClientEntityManager.cs | 94 ++++++++++++------- 1 file changed, 59 insertions(+), 35 deletions(-) diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 76ee207..658f1c9 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -454,39 +454,11 @@ float GetSpeedMultiplier(float bufferTime) => private unsafe void GoToNextState() { - //remove processed inputs - foreach (var controller in GetEntities()) - controller.RemoveClientProcessedInputs(_stateB.ProcessedTick); - while (_storedInputHeaders.Count > 0 && Utils.SequenceDiff(_stateB.ProcessedTick, _storedInputHeaders.Front().Tick) >= 0) - _storedInputHeaders.PopFront(); - - //delete predicted - while (_spawnPredictedEntities.TryPeek(out var info)) - { - if (Utils.SequenceDiff(_stateB.ProcessedTick, info.tick) >= 0) - { - //Logger.Log($"Delete predicted. Tick: {info.tick}, Entity: {info.entity}"); - _spawnPredictedEntities.Dequeue(); - info.entity.DestroyInternal(); - RemoveEntity(info.entity); - _localIdQueue.ReuseId(info.entity.Id); - } - else - { - break; - } - } - - ushort minimalTick = _stateA.Tick; - _statesPool.Enqueue(_stateA); - _stateA = _stateB; - _stateB = null; - ServerTick = _stateA.Tick; - - //Logger.Log($"GotoState: IST: {ServerTick}, TST:{_stateA.Tick}}"); - _remoteInterpolationTimer -= _remoteInterpolationTotalTime; + ushort targetTick = _tick; + //Step a little to match "predicted" state at server processed tick + //for correct BindOnSync execution //================== Rollback part =========================== _entitiesToRollback.Clear(); foreach (var entity in _modifiedEntitiesToRollback) @@ -519,6 +491,58 @@ private unsafe void GoToNextState() entity.OnRollback(); } + //reapply input + int cmdNum = 0; + UpdateMode = UpdateMode.PredictionRollback; + while(_storedInputHeaders.Count > 0 && Utils.SequenceDiff(_stateB.ProcessedTick, _storedInputHeaders.Front().Tick) >= 0) + { + var storedInput = _storedInputHeaders.Front(); + _storedInputHeaders.PopFront(); + _localPlayer.LoadInputInfo(storedInput.Header); + _tick = storedInput.Tick; + foreach (var controller in GetEntities()) + controller.ReadStoredInput(cmdNum); + cmdNum++; + + //simple update + foreach (var entity in _entitiesToRollback) + { + if (!entity.IsLocalControlled || !AliveEntities.Contains(entity)) + continue; + entity.Update(); + } + } + UpdateMode = UpdateMode.Normal; + + //remove processed inputs + foreach (var controller in GetEntities()) + controller.RemoveClientProcessedInputs(_stateB.ProcessedTick); + + //delete predicted + while (_spawnPredictedEntities.TryPeek(out var info)) + { + if (Utils.SequenceDiff(_stateB.ProcessedTick, info.tick) >= 0) + { + //Logger.Log($"Delete predicted. Tick: {info.tick}, Entity: {info.entity}"); + _spawnPredictedEntities.Dequeue(); + info.entity.DestroyInternal(); + RemoveEntity(info.entity); + _localIdQueue.ReuseId(info.entity.Id); + } + else + { + break; + } + } + + ushort minimalTick = _stateA.Tick; + _statesPool.Enqueue(_stateA); + _stateA = _stateB; + _stateB = null; + ServerTick = _stateA.Tick; + + //Logger.Log($"GotoState: IST: {ServerTick}, TST:{_stateA.Tick}"); + //================== ReadEntityStates BEGIN ================== _changedEntities.Clear(); _stateA.ExecuteRpcs(minimalTick, RPCExecuteMode.OnNextState); @@ -548,9 +572,8 @@ private unsafe void GoToNextState() _modifiedEntitiesToRollback.Clear(); //reapply input - UpdateMode = UpdateMode.PredictionRollback; - ushort targetTick = _tick; - for(int cmdNum = 0; cmdNum < _storedInputHeaders.Count; cmdNum++) + UpdateMode = UpdateMode.PredictionRollback; + for(cmdNum = 0; cmdNum < _storedInputHeaders.Count; cmdNum++) { //reapply input data var storedInput = _storedInputHeaders[cmdNum]; @@ -578,8 +601,9 @@ private unsafe void GoToNextState() entity.Update(); } } - _tick = targetTick; UpdateMode = UpdateMode.Normal; + + _tick = targetTick; _entitiesToRollback.Clear(); } From b92d5891fb29b525caddbc6b0569bb6ed5455b22 Mon Sep 17 00:00:00 2001 From: RevenantX Date: Fri, 11 Jul 2025 10:39:49 +0300 Subject: [PATCH 14/22] upd --- .../LiteEntitySystem/ClientEntityManager.cs | 13 +++++++++---- .../LiteEntitySystem/Collections/BitSpan.cs | 14 +++++++------- .../LiteEntitySystem/Internal/DeltaCompressor.cs | 4 ++-- .../LiteEntitySystem/Internal/EntityClassData.cs | 14 +++++++------- .../Internal/ValueTypeProcessor.cs | 8 ++++++++ Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs | 11 ++++++++--- Assets/Plugins/LiteEntitySystem/Utils.cs | 12 ++++++++++++ 7 files changed, 53 insertions(+), 23 deletions(-) diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 658f1c9..11fb361 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -456,6 +456,7 @@ private unsafe void GoToNextState() { _remoteInterpolationTimer -= _remoteInterpolationTotalTime; ushort targetTick = _tick; + var humanControllerFilter = GetEntities(); //Step a little to match "predicted" state at server processed tick //for correct BindOnSync execution @@ -474,12 +475,16 @@ private unsafe void GoToNextState() { for (int i = 0; i < rollbackFields.Length; i++) { - ref var field = ref rollbackFields[i]; + ref var field = ref classData.Fields[rollbackFields[i]]; if (field.FieldType == FieldType.SyncableSyncVar) { var syncableField = RefMagic.GetFieldValue(entity, field.Offset); field.TypeProcessor.SetFrom(syncableField, field.SyncableSyncVarOffset, lastServerData + field.PredictedOffset); } + else if (field.OnSync != null && (field.OnSyncFlags & BindOnChangeFlags.ExecuteOnRollbackReset) != 0) + { + field.TypeProcessor.SetFromAndSync(entity, field.Offset, lastServerData + field.PredictedOffset, field.OnSync); + } else { field.TypeProcessor.SetFrom(entity, field.Offset, lastServerData + field.PredictedOffset); @@ -500,7 +505,7 @@ private unsafe void GoToNextState() _storedInputHeaders.PopFront(); _localPlayer.LoadInputInfo(storedInput.Header); _tick = storedInput.Tick; - foreach (var controller in GetEntities()) + foreach (var controller in humanControllerFilter) controller.ReadStoredInput(cmdNum); cmdNum++; @@ -515,7 +520,7 @@ private unsafe void GoToNextState() UpdateMode = UpdateMode.Normal; //remove processed inputs - foreach (var controller in GetEntities()) + foreach (var controller in humanControllerFilter) controller.RemoveClientProcessedInputs(_stateB.ProcessedTick); //delete predicted @@ -579,7 +584,7 @@ private unsafe void GoToNextState() var storedInput = _storedInputHeaders[cmdNum]; _localPlayer.LoadInputInfo(storedInput.Header); _tick = storedInput.Tick; - foreach (var controller in GetEntities()) + foreach (var controller in humanControllerFilter) controller.ReadStoredInput(cmdNum); //simple update foreach (var entity in _entitiesToRollback) diff --git a/Assets/Plugins/LiteEntitySystem/Collections/BitSpan.cs b/Assets/Plugins/LiteEntitySystem/Collections/BitSpan.cs index d3da66b..bbb4a46 100644 --- a/Assets/Plugins/LiteEntitySystem/Collections/BitSpan.cs +++ b/Assets/Plugins/LiteEntitySystem/Collections/BitSpan.cs @@ -20,21 +20,21 @@ public BitSpan(Span bitRegion) public BitSpan(Span bitRegion, int bitCount) { BitCount = bitCount; - ByteCount = bitCount / BitsInByte + (bitCount % BitsInByte == 0 ? 0 : 1); + ByteCount = (bitCount + 7) / BitsInByte; _bitRegion = bitRegion; } public unsafe BitSpan(byte* bitRegion, int bitCount) { BitCount = bitCount; - ByteCount = bitCount / BitsInByte + (bitCount % BitsInByte == 0 ? 0 : 1); + ByteCount = (bitCount + 7) / BitsInByte; _bitRegion = new Span(bitRegion, ByteCount); } public BitSpan(byte[] bitRegion, int offset, int bitCount) { BitCount = bitCount; - ByteCount = bitCount / BitsInByte + (bitCount % BitsInByte == 0 ? 0 : 1); + ByteCount = (bitCount + 7) / BitsInByte; _bitRegion = new Span(bitRegion, offset, ByteCount); } @@ -82,28 +82,28 @@ public BitReadOnlySpan(Span bitRegion) public BitReadOnlySpan(Span bitRegion, int bitCount) { BitCount = bitCount; - ByteCount = bitCount / BitsInByte + (bitCount % BitsInByte == 0 ? 0 : 1); + ByteCount = (bitCount + 7) / BitsInByte; _bitRegion = bitRegion; } public BitReadOnlySpan(ReadOnlySpan bitRegion, int bitCount) { BitCount = bitCount; - ByteCount = bitCount / BitsInByte + (bitCount % BitsInByte == 0 ? 0 : 1); + ByteCount = (bitCount + 7) / BitsInByte; _bitRegion = bitRegion; } public unsafe BitReadOnlySpan(byte* bitRegion, int bitCount) { BitCount = bitCount; - ByteCount = bitCount / BitsInByte + (bitCount % BitsInByte == 0 ? 0 : 1); + ByteCount = (bitCount + 7) / BitsInByte; _bitRegion = new Span(bitRegion, ByteCount); } public BitReadOnlySpan(byte[] bitRegion, int offset, int bitCount) { BitCount = bitCount; - ByteCount = bitCount / BitsInByte + (bitCount % BitsInByte == 0 ? 0 : 1); + ByteCount = (bitCount + 7) / BitsInByte; _bitRegion = new Span(bitRegion, offset, ByteCount); } diff --git a/Assets/Plugins/LiteEntitySystem/Internal/DeltaCompressor.cs b/Assets/Plugins/LiteEntitySystem/Internal/DeltaCompressor.cs index 6957c6d..9d4f583 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/DeltaCompressor.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/DeltaCompressor.cs @@ -18,8 +18,8 @@ internal struct DeltaCompressor public DeltaCompressor(int size) { Size = size; - DeltaBits = Size / FieldsDivision + (Size % FieldsDivision == 0 ? 0 : 1); - MinDeltaSize = DeltaBits / 8 + (DeltaBits % 8 == 0 ? 0 : 1); + DeltaBits = (Size + FieldsDivision - 1) / FieldsDivision; + MinDeltaSize = (DeltaBits + 7) / 8; MaxDeltaSize = MinDeltaSize + Size; _firstFullData = null; } diff --git a/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs b/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs index ad12b1b..a0643e0 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs @@ -74,8 +74,8 @@ internal struct EntityClassData public RpcFieldInfo[] RemoteCallsClient; public readonly Type Type; - private readonly EntityFieldInfo[] _ownedRollbackFields; - private readonly EntityFieldInfo[] _remoteRollbackFields; + private readonly int[] _ownedRollbackFields; + private readonly int[] _remoteRollbackFields; private static readonly Type InternalEntityType = typeof(InternalEntity); internal static readonly Type SingletonEntityType = typeof(SingletonEntityLogic); @@ -89,7 +89,7 @@ internal struct EntityClassData public Span GetLastServerData(InternalEntity e) => new (e.IOBuffer, 0, PredictedSize); - public EntityFieldInfo[] GetRollbackFields(bool isOwned) => + public int[] GetRollbackFields(bool isOwned) => isOwned ? _ownedRollbackFields : _remoteRollbackFields; public unsafe void WriteHistory(EntityLogic e, ushort tick) @@ -189,8 +189,8 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp var syncableFields = new List(); var syncableFieldsWithCustomRollback = new List(); var lagCompensatedFields = new List(); - var ownedRollbackFields = new List(); - var remoteRollbackFields = new List(); + var ownedRollbackFields = new List(); + var remoteRollbackFields = new List(); var allTypesStack = Utils.GetBaseTypes(entType, InternalEntityType, true, true); while(allTypesStack.Count > 0) @@ -322,10 +322,10 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp //singletons can't be owned if (!IsSingleton) - ownedRollbackFields.Add(field); + ownedRollbackFields.Add(i); if(field.Flags.HasFlagFast(SyncFlags.AlwaysRollback)) - remoteRollbackFields.Add(field); + remoteRollbackFields.Add(i); } else { diff --git a/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs b/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs index bf1e015..97932f3 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs @@ -16,6 +16,7 @@ internal abstract unsafe class ValueTypeProcessor internal abstract void InitSyncVar(InternalBaseClass obj, int offset, InternalEntity entity, ushort fieldId); internal abstract void SetFrom(InternalBaseClass obj, int offset, byte* data); internal abstract bool SetFromAndSync(InternalBaseClass obj, int offset, byte* data); + internal abstract void SetFromAndSync(InternalBaseClass obj, int offset, byte* data, MethodCallDelegate onSyncDelegate); internal abstract void SetInterpValue(InternalBaseClass obj, int offset, byte* data); internal abstract void SetInterpValueFromCurrentValue(InternalBaseClass obj, int offset); internal abstract void WriteTo(InternalBaseClass obj, int offset, byte* data); @@ -46,6 +47,13 @@ internal sealed override void SetFrom(InternalBaseClass obj, int offset, byte* d internal sealed override bool SetFromAndSync(InternalBaseClass obj, int offset, byte* data) => RefMagic.SyncVarSetFromAndSync>(obj, offset, ref *(T*)data); + internal sealed override void SetFromAndSync(InternalBaseClass obj, int offset, byte* data, MethodCallDelegate onSyncDelegate) + { + var tempData = *(T*)data; + if(RefMagic.SyncVarSetFromAndSync>(obj, offset, ref tempData)) + onSyncDelegate(obj, new ReadOnlySpan(&tempData, Size)); + } + internal sealed override void SetInterpValue(InternalBaseClass obj, int offset, byte* data) => RefMagic.SyncVarSetInterp>(obj, offset, *(T*)data); diff --git a/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs b/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs index 3e6c352..ff4ca0a 100644 --- a/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs +++ b/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs @@ -16,17 +16,22 @@ public enum BindOnChangeFlags /// /// Execute on local prediction on Client /// - ExecuteOnPrediction = 1 << 2, + ExecuteOnPrediction = 1 << 1, /// /// Execute when value changed on server /// - ExecuteOnServer = 1 << 3, + ExecuteOnServer = 1 << 2, + + /// + /// Execute when SyncVar values reset in rollback (before OnRollback() called) + /// + ExecuteOnRollbackReset = 1 << 3, /// /// Combines ExecuteOnSync, ExecuteOnPrediction and ExecuteOnServer flags /// - ExecuteAlways = ExecuteOnSync | ExecuteOnPrediction | ExecuteOnServer + ExecuteAlways = ExecuteOnSync | ExecuteOnPrediction | ExecuteOnServer | ExecuteOnRollbackReset } public delegate void SpanAction(ReadOnlySpan data); diff --git a/Assets/Plugins/LiteEntitySystem/Utils.cs b/Assets/Plugins/LiteEntitySystem/Utils.cs index 81ef3c3..b492a61 100644 --- a/Assets/Plugins/LiteEntitySystem/Utils.cs +++ b/Assets/Plugins/LiteEntitySystem/Utils.cs @@ -216,6 +216,18 @@ public static ushort LerpSequence(ushort seq1, ushort seq2, float t) => [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static T CreateDelegateHelper(this MethodInfo method) where T : Delegate => (T)method.CreateDelegate(typeof(T)); + + public static string BytesToHexString(ReadOnlySpan bytes) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bytes.Length; i++) + { + sb.Append(bytes[i].ToString("X2")); + if(i != bytes.Length - 1) + sb.Append('_'); + } + return sb.ToString(); + } internal static Stack GetBaseTypes(Type ofType, Type until, bool includeSelf, bool includeLast) { From 820e825b92c86b722d5b4ecd699f5b5996fee7b5 Mon Sep 17 00:00:00 2001 From: RevenantX Date: Fri, 11 Jul 2025 18:06:19 +0300 Subject: [PATCH 15/22] update --- Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs | 9 +++++---- .../Plugins/LiteEntitySystem/Internal/StateSerializer.cs | 6 ------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 11fb361..cbe48a6 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -464,6 +464,8 @@ private unsafe void GoToNextState() _entitiesToRollback.Clear(); foreach (var entity in _modifiedEntitiesToRollback) _entitiesToRollback.Enqueue(entity); + + UpdateMode = UpdateMode.PredictionRollback; //reset predicted entities foreach (var entity in _entitiesToRollback) { @@ -496,9 +498,11 @@ private unsafe void GoToNextState() entity.OnRollback(); } + //clear modified here to readd changes after RollbackUpdate + _modifiedEntitiesToRollback.Clear(); + //reapply input int cmdNum = 0; - UpdateMode = UpdateMode.PredictionRollback; while(_storedInputHeaders.Count > 0 && Utils.SequenceDiff(_stateB.ProcessedTick, _storedInputHeaders.Front().Tick) >= 0) { var storedInput = _storedInputHeaders.Front(); @@ -572,9 +576,6 @@ private unsafe void GoToNextState() i--; } //================== ReadEntityStates END ==================== - - //clear modified here to readd changes after RollbackUpdate - _modifiedEntitiesToRollback.Clear(); //reapply input UpdateMode = UpdateMode.PredictionRollback; diff --git a/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs b/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs index 5985af4..09641ff 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs @@ -246,12 +246,6 @@ public unsafe bool MakeDiff(NetPlayer player, ushort minimalTick, byte* resultDa *fields = 0; } - //skip very old - if (Utils.SequenceDiff(_fieldChangeTicks[i], minimalTick) <= 0) - { - continue; - } - //not actual if (Utils.SequenceDiff(_fieldChangeTicks[i], compareToTick) <= 0) { From aec92692650c9dbbd6bba0f6829d3b1ff0c2c239 Mon Sep 17 00:00:00 2001 From: RevenantX Date: Wed, 23 Jul 2025 13:16:34 +0300 Subject: [PATCH 16/22] upd debug --- .../LiteEntitySystem/ClientEntityManager.cs | 20 +++++++++++++++++++ .../Plugins/LiteEntitySystem/EntityManager.cs | 18 +++++++++++++++++ .../LiteEntitySystem/ServerEntityManager.cs | 3 +++ 3 files changed, 41 insertions(+) diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index cbe48a6..14021ae 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -150,6 +150,11 @@ public sealed class ClientEntityManager : EntityManager private ushort _lastReceivedInputTick; private ushort _lastReadyTick; + //used for debug info + private ushort _rollbackStep; + private ushort _rollbackTotalSteps; + private ushort _tickBeforeRollback; + //time manipulation private readonly float[] _jitterSamples = new float[50]; private int _jitterSampleIdx; @@ -189,6 +194,13 @@ public void Execute(ServerStateData state) } } + public override string GetCurrentFrameDebugInfo(DebugFrameModes modes) => + UpdateMode == UpdateMode.PredictionRollback && modes.HasFlagFast(DebugFrameModes.Rollback) + ? $"[ClientRollback({_rollbackStep}/{_rollbackTotalSteps})] Tick {_tickBeforeRollback}. RollbackTick: {_tick}. ServerTick: {ServerTick}" + : modes.HasFlagFast(DebugFrameModes.Client) && UpdateMode == UpdateMode.Normal + ? $"[Client] Tick {_tick}. ServerTick: {ServerTick}" + : string.Empty; + /// /// Return client controller if exist /// @@ -455,6 +467,11 @@ float GetSpeedMultiplier(float bufferTime) => private unsafe void GoToNextState() { _remoteInterpolationTimer -= _remoteInterpolationTotalTime; + + _tickBeforeRollback = _tick; + _rollbackStep = 0; + _rollbackTotalSteps = (ushort)_storedInputHeaders.Count; + ushort targetTick = _tick; var humanControllerFilter = GetEntities(); @@ -512,6 +529,7 @@ private unsafe void GoToNextState() foreach (var controller in humanControllerFilter) controller.ReadStoredInput(cmdNum); cmdNum++; + _rollbackStep++; //simple update foreach (var entity in _entitiesToRollback) @@ -587,6 +605,8 @@ private unsafe void GoToNextState() _tick = storedInput.Tick; foreach (var controller in humanControllerFilter) controller.ReadStoredInput(cmdNum); + _rollbackStep++; + //simple update foreach (var entity in _entitiesToRollback) { diff --git a/Assets/Plugins/LiteEntitySystem/EntityManager.cs b/Assets/Plugins/LiteEntitySystem/EntityManager.cs index 51ed628..d6e2231 100644 --- a/Assets/Plugins/LiteEntitySystem/EntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/EntityManager.cs @@ -66,6 +66,18 @@ public enum DeserializeResult Error, HeaderCheckFailed } + + [Flags] + public enum DebugFrameModes + { + Client = 1 << 0, + Server = 1 << 1, + Rollback = 1 << 2, + + All = Client | Server | Rollback, + ClientAndRollback = Client | Rollback, + ClientAndServer = Server | Client + } public enum MaxHistorySize : byte { @@ -243,6 +255,12 @@ public abstract class EntityManager /// interpolation function public static void RegisterFieldType(InterpolatorDelegateWithReturn interpolationDelegate) where T : unmanaged => ValueTypeProcessor.Registered[typeof(T)] = new UserTypeProcessor(interpolationDelegate); + + /// + /// Get information about current update frame + /// + /// debug formatted string + public abstract string GetCurrentFrameDebugInfo(DebugFrameModes modes); /// /// Register custom field type diff --git a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs index 752026b..19c3dc5 100644 --- a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs @@ -98,6 +98,9 @@ public ServerEntityManager( SetTickrate(framesPerSecond); } + public override string GetCurrentFrameDebugInfo(DebugFrameModes modes) => + modes.HasFlagFast(DebugFrameModes.Server) ? $"[Server] Tick {_tick}" : string.Empty; + public override void Reset() { base.Reset(); From 93fe2f2123031deab4e664c2c6e10477d5c09c5b Mon Sep 17 00:00:00 2001 From: RevenantX Date: Thu, 24 Jul 2025 21:47:01 +0300 Subject: [PATCH 17/22] update LES --- .../LiteEntitySystem/ClientEntityManager.cs | 138 +++++++++++------- .../Plugins/LiteEntitySystem/EntityLogic.cs | 34 ++--- .../Internal/ValueTypeProcessor.cs | 6 +- .../PredictableEntityLogic.cs | 58 ++++++++ .../PredictableEntityLogic.cs.meta | 11 ++ Assets/Scripts/Shared/SimpleProjectile.cs | 2 +- 6 files changed, 175 insertions(+), 74 deletions(-) create mode 100644 Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs create mode 100644 Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs.meta diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 14021ae..79f4d6a 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -126,7 +126,7 @@ public sealed class ClientEntityManager : EntityManager private readonly Queue _statesPool = new(MaxSavedStateDiff); private readonly Dictionary _receivedStates = new(); private readonly SequenceBinaryHeap _readyStates = new(MaxSavedStateDiff); - private readonly Queue<(ushort tick, EntityLogic entity)> _spawnPredictedEntities = new (); + private readonly Queue _tempLocalEntities = new (); private readonly byte[] _sendBuffer = new byte[NetConstants.MaxPacketSize]; private readonly HashSet _changedEntities = new(); private readonly CircularBuffer _storedInputHeaders = new(InputBufferSize); @@ -252,7 +252,7 @@ public override void Reset() /// /// Entity type /// Created entity or null if entities limit is reached () - internal T AddLocalEntity(EntityLogic parent, Action initMethod) where T : EntityLogic + internal unsafe T AddLocalEntity(EntityLogic parent, Action initMethod) where T : PredictableEntityLogic { if (_localIdQueue.AvailableIds == 0) { @@ -276,26 +276,36 @@ internal T AddLocalEntity(EntityLogic parent, Action initMethod) where T : entity.SetParentInternal(parent); initMethod(entity); ConstructEntity(entity); - _spawnPredictedEntities.Enqueue((_tick, entity)); - - for(int i = 0; i < classData.InterpolatedCount; i++) + _tempLocalEntities.Enqueue(entity); + + fixed (byte* predictedData = classData.GetLastServerData(entity)) { - ref var field = ref classData.Fields[i]; - field.TypeProcessor.SetInterpValueFromCurrentValue(entity, field.Offset); + for (int i = 0; i < classData.FieldsCount; i++) + { + ref var field = ref classData.Fields[i]; + if(field.Flags.HasFlagFast(SyncFlags.Interpolated)) + field.TypeProcessor.SetInterpValueFromCurrentValue(entity, field.Offset); + if(field.IsPredicted) + field.TypeProcessor.WriteTo(entity, field.Offset, predictedData + field.PredictedOffset); + } } + //init syncable rollback + for (int i = 0; i < classData.SyncableFieldsCustomRollback.Length; i++) + { + var syncableField = RefMagic.GetFieldValue(entity, classData.SyncableFieldsCustomRollback[i].Offset); + syncableField.BeforeReadRPC(); + syncableField.AfterReadRPC(); + } + return entity; } - internal EntityLogic FindEntityByPredictedId(ushort tick, ushort parentId, ushort predictedId) + internal PredictableEntityLogic FindEntityByPredictedId(ushort tick, ushort parentId, ushort predictedId) { - foreach (var predictedEntity in _spawnPredictedEntities) - { - if (predictedEntity.tick == tick && - predictedEntity.entity.ParentId.Id == parentId && - predictedEntity.entity.PredictedId == predictedId) - return predictedEntity.entity; - } + foreach (var predictedEntity in _tempLocalEntities) + if (predictedEntity.IsEntityMatch(predictedId, parentId, tick)) + return predictedEntity; return null; } @@ -475,12 +485,13 @@ private unsafe void GoToNextState() ushort targetTick = _tick; var humanControllerFilter = GetEntities(); - //Step a little to match "predicted" state at server processed tick - //for correct BindOnSync execution //================== Rollback part =========================== _entitiesToRollback.Clear(); foreach (var entity in _modifiedEntitiesToRollback) - _entitiesToRollback.Enqueue(entity); + { + if(!entity.IsRemoved) + _entitiesToRollback.Enqueue(entity); + } UpdateMode = UpdateMode.PredictionRollback; //reset predicted entities @@ -518,7 +529,8 @@ private unsafe void GoToNextState() //clear modified here to readd changes after RollbackUpdate _modifiedEntitiesToRollback.Clear(); - //reapply input + //Step a little to match "predicted" state at server processed tick + //for correct BindOnSync execution int cmdNum = 0; while(_storedInputHeaders.Count > 0 && Utils.SequenceDiff(_stateB.ProcessedTick, _storedInputHeaders.Front().Tick) >= 0) { @@ -536,6 +548,11 @@ private unsafe void GoToNextState() { if (!entity.IsLocalControlled || !AliveEntities.Contains(entity)) continue; + + //skip local entities that spawned later + if (entity.IsLocal && entity is PredictableEntityLogic el && Utils.SequenceDiff(el.CreatedAtTick, _tick) >= 0) + continue; + entity.Update(); } } @@ -545,23 +562,6 @@ private unsafe void GoToNextState() foreach (var controller in humanControllerFilter) controller.RemoveClientProcessedInputs(_stateB.ProcessedTick); - //delete predicted - while (_spawnPredictedEntities.TryPeek(out var info)) - { - if (Utils.SequenceDiff(_stateB.ProcessedTick, info.tick) >= 0) - { - //Logger.Log($"Delete predicted. Tick: {info.tick}, Entity: {info.entity}"); - _spawnPredictedEntities.Dequeue(); - info.entity.DestroyInternal(); - RemoveEntity(info.entity); - _localIdQueue.ReuseId(info.entity.Id); - } - else - { - break; - } - } - ushort minimalTick = _stateA.Tick; _statesPool.Enqueue(_stateA); _stateA = _stateB; @@ -569,12 +569,29 @@ private unsafe void GoToNextState() ServerTick = _stateA.Tick; //Logger.Log($"GotoState: IST: {ServerTick}, TST:{_stateA.Tick}"); - - //================== ReadEntityStates BEGIN ================== _changedEntities.Clear(); _stateA.ExecuteRpcs(minimalTick, RPCExecuteMode.OnNextState); ReadDiff(); ExecuteSyncCalls(_stateA); + + //delete predicted + while (_tempLocalEntities.TryPeek(out var entity)) + { + if (Utils.SequenceDiff(_stateA.ProcessedTick, entity.CreatedAtTick) >= 0) + { + //Logger.Log($"Delete predicted. Tick: {info.tick}, Entity: {info.entity}"); + _tempLocalEntities.Dequeue(); + entity.DestroyInternal(); + RemoveEntity(entity); + _localIdQueue.ReuseId(entity.Id); + } + else + { + break; + } + } + + //write lag comp history for remote rollbackable lagcomp fields foreach (var lagCompensatedEntity in LagCompensatedEntities) ClassDataDict[lagCompensatedEntity.ClassId].WriteHistory(lagCompensatedEntity, ServerTick); @@ -593,8 +610,7 @@ private unsafe void GoToNextState() _entitiesToRemove[_entitiesToRemoveCount] = null; i--; } - //================== ReadEntityStates END ==================== - + //reapply input UpdateMode = UpdateMode.PredictionRollback; for(cmdNum = 0; cmdNum < _storedInputHeaders.Count; cmdNum++) @@ -613,6 +629,10 @@ private unsafe void GoToNextState() if (!entity.IsLocalControlled || !AliveEntities.Contains(entity)) continue; + //skip local entities that spawned later + if (entity.IsLocal && entity is PredictableEntityLogic el && Utils.SequenceDiff(el.CreatedAtTick, _tick) >= 0) + continue; + //update interpolation data if (cmdNum == _storedInputHeaders.Count - 1) { @@ -635,7 +655,7 @@ private unsafe void GoToNextState() internal void MarkEntityChanged(InternalEntity entity) { - if (entity.IsLocal || entity.IsDestroyed) + if (entity.IsRemoved) return; _modifiedEntitiesToRollback.Add(entity); } @@ -653,9 +673,6 @@ internal override unsafe void EntityFieldChanged(InternalEntity entity, ushor T value = oldValue; fieldInfo.OnSync(entity, new ReadOnlySpan(&value, sizeof(T))); } - - if (entity.IsLocal) - return; var rollbackFields = classData.GetRollbackFields(entity.IsLocalControlled); if (rollbackFields != null && rollbackFields.Length > 0 && fieldInfo.IsPredicted) @@ -666,13 +683,12 @@ internal override void OnEntityDestroyed(InternalEntity e) { if (!e.IsLocal) { + //because remote and server alive entities managed by EntityManager if(e.IsLocalControlled && e is EntityLogic eLogic) RemoveOwned(eLogic); Utils.AddToArrayDynamic(ref _entitiesToRemove, ref _entitiesToRemoveCount, e); - - _modifiedEntitiesToRollback.Remove(e); } - + _modifiedEntitiesToRollback.Remove(e); base.OnEntityDestroyed(e); } @@ -919,22 +935,46 @@ internal unsafe void ReadConstructRPC(ushort entityId, byte* rawData, int reader } var entity = EntitiesDict[entityId]; + bool writeInterpolationData = !entity.IsConstructed || entity.IsRemoteControlled; ref var classData = ref entity.ClassData; Utils.ResizeOrCreate(ref _syncCalls, _syncCallsCount + classData.FieldsCount); + + //set values to same as predicted entity for correct OnSync calls + if (!entity.IsConstructed && entity is PredictableEntityLogic pel) + { + foreach (var localEntity in _tempLocalEntities) + { + if (!pel.IsSameAsLocal(localEntity)) + continue; + + for (int i = 0; i < classData.FieldsCount; i++) + { + ref var field = ref classData.Fields[i]; + //skip some fields because they need to trigger onChange + if (i == pel.InternalOwnerId.FieldId || i == pel.IsSyncEnabledFieldId) + continue; + + field.TypeProcessor.CopyFrom(pel, localEntity, field.Offset); + } + + break; + } + } fixed (byte* predictedData = classData.GetLastServerData(entity)) { for (int i = 0; i < classData.FieldsCount; i++) { ref var field = ref classData.Fields[i]; + if (writeInterpolationData && field.Flags.HasFlagFast(SyncFlags.Interpolated)) field.TypeProcessor.SetInterpValue(entity, field.Offset, rawData + readerPosition); - + if (field.ReadField(entity, rawData + readerPosition, predictedData)) _syncCalls[_syncCallsCount++] = new SyncCallInfo(field.OnSync, entity, readerPosition, field.IntSize); - //Logger.Log($"E {entity.Id} Field updated: {field.Name}"); + //Logger.Log($"E {entity.Id} Field updated: {field.Name} = {field.TypeProcessor.ToString(entity, field.Offset)}"); readerPosition += field.IntSize; } } diff --git a/Assets/Plugins/LiteEntitySystem/EntityLogic.cs b/Assets/Plugins/LiteEntitySystem/EntityLogic.cs index 7128b08..adc3f37 100644 --- a/Assets/Plugins/LiteEntitySystem/EntityLogic.cs +++ b/Assets/Plugins/LiteEntitySystem/EntityLogic.cs @@ -85,19 +85,6 @@ public ChangeParentData(ChangeType type, EntitySharedReference reference) [SyncVarFlags(SyncFlags.OnlyForOwner)] private SyncVar _localPredictedIdCounter; - - [SyncVarFlags(SyncFlags.OnlyForOwner)] - private SyncVar _predictedId; - - [SyncVarFlags(SyncFlags.OnlyForOwner)] - private SyncVar _isPredicted; - - internal ulong PredictedId => _predictedId.Value; - - /// - /// Is entity spawned using AddPredictedEntity - /// - public bool IsPredicted => _isPredicted.Value; //specific per player private SyncVar _isSyncEnabled; @@ -213,13 +200,12 @@ public bool TryGetOwnerTick(out ushort tick) /// Entity type /// Method that will be called after entity constructed /// Created predicted local entity - public T AddPredictedEntity(Action initMethod = null) where T : EntityLogic + public T AddPredictedEntity(Action initMethod = null) where T : PredictableEntityLogic { if (EntityManager.IsServer) return ServerManager.AddEntity(this, e => { - e._predictedId.Value = _localPredictedIdCounter.Value++; - e._isPredicted.Value = true; + e.InitEntity(_localPredictedIdCounter.Value++, this); initMethod?.Invoke(e); }); @@ -258,8 +244,7 @@ public T AddPredictedEntity(Action initMethod = null) where T : EntityLogi entity = ClientManager.AddLocalEntity(this, e => { - e._predictedId.Value = _localPredictedIdCounter.Value++; - e._isPredicted.Value = true; + e.InitEntity(_localPredictedIdCounter.Value++, this); //Logger.Log($"AddPredictedEntity. Id: {e.Id}, ClassId: {e.ClassId} PredId: {e._predictedId.Value}, Tick: {e.ClientManager.Tick}"); initMethod?.Invoke(e); }); @@ -274,7 +259,7 @@ public T AddPredictedEntity(Action initMethod = null) where T : EntityLogi /// SyncVar of class that will be set to predicted entity and synchronized once confirmation will be received /// Method that will be called after entity constructed /// Created predicted local entity - public void AddPredictedEntity(ref SyncVar targetReference, Action initMethod = null) where T : EntityLogic + public void AddPredictedEntity(ref SyncVar targetReference, Action initMethod = null) where T : PredictableEntityLogic { T entity; if (EntityManager.InRollBackState) @@ -289,7 +274,11 @@ public void AddPredictedEntity(ref SyncVar targetRefer if (EntityManager.IsServer) { - entity = ServerManager.AddEntity(this, initMethod); + entity = ServerManager.AddEntity(this, e => + { + e.InitEntity(_localPredictedIdCounter.Value++, this); + initMethod?.Invoke(e); + }); targetReference.Value = entity; return; } @@ -302,7 +291,7 @@ public void AddPredictedEntity(ref SyncVar targetRefer entity = ClientManager.AddLocalEntity(this, e => { - e._predictedId.Value = _localPredictedIdCounter.Value++; + e.InitEntity(_localPredictedIdCounter.Value++, this); initMethod?.Invoke(e); }); targetReference.Value = entity; @@ -390,8 +379,7 @@ internal override void DestroyInternal() { ClientManager.RemoveOwned(this); } - var parent = EntityManager.GetEntityById(_parentRef); - if (parent != null && !parent.IsDestroyed) + if (EntityManager.TryGetEntityById(_parentRef, out var parent) && !parent.IsDestroyed) { parent.Childs.Remove(this); } diff --git a/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs b/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs index 97932f3..899fac2 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs @@ -20,6 +20,7 @@ internal abstract unsafe class ValueTypeProcessor internal abstract void SetInterpValue(InternalBaseClass obj, int offset, byte* data); internal abstract void SetInterpValueFromCurrentValue(InternalBaseClass obj, int offset); internal abstract void WriteTo(InternalBaseClass obj, int offset, byte* data); + internal abstract void CopyFrom(InternalBaseClass toObj, InternalBaseClass fromObj, int offset); internal abstract void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime); internal abstract int GetHashCode(InternalBaseClass obj, int offset); internal abstract string ToString(InternalBaseClass obj, int offset); @@ -37,7 +38,10 @@ internal sealed override void InitSyncVar(InternalBaseClass obj, int offset, Int sv.Init(entity, fieldId); RefMagic.SetFieldValue(obj, offset, sv); } - + + internal override void CopyFrom(InternalBaseClass toObj, InternalBaseClass fromObj, int offset) => + RefMagic.SyncVarSetDirect>(toObj, offset, RefMagic.GetFieldValue>(fromObj, offset).Value); + internal override void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, *(T*)historyA, out *(T*)tempHistory); diff --git a/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs b/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs new file mode 100644 index 0000000..d6c58a8 --- /dev/null +++ b/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs @@ -0,0 +1,58 @@ +namespace LiteEntitySystem +{ + /// + /// Entity that can be spawned on client prediction + /// + public abstract class PredictableEntityLogic : EntityLogic + { + private struct InitialData + { + public ushort PredictedId; + public EntitySharedReference Parent; + } + + private ushort _predictedId; + private EntitySharedReference _initialParent; + private static RemoteCall SyncRPC; + + //used for spawn prediction + public readonly ushort CreatedAtTick; + + internal void InitEntity(ushort predictedId, EntitySharedReference initialParent) + { + _predictedId = predictedId; + _initialParent = initialParent; + } + + protected PredictableEntityLogic(EntityParams entityParams) : base(entityParams) + { + CreatedAtTick = entityParams.EntityManager.Tick; + } + + internal bool IsSameAsLocal(PredictableEntityLogic other) => + _predictedId == other._predictedId && _initialParent == other._initialParent && ClassId == other.ClassId; + + //used for finding entity in rollback + internal bool IsEntityMatch(ushort predictedId, ushort parentId, ushort createdAtTick) => + _predictedId == predictedId && _initialParent.Id == parentId && CreatedAtTick == createdAtTick; + + protected override void RegisterRPC(ref RPCRegistrator r) + { + base.RegisterRPC(ref r); + r.CreateRPCAction(this, OnSyncRPC, ref SyncRPC, ExecuteFlags.SendToOwner); + } + + private void OnSyncRPC(InitialData initialData) + { + _predictedId = initialData.PredictedId; + _initialParent = initialData.Parent; + //Logger.Log($"OnSyncRPC called. pred id: {_predictedId}, initial parent: {_initialParent}"); + } + + protected internal override void OnSyncRequested() + { + base.OnSyncRequested(); + ExecuteRPC(SyncRPC, new InitialData { PredictedId = _predictedId, Parent = ParentId }); + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs.meta b/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs.meta new file mode 100644 index 0000000..5509d33 --- /dev/null +++ b/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 25deb888ed71d176b8e1726b2999fbed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Shared/SimpleProjectile.cs b/Assets/Scripts/Shared/SimpleProjectile.cs index 4128aa1..2c0e377 100644 --- a/Assets/Scripts/Shared/SimpleProjectile.cs +++ b/Assets/Scripts/Shared/SimpleProjectile.cs @@ -13,7 +13,7 @@ public struct ProjectileInitParams } [EntityFlags(EntityFlags.UpdateOnClient)] - public class SimpleProjectile : EntityLogic + public class SimpleProjectile : PredictableEntityLogic { private static readonly RaycastHit2D[] RaycastHits = new RaycastHit2D[10]; From 5b97ab43eb72759764c02d6fe174ed73faa71380 Mon Sep 17 00:00:00 2001 From: RevenantX Date: Thu, 24 Jul 2025 22:01:14 +0300 Subject: [PATCH 18/22] make CreatedAtTick - internal --- Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs b/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs index d6c58a8..431ca17 100644 --- a/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs +++ b/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs @@ -16,7 +16,7 @@ private struct InitialData private static RemoteCall SyncRPC; //used for spawn prediction - public readonly ushort CreatedAtTick; + internal readonly ushort CreatedAtTick; internal void InitEntity(ushort predictedId, EntitySharedReference initialParent) { From 5f2f5b45769cac3980d25647c13893141dfb0798 Mon Sep 17 00:00:00 2001 From: RevenantX Date: Mon, 25 Aug 2025 11:12:24 +0300 Subject: [PATCH 19/22] update LiteEntitySystem --- .../LiteEntitySystem/ClientEntityManager.cs | 50 +++++++++++-------- .../Plugins/LiteEntitySystem/EntityFilter.cs | 11 ++-- .../Plugins/LiteEntitySystem/EntityManager.cs | 28 +++++++++-- .../Extensions/SyncPongTimer.cs | 50 +++++++++++++++++++ .../Extensions/SyncPongTimer.cs.meta | 11 ++++ .../LiteEntitySystem/HumanControllerLogic.cs | 1 + .../Internal/EntityClassData.cs | 13 +++++ .../Internal/EntityFieldInfo.cs | 14 +++++- .../Internal/InternalEntity.cs | 18 +++++-- .../PredictableEntityLogic.cs | 1 + .../LiteEntitySystem/RPCRegistrator.cs | 38 +++++++++++--- .../LiteEntitySystem/ServerEntityManager.cs | 6 +-- Assets/Plugins/LiteEntitySystem/SyncVar.cs | 20 ++++++-- Assets/Scripts/Client/RemotePlayerView.cs | 1 + 14 files changed, 212 insertions(+), 50 deletions(-) create mode 100644 Assets/Plugins/LiteEntitySystem/Extensions/SyncPongTimer.cs create mode 100644 Assets/Plugins/LiteEntitySystem/Extensions/SyncPongTimer.cs.meta diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 79f4d6a..612d63b 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -169,14 +169,12 @@ public sealed class ClientEntityManager : EntityManager private readonly struct SyncCallInfo { private readonly InternalEntity _entity; - private readonly MethodCallDelegate _onSync; private readonly int _prevDataPos; - private readonly int _dataSize; + private readonly int _fieldId; - public SyncCallInfo(MethodCallDelegate onSync, InternalEntity entity, int prevDataPos, int dataSize) + public SyncCallInfo(InternalEntity entity, int prevDataPos, int fieldId) { - _dataSize = dataSize; - _onSync = onSync; + _fieldId = fieldId; _entity = entity; _prevDataPos = prevDataPos; } @@ -185,7 +183,9 @@ public void Execute(ServerStateData state) { try { - _onSync(_entity, new ReadOnlySpan(state.Data, _prevDataPos, _dataSize)); + ref var classData = ref _entity.ClassData; + ref var fieldInfo = ref classData.Fields[_fieldId]; + classData.CallOnSync(_entity, ref fieldInfo, new ReadOnlySpan(state.Data, _prevDataPos, fieldInfo.IntSize)); } catch (Exception e) { @@ -506,18 +506,27 @@ private unsafe void GoToNextState() for (int i = 0; i < rollbackFields.Length; i++) { ref var field = ref classData.Fields[rollbackFields[i]]; - if (field.FieldType == FieldType.SyncableSyncVar) + if (field.OnSync != null && (field.OnSyncFlags & BindOnChangeFlags.ExecuteOnRollbackReset) != 0) { - var syncableField = RefMagic.GetFieldValue(entity, field.Offset); - field.TypeProcessor.SetFrom(syncableField, field.SyncableSyncVarOffset, lastServerData + field.PredictedOffset); + //this is currently only place which doesn't call classData.CallOnSync + if (field.FieldType == FieldType.SyncableSyncVar) + { + var syncableField = RefMagic.GetFieldValue(entity, field.Offset); + field.TypeProcessor.SetFromAndSync(syncableField, field.SyncableSyncVarOffset, lastServerData + field.PredictedOffset, field.OnSync); + } + else + { + field.TypeProcessor.SetFromAndSync(entity, field.Offset, lastServerData + field.PredictedOffset, field.OnSync); + } } - else if (field.OnSync != null && (field.OnSyncFlags & BindOnChangeFlags.ExecuteOnRollbackReset) != 0) + else if (field.FieldType == FieldType.SyncVar) { - field.TypeProcessor.SetFromAndSync(entity, field.Offset, lastServerData + field.PredictedOffset, field.OnSync); + field.TypeProcessor.SetFrom(entity, field.Offset, lastServerData + field.PredictedOffset); } else { - field.TypeProcessor.SetFrom(entity, field.Offset, lastServerData + field.PredictedOffset); + var syncableField = RefMagic.GetFieldValue(entity, field.Offset); + field.TypeProcessor.SetFrom(syncableField, field.SyncableSyncVarOffset, lastServerData + field.PredictedOffset); } } } @@ -660,7 +669,7 @@ internal void MarkEntityChanged(InternalEntity entity) _modifiedEntitiesToRollback.Add(entity); } - internal override unsafe void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue, ref T oldValue) + internal override unsafe void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue, ref T oldValue, bool skipOnSync) { if (entity.IsRemoved) return; @@ -668,10 +677,10 @@ internal override unsafe void EntityFieldChanged(InternalEntity entity, ushor ref var classData = ref ClassDataDict[entity.ClassId]; ref var fieldInfo = ref classData.Fields[fieldId]; - if ((fieldInfo.OnSyncFlags & BindOnChangeFlags.ExecuteOnPrediction) != 0) + if (!skipOnSync && (fieldInfo.OnSyncFlags & BindOnChangeFlags.ExecuteOnPrediction) != 0) { - T value = oldValue; - fieldInfo.OnSync(entity, new ReadOnlySpan(&value, sizeof(T))); + T oldValueCopy = oldValue; + classData.CallOnSync(entity, ref fieldInfo, new ReadOnlySpan(&oldValueCopy, fieldInfo.IntSize)); } var rollbackFields = classData.GetRollbackFields(entity.IsLocalControlled); @@ -947,12 +956,14 @@ internal unsafe void ReadConstructRPC(ushort entityId, byte* rawData, int reader { if (!pel.IsSameAsLocal(localEntity)) continue; + + //Logger.Log($"Found predictable entity. LocalId was: {localEntity.Id}, server id: {entityId}"); for (int i = 0; i < classData.FieldsCount; i++) { ref var field = ref classData.Fields[i]; //skip some fields because they need to trigger onChange - if (i == pel.InternalOwnerId.FieldId || i == pel.IsSyncEnabledFieldId) + if (field.OnSync == null || i == pel.InternalOwnerId.FieldId || i == pel.IsSyncEnabledFieldId) continue; field.TypeProcessor.CopyFrom(pel, localEntity, field.Offset); @@ -972,7 +983,7 @@ internal unsafe void ReadConstructRPC(ushort entityId, byte* rawData, int reader field.TypeProcessor.SetInterpValue(entity, field.Offset, rawData + readerPosition); if (field.ReadField(entity, rawData + readerPosition, predictedData)) - _syncCalls[_syncCallsCount++] = new SyncCallInfo(field.OnSync, entity, readerPosition, field.IntSize); + _syncCalls[_syncCallsCount++] = new SyncCallInfo(entity, readerPosition, i); //Logger.Log($"E {entity.Id} Field updated: {field.Name} = {field.TypeProcessor.ToString(entity, field.Offset)}"); readerPosition += field.IntSize; @@ -1024,8 +1035,7 @@ private unsafe void ReadDiff() continue; ref var field = ref classData.Fields[i]; if (field.ReadField(entity, rawData + readerPosition, predictedData)) - _syncCalls[_syncCallsCount++] = new SyncCallInfo(field.OnSync, entity, readerPosition, - field.IntSize); + _syncCalls[_syncCallsCount++] = new SyncCallInfo(entity, readerPosition, i); //Logger.Log($"E {entity.Id} Field updated: {field.Name}"); readerPosition += field.IntSize; diff --git a/Assets/Plugins/LiteEntitySystem/EntityFilter.cs b/Assets/Plugins/LiteEntitySystem/EntityFilter.cs index 1e4b9f4..8997346 100644 --- a/Assets/Plugins/LiteEntitySystem/EntityFilter.cs +++ b/Assets/Plugins/LiteEntitySystem/EntityFilter.cs @@ -8,6 +8,7 @@ public interface IEntityFilter { internal void Add(InternalEntity entity); internal void Remove(InternalEntity entity); + internal void TriggerConstructedEvent(InternalEntity entity); } //SortedSet like collection based on AVLTree @@ -29,7 +30,8 @@ public void SubscribeToConstructed(Action onConstructed, bool callOnExisting) { if (callOnExisting) foreach (var entity in this) - onConstructed(entity); + if(entity.State == EntityState.LateConstructed) + onConstructed(entity); OnConstructed += onConstructed; } @@ -46,14 +48,9 @@ internal override bool Remove(T entity) return base.Remove(entity); } - internal override void Add(T data) - { - base.Add(data); - OnConstructed?.Invoke(data); - } - void IEntityFilter.Add(InternalEntity entity) => Add((T) entity); void IEntityFilter.Remove(InternalEntity entity) => Remove((T) entity); + void IEntityFilter.TriggerConstructedEvent(InternalEntity entity) => OnConstructed?.Invoke((T)entity); internal override void Clear() { diff --git a/Assets/Plugins/LiteEntitySystem/EntityManager.cs b/Assets/Plugins/LiteEntitySystem/EntityManager.cs index d6e2231..a00823c 100644 --- a/Assets/Plugins/LiteEntitySystem/EntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/EntityManager.cs @@ -435,8 +435,12 @@ public EntityFilter GetEntities() where T : InternalEntity { foreach (var entity in GetEntities()) { - if(entity is T castedEnt) + if (entity is T castedEnt) + { typedFilter.Add(castedEnt); + if(entity.State == EntityState.LateConstructed) + entityFilter.TriggerConstructedEvent(entity); + } } } @@ -669,14 +673,30 @@ public void DisableLagCompensation() protected void ExecuteLateConstruct() { - foreach (var internalEntity in _entitiesToLateConstruct) - internalEntity.OnLateConstructed(); + foreach (var e in _entitiesToLateConstruct) + { + e.LateConstructInternal(); + + ref var classData = ref ClassDataDict[e.ClassId]; + if (classData.IsSingleton) + { + foreach (var baseTypeInfo in classData.BaseTypes) + if(!baseTypeInfo.IsSingleton) + _entityFilters[baseTypeInfo.Id]?.TriggerConstructedEvent(e); + } + else + { + _entityFilters[classData.FilterId]?.TriggerConstructedEvent(e); + foreach (var baseTypeInfo in classData.BaseTypes) + _entityFilters[baseTypeInfo.Id]?.TriggerConstructedEvent(e); + } + } _entitiesToLateConstruct.Clear(); } protected abstract void OnLogicTick(); - internal abstract void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue, ref T oldValue) + internal abstract void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue, ref T oldValue, bool skipOnSync) where T : unmanaged; /// diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncPongTimer.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncPongTimer.cs new file mode 100644 index 0000000..8630812 --- /dev/null +++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncPongTimer.cs @@ -0,0 +1,50 @@ +namespace LiteEntitySystem.Extensions +{ + public class SyncPongTimer : SyncableField + { + public float MaxTime => _maxTime; + public float ElapsedTime => _time; + public bool IsTimeElapsed => _time >= _maxTime; + public float CountdownTime => _maxTime - _time; + public bool HasStarted => _time > 0; + + private SyncVar _time; + private const float _maxTime = 1f; + + public SyncPongTimer() { } + + public float Progress => _time; + + public void Reset() + { + _time.Value = 0f; + } + + public void Finish() + { + _time.Value = _maxTime; + } + + public bool UpdateForward(float delta) + { + if (delta > 0f) + { + float newTime = _time.Value + delta; + if (newTime > 1f) newTime = 1f; + _time.Value = newTime; + } + return IsTimeElapsed; + } + + public bool UpdateBackward(float delta) + { + if (delta > 0f) + { + float newTime = _time.Value - delta; + if (newTime < 0f) newTime = 0f; + _time.Value = newTime; + } + return !HasStarted; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncPongTimer.cs.meta b/Assets/Plugins/LiteEntitySystem/Extensions/SyncPongTimer.cs.meta new file mode 100644 index 0000000..9b49d65 --- /dev/null +++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncPongTimer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 074123fd4653005b096387a88a742e15 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/LiteEntitySystem/HumanControllerLogic.cs b/Assets/Plugins/LiteEntitySystem/HumanControllerLogic.cs index 8f39138..68336ea 100644 --- a/Assets/Plugins/LiteEntitySystem/HumanControllerLogic.cs +++ b/Assets/Plugins/LiteEntitySystem/HumanControllerLogic.cs @@ -285,6 +285,7 @@ protected HumanControllerLogic(EntityParams entityParams) : base(entityParams, U // ReSharper disable once VirtualMemberCallInConstructor _currentInput = GetDefaultInput(); + _pendingInput = _currentInput; } internal override void RemoveClientProcessedInputs(ushort processedTick) diff --git a/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs b/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs index a0643e0..223607d 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs @@ -164,6 +164,19 @@ public void ReleaseDataCache(InternalEntity entity) entity.IOBuffer = null; } } + + public void CallOnSync(InternalBaseClass entity, ref EntityFieldInfo field, ReadOnlySpan buffer) + { + if (field.FieldType == FieldType.SyncableSyncVar) + { + var syncableField = RefMagic.GetFieldValue(entity, field.Offset); + field.OnSync(syncableField, buffer); + } + else + { + field.OnSync(entity, buffer); + } + } public EntityClassData(EntityManager entityManager, ushort filterId, Type entType, RegisteredTypeInfo typeInfo) { diff --git a/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs b/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs index bd31d0b..da95e94 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs @@ -1,4 +1,6 @@ -namespace LiteEntitySystem.Internal +using System; + +namespace LiteEntitySystem.Internal { internal enum FieldType { @@ -73,7 +75,15 @@ public unsafe bool ReadField( if (FieldType == FieldType.SyncableSyncVar) { var syncableField = RefMagic.GetFieldValue(entity, Offset); - TypeProcessor.SetFrom(syncableField, SyncableSyncVarOffset, rawData); + if (OnSync != null && (OnSyncFlags & BindOnChangeFlags.ExecuteOnSync) != 0) + { + if (TypeProcessor.SetFromAndSync(syncableField, SyncableSyncVarOffset, rawData)) + return true; //create sync call + } + else + { + TypeProcessor.SetFrom(syncableField, SyncableSyncVarOffset, rawData); + } } else { diff --git a/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs b/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs index 0ac23bf..670a35d 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs @@ -8,8 +8,9 @@ public enum EntityState { New = 0, Constructed = 1, - Destroyed = 2, - Removed = 3 + LateConstructed = 2, + Destroyed = 3, + Removed = 4 } public abstract class InternalEntity : InternalBaseClass, IComparable @@ -159,6 +160,15 @@ internal void ConstructInternal() _entityState = EntityState.Constructed; } + internal void LateConstructInternal() + { + if (_entityState != EntityState.Constructed) + Logger.LogError($"Error! Calling late construct on not constructed entity: {this}"); + + _entityState = EntityState.LateConstructed; + OnLateConstructed(); + } + internal void Remove() { if (_entityState != EntityState.Destroyed) @@ -221,7 +231,7 @@ protected internal virtual void OnConstructed() /// /// Called when entity constructed but at end of frame /// - protected internal virtual void OnLateConstructed() + protected virtual void OnLateConstructed() { } @@ -270,7 +280,7 @@ internal void RegisterRpcInternal() { syncField.RPCOffset = (ushort)rpcCache.Count; syncFieldInfo.RPCOffset = syncField.RPCOffset; - var syncablesRegistrator = new SyncableRPCRegistrator(syncFieldInfo.Offset, rpcCache); + var syncablesRegistrator = new SyncableRPCRegistrator(syncFieldInfo.Offset, rpcCache, classData.Fields); syncField.RegisterRPC(ref syncablesRegistrator); } } diff --git a/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs b/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs index 431ca17..a68c2c6 100644 --- a/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs +++ b/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs @@ -20,6 +20,7 @@ private struct InitialData internal void InitEntity(ushort predictedId, EntitySharedReference initialParent) { + //Logger.Log($"InitEntity. PredId: {predictedId}. Id: {Id}, Class: {ClassData.ClassEnumName}. Mode: {EntityManager.Mode}. InitalParrent: {initialParent}"); _predictedId = predictedId; _initialParent = initialParent; } diff --git a/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs b/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs index ff4ca0a..f2c844a 100644 --- a/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs +++ b/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs @@ -36,11 +36,12 @@ public enum BindOnChangeFlags public delegate void SpanAction(ReadOnlySpan data); public delegate void SpanAction(TCaller caller, ReadOnlySpan data); - internal delegate void MethodCallDelegate(object classPtr, ReadOnlySpan buffer); + internal delegate void MethodCallDelegate(InternalBaseClass classPtr, ReadOnlySpan buffer); public readonly struct RemoteCall { - internal static MethodCallDelegate CreateMCD(Action methodToCall) => (classPtr, _) => methodToCall((TClass)classPtr); + internal static MethodCallDelegate CreateMCD(Action methodToCall) where TClass : InternalBaseClass => + (classPtr, _) => methodToCall((TClass)classPtr); internal readonly Action CachedAction; internal readonly ushort Id; @@ -58,7 +59,7 @@ internal RemoteCall(Action action, ushort rpcId, ExecuteFlags fl public readonly struct RemoteCall where T : unmanaged { - internal static unsafe MethodCallDelegate CreateMCD(Action methodToCall) => + internal static unsafe MethodCallDelegate CreateMCD(Action methodToCall) where TClass : InternalBaseClass => (classPtr, buffer) => { fixed (byte* data = buffer) @@ -81,7 +82,7 @@ internal RemoteCall(Action action, ushort rpcId, ExecuteFlags public readonly struct RemoteCallSpan where T : unmanaged { - internal static MethodCallDelegate CreateMCD(SpanAction methodToCall) => + internal static MethodCallDelegate CreateMCD(SpanAction methodToCall) where TClass : InternalBaseClass => (classPtr, buffer) => methodToCall((TClass)classPtr, MemoryMarshal.Cast(buffer)); internal readonly SpanAction CachedAction; @@ -100,7 +101,7 @@ internal RemoteCallSpan(SpanAction action, ushort rpcId, Exec public readonly struct RemoteCallSerializable where T : struct, ISpanSerializable { - internal static MethodCallDelegate CreateMCD(Action methodToCall) => + internal static MethodCallDelegate CreateMCD(Action methodToCall) where TClass : InternalBaseClass => (classPtr, buffer) => { var t = default(T); @@ -274,12 +275,14 @@ public void CreateRPCAction(Action methodToCall, ref Rem public readonly ref struct SyncableRPCRegistrator { + private readonly EntityFieldInfo[] _fields; private readonly List _calls; private readonly int _syncableOffset; private readonly ushort _initialCallsSize; - internal SyncableRPCRegistrator(int syncableOffset, List remoteCallsList) + internal SyncableRPCRegistrator(int syncableOffset, List remoteCallsList, EntityFieldInfo[] fields) { + _fields = fields; _calls = remoteCallsList; _initialCallsSize = (ushort)_calls.Count; _syncableOffset = syncableOffset; @@ -336,5 +339,28 @@ public void CreateClientAction(Action methodToCall remoteCallHandle = new RemoteCallSerializable(null, (ushort)(_calls.Count - _initialCallsSize), 0); _calls.Add(new RpcFieldInfo(_syncableOffset, RemoteCallSerializable.CreateMCD(methodToCall))); } + + /// + /// Bind notification of SyncVar changes to action + /// + /// Variable to bind + /// Action that will be called when variable changes by sync + public void BindOnChange(ref SyncVar syncVar, Action onChangedAction, BindOnChangeFlags flags = BindOnChangeFlags.ExecuteOnSync) where T : unmanaged where TSyncField : SyncableField + { + _fields[syncVar.FieldId].OnSync = RemoteCall.CreateMCD(onChangedAction); + _fields[syncVar.FieldId].OnSyncFlags = flags; + } + + /// + /// Bind notification of SyncVar changes to action + /// + /// Target entity for binding + /// Variable to bind + /// Action that will be called when variable changes by sync + public void BindOnChange(TSyncField self, ref SyncVar syncVar, Action onChangedAction, BindOnChangeFlags flags = BindOnChangeFlags.ExecuteOnSync) where T : unmanaged where TSyncField : SyncableField + { + RPCRegistrator.CheckTarget(self, onChangedAction.Target); + BindOnChange(ref syncVar, onChangedAction.Method.CreateDelegateHelper>(), flags); + } } } \ No newline at end of file diff --git a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs index 19c3dc5..4119d6b 100644 --- a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs @@ -868,7 +868,7 @@ bool ShouldSendRPC(RemoteCallPacket rpcNode, NetPlayer player) } } - internal override unsafe void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue, ref T oldValue) + internal override unsafe void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue, ref T oldValue, bool skipOnSync) { if (entity.IsRemoved) { @@ -882,10 +882,10 @@ internal override unsafe void EntityFieldChanged(InternalEntity entity, ushor _stateSerializers[entity.Id].UpdateFieldValue(fieldId, _minimalTick, _tick, ref newValue); ref var fieldInfo = ref entity.ClassData.Fields[fieldId]; - if ((fieldInfo.OnSyncFlags & BindOnChangeFlags.ExecuteOnServer) != 0) + if (!skipOnSync && (fieldInfo.OnSyncFlags & BindOnChangeFlags.ExecuteOnServer) != 0) { T value = oldValue; - fieldInfo.OnSync(entity, new ReadOnlySpan(&value, sizeof(T))); + entity.ClassData.CallOnSync(entity, ref fieldInfo, new ReadOnlySpan(&value, fieldInfo.IntSize)); } } diff --git a/Assets/Plugins/LiteEntitySystem/SyncVar.cs b/Assets/Plugins/LiteEntitySystem/SyncVar.cs index d0c6f52..8fd8673 100644 --- a/Assets/Plugins/LiteEntitySystem/SyncVar.cs +++ b/Assets/Plugins/LiteEntitySystem/SyncVar.cs @@ -105,9 +105,9 @@ bool ISyncVar.SvSetFromAndSync(ref T value) if (!Utils.FastEquals(ref _value, ref value)) { // ReSharper disable once SwapViaDeconstruction - var tmp = _value; + var oldValue = _value; _value = value; - value = tmp; + value = oldValue; return true; } return false; @@ -124,17 +124,29 @@ public T Value var oldValue = _value; _value = value; if (Container != null && !Utils.FastEquals(ref value, ref oldValue)) - Container.EntityManager.EntityFieldChanged(Container, FieldId, ref value, ref oldValue); + Container.EntityManager.EntityFieldChanged(Container, FieldId, ref value, ref oldValue, false); } } + /// + /// Set value without triggering local OnSync notifications if there is any + /// + /// + public void SetValueWithoutOnSyncNotification(T value) + { + var oldValue = _value; + _value = value; + if (Container != null && !Utils.FastEquals(ref value, ref oldValue)) + Container.EntityManager.EntityFieldChanged(Container, FieldId, ref value, ref oldValue, true); + } + internal void Init(InternalEntity container, ushort fieldId) { Container = container; FieldId = fieldId; T defaultValue = default; if(!Utils.FastEquals(ref _value, ref defaultValue)) - Container.EntityManager.EntityFieldChanged(Container, FieldId, ref _value, ref defaultValue); + Container.EntityManager.EntityFieldChanged(Container, FieldId, ref _value, ref defaultValue, false); } public static implicit operator T(SyncVar sv) => sv._value; diff --git a/Assets/Scripts/Client/RemotePlayerView.cs b/Assets/Scripts/Client/RemotePlayerView.cs index 7197510..96d756c 100644 --- a/Assets/Scripts/Client/RemotePlayerView.cs +++ b/Assets/Scripts/Client/RemotePlayerView.cs @@ -16,6 +16,7 @@ public static RemotePlayerView Create(RemotePlayerView prefab, BasePlayer player obj._player = player; var textObj = new GameObject($"text_{player.Name}_{player.Id}"); textObj.transform.SetParent(obj.transform); + textObj.transform.localPosition = Vector3.zero; obj._health = textObj.AddComponent(); obj._health.characterSize = 0.3f; obj._health.anchor = TextAnchor.MiddleCenter; From 13f558a5bd738abe73b23ad2ad0e756cd3a3211d Mon Sep 17 00:00:00 2001 From: RevenantX Date: Tue, 14 Oct 2025 21:12:42 +0300 Subject: [PATCH 20/22] update project. Remove some old packages --- Packages/manifest.json | 6 ++---- Packages/packages-lock.json | 25 +++---------------------- ProjectSettings/ProjectVersion.txt | 4 ++-- 3 files changed, 7 insertions(+), 28 deletions(-) diff --git a/Packages/manifest.json b/Packages/manifest.json index 30ec5d6..9c8cb98 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -3,10 +3,9 @@ "com.revenantx.litenetlib": "1.3.3", "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", - "com.unity.ai.navigation": "1.1.6", "com.unity.ext.nunit": "1.0.6", - "com.unity.ide.rider": "3.0.36", - "com.unity.ide.visualstudio": "2.0.23", + "com.unity.ide.rider": "3.0.38", + "com.unity.ide.visualstudio": "2.0.25", "com.unity.ide.vscode": "1.2.5", "com.unity.test-framework": "1.1.33", "com.unity.textmeshpro": "3.0.9", @@ -15,7 +14,6 @@ "com.unity.toolchain.macos-x86_64-linux-x86_64": "2.0.10", "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10", "com.unity.ugui": "1.0.0", - "com.unity.xr.legacyinputhelpers": "2.1.12", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index a1fb5dc..fa97225 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -22,15 +22,6 @@ "com.unity.modules.uielements": "1.0.0" } }, - "com.unity.ai.navigation": { - "version": "1.1.6", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.modules.ai": "1.0.0" - }, - "url": "https://packages.unity.com" - }, "com.unity.ext.nunit": { "version": "1.0.6", "depth": 0, @@ -39,7 +30,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.36", + "version": "3.0.38", "depth": 0, "source": "registry", "dependencies": { @@ -48,11 +39,11 @@ "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.23", + "version": "2.0.25", "depth": 0, "source": "registry", "dependencies": { - "com.unity.test-framework": "1.1.9" + "com.unity.test-framework": "1.1.31" }, "url": "https://packages.unity.com" }, @@ -150,16 +141,6 @@ "com.unity.modules.imgui": "1.0.0" } }, - "com.unity.xr.legacyinputhelpers": { - "version": "2.1.12", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.modules.vr": "1.0.0", - "com.unity.modules.xr": "1.0.0" - }, - "url": "https://packages.unity.com" - }, "com.unity.modules.ai": { "version": "1.0.0", "depth": 0, diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index 7a6f40b..b852362 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2022.3.62f1 -m_EditorVersionWithRevision: 2022.3.62f1 (4af31df58517) +m_EditorVersion: 2022.3.62f2 +m_EditorVersionWithRevision: 2022.3.62f2 (7670c08855a9) From 860a86de7811f92975343d134a9914d17bbb2de7 Mon Sep 17 00:00:00 2001 From: RevenantX Date: Mon, 26 Jan 2026 18:45:52 +0200 Subject: [PATCH 21/22] update les --- .../LiteEntitySystem/ClientEntityManager.cs | 152 +++++++++----- .../LiteEntitySystem/Collections/AVLTree.cs | 2 +- .../Plugins/LiteEntitySystem/EntityFilter.cs | 2 +- .../Plugins/LiteEntitySystem/EntityLogic.cs | 4 +- .../Plugins/LiteEntitySystem/EntityManager.cs | 55 +++-- .../Extensions/LocalMessageBus.cs | 192 ++++++++++++++++++ .../Extensions/LocalMessageBus.cs.meta | 11 + .../LiteEntitySystem/Extensions/SyncTimer.cs | 3 + .../LiteEntitySystem/ILocalSingleton.cs | 1 + .../Internal/EntityClassData.cs | 31 +-- .../Internal/EntityFieldInfo.cs | 58 ++---- .../Internal/InternalEntity.cs | 47 +++-- .../Internal/RemoteCallPacket.cs | 21 +- .../Internal/ServerStateData.cs | 17 +- .../Internal/StateSerializer.cs | 112 ++++++++-- Assets/Plugins/LiteEntitySystem/NetPlayer.cs | 6 +- Assets/Plugins/LiteEntitySystem/PawnLogic.cs | 1 - .../PredictableEntityLogic.cs | 6 + .../LiteEntitySystem/RPCRegistrator.cs | 11 +- .../LiteEntitySystem/ServerEntityManager.cs | 101 ++++++--- Assets/Plugins/LiteEntitySystem/SyncChilds.cs | 23 ++- .../Plugins/LiteEntitySystem/SyncableField.cs | 4 + Assets/Plugins/LiteEntitySystem/Utils.cs | 20 ++ ProjectSettings/ProjectVersion.txt | 4 +- 24 files changed, 680 insertions(+), 204 deletions(-) create mode 100644 Assets/Plugins/LiteEntitySystem/Extensions/LocalMessageBus.cs create mode 100644 Assets/Plugins/LiteEntitySystem/Extensions/LocalMessageBus.cs.meta diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs index 612d63b..b68a1ca 100644 --- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs @@ -128,6 +128,7 @@ public sealed class ClientEntityManager : EntityManager private readonly SequenceBinaryHeap _readyStates = new(MaxSavedStateDiff); private readonly Queue _tempLocalEntities = new (); private readonly byte[] _sendBuffer = new byte[NetConstants.MaxPacketSize]; + private readonly byte[] _zeroFieldBuffer = new byte[StateSerializer.MaxStateSize]; private readonly HashSet _changedEntities = new(); private readonly CircularBuffer _storedInputHeaders = new(InputBufferSize); private InternalEntity[] _entitiesToRemove = new InternalEntity[64]; @@ -185,7 +186,7 @@ public void Execute(ServerStateData state) { ref var classData = ref _entity.ClassData; ref var fieldInfo = ref classData.Fields[_fieldId]; - classData.CallOnSync(_entity, ref fieldInfo, new ReadOnlySpan(state.Data, _prevDataPos, fieldInfo.IntSize)); + fieldInfo.OnSync(fieldInfo.GetTargetObject(_entity), new ReadOnlySpan(state.Data, _prevDataPos, fieldInfo.IntSize)); } catch (Exception e) { @@ -283,10 +284,12 @@ internal unsafe T AddLocalEntity(EntityLogic parent, Action initMethod) wh for (int i = 0; i < classData.FieldsCount; i++) { ref var field = ref classData.Fields[i]; + var targetObject = field.GetTargetObjectAndOffset(entity, out int offset); + if(field.Flags.HasFlagFast(SyncFlags.Interpolated)) - field.TypeProcessor.SetInterpValueFromCurrentValue(entity, field.Offset); + field.TypeProcessor.SetInterpValueFromCurrentValue(targetObject, offset); if(field.IsPredicted) - field.TypeProcessor.WriteTo(entity, field.Offset, predictedData + field.PredictedOffset); + field.TypeProcessor.WriteTo(targetObject, offset, predictedData + field.PredictedOffset); } } @@ -506,27 +509,16 @@ private unsafe void GoToNextState() for (int i = 0; i < rollbackFields.Length; i++) { ref var field = ref classData.Fields[rollbackFields[i]]; + var target = field.GetTargetObjectAndOffset(entity, out int offset); + if (field.OnSync != null && (field.OnSyncFlags & BindOnChangeFlags.ExecuteOnRollbackReset) != 0) { //this is currently only place which doesn't call classData.CallOnSync - if (field.FieldType == FieldType.SyncableSyncVar) - { - var syncableField = RefMagic.GetFieldValue(entity, field.Offset); - field.TypeProcessor.SetFromAndSync(syncableField, field.SyncableSyncVarOffset, lastServerData + field.PredictedOffset, field.OnSync); - } - else - { - field.TypeProcessor.SetFromAndSync(entity, field.Offset, lastServerData + field.PredictedOffset, field.OnSync); - } - } - else if (field.FieldType == FieldType.SyncVar) - { - field.TypeProcessor.SetFrom(entity, field.Offset, lastServerData + field.PredictedOffset); + field.TypeProcessor.SetFromAndSync(target, offset, lastServerData + field.PredictedOffset, field.OnSync); } else { - var syncableField = RefMagic.GetFieldValue(entity, field.Offset); - field.TypeProcessor.SetFrom(syncableField, field.SyncableSyncVarOffset, lastServerData + field.PredictedOffset); + field.TypeProcessor.SetFrom(target, offset, lastServerData + field.PredictedOffset); } } } @@ -649,7 +641,8 @@ private unsafe void GoToNextState() for(int i = 0; i < classData.InterpolatedCount; i++) { ref var field = ref classData.Fields[i]; - field.TypeProcessor.SetInterpValueFromCurrentValue(entity, field.Offset); + var target = field.GetTargetObjectAndOffset(entity, out int offset); + field.TypeProcessor.SetInterpValueFromCurrentValue(target, offset); } } @@ -664,7 +657,7 @@ private unsafe void GoToNextState() internal void MarkEntityChanged(InternalEntity entity) { - if (entity.IsRemoved) + if (entity.IsRemoved || IsExecutingRPC) return; _modifiedEntitiesToRollback.Add(entity); } @@ -680,7 +673,7 @@ internal override unsafe void EntityFieldChanged(InternalEntity entity, ushor if (!skipOnSync && (fieldInfo.OnSyncFlags & BindOnChangeFlags.ExecuteOnPrediction) != 0) { T oldValueCopy = oldValue; - classData.CallOnSync(entity, ref fieldInfo, new ReadOnlySpan(&oldValueCopy, fieldInfo.IntSize)); + fieldInfo.OnSync(fieldInfo.GetTargetObject(entity), new ReadOnlySpan(&oldValueCopy, fieldInfo.IntSize)); } var rollbackFields = classData.GetRollbackFields(entity.IsLocalControlled); @@ -730,12 +723,14 @@ protected override void OnLogicTick() for (int i = 0; i < classData.InterpolatedCount; i++) { ref var field = ref classData.Fields[i]; - field.TypeProcessor.SetInterpValueFromCurrentValue(entity, field.Offset); + var target = field.GetTargetObjectAndOffset(entity, out int offset); + field.TypeProcessor.SetInterpValueFromCurrentValue(target, offset); } } entity.Update(); } + ExecuteLocalSingletonsLateUpdate(); } /// @@ -896,7 +891,7 @@ internal void RemoveOwned(EntityLogic entity) AliveEntities.Remove(entity); } - internal unsafe void ReadNewRPC(ushort entityId, byte* rawData) + internal unsafe void ReadNewRPC(ushort entityId, byte* rawData, int byteCount) { //Logger.Log("NewRPC"); var entityDataHeader = *(EntityDataHeader*)rawData; @@ -911,19 +906,23 @@ internal unsafe void ReadNewRPC(ushort entityId, byte* rawData) //remove old entity if (entity != null && entity.Version != entityDataHeader.Version) { - //this should be impossible now? - + //can happen when entities in remove list but new arrived //this can be only on logics (not on singletons) Logger.Log($"[CEM] Replace entity by new: {entityDataHeader.Version}. Class: {entityDataHeader.ClassId}. Id: {entityId}"); entity.DestroyInternal(); + RemoveEntity(entity); entity = null; } if (entity == null) //create new { ref var classData = ref ClassDataDict[entityDataHeader.ClassId]; - AddEntity(new EntityParams(entityId, entityDataHeader, this, classData.AllocateDataCache())); + entity = AddEntity(new EntityParams(entityId, entityDataHeader, this, classData.AllocateDataCache())); } + + ApplyEntityDelta(entity, rawData, StateSerializer.HeaderSize, true); + + //Logger.Log($"NewRPC bytes (client) eid:{entityId} cls:{entity.ClassId} data:{Utils.BytesToHexString(new ReadOnlySpan(rawData, byteCount))}"); } private void ExecuteSyncCalls(ServerStateData stateData) @@ -956,6 +955,8 @@ internal unsafe void ReadConstructRPC(ushort entityId, byte* rawData, int reader { if (!pel.IsSameAsLocal(localEntity)) continue; + + pel.IsRecreated = true; //Logger.Log($"Found predictable entity. LocalId was: {localEntity.Id}, server id: {entityId}"); @@ -966,7 +967,8 @@ internal unsafe void ReadConstructRPC(ushort entityId, byte* rawData, int reader if (field.OnSync == null || i == pel.InternalOwnerId.FieldId || i == pel.IsSyncEnabledFieldId) continue; - field.TypeProcessor.CopyFrom(pel, localEntity, field.Offset); + var target = field.GetTargetObjectAndOffset(entity, out int offset); + field.TypeProcessor.CopyFrom(target, localEntity, offset); } break; @@ -978,12 +980,23 @@ internal unsafe void ReadConstructRPC(ushort entityId, byte* rawData, int reader for (int i = 0; i < classData.FieldsCount; i++) { ref var field = ref classData.Fields[i]; + var target = field.GetTargetObjectAndOffset(entity, out int offset); if (writeInterpolationData && field.Flags.HasFlagFast(SyncFlags.Interpolated)) - field.TypeProcessor.SetInterpValue(entity, field.Offset, rawData + readerPosition); + field.TypeProcessor.SetInterpValue(target, offset, rawData + readerPosition); - if (field.ReadField(entity, rawData + readerPosition, predictedData)) - _syncCalls[_syncCallsCount++] = new SyncCallInfo(entity, readerPosition, i); + if (field.IsPredicted) + RefMagic.CopyBlock(predictedData + field.PredictedOffset, rawData + readerPosition, field.Size); + + if (field.OnSync != null && (field.OnSyncFlags & BindOnChangeFlags.ExecuteOnSync) != 0) + { + if (field.TypeProcessor.SetFromAndSync(target, offset, rawData + readerPosition)) + _syncCalls[_syncCallsCount++] = new SyncCallInfo(entity, readerPosition, i); + } + else + { + field.TypeProcessor.SetFrom(target, offset, rawData + readerPosition); + } //Logger.Log($"E {entity.Id} Field updated: {field.Name} = {field.TypeProcessor.ToString(entity, field.Offset)}"); readerPosition += field.IntSize; @@ -1020,29 +1033,76 @@ private unsafe void ReadDiff() readerPosition = endPos; continue; } + + ApplyEntityDelta(entity, rawData, readerPosition, false); + readerPosition = endPos; + } + } + } - ref var classData = ref entity.ClassData; - readerPosition += classData.FieldsFlagsSize; - _changedEntities.Add(entity); - Utils.ResizeOrCreate(ref _syncCalls, _syncCallsCount + classData.FieldsCount); + private unsafe void ApplyEntityDelta(InternalEntity entity, byte* rawData, int fieldsFlagsOffset, bool insideNewRPC) + { + ref var classData = ref entity.ClassData; + _changedEntities.Add(entity); + if (!insideNewRPC) + Utils.ResizeOrCreate(ref _syncCalls, _syncCallsCount + classData.FieldsCount); + int readerPosition = fieldsFlagsOffset + classData.FieldsFlagsSize; + + fixed (byte* predictedData = classData.GetLastServerData(entity), zeroFieldData = _zeroFieldBuffer) + { + for (int i = 0; i < classData.FieldsCount; i++) + { + ref var field = ref classData.Fields[i]; + bool hasData = Utils.IsBitSet(rawData + fieldsFlagsOffset, i); + + byte* fieldData; + int fieldSize = field.IntSize; - int fieldsFlagsOffset = readerPosition - classData.FieldsFlagsSize; - fixed (byte* predictedData = classData.GetLastServerData(entity)) + //inside new rpc StateSerializer compares data with zeroes. So use same logic to decode + if (insideNewRPC && !hasData) { - for (int i = 0; i < classData.FieldsCount; i++) + if (fieldSize > StateSerializer.MaxStateSize) { - if (!Utils.IsBitSet(rawData + fieldsFlagsOffset, i)) - continue; - ref var field = ref classData.Fields[i]; - if (field.ReadField(entity, rawData + readerPosition, predictedData)) - _syncCalls[_syncCallsCount++] = new SyncCallInfo(entity, readerPosition, i); - - //Logger.Log($"E {entity.Id} Field updated: {field.Name}"); - readerPosition += field.IntSize; + Logger.LogError($"Field too large for zero buffer. Entity: {entity}. Field: {field.Name}. Size: {fieldSize}"); + continue; } + fieldData = zeroFieldData; + } + else if (!hasData) + { + continue; + } + else + { + fieldData = rawData + readerPosition; + } + + var target = field.GetTargetObjectAndOffset(entity, out int offset); + + if (field.IsPredicted) + RefMagic.CopyBlock(predictedData + field.PredictedOffset, fieldData, field.Size); + + + if (field.OnSync != null && insideNewRPC) + { + if ((field.OnSyncFlags & BindOnChangeFlags.ExecuteOnNew) != 0) + { + //execute immediately using temp data buffer inside SetFromAndSync + field.TypeProcessor.SetFromAndSync(target, offset, fieldData, field.OnSync); + } + //else skip set? because will be triggered in OnConstructed + } + else if (field.OnSync != null && (field.OnSyncFlags & BindOnChangeFlags.ExecuteOnSync) != 0 && field.TypeProcessor.SetFromAndSync(target, offset, fieldData)) + { + _syncCalls[_syncCallsCount++] = new SyncCallInfo(entity, readerPosition, i); + } + else + { + field.TypeProcessor.SetFrom(target, offset, fieldData); } - readerPosition = endPos; + if (hasData) + readerPosition += field.IntSize; } } } diff --git a/Assets/Plugins/LiteEntitySystem/Collections/AVLTree.cs b/Assets/Plugins/LiteEntitySystem/Collections/AVLTree.cs index b89c25f..2f1104b 100644 --- a/Assets/Plugins/LiteEntitySystem/Collections/AVLTree.cs +++ b/Assets/Plugins/LiteEntitySystem/Collections/AVLTree.cs @@ -147,7 +147,7 @@ private static int RightLeftRotate(TreeNode[] nodes, int a) return c; } - internal virtual void Add(T data) + internal void Add(T data) { if (_nodesCount + 1 == int.MaxValue) throw new Exception("collection overflow"); diff --git a/Assets/Plugins/LiteEntitySystem/EntityFilter.cs b/Assets/Plugins/LiteEntitySystem/EntityFilter.cs index 8997346..b3d7e37 100644 --- a/Assets/Plugins/LiteEntitySystem/EntityFilter.cs +++ b/Assets/Plugins/LiteEntitySystem/EntityFilter.cs @@ -30,7 +30,7 @@ public void SubscribeToConstructed(Action onConstructed, bool callOnExisting) { if (callOnExisting) foreach (var entity in this) - if(entity.State == EntityState.LateConstructed) + if(entity.CreationState == EntityState.LateConstructed) onConstructed(entity); OnConstructed += onConstructed; } diff --git a/Assets/Plugins/LiteEntitySystem/EntityLogic.cs b/Assets/Plugins/LiteEntitySystem/EntityLogic.cs index adc3f37..5307644 100644 --- a/Assets/Plugins/LiteEntitySystem/EntityLogic.cs +++ b/Assets/Plugins/LiteEntitySystem/EntityLogic.cs @@ -198,7 +198,7 @@ public bool TryGetOwnerTick(out ushort tick) /// Don't call this method inside Server->Client RPC! This will break many things. /// /// Entity type - /// Method that will be called after entity constructed + /// Method that will be called before entity OnConstructed /// Created predicted local entity public T AddPredictedEntity(Action initMethod = null) where T : PredictableEntityLogic { @@ -257,7 +257,7 @@ public T AddPredictedEntity(Action initMethod = null) where T : Predictabl /// /// Entity type /// SyncVar of class that will be set to predicted entity and synchronized once confirmation will be received - /// Method that will be called after entity constructed + /// Method that will be called before entity OnConstructed /// Created predicted local entity public void AddPredictedEntity(ref SyncVar targetReference, Action initMethod = null) where T : PredictableEntityLogic { diff --git a/Assets/Plugins/LiteEntitySystem/EntityManager.cs b/Assets/Plugins/LiteEntitySystem/EntityManager.cs index a00823c..eaa9ded 100644 --- a/Assets/Plugins/LiteEntitySystem/EntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/EntityManager.cs @@ -360,8 +360,12 @@ protected EntityManager( public void GetEntitySyncVarInfo(InternalEntity entity, IEntitySyncVarInfoPrinter resultPrinter) { ref var classData = ref ClassDataDict[entity.ClassId]; - foreach (EntityFieldInfo fi in classData.Fields) - resultPrinter.PrintFieldInfo(fi.Name, fi.TypeProcessor.ToString(entity, fi.Offset)); + for(int i = 0; i < classData.FieldsCount; i++) + { + ref var fi = ref classData.Fields[i]; + var target = fi.GetTargetObjectAndOffset(entity, out int offset); + resultPrinter.PrintFieldInfo(fi.Name, fi.TypeProcessor.ToString(target, offset)); + } } /// @@ -369,18 +373,18 @@ public void GetEntitySyncVarInfo(InternalEntity entity, IEntitySyncVarInfoPrinte /// public virtual void Reset() { - EntitiesCount = 0; - - _tick = 0; - VisualDeltaTime = 0.0; - _accumulator = 0; - _lastTime = 0; - InternalPlayerId = 0; - _stopwatch.Stop(); - _stopwatch.Reset(); + var entityFilter = GetEntities(); + var entitiesToDestroy = new InternalEntity[entityFilter.Count]; + int idx = 0; foreach (var entity in GetEntities()) - entity.DestroyInternal(); + entitiesToDestroy[idx++] = entity; + + for (int i = 0; i < entitiesToDestroy.Length; i++) + entitiesToDestroy[i].DestroyInternal(); + + for (int i = 0; i < entitiesToDestroy.Length; i++) + RemoveEntity(entitiesToDestroy[i]); foreach (var localSingleton in _localSingletons) localSingleton.Value?.Destroy(); @@ -389,8 +393,18 @@ public virtual void Reset() AliveEntities.Clear(); _localSingletons.Clear(); _localSingletonBaseTypes.Clear(); - Array.Clear(EntitiesDict, 0, EntitiesDict.Length); Array.Clear(_entityFilters, 0, _entityFilters.Length); + + if(EntitiesCount != 0) + Logger.LogWarning($"Entities not fully removed: {EntitiesCount}"); + EntitiesCount = 0; + + _tick = 0; + VisualDeltaTime = 0.0; + _accumulator = 0; + _lastTime = 0; + _stopwatch.Stop(); + _stopwatch.Reset(); } /// @@ -438,7 +452,7 @@ public EntityFilter GetEntities() where T : InternalEntity if (entity is T castedEnt) { typedFilter.Add(castedEnt); - if(entity.State == EntityState.LateConstructed) + if(entity.CreationState == EntityState.LateConstructed) entityFilter.TriggerConstructedEvent(entity); } } @@ -633,6 +647,10 @@ internal virtual void OnEntityDestroyed(InternalEntity e) protected void RemoveEntity(InternalEntity e) { + //this can happen on entity replace. + if (e.IsRemoved) + return; + if(!e.IsDestroyed) Logger.LogError($"Remove not destroyed entity!: {e}"); @@ -675,6 +693,8 @@ protected void ExecuteLateConstruct() { foreach (var e in _entitiesToLateConstruct) { + if (e.IsDestroyed) + continue; e.LateConstructInternal(); ref var classData = ref ClassDataDict[e.ClassId]; @@ -699,6 +719,13 @@ protected void ExecuteLateConstruct() internal abstract void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue, ref T oldValue, bool skipOnSync) where T : unmanaged; + protected void ExecuteLocalSingletonsLateUpdate() + { + foreach (var localSingleton in _localSingletons) + if(localSingleton.Value is ILocalSingletonWithUpdate updSingleton) + updSingleton.LateUpdate(DeltaTimeF); + } + /// /// Main update method, updates internal fixed timer and do all other stuff /// diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/LocalMessageBus.cs b/Assets/Plugins/LiteEntitySystem/Extensions/LocalMessageBus.cs new file mode 100644 index 0000000..129db85 --- /dev/null +++ b/Assets/Plugins/LiteEntitySystem/Extensions/LocalMessageBus.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using LiteEntitySystem; +using LiteEntitySystem.Internal; + +namespace LiteEntitySystem.Extensions +{ + /// + /// Represents a subscription to a message bus channel that can be disposed to unsubscribe. + /// + public readonly struct BusSubscription : IDisposable + { + private readonly Action _dispose; + public BusSubscription(Action dispose) => _dispose = dispose; + + /// + /// Disposes the subscription, unsubscribing the handler from the message bus. + /// + public void Dispose() => _dispose?.Invoke(); + } + + /// + /// Marker interface for message bus messages. TSender declares the expected sender type for the message. + /// + /// The type of the entity that sends this message. + public interface IBusMessage + { + } + + /// + /// A local message bus for publishing and subscribing to typed messages within a local singleton context. + /// + public sealed class LocalMessageBus : ILocalSingleton + { + /// + /// If true, all published messages will be logged for debugging purposes. + /// + public bool DebugToLog; + + private interface IChannel : IDisposable { } + + private interface IChannel : IChannel + where TMessage : struct, IBusMessage + { + BusSubscription Subscribe(Action handler); + void Publish(TSender sender, in TMessage msg, bool debugToLog = false); + } + + // Null-object channel used after the bus is disposed to avoid null checks and NREs + private sealed class NullChannel : IChannel + where TMessage : struct, IBusMessage + { + public static readonly NullChannel Instance = new(); + public BusSubscription Subscribe(Action handler) => default; + public void Publish(TSender sender, in TMessage msg, bool debugToLog = false) { } + public void Dispose() { } + } + + private sealed class Channel : IChannel + where TMessage : struct, IBusMessage + { + private const int PreallocatedSize = 8; + private Action[] _buffer; + + private readonly List> _handlers = new(); + private bool _disposed; + + public BusSubscription Subscribe(Action handler) + { + if (_disposed) throw new ObjectDisposedException(nameof(Channel)); + _handlers.Add(handler); + return new BusSubscription(() => Unsubscribe(handler)); + } + + private void Unsubscribe(Action handler) + { + _handlers.Remove(handler); + } + + public void Publish(TSender sender, in TMessage msg, bool debugToLog = false) + { + if (_disposed || _handlers == null || _handlers.Count == 0) + return; + + var count = _handlers.Count; + if (_buffer == null || _buffer.Length < count) + _buffer = new Action[Math.Max(count, PreallocatedSize)]; + + // Fast copy handlers into buffer + _handlers.CopyTo(0, _buffer, 0, count); + + for (int i = 0; i < count; i++) + { + if (debugToLog) + { + Logger.Log($"MsgBus: {typeof(TSender)} -> {_buffer[i].Target}, msg: {msg}"); + } + _buffer[i](sender, msg); + } + } + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + _handlers.Clear(); + } + } + + private readonly Dictionary _channels = new(); + private bool _disposed; + + private IChannel Get() + where TMessage : struct, IBusMessage + { + if (_disposed) + return NullChannel.Instance; + + var t = typeof(TMessage); + if (_channels.TryGetValue(t, out var ch)) + return (IChannel)ch; + + var created = new Channel(); + _channels.Add(t, created); + return created; + } + + /// + /// Subscribes a handler to receive messages of a specific type. + /// + /// The type of the sender of the message. + /// The type of message to subscribe to. + /// The handler delegate to invoke when messages are published. + /// A subscription token that can be disposed to unsubscribe. + public BusSubscription Subscribe(Action handler) + where TMessage : struct, IBusMessage + => Get().Subscribe(handler); + + /// + /// Publishes a message to all subscribers. + /// + /// The type of the sender of the message. + /// The type of message to publish. + /// The entity sending the message. + /// The message to publish. + public void Send(TSender sender, in TMessage msg) + where TMessage : struct, IBusMessage + => Get().Publish(sender, in msg, DebugToLog); + + /// + /// Destroys the message bus, disposing all channels and unsubscribing all handlers. + /// + public void Destroy() + { + if (_disposed) return; + _disposed = true; + foreach (var ch in _channels.Values) + ch.Dispose(); + _channels.Clear(); + } + } + + /// + /// Extension methods for accessing the local message bus from entities. + /// + public static class LocalMessageBusExtensions + { + /// + /// Gets or creates a local message bus singleton for the entity manager. + /// + /// The entity to get the message bus from. + /// The local message bus instance, creating it if it doesn't exist. + public static LocalMessageBus GetOrCreateLocalMessageBus(this InternalEntity ent) + { + var msgBus = ent.EntityManager.GetLocalSingleton(); + if(msgBus == null) + { + msgBus = new LocalMessageBus(); + ent.EntityManager.AddLocalSingleton(msgBus); + } + return msgBus; + } + + /// + /// Gets the local message bus singleton from the entity manager. + /// + /// The entity to get the message bus from. + /// The local message bus instance, or null if not yet created. + public static LocalMessageBus GetLocalMessageBus(this InternalEntity ent) => + ent.EntityManager.GetLocalSingleton(); + } +} \ No newline at end of file diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/LocalMessageBus.cs.meta b/Assets/Plugins/LiteEntitySystem/Extensions/LocalMessageBus.cs.meta new file mode 100644 index 0000000..60b61bf --- /dev/null +++ b/Assets/Plugins/LiteEntitySystem/Extensions/LocalMessageBus.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a935e910dd44ccf1696a1b7fd0028dc5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncTimer.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncTimer.cs index 8a85aae..487c1d9 100644 --- a/Assets/Plugins/LiteEntitySystem/Extensions/SyncTimer.cs +++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncTimer.cs @@ -26,6 +26,9 @@ public float Progress { get { + if(_maxTime == 0f) + return 1f; + float p = _time/_maxTime; return p > 1f ? 1f : p; } diff --git a/Assets/Plugins/LiteEntitySystem/ILocalSingleton.cs b/Assets/Plugins/LiteEntitySystem/ILocalSingleton.cs index abb7971..0f5d310 100644 --- a/Assets/Plugins/LiteEntitySystem/ILocalSingleton.cs +++ b/Assets/Plugins/LiteEntitySystem/ILocalSingleton.cs @@ -9,5 +9,6 @@ public interface ILocalSingletonWithUpdate : ILocalSingleton { void Update(float dt); void VisualUpdate(float dt); + void LateUpdate(float dt); } } \ No newline at end of file diff --git a/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs b/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs index 223607d..f004d5a 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs @@ -92,6 +92,7 @@ internal struct EntityClassData public int[] GetRollbackFields(bool isOwned) => isOwned ? _ownedRollbackFields : _remoteRollbackFields; + //here Offset used because SyncableFields doesn't support LagCompensated fields public unsafe void WriteHistory(EntityLogic e, ushort tick) { int historyOffset = ((tick % _maxHistoryCount)+1)*LagCompensatedSize; @@ -164,19 +165,6 @@ public void ReleaseDataCache(InternalEntity entity) entity.IOBuffer = null; } } - - public void CallOnSync(InternalBaseClass entity, ref EntityFieldInfo field, ReadOnlySpan buffer) - { - if (field.FieldType == FieldType.SyncableSyncVar) - { - var syncableField = RefMagic.GetFieldValue(entity, field.Offset); - field.OnSync(syncableField, buffer); - } - else - { - field.OnSync(entity, buffer); - } - } public EntityClassData(EntityManager entityManager, ushort filterId, Type entType, RegisteredTypeInfo typeInfo) { @@ -244,7 +232,7 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp { InterpolatedCount++; } - var fieldInfo = new EntityFieldInfo($"{baseType.Name}-{field.Name}", valueTypeProcessor, offset, syncVarFlags); + var fieldInfo = new EntityFieldInfo($"{baseType.Name}-{field.Name}", valueTypeProcessor, offset, syncVarFlags?.Flags ?? SyncFlags.None); if (syncFlags.HasFlagFast(SyncFlags.LagCompensated)) { lagCompensatedFields.Add(fieldInfo); @@ -294,8 +282,21 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp Logger.LogError($"Unregistered field type: {syncableFieldType}"); continue; } + + var mergedSyncFlags = (syncableField.GetCustomAttribute()?.Flags ?? SyncFlags.None) | (syncVarFlags?.Flags ?? SyncFlags.None); + if (mergedSyncFlags.HasFlagFast(SyncFlags.OnlyForOwner) && + mergedSyncFlags.HasFlagFast(SyncFlags.OnlyForOtherPlayers)) + { + Logger.LogWarning($"{SyncFlags.OnlyForOwner} and {SyncFlags.OnlyForOtherPlayers} flags can't be used together! Field: {syncableType} - {syncableField.Name}"); + } + if (mergedSyncFlags.HasFlagFast(SyncFlags.AlwaysRollback) && + mergedSyncFlags.HasFlagFast(SyncFlags.NeverRollBack)) + { + Logger.LogWarning($"{SyncFlags.AlwaysRollback} and {SyncFlags.NeverRollBack} flags can't be used together! Field: {syncableType} - {syncableField.Name}"); + } + int syncvarOffset = Utils.GetFieldOffset(syncableField); - var fieldInfo = new EntityFieldInfo($"{baseType.Name}-{field.Name}:{syncableField.Name}", valueTypeProcessor, offset, syncvarOffset, syncVarFlags); + var fieldInfo = new EntityFieldInfo($"{baseType.Name}-{field.Name}:{syncableField.Name}", valueTypeProcessor, offset, syncvarOffset, mergedSyncFlags); fields.Add(fieldInfo); FixedFieldsSize += fieldInfo.IntSize; if (fieldInfo.IsPredicted) diff --git a/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs b/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs index da95e94..93819ad 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; namespace LiteEntitySystem.Internal { @@ -12,7 +13,6 @@ internal struct EntityFieldInfo { public readonly string Name; //used for debug public readonly ValueTypeProcessor TypeProcessor; - public readonly int Offset; public readonly int SyncableSyncVarOffset; public readonly uint Size; public readonly int IntSize; @@ -24,16 +24,21 @@ internal struct EntityFieldInfo public BindOnChangeFlags OnSyncFlags; public int FixedOffset; public int PredictedOffset; + + /// + /// Direct field offset which for Entities - is SyncVar, and for SyncableField - SyncableField + /// + public readonly int Offset; //for value type - public EntityFieldInfo(string name, ValueTypeProcessor valueTypeProcessor, int offset, SyncVarFlags flags) : + public EntityFieldInfo(string name, ValueTypeProcessor valueTypeProcessor, int offset, SyncFlags flags) : this(name, valueTypeProcessor, offset, -1, flags, FieldType.SyncVar) { } //For syncable syncvar - public EntityFieldInfo(string name, ValueTypeProcessor valueTypeProcessor, int offset, int syncableSyncVarOffset, SyncVarFlags flags) : + public EntityFieldInfo(string name, ValueTypeProcessor valueTypeProcessor, int offset, int syncableSyncVarOffset, SyncFlags flags) : this(name, valueTypeProcessor, offset, syncableSyncVarOffset, flags, FieldType.SyncableSyncVar) { @@ -44,7 +49,7 @@ private EntityFieldInfo( ValueTypeProcessor valueTypeProcessor, int offset, int syncableSyncVarOffset, - SyncVarFlags flags, + SyncFlags flags, FieldType fieldType) { OnSyncFlags = 0; @@ -58,47 +63,28 @@ private EntityFieldInfo( FixedOffset = 0; PredictedOffset = 0; OnSync = null; - Flags = flags?.Flags ?? SyncFlags.None; + Flags = flags; IsPredicted = Flags.HasFlagFast(SyncFlags.AlwaysRollback) || (!Flags.HasFlagFast(SyncFlags.OnlyForOtherPlayers) && !Flags.HasFlagFast(SyncFlags.NeverRollBack)); } - - public unsafe bool ReadField( - InternalEntity entity, - byte* rawData, - byte* predictedData) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public InternalBaseClass GetTargetObjectAndOffset(InternalEntity entity, out int offset) { - if (IsPredicted) - RefMagic.CopyBlock(predictedData + PredictedOffset, rawData, Size); if (FieldType == FieldType.SyncableSyncVar) { - var syncableField = RefMagic.GetFieldValue(entity, Offset); - if (OnSync != null && (OnSyncFlags & BindOnChangeFlags.ExecuteOnSync) != 0) - { - if (TypeProcessor.SetFromAndSync(syncableField, SyncableSyncVarOffset, rawData)) - return true; //create sync call - } - else - { - TypeProcessor.SetFrom(syncableField, SyncableSyncVarOffset, rawData); - } + offset = SyncableSyncVarOffset; + return RefMagic.GetFieldValue(entity, Offset); } - else - { - if (OnSync != null && (OnSyncFlags & BindOnChangeFlags.ExecuteOnSync) != 0) - { - if (TypeProcessor.SetFromAndSync(entity, Offset, rawData)) - return true; //create sync call - } - else - { - TypeProcessor.SetFrom(entity, Offset, rawData); - } - } - - return false; + offset = Offset; + return entity; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public InternalBaseClass GetTargetObject(InternalEntity entity) => + FieldType == FieldType.SyncableSyncVar + ? RefMagic.GetFieldValue(entity, Offset) + : entity; } } \ No newline at end of file diff --git a/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs b/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs index 670a35d..a5fadc8 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs @@ -49,6 +49,11 @@ public abstract class InternalEntity : InternalBaseClass, IComparable public bool IsClient => EntityManager.IsClient; + /// + /// Entity ClassId name + /// + public string ClassIdName => ClassData.ClassEnumName; + /// /// Entity version (for id reuse) /// @@ -123,7 +128,13 @@ public abstract class InternalEntity : InternalBaseClass, IComparable /// Entity state. New, Constructed, Destroyed, Removed /// - public EntityState State => _entityState; + public EntityState CreationState => _entityState; + + /// + /// Used to detect situation when base.RegisterRPC isn't called + /// + [ThreadStatic] + private static bool IsBaseRegisterRPCCalled; /// /// Destroy entity @@ -147,6 +158,12 @@ internal virtual void DestroyInternal() { if (IsDestroyed) return; + if (_entityState == EntityState.Constructed) + { + //id destroyed before late construct - execute late construct first + LateConstructInternal(); + } + _entityState = EntityState.Destroyed; EntityManager.OnEntityDestroyed(this); OnDestroy(); @@ -155,7 +172,7 @@ internal virtual void DestroyInternal() internal void ConstructInternal() { if (_entityState != EntityState.New) - Logger.LogError($"Error! Calling construct on not new entity: {this}"); + Logger.LogError($"Error! Calling construct on not new entity: {this}. State: {_entityState}"); _entityState = EntityState.Constructed; } @@ -163,7 +180,7 @@ internal void ConstructInternal() internal void LateConstructInternal() { if (_entityState != EntityState.Constructed) - Logger.LogError($"Error! Calling late construct on not constructed entity: {this}"); + Logger.LogError($"Error! Calling late construct on not constructed entity: {this}. State: {_entityState}"); _entityState = EntityState.LateConstructed; OnLateConstructed(); @@ -172,7 +189,7 @@ internal void LateConstructInternal() internal void Remove() { if (_entityState != EntityState.Destroyed) - Logger.LogError($"Error! Calling remove on not destroyed entity: {this}"); + Logger.LogError($"Error! Calling remove on not destroyed entity: {this}. State: {_entityState}"); _entityState = EntityState.Removed; } @@ -244,15 +261,12 @@ internal void RegisterRpcInternal() for (int i = 0; i < classData.FieldsCount; i++) { ref var field = ref classData.Fields[i]; - if (field.FieldType == FieldType.SyncVar) - { - field.TypeProcessor.InitSyncVar(this, field.Offset, this, (ushort)i); - } - else - { - var syncableField = RefMagic.GetFieldValue(this, field.Offset); - field.TypeProcessor.InitSyncVar(syncableField, field.SyncableSyncVarOffset, this, (ushort)i); - } + var target = field.GetTargetObjectAndOffset(this, out int offset); + + //init before SyncVar init because SyncVar can call OnChange + if (field.FieldType == FieldType.SyncableSyncVar) + RefMagic.GetFieldValue(this, field.Offset).Init(this, field.Flags); + field.TypeProcessor.InitSyncVar(target, offset, this, (ushort)i); } List rpcCache = null; @@ -263,7 +277,12 @@ internal void RegisterRpcInternal() RemoteCallPacket.InitReservedRPCs(rpcCache); var rpcRegistrator = new RPCRegistrator(rpcCache, classData.Fields); + IsBaseRegisterRPCCalled = false; RegisterRPC(ref rpcRegistrator); + if (!IsBaseRegisterRPCCalled) + { + Logger.LogError($"Error! You need always call to base.RegisterRPC in your overrides. Failed class: {ClassIdName}"); + } //Logger.Log($"RegisterRPCs for class: {classData.ClassId}"); } //setup id for later sync calls @@ -295,7 +314,7 @@ internal void RegisterRpcInternal() /// protected virtual void RegisterRPC(ref RPCRegistrator r) { - + IsBaseRegisterRPCCalled = true; } protected void ExecuteRPC(in RemoteCall rpc) diff --git a/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs b/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs index 084f71d..03c3211 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs @@ -10,6 +10,16 @@ internal struct RPCHeader public ushort Tick; public ushort ByteCount; } + + internal enum InternalRPCType : ushort + { + New, + NewOwned, + Construct, + Destroy, + + Total + } internal sealed class RemoteCallPacket { @@ -19,21 +29,14 @@ internal sealed class RemoteCallPacket public ExecuteFlags ExecuteFlags; public int TotalSize => RpcDeltaCompressor.MaxDeltaSize + Header.ByteCount; - - public const int ReservedRPCsCount = 4; - - public const ushort NewRPCId = 0; - public const ushort NewOwnedRPCId = 1; - public const ushort ConstructRPCId = 2; - public const ushort DestroyRPCId = 3; //can be static because doesnt use any buffers private static DeltaCompressor RpcDeltaCompressor = new(Utils.SizeOfStruct()); public static void InitReservedRPCs(List rpcCache) { - for(int i = 0; i < ReservedRPCsCount; i++) - rpcCache.Add(new RpcFieldInfo(-1, null)); + for(var i = InternalRPCType.New; i < InternalRPCType.Total; i++) + rpcCache.Add(new RpcFieldInfo(null)); } public bool AllowToSendForPlayer(byte forPlayerId, byte entityOwnerId) diff --git a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs index 14bf47c..55b581e 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs @@ -182,7 +182,8 @@ private unsafe void PreloadInterpolation(InternalEntity entity, int offset) if (!Utils.IsBitSet(Data, entityFieldsOffset, i)) continue; ref var field = ref classData.Fields[i]; - field.TypeProcessor.SetInterpValue(entity, field.Offset, rawData + stateReaderOffset); + var target = field.GetTargetObjectAndOffset(entity, out int fieldOffset); + field.TypeProcessor.SetInterpValue(target, fieldOffset, rawData + stateReaderOffset); stateReaderOffset += field.IntSize; } } @@ -258,10 +259,10 @@ public unsafe void ExecuteRpcs(ushort minimalTick, RPCExecuteMode executeMode) } } - if (header.Id == RemoteCallPacket.NewRPCId || - header.Id == RemoteCallPacket.NewOwnedRPCId) + if (header.Id == (ushort)InternalRPCType.New || + header.Id == (ushort)InternalRPCType.NewOwned) { - _entityManager.ReadNewRPC(header.EntityId, rawData + remoteCallInfo.DataOffset); + _entityManager.ReadNewRPC(header.EntityId, rawData + remoteCallInfo.DataOffset, remoteCallInfo.Header.ByteCount); continue; } @@ -278,15 +279,15 @@ public unsafe void ExecuteRpcs(ushort minimalTick, RPCExecuteMode executeMode) { try { - switch (header.Id) + switch ((InternalRPCType)header.Id) { - case RemoteCallPacket.ConstructRPCId: + case InternalRPCType.Construct: //Logger.Log($"ConstructRPC for entity: {header.EntityId}, Size: {header.ByteCount}, RpcReadPos: {remoteCallInfo.DataOffset}, Tick: {header.Tick}"); //Logger.Log($"CRPCData: {Utils.BytesToHexString(new ReadOnlySpan(rawData + remoteCallInfo.DataOffset, header.ByteCount))}"); _entityManager.ReadConstructRPC(header.EntityId, rawData, remoteCallInfo.DataOffset); break; - case RemoteCallPacket.DestroyRPCId: + case InternalRPCType.Destroy: //Logger.Log($"DestroyRPC for {header.EntityId}"); entity.DestroyInternal(); break; @@ -371,7 +372,7 @@ private unsafe void PreloadRPCs(int rpcsSize) { executeOnNextState = true; } - else if (header.Id == RemoteCallPacket.NewOwnedRPCId || + else if (header.Id == (ushort)InternalRPCType.NewOwned || _entityManager.EntitiesDict[header.EntityId]?.InternalOwnerId.Value == _entityManager.InternalPlayerId) { LocalEntitiesBuffer.Add(header.EntityId); diff --git a/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs b/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs index 09641ff..9d54726 100644 --- a/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs +++ b/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs @@ -4,7 +4,7 @@ namespace LiteEntitySystem.Internal { internal struct StateSerializer { - public static readonly int HeaderSize = Utils.SizeOfStruct(); + public static readonly ushort HeaderSize = (ushort)Utils.SizeOfStruct(); public const int DiffHeaderSize = 4; public const int MaxStateSize = 32767; //half of ushort @@ -19,12 +19,18 @@ internal struct StateSerializer private ushort[] _fieldChangeTicks; private ushort _versionChangedTick; private uint _fullDataSize; + private DateTime _lastRefreshedTime; + private int _secondsBetweenRefresh; public byte NextVersion; public ushort LastChangedTick; - private DateTime _lastRefreshedTime; - private int _secondsBetweenRefresh; + /// + /// Get maximum delta size + /// + /// FullDataSize + ushort(size of data) + ushort(entityId) + _fieldFlagsSize + public int MaximumSize => + _entity == null ? 0 : (int)_fullDataSize + sizeof(ushort) + sizeof(ushort) + _fieldsFlagsSize; public void AllocateMemory(ref EntityClassData classData, byte[] ioBuffer) { @@ -78,7 +84,7 @@ public void MarkFieldsChanged(ushort minimalTick, ushort tick, SyncFlags onlyWit public void MarkChanged(ushort minimalTick, ushort tick) { LastChangedTick = tick; - //refresh every X seconds to prevent wrap-arounded bugs + //refresh every X seconds to prevent wrap-around bugs DateTime currentTime = DateTime.UtcNow; if ((currentTime - _lastRefreshedTime).TotalSeconds > _secondsBetweenRefresh) { @@ -90,24 +96,95 @@ public void MarkChanged(ushort minimalTick, ushort tick) } } - public int GetMaximumSize() => - _entity == null ? 0 : (int)_fullDataSize + sizeof(ushort) + sizeof(ushort) + _fieldsFlagsSize; - - public void MakeNewRPC() + public RemoteCallPacket MakeNewRPC() { - _entity.ServerManager.AddRemoteCall( + //add rpc + return _entity.ServerManager.AddRemoteCall( _entity, - new ReadOnlySpan(_latestEntityData, 0, HeaderSize), - RemoteCallPacket.NewRPCId, + (ushort)InternalRPCType.New, ExecuteFlags.SendToAll); } + + public unsafe void RefreshNewRPC(RemoteCallPacket packet) + { + //skip cases when no packet generated + if (packet == null) + return; + + Utils.ResizeOrCreate(ref packet.Data, MaximumSize); + + fixed (byte* lastEntityData = _latestEntityData, resultData = packet.Data) + { + //copy header + RefMagic.CopyBlock(resultData, lastEntityData, HeaderSize); + //make diff between default data + byte* entityDataAfterHeader = lastEntityData + HeaderSize; + + // -1 for cycle + int writePosition = HeaderSize + _fieldsFlagsSize; + byte* fields = resultData + HeaderSize - 1; + int readPosition = 0; + + //write fields + for (int i = 0; i < _fieldsCount; i++) + { + if (i % 8 == 0) //reset next byte each 8 bits + *++fields = 0; + + ref var field = ref _fields[i]; + + /* + if (((field.Flags & SyncFlags.OnlyForOwner) != 0 && !isOwned) || + ((field.Flags & SyncFlags.OnlyForOtherPlayers) != 0 && isOwned)) + { + //Logger.Log($"SkipSync: {field.Name}, isOwned: {isOwned}"); + continue; + } + + + if(!isOwned && SyncGroupUtils.IsSyncVarDisabled(enabledSyncGroups, field.Flags)) + { + //IgnoreDiffSyncSettings + continue; + } + */ + //compare with 0 + if (Utils.IsZero(lastEntityData + HeaderSize + readPosition, field.IntSize)) + { + readPosition += field.IntSize; + continue; + } + readPosition += field.IntSize; + + *fields |= (byte)(1 << i % 8); + //Logger.Log($"WriteNewChanges. Entity: {_entity}, {field.Name}"); + + RefMagic.CopyBlock(resultData + writePosition, entityDataAfterHeader + field.FixedOffset, field.Size); + writePosition += field.IntSize; + } + + if (writePosition > MaxStateSize) + { + Logger.LogError($"Entity {_entity.Id}, Class: {_entity.ClassId} state size is more than: {MaxStateSize}"); + writePosition = HeaderSize; + } + + //update info after resize for correct buffer allocation + int prevTotalSize = packet.TotalSize; + packet.Header.ByteCount = (ushort)writePosition; + _entity.ServerManager.NotifyRPCResized(prevTotalSize, packet.TotalSize); + + //if (writePosition > HeaderSize + _fieldsFlagsSize) + // Logger.Log($"NewRPC bytes (server) eid:{_entity.Id} cls:{_entity.ClassId} len:{writePosition} data:{Utils.BytesToHexString(new ReadOnlySpan(resultData, writePosition))}"); + } + } //refresh construct rpc with latest values (old behaviour) public unsafe void RefreshConstructedRPC(RemoteCallPacket packet) { fixed (byte* sourceData = _latestEntityData, rawData = packet.Data) { - RefMagic.CopyBlock(rawData, sourceData + HeaderSize, (uint)(_fullDataSize - HeaderSize)); + RefMagic.CopyBlock(rawData, sourceData + HeaderSize, _fullDataSize - HeaderSize); } } @@ -157,7 +234,7 @@ public void MakeConstructedRPC(NetPlayer player) _entity.ServerManager.AddRemoteCall( _entity, (ReadOnlySpan)entityDataSpan, - RemoteCallPacket.ConstructRPCId, + (ushort)InternalRPCType.Construct, ExecuteFlags.SendToAll); //Logger.Log($"Added constructed RPC: {_entity}"); } @@ -168,7 +245,7 @@ public void MakeDestroyedRPC(ushort tick) LastChangedTick = tick; _entity.ServerManager.AddRemoteCall( _entity, - RemoteCallPacket.DestroyRPCId, + (ushort)InternalRPCType.Destroy, ExecuteFlags.SendToAll); } @@ -240,11 +317,8 @@ public unsafe bool MakeDiff(NetPlayer player, ushort minimalTick, byte* resultDa //write fields for (int i = 0; i < _fieldsCount; i++) { - if (i % 8 == 0) - { - fields++; - *fields = 0; - } + if (i % 8 == 0) //reset next byte each 8 bits + *++fields = 0; //not actual if (Utils.SequenceDiff(_fieldChangeTicks[i], compareToTick) <= 0) diff --git a/Assets/Plugins/LiteEntitySystem/NetPlayer.cs b/Assets/Plugins/LiteEntitySystem/NetPlayer.cs index c429905..9a73532 100644 --- a/Assets/Plugins/LiteEntitySystem/NetPlayer.cs +++ b/Assets/Plugins/LiteEntitySystem/NetPlayer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using LiteEntitySystem.Collections; using LiteEntitySystem.Internal; using LiteEntitySystem.Transport; @@ -43,7 +44,8 @@ public class NetPlayer internal NetPlayerState State; internal readonly SequenceBinaryHeap AvailableInput; internal readonly Dictionary EntitySyncInfo; - + internal DateTime ServerTickChangedTime; + internal NetPlayer(AbstractNetPeer peer, byte id) { Id = id; diff --git a/Assets/Plugins/LiteEntitySystem/PawnLogic.cs b/Assets/Plugins/LiteEntitySystem/PawnLogic.cs index 8e0a781..d2a5648 100644 --- a/Assets/Plugins/LiteEntitySystem/PawnLogic.cs +++ b/Assets/Plugins/LiteEntitySystem/PawnLogic.cs @@ -27,7 +27,6 @@ internal set protected internal override void Update() { - base.Update(); Controller?.BeforeControlledUpdate(); } diff --git a/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs b/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs index a68c2c6..6e4c0e7 100644 --- a/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs +++ b/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs @@ -18,6 +18,12 @@ private struct InitialData //used for spawn prediction internal readonly ushort CreatedAtTick; + /// + /// Is entity is recrated from server when created using AddPredictedEntity + /// Can be true only on client + /// + public bool IsRecreated { get; internal set; } + internal void InitEntity(ushort predictedId, EntitySharedReference initialParent) { //Logger.Log($"InitEntity. PredId: {predictedId}. Id: {Id}, Class: {ClassData.ClassEnumName}. Mode: {EntityManager.Mode}. InitalParrent: {initialParent}"); diff --git a/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs b/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs index f2c844a..40cc174 100644 --- a/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs +++ b/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs @@ -14,7 +14,7 @@ public enum BindOnChangeFlags ExecuteOnSync = 1 << 0, /// - /// Execute on local prediction on Client + /// Execute on local prediction on Client and on rollback /// ExecuteOnPrediction = 1 << 1, @@ -28,6 +28,11 @@ public enum BindOnChangeFlags /// ExecuteOnRollbackReset = 1 << 3, + /// + /// Execute after entity new() called and initial state read before OnConstructed + /// + ExecuteOnNew = 1 << 4, + /// /// Combines ExecuteOnSync, ExecuteOnPrediction and ExecuteOnServer flags /// @@ -220,6 +225,7 @@ public void CreateRPCAction(TEntity self, Action methodToCall, re /// /// Creates cached rpc action + /// This method can be used for virtual methods /// /// RPC method to call /// output handle that should be used to call rpc @@ -233,6 +239,7 @@ public void CreateRPCAction(Action methodToCall, ref RemoteCal /// /// Creates cached rpc action with valueType argument + /// This method can be used for virtual methods /// /// RPC method to call /// output handle that should be used to call rpc @@ -246,6 +253,7 @@ public void CreateRPCAction(Action methodToCall, ref Rem /// /// Creates cached rpc action with Span argument + /// This method can be used for virtual methods /// /// RPC method to call /// output handle that should be used to call rpc @@ -259,6 +267,7 @@ public void CreateRPCAction(SpanAction methodToCall, ref /// /// Creates cached rpc action with ISpanSerializable argument + /// This method can be used for virtual methods /// /// RPC method to call /// output handle that should be used to call rpc diff --git a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs index 4119d6b..f09a549 100644 --- a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs @@ -61,6 +61,11 @@ public sealed class ServerEntityManager : EntityManager /// Network players count /// public int PlayersCount => _netPlayers.Count; + + /// + /// Timeout after which player will receive baseline state if player cannot receive big partial state + /// + public float PlayerResyncTimeout = 4.0f; /// /// Rate at which server will make and send packets @@ -288,7 +293,7 @@ public T AddController(NetPlayer owner, Action initMethod = null) where T /// /// Player that owns this controller /// pawn that will be controlled - /// Method that will be called after entity construction + /// Method that will be called before entity OnConstructed /// Entity type /// Created entity or null in case of limit public T AddController(NetPlayer owner, PawnLogic entityToControl, Action initMethod = null) where T : HumanControllerLogic => @@ -302,7 +307,7 @@ public T AddController(NetPlayer owner, PawnLogic entityToControl, Action /// /// Add new AI controller entity /// - /// Method that will be called after entity construction + /// Method that will be called before entity OnConstructed /// Entity type /// Created entity or null in case of limit public T AddAIController(Action initMethod = null) where T : AiControllerLogic => @@ -311,7 +316,7 @@ public T AddAIController(Action initMethod = null) where T : AiControllerL /// /// Add new entity /// - /// Method that will be called after entity construction + /// Method that will be called before entity OnConstructed /// Entity type /// Created entity or null in case of limit public T AddSingleton(Action initMethod = null) where T : SingletonEntityLogic => @@ -320,7 +325,7 @@ public T AddSingleton(Action initMethod = null) where T : SingletonEntityL /// /// Add new entity /// - /// Method that will be called after entity construction + /// Method that will be called before entity OnConstructed /// Entity type /// Created entity or null in case of limit public T AddEntity(Action initMethod = null) where T : EntityLogic => @@ -330,7 +335,7 @@ public T AddEntity(Action initMethod = null) where T : EntityLogic => /// Add new entity and set parent entity /// /// Parent entity - /// Method that will be called after entity construction + /// Method that will be called before entity OnConstructed /// Entity type /// Created entity or null in case of limit public T AddEntity(EntityLogic parent, Action initMethod = null) where T : EntityLogic => @@ -356,7 +361,7 @@ public DeserializeResult Deserialize(AbstractNetPeer peer, ReadOnlySpan in /// incoming data with header public unsafe DeserializeResult Deserialize(NetPlayer player, ReadOnlySpan inData) { - if (inData[0] != HeaderByte) + if (inData.Length == 0 || inData[0] != HeaderByte) return DeserializeResult.HeaderCheckFailed; inData = inData.Slice(1); @@ -395,8 +400,11 @@ public unsafe DeserializeResult Deserialize(NetPlayer player, ReadOnlySpan humanControllerLogic.DeltaDecodeInit(); } + if (player.State == NetPlayerState.WaitingForFirstInput) + player.AvailableInput.Clear(); + ushort clientTick = BitConverter.ToUInt16(inData); - inData = inData.Slice(2); + inData = inData.Slice(sizeof(ushort)); while (inData.Length >= InputPacketHeader.Size) { var inputInfo = new InputInfo{ Tick = clientTick }; @@ -429,7 +437,10 @@ public unsafe DeserializeResult Deserialize(NetPlayer player, ReadOnlySpan } inputInfo.Header.LerpMsec = Math.Clamp(inputInfo.Header.LerpMsec, 0f, 1f); if (Utils.SequenceDiff(inputInfo.Header.StateB, player.CurrentServerTick) > 0) + { player.CurrentServerTick = inputInfo.Header.StateB; + player.ServerTickChangedTime = DateTime.UtcNow; + } //Logger.Log($"ReadInput: {clientTick} stateA: {inputInfo.Header.StateA}"); clientTick++; @@ -485,17 +496,21 @@ private T Add(Action initMethod) where T : InternalEntity stateSerializer.Init(entity, _tick); //create new rpc - stateSerializer.MakeNewRPC(); + var newRPCPacket = stateSerializer.MakeNewRPC(); //init and construct initMethod?.Invoke(entity); + + //refresh with data set after init method + stateSerializer.RefreshNewRPC(newRPCPacket); + ConstructEntity(entity); //create OnConstructed rpc stateSerializer.MakeConstructedRPC(null); _changedEntities.Add(entity); - _maxDataSize += stateSerializer.GetMaximumSize(); + _maxDataSize += stateSerializer.MaximumSize; //Debug.Log($"[SEM] Entity create. clsId: {classData.ClassId}, id: {entityId}, v: {version}"); return entity; @@ -533,6 +548,8 @@ protected override unsafe void OnLogicTick() _minimalTick = _tick; bool resizeCompressionBuffer = false; int playersCount = _netPlayers.Count; + + //apply pending incoming inputs for (int pidx = 0; pidx < playersCount; pidx++) { var player = _netPlayers.GetByIndex(pidx); @@ -565,6 +582,7 @@ protected override unsafe void OnLogicTick() } } + //update entities if (SafeEntityUpdate) { foreach (var aliveEntity in AliveEntities) @@ -579,11 +597,12 @@ protected override unsafe void OnLogicTick() } ExecuteLateConstruct(); + ExecuteLocalSingletonsLateUpdate(); //refresh construct rpc with latest updates to entity at creation tick foreach (var rpcNode in _pendingRPCs) { - if (rpcNode.Header.Id == RemoteCallPacket.ConstructRPCId && rpcNode.Header.Tick == _tick) + if (rpcNode.Header.Id == (ushort)InternalRPCType.Construct && rpcNode.Header.Tick == _tick) _stateSerializers[rpcNode.Header.EntityId].RefreshConstructedRPC(rpcNode); } @@ -595,6 +614,8 @@ protected override unsafe void OnLogicTick() //================================================================== if (playersCount == 0 || _tick % (int) SendRate != 0) return; + + var currentTimeUtc = DateTime.UtcNow; //remove old rpcs while (_pendingRPCs.TryPeek(out var rpcNode) && Utils.SequenceDiff(rpcNode.Header.Tick, _minimalTick) < 0) @@ -623,6 +644,8 @@ protected override unsafe void OnLogicTick() _syncForPlayer = null; int rpcSize = 0; RPCHeader prevRpcHeader = new(); + + //if player need baseline state - send it reliably if (player.State == NetPlayerState.RequestBaseline) { int originalLength = 0; @@ -635,7 +658,8 @@ protected override unsafe void OnLogicTick() { if (_stateSerializers[e.Id].ShouldSync(player.Id, false)) { - _stateSerializers[e.Id].MakeNewRPC(); + var newRPC = _stateSerializers[e.Id].MakeNewRPC(); + _stateSerializers[e.Id].RefreshNewRPC(newRPC); _stateSerializers[e.Id].MakeConstructedRPC(player); } } @@ -654,7 +678,7 @@ protected override unsafe void OnLogicTick() else //like baseline but only queued rpcs and diff data { foreach (var rpcNode in _pendingRPCs) - if(ShouldSendRPC(rpcNode, player)) + if(ShouldSendRPC(rpcNode, player, true)) rpcNode.WriteTo(packetBuffer, ref originalLength, ref prevRpcHeader); rpcSize = originalLength; @@ -688,15 +712,25 @@ protected override unsafe void OnLogicTick() player.StateBTick = _tick; player.CurrentServerTick = _tick; player.State = NetPlayerState.WaitingForFirstInput; + player.ServerTickChangedTime = DateTime.UtcNow; Logger.Log($"[SEM] SendWorld to player {player.Id}. orig: {originalLength} b, compressed: {encodedLength} b, ExecutedTick: {_tick}"); continue; } + if (player.State != NetPlayerState.Active) { //waiting to load initial state continue; } + //check if player cannot receive partial state due to high packet loss and large state size + if ((currentTimeUtc - player.ServerTickChangedTime).TotalSeconds > PlayerResyncTimeout) + { + Logger.Log($"P:{player.Id} Request baseline {_tick} because timeout"); + player.State = NetPlayerState.RequestBaseline; + continue; + } + //Partial diff sync var header = (DiffPartHeader*)packetBuffer; header->UserHeader = HeaderByte; @@ -708,7 +742,7 @@ protected override unsafe void OnLogicTick() foreach (var rpcNode in _pendingRPCs) { - if(!ShouldSendRPC(rpcNode, player)) + if(!ShouldSendRPC(rpcNode, player, false)) continue; //use another approach to sum size because writePosition can be edited by CheckOverflow @@ -746,7 +780,7 @@ protected override unsafe void OnLogicTick() //Logger.Log($"Removed highest order entity: {e.UpdateOrderNum}, new highest: {_nextOrderNum}"); } _entityIdQueue.ReuseId(entity.Id); - _maxDataSize -= stateSerializer.GetMaximumSize(); + _maxDataSize -= stateSerializer.MaximumSize; stateSerializer.Free(); //Logger.Log($"[SRV] RemoveEntity: {e.Id}"); RemoveEntity(entity); @@ -796,7 +830,7 @@ void CheckOverflowAndSend(NetPlayer player, DiffPartHeader *header, byte* packet { if (header->Part == MaxParts-1) { - Logger.Log($"P:{player.Id} Request baseline {_tick}"); + Logger.Log($"P:{player.Id} Request baseline {_tick} because state size"); player.State = NetPlayerState.RequestBaseline; break; } @@ -812,7 +846,7 @@ void CheckOverflowAndSend(NetPlayer player, DiffPartHeader *header, byte* packet } } - bool ShouldSendRPC(RemoteCallPacket rpcNode, NetPlayer player) + bool ShouldSendRPC(RemoteCallPacket rpcNode, NetPlayer player, bool isBaseline) { //SyncForPlayer calls? if (rpcNode.OnlyForPlayer != null && rpcNode.OnlyForPlayer != player) @@ -851,15 +885,18 @@ bool ShouldSendRPC(RemoteCallPacket rpcNode, NetPlayer player) } } - switch (rpcNode.Header.Id) + switch ((InternalRPCType)rpcNode.Header.Id) { - case RemoteCallPacket.NewOwnedRPCId when entity.InternalOwnerId.Value != player.Id: - rpcNode.Header.Id = RemoteCallPacket.NewRPCId; + case InternalRPCType.NewOwned when entity.InternalOwnerId.Value != player.Id || isBaseline: + rpcNode.Header.Id = (ushort)InternalRPCType.New; break; - case RemoteCallPacket.NewRPCId when entity.InternalOwnerId.Value == player.Id: - rpcNode.Header.Id = RemoteCallPacket.NewOwnedRPCId; + + //do this change only for diff states for best local->remote entity swap + case InternalRPCType.New when entity.InternalOwnerId.Value == player.Id && !isBaseline: + rpcNode.Header.Id = (ushort)InternalRPCType.NewOwned; break; - case RemoteCallPacket.ConstructRPCId: + + case InternalRPCType.Construct: stateSerializer.RefreshSyncGroupsVariable(player, new Span(rpcNode.Data)); break; } @@ -885,7 +922,7 @@ internal override unsafe void EntityFieldChanged(InternalEntity entity, ushor if (!skipOnSync && (fieldInfo.OnSyncFlags & BindOnChangeFlags.ExecuteOnServer) != 0) { T value = oldValue; - entity.ClassData.CallOnSync(entity, ref fieldInfo, new ReadOnlySpan(&value, fieldInfo.IntSize)); + fieldInfo.OnSync(fieldInfo.GetTargetObject(entity), new ReadOnlySpan(&value, fieldInfo.IntSize)); } } @@ -895,34 +932,41 @@ internal void MarkFieldsChanged(InternalEntity entity, SyncFlags onlyWithFlags) _stateSerializers[entity.Id].MarkFieldsChanged(_minimalTick, _tick, onlyWithFlags); } - internal void AddRemoteCall(InternalEntity entity, ushort rpcId, ExecuteFlags flags) + internal RemoteCallPacket AddRemoteCall(InternalEntity entity, ushort rpcId, ExecuteFlags flags) { if (PlayersCount == 0 || entity.IsRemoved || entity is AiControllerLogic || (flags & ExecuteFlags.SendToAll) == 0) - return; + return null; var rpc = _rpcPool.Count > 0 ? _rpcPool.Dequeue() : new RemoteCallPacket(); rpc.Init(_syncForPlayer, entity, _tick, 0, rpcId, flags); _pendingRPCs.Enqueue(rpc); _maxDataSize += rpc.TotalSize; + return rpc; + } + + internal void NotifyRPCResized(int prevTotalSize, int newTotalSize) + { + _maxDataSize -= prevTotalSize; + _maxDataSize += newTotalSize; } - internal unsafe void AddRemoteCall(InternalEntity entity, ReadOnlySpan value, ushort rpcId, ExecuteFlags flags) where T : unmanaged + internal unsafe RemoteCallPacket AddRemoteCall(InternalEntity entity, ReadOnlySpan value, ushort rpcId, ExecuteFlags flags) where T : unmanaged { if (PlayersCount == 0 || entity.IsRemoved || entity is AiControllerLogic || (flags & ExecuteFlags.SendToAll) == 0) - return; + return null; var rpc = _rpcPool.Count > 0 ? _rpcPool.Dequeue() : new RemoteCallPacket(); int dataSize = sizeof(T) * value.Length; if (dataSize > ushort.MaxValue) { Logger.LogError($"DataSize on rpc: {rpcId}, entity: {entity} is more than {ushort.MaxValue}"); - return; + return null; } rpc.Init(_syncForPlayer, entity, _tick, (ushort)dataSize, rpcId, flags); @@ -931,6 +975,7 @@ entity is AiControllerLogic || RefMagic.CopyBlock(rawData, rawValue, (uint)dataSize); _pendingRPCs.Enqueue(rpc); _maxDataSize += rpc.TotalSize; + return rpc; } } } diff --git a/Assets/Plugins/LiteEntitySystem/SyncChilds.cs b/Assets/Plugins/LiteEntitySystem/SyncChilds.cs index a0ccc06..5345f94 100644 --- a/Assets/Plugins/LiteEntitySystem/SyncChilds.cs +++ b/Assets/Plugins/LiteEntitySystem/SyncChilds.cs @@ -37,17 +37,18 @@ protected internal override void RegisterRPC(ref SyncableRPCRegistrator r) protected internal override void OnRollback() { - if (_data == null || _serverData == null) + if (_data == null) return; _data.Clear(); + //Logger.Log($"ClearChilds: {ParentEntity.EntityManager.UpdateMode}"); + if (_serverData == null) + return; foreach (var x in _serverData) _data.Add(x); } protected internal override void BeforeReadRPC() { - if (_data == null) - return; _serverData ??= new HashSet(SharedReferenceComparer); _tempData = _data; _data = _serverData; @@ -55,9 +56,16 @@ protected internal override void BeforeReadRPC() protected internal override void AfterReadRPC() { - if (_data == null || _tempData == null) - return; _data = _tempData; + _tempData = null; + if (_serverData == null) + return; + if (_data == null) + { + if (_serverData.Count == 0) + return; + _data = new HashSet(SharedReferenceComparer); + } _data.Clear(); foreach (var kv in _serverData) _data.Add(kv); @@ -110,6 +118,9 @@ internal void Add(EntitySharedReference x) _data ??= new HashSet(SharedReferenceComparer); _data.Add(x); ExecuteRPC(_addAction, x); + MarkAsChanged(); + //if(IsClient) + // Logger.Log($"AddChild: {x} {ParentEntity.EntityManager.UpdateMode} at tick: {ParentEntity.ClientManager.Tick}"); } internal void Clear() @@ -118,6 +129,7 @@ internal void Clear() return; _data.Clear(); ExecuteRPC(_clearAction); + MarkAsChanged(); } public bool Contains(EntitySharedReference x) => _data != null && _data.Contains(x); @@ -129,6 +141,7 @@ internal bool Remove(EntitySharedReference key) if (_data == null || !_data.Remove(key)) return false; ExecuteRPC(_removeAction, key); + MarkAsChanged(); return true; } diff --git a/Assets/Plugins/LiteEntitySystem/SyncableField.cs b/Assets/Plugins/LiteEntitySystem/SyncableField.cs index 6c32c6a..93944b2 100644 --- a/Assets/Plugins/LiteEntitySystem/SyncableField.cs +++ b/Assets/Plugins/LiteEntitySystem/SyncableField.cs @@ -49,6 +49,10 @@ public abstract class SyncableField : InternalBaseClass internal void Init(InternalEntity parentEntity, SyncFlags fieldFlags) { + //already initialized + if (_parentEntity != null) + return; + _parentEntity = parentEntity; if (fieldFlags.HasFlagFast(SyncFlags.OnlyForOwner)) _executeFlags = ExecuteFlags.SendToOwner; diff --git a/Assets/Plugins/LiteEntitySystem/Utils.cs b/Assets/Plugins/LiteEntitySystem/Utils.cs index b492a61..e436e39 100644 --- a/Assets/Plugins/LiteEntitySystem/Utils.cs +++ b/Assets/Plugins/LiteEntitySystem/Utils.cs @@ -153,6 +153,26 @@ public static unsafe bool FastEquals(ref T a, byte *x2) where T : unmanaged } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe bool FastEquals(byte *x1, byte *x2, int l) + { + for (int i=0; i < l/8; i++, x1+=8, x2+=8) + if (*(long*)x1 != *(long*)x2) return false; + if ((l & 4)!=0) { if (*(int*)x1!=*(int*)x2) return false; x1+=4; x2+=4; } + if ((l & 2)!=0) { if (*(short*)x1!=*(short*)x2) return false; x1+=2; x2+=2; } + return (l & 1) == 0 || *x1 == *x2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe bool IsZero(byte *x1, int l) + { + for (int i=0; i < l/8; i++, x1+=8) + if (*(long*)x1 != 0) return false; + if ((l & 4)!=0) { if (*(int*)x1!=0) return false; x1+=4; } + if ((l & 2)!=0) { if (*(short*)x1!=0) return false; x1+=2; } + return (l & 1) == 0 || *x1 == 0; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ResizeIfFull(ref T[] arr, int count) { diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index b852362..587f809 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2022.3.62f2 -m_EditorVersionWithRevision: 2022.3.62f2 (7670c08855a9) +m_EditorVersion: 2022.3.62f3 +m_EditorVersionWithRevision: 2022.3.62f3 (96770f904ca7) From 334c9045b94f6f1c8d490440c2c0e12a4ea401d8 Mon Sep 17 00:00:00 2001 From: RevenantX Date: Mon, 2 Feb 2026 13:55:00 +0200 Subject: [PATCH 22/22] update LES and LiteNetLib to 2.0.0 --- Assets/Plugins/LiteEntitySystem/PawnLogic.cs | 3 +++ .../LiteEntitySystem/ServerEntityManager.cs | 10 ++++++-- .../Transport/LiteNetLibNetPeer.cs | 10 ++++---- Assets/Scripts/Client/ClientLogic.cs | 22 ++++++++--------- Assets/Scripts/Server/ServerLogic.cs | 24 +++++++++---------- Packages/manifest.json | 2 +- Packages/packages-lock.json | 2 +- 7 files changed, 41 insertions(+), 32 deletions(-) diff --git a/Assets/Plugins/LiteEntitySystem/PawnLogic.cs b/Assets/Plugins/LiteEntitySystem/PawnLogic.cs index d2a5648..dd492ea 100644 --- a/Assets/Plugins/LiteEntitySystem/PawnLogic.cs +++ b/Assets/Plugins/LiteEntitySystem/PawnLogic.cs @@ -14,6 +14,9 @@ public ControllerLogic Controller get => EntityManager.GetEntityById(_controller); internal set { + if(IsDestroyed) + return; + byte ownerId = EntityManager.ServerPlayerId; if (value != null) { diff --git a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs index f09a549..85dbdfd 100644 --- a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs +++ b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs @@ -519,7 +519,8 @@ private T Add(Action initMethod) where T : InternalEntity internal override void OnEntityDestroyed(InternalEntity e) { //sync all disabled data before destroy - if (e is EntityLogic el) + /* + if (_netPlayers.Count > 0 && e is EntityLogic el) { for(int i = 0; i < _netPlayers.Count; i++) { @@ -527,9 +528,14 @@ internal override void OnEntityDestroyed(InternalEntity e) MarkFieldsChanged(e, SyncGroupUtils.ToSyncFlags(~syncGroupData.EnabledGroups)); } } + */ - _stateSerializers[e.Id].MakeDestroyedRPC(_tick); base.OnEntityDestroyed(e); + + if(_netPlayers.Count == 0) + RemoveEntity(e); + else + _stateSerializers[e.Id].MakeDestroyedRPC(_tick); } protected override unsafe void OnLogicTick() diff --git a/Assets/Plugins/LiteEntitySystem/Transport/LiteNetLibNetPeer.cs b/Assets/Plugins/LiteEntitySystem/Transport/LiteNetLibNetPeer.cs index 60217cb..8569165 100644 --- a/Assets/Plugins/LiteEntitySystem/Transport/LiteNetLibNetPeer.cs +++ b/Assets/Plugins/LiteEntitySystem/Transport/LiteNetLibNetPeer.cs @@ -5,9 +5,9 @@ namespace LiteEntitySystem.Transport { public class LiteNetLibNetPeer : AbstractNetPeer { - public readonly NetPeer NetPeer; + public readonly LiteNetPeer NetPeer; - public LiteNetLibNetPeer(NetPeer netPeer, bool assignToTag) + public LiteNetLibNetPeer(LiteNetPeer netPeer, bool assignToTag) { NetPeer = netPeer; if(assignToTag) @@ -15,15 +15,15 @@ public LiteNetLibNetPeer(NetPeer netPeer, bool assignToTag) } public override void TriggerSend() => NetPeer.NetManager.TriggerUpdate(); - public override void SendReliableOrdered(ReadOnlySpan data) => NetPeer.Send(data, 0, DeliveryMethod.ReliableOrdered); - public override void SendUnreliable(ReadOnlySpan data) => NetPeer.Send(data, 0, DeliveryMethod.Unreliable); + public override void SendReliableOrdered(ReadOnlySpan data) => NetPeer.Send(data, DeliveryMethod.ReliableOrdered); + public override void SendUnreliable(ReadOnlySpan data) => NetPeer.Send(data, DeliveryMethod.Unreliable); public override int GetMaxUnreliablePacketSize() => NetPeer.GetMaxSinglePacketSize(DeliveryMethod.Unreliable); public override string ToString() => NetPeer.ToString(); } public static class LiteNetLibExtensions { - public static LiteNetLibNetPeer GetLiteNetLibNetPeerFromTag(this NetPeer peer) => (LiteNetLibNetPeer)peer.Tag; + public static LiteNetLibNetPeer GetLiteNetLibNetPeerFromTag(this LiteNetPeer peer) => (LiteNetLibNetPeer)peer.Tag; public static LiteNetLibNetPeer GetLiteNetLibNetPeer(this NetPlayer player) => (LiteNetLibNetPeer)player.Peer; } } \ No newline at end of file diff --git a/Assets/Scripts/Client/ClientLogic.cs b/Assets/Scripts/Client/ClientLogic.cs index b247d21..7268822 100644 --- a/Assets/Scripts/Client/ClientLogic.cs +++ b/Assets/Scripts/Client/ClientLogic.cs @@ -12,7 +12,7 @@ namespace Code.Client { - public class ClientLogic : MonoBehaviour, INetEventListener + public class ClientLogic : MonoBehaviour, ILiteNetEventListener { [SerializeField] private ClientPlayerView _clientPlayerViewPrefab; [SerializeField] private RemotePlayerView _remotePlayerViewPrefab; @@ -24,12 +24,12 @@ public class ClientLogic : MonoBehaviour, INetEventListener private GamePool _shootsPool; private GamePool _hitsPool; - private NetManager _netManager; + private LiteNetManager _netManager; private NetDataWriter _writer; private NetPacketProcessor _packetProcessor; private string _userName; - private NetPeer _server; + private LiteNetPeer _server; private ClientEntityManager _entityManager; private int _ping; @@ -81,7 +81,7 @@ private void Awake() _shootsPool = new GamePool(ShootEffectConstructor, 200); _hitsPool = new GamePool(HitEffectConstructor, 200); _packetProcessor = new NetPacketProcessor(); - _netManager = new NetManager(this) + _netManager = new LiteNetManager(this) { AutoRecycle = true, EnableStatistics = true, @@ -158,7 +158,7 @@ public void SpawnHit(Vector2 from) _server.Send(_writer, deliveryMethod); } - void INetEventListener.OnPeerConnected(NetPeer peer) + void ILiteNetEventListener.OnPeerConnected(LiteNetPeer peer) { Debug.Log("[C] Connected to server: " + peer); _server = peer; @@ -192,7 +192,7 @@ void INetEventListener.OnPeerConnected(NetPeer peer) }, true); } - void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) + void ILiteNetEventListener.OnPeerDisconnected(LiteNetPeer peer, DisconnectInfo disconnectInfo) { _server = null; _entityManager = null; @@ -204,12 +204,12 @@ void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnec } } - void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketError) + void ILiteNetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketError) { Debug.Log("[C] NetworkError: " + socketError); } - void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod) + void ILiteNetEventListener.OnNetworkReceive(LiteNetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) { byte packetType = reader.PeekByte(); var pt = (PacketType) packetType; @@ -230,18 +230,18 @@ void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, by } } - void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, + void ILiteNetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) { } - void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency) + void ILiteNetEventListener.OnNetworkLatencyUpdate(LiteNetPeer peer, int latency) { _ping = latency; } - void INetEventListener.OnConnectionRequest(ConnectionRequest request) + void ILiteNetEventListener.OnConnectionRequest(ConnectionRequest request) { request.Reject(); } diff --git a/Assets/Scripts/Server/ServerLogic.cs b/Assets/Scripts/Server/ServerLogic.cs index e393898..ae8216c 100644 --- a/Assets/Scripts/Server/ServerLogic.cs +++ b/Assets/Scripts/Server/ServerLogic.cs @@ -11,9 +11,9 @@ namespace Code.Server { - public class ServerLogic : MonoBehaviour, INetEventListener + public class ServerLogic : MonoBehaviour, ILiteNetEventListener { - private NetManager _netManager; + private LiteNetManager _netManager; private NetPacketProcessor _packetProcessor; public ushort Tick => _serverEntityManager.Tick; private ServerEntityManager _serverEntityManager; @@ -27,7 +27,7 @@ static ServerLogic() private void Awake() { EntityManager.RegisterFieldType(Vector2.Lerp); - _netManager = new NetManager(this) + _netManager = new LiteNetManager(this) { AutoRecycle = true, PacketPoolSize = 1000, @@ -43,7 +43,7 @@ private void Awake() #endif _packetProcessor = new NetPacketProcessor(); - _packetProcessor.SubscribeReusable(OnJoinReceived); + _packetProcessor.SubscribeReusable(OnJoinReceived); var typesMap = new EntityTypesMap() .Register(GameEntities.Player, e => new BasePlayer(e)) @@ -89,7 +89,7 @@ private void Update() _serverEntityManager?.Update(); } - private void OnJoinReceived(JoinPacket joinPacket, NetPeer peer) + private void OnJoinReceived(JoinPacket joinPacket, LiteNetPeer peer) { Debug.Log("[S] Join packet received: " + joinPacket.UserName); @@ -109,12 +109,12 @@ private void OnJoinReceived(JoinPacket joinPacket, NetPeer peer) _serverEntityManager.AddController(serverPlayer, player); } - void INetEventListener.OnPeerConnected(NetPeer peer) + void ILiteNetEventListener.OnPeerConnected(LiteNetPeer peer) { Debug.Log("[S] Player connected: " + peer); } - void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) + void ILiteNetEventListener.OnPeerDisconnected(LiteNetPeer peer, DisconnectInfo disconnectInfo) { Debug.Log("[S] Player disconnected: " + disconnectInfo.Reason); @@ -124,12 +124,12 @@ void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnec } } - void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketError) + void ILiteNetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketError) { Debug.Log("[S] NetworkError: " + socketError); } - void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod) + void ILiteNetEventListener.OnNetworkReceive(LiteNetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) { byte packetType = reader.PeekByte(); switch ((PacketType)packetType) @@ -149,18 +149,18 @@ void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, by } } - void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, + void ILiteNetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) { } - void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency) + void ILiteNetEventListener.OnNetworkLatencyUpdate(LiteNetPeer peer, int latency) { } - void INetEventListener.OnConnectionRequest(ConnectionRequest request) + void ILiteNetEventListener.OnConnectionRequest(ConnectionRequest request) { request.AcceptIfKey("ExampleGame"); } diff --git a/Packages/manifest.json b/Packages/manifest.json index 9c8cb98..0cbb338 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,6 +1,6 @@ { "dependencies": { - "com.revenantx.litenetlib": "1.3.3", + "com.revenantx.litenetlib": "2.0.0", "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", "com.unity.ext.nunit": "1.0.6", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index fa97225..dba2226 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -1,7 +1,7 @@ { "dependencies": { "com.revenantx.litenetlib": { - "version": "1.3.3", + "version": "2.0.0", "depth": 0, "source": "registry", "dependencies": {},