diff --git a/LiteEntitySystem/EntityManager.cs b/LiteEntitySystem/EntityManager.cs
index cf3cbd8..7fe5479 100644
--- a/LiteEntitySystem/EntityManager.cs
+++ b/LiteEntitySystem/EntityManager.cs
@@ -57,6 +57,14 @@ public enum MaxHistorySize : byte
Size64 = 64,
Size128 = 128
}
+
+ ///
+ /// Helper interface for printing entity syncVar names and values
+ ///
+ public interface IEntitySyncVarInfoPrinter
+ {
+ void PrintFieldInfo(string fieldName, string fieldValue);
+ }
///
/// Base class for client and server manager
@@ -273,6 +281,18 @@ protected EntityManager(EntityTypesMap typesMap, InputProcessor inputProcessor,
///
///
public Type GetEntityTypeFromClassId(ushort classId) => classId >= ClassDataDict.Length ? null : ClassDataDict[classId].Type;
+
+ ///
+ /// Prints names and values of entity syncVars using IEntitySyncVarInfoPrinter
+ ///
+ /// entity to show
+ /// IEntitySyncVarInfoPrinter implementation
+ public void GetEntitySyncVarInfo(InternalEntity entity, IEntitySyncVarInfoPrinter resultPrinter)
+ {
+ ref var classData = ref ClassDataDict[entity.ClassId];
+ foreach (EntityFieldInfo fi in classData.Fields)
+ resultPrinter.PrintFieldInfo(fi.Name, fi.TypeProcessor.ToString(entity, fi.Offset));
+ }
///
/// Remove all entities and reset all counters and timers
diff --git a/LiteEntitySystem/Internal/InternalEntity.cs b/LiteEntitySystem/Internal/InternalEntity.cs
index 3cc31d4..9ea3e56 100644
--- a/LiteEntitySystem/Internal/InternalEntity.cs
+++ b/LiteEntitySystem/Internal/InternalEntity.cs
@@ -1,341 +1,346 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-
-namespace LiteEntitySystem.Internal
-{
- public readonly struct EntityDataHeader
- {
- public readonly ushort Id;
- public readonly ushort ClassId;
- public readonly byte Version;
- public readonly int UpdateOrder;
-
- public EntityDataHeader(ushort id, ushort classId, byte version, int updateOrder)
- {
- Id = id;
- ClassId = classId;
- Version = version;
- UpdateOrder = updateOrder;
- }
- }
-
- public abstract class InternalEntity : InternalBaseClass, IComparable
- {
- [SyncVarFlags(SyncFlags.NeverRollBack)]
- internal SyncVar InternalOwnerId;
-
- internal byte[] IOBuffer;
-
- internal readonly int UpdateOrderNum;
-
- ///
- /// Entity class id
- ///
- public readonly ushort ClassId;
-
- ///
- /// Entity instance id
- ///
- public readonly ushort Id;
-
-
- ///
- /// Entity manager
- ///
- public readonly EntityManager EntityManager;
-
- ///
- /// Is entity on server
- ///
- protected internal bool IsServer => EntityManager.IsServer;
-
- ///
- /// Is entity on server
- ///
- protected internal bool IsClient => EntityManager.IsClient;
-
- ///
- /// Entity version (for id reuse)
- ///
- public readonly byte Version;
-
- internal EntityDataHeader DataHeader => new EntityDataHeader
- (
- Id,
- ClassId,
- Version,
- UpdateOrderNum
- );
-
- [SyncVarFlags(SyncFlags.NeverRollBack)]
- private SyncVar _isDestroyed;
-
- ///
- /// Is entity is destroyed
- ///
- public bool IsDestroyed => _isDestroyed;
-
- ///
- /// Is entity local controlled
- ///
- public bool IsLocalControlled => InternalOwnerId.Value == EntityManager.InternalPlayerId;
-
- ///
- /// Is entity remote controlled
- ///
- 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
- /// Singletons always controlled by server
- ///
- public byte OwnerId => InternalOwnerId.Value;
-
- ///
- /// Is locally created entity
- ///
- public bool IsLocal => Id >= EntityManager.MaxSyncedEntityCount;
-
- internal ref EntityClassData ClassData => ref EntityManager.ClassDataDict[ClassId];
-
- ///
- /// Destroy entity
- ///
- public void Destroy()
- {
- if ((EntityManager.IsClient && !IsLocal) || _isDestroyed)
- return;
- DestroyInternal();
- }
-
- private void OnDestroyChange(bool prevValue)
- {
- if (!prevValue && _isDestroyed)
- {
- _isDestroyed.Value = false;
- DestroyInternal();
- }
- }
-
- ///
- /// Event called on entity destroy
- ///
- protected virtual void OnDestroy()
- {
-
- }
-
- internal virtual void DestroyInternal()
- {
- if (_isDestroyed)
- return;
- _isDestroyed.Value = true;
- EntityManager.OnEntityDestroyed(this);
- OnDestroy();
- }
-
- internal void SafeUpdate()
- {
- try
- {
- Update();
- }
- catch (Exception e)
- {
- Logger.LogError($"Exception in entity({Id}) update:\n{e}");
- }
- }
-
- ///
- /// Fixed update. Called if entity has attribute and flag Updateable
- ///
- protected internal virtual void Update()
- {
- }
-
- ///
- /// Called at rollback begin before all values reset to first frame in rollback queue.
- ///
- protected internal virtual void OnBeforeRollback()
- {
-
- }
-
- ///
- /// Called at rollback begin after all values reset to first frame in rollback queue.
- ///
- protected internal virtual void OnRollback()
- {
-
- }
-
- ///
- /// Called only on and if entity has attribute and flag Updateable
- ///
- protected internal virtual void VisualUpdate()
- {
-
- }
-
- ///
- /// Called when entity constructed
- ///
- 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
- 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.RefFieldValue(this, field.Offset);
- field.TypeProcessor.InitSyncVar(syncableField, field.SyncableSyncVarOffset, onChangeTarget, (ushort)i);
- }
- }
-
- List rpcCahce = null;
- if(classData.RemoteCallsClient == null)
- {
- rpcCahce = new List();
- var rpcRegistrator = new RPCRegistrator(rpcCahce, classData.Fields);
- RegisterRPC(ref rpcRegistrator);
- //Logger.Log($"RegisterRPCs for class: {classData.ClassId}");
- }
- //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.RefFieldValue(this, syncFieldInfo.Offset);
- syncField.ParentEntityInternal = this;
- if (syncFieldInfo.Flags.HasFlagFast(SyncFlags.OnlyForOwner))
- syncField.Flags = ExecuteFlags.SendToOwner;
- else if (syncFieldInfo.Flags.HasFlagFast(SyncFlags.OnlyForOtherPlayers))
- syncField.Flags = ExecuteFlags.SendToOther;
- else
- syncField.Flags = ExecuteFlags.SendToAll;
- if (classData.RemoteCallsClient != null)
- {
- syncField.RPCOffset = syncFieldInfo.RPCOffset;
- }
- else
- {
- syncField.RPCOffset = (ushort)rpcCahce.Count;
- syncFieldInfo.RPCOffset = syncField.RPCOffset;
- var syncablesRegistrator = new SyncableRPCRegistrator(syncFieldInfo.Offset, rpcCahce);
- syncField.RegisterRPC(ref syncablesRegistrator);
- }
- }
- classData.RemoteCallsClient ??= rpcCahce.ToArray();
- }
-
-
-
- ///
- /// Method for registering RPCs and OnChange notifications
- ///
- ///
- protected virtual void RegisterRPC(ref RPCRegistrator r)
- {
- r.BindOnChange(this, ref _isDestroyed, OnDestroyChange);
- }
-
- protected void ExecuteRPC(in RemoteCall rpc)
- {
- if (IsServer)
- {
- if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnServer))
- rpc.CachedAction(this);
- ServerManager.AddRemoteCall(this, rpc.Id, rpc.Flags);
- }
- else if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnPrediction) && IsLocalControlled)
- rpc.CachedAction(this);
- }
-
- protected void ExecuteRPC(in RemoteCall rpc, T value) where T : unmanaged
- {
- if (IsServer)
- {
- if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnServer))
- rpc.CachedAction(this, value);
- unsafe
- {
- ServerManager.AddRemoteCall(this, new ReadOnlySpan(&value, 1), rpc.Id, rpc.Flags);
- }
- }
- else if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnPrediction) && IsLocalControlled)
- rpc.CachedAction(this, value);
- }
-
- protected void ExecuteRPC(in RemoteCallSpan rpc, ReadOnlySpan value) where T : unmanaged
- {
- if (IsServer)
- {
- if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnServer))
- rpc.CachedAction(this, value);
- ServerManager.AddRemoteCall(this, value, rpc.Id, rpc.Flags);
- }
- else if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnPrediction) && IsLocalControlled)
- rpc.CachedAction(this, value);
- }
-
- protected void ExecuteRPC(in RemoteCallSerializable rpc, T value) where T : struct, ISpanSerializable
- {
- if (IsServer)
- {
- if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnServer))
- rpc.CachedAction(this, value);
- var writer = new SpanWriter(stackalloc byte[value.MaxSize]);
- value.Serialize(ref writer);
- ServerManager.AddRemoteCall(this, writer.RawData.Slice(0, writer.Position), rpc.Id, rpc.Flags);
- }
- else if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnPrediction) && IsLocalControlled)
- rpc.CachedAction(this, value);
- }
-
- protected InternalEntity(EntityParams entityParams)
- {
- EntityManager = entityParams.EntityManager;
- Id = entityParams.Header.Id;
- ClassId = entityParams.Header.ClassId;
- Version = entityParams.Header.Version;
- UpdateOrderNum = entityParams.Header.UpdateOrder;
- IOBuffer = entityParams.IOBuffer;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int CompareTo(InternalEntity other) => UpdateOrderNum != other.UpdateOrderNum ? UpdateOrderNum - other.UpdateOrderNum : Id - other.Id;
-
- public override int GetHashCode() => UpdateOrderNum;
-
- public override string ToString() =>
- $"Entity. Id: {Id}, ClassId: {ClassId}, Version: {Version}";
- }
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace LiteEntitySystem.Internal
+{
+ public readonly struct EntityDataHeader
+ {
+ public readonly ushort Id;
+ public readonly ushort ClassId;
+ public readonly byte Version;
+ public readonly int UpdateOrder;
+
+ public EntityDataHeader(ushort id, ushort classId, byte version, int updateOrder)
+ {
+ Id = id;
+ ClassId = classId;
+ Version = version;
+ UpdateOrder = updateOrder;
+ }
+ }
+
+ public abstract class InternalEntity : InternalBaseClass, IComparable
+ {
+ [SyncVarFlags(SyncFlags.NeverRollBack)]
+ internal SyncVar InternalOwnerId;
+
+ internal byte[] IOBuffer;
+
+ internal readonly int UpdateOrderNum;
+
+ ///
+ /// Entity class id
+ ///
+ public readonly ushort ClassId;
+
+ ///
+ /// Entity instance id
+ ///
+ public readonly ushort Id;
+
+
+ ///
+ /// Entity manager
+ ///
+ public readonly EntityManager EntityManager;
+
+ ///
+ /// Is entity on server
+ ///
+ public bool IsServer => EntityManager.IsServer;
+
+ ///
+ /// Is entity on server
+ ///
+ public bool IsClient => EntityManager.IsClient;
+
+ ///
+ /// Entity version (for id reuse)
+ ///
+ public readonly byte Version;
+
+ internal EntityDataHeader DataHeader => new EntityDataHeader
+ (
+ Id,
+ ClassId,
+ Version,
+ UpdateOrderNum
+ );
+
+ [SyncVarFlags(SyncFlags.NeverRollBack)]
+ private SyncVar _isDestroyed;
+
+ ///
+ /// Is entity is destroyed
+ ///
+ public bool IsDestroyed => _isDestroyed;
+
+ ///
+ /// Is entity local controlled
+ ///
+ public bool IsLocalControlled => InternalOwnerId.Value == EntityManager.InternalPlayerId;
+
+ ///
+ /// Is entity remote controlled
+ ///
+ 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
+ /// Singletons always controlled by server
+ ///
+ public byte OwnerId => InternalOwnerId.Value;
+
+ ///
+ /// Is locally created entity
+ ///
+ public bool IsLocal => Id >= EntityManager.MaxSyncedEntityCount;
+
+ ///
+ /// Is entity based on SingletonEntityLogic
+ ///
+ public bool IsSingleton => ClassData.IsSingleton;
+
+ internal ref EntityClassData ClassData => ref EntityManager.ClassDataDict[ClassId];
+
+ ///
+ /// Destroy entity
+ ///
+ public void Destroy()
+ {
+ if ((EntityManager.IsClient && !IsLocal) || _isDestroyed)
+ return;
+ DestroyInternal();
+ }
+
+ private void OnDestroyChange(bool prevValue)
+ {
+ if (!prevValue && _isDestroyed)
+ {
+ _isDestroyed.Value = false;
+ DestroyInternal();
+ }
+ }
+
+ ///
+ /// Event called on entity destroy
+ ///
+ protected virtual void OnDestroy()
+ {
+
+ }
+
+ internal virtual void DestroyInternal()
+ {
+ if (_isDestroyed)
+ return;
+ _isDestroyed.Value = true;
+ EntityManager.OnEntityDestroyed(this);
+ OnDestroy();
+ }
+
+ internal void SafeUpdate()
+ {
+ try
+ {
+ Update();
+ }
+ catch (Exception e)
+ {
+ Logger.LogError($"Exception in entity({Id}) update:\n{e}");
+ }
+ }
+
+ ///
+ /// Fixed update. Called if entity has attribute and flag Updateable
+ ///
+ protected internal virtual void Update()
+ {
+ }
+
+ ///
+ /// Called at rollback begin before all values reset to first frame in rollback queue.
+ ///
+ protected internal virtual void OnBeforeRollback()
+ {
+
+ }
+
+ ///
+ /// Called at rollback begin after all values reset to first frame in rollback queue.
+ ///
+ protected internal virtual void OnRollback()
+ {
+
+ }
+
+ ///
+ /// Called only on and if entity has attribute and flag Updateable
+ ///
+ protected internal virtual void VisualUpdate()
+ {
+
+ }
+
+ ///
+ /// Called when entity constructed
+ ///
+ 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
+ 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.RefFieldValue(this, field.Offset);
+ field.TypeProcessor.InitSyncVar(syncableField, field.SyncableSyncVarOffset, onChangeTarget, (ushort)i);
+ }
+ }
+
+ List rpcCahce = null;
+ if(classData.RemoteCallsClient == null)
+ {
+ rpcCahce = new List();
+ var rpcRegistrator = new RPCRegistrator(rpcCahce, classData.Fields);
+ RegisterRPC(ref rpcRegistrator);
+ //Logger.Log($"RegisterRPCs for class: {classData.ClassId}");
+ }
+ //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.RefFieldValue(this, syncFieldInfo.Offset);
+ syncField.ParentEntityInternal = this;
+ if (syncFieldInfo.Flags.HasFlagFast(SyncFlags.OnlyForOwner))
+ syncField.Flags = ExecuteFlags.SendToOwner;
+ else if (syncFieldInfo.Flags.HasFlagFast(SyncFlags.OnlyForOtherPlayers))
+ syncField.Flags = ExecuteFlags.SendToOther;
+ else
+ syncField.Flags = ExecuteFlags.SendToAll;
+ if (classData.RemoteCallsClient != null)
+ {
+ syncField.RPCOffset = syncFieldInfo.RPCOffset;
+ }
+ else
+ {
+ syncField.RPCOffset = (ushort)rpcCahce.Count;
+ syncFieldInfo.RPCOffset = syncField.RPCOffset;
+ var syncablesRegistrator = new SyncableRPCRegistrator(syncFieldInfo.Offset, rpcCahce);
+ syncField.RegisterRPC(ref syncablesRegistrator);
+ }
+ }
+ classData.RemoteCallsClient ??= rpcCahce.ToArray();
+ }
+
+
+
+ ///
+ /// Method for registering RPCs and OnChange notifications
+ ///
+ ///
+ protected virtual void RegisterRPC(ref RPCRegistrator r)
+ {
+ r.BindOnChange(this, ref _isDestroyed, OnDestroyChange);
+ }
+
+ protected void ExecuteRPC(in RemoteCall rpc)
+ {
+ if (IsServer)
+ {
+ if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnServer))
+ rpc.CachedAction(this);
+ ServerManager.AddRemoteCall(this, rpc.Id, rpc.Flags);
+ }
+ else if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnPrediction) && IsLocalControlled)
+ rpc.CachedAction(this);
+ }
+
+ protected void ExecuteRPC(in RemoteCall rpc, T value) where T : unmanaged
+ {
+ if (IsServer)
+ {
+ if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnServer))
+ rpc.CachedAction(this, value);
+ unsafe
+ {
+ ServerManager.AddRemoteCall(this, new ReadOnlySpan(&value, 1), rpc.Id, rpc.Flags);
+ }
+ }
+ else if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnPrediction) && IsLocalControlled)
+ rpc.CachedAction(this, value);
+ }
+
+ protected void ExecuteRPC(in RemoteCallSpan rpc, ReadOnlySpan value) where T : unmanaged
+ {
+ if (IsServer)
+ {
+ if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnServer))
+ rpc.CachedAction(this, value);
+ ServerManager.AddRemoteCall(this, value, rpc.Id, rpc.Flags);
+ }
+ else if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnPrediction) && IsLocalControlled)
+ rpc.CachedAction(this, value);
+ }
+
+ protected void ExecuteRPC(in RemoteCallSerializable rpc, T value) where T : struct, ISpanSerializable
+ {
+ if (IsServer)
+ {
+ if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnServer))
+ rpc.CachedAction(this, value);
+ var writer = new SpanWriter(stackalloc byte[value.MaxSize]);
+ value.Serialize(ref writer);
+ ServerManager.AddRemoteCall(this, writer.RawData.Slice(0, writer.Position), rpc.Id, rpc.Flags);
+ }
+ else if (rpc.Flags.HasFlagFast(ExecuteFlags.ExecuteOnPrediction) && IsLocalControlled)
+ rpc.CachedAction(this, value);
+ }
+
+ protected InternalEntity(EntityParams entityParams)
+ {
+ EntityManager = entityParams.EntityManager;
+ Id = entityParams.Header.Id;
+ ClassId = entityParams.Header.ClassId;
+ Version = entityParams.Header.Version;
+ UpdateOrderNum = entityParams.Header.UpdateOrder;
+ IOBuffer = entityParams.IOBuffer;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int CompareTo(InternalEntity other) => UpdateOrderNum != other.UpdateOrderNum ? UpdateOrderNum - other.UpdateOrderNum : Id - other.Id;
+
+ public override int GetHashCode() => UpdateOrderNum;
+
+ public override string ToString() =>
+ $"Entity. Id: {Id}, ClassId: {ClassId}, Version: {Version}";
+ }
}
\ No newline at end of file
diff --git a/LiteEntitySystem/Internal/ValueTypeProcessor.cs b/LiteEntitySystem/Internal/ValueTypeProcessor.cs
index 41bd926..a15a9a9 100644
--- a/LiteEntitySystem/Internal/ValueTypeProcessor.cs
+++ b/LiteEntitySystem/Internal/ValueTypeProcessor.cs
@@ -20,6 +20,7 @@ internal abstract unsafe class ValueTypeProcessor
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 unsafe class ValueTypeProcessor : ValueTypeProcessor where T : unmanaged
@@ -50,6 +51,9 @@ internal override void WriteTo(InternalBaseClass obj, int offset, byte* data) =>
internal override int GetHashCode(InternalBaseClass obj, int offset) =>
RefMagic.RefFieldValue>(obj, offset).GetHashCode();
+
+ internal override string ToString(InternalBaseClass obj, int offset) =>
+ RefMagic.RefFieldValue>(obj, offset).ToString();
}
internal class ValueTypeProcessorInt : ValueTypeProcessor