From a2ac7164ccf58e44ff48bfbda2014f3b3751b752 Mon Sep 17 00:00:00 2001 From: CoreAidan Date: Sun, 11 May 2025 09:04:22 -0400 Subject: [PATCH 1/3] added registeration for nested SyncableFields --- LiteEntitySystem/ClientEntityManager.cs | 240 +++++++-------- LiteEntitySystem/EntityManager.cs | 2 +- LiteEntitySystem/Internal/EntityClassData.cs | 273 ++++++++++++------ LiteEntitySystem/Internal/EntityFieldInfo.cs | 61 ++-- LiteEntitySystem/Internal/InternalEntity.cs | 116 +++++--- LiteEntitySystem/Internal/ServerStateData.cs | 19 +- LiteEntitySystem/Internal/StateSerializer.cs | 12 +- .../Internal/ValueTypeProcessor.cs | 232 ++++++++++++--- LiteEntitySystem/RPCRegistrator.cs | 14 +- LiteEntitySystem/Utils.cs | 27 ++ 10 files changed, 654 insertions(+), 342 deletions(-) diff --git a/LiteEntitySystem/ClientEntityManager.cs b/LiteEntitySystem/ClientEntityManager.cs index 3e832ef..fe16dc4 100644 --- a/LiteEntitySystem/ClientEntityManager.cs +++ b/LiteEntitySystem/ClientEntityManager.cs @@ -18,7 +18,7 @@ public sealed class ClientEntityManager : EntityManager /// Current interpolated server tick /// public ushort ServerTick { get; private set; } - + /// /// Current rollback tick (valid only in Rollback state) /// @@ -48,12 +48,12 @@ public sealed class ClientEntityManager : EntityManager /// Our local player /// public NetPlayer LocalPlayer => _localPlayer; - + /// /// Stored input commands count for prediction correction /// public int StoredCommands => _storedInputHeaders.Count; - + /// /// Player tick processed by server /// @@ -63,7 +63,7 @@ public sealed class ClientEntityManager : EntityManager /// Last received player tick by server /// public ushort LastReceivedTick => _stateA?.LastReceivedTick ?? 0; - + /// /// Inputs count in server input buffer /// @@ -73,7 +73,7 @@ public sealed class ClientEntityManager : EntityManager /// Send rate of server /// public ServerSendRate ServerSendRate => _serverSendRate; - + /// /// States count in interpolation buffer /// @@ -83,7 +83,7 @@ public sealed class ClientEntityManager : EntityManager /// Total states time in interpolation buffer /// public float LerpBufferTimeLength => _readyStates.Count * DeltaTimeF * (int)_serverSendRate; - + /// /// Current state size in bytes /// @@ -108,8 +108,8 @@ 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.01f; + /// /// Preferred input and incoming states buffer length in seconds lowest bound /// Buffer automatically decreases to Jitter time + PreferredBufferTimeHighest @@ -120,10 +120,10 @@ public sealed class ClientEntityManager : EntityManager /// Entities that waiting for remove /// public int PendingToRemoveEntites => _entitiesToRemoveCount; - + private const float TimeSpeedChangeFadeTime = 0.1f; private const float MaxJitter = 0.2f; - + /// /// Maximum stored inputs count /// @@ -131,12 +131,12 @@ public sealed class ClientEntityManager : EntityManager //predicted entities that should use rollback private readonly AVLTree _predictedEntities = new(); - + private readonly AbstractNetPeer _netPeer; 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<(ushort tick, EntityLogic entity)> _spawnPredictedEntities = new(); private readonly byte[] _sendBuffer = new byte[NetConstants.MaxPacketSize]; private readonly HashSet _changedEntities = new(); private readonly CircularBuffer _storedInputHeaders = new(InputBufferSize); @@ -148,13 +148,13 @@ public sealed class ClientEntityManager : EntityManager private ServerStateData _stateB; private float _lerpTime; private double _timer; - + private readonly IdGeneratorUShort _localIdQueue = new(MaxSyncedEntityCount, MaxEntityCount); private readonly struct SyncCallInfo { public readonly InternalEntity Entity; - + private readonly MethodCallDelegate _onSync; private readonly int _prevDataPos; @@ -165,11 +165,11 @@ public SyncCallInfo(MethodCallDelegate onSync, InternalEntity entity, int prevDa _prevDataPos = prevDataPos; } - public void Execute(ServerStateData state) => _onSync(Entity, new ReadOnlySpan(state.Data, _prevDataPos, state.Size-_prevDataPos)); + public void Execute(ServerStateData state) => _onSync(Entity, new ReadOnlySpan(state.Data, _prevDataPos, state.Size - _prevDataPos)); } private SyncCallInfo[] _syncCalls; private int _syncCallsCount; - + private readonly AVLTree _entitiesToConstruct = new(); private ushort _lastReceivedInputTick; private float _logicLerpMsec; @@ -182,7 +182,7 @@ public SyncCallInfo(MethodCallDelegate onSync, InternalEntity entity, int prevDa private float _jitterPrevTime; private float _jitterMiddle; private float _jitterSum; - + //local player private NetPlayer _localPlayer; @@ -209,15 +209,15 @@ public T GetPlayerController() where T : HumanControllerLogic /// Header byte that will be used for packets (to distinguish entity system packets) /// Maximum size of lag compensation history in ticks public ClientEntityManager( - EntityTypesMap typesMap, - AbstractNetPeer netPeer, - byte headerByte, + EntityTypesMap typesMap, + AbstractNetPeer netPeer, + byte headerByte, MaxHistorySize maxHistorySize = MaxHistorySize.Size32) : base(typesMap, NetworkMode.Client, headerByte, maxHistorySize) { _netPeer = netPeer; _sendBuffer[0] = headerByte; _sendBuffer[1] = InternalPackets.ClientInput; - + for (int i = 0; i < MaxSavedStateDiff; i++) _statesPool.Enqueue(new ServerStateData()); } @@ -228,7 +228,7 @@ public override void Reset() _localIdQueue.Reset(); _entitiesToRemoveCount = 0; } - + /// /// Add local entity that will be not synchronized /// @@ -241,23 +241,23 @@ internal T AddLocalEntity(Action initMethod) where T : EntityLogic Logger.LogError("Max local entities count reached"); return null; } - + var entity = AddEntity(new EntityParams( new EntityDataHeader( - _localIdQueue.GetNewId(), - EntityClassInfo.ClassId, - 0, + _localIdQueue.GetNewId(), + EntityClassInfo.ClassId, + 0, 0), this, ClassDataDict[EntityClassInfo.ClassId].AllocateDataCache())); - + //Logger.Log($"AddPredicted, tick: {_tick}, rb: {InRollBackState}, id: {entity.Id}"); - + entity.InternalOwnerId.Value = InternalPlayerId; initMethod(entity); ConstructEntity(entity); _spawnPredictedEntities.Enqueue((_tick, entity)); - + return entity; } @@ -265,7 +265,7 @@ internal EntityLogic FindEntityByPredictedId(ushort tick, ushort parentId, ushor { foreach (var predictedEntity in _spawnPredictedEntities) { - if (predictedEntity.tick == tick && + if (predictedEntity.tick == tick && predictedEntity.entity.ParentId.Id == parentId && predictedEntity.entity.PredictedId == predictedId) return predictedEntity.entity; @@ -287,12 +287,12 @@ protected override unsafe void OnAliveEntityAdded(InternalEntity 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); + field.TypeProcessor.WriteTo(entity, field.Offsets, interpDataPtr + field.FixedOffset); + field.TypeProcessor.WriteTo(entity, field.Offsets, prevDataPtr + field.FixedOffset); } } } - + /// Read incoming data /// Incoming data including header byte /// Deserialization result @@ -306,7 +306,7 @@ public unsafe DeserializeResult Deserialize(ReadOnlySpan inData) { if (inData.Length < sizeof(BaselineDataHeader)) return DeserializeResult.Error; - + _entitiesToConstruct.Clear(); _syncCallsCount = 0; //read header and decode @@ -384,7 +384,7 @@ public unsafe DeserializeResult Deserialize(ReadOnlySpan inData) //sample jitter float currentJitterTimer = _jitterTimer.ElapsedMilliseconds / 1000f; ref float jitterSample = ref _jitterSamples[_jitterSampleIdx]; - + _jitterSum -= jitterSample; jitterSample = Math.Abs(currentJitterTimer - _jitterPrevTime); _jitterSum += jitterSample; @@ -442,12 +442,12 @@ private bool PreloadNextState() return true; if (_readyStates.Count == 0) return false; - + //get max and middle jitter _jitterMiddle = _jitterSum / _jitterSamples.Length; if (_jitterMiddle > NetworkJitter) NetworkJitter = _jitterMiddle; - + _stateB = _readyStates.ExtractMin(); _stateB.Preload(EntitiesDict); //Logger.Log($"Preload A: {_stateA.Tick}, B: {_stateB.Tick}"); @@ -460,17 +460,17 @@ private bool PreloadNextState() //tune buffer playing speed _lerpTime = Utils.SequenceDiff(_stateB.Tick, _stateA.Tick) * DeltaTimeF; - _lerpTime *= 1 - GetSpeedMultiplier(LerpBufferTimeLength)*TimeSpeedChangeCoef; + _lerpTime *= 1 - GetSpeedMultiplier(LerpBufferTimeLength) * TimeSpeedChangeCoef; //tune game prediction and input generation speed SpeedMultiplier = GetSpeedMultiplier(_stateB.BufferedInputsCount * DeltaTimeF); - + //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(); - + return true; float GetSpeedMultiplier(float bufferTime) => @@ -484,21 +484,21 @@ private unsafe void GoToNextState() _statesPool.Enqueue(_stateA); _stateA = _stateB; _stateB = null; - + //Logger.Log($"GotoState: IST: {ServerTick}, TST:{_stateA.Tick}"); fixed (byte* stateData = _stateA.Data) if (ReadEntityState(stateData, false) == false) return; ConstructAndSync(false, minimalTick); - + _timer -= _lerpTime; - + //reset owned entities foreach (var entity in _predictedEntities) { ref var classData = ref ClassDataDict[entity.ClassId]; var rollbackFields = classData.GetRollbackFields(entity.IsLocalControlled); - if(rollbackFields == null || rollbackFields.Length == 0) + if (rollbackFields == null || rollbackFields.Length == 0) continue; entity.OnBeforeRollback(); @@ -509,24 +509,25 @@ private unsafe void GoToNextState() ref var field = ref rollbackFields[i]; if (field.FieldType == FieldType.SyncableSyncVar) { - var syncableField = RefMagic.RefFieldValue(entity, field.Offset); - field.TypeProcessor.SetFrom(syncableField, field.SyncableSyncVarOffset, predictedData + field.PredictedOffset); + field.TypeProcessor.SetFrom(entity, field.Offsets, predictedData + field.PredictedOffset); } else { - field.TypeProcessor.SetFrom(entity, field.Offset, predictedData + field.PredictedOffset); + field.TypeProcessor.SetFrom(entity, field.Offsets, predictedData + field.PredictedOffset); } } } for (int i = 0; i < classData.SyncableFields.Length; i++) - RefMagic.RefFieldValue(entity, classData.SyncableFields[i].Offset).OnRollback(); + { + Utils.GetSyncableField(entity, classData.SyncableFields[i].Offsets).OnRollback(); + } entity.OnRollback(); } //reapply input UpdateMode = UpdateMode.PredictionRollback; - - for(int cmdNum = 0; cmdNum < _storedInputHeaders.Count; cmdNum++) + + for (int cmdNum = 0; cmdNum < _storedInputHeaders.Count; cmdNum++) { //reapply input data var storedInput = _storedInputHeaders[cmdNum]; @@ -536,12 +537,12 @@ private unsafe void GoToNextState() RollBackTick = storedInput.Tick; foreach (var controller in GetEntities()) controller.ReadStoredInput(cmdNum); - + foreach (var entity in AliveEntities) { - if(entity.IsLocal || !entity.IsLocalControlled) + if (entity.IsLocal || !entity.IsLocalControlled) continue; - + //if new entity set previous interp data from data that was before latest rollback update if (cmdNum == _storedInputHeaders.Count - 1 && _entitiesToConstruct.Contains(entity)) { @@ -551,35 +552,35 @@ private unsafe void GoToNextState() for (int i = 0; i < classData.InterpolatedCount; i++) { ref var field = ref classData.Fields[i]; - field.TypeProcessor.WriteTo(entity, field.Offset, prevDataPtr + field.FixedOffset); + field.TypeProcessor.WriteTo(entity, field.Offsets, prevDataPtr + field.FixedOffset); } } } - + entity.Update(); } } UpdateMode = UpdateMode.Normal; - + _entitiesToConstruct.Clear(); - + //update local interpolated position foreach (var entity in AliveEntities) { - if(entity.IsLocal || !entity.IsLocalControlled) + if (entity.IsLocal || !entity.IsLocalControlled) continue; - + ref var classData = ref ClassDataDict[entity.ClassId]; - for(int i = 0; i < classData.InterpolatedCount; i++) + 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); + field.TypeProcessor.WriteTo(entity, field.Offsets, currentDataPtr + field.FixedOffset); } } } - + //delete predicted while (_spawnPredictedEntities.TryPeek(out var info)) { @@ -602,11 +603,11 @@ internal override void OnEntityDestroyed(InternalEntity e) { if (!e.IsLocal) { - if(e.IsLocalControlled && e is EntityLogic eLogic) + if (e.IsLocalControlled && e is EntityLogic eLogic) RemoveOwned(eLogic); Utils.AddToArrayDynamic(ref _entitiesToRemove, ref _entitiesToRemoveCount, e); } - + base.OnEntityDestroyed(e); } @@ -614,7 +615,7 @@ protected override unsafe void OnLogicTick() { if (_stateB != null) { - ServerTick = Utils.LerpSequence(_stateA.Tick, _stateB.Tick, (float)(_timer/_lerpTime)); + ServerTick = Utils.LerpSequence(_stateA.Tick, _stateB.Tick, (float)(_timer / _lerpTime)); _stateB.ExecuteRpcs(this, _stateA.Tick, false); } @@ -645,25 +646,25 @@ protected override unsafe void OnLogicTick() prevDataPtr = classData.ClientInterpolatedPrevData(entity)) { //restore previous - for(int i = 0; i < classData.InterpolatedCount; i++) + for (int i = 0; i < classData.InterpolatedCount; i++) { var field = classData.Fields[i]; - field.TypeProcessor.SetFrom(entity, field.Offset, currentDataPtr + field.FixedOffset); + field.TypeProcessor.SetFrom(entity, field.Offsets, currentDataPtr + field.FixedOffset); } //update entity.Update(); - + //save current RefMagic.CopyBlock(prevDataPtr, currentDataPtr, (uint)classData.InterpolatedFieldsSize); - for(int i = 0; i < classData.InterpolatedCount; i++) + for (int i = 0; i < classData.InterpolatedCount; i++) { var field = classData.Fields[i]; - field.TypeProcessor.WriteTo(entity, field.Offset, currentDataPtr + field.FixedOffset); + field.TypeProcessor.WriteTo(entity, field.Offsets, currentDataPtr + field.FixedOffset); } } } - else if(classData.Flags.HasFlagFast(EntityFlags.UpdateOnClient)) + else if (classData.Flags.HasFlagFast(EntityFlags.UpdateOnClient)) { entity.Update(); } @@ -681,39 +682,39 @@ public override unsafe void Update() //skip update until receive first sync and tickrate if (Tickrate == 0) return; - + //logic update ushort prevTick = _tick; - + base.Update(); - + //send buffered input if (_tick != prevTick) SendBufferedInput(); - + if (PreloadNextState()) { _timer += VisualDeltaTime; - while(_timer >= _lerpTime) + while (_timer >= _lerpTime) { GoToNextState(); if (!PreloadNextState()) break; } - + if (_stateB != null) { //remote interpolation - _logicLerpMsec = (float)(_timer/_lerpTime); - for(int i = 0; i < _stateB.InterpolatedCachesCount; i++) + _logicLerpMsec = (float)(_timer / _lerpTime); + for (int i = 0; i < _stateB.InterpolatedCachesCount; i++) { ref var interpolatedCache = ref _stateB.InterpolatedCaches[i]; fixed (byte* initialDataPtr = interpolatedCache.Entity.ClassData.ClientInterpolatedNextData(interpolatedCache.Entity), nextDataPtr = _stateB.Data) interpolatedCache.TypeProcessor.SetInterpolation( - interpolatedCache.Entity, - interpolatedCache.FieldOffset, + interpolatedCache.Entity, + interpolatedCache.FieldOffsets, initialDataPtr + interpolatedCache.FieldFixedOffset, - nextDataPtr + interpolatedCache.StateReaderOffset, + nextDataPtr + interpolatedCache.StateReaderOffset, _logicLerpMsec); } } @@ -725,23 +726,23 @@ public override unsafe void Update() { 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++) + for (int i = 0; i < classData.InterpolatedCount; i++) { var field = classData.Fields[i]; field.TypeProcessor.SetInterpolation( entity, - field.Offset, + field.Offsets, prevDataPtr + field.FixedOffset, currentDataPtr + field.FixedOffset, localLerpT); } } } - + //local only and UpdateOnClient foreach (var entity in AliveEntities) { @@ -758,16 +759,16 @@ private unsafe void SendBufferedInput() *(ushort*)(sendBuffer + 2) = Tick; *(InputPacketHeader*)(sendBuffer + 4) = new InputPacketHeader { - LerpMsec = _logicLerpMsec, - StateA = _stateA.Tick, + LerpMsec = _logicLerpMsec, + StateA = _stateA.Tick, StateB = RawTargetServerTick }; - _netPeer.SendUnreliable(new ReadOnlySpan(sendBuffer, 4+InputPacketHeader.Size)); + _netPeer.SendUnreliable(new ReadOnlySpan(sendBuffer, 4 + InputPacketHeader.Size)); _netPeer.TriggerSend(); } return; } - + //pack tick first int offset = 4; int maxSinglePacketSize = _netPeer.GetMaxUnreliablePacketSize(); @@ -788,18 +789,18 @@ private unsafe void SendBufferedInput() Logger.LogError($"Input data from controllers is more than MTU: {maxSinglePacketSize}"); return; } - + fixed (byte* sendBuffer = _sendBuffer) { //Logger.Log($"SendingCommands start {_tick}"); - for(int i = 0; i < _storedInputHeaders.Count; i++) + for (int i = 0; i < _storedInputHeaders.Count; i++) { if (Utils.SequenceDiff(currentTick, _lastReceivedInputTick) <= 0) { currentTick++; continue; } - if(prevInputIndex >= 0)//make delta + if (prevInputIndex >= 0)//make delta { //overflow if (offset + InputPacketHeader.Size + maxDeltaSize > maxSinglePacketSize) @@ -816,7 +817,7 @@ private unsafe void SendBufferedInput() //write header *(InputPacketHeader*)(sendBuffer + offset) = _storedInputHeaders[i].Header; offset += InputPacketHeader.Size; - + //write current into temporary buffer for delta encoding foreach (var controller in GetEntities()) { @@ -856,7 +857,7 @@ internal void AddOwned(EntityLogic entity) if (flags.HasFlagFast(EntityFlags.Updateable) && !flags.HasFlagFast(EntityFlags.UpdateOnClient)) AliveEntities.Add(entity); } - + internal void RemoveOwned(EntityLogic entity) { var flags = entity.ClassData.Flags; @@ -884,29 +885,29 @@ private void ConstructAndSync(bool firstSync, ushort minimalTick = 0) { //execute all previous rpcs ServerTick = _stateA.Tick; - + //execute syncable fields first _stateA.ExecuteSyncableRpcs(this, minimalTick, firstSync); - + //Call construct methods - foreach(var entity in _entitiesToConstruct) + foreach (var entity in _entitiesToConstruct) ConstructEntity(entity); - + //Make OnChangeCalls after construct ExecuteSyncCalls(_syncCalls, ref _syncCallsCount); - + //execute entity rpcs _stateA.ExecuteRpcs(this, minimalTick, firstSync); - + foreach (var lagCompensatedEntity in LagCompensatedEntities) ClassDataDict[lagCompensatedEntity.ClassId].WriteHistory(lagCompensatedEntity, ServerTick); } - + private unsafe bool ReadEntityState(byte* rawData, bool fistSync) { var emptyClassData = new EntityClassData(); _changedEntities.Clear(); - + for (int readerPosition = 0; readerPosition < _stateA.Size;) { bool fullSync = true; @@ -914,7 +915,7 @@ private unsafe bool ReadEntityState(byte* rawData, bool fistSync) InternalEntity entity; ref var classData = ref emptyClassData; bool writeInterpolationData; - + if (!fistSync) //diff data { ushort fullSyncAndTotalSize = *(ushort*)(rawData + readerPosition); @@ -922,7 +923,7 @@ private unsafe bool ReadEntityState(byte* rawData, bool fistSync) endPos = readerPosition + (fullSyncAndTotalSize >> 1); readerPosition += sizeof(ushort); } - + if (fullSync) { var entityDataHeader = *(EntityDataHeader*)(rawData + readerPosition); @@ -930,7 +931,7 @@ private unsafe bool ReadEntityState(byte* rawData, bool fistSync) if (!IsEntityIdValid(entityDataHeader.Id)) return false; entity = EntitiesDict[entityDataHeader.Id]; - + //Logger.Log($"[CEM] ReadBaseline Entity: {entityId} pos: {bytesRead}"); //remove old entity if (entity != null && entity.Version != entityDataHeader.Version) @@ -940,12 +941,12 @@ private unsafe bool ReadEntityState(byte* rawData, bool fistSync) entity.DestroyInternal(); RemoveEntity(entity); entity = null; - } + } if (entity == null) //create new { classData = ref ClassDataDict[entityDataHeader.ClassId]; entity = AddEntity(new EntityParams(entityDataHeader, this, classData.AllocateDataCache())); - + if (classData.PredictedSize > 0 || classData.SyncableFields.Length > 0) { _predictedEntities.Add(entity); @@ -967,7 +968,7 @@ private unsafe bool ReadEntityState(byte* rawData, bool fistSync) if (!IsEntityIdValid(entityId)) return false; entity = EntitiesDict[entityId]; - if(entity != null) + if (entity != null) { classData = ref entity.ClassData; writeInterpolationData = entity.IsRemoteControlled; @@ -981,9 +982,9 @@ private unsafe bool ReadEntityState(byte* rawData, bool fistSync) } _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)) for (int i = 0; i < classData.FieldsCount; i++) @@ -996,8 +997,7 @@ private unsafe bool ReadEntityState(byte* rawData, bool fistSync) RefMagic.CopyBlock(predictedData + field.PredictedOffset, readDataPtr, field.Size); if (field.FieldType == FieldType.SyncableSyncVar) { - var syncableField = RefMagic.RefFieldValue(entity, field.Offset); - field.TypeProcessor.SetFrom(syncableField, field.SyncableSyncVarOffset, readDataPtr); + field.TypeProcessor.SetFrom(entity, field.Offsets, readDataPtr); } else { @@ -1008,18 +1008,18 @@ private unsafe bool ReadEntityState(byte* rawData, bool fistSync) } if (field.OnSync != null) { - if (field.TypeProcessor.SetFromAndSync(entity, field.Offset, readDataPtr)) + if (field.TypeProcessor.SetFromAndSync(entity, field.Offsets, readDataPtr)) _syncCalls[_syncCallsCount++] = new SyncCallInfo(field.OnSync, entity, readerPosition); } else { - field.TypeProcessor.SetFrom(entity, field.Offset, readDataPtr); + field.TypeProcessor.SetFrom(entity, field.Offsets, readDataPtr); } } //Logger.Log($"E {entity.Id} Field updated: {field.Name}"); readerPosition += field.IntSize; } - + if (fullSync) { _stateA.ReadRPCs(rawData, ref readerPosition, new EntitySharedReference(entity.Id, entity.Version), ref classData); @@ -1027,8 +1027,8 @@ private unsafe bool ReadEntityState(byte* rawData, bool fistSync) } readerPosition = endPos; } - - for(int i = 0; i < _entitiesToRemoveCount; i++) + + for (int i = 0; i < _entitiesToRemoveCount; i++) { //skip changed var entityToRemove = _entitiesToRemove[i]; @@ -1036,10 +1036,10 @@ private unsafe bool ReadEntityState(byte* rawData, bool fistSync) continue; _predictedEntities.Remove(entityToRemove); - + //Logger.Log($"[CLI] RemovingEntity: {_entitiesToRemove[i].Id}"); RemoveEntity(entityToRemove); - + _entitiesToRemoveCount--; _entitiesToRemove[i] = _entitiesToRemove[_entitiesToRemoveCount]; _entitiesToRemove[_entitiesToRemoveCount] = null; diff --git a/LiteEntitySystem/EntityManager.cs b/LiteEntitySystem/EntityManager.cs index 0e6f761..acb1057 100644 --- a/LiteEntitySystem/EntityManager.cs +++ b/LiteEntitySystem/EntityManager.cs @@ -299,7 +299,7 @@ public void GetEntitySyncVarInfo(InternalEntity entity, IEntitySyncVarInfoPrinte { ref var classData = ref ClassDataDict[entity.ClassId]; foreach (EntityFieldInfo fi in classData.Fields) - resultPrinter.PrintFieldInfo(fi.Name, fi.TypeProcessor.ToString(entity, fi.Offset)); + resultPrinter.PrintFieldInfo(fi.Name, fi.TypeProcessor.ToString(entity, fi.Offsets)); } /// diff --git a/LiteEntitySystem/Internal/EntityClassData.cs b/LiteEntitySystem/Internal/EntityClassData.cs index 9425a3c..b92b6c6 100644 --- a/LiteEntitySystem/Internal/EntityClassData.cs +++ b/LiteEntitySystem/Internal/EntityClassData.cs @@ -1,37 +1,72 @@ using System; using System.Collections.Generic; using System.Reflection; +using ImGuiGodot.Internal; namespace LiteEntitySystem.Internal { + /// + /// Holds metadata for a SyncableField, including its relationship to a parent container + /// and the relative offset of the field within that container. + /// internal struct SyncableFieldInfo { - public readonly int Offset; + /// + /// For debug: the fully qualified name (e.g. "Entity-MovementComponent"). + /// + public readonly string Name; + + /// + /// Absolute byte offset of the containing object (entity or parent SyncableField) within the root InternalEntity. + /// + // public readonly int ParentOffset; + + /// + /// Byte offset map to this SyncableField within its parent InternalEntity. + /// + public readonly int[] Offsets; + + /// + /// SyncFlags controlling rollback, prediction, interpolation, etc. + /// public readonly SyncFlags Flags; + + /// + /// Offset into the RPC table (initialized later). + /// public ushort RPCOffset; - public SyncableFieldInfo(int offset, SyncFlags executeFlags) + /// + /// Constructs a new SyncableFieldInfo. + /// + /// Debug name of the field. + /// Absolute offset of the parent container. + /// Relative offset within the parent container. + /// SyncFlags for this field. + public SyncableFieldInfo(string name, int[] offsets, SyncFlags flags) { - Offset = offset; - Flags = executeFlags; + Name = name; + Offsets = offsets; + Flags = flags; RPCOffset = ushort.MaxValue; } } - + + internal readonly struct RpcFieldInfo { - public readonly int SyncableOffset; + public readonly int[] SyncableOffsets; public readonly MethodCallDelegate Method; public RpcFieldInfo(MethodCallDelegate method) { - SyncableOffset = -1; + SyncableOffsets = Array.Empty(); Method = method; } - - public RpcFieldInfo(int syncableOffset, MethodCallDelegate method) + + public RpcFieldInfo(int[] syncableOffsets, MethodCallDelegate method) { - SyncableOffset = syncableOffset; + SyncableOffsets = syncableOffsets; Method = method; } } @@ -49,7 +84,7 @@ public BaseTypeInfo(Type type) Id = ushort.MaxValue; } } - + internal struct EntityClassData { public readonly ushort ClassId; @@ -74,33 +109,33 @@ internal struct EntityClassData private readonly EntityFieldInfo[] _ownedRollbackFields; private readonly EntityFieldInfo[] _remoteRollbackFields; - + private static readonly Type InternalEntityType = typeof(InternalEntity); internal static readonly Type SingletonEntityType = typeof(SingletonEntityLogic); private static readonly Type SyncableFieldType = typeof(SyncableField); private static readonly Type EntityLogicType = typeof(EntityLogic); - + private readonly Queue _dataCache; 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 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 EntityFieldInfo[] GetRollbackFields(bool isOwned) => isOwned ? _ownedRollbackFields : _remoteRollbackFields; - + public unsafe void WriteHistory(EntityLogic e, ushort tick) { - int historyOffset = ((tick % _maxHistoryCount)+1)*LagCompensatedSize; + int historyOffset = ((tick % _maxHistoryCount) + 1) * LagCompensatedSize; fixed (byte* history = &e.IOBuffer[_historyStart]) { for (int i = 0; i < LagCompensatedCount; i++) { ref var field = ref LagCompensatedFields[i]; - field.TypeProcessor.WriteTo(e, field.Offset, history + historyOffset); + field.TypeProcessor.WriteTo(e, field.Offsets, history + historyOffset); historyOffset += field.IntSize; } } @@ -108,8 +143,8 @@ public unsafe void WriteHistory(EntityLogic e, ushort tick) public unsafe void LoadHistroy(NetPlayer player, EntityLogic e) { - int historyAOffset = ((player.StateATick % _maxHistoryCount)+1)*LagCompensatedSize; - int historyBOffset = ((player.StateBTick % _maxHistoryCount)+1)*LagCompensatedSize; + int historyAOffset = ((player.StateATick % _maxHistoryCount) + 1) * LagCompensatedSize; + int historyBOffset = ((player.StateBTick % _maxHistoryCount) + 1) * LagCompensatedSize; int historyCurrent = 0; fixed (byte* history = &e.IOBuffer[_historyStart]) { @@ -117,8 +152,8 @@ public unsafe void LoadHistroy(NetPlayer player, EntityLogic e) { ref var field = ref LagCompensatedFields[i]; field.TypeProcessor.LoadHistory( - e, - field.Offset, + e, + field.Offsets, history + historyCurrent, history + historyAOffset, history + historyBOffset, @@ -138,12 +173,12 @@ public unsafe void UndoHistory(EntityLogic e) for (int i = 0; i < LagCompensatedCount; i++) { ref var field = ref LagCompensatedFields[i]; - field.TypeProcessor.SetFrom(e, field.Offset, history + historyOffset); + field.TypeProcessor.SetFrom(e, field.Offsets, history + historyOffset); historyOffset += field.IntSize; } } } - + public byte[] AllocateDataCache() { if (_dataCache.Count > 0) @@ -165,6 +200,16 @@ public void ReleaseDataCache(InternalEntity entity) } } + // PendingField carries both the FieldInfo and its metadata + private struct PendingField + { + public FieldInfo Field; + public string NamePrefix; + public List Offsets; + public SyncVarFlags Flags; + public Type DeclaringType; + } + public EntityClassData(EntityManager entityManager, ushort filterId, Type entType, RegisteredTypeInfo typeInfo) { _dataCache = new Queue(); @@ -184,110 +229,150 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp BaseTypes = new BaseTypeInfo[tempBaseTypes.Count]; for (int i = 0; i < BaseTypes.Length; i++) BaseTypes[i] = new BaseTypeInfo(tempBaseTypes.Pop()); - + var fields = new List(); var syncableFields = new List(); var lagCompensatedFields = new List(); var ownedRollbackFields = new List(); var remoteRollbackFields = new List(); - + + Logger.LogError($"Class Data for : {entType}"); + var allTypesStack = Utils.GetBaseTypes(entType, InternalEntityType, true, true); - while(allTypesStack.Count > 0) + while (allTypesStack.Count > 0) { var baseType = allTypesStack.Pop(); - + var setFlagsAttribute = baseType.GetCustomAttribute(); Flags |= setFlagsAttribute != null ? setFlagsAttribute.Flags : 0; - - //cache fields - foreach (var field in Utils.GetProcessedFields(baseType)) + + // Seed the PendingField stack with top-level fields + var fieldsStack = new Stack(); + foreach (var f in Utils.GetProcessedFields(baseType)) { + var flags = f.GetCustomAttribute() + ?? baseType.GetCustomAttribute() + ?? new SyncVarFlags(SyncFlags.None); + + if (Utils.IsRemoteCallType(f.FieldType) && !f.IsStatic) + throw new Exception($"RemoteCalls should be static! (Class: {entType} Field: {f.Name})"); + if (f.IsStatic) + continue; + + var offset = Utils.GetFieldOffset(f); + + fieldsStack.Push(new PendingField + { + Field = f, + NamePrefix = $"{baseType.Name}-{f.Name}", + Offsets = new List { offset }, + Flags = flags, + DeclaringType = baseType + }); + } + + // Process each pending field (handles nested SyncableFields inline) + while (fieldsStack.Count > 0) + { + var ctx = fieldsStack.Pop(); + var field = ctx.Field; var ft = field.FieldType; - if(Utils.IsRemoteCallType(ft) && !field.IsStatic) + var name = ctx.NamePrefix; + var offsetMap = ctx.Offsets; // chain of offsets to this field + var syncVarFlags = ctx.Flags; + var syncFlags = syncVarFlags.Flags; + + if (Utils.IsRemoteCallType(ft) && !field.IsStatic) throw new Exception($"RemoteCalls should be static! (Class: {entType} Field: {field.Name})"); - - if(field.IsStatic) + if (field.IsStatic) continue; - - var syncVarFlags = field.GetCustomAttribute() ?? baseType.GetCustomAttribute(); - var syncFlags = syncVarFlags?.Flags ?? SyncFlags.None; - int offset = Utils.GetFieldOffset(field); - - //syncvars + + // --- SyncVar handling --- if (ft.IsGenericType && !ft.IsArray && ft.GetGenericTypeDefinition() == typeof(SyncVar<>)) { - ft = ft.GetGenericArguments()[0]; - if (ft.IsEnum) - ft = ft.GetEnumUnderlyingType(); + var innerType = ft.GetGenericArguments()[0]; + if (innerType.IsEnum) + innerType = innerType.GetEnumUnderlyingType(); - if (!ValueTypeProcessor.Registered.TryGetValue(ft, out var valueTypeProcessor)) + if (!ValueTypeProcessor.Registered.TryGetValue(innerType, out var processor)) { - Logger.LogError($"Unregistered field type: {ft}"); + Logger.LogError($"Unregistered field type: {innerType}"); continue; } - int fieldSize = valueTypeProcessor.Size; - if (syncFlags.HasFlagFast(SyncFlags.Interpolated) && !ft.IsEnum) + + var fieldSize = processor.Size; + + // choose the correct constructor: + EntityFieldInfo ef; + if (ctx.DeclaringType.IsSubclassOf(SyncableFieldType)) { - InterpolatedFieldsSize += fieldSize; - InterpolatedCount++; + // nested SyncableField's SyncVar<…> + ef = new EntityFieldInfo(name, processor, offsetMap.ToArray(), syncVarFlags, FieldType.SyncableSyncVar); + Logger.Log($"Registered syncable sync var field: {name} with offset {string.Join(",", offsetMap)}"); } - var fieldInfo = new EntityFieldInfo($"{baseType.Name}-{field.Name}", valueTypeProcessor, offset, syncVarFlags); - if (syncFlags.HasFlagFast(SyncFlags.LagCompensated)) + else { - lagCompensatedFields.Add(fieldInfo); - LagCompensatedSize += fieldSize; + // top-level field on the entity itself + ef = new EntityFieldInfo(name, processor, offsetMap.ToArray(), syncVarFlags, FieldType.SyncVar); + Logger.Log($"Registered field: {name} with offsets {string.Join(",", offsetMap)}"); + + if (syncFlags.HasFlagFast(SyncFlags.Interpolated) && !ft.IsEnum) + { + InterpolatedFieldsSize += fieldSize; + InterpolatedCount++; + } + + if (syncFlags.HasFlagFast(SyncFlags.LagCompensated)) + { + lagCompensatedFields.Add(ef); + LagCompensatedSize += fieldSize; + } } - if (fieldInfo.IsPredicted) - PredictedSize += fieldSize; + if (ef.IsPredicted) + PredictedSize += ef.IntSize; - fields.Add(fieldInfo); - FixedFieldsSize += fieldSize; + fields.Add(ef); + FixedFieldsSize += ef.IntSize; } + // --- nested SyncableFieldType --- else if (ft.IsSubclassOf(SyncableFieldType)) { if (!field.IsInitOnly) throw new Exception($"Syncable fields should be readonly! (Class: {entType} Field: {field.Name})"); - - syncableFields.Add(new SyncableFieldInfo(offset, syncFlags)); - var syncableFieldTypesWithBase = Utils.GetBaseTypes(ft, SyncableFieldType, true, true); - while(syncableFieldTypesWithBase.Count > 0) + + syncableFields.Add(new SyncableFieldInfo(name, offsetMap.ToArray(), syncVarFlags.Flags)); + Logger.Log($"Registered syncable field {name} with offsets {string.Join(",", offsetMap)}"); + + var nestedTypes = Utils.GetBaseTypes(ft, SyncableFieldType, true, true); + foreach (var nest in nestedTypes) { - var syncableType = syncableFieldTypesWithBase.Pop(); - //syncable fields - foreach (var syncableField in Utils.GetProcessedFields(syncableType)) + foreach (var nestedField in Utils.GetProcessedFields(nest)) { - var syncableFieldType = syncableField.FieldType; - if(Utils.IsRemoteCallType(syncableFieldType) && !syncableField.IsStatic) - throw new Exception($"RemoteCalls should be static! (Class: {syncableType} Field: {syncableField.Name})"); - - if (!syncableFieldType.IsValueType || - !syncableFieldType.IsGenericType || - syncableFieldType.GetGenericTypeDefinition() != typeof(SyncVar<>) || - syncableField.IsStatic) - continue; - - syncableFieldType = syncableFieldType.GetGenericArguments()[0]; - if (syncableFieldType.IsEnum) - syncableFieldType = syncableFieldType.GetEnumUnderlyingType(); - - if (!ValueTypeProcessor.Registered.TryGetValue(syncableFieldType, out var valueTypeProcessor)) + var nestedFlags = nestedField.GetCustomAttribute() + ?? nest.GetCustomAttribute() + ?? syncVarFlags; + + var nestFieldOffset = Utils.GetFieldOffset(nestedField); + + // build new chain: existing chain + nestFieldOffset + var nestedOffsetMap = new List(offsetMap) { nestFieldOffset }; + + fieldsStack.Push(new PendingField { - Logger.LogError($"Unregistered field type: {syncableFieldType}"); - continue; - } - int syncvarOffset = Utils.GetFieldOffset(syncableField); - var fieldInfo = new EntityFieldInfo($"{baseType.Name}-{field.Name}:{syncableField.Name}", valueTypeProcessor, offset, syncvarOffset, syncVarFlags); - fields.Add(fieldInfo); - FixedFieldsSize += fieldInfo.IntSize; - if (fieldInfo.IsPredicted) - PredictedSize += fieldInfo.IntSize; + Field = nestedField, + NamePrefix = $"{name}:{nestedField.Name}", + Offsets = nestedOffsetMap, + Flags = nestedFlags, + DeclaringType = nest + }); } } + continue; } } } - + //sort by placing interpolated first fields.Sort((a, b) => { @@ -298,7 +383,7 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp Fields = fields.ToArray(); SyncableFields = syncableFields.ToArray(); FieldsCount = Fields.Length; - FieldsFlagsSize = (FieldsCount-1) / 8 + 1; + FieldsFlagsSize = (FieldsCount - 1) / 8 + 1; LagCompensatedFields = lagCompensatedFields.ToArray(); LagCompensatedCount = LagCompensatedFields.Length; @@ -314,7 +399,7 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp field.PredictedOffset = predictedOffset; predictedOffset += field.IntSize; ownedRollbackFields.Add(field); - if(field.Flags.HasFlagFast(SyncFlags.AlwaysRollback)) + if (field.Flags.HasFlagFast(SyncFlags.AlwaysRollback)) remoteRollbackFields.Add(field); } else @@ -322,11 +407,11 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp field.PredictedOffset = -1; } } - + //cache rollbackFields _ownedRollbackFields = ownedRollbackFields.ToArray(); _remoteRollbackFields = remoteRollbackFields.ToArray(); - + _maxHistoryCount = (byte)entityManager.MaxHistorySize; int historySize = entType.IsSubclassOf(EntityLogicType) ? (_maxHistoryCount + 1) * LagCompensatedSize : 0; if (entityManager.IsServer) diff --git a/LiteEntitySystem/Internal/EntityFieldInfo.cs b/LiteEntitySystem/Internal/EntityFieldInfo.cs index ece94bc..7f430b0 100644 --- a/LiteEntitySystem/Internal/EntityFieldInfo.cs +++ b/LiteEntitySystem/Internal/EntityFieldInfo.cs @@ -1,4 +1,6 @@ -namespace LiteEntitySystem.Internal +using System.Linq; + +namespace LiteEntitySystem.Internal { internal enum FieldType { @@ -10,8 +12,7 @@ internal struct EntityFieldInfo { public readonly string Name; //used for debug public readonly ValueTypeProcessor TypeProcessor; - public readonly int Offset; - public readonly int SyncableSyncVarOffset; + public readonly int[] Offsets; public readonly uint Size; public readonly int IntSize; public readonly FieldType FieldType; @@ -26,16 +27,16 @@ internal struct EntityFieldInfo public EntityFieldInfo( string name, ValueTypeProcessor valueTypeProcessor, - int offset, - SyncVarFlags flags) + int[] offsets, + SyncVarFlags flags, + FieldType fieldType) { Name = name; TypeProcessor = valueTypeProcessor; - SyncableSyncVarOffset = -1; - Offset = offset; + Offsets = offsets; Size = (uint)TypeProcessor.Size; IntSize = TypeProcessor.Size; - FieldType = FieldType.SyncVar; + FieldType = fieldType; FixedOffset = 0; PredictedOffset = 0; OnSync = null; @@ -46,27 +47,27 @@ public EntityFieldInfo( } //For syncable syncvar - public EntityFieldInfo( - string name, - ValueTypeProcessor valueTypeProcessor, - int offset, - int syncableSyncVarOffset, - SyncVarFlags flags) - { - Name = name; - TypeProcessor = valueTypeProcessor; - SyncableSyncVarOffset = syncableSyncVarOffset; - Offset = offset; - Size = (uint)TypeProcessor.Size; - IntSize = TypeProcessor.Size; - FieldType = FieldType.SyncableSyncVar; - FixedOffset = 0; - PredictedOffset = 0; - OnSync = null; - Flags = flags?.Flags ?? SyncFlags.None; - IsPredicted = Flags.HasFlagFast(SyncFlags.AlwaysRollback) || - (!Flags.HasFlagFast(SyncFlags.OnlyForOtherPlayers) && - !Flags.HasFlagFast(SyncFlags.NeverRollBack)); - } + // public EntityFieldInfo( + // string name, + // ValueTypeProcessor valueTypeProcessor, + // int offset, + // int syncableSyncVarOffset, + // SyncVarFlags flags) + // { + // Name = name; + // TypeProcessor = valueTypeProcessor; + // SyncableSyncVarOffset = syncableSyncVarOffset; + // Offset = offset; + // Size = (uint)TypeProcessor.Size; + // IntSize = TypeProcessor.Size; + // FieldType = FieldType.SyncableSyncVar; + // FixedOffset = 0; + // PredictedOffset = 0; + // OnSync = null; + // Flags = flags?.Flags ?? SyncFlags.None; + // IsPredicted = Flags.HasFlagFast(SyncFlags.AlwaysRollback) || + // (!Flags.HasFlagFast(SyncFlags.OnlyForOtherPlayers) && + // !Flags.HasFlagFast(SyncFlags.NeverRollBack)); + // } } } \ No newline at end of file diff --git a/LiteEntitySystem/Internal/InternalEntity.cs b/LiteEntitySystem/Internal/InternalEntity.cs index 2c9189c..4c0ae13 100644 --- a/LiteEntitySystem/Internal/InternalEntity.cs +++ b/LiteEntitySystem/Internal/InternalEntity.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.CompilerServices; namespace LiteEntitySystem.Internal @@ -8,22 +9,22 @@ public abstract class InternalEntity : InternalBaseClass, IComparable InternalOwnerId; - + internal byte[] IOBuffer; internal readonly int UpdateOrderNum; - + /// /// Entity class id /// public readonly ushort ClassId; - + /// /// Entity instance id /// public readonly ushort Id; - + /// /// Entity manager /// @@ -33,7 +34,7 @@ public abstract class InternalEntity : InternalBaseClass, IComparable public bool IsServer => EntityManager.IsServer; - + /// /// Is entity on server /// @@ -51,10 +52,10 @@ public abstract class InternalEntity : InternalBaseClass, IComparable _isDestroyed; - + /// /// Is entity is destroyed /// @@ -69,22 +70,22 @@ public abstract class InternalEntity : InternalBaseClass, IComparable public bool IsRemoteControlled => InternalOwnerId.Value != EntityManager.InternalPlayerId; - + /// /// Is entity is controlled by server /// public bool IsServerControlled => InternalOwnerId.Value == EntityManager.ServerPlayerId; - + /// /// ClientEntityManager that available only on client. Will throw exception if called on server /// public ClientEntityManager ClientManager => (ClientEntityManager)EntityManager; - + /// /// ServerEntityManager that available only on server. Will throw exception if called on client /// public ServerEntityManager ServerManager => (ServerEntityManager)EntityManager; - + /// /// Owner player id /// ServerPlayerId - 0 @@ -101,7 +102,7 @@ public abstract class InternalEntity : InternalBaseClass, IComparable public bool IsSingleton => ClassData.IsSingleton; - + internal ref EntityClassData ClassData => ref EntityManager.ClassDataDict[ClassId]; /// @@ -118,7 +119,7 @@ public void Destroy() return; DestroyInternal(); } - + private void OnDestroyChange(bool prevValue) { if (!prevValue && _isDestroyed) @@ -154,7 +155,7 @@ internal void SafeUpdate() catch (Exception e) { Logger.LogError($"Exception in entity({Id}) update:\n{e}"); - } + } } /// @@ -163,13 +164,13 @@ internal void SafeUpdate() protected internal virtual void Update() { } - + /// /// Called at rollback begin before all values reset to first frame in rollback queue. /// protected internal virtual void OnBeforeRollback() { - + } /// @@ -177,7 +178,7 @@ protected internal virtual void OnBeforeRollback() /// protected internal virtual void OnRollback() { - + } /// @@ -185,7 +186,7 @@ protected internal virtual void OnRollback() /// protected internal virtual void VisualUpdate() { - + } /// @@ -198,60 +199,93 @@ protected internal virtual void OnConstructed() internal void RegisterRpcInternal() { ref var classData = ref EntityManager.ClassDataDict[ClassId]; - - //setup field ids for BindOnChange and pass on server this for OnChangedEvent to StateSerializer + + // Setup SyncVar onChange bindings var onChangeTarget = EntityManager.IsServer && !IsLocal ? this : null; for (int i = 0; i < classData.FieldsCount; i++) { ref var field = ref classData.Fields[i]; + + // SyncVar inside a syncable field (first-level only) if (field.FieldType == FieldType.SyncVar) { - field.TypeProcessor.InitSyncVar(this, field.Offset, onChangeTarget, (ushort)i); + field.TypeProcessor.InitSyncVar(this, field.Offsets.Last(), onChangeTarget, (ushort)i); } else { - var syncableField = RefMagic.RefFieldValue(this, field.Offset); - field.TypeProcessor.InitSyncVar(syncableField, field.SyncableSyncVarOffset, onChangeTarget, (ushort)i); + // find SyncVar via offset map + InternalBaseClass syncable = this; + for (int j = 0; j < field.Offsets.Length-1; j++) + { + syncable = RefMagic.RefFieldValue(syncable, field.Offsets[j]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {field.Offsets[j]} is null"); + } + + field.TypeProcessor.InitSyncVar(syncable, field.Offsets.Last(), onChangeTarget, (ushort)i); } } - - List rpcCahce = null; - if(classData.RemoteCallsClient == null) + + // Register top-level RPCs if not yet cached + List rpcCache = null; + if (classData.RemoteCallsClient == null) { - rpcCahce = new List(); - var rpcRegistrator = new RPCRegistrator(rpcCahce, classData.Fields); + rpcCache = new List(); + var rpcRegistrator = new RPCRegistrator(rpcCache, classData.Fields); RegisterRPC(ref rpcRegistrator); - //Logger.Log($"RegisterRPCs for class: {classData.ClassId}"); + Logger.Log($"RegisterRPCs for class: {classData.ClassId}"); } - //setup id for later sync calls + + // Setup SyncableField RPC offsets and bindings with nested traversal for (int i = 0; i < classData.SyncableFields.Length; i++) { - ref var syncFieldInfo = ref classData.SyncableFields[i]; - var syncField = RefMagic.RefFieldValue(this, syncFieldInfo.Offset); + ref var syncInfo = ref classData.SyncableFields[i]; + + // Traverse the chain of nested SyncableFields via offset map + object current = this; + foreach (var relOffset in syncInfo.Offsets) + { + current = RefMagic.RefFieldValue(current, relOffset); + if (current == null) + throw new NullReferenceException($"Nested SyncableField at offset {relOffset} is null"); + } + var syncField = (SyncableField)current; syncField.ParentEntityInternal = this; - if (syncFieldInfo.Flags.HasFlagFast(SyncFlags.OnlyForOwner)) + + // Apply flag-based RPC targeting + if (syncInfo.Flags.HasFlagFast(SyncFlags.OnlyForOwner)) syncField.Flags = ExecuteFlags.SendToOwner; - else if (syncFieldInfo.Flags.HasFlagFast(SyncFlags.OnlyForOtherPlayers)) + else if (syncInfo.Flags.HasFlagFast(SyncFlags.OnlyForOtherPlayers)) syncField.Flags = ExecuteFlags.SendToOther; else syncField.Flags = ExecuteFlags.SendToAll; + + // Assign or register RPC offsets if (classData.RemoteCallsClient != null) { - syncField.RPCOffset = syncFieldInfo.RPCOffset; + // Use cached offsets + syncField.RPCOffset = syncInfo.RPCOffset; } else { - syncField.RPCOffset = (ushort)rpcCahce.Count; - syncFieldInfo.RPCOffset = syncField.RPCOffset; - var syncablesRegistrator = new SyncableRPCRegistrator(syncFieldInfo.Offset, rpcCahce); - syncField.RegisterRPC(ref syncablesRegistrator); + // New registration + syncField.RPCOffset = (ushort)rpcCache.Count; + syncInfo.RPCOffset = syncField.RPCOffset; + // Use AbsoluteOffset to register RPCs on this nested SyncableField + var syncRpcRegistrator = new SyncableRPCRegistrator(syncInfo.Offsets, rpcCache); + syncField.RegisterRPC(ref syncRpcRegistrator); + Logger.Log($"RegisterSyncableRPCs for class: {classData.ClassId}"); } } - classData.RemoteCallsClient ??= rpcCahce.ToArray(); + + // Cache the RPC list if newly created + if (classData.RemoteCallsClient == null) + classData.RemoteCallsClient = rpcCache.ToArray(); } + /// /// Method for registering RPCs and OnChange notifications /// @@ -260,7 +294,7 @@ protected virtual void RegisterRPC(ref RPCRegistrator r) { r.BindOnChange(this, ref _isDestroyed, OnDestroyChange); } - + protected void ExecuteRPC(in RemoteCall rpc) { if (IsRemoved) diff --git a/LiteEntitySystem/Internal/ServerStateData.cs b/LiteEntitySystem/Internal/ServerStateData.cs index 6477be4..ac94d58 100644 --- a/LiteEntitySystem/Internal/ServerStateData.cs +++ b/LiteEntitySystem/Internal/ServerStateData.cs @@ -10,7 +10,7 @@ internal struct RemoteCallsCache public readonly RPCHeader Header; public readonly EntitySharedReference EntityId; public readonly int Offset; - public readonly int SyncableOffset; + public readonly int[] SyncableOffsets; public readonly MethodCallDelegate Delegate; public bool Executed; @@ -20,7 +20,7 @@ public RemoteCallsCache(RPCHeader header, EntitySharedReference entityId, RpcFie EntityId = entityId; Delegate = rpcFieldInfo.Method; Offset = offset; - SyncableOffset = rpcFieldInfo.SyncableOffset; + SyncableOffsets = rpcFieldInfo.SyncableOffsets; Executed = false; if (Delegate == null) Logger.LogError($"ZeroRPC: {header.Id}"); @@ -30,7 +30,7 @@ public RemoteCallsCache(RPCHeader header, EntitySharedReference entityId, RpcFie internal readonly struct InterpolatedCache { public readonly InternalEntity Entity; - public readonly int FieldOffset; + public readonly int[] FieldOffsets; public readonly int FieldFixedOffset; public readonly ValueTypeProcessor TypeProcessor; public readonly int StateReaderOffset; @@ -38,7 +38,7 @@ internal readonly struct InterpolatedCache public InterpolatedCache(InternalEntity entity, ref EntityFieldInfo field, int offset) { Entity = entity; - FieldOffset = field.Offset; + FieldOffsets = field.Offsets; FieldFixedOffset = field.FixedOffset; TypeProcessor = field.TypeProcessor; StateReaderOffset = offset; @@ -149,7 +149,14 @@ public void ExecuteSyncableRpcs(ClientEntityManager entityManager, ushort minima } rpc.Executed = true; entityManager.CurrentRPCTick = rpc.Header.Tick; - var syncableField = RefMagic.RefFieldValue(entity, rpc.SyncableOffset); + InternalBaseClass syncable = entity; + for (int j = 0; j < rpc.SyncableOffsets.Length; j++) + { + syncable = RefMagic.RefFieldValue(syncable, rpc.SyncableOffsets[j]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {rpc.SyncableOffsets[j]} is null"); + } + var syncableField = (SyncableField)syncable; if (syncSet.Add(syncableField)) syncableField.BeforeReadRPC(); try @@ -231,7 +238,7 @@ public unsafe void ReadRPCs(byte* rawData, ref int position, EntitySharedReferen //Logger.Log($"[CEM] ReadRPC. RpcId: {header.Id}, Tick: {header.Tick}, TypeSize: {header.TypeSize}, Count: {header.Count}"); //this is entity rpc - if (rpcCache.SyncableOffset == -1) + if (rpcCache.SyncableOffsets.Length == 0) { _remoteCallsCaches[_remoteCallsCount] = rpcCache; _remoteCallsCount++; diff --git a/LiteEntitySystem/Internal/StateSerializer.cs b/LiteEntitySystem/Internal/StateSerializer.cs index bdfe0c8..705f07c 100644 --- a/LiteEntitySystem/Internal/StateSerializer.cs +++ b/LiteEntitySystem/Internal/StateSerializer.cs @@ -158,8 +158,16 @@ private void MakeOnSync(ushort tick) _entity.OnSyncRequested(); var syncableFields = _entity.ClassData.SyncableFields; for (int i = 0; i < syncableFields.Length; i++) - RefMagic.RefFieldValue(_entity, syncableFields[i].Offset) - .OnSyncRequested(); + { + InternalBaseClass syncable = _entity; + for (int j = 0; j < syncableFields[i].Offsets.Length; j++) + { + syncable = RefMagic.RefFieldValue(syncable, syncableFields[i].Offsets[j]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {syncableFields[i].Offsets[j]} is null"); + } + ((SyncableField)syncable).OnSyncRequested(); + } } catch (Exception e) { diff --git a/LiteEntitySystem/Internal/ValueTypeProcessor.cs b/LiteEntitySystem/Internal/ValueTypeProcessor.cs index a15a9a9..f7acafc 100644 --- a/LiteEntitySystem/Internal/ValueTypeProcessor.cs +++ b/LiteEntitySystem/Internal/ValueTypeProcessor.cs @@ -14,56 +14,134 @@ internal abstract unsafe class ValueTypeProcessor protected ValueTypeProcessor(int size) => Size = size; 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 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); + internal abstract void SetFrom(InternalBaseClass obj, int[] offsetMap, byte* data); + internal abstract bool SetFromAndSync(InternalBaseClass obj, int[] offsetMap, byte* data); + internal abstract void WriteTo(InternalBaseClass obj, int[] offsetMap, byte* data); + internal abstract void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer); + internal abstract void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime); + internal abstract int GetHashCode(InternalBaseClass obj, int[] offsetMap); + internal abstract string ToString(InternalBaseClass obj, int[] offsetMap); } internal unsafe class ValueTypeProcessor : ValueTypeProcessor where T : unmanaged { public ValueTypeProcessor() : base(sizeof(T)) { } - internal override void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => + internal override void SetInterpolation(InternalBaseClass obj, int[] offsetMap, 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) => + 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 LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) { - ref var a = ref RefMagic.RefFieldValue>(obj, offset); + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + ref var a = ref RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]); *(T*)tempHistory = a; a.SetDirect(*(T*)historyA); } - internal override void SetFrom(InternalBaseClass obj, int offset, byte* data) => - RefMagic.RefFieldValue>(obj, offset).SetDirect(*(T*)data); + internal override void SetFrom(InternalBaseClass obj, int[] offsetMap, byte* data) + { + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).SetDirect(*(T*)data); + } - internal override bool SetFromAndSync(InternalBaseClass obj, int offset, byte* data) => - RefMagic.RefFieldValue>(obj, offset).SetFromAndSync(data); + internal override bool SetFromAndSync(InternalBaseClass obj, int[] offsetMap, byte* data) + { + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + return RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).SetFromAndSync(data); + } + - internal override void WriteTo(InternalBaseClass obj, int offset, byte* data) => - *(T*)data = RefMagic.RefFieldValue>(obj, offset); + internal override void WriteTo(InternalBaseClass obj, int[] offsetMap, byte* data) + { + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + + *(T*)data = RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]); + } - internal override int GetHashCode(InternalBaseClass obj, int offset) => - RefMagic.RefFieldValue>(obj, offset).GetHashCode(); + internal override int GetHashCode(InternalBaseClass obj, int[] offsetMap) + { + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + + return RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).GetHashCode(); + } + - internal override string ToString(InternalBaseClass obj, int offset) => - RefMagic.RefFieldValue>(obj, offset).ToString(); + internal override string ToString(InternalBaseClass obj, int[] offsetMap) + { + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + + return RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).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)); + internal override unsafe void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) + { + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + + RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).SetDirect(Utils.Lerp(*(int*)prev, *(int*)current, fTimer)); + } - internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) + internal override unsafe void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) { - ref var a = ref RefMagic.RefFieldValue>(obj, offset); + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + + ref var a = ref RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]); *(int*)tempHistory = a; a.SetDirect(Utils.Lerp(*(int*)historyA, *(int*)historyB, lerpTime)); } @@ -71,12 +149,30 @@ 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.RefFieldValue>(obj, offset).SetDirect(Utils.Lerp(*(long*)prev, *(long*)current, fTimer)); + internal override unsafe void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) + { + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + + RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).SetDirect(Utils.Lerp(*(long*)prev, *(long*)current, fTimer)); + } - internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) + internal override unsafe void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) { - ref var a = ref RefMagic.RefFieldValue>(obj, offset); + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + + ref var a = ref RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]); *(long*)tempHistory = a; a.SetDirect(Utils.Lerp(*(long*)historyA, *(long*)historyB, lerpTime)); } @@ -84,12 +180,30 @@ 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.RefFieldValue>(obj, offset).SetDirect(Utils.Lerp(*(float*)prev, *(float*)current, fTimer)); + internal override unsafe void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) + { + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + + RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).SetDirect(Utils.Lerp(*(float*)prev, *(float*)current, fTimer)); + } - internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) + internal override unsafe void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) { - ref var a = ref RefMagic.RefFieldValue>(obj, offset); + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + + ref var a = ref RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]); *(float*)tempHistory = a; a.SetDirect(Utils.Lerp(*(float*)historyA, *(float*)historyB, lerpTime)); } @@ -97,12 +211,30 @@ 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.RefFieldValue>(obj, offset).SetDirect(Utils.Lerp(*(double*)prev, *(double*)current, fTimer)); + internal override unsafe void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) + { + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + + RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).SetDirect(Utils.Lerp(*(double*)prev, *(double*)current, fTimer)); + } - internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) + internal override unsafe void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) { - ref var a = ref RefMagic.RefFieldValue>(obj, offset); + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + + ref var a = ref RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]); *(double*)tempHistory = a; a.SetDirect(Utils.Lerp(*(double*)historyA, *(double*)historyB, lerpTime)); } @@ -112,12 +244,30 @@ 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 SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) + { + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + + RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).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) + internal override void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) { - ref var a = ref RefMagic.RefFieldValue>(obj, offset); + InternalBaseClass syncable = obj; + for (int i = 0; i < offsetMap.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); + if (syncable == null) + throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); + } + + ref var a = ref RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]); *(T*)tempHistory = a; a.SetDirect(_interpDelegate?.Invoke(*(T*)historyA, *(T*)historyB, lerpTime) ?? *(T*)historyA); } diff --git a/LiteEntitySystem/RPCRegistrator.cs b/LiteEntitySystem/RPCRegistrator.cs index abadca7..db04457 100644 --- a/LiteEntitySystem/RPCRegistrator.cs +++ b/LiteEntitySystem/RPCRegistrator.cs @@ -248,14 +248,14 @@ public void CreateRPCAction(Action methodToCall, ref Rem public readonly ref struct SyncableRPCRegistrator { private readonly List _calls; - private readonly int _syncableOffset; + private readonly int[] _syncableOffsets; private readonly ushort _initialCallsSize; - internal SyncableRPCRegistrator(int syncableOffset, List remoteCallsList) + internal SyncableRPCRegistrator(int[] syncableOffsets, List remoteCallsList) { _calls = remoteCallsList; _initialCallsSize = (ushort)_calls.Count; - _syncableOffset = syncableOffset; + _syncableOffsets = syncableOffsets; } public void CreateClientAction(TSyncField self, Action methodToCall, ref RemoteCall remoteCallHandle) where TSyncField : SyncableField @@ -286,28 +286,28 @@ public void CreateClientAction(Action methodToCall, ref { if (!remoteCallHandle.Initialized) remoteCallHandle = new RemoteCall(null, (ushort)(_calls.Count - _initialCallsSize), 0); - _calls.Add(new RpcFieldInfo(_syncableOffset, RemoteCall.CreateMCD(methodToCall))); + _calls.Add(new RpcFieldInfo(_syncableOffsets, RemoteCall.CreateMCD(methodToCall))); } public void CreateClientAction(Action methodToCall, ref RemoteCall remoteCallHandle) where T : unmanaged where TSyncField : SyncableField { if (!remoteCallHandle.Initialized) remoteCallHandle = new RemoteCall(null, (ushort)(_calls.Count - _initialCallsSize), 0); - _calls.Add(new RpcFieldInfo(_syncableOffset, RemoteCall.CreateMCD(methodToCall))); + _calls.Add(new RpcFieldInfo(_syncableOffsets, RemoteCall.CreateMCD(methodToCall))); } public void CreateClientAction(SpanAction methodToCall, ref RemoteCallSpan remoteCallHandle) where T : unmanaged where TSyncField : SyncableField { if (!remoteCallHandle.Initialized) remoteCallHandle = new RemoteCallSpan(null, (ushort)(_calls.Count - _initialCallsSize), 0); - _calls.Add(new RpcFieldInfo(_syncableOffset, RemoteCallSpan.CreateMCD(methodToCall))); + _calls.Add(new RpcFieldInfo(_syncableOffsets, RemoteCallSpan.CreateMCD(methodToCall))); } public void CreateClientAction(Action methodToCall, ref RemoteCallSerializable remoteCallHandle) where T : struct, ISpanSerializable where TSyncField : SyncableField { if (!remoteCallHandle.Initialized) remoteCallHandle = new RemoteCallSerializable(null, (ushort)(_calls.Count - _initialCallsSize), 0); - _calls.Add(new RpcFieldInfo(_syncableOffset, RemoteCallSerializable.CreateMCD(methodToCall))); + _calls.Add(new RpcFieldInfo(_syncableOffsets, RemoteCallSerializable.CreateMCD(methodToCall))); } } } \ No newline at end of file diff --git a/LiteEntitySystem/Utils.cs b/LiteEntitySystem/Utils.cs index 7a979c6..bedcda8 100644 --- a/LiteEntitySystem/Utils.cs +++ b/LiteEntitySystem/Utils.cs @@ -283,6 +283,33 @@ internal static int GetFieldOffset(FieldInfo fieldInfo) : (Marshal.ReadInt32(fieldInfo.FieldHandle.Value + DotNetOffset) & 0xFFFFFF) + IntPtr.Size; } + internal static SyncableField GetSyncableField(InternalBaseClass baseClass, int[] offsets) + { + InternalBaseClass syncable = baseClass; + for (int j = 0; j < offsets.Length; j++) + { + syncable = RefMagic.RefFieldValue(syncable, offsets[j]); + if (syncable == null) + throw new NullReferenceException($"SyncableField at offset {offsets[j]} is null"); + } + + return (SyncableField)syncable; + } + + internal static SyncVar GetSyncableSyncVar(InternalBaseClass baseClass, int[] offsets) where T : unmanaged + { + InternalBaseClass syncable = baseClass; + for (int i = 0; i < offsets.Length-1; i++) + { + syncable = RefMagic.RefFieldValue(syncable, offsets[i]); + if (syncable == null) + throw new NullReferenceException($"SyncableField at offset {offsets[i]} is null"); + } + ref var a = ref RefMagic.RefFieldValue>(syncable, offsets[offsets.Length-1]); + + return a; + } + static Utils() { IsMono = Type.GetType("Mono.Runtime") != null From a01fd23118572bd800c650231c088cf9100bc2b8 Mon Sep 17 00:00:00 2001 From: CoreAidan Date: Mon, 12 May 2025 21:53:29 -0400 Subject: [PATCH 2/3] fixed issues with interpolated fields in syncablefields --- LiteEntitySystem/Internal/EntityClassData.cs | 25 +-- LiteEntitySystem/Internal/InternalEntity.cs | 19 +- LiteEntitySystem/Internal/ServerStateData.cs | 12 +- LiteEntitySystem/Internal/StateSerializer.cs | 10 +- .../Internal/ValueTypeProcessor.cs | 206 ++++-------------- LiteEntitySystem/Utils.cs | 4 +- 6 files changed, 66 insertions(+), 210 deletions(-) diff --git a/LiteEntitySystem/Internal/EntityClassData.cs b/LiteEntitySystem/Internal/EntityClassData.cs index b92b6c6..312a6fc 100644 --- a/LiteEntitySystem/Internal/EntityClassData.cs +++ b/LiteEntitySystem/Internal/EntityClassData.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Reflection; -using ImGuiGodot.Internal; namespace LiteEntitySystem.Internal { @@ -315,25 +314,25 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp // top-level field on the entity itself ef = new EntityFieldInfo(name, processor, offsetMap.ToArray(), syncVarFlags, FieldType.SyncVar); Logger.Log($"Registered field: {name} with offsets {string.Join(",", offsetMap)}"); + } - if (syncFlags.HasFlagFast(SyncFlags.Interpolated) && !ft.IsEnum) - { - InterpolatedFieldsSize += fieldSize; - InterpolatedCount++; - } + if (syncFlags.HasFlagFast(SyncFlags.Interpolated) && !ft.IsEnum) + { + InterpolatedFieldsSize += fieldSize; + InterpolatedCount++; + } - if (syncFlags.HasFlagFast(SyncFlags.LagCompensated)) - { - lagCompensatedFields.Add(ef); - LagCompensatedSize += fieldSize; - } + if (syncFlags.HasFlagFast(SyncFlags.LagCompensated)) + { + lagCompensatedFields.Add(ef); + LagCompensatedSize += fieldSize; } if (ef.IsPredicted) - PredictedSize += ef.IntSize; + PredictedSize += fieldSize; fields.Add(ef); - FixedFieldsSize += ef.IntSize; + FixedFieldsSize += fieldSize; } // --- nested SyncableFieldType --- else if (ft.IsSubclassOf(SyncableFieldType)) diff --git a/LiteEntitySystem/Internal/InternalEntity.cs b/LiteEntitySystem/Internal/InternalEntity.cs index 4c0ae13..d2a8bda 100644 --- a/LiteEntitySystem/Internal/InternalEntity.cs +++ b/LiteEntitySystem/Internal/InternalEntity.cs @@ -206,24 +206,7 @@ internal void RegisterRpcInternal() { ref var field = ref classData.Fields[i]; - // SyncVar inside a syncable field (first-level only) - if (field.FieldType == FieldType.SyncVar) - { - field.TypeProcessor.InitSyncVar(this, field.Offsets.Last(), onChangeTarget, (ushort)i); - } - else - { - // find SyncVar via offset map - InternalBaseClass syncable = this; - for (int j = 0; j < field.Offsets.Length-1; j++) - { - syncable = RefMagic.RefFieldValue(syncable, field.Offsets[j]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {field.Offsets[j]} is null"); - } - - field.TypeProcessor.InitSyncVar(syncable, field.Offsets.Last(), onChangeTarget, (ushort)i); - } + field.TypeProcessor.InitSyncVar(this, field.Offsets, onChangeTarget, (ushort)i); } // Register top-level RPCs if not yet cached diff --git a/LiteEntitySystem/Internal/ServerStateData.cs b/LiteEntitySystem/Internal/ServerStateData.cs index ac94d58..fe91f63 100644 --- a/LiteEntitySystem/Internal/ServerStateData.cs +++ b/LiteEntitySystem/Internal/ServerStateData.cs @@ -149,14 +149,9 @@ public void ExecuteSyncableRpcs(ClientEntityManager entityManager, ushort minima } rpc.Executed = true; entityManager.CurrentRPCTick = rpc.Header.Tick; - InternalBaseClass syncable = entity; - for (int j = 0; j < rpc.SyncableOffsets.Length; j++) - { - syncable = RefMagic.RefFieldValue(syncable, rpc.SyncableOffsets[j]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {rpc.SyncableOffsets[j]} is null"); - } - var syncableField = (SyncableField)syncable; + + var syncableField = Utils.GetSyncableField(entity, rpc.SyncableOffsets); + if (syncSet.Add(syncableField)) syncableField.BeforeReadRPC(); try @@ -168,6 +163,7 @@ public void ExecuteSyncableRpcs(ClientEntityManager entityManager, ushort minima Logger.LogError($"Error when executing syncableRPC: {entity}. RPCID: {rpc.Header.Id}. {e}"); } } + foreach (var syncableField in syncSet) syncableField.AfterReadRPC(); entityManager.IsExecutingRPC = false; diff --git a/LiteEntitySystem/Internal/StateSerializer.cs b/LiteEntitySystem/Internal/StateSerializer.cs index 705f07c..5420b69 100644 --- a/LiteEntitySystem/Internal/StateSerializer.cs +++ b/LiteEntitySystem/Internal/StateSerializer.cs @@ -159,14 +159,8 @@ private void MakeOnSync(ushort tick) var syncableFields = _entity.ClassData.SyncableFields; for (int i = 0; i < syncableFields.Length; i++) { - InternalBaseClass syncable = _entity; - for (int j = 0; j < syncableFields[i].Offsets.Length; j++) - { - syncable = RefMagic.RefFieldValue(syncable, syncableFields[i].Offsets[j]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {syncableFields[i].Offsets[j]} is null"); - } - ((SyncableField)syncable).OnSyncRequested(); + SyncableField syncable = Utils.GetSyncableField(_entity, syncableFields[i].Offsets); + syncable.OnSyncRequested(); } } catch (Exception e) diff --git a/LiteEntitySystem/Internal/ValueTypeProcessor.cs b/LiteEntitySystem/Internal/ValueTypeProcessor.cs index f7acafc..06bb0ef 100644 --- a/LiteEntitySystem/Internal/ValueTypeProcessor.cs +++ b/LiteEntitySystem/Internal/ValueTypeProcessor.cs @@ -4,16 +4,16 @@ namespace LiteEntitySystem.Internal { public delegate T InterpolatorDelegateWithReturn(T prev, T current, float t) where T : unmanaged; - + internal abstract unsafe class ValueTypeProcessor { - public static readonly Dictionary Registered = new (); - + public static readonly Dictionary Registered = new(); + internal readonly int Size; protected ValueTypeProcessor(int size) => Size = size; - internal abstract void InitSyncVar(InternalBaseClass obj, int offset, InternalEntity entity, ushort fieldId); + internal abstract void InitSyncVar(InternalBaseClass obj, int[] offsetMap, InternalEntity entity, ushort fieldId); internal abstract void SetFrom(InternalBaseClass obj, int[] offsetMap, byte* data); internal abstract bool SetFromAndSync(InternalBaseClass obj, int[] offsetMap, byte* data); internal abstract void WriteTo(InternalBaseClass obj, int[] offsetMap, byte* data); @@ -30,89 +30,44 @@ public ValueTypeProcessor() : base(sizeof(T)) { } internal override void SetInterpolation(InternalBaseClass obj, int[] offsetMap, 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) + internal override void InitSyncVar(InternalBaseClass obj, int[] offsetMap, InternalEntity entity, ushort fieldId) { - - RefMagic.RefFieldValue>(obj, offset).Init(entity, fieldId); + Utils.GetSyncableSyncVar(obj, offsetMap).Init(entity, fieldId); } internal override void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - ref var a = ref RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]); - *(T*)tempHistory = a; - a.SetDirect(*(T*)historyA); + ref var syncvar = ref Utils.GetSyncableSyncVar(obj, offsetMap); + + *(T*)tempHistory = syncvar; + syncvar.SetDirect(*(T*)historyA); } - + internal override void SetFrom(InternalBaseClass obj, int[] offsetMap, byte* data) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).SetDirect(*(T*)data); + Utils.GetSyncableSyncVar(obj, offsetMap).SetDirect(*(T*)data); } internal override bool SetFromAndSync(InternalBaseClass obj, int[] offsetMap, byte* data) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - return RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).SetFromAndSync(data); + return Utils.GetSyncableSyncVar(obj, offsetMap).SetFromAndSync(data); } - + internal override void WriteTo(InternalBaseClass obj, int[] offsetMap, byte* data) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - - *(T*)data = RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]); + *(T*)data = Utils.GetSyncableSyncVar(obj, offsetMap); } internal override int GetHashCode(InternalBaseClass obj, int[] offsetMap) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - - return RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).GetHashCode(); + return Utils.GetSyncableSyncVar(obj, offsetMap).GetHashCode(); } - - + + internal override string ToString(InternalBaseClass obj, int[] offsetMap) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - - return RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).ToString(); + return Utils.GetSyncableSyncVar(obj, offsetMap).ToString(); } } @@ -120,59 +75,27 @@ internal class ValueTypeProcessorInt : ValueTypeProcessor { internal override unsafe void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - - RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).SetDirect(Utils.Lerp(*(int*)prev, *(int*)current, fTimer)); + Utils.GetSyncableSyncVar(obj, offsetMap).SetDirect(Utils.Lerp(*(int*)prev, *(int*)current, fTimer)); } - + internal override unsafe void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - - ref var a = ref RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]); + ref var a = ref Utils.GetSyncableSyncVar(obj, offsetMap); *(int*)tempHistory = a; a.SetDirect(Utils.Lerp(*(int*)historyA, *(int*)historyB, lerpTime)); } } - + internal class ValueTypeProcessorLong : ValueTypeProcessor { internal override unsafe void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - - RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).SetDirect(Utils.Lerp(*(long*)prev, *(long*)current, fTimer)); + Utils.GetSyncableSyncVar(obj, offsetMap).SetDirect(Utils.Lerp(*(long*)prev, *(long*)current, fTimer)); } - + internal override unsafe void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - - ref var a = ref RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]); + ref var a = ref Utils.GetSyncableSyncVar(obj, offsetMap); *(long*)tempHistory = a; a.SetDirect(Utils.Lerp(*(long*)historyA, *(long*)historyB, lerpTime)); } @@ -182,59 +105,31 @@ internal class ValueTypeProcessorFloat : ValueTypeProcessor { internal override unsafe void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - - RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).SetDirect(Utils.Lerp(*(float*)prev, *(float*)current, fTimer)); + var syncvar = Utils.GetSyncableSyncVar(obj, offsetMap); + var a = *(float*)prev; + var b = *(float*)current; + + syncvar.SetDirect(Utils.Lerp(a, b, fTimer)); } - + internal override unsafe void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - - ref var a = ref RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]); + ref var a = ref Utils.GetSyncableSyncVar(obj, offsetMap); *(float*)tempHistory = a; a.SetDirect(Utils.Lerp(*(float*)historyA, *(float*)historyB, lerpTime)); } } - + internal class ValueTypeProcessorDouble : ValueTypeProcessor { internal override unsafe void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - - RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).SetDirect(Utils.Lerp(*(double*)prev, *(double*)current, fTimer)); + Utils.GetSyncableSyncVar(obj, offsetMap).SetDirect(Utils.Lerp(*(double*)prev, *(double*)current, fTimer)); } - + internal override unsafe void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - - ref var a = ref RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]); + ref var a = ref Utils.GetSyncableSyncVar(obj, offsetMap); *(double*)tempHistory = a; a.SetDirect(Utils.Lerp(*(double*)historyA, *(double*)historyB, lerpTime)); } @@ -246,28 +141,17 @@ internal unsafe class UserTypeProcessor : ValueTypeProcessor where T : unm internal override void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - - RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]).SetDirect(_interpDelegate?.Invoke(*(T*)prev, *(T*)current, fTimer) ?? *(T*)prev); + var syncvar = Utils.GetSyncableSyncVar(obj, offsetMap); + + var a = *(T*)prev; + var b = *(T*)current; + + syncvar.SetDirect(_interpDelegate?.Invoke(*(T*)prev, *(T*)current, fTimer) ?? *(T*)prev); } - + internal override void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) { - InternalBaseClass syncable = obj; - for (int i = 0; i < offsetMap.Length-1; i++) - { - syncable = RefMagic.RefFieldValue(syncable, offsetMap[i]); - if (syncable == null) - throw new NullReferenceException($"SyncVar at offset {offsetMap[i]} is null"); - } - - ref var a = ref RefMagic.RefFieldValue>(syncable, offsetMap[offsetMap.Length-1]); + ref var a = ref Utils.GetSyncableSyncVar(obj, offsetMap); *(T*)tempHistory = a; a.SetDirect(_interpDelegate?.Invoke(*(T*)historyA, *(T*)historyB, lerpTime) ?? *(T*)historyA); } diff --git a/LiteEntitySystem/Utils.cs b/LiteEntitySystem/Utils.cs index bedcda8..faa078a 100644 --- a/LiteEntitySystem/Utils.cs +++ b/LiteEntitySystem/Utils.cs @@ -296,7 +296,7 @@ internal static SyncableField GetSyncableField(InternalBaseClass baseClass, int[ return (SyncableField)syncable; } - internal static SyncVar GetSyncableSyncVar(InternalBaseClass baseClass, int[] offsets) where T : unmanaged + internal static ref SyncVar GetSyncableSyncVar(InternalBaseClass baseClass, int[] offsets) where T : unmanaged { InternalBaseClass syncable = baseClass; for (int i = 0; i < offsets.Length-1; i++) @@ -307,7 +307,7 @@ internal static SyncVar GetSyncableSyncVar(InternalBaseClass baseClass, in } ref var a = ref RefMagic.RefFieldValue>(syncable, offsets[offsets.Length-1]); - return a; + return ref a; } static Utils() From 0f78a84fe61eda36c232c445b2a705a166d13dc1 Mon Sep 17 00:00:00 2001 From: CoreAidan Date: Mon, 23 Jun 2025 13:38:31 -0400 Subject: [PATCH 3/3] bug fixes --- LiteEntitySystem/ClientEntityManager.cs | 124 +++++++++--------- LiteEntitySystem/ILPart/RefMagic.dll | Bin 3584 -> 3584 bytes LiteEntitySystem/ILPart/RefMagic.il | 12 +- LiteEntitySystem/Internal/EntityClassData.cs | 1 + LiteEntitySystem/Internal/EntityFieldInfo.cs | 32 ++--- LiteEntitySystem/Internal/InternalEntity.cs | 9 +- LiteEntitySystem/Internal/ServerStateData.cs | 1 + .../Internal/ValueTypeProcessor.cs | 2 + LiteEntitySystem/ServerEntityManager.cs | 2 +- LiteEntitySystem/Utils.cs | 3 +- 10 files changed, 86 insertions(+), 100 deletions(-) diff --git a/LiteEntitySystem/ClientEntityManager.cs b/LiteEntitySystem/ClientEntityManager.cs index 7de97dc..b805307 100644 --- a/LiteEntitySystem/ClientEntityManager.cs +++ b/LiteEntitySystem/ClientEntityManager.cs @@ -17,7 +17,7 @@ public sealed class ClientEntityManager : EntityManager /// Current interpolated server tick /// public ushort ServerTick { get; private set; } - + /// /// Current rollback tick (valid only in Rollback state) /// @@ -47,12 +47,12 @@ public sealed class ClientEntityManager : EntityManager /// Our local player /// public NetPlayer LocalPlayer => _localPlayer; - + /// /// Stored input commands count for prediction correction /// public int StoredCommands => _storedInputHeaders.Count; - + /// /// Player tick processed by server /// @@ -62,7 +62,7 @@ public sealed class ClientEntityManager : EntityManager /// Last received player tick by server /// public ushort LastReceivedTick => _stateA?.LastReceivedTick ?? 0; - + /// /// Inputs count in server input buffer /// @@ -72,7 +72,7 @@ public sealed class ClientEntityManager : EntityManager /// Send rate of server /// public ServerSendRate ServerSendRate => _serverSendRate; - + /// /// States count in interpolation buffer /// @@ -82,7 +82,7 @@ public sealed class ClientEntityManager : EntityManager /// Total states time in interpolation buffer /// public float LerpBufferTimeLength => _readyStates.Count * DeltaTimeF * (int)_serverSendRate; - + /// /// Current state size in bytes /// @@ -107,8 +107,8 @@ 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 /// Buffer automatically decreases to Jitter time + PreferredBufferTimeHighest @@ -119,10 +119,11 @@ public sealed class ClientEntityManager : EntityManager /// Entities that waiting for remove /// public int PendingToRemoveEntites => _entitiesToRemoveCount; - + private const float TimeSpeedChangeFadeTime = 0.1f; private const float MaxJitter = 0.2f; - + private const float MinJitter = 0.001f; + /// /// Maximum stored inputs count /// @@ -130,12 +131,12 @@ public sealed class ClientEntityManager : EntityManager //predicted entities that should use rollback private readonly AVLTree _predictedEntities = new(); - + private readonly AbstractNetPeer _netPeer; 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<(ushort tick, EntityLogic entity)> _spawnPredictedEntities = new (); private readonly byte[] _sendBuffer = new byte[NetConstants.MaxPacketSize]; private readonly HashSet _changedEntities = new(); private readonly CircularBuffer _storedInputHeaders = new(InputBufferSize); @@ -192,7 +193,7 @@ public void Execute(ServerStateData state) private float _jitterPrevTime; private float _jitterMiddle; private float _jitterSum; - + //local player private NetPlayer _localPlayer; @@ -219,15 +220,15 @@ public T GetPlayerController() where T : HumanControllerLogic /// Header byte that will be used for packets (to distinguish entity system packets) /// Maximum size of lag compensation history in ticks public ClientEntityManager( - EntityTypesMap typesMap, - AbstractNetPeer netPeer, - byte headerByte, + EntityTypesMap typesMap, + AbstractNetPeer netPeer, + byte headerByte, MaxHistorySize maxHistorySize = MaxHistorySize.Size32) : base(typesMap, NetworkMode.Client, headerByte, maxHistorySize) { _netPeer = netPeer; _sendBuffer[0] = headerByte; _sendBuffer[1] = InternalPackets.ClientInput; - + for (int i = 0; i < MaxSavedStateDiff; i++) _statesPool.Enqueue(new ServerStateData()); } @@ -238,7 +239,7 @@ public override void Reset() _localIdQueue.Reset(); _entitiesToRemoveCount = 0; } - + /// /// Add local entity that will be not synchronized /// @@ -251,7 +252,7 @@ internal T AddLocalEntity(EntityLogic parent, Action initMethod) where T : Logger.LogError("Max local entities count reached"); return null; } - + var entity = AddEntity(new EntityParams( _localIdQueue.GetNewId(), new EntityDataHeader( @@ -260,7 +261,7 @@ internal T AddLocalEntity(EntityLogic parent, Action initMethod) where T : 0), this, ClassDataDict[EntityClassInfo.ClassId].AllocateDataCache())); - + //Logger.Log($"AddPredicted, tick: {_tick}, rb: {InRollBackState}, id: {entity.Id}"); entity.InternalOwnerId.Value = parent.InternalOwnerId; @@ -268,7 +269,7 @@ internal T AddLocalEntity(EntityLogic parent, Action initMethod) where T : initMethod(entity); ConstructEntity(entity); _spawnPredictedEntities.Enqueue((_tick, entity)); - + return entity; } @@ -276,7 +277,7 @@ internal EntityLogic FindEntityByPredictedId(ushort tick, ushort parentId, ushor { foreach (var predictedEntity in _spawnPredictedEntities) { - if (predictedEntity.tick == tick && + if (predictedEntity.tick == tick && predictedEntity.entity.ParentId.Id == parentId && predictedEntity.entity.PredictedId == predictedId) return predictedEntity.entity; @@ -303,7 +304,7 @@ protected override unsafe void OnAliveEntityAdded(InternalEntity entity) } } } - + /// Read incoming data /// Incoming data including header byte /// Deserialization result @@ -378,7 +379,7 @@ public unsafe DeserializeResult Deserialize(ReadOnlySpan inData) //sample jitter float currentJitterTimer = _jitterTimer.ElapsedMilliseconds / 1000f; ref float jitterSample = ref _jitterSamples[_jitterSampleIdx]; - + _jitterSum -= jitterSample; jitterSample = Math.Abs(currentJitterTimer - _jitterPrevTime); _jitterSum += jitterSample; @@ -436,12 +437,12 @@ private bool PreloadNextState() return true; if (_readyStates.Count == 0) return false; - + //get max and middle jitter _jitterMiddle = _jitterSum / _jitterSamples.Length; if (_jitterMiddle > NetworkJitter) NetworkJitter = _jitterMiddle; - + _stateB = _readyStates.ExtractMin(); _stateB.Preload(EntitiesDict); //Logger.Log($"Preload A: {_stateA.Tick}, B: {_stateB.Tick}"); @@ -449,12 +450,12 @@ 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; - _lerpTime *= 1 - GetSpeedMultiplier(LerpBufferTimeLength) * TimeSpeedChangeCoef; + _lerpTime *= 1 - GetSpeedMultiplier(LerpBufferTimeLength)*TimeSpeedChangeCoef; //tune game prediction and input generation speed SpeedMultiplier = GetSpeedMultiplier(_stateB.BufferedInputsCount * DeltaTimeF); @@ -536,7 +537,7 @@ private unsafe void GoToNextState() { ref var classData = ref ClassDataDict[entity.ClassId]; var rollbackFields = classData.GetRollbackFields(entity.IsLocalControlled); - if (rollbackFields == null || rollbackFields.Length == 0) + if(rollbackFields == null || rollbackFields.Length == 0) continue; entity.OnBeforeRollback(); @@ -545,20 +546,11 @@ private unsafe void GoToNextState() for (int i = 0; i < rollbackFields.Length; i++) { ref var field = ref rollbackFields[i]; - if (field.FieldType == FieldType.SyncableSyncVar) - { - field.TypeProcessor.SetFrom(entity, field.Offsets, predictedData + field.PredictedOffset); - } - else - { - field.TypeProcessor.SetFrom(entity, field.Offsets, predictedData + field.PredictedOffset); - } + field.TypeProcessor.SetFrom(entity, field.Offsets, predictedData + field.PredictedOffset); } } for (int i = 0; i < classData.SyncableFields.Length; i++) - { Utils.GetSyncableField(entity, classData.SyncableFields[i].Offsets).OnRollback(); - } entity.OnRollback(); } @@ -585,11 +577,11 @@ private unsafe void GoToNextState() //update local interpolated position foreach (var entity in AliveEntities) { - if (entity.IsLocal || !entity.IsLocalControlled) + if(entity.IsLocal || !entity.IsLocalControlled) continue; - + ref var classData = ref ClassDataDict[entity.ClassId]; - for (int i = 0; i < classData.InterpolatedCount; i++) + for(int i = 0; i < classData.InterpolatedCount; i++) { fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(entity)) { @@ -604,11 +596,11 @@ internal override void OnEntityDestroyed(InternalEntity e) { if (!e.IsLocal) { - if (e.IsLocalControlled && e is EntityLogic eLogic) + if(e.IsLocalControlled && e is EntityLogic eLogic) RemoveOwned(eLogic); Utils.AddToArrayDynamic(ref _entitiesToRemove, ref _entitiesToRemoveCount, e); } - + base.OnEntityDestroyed(e); } @@ -677,7 +669,7 @@ protected override unsafe void OnLogicTick() prevDataPtr = classData.ClientInterpolatedPrevData(entity)) { RefMagic.CopyBlock(prevDataPtr, currentDataPtr, (uint)classData.InterpolatedFieldsSize); - for (int i = 0; i < classData.InterpolatedCount; i++) + for(int i = 0; i < classData.InterpolatedCount; i++) { ref var field = ref classData.Fields[i]; field.TypeProcessor.WriteTo(entity, field.Offsets, currentDataPtr + field.FixedOffset); @@ -693,7 +685,11 @@ protected override unsafe void OnLogicTick() } if (NetworkJitter > _jitterMiddle) + { NetworkJitter -= DeltaTimeF * 0.1f; + if (NetworkJitter < MinJitter) + NetworkJitter = MinJitter; + } } /// @@ -704,26 +700,26 @@ public override unsafe void Update() //skip update until receive first sync and tickrate if (Tickrate == 0) return; - + //logic update ushort prevTick = _tick; - + base.Update(); - + //send buffered input if (_tick != prevTick) SendBufferedInput(); - + if (PreloadNextState()) { _timer += VisualDeltaTime; - while (_timer >= _lerpTime) + while(_timer >= _lerpTime) { GoToNextState(); if (!PreloadNextState()) break; } - + if (_stateB != null) { _logicLerpMsec = (float)(_timer/_lerpTime); @@ -737,11 +733,11 @@ public override unsafe void Update() { 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++) + for(int i = 0; i < classData.InterpolatedCount; i++) { ref var field = ref classData.Fields[i]; field.TypeProcessor.SetInterpolation( @@ -753,7 +749,7 @@ public override unsafe void Update() } } } - + //local only and UpdateOnClient foreach (var entity in AliveEntities) { @@ -770,16 +766,16 @@ private unsafe void SendBufferedInput() *(ushort*)(sendBuffer + 2) = Tick; *(InputPacketHeader*)(sendBuffer + 4) = new InputPacketHeader { - LerpMsec = _logicLerpMsec, - StateA = _stateA.Tick, + LerpMsec = _logicLerpMsec, + StateA = _stateA.Tick, StateB = RawTargetServerTick }; - _netPeer.SendUnreliable(new ReadOnlySpan(sendBuffer, 4 + InputPacketHeader.Size)); + _netPeer.SendUnreliable(new ReadOnlySpan(sendBuffer, 4+InputPacketHeader.Size)); _netPeer.TriggerSend(); } return; } - + //pack tick first int offset = 4; int maxSinglePacketSize = _netPeer.GetMaxUnreliablePacketSize(); @@ -796,11 +792,11 @@ private unsafe void SendBufferedInput() Logger.LogError($"Input data from controllers is more than MTU: {maxSinglePacketSize}"); return; } - + fixed (byte* sendBuffer = _sendBuffer) { //Logger.Log($"SendingCommands start {_tick}"); - for (int i = 0; i < _storedInputHeaders.Count; i++) + for(int i = 0; i < _storedInputHeaders.Count; i++) { if (Utils.SequenceDiff(currentTick, _lastReceivedInputTick) <= 0) { @@ -848,7 +844,7 @@ internal void AddOwned(EntityLogic entity) if (flags.HasFlagFast(EntityFlags.Updateable) && !flags.HasFlagFast(EntityFlags.UpdateOnClient)) AliveEntities.Add(entity); } - + internal void RemoveOwned(EntityLogic entity) { var flags = entity.ClassData.Flags; @@ -919,7 +915,7 @@ private unsafe void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData) field.TypeProcessor.WriteTo(e, field.Offsets, prevDataPtr + field.FixedOffset); } } - e.Update(); + e.SafeUpdate(); for (int i = 0; i < classData.InterpolatedCount; i++) { fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(e)) @@ -931,7 +927,7 @@ private unsafe void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData) } else { - e.Update(); + e.SafeUpdate(); } } } diff --git a/LiteEntitySystem/ILPart/RefMagic.dll b/LiteEntitySystem/ILPart/RefMagic.dll index 94bfaae8c5373e19f529a5e2d2a64bd24c7796f8..26d63867cdef9b130123fe0a2221a50a8061f67c 100644 GIT binary patch delta 36 scmZpWX^@%F!R(|Lvawr`LqI?CpE3X9EVsqtA(LaKpZ40kh9iU(0Op?!5dZ)H delta 36 scmZpWX^@%F!F=at(8g{-4uL*}6^{-m#QH6goZ&92YW8XK8jcWF01vDVKL7v# diff --git a/LiteEntitySystem/ILPart/RefMagic.il b/LiteEntitySystem/ILPart/RefMagic.il index 4b71ef3..e5f2529 100644 --- a/LiteEntitySystem/ILPart/RefMagic.il +++ b/LiteEntitySystem/ILPart/RefMagic.il @@ -60,8 +60,8 @@ } .method public hidebysig static void SyncVarSetDirect< - valuetype .ctor (class System.ValueType modreq ([netstandard]System.Runtime.InteropServices.UnmanagedType)) T, - valuetype .ctor (class LiteEntitySystem.Internal.ISyncVar`1, class System.ValueType) TSyncVar> + 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 @@ -78,8 +78,8 @@ } .method public hidebysig static void SyncVarSetDirectAndStorePrev< - valuetype .ctor (class System.ValueType modreq([netstandard]System.Runtime.InteropServices.UnmanagedType)) T, - valuetype .ctor (class LiteEntitySystem.Internal.ISyncVar`1, System.ValueType) TSyncVar> + 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 @@ -97,8 +97,8 @@ } .method public hidebysig static bool SyncVarSetFromAndSync< - valuetype .ctor (class System.ValueType modreq([netstandard]System.Runtime.InteropServices.UnmanagedType)) T, - valuetype .ctor (class LiteEntitySystem.Internal.ISyncVar`1, System.ValueType) TSyncVar> + 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 diff --git a/LiteEntitySystem/Internal/EntityClassData.cs b/LiteEntitySystem/Internal/EntityClassData.cs index 73b1180..fc530ec 100644 --- a/LiteEntitySystem/Internal/EntityClassData.cs +++ b/LiteEntitySystem/Internal/EntityClassData.cs @@ -312,6 +312,7 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp // nested SyncableField's SyncVar<…> ef = new EntityFieldInfo(name, processor, offsetMap.ToArray(), syncVarFlags, FieldType.SyncableSyncVar); GD.Print($"Registered syncable sync var field: {name} with offset {string.Join(",", offsetMap)}"); + fieldSize = ef.IntSize; } else { diff --git a/LiteEntitySystem/Internal/EntityFieldInfo.cs b/LiteEntitySystem/Internal/EntityFieldInfo.cs index 4187d3a..953d310 100644 --- a/LiteEntitySystem/Internal/EntityFieldInfo.cs +++ b/LiteEntitySystem/Internal/EntityFieldInfo.cs @@ -55,29 +55,23 @@ public unsafe bool ReadField( { if (IsPredicted) RefMagic.CopyBlock(predictedData + PredictedOffset, rawData, Size); - if (FieldType == FieldType.SyncableSyncVar) + + if (Flags.HasFlagFast(SyncFlags.Interpolated)) { - TypeProcessor.SetFrom(entity, Offsets, rawData); + 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, Offsets, rawData)) + return true; //create sync call } 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, Offsets, rawData)) - return true; //create sync call - } - else - { - TypeProcessor.SetFrom(entity, Offsets, rawData); - } + TypeProcessor.SetFrom(entity, Offsets, rawData); } return false; diff --git a/LiteEntitySystem/Internal/InternalEntity.cs b/LiteEntitySystem/Internal/InternalEntity.cs index b882ca3..90dff5e 100644 --- a/LiteEntitySystem/Internal/InternalEntity.cs +++ b/LiteEntitySystem/Internal/InternalEntity.cs @@ -229,14 +229,7 @@ internal void RegisterRpcInternal() ref var syncFieldInfo = ref classData.SyncableFields[i]; // Traverse the chain of nested SyncableFields via offset map - object current = this; - foreach (var relOffset in syncFieldInfo.Offsets) - { - current = RefMagic.GetFieldValue(current, relOffset); - if (current == null) - throw new NullReferenceException($"Nested SyncableField at offset {relOffset} is null"); - } - var syncField = (SyncableField)current; + var syncField = Utils.GetSyncableField(this, syncFieldInfo.Offsets); syncField.Init(this, syncFieldInfo.Flags); diff --git a/LiteEntitySystem/Internal/ServerStateData.cs b/LiteEntitySystem/Internal/ServerStateData.cs index 5b238fd..7a4527b 100644 --- a/LiteEntitySystem/Internal/ServerStateData.cs +++ b/LiteEntitySystem/Internal/ServerStateData.cs @@ -211,6 +211,7 @@ public unsafe void RemoteInterpolation(InternalEntity[] entityDict, float logicL continue; //Logger.Log($"Read pending interpolation: {entity.Id}"); + PreloadInterpolation(entity, _nullEntitiesData[i].Offset); //remove diff --git a/LiteEntitySystem/Internal/ValueTypeProcessor.cs b/LiteEntitySystem/Internal/ValueTypeProcessor.cs index 07d6af7..7fb16ad 100644 --- a/LiteEntitySystem/Internal/ValueTypeProcessor.cs +++ b/LiteEntitySystem/Internal/ValueTypeProcessor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Godot; namespace LiteEntitySystem.Internal { @@ -124,6 +125,7 @@ internal override unsafe void SetInterpolation(InternalBaseClass obj, int[] offs { var owner = Utils.GetSyncVarOwner(obj, offsetMap); var offset = offsetMap[offsetMap.Length - 1]; + RefMagic.SyncVarSetDirect>(owner, offset, Utils.Lerp(*(float*)prev, *(float*)current, fTimer)); } diff --git a/LiteEntitySystem/ServerEntityManager.cs b/LiteEntitySystem/ServerEntityManager.cs index dc927a4..70616ef 100644 --- a/LiteEntitySystem/ServerEntityManager.cs +++ b/LiteEntitySystem/ServerEntityManager.cs @@ -896,4 +896,4 @@ entity is AiControllerLogic || _maxDataSize += rpc.TotalSize; } } -} +} \ No newline at end of file diff --git a/LiteEntitySystem/Utils.cs b/LiteEntitySystem/Utils.cs index 1003f87..48f4329 100644 --- a/LiteEntitySystem/Utils.cs +++ b/LiteEntitySystem/Utils.cs @@ -330,9 +330,8 @@ internal static SyncVar GetSyncVar(InternalBaseClass baseClass, int[] offs if (syncable == null) throw new NullReferenceException($"SyncableField at offset {offsets[i]} is null"); } - var a = RefMagic.GetFieldValue>(syncable, offsets[offsets.Length - 1]); - return a; + return RefMagic.GetFieldValue>(syncable, offsets[offsets.Length - 1]);; } static Utils()