diff --git a/LiteEntitySystem/ClientEntityManager.cs b/LiteEntitySystem/ClientEntityManager.cs index 5ba4855..b805307 100644 --- a/LiteEntitySystem/ClientEntityManager.cs +++ b/LiteEntitySystem/ClientEntityManager.cs @@ -107,7 +107,7 @@ public sealed class ClientEntityManager : EntityManager /// Preferred input and incoming states buffer length in seconds lowest bound /// Buffer automatically increases to Jitter time + PreferredBufferTimeLowest /// - public float PreferredBufferTimeLowest = 0.01f; + public float PreferredBufferTimeLowest = 0.025f; /// /// Preferred input and incoming states buffer length in seconds lowest bound @@ -122,6 +122,7 @@ public sealed class ClientEntityManager : EntityManager private const float TimeSpeedChangeFadeTime = 0.1f; private const float MaxJitter = 0.2f; + private const float MinJitter = 0.001f; /// /// Maximum stored inputs count @@ -298,8 +299,8 @@ 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); } } } @@ -449,8 +450,8 @@ private bool PreloadNextState() //limit jitter for pause scenarios if (NetworkJitter > MaxJitter) NetworkJitter = MaxJitter; - float lowestBound = NetworkJitter + PreferredBufferTimeLowest; - float upperBound = NetworkJitter + PreferredBufferTimeHighest; + float lowestBound = NetworkJitter * 1.5f + PreferredBufferTimeLowest; + float upperBound = NetworkJitter * 1.5f + PreferredBufferTimeHighest; //tune buffer playing speed _lerpTime = Utils.SequenceDiff(_stateB.Tick, _stateA.Tick) * DeltaTimeF; @@ -545,19 +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) - { - var syncableField = RefMagic.GetFieldValue(entity, field.Offset); - field.TypeProcessor.SetFrom(syncableField, field.SyncableSyncVarOffset, 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.GetFieldValue(entity, classData.SyncableFields[i].Offset).OnRollback(); + Utils.GetSyncableField(entity, classData.SyncableFields[i].Offsets).OnRollback(); entity.OnRollback(); } @@ -593,7 +586,7 @@ private unsafe void GoToNextState() 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); } } } @@ -634,7 +627,7 @@ protected override unsafe void OnLogicTick() for (int i = 0; i < classData.InterpolatedCount; i++) { ref var field = ref classData.Fields[i]; - field.TypeProcessor.SetFrom(entity, field.Offset, currentDataPtr + field.FixedOffset); + field.TypeProcessor.SetFrom(entity, field.Offsets, currentDataPtr + field.FixedOffset); } } } @@ -679,7 +672,7 @@ protected override unsafe void OnLogicTick() for(int i = 0; i < classData.InterpolatedCount; i++) { 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); } } } @@ -692,7 +685,11 @@ protected override unsafe void OnLogicTick() } if (NetworkJitter > _jitterMiddle) + { NetworkJitter -= DeltaTimeF * 0.1f; + if (NetworkJitter < MinJitter) + NetworkJitter = MinJitter; + } } /// @@ -745,7 +742,7 @@ public override unsafe void Update() ref var field = ref classData.Fields[i]; field.TypeProcessor.SetInterpolation( entity, - field.Offset, + field.Offsets, prevDataPtr + field.FixedOffset, currentDataPtr + field.FixedOffset, localLerpT); @@ -915,22 +912,22 @@ private unsafe void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData) fixed (byte* prevDataPtr = classData.ClientInterpolatedPrevData(e)) { ref var field = ref classData.Fields[i]; - field.TypeProcessor.WriteTo(e, field.Offset, prevDataPtr + field.FixedOffset); + 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)) { ref var field = ref classData.Fields[i]; - field.TypeProcessor.WriteTo(e, field.Offset, currentDataPtr + field.FixedOffset); + field.TypeProcessor.WriteTo(e, field.Offsets, currentDataPtr + field.FixedOffset); } } } else { - e.Update(); + e.SafeUpdate(); } } } diff --git a/LiteEntitySystem/EntityManager.cs b/LiteEntitySystem/EntityManager.cs index 555259a..5632bd5 100644 --- a/LiteEntitySystem/EntityManager.cs +++ b/LiteEntitySystem/EntityManager.cs @@ -323,7 +323,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/Extensions/NetDataExtensions.cs b/LiteEntitySystem/Extensions/NetDataExtensions.cs index 56bdd2e..a583e7f 100644 --- a/LiteEntitySystem/Extensions/NetDataExtensions.cs +++ b/LiteEntitySystem/Extensions/NetDataExtensions.cs @@ -8,6 +8,38 @@ namespace LiteNetLib.Utils { public static class NetDataExtensions { + /// + /// Writes the raw bytes of any unmanaged T. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void PutUnmanaged(this NetDataWriter writer, T value) + where T : unmanaged + { + int size = sizeof(T); + var buf = new byte[size]; + fixed (byte* dst = buf) + { + Buffer.MemoryCopy(&value, dst, size, size); + } + writer.Put(buf, 0, size); + } + + /// + /// Reads sizeof(T) bytes into a temporary buffer and reinterprets them as T. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe T GetUnmanaged(this NetDataReader reader) + where T : unmanaged + { + int size = sizeof(T); + var buf = new byte[size]; + reader.GetBytes(buf, 0, size); + fixed (byte* src = buf) + { + return *(T*)src; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void Put(this NetDataWriter writer, T e) where T : unmanaged, Enum { @@ -19,7 +51,7 @@ public static unsafe void Put(this NetDataWriter writer, T e) where T : unman case 8: writer.Put(*(long*)&e); break; } } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void Get(this NetDataReader reader, out T result) where T : unmanaged, Enum { @@ -33,7 +65,7 @@ public static unsafe void Get(this NetDataReader reader, out T result) where } result = e; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe T Get(this NetDataReader reader) where T : unmanaged, Enum { @@ -47,7 +79,7 @@ public static unsafe T Get(this NetDataReader reader) where T : unmanaged, En } return e; } - + #if UNITY_2021_2_OR_NEWER [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Put(this NetDataWriter writer, Vector3 v) diff --git a/LiteEntitySystem/ILPart/RefMagic.dll b/LiteEntitySystem/ILPart/RefMagic.dll index 92da363..26d6386 100644 Binary files a/LiteEntitySystem/ILPart/RefMagic.dll and b/LiteEntitySystem/ILPart/RefMagic.dll differ diff --git a/LiteEntitySystem/ILPart/RefMagic.il b/LiteEntitySystem/ILPart/RefMagic.il index 4a7183e..e5f2529 100644 --- a/LiteEntitySystem/ILPart/RefMagic.il +++ b/LiteEntitySystem/ILPart/RefMagic.il @@ -27,7 +27,7 @@ .module RefMagic.dll .class interface public abstract auto ansi beforefieldinit LiteEntitySystem.Internal.ISyncVar`1< - valuetype .ctor (class System.ValueType modreq ([netstandard]System.Runtime.InteropServices.UnmanagedType)) T> + valuetype .ctor (class [netstandard]System.ValueType modreq([netstandard]System.Runtime.InteropServices.UnmanagedType)) T> { .param type T .custom instance void [netstandard]System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = (01 00 00 00 ) @@ -36,7 +36,7 @@ .method public hidebysig virtual newslot abstract instance bool SvSetFromAndSync(!T& 'value') cil managed {} } -.class public abstract auto ansi sealed beforefieldinit LiteEntitySystem.Internal.RefMagic extends object +.class public abstract auto ansi sealed beforefieldinit LiteEntitySystem.Internal.RefMagic extends [netstandard]System.Object { .method public hidebysig static !!T GetFieldValue(object obj, int32 offs) cil managed aggressiveinlining { @@ -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 a5d32b5..fc530ec 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 Godot; 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 = [-1]; 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 string ClassEnumName; @@ -76,33 +111,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; } } @@ -110,8 +145,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]) { @@ -119,8 +154,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, @@ -140,12 +175,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) @@ -167,6 +202,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(); @@ -187,110 +232,151 @@ 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; + + var fieldSize = processor.Size; + + // choose the correct constructor: + EntityFieldInfo ef; + if (ctx.DeclaringType.IsSubclassOf(SyncableFieldType)) + { + // 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 + { + // top-level field on the entity itself + ef = new EntityFieldInfo(name, processor, offsetMap.ToArray(), syncVarFlags, FieldType.SyncVar); + GD.Print($"Registered field: {name} with offsets {string.Join(",", offsetMap)}"); + } + if (syncFlags.HasFlagFast(SyncFlags.Interpolated) && !ft.IsEnum) { InterpolatedFieldsSize += fieldSize; InterpolatedCount++; } - var fieldInfo = new EntityFieldInfo($"{baseType.Name}-{field.Name}", valueTypeProcessor, offset, syncVarFlags); + if (syncFlags.HasFlagFast(SyncFlags.LagCompensated)) { - lagCompensatedFields.Add(fieldInfo); + lagCompensatedFields.Add(ef); LagCompensatedSize += fieldSize; } - if (fieldInfo.IsPredicted) + if (ef.IsPredicted) PredictedSize += fieldSize; - fields.Add(fieldInfo); + fields.Add(ef); FixedFieldsSize += fieldSize; } + // --- 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) => { @@ -301,7 +387,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; @@ -317,7 +403,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 @@ -325,11 +411,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 c43a406..953d310 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,44 +12,27 @@ 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; public readonly SyncFlags Flags; public readonly bool IsPredicted; - + public MethodCallDelegate OnSync; public int FixedOffset; public int PredictedOffset; - //for value type - public EntityFieldInfo(string name, ValueTypeProcessor valueTypeProcessor, int offset, SyncVarFlags flags) : - this(name, valueTypeProcessor, offset, -1, flags, FieldType.SyncVar) - { - - } - - //For syncable syncvar - public EntityFieldInfo(string name, ValueTypeProcessor valueTypeProcessor, int offset, int syncableSyncVarOffset, SyncVarFlags flags) : - this(name, valueTypeProcessor, offset, syncableSyncVarOffset, flags, FieldType.SyncableSyncVar) - { - - } - - private EntityFieldInfo( + public EntityFieldInfo( string name, ValueTypeProcessor valueTypeProcessor, - int offset, - int syncableSyncVarOffset, + int[] offsets, SyncVarFlags flags, FieldType fieldType) { Name = name; TypeProcessor = valueTypeProcessor; - SyncableSyncVarOffset = syncableSyncVarOffset; - Offset = offset; + Offsets = offsets; Size = (uint)TypeProcessor.Size; IntSize = TypeProcessor.Size; FieldType = fieldType; @@ -62,38 +47,31 @@ private EntityFieldInfo( public unsafe bool ReadField( - InternalEntity entity, - byte* rawData, - byte* predictedData, - byte* nextInterpDataPtr, + InternalEntity entity, + byte* rawData, + byte* predictedData, + byte* nextInterpDataPtr, byte* prevInterpDataPtr) { if (IsPredicted) RefMagic.CopyBlock(predictedData + PredictedOffset, rawData, Size); - if (FieldType == FieldType.SyncableSyncVar) + + 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) { - var syncableField = RefMagic.GetFieldValue(entity, Offset); - TypeProcessor.SetFrom(syncableField, SyncableSyncVarOffset, rawData); + 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, Offset, rawData)) - return true; //create sync call - } - else - { - TypeProcessor.SetFrom(entity, Offset, rawData); - } + TypeProcessor.SetFrom(entity, Offsets, rawData); } return false; diff --git a/LiteEntitySystem/Internal/InternalEntity.cs b/LiteEntitySystem/Internal/InternalEntity.cs index 21bf3f6..90dff5e 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 /// @@ -65,22 +66,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 @@ -97,7 +98,7 @@ public abstract class InternalEntity : InternalBaseClass, IComparable public bool IsSingleton => ClassData.IsSingleton; - + internal ref EntityClassData ClassData => ref EntityManager.ClassDataDict[ClassId]; /// @@ -146,7 +147,7 @@ internal void SafeUpdate() catch (Exception e) { Logger.LogError($"Exception in entity({Id}) update:\n{e}"); - } + } } /// @@ -155,13 +156,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() { - + } /// @@ -169,7 +170,7 @@ protected internal virtual void OnBeforeRollback() /// protected internal virtual void OnRollback() { - + } /// @@ -177,7 +178,7 @@ protected internal virtual void OnRollback() /// protected internal virtual void VisualUpdate() { - + } /// @@ -199,25 +200,19 @@ protected internal virtual void OnLateConstructed() 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]; - if (field.FieldType == FieldType.SyncVar) - { - field.TypeProcessor.InitSyncVar(this, field.Offset, onChangeTarget, (ushort)i); - } - else - { - var syncableField = RefMagic.GetFieldValue(this, field.Offset); - field.TypeProcessor.InitSyncVar(syncableField, field.SyncableSyncVarOffset, onChangeTarget, (ushort)i); - } + + field.TypeProcessor.InitSyncVar(this, field.Offsets, onChangeTarget, (ushort)i); } - + + // Register top-level RPCs if not yet cached List rpcCache = null; - if(classData.RemoteCallsClient == null) + if (classData.RemoteCallsClient == null) { rpcCache = new List(); //place reserved rpcs @@ -225,23 +220,32 @@ internal void RegisterRpcInternal() 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 id for later sync calls for (int i = 0; i < classData.SyncableFields.Length; i++) { ref var syncFieldInfo = ref classData.SyncableFields[i]; - var syncField = RefMagic.GetFieldValue(this, syncFieldInfo.Offset); + + // Traverse the chain of nested SyncableFields via offset map + var syncField = Utils.GetSyncableField(this, syncFieldInfo.Offsets); + + syncField.Init(this, syncFieldInfo.Flags); - if (rpcCache == null) //classData.RemoteCallsClient != null + if (classData.RemoteCallsClient != null) //rpcCache == null { + // Use cached offsets syncField.RPCOffset = syncFieldInfo.RPCOffset; } else { + // New registration syncField.RPCOffset = (ushort)rpcCache.Count; syncFieldInfo.RPCOffset = syncField.RPCOffset; - var syncablesRegistrator = new SyncableRPCRegistrator(syncFieldInfo.Offset, rpcCache); + + // Use AbsoluteOffset to register RPCs on this nested SyncableField + var syncablesRegistrator = new SyncableRPCRegistrator(syncFieldInfo.Offsets, rpcCache); syncField.RegisterRPC(ref syncablesRegistrator); } } @@ -250,6 +254,7 @@ internal void RegisterRpcInternal() + /// /// Method for registering RPCs and OnChange notifications /// @@ -258,7 +263,7 @@ protected virtual void RegisterRPC(ref RPCRegistrator r) { } - + protected void ExecuteRPC(in RemoteCall rpc) { if (IsRemoved) diff --git a/LiteEntitySystem/Internal/RemoteCallPacket.cs b/LiteEntitySystem/Internal/RemoteCallPacket.cs index 8ab74a7..25ba968 100644 --- a/LiteEntitySystem/Internal/RemoteCallPacket.cs +++ b/LiteEntitySystem/Internal/RemoteCallPacket.cs @@ -31,7 +31,7 @@ internal sealed class RemoteCallPacket public static void InitReservedRPCs(List rpcCache) { for(int i = 0; i < ReserverdRPCsCount; i++) - rpcCache.Add(new RpcFieldInfo(-1, null)); + rpcCache.Add(new RpcFieldInfo(new [] { -1 }, null)); } public bool AllowToSendForPlayer(byte forPlayerId, byte entityOwnerId) diff --git a/LiteEntitySystem/Internal/ServerStateData.cs b/LiteEntitySystem/Internal/ServerStateData.cs index 579d8b3..7a4527b 100644 --- a/LiteEntitySystem/Internal/ServerStateData.cs +++ b/LiteEntitySystem/Internal/ServerStateData.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using K4os.Compression.LZ4; namespace LiteEntitySystem.Internal @@ -8,7 +9,7 @@ namespace LiteEntitySystem.Internal 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; @@ -16,7 +17,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; @@ -225,7 +226,7 @@ public unsafe void RemoteInterpolation(InternalEntity[] entityDict, float logicL fixed (byte* initialDataPtr = interpolatedCache.Entity.ClassData.ClientInterpolatedNextData(interpolatedCache.Entity), nextDataPtr = Data) interpolatedCache.TypeProcessor.SetInterpolation( interpolatedCache.Entity, - interpolatedCache.FieldOffset, + interpolatedCache.FieldOffsets, initialDataPtr + interpolatedCache.FieldFixedOffset, nextDataPtr + interpolatedCache.StateReaderOffset, logicLerpMsec); @@ -288,7 +289,7 @@ public unsafe void ExecuteRpcs(ClientEntityManager entityManager, ushort minimal entityManager.CurrentRPCTick = header.Tick; var rpcFieldInfo = entityManager.ClassDataDict[entity.ClassId].RemoteCallsClient[header.Id]; - if (rpcFieldInfo.SyncableOffset == -1) + if (rpcFieldInfo.SyncableOffsets.SequenceEqual([-1])) { try { @@ -317,7 +318,7 @@ public unsafe void ExecuteRpcs(ClientEntityManager entityManager, ushort minimal } else { - var syncableField = RefMagic.GetFieldValue(entity, rpcFieldInfo.SyncableOffset); + var syncableField = Utils.GetSyncableField(entity, rpcFieldInfo.SyncableOffsets); if (_syncablesSet.Add(syncableField)) { syncableField.BeforeReadRPC(); diff --git a/LiteEntitySystem/Internal/StateSerializer.cs b/LiteEntitySystem/Internal/StateSerializer.cs index 6a66192..ed408e1 100644 --- a/LiteEntitySystem/Internal/StateSerializer.cs +++ b/LiteEntitySystem/Internal/StateSerializer.cs @@ -139,8 +139,10 @@ public void MakeConstructedRPC(NetPlayer player) { var syncableFields = _entity.ClassData.SyncableFields; for (int i = 0; i < syncableFields.Length; i++) - RefMagic.GetFieldValue(_entity, syncableFields[i].Offset).OnSyncRequested(); - _entity.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 e348fb3..7fb16ad 100644 --- a/LiteEntitySystem/Internal/ValueTypeProcessor.cs +++ b/LiteEntitySystem/Internal/ValueTypeProcessor.cs @@ -1,111 +1,179 @@ using System; using System.Collections.Generic; +using System.Linq; +using Godot; 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 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 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); + 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[] offsetMap, InternalEntity entity, ushort fieldId) { - var sv = RefMagic.GetFieldValue>(obj, offset); + var owner = Utils.GetSyncVarOwner(obj, offsetMap); + var offset = offsetMap[offsetMap.Length - 1]; + var sv = RefMagic.GetFieldValue>(owner, offset); sv.Init(entity, fieldId); - RefMagic.SetFieldValue(obj, offset, sv); - } - - internal override void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => - RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, *(T*)historyA, out *(T*)tempHistory); - - internal override void SetFrom(InternalBaseClass obj, int offset, byte* data) => - RefMagic.SyncVarSetDirect>(obj, offset, *(T*)data); - - internal override bool SetFromAndSync(InternalBaseClass obj, int offset, byte* data) => - RefMagic.SyncVarSetFromAndSync>(obj, offset, ref *(T*)data); - - internal override void WriteTo(InternalBaseClass obj, int offset, byte* data) => - *(T*)data = RefMagic.GetFieldValue>(obj, offset); - - internal override int GetHashCode(InternalBaseClass obj, int offset) => - RefMagic.GetFieldValue>(obj, offset).GetHashCode(); - - internal override string ToString(InternalBaseClass obj, int offset) => - RefMagic.GetFieldValue>(obj, offset).ToString(); + RefMagic.SetFieldValue(owner, offset, sv); + } + + internal override void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) + { + var owner = Utils.GetSyncVarOwner(obj, offsetMap); + var offset = offsetMap[offsetMap.Length - 1]; + + RefMagic.SyncVarSetDirectAndStorePrev>(owner, offset, *(T*)historyA, out *(T*)tempHistory); + } + + internal override void SetFrom(InternalBaseClass obj, int[] offsetMap, byte* data) + { + var owner = Utils.GetSyncVarOwner(obj, offsetMap); + var offset = offsetMap[offsetMap.Length - 1]; + + RefMagic.SyncVarSetDirect>(owner, offset, *(T*)data); + } + + internal override bool SetFromAndSync(InternalBaseClass obj, int[] offsetMap, byte* data) + { + var owner = Utils.GetSyncVarOwner(obj, offsetMap); + var offset = offsetMap[offsetMap.Length - 1]; + + return RefMagic.SyncVarSetFromAndSync>(owner, offset, ref *(T*)data); + } + + + internal override void WriteTo(InternalBaseClass obj, int[] offsetMap, byte* data) + { + *(T*)data = Utils.GetSyncVar(obj, offsetMap); + } + + internal override int GetHashCode(InternalBaseClass obj, int[] offsetMap) + { + return Utils.GetSyncVar(obj, offsetMap).GetHashCode(); + } + + + internal override string ToString(InternalBaseClass obj, int[] offsetMap) + { + return Utils.GetSyncVar(obj, offsetMap).ToString(); + } } internal class ValueTypeProcessorInt : ValueTypeProcessor { - internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.SyncVarSetDirect>(obj, offset, Utils.Lerp(*(int*)prev, *(int*)current, fTimer)); - - internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => - RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, + internal override unsafe void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) + { + var owner = Utils.GetSyncVarOwner(obj, offsetMap); + var offset = offsetMap[offsetMap.Length - 1]; + RefMagic.SyncVarSetDirect>(owner, offset, Utils.Lerp(*(int*)prev, *(int*)current, fTimer)); + } + + internal override unsafe void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) + { + var owner = Utils.GetSyncVarOwner(obj, offsetMap); + var offset = offsetMap[offsetMap.Length - 1]; + RefMagic.SyncVarSetDirectAndStorePrev>(owner, offset, Utils.Lerp(*(int*)historyA, *(int*)historyB, lerpTime), out *(int*)tempHistory); + } } - + internal class ValueTypeProcessorLong : ValueTypeProcessor { - internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.SyncVarSetDirect>(obj, offset, Utils.Lerp(*(long*)prev, *(long*)current, fTimer)); - - internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => - RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, + internal override unsafe void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) + { + var owner = Utils.GetSyncVarOwner(obj, offsetMap); + var offset = offsetMap[offsetMap.Length - 1]; + RefMagic.SyncVarSetDirect>(owner, offset, Utils.Lerp(*(long*)prev, *(long*)current, fTimer)); + } + + internal override unsafe void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) + { + var owner = Utils.GetSyncVarOwner(obj, offsetMap); + var offset = offsetMap[offsetMap.Length - 1]; + RefMagic.SyncVarSetDirectAndStorePrev>(owner, offset, Utils.Lerp(*(long*)historyA, *(long*)historyB, lerpTime), out *(long*)tempHistory); + } } internal class ValueTypeProcessorFloat : ValueTypeProcessor { - internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.SyncVarSetDirect>(obj, offset, Utils.Lerp(*(float*)prev, *(float*)current, fTimer)); - - internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => - RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, + internal override unsafe void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) + { + var owner = Utils.GetSyncVarOwner(obj, offsetMap); + var offset = offsetMap[offsetMap.Length - 1]; + + RefMagic.SyncVarSetDirect>(owner, offset, Utils.Lerp(*(float*)prev, *(float*)current, fTimer)); + } + + internal override unsafe void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) + { + var owner = Utils.GetSyncVarOwner(obj, offsetMap); + var offset = offsetMap[offsetMap.Length - 1]; + RefMagic.SyncVarSetDirectAndStorePrev>(owner, offset, Utils.Lerp(*(float*)historyA, *(float*)historyB, lerpTime), out *(float*)tempHistory); + } } - + internal class ValueTypeProcessorDouble : ValueTypeProcessor { - internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.SyncVarSetDirect>(obj, offset, Utils.Lerp(*(double*)prev, *(double*)current, fTimer)); - - internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => - RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, + internal override unsafe void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) + { + var owner = Utils.GetSyncVarOwner(obj, offsetMap); + var offset = offsetMap[offsetMap.Length - 1]; + RefMagic.SyncVarSetDirect>(owner, offset, Utils.Lerp(*(double*)prev, *(double*)current, fTimer)); + } + + internal override unsafe void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) + { + var owner = Utils.GetSyncVarOwner(obj, offsetMap); + var offset = offsetMap[offsetMap.Length - 1]; + RefMagic.SyncVarSetDirectAndStorePrev>(owner, offset, Utils.Lerp(*(double*)historyA, *(double*)historyB, lerpTime), out *(double*)tempHistory); + } } internal unsafe class UserTypeProcessor : ValueTypeProcessor where T : unmanaged { private readonly InterpolatorDelegateWithReturn _interpDelegate; - internal override void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) => - RefMagic.SyncVarSetDirect>(obj, offset, _interpDelegate?.Invoke(*(T*)prev, *(T*)current, fTimer) ?? *(T*)prev); + internal override void SetInterpolation(InternalBaseClass obj, int[] offsetMap, byte* prev, byte* current, float fTimer) + { + var owner = Utils.GetSyncVarOwner(obj, offsetMap); + var offset = offsetMap[offsetMap.Length - 1]; + RefMagic.SyncVarSetDirect>(owner, offset, _interpDelegate?.Invoke(*(T*)prev, *(T*)current, fTimer) ?? *(T*)prev); + } - internal override void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) => - RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, + internal override void LoadHistory(InternalBaseClass obj, int[] offsetMap, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) + { + var owner = Utils.GetSyncVarOwner(obj, offsetMap); + var offset = offsetMap[offsetMap.Length - 1]; + RefMagic.SyncVarSetDirectAndStorePrev>(owner, offset, _interpDelegate?.Invoke(*(T*)historyA, *(T*)historyB, lerpTime) ?? *(T*)historyA, out *(T*)tempHistory); + } public UserTypeProcessor(InterpolatorDelegateWithReturn interpolationDelegate) => _interpDelegate = interpolationDelegate; diff --git a/LiteEntitySystem/RPCRegistrator.cs b/LiteEntitySystem/RPCRegistrator.cs index 5e45c9c..3c1bda4 100644 --- a/LiteEntitySystem/RPCRegistrator.cs +++ b/LiteEntitySystem/RPCRegistrator.cs @@ -245,14 +245,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 @@ -283,28 +283,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/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 81ef3c3..48f4329 100644 --- a/LiteEntitySystem/Utils.cs +++ b/LiteEntitySystem/Utils.cs @@ -295,6 +295,45 @@ 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.GetFieldValue(syncable, offsets[j]); + if (syncable == null) + throw new NullReferenceException($"SyncableField at offset {offsets[j]} is null"); + } + + return (SyncableField)syncable; + } + + internal static InternalBaseClass GetSyncVarOwner(InternalBaseClass baseClass, int[] offsets) + { + InternalBaseClass owner = baseClass; + for (int i = 0; i < offsets.Length-1; i++) + { + owner = RefMagic.GetFieldValue(owner, offsets[i]); + if (owner == null) + throw new NullReferenceException($"SyncVar at offset {offsets[i]} is null"); + } + + return owner; + } + + internal static SyncVar GetSyncVar(InternalBaseClass baseClass, int[] offsets) where T : unmanaged + { + InternalBaseClass syncable = baseClass; + for (int i = 0; i < offsets.Length - 1; i++) + { + syncable = RefMagic.GetFieldValue(syncable, offsets[i]); + if (syncable == null) + throw new NullReferenceException($"SyncableField at offset {offsets[i]} is null"); + } + + return RefMagic.GetFieldValue>(syncable, offsets[offsets.Length - 1]);; + } + static Utils() { IsMono = Type.GetType("Mono.Runtime") != null