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