diff --git a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs
index 86af30b..b68a1ca 100644
--- a/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs
+++ b/Assets/Plugins/LiteEntitySystem/ClientEntityManager.cs
@@ -17,21 +17,11 @@ public sealed class ClientEntityManager : EntityManager
/// Current interpolated server tick
///
public ushort ServerTick { get; private set; }
-
- ///
- /// Current rollback tick (valid only in Rollback state)
- ///
- public ushort RollBackTick { get; private set; }
-
- ///
- /// Tick of currently executing rpc (check only in client RPC methods)
- ///
- public ushort CurrentRPCTick { get; internal set; }
///
/// Is server->client rpc currently executing
///
- public bool IsExecutingRPC { get; private set; }
+ public bool IsExecutingRPC { get; internal set; }
///
/// Current state server tick
@@ -107,7 +97,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 +112,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
@@ -129,40 +120,62 @@ public sealed class ClientEntityManager : EntityManager
public const int InputBufferSize = 64;
//predicted entities that should use rollback
- private readonly AVLTree _predictedEntities = new();
+ private readonly AVLTree _modifiedEntitiesToRollback = new();
private readonly AbstractNetPeer _netPeer;
private readonly Queue _statesPool = new(MaxSavedStateDiff);
private readonly Dictionary _receivedStates = new();
private readonly SequenceBinaryHeap _readyStates = new(MaxSavedStateDiff);
- private readonly Queue<(ushort tick, EntityLogic entity)> _spawnPredictedEntities = new ();
+ private readonly Queue _tempLocalEntities = new ();
private readonly byte[] _sendBuffer = new byte[NetConstants.MaxPacketSize];
+ private readonly byte[] _zeroFieldBuffer = new byte[StateSerializer.MaxStateSize];
private readonly HashSet _changedEntities = new();
private readonly CircularBuffer _storedInputHeaders = new(InputBufferSize);
private InternalEntity[] _entitiesToRemove = new InternalEntity[64];
- private readonly AVLTree _tempEntityTree = new();
+ private readonly Queue _entitiesToRollback = new();
private int _entitiesToRemoveCount;
private ServerSendRate _serverSendRate;
private ServerStateData _stateA;
private ServerStateData _stateB;
- private float _lerpTime;
- private double _timer;
- private int _syncCallsCount;
+ private float _remoteInterpolationTotalTime;
+ private float _remoteInterpolationTimer;
+ private float _remoteLerpFactor;
+ private ushort _prevTick;
private readonly IdGeneratorUShort _localIdQueue = new(MaxSyncedEntityCount, MaxEntityCount);
+
+ private SyncCallInfo[] _syncCalls;
+ private int _syncCallsCount;
+
+ private ushort _lastReceivedInputTick;
+ private ushort _lastReadyTick;
+
+ //used for debug info
+ private ushort _rollbackStep;
+ private ushort _rollbackTotalSteps;
+ private ushort _tickBeforeRollback;
+
+ //time manipulation
+ private readonly float[] _jitterSamples = new float[50];
+ private int _jitterSampleIdx;
+ private readonly Stopwatch _jitterTimer = new();
+ private float _jitterPrevTime;
+ private float _jitterMiddle;
+ private float _jitterSum;
+
+ //local player
+ private NetPlayer _localPlayer;
private readonly struct SyncCallInfo
{
private readonly InternalEntity _entity;
- private readonly MethodCallDelegate _onSync;
private readonly int _prevDataPos;
- private readonly int _dataSize;
+ private readonly int _fieldId;
- public SyncCallInfo(MethodCallDelegate onSync, InternalEntity entity, int prevDataPos, int dataSize)
+ public SyncCallInfo(InternalEntity entity, int prevDataPos, int fieldId)
{
- _dataSize = dataSize;
- _onSync = onSync;
+ _fieldId = fieldId;
_entity = entity;
_prevDataPos = prevDataPos;
}
@@ -171,7 +184,9 @@ public void Execute(ServerStateData state)
{
try
{
- _onSync(_entity, new ReadOnlySpan(state.Data, _prevDataPos, _dataSize));
+ ref var classData = ref _entity.ClassData;
+ ref var fieldInfo = ref classData.Fields[_fieldId];
+ fieldInfo.OnSync(fieldInfo.GetTargetObject(_entity), new ReadOnlySpan(state.Data, _prevDataPos, fieldInfo.IntSize));
}
catch (Exception e)
{
@@ -179,22 +194,13 @@ public void Execute(ServerStateData state)
}
}
}
- private SyncCallInfo[] _syncCalls;
-
- private ushort _lastReceivedInputTick;
- private float _logicLerpMsec;
- private ushort _lastReadyTick;
- //time manipulation
- private readonly float[] _jitterSamples = new float[50];
- private int _jitterSampleIdx;
- private readonly Stopwatch _jitterTimer = new();
- private float _jitterPrevTime;
- private float _jitterMiddle;
- private float _jitterSum;
-
- //local player
- private NetPlayer _localPlayer;
+ public override string GetCurrentFrameDebugInfo(DebugFrameModes modes) =>
+ UpdateMode == UpdateMode.PredictionRollback && modes.HasFlagFast(DebugFrameModes.Rollback)
+ ? $"[ClientRollback({_rollbackStep}/{_rollbackTotalSteps})] Tick {_tickBeforeRollback}. RollbackTick: {_tick}. ServerTick: {ServerTick}"
+ : modes.HasFlagFast(DebugFrameModes.Client) && UpdateMode == UpdateMode.Normal
+ ? $"[Client] Tick {_tick}. ServerTick: {ServerTick}"
+ : string.Empty;
///
/// Return client controller if exist
@@ -229,13 +235,16 @@ public ClientEntityManager(
_sendBuffer[1] = InternalPackets.ClientInput;
for (int i = 0; i < MaxSavedStateDiff; i++)
- _statesPool.Enqueue(new ServerStateData());
+ _statesPool.Enqueue(new ServerStateData(this));
}
public override void Reset()
{
base.Reset();
+ _modifiedEntitiesToRollback.Clear();
_localIdQueue.Reset();
+ _readyStates.Clear();
+ _syncCallsCount = 0;
_entitiesToRemoveCount = 0;
}
@@ -244,14 +253,15 @@ public override void Reset()
///
/// Entity type
/// Created entity or null if entities limit is reached ()
- internal T AddLocalEntity(EntityLogic parent, Action initMethod) where T : EntityLogic
+ internal unsafe T AddLocalEntity(EntityLogic parent, Action initMethod) where T : PredictableEntityLogic
{
if (_localIdQueue.AvailableIds == 0)
{
Logger.LogError("Max local entities count reached");
return null;
}
-
+
+ ref var classData = ref ClassDataDict[EntityClassInfo.ClassId];
var entity = AddEntity(new EntityParams(
_localIdQueue.GetNewId(),
new EntityDataHeader(
@@ -259,7 +269,7 @@ internal T AddLocalEntity(EntityLogic parent, Action initMethod) where T :
0,
0),
this,
- ClassDataDict[EntityClassInfo.ClassId].AllocateDataCache()));
+ classData.AllocateDataCache()));
//Logger.Log($"AddPredicted, tick: {_tick}, rb: {InRollBackState}, id: {entity.Id}");
@@ -267,41 +277,39 @@ internal T AddLocalEntity(EntityLogic parent, Action initMethod) where T :
entity.SetParentInternal(parent);
initMethod(entity);
ConstructEntity(entity);
- _spawnPredictedEntities.Enqueue((_tick, entity));
-
- return entity;
- }
+ _tempLocalEntities.Enqueue(entity);
- internal EntityLogic FindEntityByPredictedId(ushort tick, ushort parentId, ushort predictedId)
- {
- foreach (var predictedEntity in _spawnPredictedEntities)
+ fixed (byte* predictedData = classData.GetLastServerData(entity))
+ {
+ for (int i = 0; i < classData.FieldsCount; i++)
+ {
+ ref var field = ref classData.Fields[i];
+ var targetObject = field.GetTargetObjectAndOffset(entity, out int offset);
+
+ if(field.Flags.HasFlagFast(SyncFlags.Interpolated))
+ field.TypeProcessor.SetInterpValueFromCurrentValue(targetObject, offset);
+ if(field.IsPredicted)
+ field.TypeProcessor.WriteTo(targetObject, offset, predictedData + field.PredictedOffset);
+ }
+ }
+
+ //init syncable rollback
+ for (int i = 0; i < classData.SyncableFieldsCustomRollback.Length; i++)
{
- if (predictedEntity.tick == tick &&
- predictedEntity.entity.ParentId.Id == parentId &&
- predictedEntity.entity.PredictedId == predictedId)
- return predictedEntity.entity;
+ var syncableField = RefMagic.GetFieldValue(entity, classData.SyncableFieldsCustomRollback[i].Offset);
+ syncableField.BeforeReadRPC();
+ syncableField.AfterReadRPC();
}
- return null;
- }
- internal override void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue)
- {
- //currently nothing
+ return entity;
}
- protected override unsafe void OnAliveEntityAdded(InternalEntity entity)
+ internal PredictableEntityLogic FindEntityByPredictedId(ushort tick, ushort parentId, ushort predictedId)
{
- ref var classData = ref ClassDataDict[entity.ClassId];
- fixed (byte* interpDataPtr = classData.ClientInterpolatedNextData(entity),
- prevDataPtr = classData.ClientInterpolatedPrevData(entity))
- {
- for (int i = 0; i < classData.InterpolatedCount; i++)
- {
- var field = classData.Fields[i];
- field.TypeProcessor.WriteTo(entity, field.Offset, interpDataPtr + field.FixedOffset);
- field.TypeProcessor.WriteTo(entity, field.Offset, prevDataPtr + field.FixedOffset);
- }
- }
+ foreach (var predictedEntity in _tempLocalEntities)
+ if (predictedEntity.IsEntityMatch(predictedId, parentId, tick))
+ return predictedEntity;
+ return null;
}
/// Read incoming data
@@ -349,17 +357,18 @@ public unsafe DeserializeResult Deserialize(ReadOnlySpan inData)
_localPlayer = new NetPlayer(_netPeer, InternalPlayerId);
ServerTick = _stateA.Tick;
_lastReadyTick = ServerTick;
+ _remoteInterpolationTimer = 0f;
+ _remoteLerpFactor = 0f;
foreach (var controller in GetEntities())
controller.ClearClientStoredInputs();
_storedInputHeaders.Clear();
_jitterTimer.Reset();
- IsExecutingRPC = true;
- _stateA.ExecuteRpcs(this, 0, true);
- IsExecutingRPC = false;
- int readerPosition = _stateA.DataOffset;
- ReadDiff(ref readerPosition);
- ExecuteSyncCallsAndWriteHistory(_stateA);
+ _stateA.ExecuteRpcs((ushort)(_stateA.Tick - 1),RPCExecuteMode.FirstSync);
+ ReadDiff();
+ ExecuteSyncCalls(_stateA);
+ foreach (var lagCompensatedEntity in LagCompensatedEntities)
+ ClassDataDict[lagCompensatedEntity.ClassId].WriteHistory(lagCompensatedEntity, ServerTick);
Logger.Log($"[CEM] Got baseline sync. Assigned player id: {header.PlayerId}, Original: {_stateA.Size}, Tick: {header.Tick}, SendRate: {_serverSendRate}");
}
@@ -391,7 +400,7 @@ public unsafe DeserializeResult Deserialize(ReadOnlySpan inData)
{
if (_statesPool.Count == 0)
{
- serverState = new ServerStateData { Tick = diffHeader.Tick };
+ serverState = new ServerStateData(this) { Tick = diffHeader.Tick };
}
else
{
@@ -415,9 +424,11 @@ public unsafe DeserializeResult Deserialize(ReadOnlySpan inData)
if (_readyStates.Count == MaxSavedStateDiff)
{
//one state should be already preloaded
- _timer = _lerpTime;
+ _remoteInterpolationTimer = _remoteInterpolationTotalTime;
//fast-forward
GoToNextState();
+
+ //to add space to _readyStates
PreloadNextState();
}
@@ -449,12 +460,12 @@ private bool PreloadNextState()
//limit jitter for pause scenarios
if (NetworkJitter > MaxJitter)
NetworkJitter = MaxJitter;
- float lowestBound = NetworkJitter + PreferredBufferTimeLowest;
- float upperBound = NetworkJitter + PreferredBufferTimeHighest;
+ float lowestBound = NetworkJitter * 1.5f + PreferredBufferTimeLowest;
+ float upperBound = NetworkJitter * 1.5f + PreferredBufferTimeHighest;
//tune buffer playing speed
- _lerpTime = Utils.SequenceDiff(_stateB.Tick, _stateA.Tick) * DeltaTimeF;
- _lerpTime *= 1 - GetSpeedMultiplier(LerpBufferTimeLength)*TimeSpeedChangeCoef;
+ _remoteInterpolationTotalTime = Utils.SequenceDiff(_stateB.Tick, _stateA.Tick) * DeltaTimeF;
+ _remoteInterpolationTotalTime *= 1 - GetSpeedMultiplier(LerpBufferTimeLength)*TimeSpeedChangeCoef;
//tune game prediction and input generation speed
SpeedMultiplier = GetSpeedMultiplier(_stateB.BufferedInputsCount * DeltaTimeF);
@@ -468,45 +479,122 @@ float GetSpeedMultiplier(float bufferTime) =>
private unsafe void GoToNextState()
{
- //remove processed inputs
- foreach (var controller in GetEntities())
- controller.RemoveClientProcessedInputs(_stateB.ProcessedTick);
- while (_storedInputHeaders.Count > 0 && Utils.SequenceDiff(_stateB.ProcessedTick, _storedInputHeaders.Front().Tick) >= 0)
- _storedInputHeaders.PopFront();
+ _remoteInterpolationTimer -= _remoteInterpolationTotalTime;
+
+ _tickBeforeRollback = _tick;
+ _rollbackStep = 0;
+ _rollbackTotalSteps = (ushort)_storedInputHeaders.Count;
- //delete predicted
- while (_spawnPredictedEntities.TryPeek(out var info))
+ ushort targetTick = _tick;
+ var humanControllerFilter = GetEntities();
+
+ //================== Rollback part ===========================
+ _entitiesToRollback.Clear();
+ foreach (var entity in _modifiedEntitiesToRollback)
+ {
+ if(!entity.IsRemoved)
+ _entitiesToRollback.Enqueue(entity);
+ }
+
+ UpdateMode = UpdateMode.PredictionRollback;
+ //reset predicted entities
+ foreach (var entity in _entitiesToRollback)
{
- if (Utils.SequenceDiff(_stateB.ProcessedTick, info.tick) >= 0)
+ ref var classData = ref ClassDataDict[entity.ClassId];
+ var rollbackFields = classData.GetRollbackFields(entity.IsLocalControlled);
+ entity.OnBeforeRollback();
+
+ fixed (byte* lastServerData = classData.GetLastServerData(entity))
{
- //Logger.Log($"Delete predicted. Tick: {info.tick}, Entity: {info.entity}");
- _spawnPredictedEntities.Dequeue();
- info.entity.DestroyInternal();
- RemoveEntity(info.entity);
- _localIdQueue.ReuseId(info.entity.Id);
+ for (int i = 0; i < rollbackFields.Length; i++)
+ {
+ ref var field = ref classData.Fields[rollbackFields[i]];
+ var target = field.GetTargetObjectAndOffset(entity, out int offset);
+
+ if (field.OnSync != null && (field.OnSyncFlags & BindOnChangeFlags.ExecuteOnRollbackReset) != 0)
+ {
+ //this is currently only place which doesn't call classData.CallOnSync
+ field.TypeProcessor.SetFromAndSync(target, offset, lastServerData + field.PredictedOffset, field.OnSync);
+ }
+ else
+ {
+ field.TypeProcessor.SetFrom(target, offset, lastServerData + field.PredictedOffset);
+ }
+ }
}
- else
+ for (int i = 0; i < classData.SyncableFieldsCustomRollback.Length; i++)
+ RefMagic.GetFieldValue(entity, classData.SyncableFieldsCustomRollback[i].Offset).OnRollback();
+ entity.OnRollback();
+ }
+
+ //clear modified here to readd changes after RollbackUpdate
+ _modifiedEntitiesToRollback.Clear();
+
+ //Step a little to match "predicted" state at server processed tick
+ //for correct BindOnSync execution
+ int cmdNum = 0;
+ while(_storedInputHeaders.Count > 0 && Utils.SequenceDiff(_stateB.ProcessedTick, _storedInputHeaders.Front().Tick) >= 0)
+ {
+ var storedInput = _storedInputHeaders.Front();
+ _storedInputHeaders.PopFront();
+ _localPlayer.LoadInputInfo(storedInput.Header);
+ _tick = storedInput.Tick;
+ foreach (var controller in humanControllerFilter)
+ controller.ReadStoredInput(cmdNum);
+ cmdNum++;
+ _rollbackStep++;
+
+ //simple update
+ foreach (var entity in _entitiesToRollback)
{
- break;
+ if (!entity.IsLocalControlled || !AliveEntities.Contains(entity))
+ continue;
+
+ //skip local entities that spawned later
+ if (entity.IsLocal && entity is PredictableEntityLogic el && Utils.SequenceDiff(el.CreatedAtTick, _tick) >= 0)
+ continue;
+
+ entity.Update();
}
}
+ UpdateMode = UpdateMode.Normal;
+
+ //remove processed inputs
+ foreach (var controller in humanControllerFilter)
+ controller.RemoveClientProcessedInputs(_stateB.ProcessedTick);
ushort minimalTick = _stateA.Tick;
_statesPool.Enqueue(_stateA);
_stateA = _stateB;
_stateB = null;
+ ServerTick = _stateA.Tick;
- //Logger.Log($"GotoState: IST: {ServerTick}, TST:{_stateA.Tick}}");
-
- //================== ReadEntityStates BEGIN ==================
+ //Logger.Log($"GotoState: IST: {ServerTick}, TST:{_stateA.Tick}");
_changedEntities.Clear();
- ServerTick = _stateA.Tick;
- IsExecutingRPC = true;
- _stateA.ExecuteRpcs(this, minimalTick, false);
- IsExecutingRPC = false;
- int readerPosition = _stateA.DataOffset;
- ReadDiff(ref readerPosition);
- ExecuteSyncCallsAndWriteHistory(_stateA);
+ _stateA.ExecuteRpcs(minimalTick, RPCExecuteMode.OnNextState);
+ ReadDiff();
+ ExecuteSyncCalls(_stateA);
+
+ //delete predicted
+ while (_tempLocalEntities.TryPeek(out var entity))
+ {
+ if (Utils.SequenceDiff(_stateA.ProcessedTick, entity.CreatedAtTick) >= 0)
+ {
+ //Logger.Log($"Delete predicted. Tick: {info.tick}, Entity: {info.entity}");
+ _tempLocalEntities.Dequeue();
+ entity.DestroyInternal();
+ RemoveEntity(entity);
+ _localIdQueue.ReuseId(entity.Id);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ //write lag comp history for remote rollbackable lagcomp fields
+ foreach (var lagCompensatedEntity in LagCompensatedEntities)
+ ClassDataDict[lagCompensatedEntity.ClassId].WriteHistory(lagCompensatedEntity, ServerTick);
for(int i = 0; i < _entitiesToRemoveCount; i++)
{
@@ -514,8 +602,6 @@ private unsafe void GoToNextState()
var entityToRemove = _entitiesToRemove[i];
if (_changedEntities.Contains(entityToRemove))
continue;
-
- _predictedEntities.Remove(entityToRemove);
//Logger.Log($"[CLI] RemovingEntity: {_entitiesToRemove[i].Id}");
RemoveEntity(entityToRemove);
@@ -525,105 +611,91 @@ private unsafe void GoToNextState()
_entitiesToRemove[_entitiesToRemoveCount] = null;
i--;
}
-
- //================== ReadEntityStates END ====================
-
- _timer -= _lerpTime;
-
- //================== Rollback part ===========================
- //reset owned entities
- foreach (var entity in _predictedEntities)
- {
- ref var classData = ref ClassDataDict[entity.ClassId];
- var rollbackFields = classData.GetRollbackFields(entity.IsLocalControlled);
- if(rollbackFields == null || rollbackFields.Length == 0)
- continue;
- entity.OnBeforeRollback();
- fixed (byte* predictedData = classData.ClientPredictedData(entity))
- {
- for (int i = 0; i < rollbackFields.Length; i++)
- {
- ref var field = ref rollbackFields[i];
- if (field.FieldType == FieldType.SyncableSyncVar)
- {
- var syncableField = RefMagic.RefFieldValue(entity, field.Offset);
- field.TypeProcessor.SetFrom(syncableField, field.SyncableSyncVarOffset, predictedData + field.PredictedOffset);
- }
- else
- {
- field.TypeProcessor.SetFrom(entity, field.Offset, predictedData + field.PredictedOffset);
- }
- }
- }
- for (int i = 0; i < classData.SyncableFields.Length; i++)
- RefMagic.RefFieldValue(entity, classData.SyncableFields[i].Offset).OnRollback();
- entity.OnRollback();
- }
-
//reapply input
- UpdateMode = UpdateMode.PredictionRollback;
- for(int cmdNum = 0; cmdNum < _storedInputHeaders.Count; cmdNum++)
+ UpdateMode = UpdateMode.PredictionRollback;
+ for(cmdNum = 0; cmdNum < _storedInputHeaders.Count; cmdNum++)
{
//reapply input data
var storedInput = _storedInputHeaders[cmdNum];
_localPlayer.LoadInputInfo(storedInput.Header);
- RollBackTick = storedInput.Tick;
- foreach (var controller in GetEntities())
+ _tick = storedInput.Tick;
+ foreach (var controller in humanControllerFilter)
controller.ReadStoredInput(cmdNum);
+ _rollbackStep++;
+
//simple update
- foreach (var entity in AliveEntities)
+ foreach (var entity in _entitiesToRollback)
{
- if (entity.IsLocal || !entity.IsLocalControlled)
+ if (!entity.IsLocalControlled || !AliveEntities.Contains(entity))
continue;
+
+ //skip local entities that spawned later
+ if (entity.IsLocal && entity is PredictableEntityLogic el && Utils.SequenceDiff(el.CreatedAtTick, _tick) >= 0)
+ continue;
+
+ //update interpolation data
+ if (cmdNum == _storedInputHeaders.Count - 1)
+ {
+ ref var classData = ref ClassDataDict[entity.ClassId];
+ for(int i = 0; i < classData.InterpolatedCount; i++)
+ {
+ ref var field = ref classData.Fields[i];
+ var target = field.GetTargetObjectAndOffset(entity, out int offset);
+ field.TypeProcessor.SetInterpValueFromCurrentValue(target, offset);
+ }
+ }
+
entity.Update();
}
}
UpdateMode = UpdateMode.Normal;
+
+ _tick = targetTick;
+ _entitiesToRollback.Clear();
+ }
+
+ internal void MarkEntityChanged(InternalEntity entity)
+ {
+ if (entity.IsRemoved || IsExecutingRPC)
+ return;
+ _modifiedEntitiesToRollback.Add(entity);
+ }
+
+ internal override unsafe void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue, ref T oldValue, bool skipOnSync)
+ {
+ if (entity.IsRemoved)
+ return;
+
+ ref var classData = ref ClassDataDict[entity.ClassId];
+ ref var fieldInfo = ref classData.Fields[fieldId];
+
+ if (!skipOnSync && (fieldInfo.OnSyncFlags & BindOnChangeFlags.ExecuteOnPrediction) != 0)
+ {
+ T oldValueCopy = oldValue;
+ fieldInfo.OnSync(fieldInfo.GetTargetObject(entity), new ReadOnlySpan(&oldValueCopy, fieldInfo.IntSize));
+ }
+
+ var rollbackFields = classData.GetRollbackFields(entity.IsLocalControlled);
+ if (rollbackFields != null && rollbackFields.Length > 0 && fieldInfo.IsPredicted)
+ _modifiedEntitiesToRollback.Add(entity);
}
internal override void OnEntityDestroyed(InternalEntity e)
{
if (!e.IsLocal)
{
+ //because remote and server alive entities managed by EntityManager
if(e.IsLocalControlled && e is EntityLogic eLogic)
RemoveOwned(eLogic);
Utils.AddToArrayDynamic(ref _entitiesToRemove, ref _entitiesToRemoveCount, e);
}
-
+ _modifiedEntitiesToRollback.Remove(e);
base.OnEntityDestroyed(e);
}
- protected override unsafe void OnLogicTick()
+ protected override void OnLogicTick()
{
- if (_stateB != null)
- {
- ServerTick = Utils.LerpSequence(_stateA.Tick, _stateB.Tick, (float)(_timer/_lerpTime));
-
- //restore actual field values
- _tempEntityTree.Clear();
- foreach (var entity in AliveEntities)
- {
- ref var classData = ref ClassDataDict[entity.ClassId];
- if(classData.InterpolatedCount == 0)
- continue;
- if (entity.IsLocal || entity.IsLocalControlled)
- {
- //save data for interpolation before update
- fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(entity))
- {
- //restore previous
- for (int i = 0; i < classData.InterpolatedCount; i++)
- {
- var field = classData.Fields[i];
- field.TypeProcessor.SetFrom(entity, field.Offset, currentDataPtr + field.FixedOffset);
- }
- }
- _tempEntityTree.Add(entity);
- }
- }
- }
-
//apply input
var humanControllers = GetEntities();
if (humanControllers.Count > 0)
@@ -632,7 +704,7 @@ protected override unsafe void OnLogicTick()
{
StateA = _stateA.Tick,
StateB = RawTargetServerTick,
- LerpMsec = _logicLerpMsec
+ LerpMsec = _remoteLerpFactor
}));
foreach (var controller in humanControllers)
{
@@ -642,93 +714,68 @@ protected override unsafe void OnLogicTick()
//local only and UpdateOnClient
foreach (var entity in AliveEntities)
- entity.Update();
-
- if (_stateB != null)
{
- //save interpolation data
- foreach (var entity in _tempEntityTree)
+ //first logic tick in Update
+ if (_tick == _prevTick && (entity.IsLocal || entity.IsLocalControlled))
{
ref var classData = ref ClassDataDict[entity.ClassId];
- fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(entity),
- prevDataPtr = classData.ClientInterpolatedPrevData(entity))
+ //save data for interpolation before update
+ for (int i = 0; i < classData.InterpolatedCount; i++)
{
- RefMagic.CopyBlock(prevDataPtr, currentDataPtr, (uint)classData.InterpolatedFieldsSize);
- for(int i = 0; i < classData.InterpolatedCount; i++)
- {
- var field = classData.Fields[i];
- field.TypeProcessor.WriteTo(entity, field.Offset, currentDataPtr + field.FixedOffset);
- }
+ ref var field = ref classData.Fields[i];
+ var target = field.GetTargetObjectAndOffset(entity, out int offset);
+ field.TypeProcessor.SetInterpValueFromCurrentValue(target, offset);
}
}
- //execute rpcs and spawn entities
- IsExecutingRPC = true;
- _stateB.ExecuteRpcs(this, _stateA.Tick, false);
- IsExecutingRPC = false;
- ExecuteSyncCallsAndWriteHistory(_stateB);
+ entity.Update();
}
-
- if (NetworkJitter > _jitterMiddle)
- NetworkJitter -= DeltaTimeF * 0.1f;
+ ExecuteLocalSingletonsLateUpdate();
}
///
/// Update method, call this every frame
///
- public override unsafe void Update()
+ public override void Update()
{
//skip update until receive first sync and tickrate
if (Tickrate == 0)
return;
//logic update
- ushort prevTick = _tick;
-
+ _prevTick = _tick;
+ ServerTick = Utils.LerpSequence(_stateA.Tick, _stateB?.Tick ?? _stateA.Tick, _remoteLerpFactor);
+
base.Update();
+ //reduce max jitter to middle
+ float dt = (float)VisualDeltaTime;
+ if (NetworkJitter > _jitterMiddle)
+ {
+ NetworkJitter -= dt * 0.1f;
+ if (NetworkJitter < MinJitter)
+ NetworkJitter = MinJitter;
+ }
+
//send buffered input
- if (_tick != prevTick)
+ if (_tick != _prevTick)
SendBufferedInput();
- if (PreloadNextState())
+ if (_stateB != null)
{
- _timer += VisualDeltaTime;
- while(_timer >= _lerpTime)
+ //execute rpcs and spawn entities
+ _stateB.ExecuteRpcs(_stateA.Tick, RPCExecuteMode.BetweenStates);
+ ExecuteSyncCalls(_stateB);
+
+ while(_remoteInterpolationTimer >= _remoteInterpolationTotalTime)
{
GoToNextState();
if (!PreloadNextState())
break;
}
-
- if (_stateB != null)
- {
- _logicLerpMsec = (float)(_timer/_lerpTime);
- _stateB.RemoteInterpolation(EntitiesDict, _logicLerpMsec);
- }
- }
-
- //local interpolation
- float localLerpT = LerpFactor;
- foreach (var entity in AliveEntities)
- {
- if (!entity.IsLocalControlled && !entity.IsLocal)
- continue;
-
- ref var classData = ref ClassDataDict[entity.ClassId];
- fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(entity), prevDataPtr = classData.ClientInterpolatedPrevData(entity))
- {
- for(int i = 0; i < classData.InterpolatedCount; i++)
- {
- ref var field = ref classData.Fields[i];
- field.TypeProcessor.SetInterpolation(
- entity,
- field.Offset,
- prevDataPtr + field.FixedOffset,
- currentDataPtr + field.FixedOffset,
- localLerpT);
- }
- }
+ _stateB?.PreloadInterpolationForNewEntities();
+ _remoteLerpFactor = _remoteInterpolationTimer / _remoteInterpolationTotalTime;
+ _remoteInterpolationTimer += dt;
}
//local only and UpdateOnClient
@@ -738,6 +785,17 @@ public override unsafe void Update()
}
}
+ internal T GetInterpolatedValue(ref SyncVar syncVar, T interpValue) where T : unmanaged
+ {
+ if (IsLagCompensationEnabled && IsEntityLagCompensated(syncVar.Container))
+ return syncVar.Value;
+
+ var typeProcessor = (ValueTypeProcessor)ClassDataDict[syncVar.Container.ClassId].Fields[syncVar.FieldId].TypeProcessor;
+ return syncVar.Container.IsLocalControlled
+ ? typeProcessor.GetInterpolatedValue(interpValue, syncVar.Value, LerpFactor)
+ : typeProcessor.GetInterpolatedValue(syncVar.Value, interpValue, _remoteLerpFactor);
+ }
+
private unsafe void SendBufferedInput()
{
if (_storedInputHeaders.Count == 0)
@@ -747,7 +805,7 @@ private unsafe void SendBufferedInput()
*(ushort*)(sendBuffer + 2) = Tick;
*(InputPacketHeader*)(sendBuffer + 4) = new InputPacketHeader
{
- LerpMsec = _logicLerpMsec,
+ LerpMsec = _remoteLerpFactor,
StateA = _stateA.Tick,
StateB = RawTargetServerTick
};
@@ -833,7 +891,7 @@ internal void RemoveOwned(EntityLogic entity)
AliveEntities.Remove(entity);
}
- internal unsafe void ReadNewRPC(ushort entityId, byte* rawData)
+ internal unsafe void ReadNewRPC(ushort entityId, byte* rawData, int byteCount)
{
//Logger.Log("NewRPC");
var entityDataHeader = *(EntityDataHeader*)rawData;
@@ -844,38 +902,35 @@ internal unsafe void ReadNewRPC(ushort entityId, byte* rawData)
}
var entity = EntitiesDict[entityId];
- //Logger.Log($"[CEM] ReadBaseline Entity: {entityId} pos: {bytesRead}");
+ //Logger.Log($"[CEM] New Entity: {entityId}");
//remove old entity
if (entity != null && entity.Version != entityDataHeader.Version)
{
+ //can happen when entities in remove list but new arrived
//this can be only on logics (not on singletons)
Logger.Log($"[CEM] Replace entity by new: {entityDataHeader.Version}. Class: {entityDataHeader.ClassId}. Id: {entityId}");
entity.DestroyInternal();
+
RemoveEntity(entity);
- _predictedEntities.Remove(entity);
entity = null;
}
if (entity == null) //create new
{
ref var classData = ref ClassDataDict[entityDataHeader.ClassId];
entity = AddEntity(new EntityParams(entityId, entityDataHeader, this, classData.AllocateDataCache()));
-
- if (classData.PredictedSize > 0 || classData.SyncableFields.Length > 0)
- {
- _predictedEntities.Add(entity);
- //Logger.Log($"Add predicted: {entity.GetType()}");
- }
}
+
+ ApplyEntityDelta(entity, rawData, StateSerializer.HeaderSize, true);
+
+ //Logger.Log($"NewRPC bytes (client) eid:{entityId} cls:{entity.ClassId} data:{Utils.BytesToHexString(new ReadOnlySpan(rawData, byteCount))}");
}
- private void ExecuteSyncCallsAndWriteHistory(ServerStateData stateData)
+ private void ExecuteSyncCalls(ServerStateData stateData)
{
ExecuteLateConstruct();
for (int i = 0; i < _syncCallsCount; i++)
_syncCalls[i].Execute(stateData);
_syncCallsCount = 0;
- foreach (var lagCompensatedEntity in LagCompensatedEntities)
- ClassDataDict[lagCompensatedEntity.ClassId].WriteHistory(lagCompensatedEntity, ServerTick);
}
internal unsafe void ReadConstructRPC(ushort entityId, byte* rawData, int readerPosition)
@@ -888,83 +943,79 @@ internal unsafe void ReadConstructRPC(ushort entityId, byte* rawData, int reader
}
var entity = EntitiesDict[entityId];
+
bool writeInterpolationData = !entity.IsConstructed || entity.IsRemoteControlled;
ref var classData = ref entity.ClassData;
Utils.ResizeOrCreate(ref _syncCalls, _syncCallsCount + classData.FieldsCount);
-
- fixed (byte* interpDataPtr = classData.ClientInterpolatedNextData(entity),
- prevDataPtr = classData.ClientInterpolatedPrevData(entity),
- predictedData = classData.ClientPredictedData(entity))
+
+ //set values to same as predicted entity for correct OnSync calls
+ if (!entity.IsConstructed && entity is PredictableEntityLogic pel)
{
- for (int i = 0; i < classData.FieldsCount; i++)
+ foreach (var localEntity in _tempLocalEntities)
{
- ref var field = ref classData.Fields[i];
- if (field.ReadField(
- entity,
- rawData + readerPosition,
- predictedData,
- writeInterpolationData ? interpDataPtr : null,
- writeInterpolationData ? prevDataPtr : null))
+ if (!pel.IsSameAsLocal(localEntity))
+ continue;
+
+ pel.IsRecreated = true;
+
+ //Logger.Log($"Found predictable entity. LocalId was: {localEntity.Id}, server id: {entityId}");
+
+ for (int i = 0; i < classData.FieldsCount; i++)
{
- _syncCalls[_syncCallsCount++] = new SyncCallInfo(field.OnSync, entity, readerPosition, field.IntSize);
+ ref var field = ref classData.Fields[i];
+ //skip some fields because they need to trigger onChange
+ if (field.OnSync == null || i == pel.InternalOwnerId.FieldId || i == pel.IsSyncEnabledFieldId)
+ continue;
+
+ var target = field.GetTargetObjectAndOffset(entity, out int offset);
+ field.TypeProcessor.CopyFrom(target, localEntity, offset);
}
-
- //Logger.Log($"E {entity.Id} Field updated: {field.Name}");
- readerPosition += field.IntSize;
+
+ break;
}
}
- //Construct and fast forward predicted entities
- if (ConstructEntity(entity) == false)
- return;
-
- if (entity is not EntityLogic entityLogic)
- return;
-
- //Logger.Log($"ConstructedEntity: {entityId}, pid: {entityLogic.PredictedId}");
-
- entityLogic.RefreshOwnerInfo(null);
-
- if (entityLogic.IsLocalControlled && entityLogic.IsPredicted && AliveEntities.Contains(entity))
+ fixed (byte* predictedData = classData.GetLastServerData(entity))
{
- UpdateMode = UpdateMode.PredictionRollback;
- for(int cmdNum = Utils.SequenceDiff(ServerTick, _stateA.Tick); cmdNum < _storedInputHeaders.Count; cmdNum++)
+ for (int i = 0; i < classData.FieldsCount; i++)
{
- //reapply input data
- var storedInput = _storedInputHeaders[cmdNum];
- _localPlayer.LoadInputInfo(storedInput.Header);
- RollBackTick = storedInput.Tick;
- foreach (var controller in GetEntities())
- controller.ReadStoredInput(cmdNum);
+ ref var field = ref classData.Fields[i];
+ var target = field.GetTargetObjectAndOffset(entity, out int offset);
+
+ if (writeInterpolationData && field.Flags.HasFlagFast(SyncFlags.Interpolated))
+ field.TypeProcessor.SetInterpValue(target, offset, rawData + readerPosition);
- if (cmdNum == _storedInputHeaders.Count - 1)
+ if (field.IsPredicted)
+ RefMagic.CopyBlock(predictedData + field.PredictedOffset, rawData + readerPosition, field.Size);
+
+ if (field.OnSync != null && (field.OnSyncFlags & BindOnChangeFlags.ExecuteOnSync) != 0)
{
- for(int i = 0; i < classData.InterpolatedCount; i++)
- {
- fixed (byte* prevDataPtr = classData.ClientInterpolatedPrevData(entity))
- {
- ref var field = ref classData.Fields[i];
- field.TypeProcessor.WriteTo(entity, field.Offset, prevDataPtr + field.FixedOffset);
- }
- }
+ if (field.TypeProcessor.SetFromAndSync(target, offset, rawData + readerPosition))
+ _syncCalls[_syncCallsCount++] = new SyncCallInfo(entity, readerPosition, i);
}
- entity.Update();
- }
- for (int i = 0; i < classData.InterpolatedCount; i++)
- {
- fixed (byte* currentDataPtr = classData.ClientInterpolatedNextData(entity))
+ else
{
- ref var field = ref classData.Fields[i];
- field.TypeProcessor.WriteTo(entity, field.Offset, currentDataPtr + field.FixedOffset);
+ field.TypeProcessor.SetFrom(target, offset, rawData + readerPosition);
}
+
+ //Logger.Log($"E {entity.Id} Field updated: {field.Name} = {field.TypeProcessor.ToString(entity, field.Offset)}");
+ readerPosition += field.IntSize;
}
-
- UpdateMode = UpdateMode.Normal;
}
+
+ //add owned entities to rollback queue
+ if(!entity.IsConstructed && entity.InternalOwnerId.Value == InternalPlayerId)
+ _entitiesToRollback.Enqueue(entity);
+
+ //Construct
+ ConstructEntity(entity);
+
+ //Logger.Log($"ConstructedEntity: {entityId}, pid: {entityLogic.PredictedId}");
}
- private unsafe void ReadDiff(ref int readerPosition)
+ private unsafe void ReadDiff()
{
+ int readerPosition = _stateA.DataOffset;
fixed (byte* rawData = _stateA.Data)
{
while (readerPosition < _stateA.DataOffset + _stateA.DataSize)
@@ -982,39 +1033,76 @@ private unsafe void ReadDiff(ref int readerPosition)
readerPosition = endPos;
continue;
}
+
+ ApplyEntityDelta(entity, rawData, readerPosition, false);
+ readerPosition = endPos;
+ }
+ }
+ }
- ref var classData = ref entity.ClassData;
- bool writeInterpolationData = entity.IsRemoteControlled;
- readerPosition += classData.FieldsFlagsSize;
- _changedEntities.Add(entity);
- Utils.ResizeOrCreate(ref _syncCalls, _syncCallsCount + classData.FieldsCount);
+ private unsafe void ApplyEntityDelta(InternalEntity entity, byte* rawData, int fieldsFlagsOffset, bool insideNewRPC)
+ {
+ ref var classData = ref entity.ClassData;
+ _changedEntities.Add(entity);
+ if (!insideNewRPC)
+ Utils.ResizeOrCreate(ref _syncCalls, _syncCallsCount + classData.FieldsCount);
+ int readerPosition = fieldsFlagsOffset + classData.FieldsFlagsSize;
+
+ fixed (byte* predictedData = classData.GetLastServerData(entity), zeroFieldData = _zeroFieldBuffer)
+ {
+ for (int i = 0; i < classData.FieldsCount; i++)
+ {
+ ref var field = ref classData.Fields[i];
+ bool hasData = Utils.IsBitSet(rawData + fieldsFlagsOffset, i);
+
+ byte* fieldData;
+ int fieldSize = field.IntSize;
- int fieldsFlagsOffset = readerPosition - classData.FieldsFlagsSize;
- fixed (byte* interpDataPtr = classData.ClientInterpolatedNextData(entity),
- predictedData = classData.ClientPredictedData(entity))
+ //inside new rpc StateSerializer compares data with zeroes. So use same logic to decode
+ if (insideNewRPC && !hasData)
{
- for (int i = 0; i < classData.FieldsCount; i++)
+ if (fieldSize > StateSerializer.MaxStateSize)
{
- if (!Utils.IsBitSet(rawData + fieldsFlagsOffset, i))
- continue;
- ref var field = ref classData.Fields[i];
- if (field.ReadField(
- entity,
- rawData + readerPosition,
- predictedData,
- writeInterpolationData ? interpDataPtr : null,
- null))
- {
- _syncCalls[_syncCallsCount++] = new SyncCallInfo(field.OnSync, entity, readerPosition,
- field.IntSize);
- }
-
- //Logger.Log($"E {entity.Id} Field updated: {field.Name}");
- readerPosition += field.IntSize;
+ Logger.LogError($"Field too large for zero buffer. Entity: {entity}. Field: {field.Name}. Size: {fieldSize}");
+ continue;
}
+ fieldData = zeroFieldData;
+ }
+ else if (!hasData)
+ {
+ continue;
+ }
+ else
+ {
+ fieldData = rawData + readerPosition;
+ }
+
+ var target = field.GetTargetObjectAndOffset(entity, out int offset);
+
+ if (field.IsPredicted)
+ RefMagic.CopyBlock(predictedData + field.PredictedOffset, fieldData, field.Size);
+
+
+ if (field.OnSync != null && insideNewRPC)
+ {
+ if ((field.OnSyncFlags & BindOnChangeFlags.ExecuteOnNew) != 0)
+ {
+ //execute immediately using temp data buffer inside SetFromAndSync
+ field.TypeProcessor.SetFromAndSync(target, offset, fieldData, field.OnSync);
+ }
+ //else skip set? because will be triggered in OnConstructed
+ }
+ else if (field.OnSync != null && (field.OnSyncFlags & BindOnChangeFlags.ExecuteOnSync) != 0 && field.TypeProcessor.SetFromAndSync(target, offset, fieldData))
+ {
+ _syncCalls[_syncCallsCount++] = new SyncCallInfo(entity, readerPosition, i);
+ }
+ else
+ {
+ field.TypeProcessor.SetFrom(target, offset, fieldData);
}
- readerPosition = endPos;
+ if (hasData)
+ readerPosition += field.IntSize;
}
}
}
@@ -1032,7 +1120,7 @@ private static bool IsEntityIdValid(ushort id)
public void GetDiagnosticData(Dictionary diagnosticDataDict)
{
diagnosticDataDict.Clear();
- _stateA?.GetDiagnosticData(EntitiesDict, ClassDataDict, diagnosticDataDict);
+ _stateA?.GetDiagnosticData(diagnosticDataDict);
}
}
}
\ No newline at end of file
diff --git a/Assets/Plugins/LiteEntitySystem/Collections/AVLTree.cs b/Assets/Plugins/LiteEntitySystem/Collections/AVLTree.cs
index b89c25f..2f1104b 100644
--- a/Assets/Plugins/LiteEntitySystem/Collections/AVLTree.cs
+++ b/Assets/Plugins/LiteEntitySystem/Collections/AVLTree.cs
@@ -147,7 +147,7 @@ private static int RightLeftRotate(TreeNode[] nodes, int a)
return c;
}
- internal virtual void Add(T data)
+ internal void Add(T data)
{
if (_nodesCount + 1 == int.MaxValue)
throw new Exception("collection overflow");
diff --git a/Assets/Plugins/LiteEntitySystem/Collections/BitSpan.cs b/Assets/Plugins/LiteEntitySystem/Collections/BitSpan.cs
index d3da66b..bbb4a46 100644
--- a/Assets/Plugins/LiteEntitySystem/Collections/BitSpan.cs
+++ b/Assets/Plugins/LiteEntitySystem/Collections/BitSpan.cs
@@ -20,21 +20,21 @@ public BitSpan(Span bitRegion)
public BitSpan(Span bitRegion, int bitCount)
{
BitCount = bitCount;
- ByteCount = bitCount / BitsInByte + (bitCount % BitsInByte == 0 ? 0 : 1);
+ ByteCount = (bitCount + 7) / BitsInByte;
_bitRegion = bitRegion;
}
public unsafe BitSpan(byte* bitRegion, int bitCount)
{
BitCount = bitCount;
- ByteCount = bitCount / BitsInByte + (bitCount % BitsInByte == 0 ? 0 : 1);
+ ByteCount = (bitCount + 7) / BitsInByte;
_bitRegion = new Span(bitRegion, ByteCount);
}
public BitSpan(byte[] bitRegion, int offset, int bitCount)
{
BitCount = bitCount;
- ByteCount = bitCount / BitsInByte + (bitCount % BitsInByte == 0 ? 0 : 1);
+ ByteCount = (bitCount + 7) / BitsInByte;
_bitRegion = new Span(bitRegion, offset, ByteCount);
}
@@ -82,28 +82,28 @@ public BitReadOnlySpan(Span bitRegion)
public BitReadOnlySpan(Span bitRegion, int bitCount)
{
BitCount = bitCount;
- ByteCount = bitCount / BitsInByte + (bitCount % BitsInByte == 0 ? 0 : 1);
+ ByteCount = (bitCount + 7) / BitsInByte;
_bitRegion = bitRegion;
}
public BitReadOnlySpan(ReadOnlySpan bitRegion, int bitCount)
{
BitCount = bitCount;
- ByteCount = bitCount / BitsInByte + (bitCount % BitsInByte == 0 ? 0 : 1);
+ ByteCount = (bitCount + 7) / BitsInByte;
_bitRegion = bitRegion;
}
public unsafe BitReadOnlySpan(byte* bitRegion, int bitCount)
{
BitCount = bitCount;
- ByteCount = bitCount / BitsInByte + (bitCount % BitsInByte == 0 ? 0 : 1);
+ ByteCount = (bitCount + 7) / BitsInByte;
_bitRegion = new Span(bitRegion, ByteCount);
}
public BitReadOnlySpan(byte[] bitRegion, int offset, int bitCount)
{
BitCount = bitCount;
- ByteCount = bitCount / BitsInByte + (bitCount % BitsInByte == 0 ? 0 : 1);
+ ByteCount = (bitCount + 7) / BitsInByte;
_bitRegion = new Span(bitRegion, offset, ByteCount);
}
diff --git a/Assets/Plugins/LiteEntitySystem/Collections/SequenceBinaryHeap.cs b/Assets/Plugins/LiteEntitySystem/Collections/SequenceBinaryHeap.cs
index 7d77466..6e8d334 100644
--- a/Assets/Plugins/LiteEntitySystem/Collections/SequenceBinaryHeap.cs
+++ b/Assets/Plugins/LiteEntitySystem/Collections/SequenceBinaryHeap.cs
@@ -21,6 +21,11 @@ public SequenceBinaryHeap(int capacity)
_data = new SequenceHeapNode[capacity];
_count = 0;
}
+
+ public void Clear()
+ {
+ _count = 0;
+ }
public void Add(T item, ushort sequence)
{
diff --git a/Assets/Plugins/LiteEntitySystem/EntityFilter.cs b/Assets/Plugins/LiteEntitySystem/EntityFilter.cs
index 1e4b9f4..b3d7e37 100644
--- a/Assets/Plugins/LiteEntitySystem/EntityFilter.cs
+++ b/Assets/Plugins/LiteEntitySystem/EntityFilter.cs
@@ -8,6 +8,7 @@ public interface IEntityFilter
{
internal void Add(InternalEntity entity);
internal void Remove(InternalEntity entity);
+ internal void TriggerConstructedEvent(InternalEntity entity);
}
//SortedSet like collection based on AVLTree
@@ -29,7 +30,8 @@ public void SubscribeToConstructed(Action onConstructed, bool callOnExisting)
{
if (callOnExisting)
foreach (var entity in this)
- onConstructed(entity);
+ if(entity.CreationState == EntityState.LateConstructed)
+ onConstructed(entity);
OnConstructed += onConstructed;
}
@@ -46,14 +48,9 @@ internal override bool Remove(T entity)
return base.Remove(entity);
}
- internal override void Add(T data)
- {
- base.Add(data);
- OnConstructed?.Invoke(data);
- }
-
void IEntityFilter.Add(InternalEntity entity) => Add((T) entity);
void IEntityFilter.Remove(InternalEntity entity) => Remove((T) entity);
+ void IEntityFilter.TriggerConstructedEvent(InternalEntity entity) => OnConstructed?.Invoke((T)entity);
internal override void Clear()
{
diff --git a/Assets/Plugins/LiteEntitySystem/EntityLogic.cs b/Assets/Plugins/LiteEntitySystem/EntityLogic.cs
index fe726f8..5307644 100644
--- a/Assets/Plugins/LiteEntitySystem/EntityLogic.cs
+++ b/Assets/Plugins/LiteEntitySystem/EntityLogic.cs
@@ -85,19 +85,6 @@ public ChangeParentData(ChangeType type, EntitySharedReference reference)
[SyncVarFlags(SyncFlags.OnlyForOwner)]
private SyncVar _localPredictedIdCounter;
-
- [SyncVarFlags(SyncFlags.OnlyForOwner)]
- private SyncVar _predictedId;
-
- [SyncVarFlags(SyncFlags.OnlyForOwner)]
- private SyncVar _isPredicted;
-
- internal ulong PredictedId => _predictedId.Value;
-
- ///
- /// Is entity spawned using AddPredictedEntity
- ///
- public bool IsPredicted => _isPredicted.Value;
//specific per player
private SyncVar _isSyncEnabled;
@@ -169,28 +156,56 @@ public void DisableLagCompensationForOwner() =>
EntityManager.DisableLagCompensation();
///
- /// Get synchronized seed for random generators based on current tick. Can be used for rollback or inside RPCs
+ /// Gets owner tick on client if entity is owned, and on server it returns owner tick.
+ /// Useful as seed for random generators based on current tick.
///
- /// current tick depending on entity manager state (IsExecutingRPC and InRollBackState)
- public int GetFrameSeed() =>
- EntityManager.IsClient
- ? (EntityManager.InRollBackState ? ClientManager.RollBackTick : (ClientManager.IsExecutingRPC ? ClientManager.CurrentRPCTick : EntityManager.Tick))
- : (InternalOwnerId.Value == EntityManager.ServerPlayerId ? EntityManager.Tick : ServerManager.GetPlayer(InternalOwnerId).LastProcessedTick);
+ /// true if owner tick accessible, false if you trying get remote player tick
+ public bool TryGetOwnerTick(out ushort tick)
+ {
+ if (EntityManager.IsClient)
+ {
+ if (ClientManager.InternalPlayerId == InternalOwnerId.Value)
+ {
+ tick = EntityManager.Tick;
+ return true;
+ }
+
+ if (InternalOwnerId.Value == EntityManager.ServerPlayerId)
+ {
+ tick = ClientManager.ServerTick;
+ return true;
+ }
+
+ //else
+ tick = EntityManager.Tick;
+ return false;
+ }
+
+ //Server
+ if(InternalOwnerId.Value == EntityManager.ServerPlayerId)
+ {
+ tick = EntityManager.Tick;
+ return true;
+ }
+
+ tick = ServerManager.GetPlayer(InternalOwnerId).LastProcessedTick;
+ return true;
+ }
///
/// Create predicted entity (like projectile) that will be replaced by server entity if prediction is successful
/// Should be called also in rollback mode
+ /// Don't call this method inside Server->Client RPC! This will break many things.
///
/// Entity type
- /// Method that will be called after entity constructed
+ /// Method that will be called before entity OnConstructed
/// Created predicted local entity
- public T AddPredictedEntity(Action initMethod = null) where T : EntityLogic
+ public T AddPredictedEntity(Action initMethod = null) where T : PredictableEntityLogic
{
if (EntityManager.IsServer)
return ServerManager.AddEntity(this, e =>
{
- e._predictedId.Value = _localPredictedIdCounter.Value++;
- e._isPredicted.Value = true;
+ e.InitEntity(_localPredictedIdCounter.Value++, this);
initMethod?.Invoke(e);
});
@@ -212,12 +227,12 @@ public T AddPredictedEntity(Action initMethod = null) where T : EntityLogi
//local counter here should be reset
ushort potentialId = _localPredictedIdCounter.Value++;
- var origEnt = ClientManager.FindEntityByPredictedId(ClientManager.RollBackTick, Id, potentialId);
+ var origEnt = ClientManager.FindEntityByPredictedId(ClientManager.Tick, Id, potentialId);
entity = origEnt as T;
if (entity == null)
{
- Logger.LogWarning($"Requested RbTick{ClientManager.RollBackTick}, ParentId: {Id}, potentialId: {potentialId}, requestedType: {typeof(T)}, foundType: {origEnt?.GetType()}");
- Logger.LogWarning($"Misspredicted entity add? RbTick: {ClientManager.RollBackTick}, potentialId: {potentialId}");
+ Logger.LogWarning($"Requested RbTick{ClientManager.Tick}, ParentId: {Id}, potentialId: {potentialId}, requestedType: {typeof(T)}, foundType: {origEnt?.GetType()}");
+ Logger.LogWarning($"Misspredicted entity add? RbTick: {ClientManager.Tick}, potentialId: {potentialId}");
}
else
{
@@ -229,8 +244,7 @@ public T AddPredictedEntity(Action initMethod = null) where T : EntityLogi
entity = ClientManager.AddLocalEntity(this, e =>
{
- e._predictedId.Value = _localPredictedIdCounter.Value++;
- e._isPredicted.Value = true;
+ e.InitEntity(_localPredictedIdCounter.Value++, this);
//Logger.Log($"AddPredictedEntity. Id: {e.Id}, ClassId: {e.ClassId} PredId: {e._predictedId.Value}, Tick: {e.ClientManager.Tick}");
initMethod?.Invoke(e);
});
@@ -243,9 +257,9 @@ public T AddPredictedEntity(Action initMethod = null) where T : EntityLogi
///
/// Entity type
/// SyncVar of class that will be set to predicted entity and synchronized once confirmation will be received
- /// Method that will be called after entity constructed
+ /// Method that will be called before entity OnConstructed
/// Created predicted local entity
- public void AddPredictedEntity(ref SyncVar targetReference, Action initMethod = null) where T : EntityLogic
+ public void AddPredictedEntity(ref SyncVar targetReference, Action initMethod = null) where T : PredictableEntityLogic
{
T entity;
if (EntityManager.InRollBackState)
@@ -260,7 +274,11 @@ public void AddPredictedEntity(ref SyncVar targetRefer
if (EntityManager.IsServer)
{
- entity = ServerManager.AddEntity(this, initMethod);
+ entity = ServerManager.AddEntity(this, e =>
+ {
+ e.InitEntity(_localPredictedIdCounter.Value++, this);
+ initMethod?.Invoke(e);
+ });
targetReference.Value = entity;
return;
}
@@ -273,7 +291,7 @@ public void AddPredictedEntity(ref SyncVar targetRefer
entity = ClientManager.AddLocalEntity(this, e =>
{
- e._predictedId.Value = _localPredictedIdCounter.Value++;
+ e.InitEntity(_localPredictedIdCounter.Value++, this);
initMethod?.Invoke(e);
});
targetReference.Value = entity;
@@ -361,8 +379,7 @@ internal override void DestroyInternal()
{
ClientManager.RemoveOwned(this);
}
- var parent = EntityManager.GetEntityById(_parentRef);
- if (parent != null && !parent.IsDestroyed)
+ if (EntityManager.TryGetEntityById(_parentRef, out var parent) && !parent.IsDestroyed)
{
parent.Childs.Remove(this);
}
@@ -406,11 +423,9 @@ protected virtual void OnChildRemoved(EntityLogic child)
}
- internal void RefreshOwnerInfo(EntityLogic oldOwner)
+ private void OnOwnerChanged(byte oldPlayerId)
{
- if (EntityManager.IsServer || IsLocal)
- return;
- if(oldOwner != null && oldOwner.InternalOwnerId.Value == EntityManager.InternalPlayerId)
+ if(oldPlayerId == EntityManager.InternalPlayerId)
ClientManager.RemoveOwned(this);
if(InternalOwnerId.Value == EntityManager.InternalPlayerId)
ClientManager.AddOwned(this);
@@ -420,7 +435,8 @@ protected override void RegisterRPC(ref RPCRegistrator r)
{
base.RegisterRPC(ref r);
- r.BindOnChange(ref _isSyncEnabled, (e, _) => e.OnSyncGroupsChanged(e._isSyncEnabled.Value));
+ r.BindOnChange(ref _isSyncEnabled, (e, _) => e.OnSyncGroupsChanged(e._isSyncEnabled.Value));
+ r.BindOnChange(this, ref InternalOwnerId, OnOwnerChanged);
r.CreateRPCAction(
(e, data) =>
@@ -429,7 +445,6 @@ protected override void RegisterRPC(ref RPCRegistrator r)
{
case ChangeType.Parent:
var oldOwner = e.EntityManager.GetEntityById(data.Ref);
- e.RefreshOwnerInfo(oldOwner);
e.OnParentChanged(oldOwner);
break;
case ChangeType.ChildAdd:
diff --git a/Assets/Plugins/LiteEntitySystem/EntityManager.cs b/Assets/Plugins/LiteEntitySystem/EntityManager.cs
index e57911b..eaa9ded 100644
--- a/Assets/Plugins/LiteEntitySystem/EntityManager.cs
+++ b/Assets/Plugins/LiteEntitySystem/EntityManager.cs
@@ -66,6 +66,18 @@ public enum DeserializeResult
Error,
HeaderCheckFailed
}
+
+ [Flags]
+ public enum DebugFrameModes
+ {
+ Client = 1 << 0,
+ Server = 1 << 1,
+ Rollback = 1 << 2,
+
+ All = Client | Server | Rollback,
+ ClientAndRollback = Client | Rollback,
+ ClientAndServer = Server | Client
+ }
public enum MaxHistorySize : byte
{
@@ -91,10 +103,19 @@ public abstract class EntityManager
///
/// Maximum synchronized (without LocalOnly) entities
///
- public const int MaxSyncedEntityCount = 32767;
+ public const int MaxSyncedEntityCount = 64000;
- public const int MaxEntityCount = MaxSyncedEntityCount * 2;
+ //because 0 - invalid id
+ public const int MaxEntityCount = ushort.MaxValue - 1;
+
+ ///
+ /// Maximum count of predicted local entities
+ ///
+ public const int MaxLocalEntityCount = MaxEntityCount - MaxSyncedEntityCount;
+ ///
+ /// Server player Id - always 0 and reserved
+ ///
public const byte ServerPlayerId = 0;
///
@@ -162,6 +183,9 @@ public abstract class EntityManager
///
public byte PlayerId => InternalPlayerId;
+ ///
+ /// EntityManager packets header byte (can be used to distinguish LES packets from custom made)
+ ///
public readonly byte HeaderByte;
public bool InRollBackState => UpdateMode == UpdateMode.PredictionRollback;
@@ -171,7 +195,16 @@ public abstract class EntityManager
internal const int MaxParts = 256;
private const int MaxTicksPerUpdate = 5;
+ ///
+ /// Delta time between visual Updates
+ ///
public double VisualDeltaTime { get; private set; }
+
+ ///
+ /// Delta time between visual Updates (float)
+ ///
+ public float VisualDeltaTimeF => (float)VisualDeltaTime;
+
public const int MaxPlayers = byte.MaxValue-1;
protected ushort _tick;
@@ -210,6 +243,11 @@ public abstract class EntityManager
/// IsRunning - sets to false after Reset() call
///
public bool IsRunning => _stopwatch.IsRunning;
+
+ ///
+ /// Is lag compensation currently enabled
+ ///
+ public bool IsLagCompensationEnabled => _lagCompensationEnabled;
///
/// Register custom field type with interpolation
@@ -217,6 +255,12 @@ public abstract class EntityManager
/// interpolation function
public static void RegisterFieldType(InterpolatorDelegateWithReturn interpolationDelegate) where T : unmanaged =>
ValueTypeProcessor.Registered[typeof(T)] = new UserTypeProcessor(interpolationDelegate);
+
+ ///
+ /// Get information about current update frame
+ ///
+ /// debug formatted string
+ public abstract string GetCurrentFrameDebugInfo(DebugFrameModes modes);
///
/// Register custom field type
@@ -316,8 +360,12 @@ protected EntityManager(
public void GetEntitySyncVarInfo(InternalEntity entity, IEntitySyncVarInfoPrinter resultPrinter)
{
ref var classData = ref ClassDataDict[entity.ClassId];
- foreach (EntityFieldInfo fi in classData.Fields)
- resultPrinter.PrintFieldInfo(fi.Name, fi.TypeProcessor.ToString(entity, fi.Offset));
+ for(int i = 0; i < classData.FieldsCount; i++)
+ {
+ ref var fi = ref classData.Fields[i];
+ var target = fi.GetTargetObjectAndOffset(entity, out int offset);
+ resultPrinter.PrintFieldInfo(fi.Name, fi.TypeProcessor.ToString(target, offset));
+ }
}
///
@@ -325,18 +373,18 @@ public void GetEntitySyncVarInfo(InternalEntity entity, IEntitySyncVarInfoPrinte
///
public virtual void Reset()
{
- EntitiesCount = 0;
-
- _tick = 0;
- VisualDeltaTime = 0.0;
- _accumulator = 0;
- _lastTime = 0;
- InternalPlayerId = 0;
- _stopwatch.Stop();
- _stopwatch.Reset();
+ var entityFilter = GetEntities();
+ var entitiesToDestroy = new InternalEntity[entityFilter.Count];
+ int idx = 0;
foreach (var entity in GetEntities())
- entity.DestroyInternal();
+ entitiesToDestroy[idx++] = entity;
+
+ for (int i = 0; i < entitiesToDestroy.Length; i++)
+ entitiesToDestroy[i].DestroyInternal();
+
+ for (int i = 0; i < entitiesToDestroy.Length; i++)
+ RemoveEntity(entitiesToDestroy[i]);
foreach (var localSingleton in _localSingletons)
localSingleton.Value?.Destroy();
@@ -345,8 +393,18 @@ public virtual void Reset()
AliveEntities.Clear();
_localSingletons.Clear();
_localSingletonBaseTypes.Clear();
- Array.Clear(EntitiesDict, 0, EntitiesDict.Length);
Array.Clear(_entityFilters, 0, _entityFilters.Length);
+
+ if(EntitiesCount != 0)
+ Logger.LogWarning($"Entities not fully removed: {EntitiesCount}");
+ EntitiesCount = 0;
+
+ _tick = 0;
+ VisualDeltaTime = 0.0;
+ _accumulator = 0;
+ _lastTime = 0;
+ _stopwatch.Stop();
+ _stopwatch.Reset();
}
///
@@ -391,8 +449,12 @@ public EntityFilter GetEntities() where T : InternalEntity
{
foreach (var entity in GetEntities())
{
- if(entity is T castedEnt)
+ if (entity is T castedEnt)
+ {
typedFilter.Add(castedEnt);
+ if(entity.CreationState == EntityState.LateConstructed)
+ entityFilter.TriggerConstructedEvent(entity);
+ }
}
}
@@ -518,11 +580,11 @@ protected T AddEntity(EntityParams entityParams) where T : InternalEntity
return entity;
}
- protected bool ConstructEntity(InternalEntity e)
+ protected void ConstructEntity(InternalEntity e)
{
if (e.IsConstructed)
- return false;
- e.IsConstructed = true;
+ return;
+ e.ConstructInternal();
ref var classData = ref ClassDataDict[e.ClassId];
if (classData.IsSingleton)
@@ -547,16 +609,8 @@ protected bool ConstructEntity(InternalEntity e)
if (IsEntityAlive(classData.Flags, e))
{
AliveEntities.Add(e);
- OnAliveEntityAdded(e);
}
_entitiesToLateConstruct.Enqueue(e);
-
- return true;
- }
-
- protected virtual void OnAliveEntityAdded(InternalEntity e)
- {
-
}
protected static bool IsEntityLagCompensated(InternalEntity e)
@@ -593,6 +647,10 @@ internal virtual void OnEntityDestroyed(InternalEntity e)
protected void RemoveEntity(InternalEntity e)
{
+ //this can happen on entity replace.
+ if (e.IsRemoved)
+ return;
+
if(!e.IsDestroyed)
Logger.LogError($"Remove not destroyed entity!: {e}");
@@ -600,7 +658,7 @@ protected void RemoveEntity(InternalEntity e)
EntitiesDict[e.Id] = null;
EntitiesCount--;
ClassDataDict[e.ClassId].ReleaseDataCache(e);
- e.IsRemoved = true;
+ e.Remove();
//Logger.Log($"{Mode} - RemoveEntity: {e.Id}");
}
@@ -633,16 +691,41 @@ public void DisableLagCompensation()
protected void ExecuteLateConstruct()
{
- foreach (var internalEntity in _entitiesToLateConstruct)
- internalEntity.OnLateConstructed();
+ foreach (var e in _entitiesToLateConstruct)
+ {
+ if (e.IsDestroyed)
+ continue;
+ e.LateConstructInternal();
+
+ ref var classData = ref ClassDataDict[e.ClassId];
+ if (classData.IsSingleton)
+ {
+ foreach (var baseTypeInfo in classData.BaseTypes)
+ if(!baseTypeInfo.IsSingleton)
+ _entityFilters[baseTypeInfo.Id]?.TriggerConstructedEvent(e);
+ }
+ else
+ {
+ _entityFilters[classData.FilterId]?.TriggerConstructedEvent(e);
+ foreach (var baseTypeInfo in classData.BaseTypes)
+ _entityFilters[baseTypeInfo.Id]?.TriggerConstructedEvent(e);
+ }
+ }
_entitiesToLateConstruct.Clear();
}
protected abstract void OnLogicTick();
-
- internal abstract void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue)
+
+ internal abstract void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue, ref T oldValue, bool skipOnSync)
where T : unmanaged;
+ protected void ExecuteLocalSingletonsLateUpdate()
+ {
+ foreach (var localSingleton in _localSingletons)
+ if(localSingleton.Value is ILocalSingletonWithUpdate updSingleton)
+ updSingleton.LateUpdate(DeltaTimeF);
+ }
+
///
/// Main update method, updates internal fixed timer and do all other stuff
///
diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/LocalMessageBus.cs b/Assets/Plugins/LiteEntitySystem/Extensions/LocalMessageBus.cs
new file mode 100644
index 0000000..129db85
--- /dev/null
+++ b/Assets/Plugins/LiteEntitySystem/Extensions/LocalMessageBus.cs
@@ -0,0 +1,192 @@
+using System;
+using System.Collections.Generic;
+using LiteEntitySystem;
+using LiteEntitySystem.Internal;
+
+namespace LiteEntitySystem.Extensions
+{
+ ///
+ /// Represents a subscription to a message bus channel that can be disposed to unsubscribe.
+ ///
+ public readonly struct BusSubscription : IDisposable
+ {
+ private readonly Action _dispose;
+ public BusSubscription(Action dispose) => _dispose = dispose;
+
+ ///
+ /// Disposes the subscription, unsubscribing the handler from the message bus.
+ ///
+ public void Dispose() => _dispose?.Invoke();
+ }
+
+ ///
+ /// Marker interface for message bus messages. TSender declares the expected sender type for the message.
+ ///
+ /// The type of the entity that sends this message.
+ public interface IBusMessage
+ {
+ }
+
+ ///
+ /// A local message bus for publishing and subscribing to typed messages within a local singleton context.
+ ///
+ public sealed class LocalMessageBus : ILocalSingleton
+ {
+ ///
+ /// If true, all published messages will be logged for debugging purposes.
+ ///
+ public bool DebugToLog;
+
+ private interface IChannel : IDisposable { }
+
+ private interface IChannel : IChannel
+ where TMessage : struct, IBusMessage
+ {
+ BusSubscription Subscribe(Action handler);
+ void Publish(TSender sender, in TMessage msg, bool debugToLog = false);
+ }
+
+ // Null-object channel used after the bus is disposed to avoid null checks and NREs
+ private sealed class NullChannel : IChannel
+ where TMessage : struct, IBusMessage
+ {
+ public static readonly NullChannel Instance = new();
+ public BusSubscription Subscribe(Action handler) => default;
+ public void Publish(TSender sender, in TMessage msg, bool debugToLog = false) { }
+ public void Dispose() { }
+ }
+
+ private sealed class Channel : IChannel
+ where TMessage : struct, IBusMessage
+ {
+ private const int PreallocatedSize = 8;
+ private Action[] _buffer;
+
+ private readonly List> _handlers = new();
+ private bool _disposed;
+
+ public BusSubscription Subscribe(Action handler)
+ {
+ if (_disposed) throw new ObjectDisposedException(nameof(Channel));
+ _handlers.Add(handler);
+ return new BusSubscription(() => Unsubscribe(handler));
+ }
+
+ private void Unsubscribe(Action handler)
+ {
+ _handlers.Remove(handler);
+ }
+
+ public void Publish(TSender sender, in TMessage msg, bool debugToLog = false)
+ {
+ if (_disposed || _handlers == null || _handlers.Count == 0)
+ return;
+
+ var count = _handlers.Count;
+ if (_buffer == null || _buffer.Length < count)
+ _buffer = new Action[Math.Max(count, PreallocatedSize)];
+
+ // Fast copy handlers into buffer
+ _handlers.CopyTo(0, _buffer, 0, count);
+
+ for (int i = 0; i < count; i++)
+ {
+ if (debugToLog)
+ {
+ Logger.Log($"MsgBus: {typeof(TSender)} -> {_buffer[i].Target}, msg: {msg}");
+ }
+ _buffer[i](sender, msg);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+ _handlers.Clear();
+ }
+ }
+
+ private readonly Dictionary _channels = new();
+ private bool _disposed;
+
+ private IChannel Get()
+ where TMessage : struct, IBusMessage
+ {
+ if (_disposed)
+ return NullChannel.Instance;
+
+ var t = typeof(TMessage);
+ if (_channels.TryGetValue(t, out var ch))
+ return (IChannel)ch;
+
+ var created = new Channel();
+ _channels.Add(t, created);
+ return created;
+ }
+
+ ///
+ /// Subscribes a handler to receive messages of a specific type.
+ ///
+ /// The type of the sender of the message.
+ /// The type of message to subscribe to.
+ /// The handler delegate to invoke when messages are published.
+ /// A subscription token that can be disposed to unsubscribe.
+ public BusSubscription Subscribe(Action handler)
+ where TMessage : struct, IBusMessage
+ => Get().Subscribe(handler);
+
+ ///
+ /// Publishes a message to all subscribers.
+ ///
+ /// The type of the sender of the message.
+ /// The type of message to publish.
+ /// The entity sending the message.
+ /// The message to publish.
+ public void Send(TSender sender, in TMessage msg)
+ where TMessage : struct, IBusMessage
+ => Get().Publish(sender, in msg, DebugToLog);
+
+ ///
+ /// Destroys the message bus, disposing all channels and unsubscribing all handlers.
+ ///
+ public void Destroy()
+ {
+ if (_disposed) return;
+ _disposed = true;
+ foreach (var ch in _channels.Values)
+ ch.Dispose();
+ _channels.Clear();
+ }
+ }
+
+ ///
+ /// Extension methods for accessing the local message bus from entities.
+ ///
+ public static class LocalMessageBusExtensions
+ {
+ ///
+ /// Gets or creates a local message bus singleton for the entity manager.
+ ///
+ /// The entity to get the message bus from.
+ /// The local message bus instance, creating it if it doesn't exist.
+ public static LocalMessageBus GetOrCreateLocalMessageBus(this InternalEntity ent)
+ {
+ var msgBus = ent.EntityManager.GetLocalSingleton();
+ if(msgBus == null)
+ {
+ msgBus = new LocalMessageBus();
+ ent.EntityManager.AddLocalSingleton(msgBus);
+ }
+ return msgBus;
+ }
+
+ ///
+ /// Gets the local message bus singleton from the entity manager.
+ ///
+ /// The entity to get the message bus from.
+ /// The local message bus instance, or null if not yet created.
+ public static LocalMessageBus GetLocalMessageBus(this InternalEntity ent) =>
+ ent.EntityManager.GetLocalSingleton();
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/LocalMessageBus.cs.meta b/Assets/Plugins/LiteEntitySystem/Extensions/LocalMessageBus.cs.meta
new file mode 100644
index 0000000..60b61bf
--- /dev/null
+++ b/Assets/Plugins/LiteEntitySystem/Extensions/LocalMessageBus.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a935e910dd44ccf1696a1b7fd0028dc5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncDict.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncDict.cs
index 232112d..5e45273 100644
--- a/Assets/Plugins/LiteEntitySystem/Extensions/SyncDict.cs
+++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncDict.cs
@@ -4,7 +4,7 @@
namespace LiteEntitySystem.Extensions
{
- public class SyncDict : SyncableField, IEnumerable> where TKey : unmanaged where TValue : unmanaged
+ public class SyncDict : SyncableFieldCustomRollback, IEnumerable> where TKey : unmanaged where TValue : unmanaged
{
private struct KeyValue
{
@@ -31,8 +31,6 @@ public KeyValue(TKey key, TValue value)
public Dictionary.KeyCollection Keys => _data.Keys;
public Dictionary.ValueCollection Values => _data.Values;
-
- public override bool IsRollbackSupported => true;
protected internal override void RegisterRPC(ref SyncableRPCRegistrator r)
{
@@ -85,18 +83,21 @@ private void InitAction(ReadOnlySpan data)
private void AddAction(KeyValue kv)
{
_data[kv.Key] = kv.Value;
+ MarkAsChanged();
}
public void Add(TKey key, TValue value)
{
_data.Add(key, value);
ExecuteRPC(_addAction, new KeyValue(key,value));
+ MarkAsChanged();
}
public void Clear()
{
_data.Clear();
ExecuteRPC(_clearAction);
+ MarkAsChanged();
}
public bool TryGetValue(TKey key, out TValue value)
@@ -124,6 +125,7 @@ public bool Remove(TKey key)
if (!_data.Remove(key))
return false;
ExecuteRPC(_removeAction, key);
+ MarkAsChanged();
return true;
}
diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncHashSet.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncHashSet.cs
index 3ed1c83..be9d8e4 100644
--- a/Assets/Plugins/LiteEntitySystem/Extensions/SyncHashSet.cs
+++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncHashSet.cs
@@ -4,7 +4,7 @@
namespace LiteEntitySystem.Extensions
{
- public class SyncHashSet : SyncableField, IEnumerable where T : unmanaged
+ public class SyncHashSet : SyncableFieldCustomRollback, IEnumerable where T : unmanaged
{
public int Count => _data.Count;
@@ -16,8 +16,6 @@ public class SyncHashSet : SyncableField, IEnumerable where T : unmanaged
private static RemoteCall _clearAction;
private static RemoteCall _removeAction;
private static RemoteCallSpan _initAction;
-
- public override bool IsRollbackSupported => true;
protected internal override void RegisterRPC(ref SyncableRPCRegistrator r)
{
@@ -86,12 +84,14 @@ public void Add(T x)
{
_data.Add(x);
ExecuteRPC(_addAction, x);
+ MarkAsChanged();
}
public void Clear()
{
_data.Clear();
ExecuteRPC(_clearAction);
+ MarkAsChanged();
}
public bool Contains(T x) => _data.Contains(x);
@@ -103,6 +103,7 @@ public bool Remove(T key)
if (!_data.Remove(key))
return false;
ExecuteRPC(_removeAction, key);
+ MarkAsChanged();
return true;
}
diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncList.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncList.cs
index 2263eb4..ef56c9b 100644
--- a/Assets/Plugins/LiteEntitySystem/Extensions/SyncList.cs
+++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncList.cs
@@ -5,8 +5,28 @@
namespace LiteEntitySystem.Extensions
{
- public class SyncList : SyncableField, ICollection, IReadOnlyList where T : unmanaged
+ public class SyncList : SyncableFieldCustomRollback, ICollection, IReadOnlyList where T : unmanaged
{
+ public struct SyncListEnumerator : IEnumerator
+ {
+ private readonly T[] _data;
+ private readonly int _count;
+ private int _index;
+
+ public SyncListEnumerator(T[] data, int count)
+ {
+ _data = data;
+ _count = count;
+ _index = -1;
+ }
+
+ public bool MoveNext() => ++_index < _count;
+ public void Reset() => _index = -1;
+ public T Current => _index >= 0 && _index < _data.Length ? _data[_index] : default;
+ object IEnumerator.Current => Current;
+ public void Dispose() {}
+ }
+
struct SetValueData
{
public int Index;
@@ -28,8 +48,6 @@ struct SetValueData
private static RemoteCallSpan _initAction;
private static RemoteCall _setAction;
- public override bool IsRollbackSupported => true;
-
protected internal override void BeforeReadRPC()
{
_serverData ??= new T[_data.Length];
@@ -106,12 +124,14 @@ public void Add(T item)
_data[_count] = item;
_count++;
ExecuteRPC(_addAction, item);
+ MarkAsChanged();
}
public void Clear()
{
_count = 0;
ExecuteRPC(_clearAction);
+ MarkAsChanged();
}
public bool Contains(T item)
@@ -158,6 +178,7 @@ public void RemoveAt(int index)
_data[_count - 1] = default;
_count--;
ExecuteRPC(_removeAtAction, index);
+ MarkAsChanged();
}
public T this[int index]
@@ -170,19 +191,10 @@ public T this[int index]
}
}
- public IEnumerator GetEnumerator()
- {
- int index = 0;
- while (index < _count)
- {
- yield return _data[index];
- index++;
- }
- }
+ public SyncListEnumerator GetEnumerator() => new(_data, _count);
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
\ No newline at end of file
diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncPongTimer.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncPongTimer.cs
new file mode 100644
index 0000000..8630812
--- /dev/null
+++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncPongTimer.cs
@@ -0,0 +1,50 @@
+namespace LiteEntitySystem.Extensions
+{
+ public class SyncPongTimer : SyncableField
+ {
+ public float MaxTime => _maxTime;
+ public float ElapsedTime => _time;
+ public bool IsTimeElapsed => _time >= _maxTime;
+ public float CountdownTime => _maxTime - _time;
+ public bool HasStarted => _time > 0;
+
+ private SyncVar _time;
+ private const float _maxTime = 1f;
+
+ public SyncPongTimer() { }
+
+ public float Progress => _time;
+
+ public void Reset()
+ {
+ _time.Value = 0f;
+ }
+
+ public void Finish()
+ {
+ _time.Value = _maxTime;
+ }
+
+ public bool UpdateForward(float delta)
+ {
+ if (delta > 0f)
+ {
+ float newTime = _time.Value + delta;
+ if (newTime > 1f) newTime = 1f;
+ _time.Value = newTime;
+ }
+ return IsTimeElapsed;
+ }
+
+ public bool UpdateBackward(float delta)
+ {
+ if (delta > 0f)
+ {
+ float newTime = _time.Value - delta;
+ if (newTime < 0f) newTime = 0f;
+ _time.Value = newTime;
+ }
+ return !HasStarted;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncPongTimer.cs.meta b/Assets/Plugins/LiteEntitySystem/Extensions/SyncPongTimer.cs.meta
new file mode 100644
index 0000000..9b49d65
--- /dev/null
+++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncPongTimer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 074123fd4653005b096387a88a742e15
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncQueue.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncQueue.cs
index 56a7187..bc695ae 100644
--- a/Assets/Plugins/LiteEntitySystem/Extensions/SyncQueue.cs
+++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncQueue.cs
@@ -4,7 +4,7 @@
namespace LiteEntitySystem.Extensions
{
- public class SyncQueue : SyncableField, IReadOnlyCollection, ICollection where T : unmanaged
+ public class SyncQueue : SyncableFieldCustomRollback, IReadOnlyCollection, ICollection where T : unmanaged
{
// TODO: implement ring buffer instead of using .net's Queue.
@@ -20,9 +20,7 @@ public class SyncQueue : SyncableField, IReadOnlyCollection, ICollection w
public int Count => _data.Count;
public bool IsSynchronized => false;
public object SyncRoot => throw new NotImplementedException("The SyncQueue Collection isn't thread-safe.");
-
- public override bool IsRollbackSupported => true;
-
+
protected internal override void RegisterRPC(ref SyncableRPCRegistrator r)
{
base.RegisterRPC(ref r);
@@ -74,6 +72,7 @@ public void Enqueue(T item)
{
_data.Enqueue(item);
ExecuteRPC(_enqueueAction, item);
+ MarkAsChanged();
}
private void EnqueueClientAction(T item) => _data.Enqueue(item);
@@ -82,6 +81,7 @@ public T Dequeue()
{
var value = _data.Dequeue();
ExecuteRPC(_dequeueAction);
+ MarkAsChanged();
return value;
}
@@ -89,7 +89,10 @@ public bool TryDequeue(out T item)
{
bool hasValue = _data.TryDequeue(out item);
if (hasValue)
+ {
ExecuteRPC(_dequeueAction);
+ MarkAsChanged();
+ }
return hasValue;
}
@@ -105,6 +108,7 @@ public void Clear()
{
_data.Clear();
ExecuteRPC(_clearAction);
+ MarkAsChanged();
}
private void ClearClientAction() => _data.Clear();
diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncStateMachine.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncStateMachine.cs
index 7ac74c6..9ce3b4f 100644
--- a/Assets/Plugins/LiteEntitySystem/Extensions/SyncStateMachine.cs
+++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncStateMachine.cs
@@ -15,8 +15,6 @@ public class SyncStateMachine : SyncableField where T : unmanaged, Enum
public T CurrentState => _state;
- public override bool IsRollbackSupported => true;
-
private readonly StateCalls[] _data;
public SyncStateMachine()
diff --git a/Assets/Plugins/LiteEntitySystem/Extensions/SyncTimer.cs b/Assets/Plugins/LiteEntitySystem/Extensions/SyncTimer.cs
index 1d62cb9..487c1d9 100644
--- a/Assets/Plugins/LiteEntitySystem/Extensions/SyncTimer.cs
+++ b/Assets/Plugins/LiteEntitySystem/Extensions/SyncTimer.cs
@@ -1,6 +1,4 @@
-using LiteEntitySystem.Internal;
-
-namespace LiteEntitySystem.Extensions
+namespace LiteEntitySystem.Extensions
{
public class SyncTimer : SyncableField
{
@@ -13,8 +11,6 @@ public class SyncTimer : SyncableField
private SyncVar _time;
private SyncVar _maxTime;
- public override bool IsRollbackSupported => true;
-
public SyncTimer(float maxTime)
{
_maxTime.Value = maxTime;
@@ -30,6 +26,9 @@ public float Progress
{
get
{
+ if(_maxTime == 0f)
+ return 1f;
+
float p = _time/_maxTime;
return p > 1f ? 1f : p;
}
diff --git a/Assets/Plugins/LiteEntitySystem/HumanControllerLogic.cs b/Assets/Plugins/LiteEntitySystem/HumanControllerLogic.cs
index bb386fe..68336ea 100644
--- a/Assets/Plugins/LiteEntitySystem/HumanControllerLogic.cs
+++ b/Assets/Plugins/LiteEntitySystem/HumanControllerLogic.cs
@@ -240,6 +240,12 @@ public InputCommand(ushort tick, TInput data)
//server part
internal readonly SequenceBinaryHeap AvailableInput;
+ ///
+ /// Returns default new input for custom cases where not all values should be 0
+ ///
+ /// New clean input
+ protected virtual TInput GetDefaultInput() => default;
+
///
/// Get pending input reference for modifications
/// Pending input resets to default after it was assigned to CurrentInput inside logic tick
@@ -249,7 +255,7 @@ protected ref TInput ModifyPendingInput()
{
if (_shouldResetInput)
{
- _pendingInput = default;
+ _pendingInput = GetDefaultInput();
_shouldResetInput = false;
}
return ref _pendingInput;
@@ -276,6 +282,10 @@ protected HumanControllerLogic(EntityParams entityParams) : base(entityParams, U
{
AvailableInput = new(ServerEntityManager.MaxStoredInputs);
}
+
+ // ReSharper disable once VirtualMemberCallInConstructor
+ _currentInput = GetDefaultInput();
+ _pendingInput = _currentInput;
}
internal override void RemoveClientProcessedInputs(ushort processedTick)
@@ -320,7 +330,7 @@ internal override void ApplyIncomingInput(ushort tick)
else if (seqDiff == 0)
{
//Set input if tick equals
- CurrentInput = AvailableInput.ExtractMin();
+ _currentInput = AvailableInput.ExtractMin();
break;
}
}
diff --git a/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.dll b/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.dll
index 5c12d9f..8d3b14b 100644
Binary files a/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.dll and b/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.dll differ
diff --git a/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il b/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il
new file mode 100644
index 0000000..896ad8c
--- /dev/null
+++ b/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il
@@ -0,0 +1,163 @@
+.assembly extern netstandard
+{
+ .publickeytoken = (CC 7B 13 FF CD 2D DD 51 )
+ .ver 2:1:0:0
+}
+.assembly RefMagic
+{
+ .custom instance void [netstandard]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
+ .custom instance void [netstandard]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
+ 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.
+ .custom instance void [netstandard]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 19 2E 4E 45 54 53 74 61 6E 64 61 72 64 2C // ....NETStandard,
+ 56 65 72 73 69 6F 6E 3D 76 32 2E 31 01 00 54 0E // Version=v2.1..T.
+ 14 46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C 61 // .FrameworkDispla
+ 79 4E 61 6D 65 11 2E 4E 45 54 20 53 74 61 6E 64 // yName..NET Stand
+ 61 72 64 20 32 2E 31 ) // ard 2.1 // rdLibrary..
+ .permissionset reqmin
+ = {[netstandard]System.Security.Permissions.SecurityPermissionAttribute = {property bool 'SkipVerification' = bool(true)}}
+ .hash algorithm 0x00008004
+ .ver 1:0:0:0
+}
+.custom instance void [netstandard]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 )
+.imagebase 0x10000000
+.file alignment 0x00000200
+.stackreserve 0x00100000
+.subsystem 0x0003 // WINDOWS_CUI
+.corflags 0x00000001 // ILONLY
+.module RefMagic.dll
+
+.class interface public abstract auto ansi beforefieldinit LiteEntitySystem.Internal.ISyncVar`1<
+ 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 )
+ .method public hidebysig virtual newslot abstract instance void SvSetDirect(!T 'value') cil managed {}
+ .method public hidebysig virtual newslot abstract instance void SvSetDirectAndStorePrev(!T 'value', [out] !T& prevValue) cil managed {}
+ .method public hidebysig virtual newslot abstract instance bool SvSetFromAndSync(!T& 'value') cil managed {}
+ .method public hidebysig virtual newslot abstract instance void SvSetInterpValue(!T 'value') cil managed {}
+ .method public hidebysig virtual newslot abstract instance void SvSetInterpValueFromCurrent() cil managed {}
+}
+
+.class public abstract auto ansi sealed beforefieldinit LiteEntitySystem.Internal.RefMagic extends [netstandard]System.Object
+{
+ .method public hidebysig static !!T GetFieldValue(object obj, int32 offs) cil managed aggressiveinlining
+ {
+ ldarg obj
+ conv.i
+ ldarg offs
+ add
+ ldobj !!T
+ ret
+ }
+
+ .method public hidebysig static void SetFieldValue(object obj, int32 offs, !!T 'value') cil managed aggressiveinlining
+ {
+ ldarg obj
+ conv.i
+ ldarg offs
+ add
+ ldarg 'value'
+ stobj !!T
+ ret
+ }
+
+ .method public hidebysig static void SyncVarSetDirect<
+ valuetype .ctor (class [netstandard]System.ValueType modreq ([netstandard]System.Runtime.InteropServices.UnmanagedType)) T,
+ valuetype .ctor (class LiteEntitySystem.Internal.ISyncVar`1, class [netstandard]System.ValueType) TSyncVar>
+ (object obj, int32 offs, !!T 'value') cil managed aggressiveinlining
+ {
+ .param type T
+ .custom instance void [netstandard]System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = (01 00 00 00 )
+
+ ldarg obj
+ conv.i
+ ldarg offs
+ add
+ ldarg 'value'
+ constrained. !!TSyncVar
+ callvirt instance void class LiteEntitySystem.Internal.ISyncVar`1::SvSetDirect(!0)
+ ret
+ }
+
+ .method public hidebysig static void SyncVarSetInterp<
+ valuetype .ctor (class [netstandard]System.ValueType modreq ([netstandard]System.Runtime.InteropServices.UnmanagedType)) T,
+ valuetype .ctor (class LiteEntitySystem.Internal.ISyncVar`1, class [netstandard]System.ValueType) TSyncVar>
+ (object obj, int32 offs, !!T 'value') cil managed aggressiveinlining
+ {
+ .param type T
+ .custom instance void [netstandard]System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = (01 00 00 00 )
+
+ ldarg obj
+ conv.i
+ ldarg offs
+ add
+ ldarg 'value'
+ constrained. !!TSyncVar
+ callvirt instance void class LiteEntitySystem.Internal.ISyncVar`1::SvSetInterpValue(!0)
+ ret
+ }
+
+ .method public hidebysig static void SyncVarSetInterpFromCurrent<
+ valuetype .ctor (class [netstandard]System.ValueType modreq ([netstandard]System.Runtime.InteropServices.UnmanagedType)) T,
+ valuetype .ctor (class LiteEntitySystem.Internal.ISyncVar`1, class [netstandard]System.ValueType) TSyncVar>
+ (object obj, int32 offs) cil managed aggressiveinlining
+ {
+ .param type T
+ .custom instance void [netstandard]System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = (01 00 00 00 )
+
+ ldarg obj
+ conv.i
+ ldarg offs
+ add
+ constrained. !!TSyncVar
+ callvirt instance void class LiteEntitySystem.Internal.ISyncVar`1::SvSetInterpValueFromCurrent()
+ ret
+ }
+
+ .method public hidebysig static void SyncVarSetDirectAndStorePrev<
+ valuetype .ctor (class [netstandard]System.ValueType modreq([netstandard]System.Runtime.InteropServices.UnmanagedType)) T,
+ valuetype .ctor (class LiteEntitySystem.Internal.ISyncVar`1, [netstandard]System.ValueType) TSyncVar>
+ (object obj, int32 offs, !!T 'value', [out] !!T& prevValue) cil managed aggressiveinlining
+ {
+ .param type T
+ .custom instance void [netstandard]System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = ( 01 00 00 00 )
+
+ ldarg obj
+ conv.i
+ ldarg offs
+ add
+ ldarg 'value'
+ ldarg prevValue
+ constrained. !!TSyncVar
+ callvirt instance void class LiteEntitySystem.Internal.ISyncVar`1::SvSetDirectAndStorePrev(!0, !0&)
+ ret
+ }
+
+ .method public hidebysig static bool SyncVarSetFromAndSync<
+ valuetype .ctor (class [netstandard]System.ValueType modreq([netstandard]System.Runtime.InteropServices.UnmanagedType)) T,
+ valuetype .ctor (class LiteEntitySystem.Internal.ISyncVar`1, [netstandard]System.ValueType) TSyncVar>
+ (object obj, int32 offs, !!T& 'value') cil managed aggressiveinlining
+ {
+ .param type T
+ .custom instance void [netstandard]System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = (01 00 00 00 )
+
+ ldarg obj
+ conv.i
+ ldarg offs
+ add
+ ldarg 'value'
+ constrained. !!TSyncVar
+ callvirt instance bool class LiteEntitySystem.Internal.ISyncVar`1::SvSetFromAndSync(!0&)
+ ret
+ }
+
+ .method public hidebysig static void CopyBlock(void* destination, void* source, unsigned int32 byteCount) cil managed aggressiveinlining
+ {
+ .maxstack 3
+ ldarg.0
+ ldarg.1
+ ldarg.2
+ cpblk
+ ret
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il.meta b/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il.meta
new file mode 100644
index 0000000..e597eb6
--- /dev/null
+++ b/Assets/Plugins/LiteEntitySystem/ILPart/RefMagic.il.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: ed546e403437eb249b60a62a394b4da0
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/LiteEntitySystem/ILocalSingleton.cs b/Assets/Plugins/LiteEntitySystem/ILocalSingleton.cs
index abb7971..0f5d310 100644
--- a/Assets/Plugins/LiteEntitySystem/ILocalSingleton.cs
+++ b/Assets/Plugins/LiteEntitySystem/ILocalSingleton.cs
@@ -9,5 +9,6 @@ public interface ILocalSingletonWithUpdate : ILocalSingleton
{
void Update(float dt);
void VisualUpdate(float dt);
+ void LateUpdate(float dt);
}
}
\ No newline at end of file
diff --git a/Assets/Plugins/LiteEntitySystem/Internal/DeltaCompressor.cs b/Assets/Plugins/LiteEntitySystem/Internal/DeltaCompressor.cs
index 6957c6d..9d4f583 100644
--- a/Assets/Plugins/LiteEntitySystem/Internal/DeltaCompressor.cs
+++ b/Assets/Plugins/LiteEntitySystem/Internal/DeltaCompressor.cs
@@ -18,8 +18,8 @@ internal struct DeltaCompressor
public DeltaCompressor(int size)
{
Size = size;
- DeltaBits = Size / FieldsDivision + (Size % FieldsDivision == 0 ? 0 : 1);
- MinDeltaSize = DeltaBits / 8 + (DeltaBits % 8 == 0 ? 0 : 1);
+ DeltaBits = (Size + FieldsDivision - 1) / FieldsDivision;
+ MinDeltaSize = (DeltaBits + 7) / 8;
MaxDeltaSize = MinDeltaSize + Size;
_firstFullData = null;
}
diff --git a/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs b/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs
index a5d32b5..f004d5a 100644
--- a/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs
+++ b/Assets/Plugins/LiteEntitySystem/Internal/EntityClassData.cs
@@ -63,7 +63,7 @@ internal struct EntityClassData
public readonly int PredictedSize;
public readonly EntityFieldInfo[] Fields;
public readonly SyncableFieldInfo[] SyncableFields;
- public readonly int InterpolatedFieldsSize;
+ public readonly SyncableFieldInfo[] SyncableFieldsCustomRollback;
public readonly int InterpolatedCount;
public readonly EntityFieldInfo[] LagCompensatedFields;
public readonly int LagCompensatedSize;
@@ -74,8 +74,8 @@ internal struct EntityClassData
public RpcFieldInfo[] RemoteCallsClient;
public readonly Type Type;
- private readonly EntityFieldInfo[] _ownedRollbackFields;
- private readonly EntityFieldInfo[] _remoteRollbackFields;
+ private readonly int[] _ownedRollbackFields;
+ private readonly int[] _remoteRollbackFields;
private static readonly Type InternalEntityType = typeof(InternalEntity);
internal static readonly Type SingletonEntityType = typeof(SingletonEntityLogic);
@@ -86,14 +86,13 @@ internal struct EntityClassData
private readonly int _dataCacheSize;
private readonly int _maxHistoryCount;
private readonly int _historyStart;
+
+ public Span GetLastServerData(InternalEntity e) => new (e.IOBuffer, 0, 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) =>
+ public int[] GetRollbackFields(bool isOwned) =>
isOwned ? _ownedRollbackFields : _remoteRollbackFields;
+ //here Offset used because SyncableFields doesn't support LagCompensated fields
public unsafe void WriteHistory(EntityLogic e, ushort tick)
{
int historyOffset = ((tick % _maxHistoryCount)+1)*LagCompensatedSize;
@@ -174,7 +173,6 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp
FixedFieldsSize = 0;
LagCompensatedSize = 0;
InterpolatedCount = 0;
- InterpolatedFieldsSize = 0;
RemoteCallsClient = null;
ClassId = typeInfo.ClassId;
ClassEnumName = typeInfo.ClassName;
@@ -190,9 +188,10 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp
var fields = new List();
var syncableFields = new List();
+ var syncableFieldsWithCustomRollback = new List();
var lagCompensatedFields = new List();
- var ownedRollbackFields = new List();
- var remoteRollbackFields = new List();
+ var ownedRollbackFields = new List();
+ var remoteRollbackFields = new List();
var allTypesStack = Utils.GetBaseTypes(entType, InternalEntityType, true, true);
while(allTypesStack.Count > 0)
@@ -231,10 +230,9 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp
int fieldSize = valueTypeProcessor.Size;
if (syncFlags.HasFlagFast(SyncFlags.Interpolated) && !ft.IsEnum)
{
- InterpolatedFieldsSize += fieldSize;
InterpolatedCount++;
}
- var fieldInfo = new EntityFieldInfo($"{baseType.Name}-{field.Name}", valueTypeProcessor, offset, syncVarFlags);
+ var fieldInfo = new EntityFieldInfo($"{baseType.Name}-{field.Name}", valueTypeProcessor, offset, syncVarFlags?.Flags ?? SyncFlags.None);
if (syncFlags.HasFlagFast(SyncFlags.LagCompensated))
{
lagCompensatedFields.Add(fieldInfo);
@@ -253,6 +251,11 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp
throw new Exception($"Syncable fields should be readonly! (Class: {entType} Field: {field.Name})");
syncableFields.Add(new SyncableFieldInfo(offset, syncFlags));
+
+ //add custom rollbacked separately
+ if (ft.IsSubclassOf(typeof(SyncableFieldCustomRollback)))
+ syncableFieldsWithCustomRollback.Add(new SyncableFieldInfo(offset, syncFlags));
+
var syncableFieldTypesWithBase = Utils.GetBaseTypes(ft, SyncableFieldType, true, true);
while(syncableFieldTypesWithBase.Count > 0)
{
@@ -279,8 +282,21 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp
Logger.LogError($"Unregistered field type: {syncableFieldType}");
continue;
}
+
+ var mergedSyncFlags = (syncableField.GetCustomAttribute()?.Flags ?? SyncFlags.None) | (syncVarFlags?.Flags ?? SyncFlags.None);
+ if (mergedSyncFlags.HasFlagFast(SyncFlags.OnlyForOwner) &&
+ mergedSyncFlags.HasFlagFast(SyncFlags.OnlyForOtherPlayers))
+ {
+ Logger.LogWarning($"{SyncFlags.OnlyForOwner} and {SyncFlags.OnlyForOtherPlayers} flags can't be used together! Field: {syncableType} - {syncableField.Name}");
+ }
+ if (mergedSyncFlags.HasFlagFast(SyncFlags.AlwaysRollback) &&
+ mergedSyncFlags.HasFlagFast(SyncFlags.NeverRollBack))
+ {
+ Logger.LogWarning($"{SyncFlags.AlwaysRollback} and {SyncFlags.NeverRollBack} flags can't be used together! Field: {syncableType} - {syncableField.Name}");
+ }
+
int syncvarOffset = Utils.GetFieldOffset(syncableField);
- var fieldInfo = new EntityFieldInfo($"{baseType.Name}-{field.Name}:{syncableField.Name}", valueTypeProcessor, offset, syncvarOffset, syncVarFlags);
+ var fieldInfo = new EntityFieldInfo($"{baseType.Name}-{field.Name}:{syncableField.Name}", valueTypeProcessor, offset, syncvarOffset, mergedSyncFlags);
fields.Add(fieldInfo);
FixedFieldsSize += fieldInfo.IntSize;
if (fieldInfo.IsPredicted)
@@ -300,6 +316,7 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp
});
Fields = fields.ToArray();
SyncableFields = syncableFields.ToArray();
+ SyncableFieldsCustomRollback = syncableFieldsWithCustomRollback.ToArray();
FieldsCount = Fields.Length;
FieldsFlagsSize = (FieldsCount-1) / 8 + 1;
LagCompensatedFields = lagCompensatedFields.ToArray();
@@ -316,9 +333,13 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp
{
field.PredictedOffset = predictedOffset;
predictedOffset += field.IntSize;
- ownedRollbackFields.Add(field);
+
+ //singletons can't be owned
+ if (!IsSingleton)
+ ownedRollbackFields.Add(i);
+
if(field.Flags.HasFlagFast(SyncFlags.AlwaysRollback))
- remoteRollbackFields.Add(field);
+ remoteRollbackFields.Add(i);
}
else
{
@@ -339,8 +360,8 @@ public EntityClassData(EntityManager entityManager, ushort filterId, Type entTyp
}
else
{
- _dataCacheSize = InterpolatedFieldsSize * 2 + PredictedSize + historySize;
- _historyStart = InterpolatedFieldsSize * 2 + PredictedSize;
+ _dataCacheSize = PredictedSize + historySize;
+ _historyStart = PredictedSize;
}
Type = entType;
diff --git a/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs b/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs
index 549b7ea..93819ad 100644
--- a/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs
+++ b/Assets/Plugins/LiteEntitySystem/Internal/EntityFieldInfo.cs
@@ -1,4 +1,7 @@
-namespace LiteEntitySystem.Internal
+using System;
+using System.Runtime.CompilerServices;
+
+namespace LiteEntitySystem.Internal
{
internal enum FieldType
{
@@ -10,7 +13,6 @@ internal struct EntityFieldInfo
{
public readonly string Name; //used for debug
public readonly ValueTypeProcessor TypeProcessor;
- public readonly int Offset;
public readonly int SyncableSyncVarOffset;
public readonly uint Size;
public readonly int IntSize;
@@ -19,18 +21,24 @@ internal struct EntityFieldInfo
public readonly bool IsPredicted;
public MethodCallDelegate OnSync;
+ public BindOnChangeFlags OnSyncFlags;
public int FixedOffset;
public int PredictedOffset;
+
+ ///
+ /// Direct field offset which for Entities - is SyncVar, and for SyncableField - SyncableField
+ ///
+ public readonly int Offset;
//for value type
- public EntityFieldInfo(string name, ValueTypeProcessor valueTypeProcessor, int offset, SyncVarFlags flags) :
+ public EntityFieldInfo(string name, ValueTypeProcessor valueTypeProcessor, int offset, SyncFlags flags) :
this(name, valueTypeProcessor, offset, -1, flags, FieldType.SyncVar)
{
}
//For syncable syncvar
- public EntityFieldInfo(string name, ValueTypeProcessor valueTypeProcessor, int offset, int syncableSyncVarOffset, SyncVarFlags flags) :
+ public EntityFieldInfo(string name, ValueTypeProcessor valueTypeProcessor, int offset, int syncableSyncVarOffset, SyncFlags flags) :
this(name, valueTypeProcessor, offset, syncableSyncVarOffset, flags, FieldType.SyncableSyncVar)
{
@@ -41,9 +49,10 @@ private EntityFieldInfo(
ValueTypeProcessor valueTypeProcessor,
int offset,
int syncableSyncVarOffset,
- SyncVarFlags flags,
+ SyncFlags flags,
FieldType fieldType)
{
+ OnSyncFlags = 0;
Name = name;
TypeProcessor = valueTypeProcessor;
SyncableSyncVarOffset = syncableSyncVarOffset;
@@ -54,49 +63,28 @@ private EntityFieldInfo(
FixedOffset = 0;
PredictedOffset = 0;
OnSync = null;
- Flags = flags?.Flags ?? SyncFlags.None;
+ Flags = flags;
IsPredicted = Flags.HasFlagFast(SyncFlags.AlwaysRollback) ||
(!Flags.HasFlagFast(SyncFlags.OnlyForOtherPlayers) &&
!Flags.HasFlagFast(SyncFlags.NeverRollBack));
}
-
- public unsafe bool ReadField(
- InternalEntity entity,
- byte* rawData,
- byte* predictedData,
- byte* nextInterpDataPtr,
- byte* prevInterpDataPtr)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public InternalBaseClass GetTargetObjectAndOffset(InternalEntity entity, out int offset)
{
- if (IsPredicted)
- RefMagic.CopyBlock(predictedData + PredictedOffset, rawData, Size);
if (FieldType == FieldType.SyncableSyncVar)
{
- var syncableField = RefMagic.RefFieldValue(entity, Offset);
- TypeProcessor.SetFrom(syncableField, SyncableSyncVarOffset, rawData);
+ offset = SyncableSyncVarOffset;
+ return RefMagic.GetFieldValue(entity, Offset);
}
- 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);
- }
- }
-
- return false;
+ offset = Offset;
+ return entity;
}
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public InternalBaseClass GetTargetObject(InternalEntity entity) =>
+ FieldType == FieldType.SyncableSyncVar
+ ? RefMagic.GetFieldValue(entity, Offset)
+ : entity;
}
}
\ No newline at end of file
diff --git a/Assets/Plugins/LiteEntitySystem/Internal/InternalBaseClass.cs b/Assets/Plugins/LiteEntitySystem/Internal/InternalBaseClass.cs
index 3b0d9b0..e9f820b 100644
--- a/Assets/Plugins/LiteEntitySystem/Internal/InternalBaseClass.cs
+++ b/Assets/Plugins/LiteEntitySystem/Internal/InternalBaseClass.cs
@@ -1,7 +1,14 @@
namespace LiteEntitySystem.Internal
{
+ ///
+ /// Base class for SyncableFields and Entities
+ ///
public abstract class InternalBaseClass
{
+ ///
+ /// Method for executing RPCs containing initial sync data that need to be sent after entity creation
+ /// to existing players or when new player connected
+ ///
protected internal virtual void OnSyncRequested()
{
diff --git a/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs b/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs
index 4f22cef..a5fadc8 100644
--- a/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs
+++ b/Assets/Plugins/LiteEntitySystem/Internal/InternalEntity.cs
@@ -4,11 +4,22 @@
namespace LiteEntitySystem.Internal
{
+ public enum EntityState
+ {
+ New = 0,
+ Constructed = 1,
+ LateConstructed = 2,
+ Destroyed = 3,
+ Removed = 4
+ }
+
public abstract class InternalEntity : InternalBaseClass, IComparable
{
[SyncVarFlags(SyncFlags.NeverRollBack)]
internal SyncVar InternalOwnerId;
+ private EntityState _entityState;
+
internal byte[] IOBuffer;
internal readonly int UpdateOrderNum;
@@ -22,7 +33,6 @@ public abstract class InternalEntity : InternalBaseClass, IComparable
public readonly ushort Id;
-
///
/// Entity manager
@@ -39,12 +49,17 @@ public abstract class InternalEntity : InternalBaseClass, IComparable
public bool IsClient => EntityManager.IsClient;
+ ///
+ /// Entity ClassId name
+ ///
+ public string ClassIdName => ClassData.ClassEnumName;
+
///
/// Entity version (for id reuse)
///
public readonly byte Version;
- internal EntityDataHeader DataHeader => new EntityDataHeader
+ internal EntityDataHeader DataHeader => new
(
ClassId,
Version,
@@ -54,7 +69,7 @@ public abstract class InternalEntity : InternalBaseClass, IComparable
/// Is entity is destroyed
///
- public bool IsDestroyed { get; private set; }
+ public bool IsDestroyed => _entityState >= EntityState.Destroyed;
///
/// Is entity local controlled
@@ -103,12 +118,23 @@ public abstract class InternalEntity : InternalBaseClass, IComparable
/// Is entity constructed (OnConstruct called)
///
- public bool IsConstructed { get; internal set; }
+ public bool IsConstructed => _entityState >= EntityState.Constructed;
///
/// Is entity released and not used after destroy.
///
- public bool IsRemoved { get; internal set; }
+ public bool IsRemoved => _entityState == EntityState.Removed;
+
+ ///
+ /// Entity state. New, Constructed, Destroyed, Removed
+ ///
+ public EntityState CreationState => _entityState;
+
+ ///
+ /// Used to detect situation when base.RegisterRPC isn't called
+ ///
+ [ThreadStatic]
+ private static bool IsBaseRegisterRPCCalled;
///
/// Destroy entity
@@ -132,11 +158,42 @@ internal virtual void DestroyInternal()
{
if (IsDestroyed)
return;
- IsDestroyed = true;
+ if (_entityState == EntityState.Constructed)
+ {
+ //id destroyed before late construct - execute late construct first
+ LateConstructInternal();
+ }
+
+ _entityState = EntityState.Destroyed;
EntityManager.OnEntityDestroyed(this);
OnDestroy();
}
+ internal void ConstructInternal()
+ {
+ if (_entityState != EntityState.New)
+ Logger.LogError($"Error! Calling construct on not new entity: {this}. State: {_entityState}");
+
+ _entityState = EntityState.Constructed;
+ }
+
+ internal void LateConstructInternal()
+ {
+ if (_entityState != EntityState.Constructed)
+ Logger.LogError($"Error! Calling late construct on not constructed entity: {this}. State: {_entityState}");
+
+ _entityState = EntityState.LateConstructed;
+ OnLateConstructed();
+ }
+
+ internal void Remove()
+ {
+ if (_entityState != EntityState.Destroyed)
+ Logger.LogError($"Error! Calling remove on not destroyed entity: {this}. State: {_entityState}");
+
+ _entityState = EntityState.Removed;
+ }
+
internal void SafeUpdate()
{
try
@@ -191,7 +248,7 @@ protected internal virtual void OnConstructed()
///
/// Called when entity constructed but at end of frame
///
- protected internal virtual void OnLateConstructed()
+ protected virtual void OnLateConstructed()
{
}
@@ -201,19 +258,15 @@ 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);
- }
+ var target = field.GetTargetObjectAndOffset(this, out int offset);
+
+ //init before SyncVar init because SyncVar can call OnChange
+ if (field.FieldType == FieldType.SyncableSyncVar)
+ RefMagic.GetFieldValue(this, field.Offset).Init(this, field.Flags);
+ field.TypeProcessor.InitSyncVar(target, offset, this, (ushort)i);
}
List rpcCache = null;
@@ -224,14 +277,19 @@ internal void RegisterRpcInternal()
RemoteCallPacket.InitReservedRPCs(rpcCache);
var rpcRegistrator = new RPCRegistrator(rpcCache, classData.Fields);
+ IsBaseRegisterRPCCalled = false;
RegisterRPC(ref rpcRegistrator);
+ if (!IsBaseRegisterRPCCalled)
+ {
+ Logger.LogError($"Error! You need always call to base.RegisterRPC in your overrides. Failed class: {ClassIdName}");
+ }
//Logger.Log($"RegisterRPCs for class: {classData.ClassId}");
}
//setup id for later sync calls
for (int i = 0; i < classData.SyncableFields.Length; i++)
{
ref var syncFieldInfo = ref classData.SyncableFields[i];
- var syncField = RefMagic.RefFieldValue(this, syncFieldInfo.Offset);
+ var syncField = RefMagic.GetFieldValue(this, syncFieldInfo.Offset);
syncField.Init(this, syncFieldInfo.Flags);
if (rpcCache == null) //classData.RemoteCallsClient != null
{
@@ -241,7 +299,7 @@ internal void RegisterRpcInternal()
{
syncField.RPCOffset = (ushort)rpcCache.Count;
syncFieldInfo.RPCOffset = syncField.RPCOffset;
- var syncablesRegistrator = new SyncableRPCRegistrator(syncFieldInfo.Offset, rpcCache);
+ var syncablesRegistrator = new SyncableRPCRegistrator(syncFieldInfo.Offset, rpcCache, classData.Fields);
syncField.RegisterRPC(ref syncablesRegistrator);
}
}
@@ -256,7 +314,7 @@ internal void RegisterRpcInternal()
///
protected virtual void RegisterRPC(ref RPCRegistrator r)
{
-
+ IsBaseRegisterRPCCalled = true;
}
protected void ExecuteRPC(in RemoteCall rpc)
diff --git a/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs b/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs
index e5cbcad..03c3211 100644
--- a/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs
+++ b/Assets/Plugins/LiteEntitySystem/Internal/RemoteCallPacket.cs
@@ -10,6 +10,16 @@ internal struct RPCHeader
public ushort Tick;
public ushort ByteCount;
}
+
+ internal enum InternalRPCType : ushort
+ {
+ New,
+ NewOwned,
+ Construct,
+ Destroy,
+
+ Total
+ }
internal sealed class RemoteCallPacket
{
@@ -18,17 +28,15 @@ internal sealed class RemoteCallPacket
public NetPlayer OnlyForPlayer;
public ExecuteFlags ExecuteFlags;
- public unsafe int TotalSize => sizeof(RPCHeader) + Header.ByteCount;
-
- public const int ReserverdRPCsCount = 3;
- public const ushort NewRPCId = 0;
- public const ushort ConstructRPCId = 1;
- public const ushort DestroyRPCId = 2;
+ public int TotalSize => RpcDeltaCompressor.MaxDeltaSize + Header.ByteCount;
+
+ //can be static because doesnt use any buffers
+ private static DeltaCompressor RpcDeltaCompressor = new(Utils.SizeOfStruct());
public static void InitReservedRPCs(List rpcCache)
{
- for(int i = 0; i < ReserverdRPCsCount; i++)
- rpcCache.Add(new RpcFieldInfo(-1, null));
+ for(var i = InternalRPCType.New; i < InternalRPCType.Total; i++)
+ rpcCache.Add(new RpcFieldInfo(null));
}
public bool AllowToSendForPlayer(byte forPlayerId, byte entityOwnerId)
@@ -42,21 +50,15 @@ public bool AllowToSendForPlayer(byte forPlayerId, byte entityOwnerId)
return false;
}
-
- public unsafe void WriteTo(byte* resultData, ref int position)
- {
- *(RPCHeader*)(resultData + position) = Header;
- fixed (byte* rpcData = Data)
- RefMagic.CopyBlock(resultData + sizeof(RPCHeader) + position, rpcData, Header.ByteCount);
- position += TotalSize;
- }
- public unsafe void WriteToDeltaCompressed(ref DeltaCompressor deltaCompressor, byte* resultData, ref int position, RPCHeader prevHeader)
+ public unsafe int WriteTo(byte* resultData, ref int position, ref RPCHeader prevHeader)
{
- int headerEncodedSize = deltaCompressor.Encode(ref prevHeader, ref Header, new Span(resultData + position, sizeof(RPCHeader)));
+ int headerEncodedSize = RpcDeltaCompressor.Encode(ref prevHeader, ref Header, new Span(resultData + position, RpcDeltaCompressor.MaxDeltaSize));
fixed (byte* rpcData = Data)
RefMagic.CopyBlock(resultData + headerEncodedSize + position, rpcData, Header.ByteCount);
position += headerEncodedSize + Header.ByteCount;
+ prevHeader = Header;
+ return headerEncodedSize + Header.ByteCount;
}
public void Init(NetPlayer targetPlayer, InternalEntity entity, ushort tick, ushort byteCount, ushort rpcId, ExecuteFlags executeFlags)
diff --git a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs
index 8a9db16..55b581e 100644
--- a/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs
+++ b/Assets/Plugins/LiteEntitySystem/Internal/ServerStateData.cs
@@ -5,24 +5,13 @@
namespace LiteEntitySystem.Internal
{
- internal readonly struct InterpolatedCache
+ internal enum RPCExecuteMode
{
- public readonly InternalEntity Entity;
- public readonly int FieldOffset;
- public readonly int FieldFixedOffset;
- public readonly ValueTypeProcessor TypeProcessor;
- public readonly int StateReaderOffset;
-
- public InterpolatedCache(InternalEntity entity, ref EntityFieldInfo field, int offset)
- {
- Entity = entity;
- FieldOffset = field.Offset;
- FieldFixedOffset = field.FixedOffset;
- TypeProcessor = field.TypeProcessor;
- StateReaderOffset = offset;
- }
+ FirstSync,
+ BetweenStates,
+ OnNextState
}
-
+
internal readonly struct EntityDataCache
{
public readonly ushort EntityId;
@@ -35,6 +24,20 @@ public EntityDataCache(ushort entityId, int offset)
}
}
+ internal readonly struct RemoteCallInfo
+ {
+ public readonly RPCHeader Header;
+ public readonly int DataOffset;
+ public readonly bool ExecuteOnNextState;
+
+ public RemoteCallInfo(RPCHeader header, int dataOffset, bool executeOnNextState)
+ {
+ Header = header;
+ DataOffset = dataOffset;
+ ExecuteOnNextState = executeOnNextState;
+ }
+ }
+
internal class ServerStateData
{
private const int DefaultCacheSize = 32;
@@ -46,9 +49,6 @@ internal class ServerStateData
public ushort LastReceivedTick;
public byte BufferedInputsCount;
- private int _interpolatedCachesCount;
- private InterpolatedCache[] _interpolatedCaches = new InterpolatedCache[DefaultCacheSize];
-
private int _totalPartsCount;
private int _receivedPartsCount;
private byte _maxReceivedPart;
@@ -57,48 +57,57 @@ internal class ServerStateData
private int _dataOffset;
private int _dataSize;
- private int _rpcReadPos;
- private int _rpcEndPos;
private EntityDataCache[] _nullEntitiesData = new EntityDataCache[DefaultCacheSize];
+ private RemoteCallInfo[] _remoteCallInfos = new RemoteCallInfo[DefaultCacheSize];
+
private int _nullEntitiesCount;
+ private int _remoteCallsCount;
+ private int _rpcIndex;
public int DataOffset => _dataOffset;
public int DataSize => _dataSize;
- private readonly HashSet _syncablesSet = new();
+ [ThreadStatic]
+ private static HashSet SyncablesBufferSet;
+
+ [ThreadStatic]
+ private static HashSet LocalEntitiesBuffer;
- public unsafe void GetDiagnosticData(
- InternalEntity[] entityDict,
- EntityClassData[] classDatas,
- Dictionary diagnosticDataDict)
+ private DeltaCompressor _rpcDeltaCompressor = new (Utils.SizeOfStruct());
+
+ private readonly ClientEntityManager _entityManager;
+
+ public ServerStateData(ClientEntityManager entityManager)
{
- fixed (byte* rawData = Data)
+ _entityManager = entityManager;
+ }
+
+ public unsafe void GetDiagnosticData(Dictionary diagnosticDataDict)
+ {
+ for (int i = 0; i < _remoteCallsCount; i++)
{
- int readPos = 0;
- while (readPos < _rpcEndPos)
- {
- if (_rpcEndPos - readPos < sizeof(RPCHeader))
- break;
- var header = *(RPCHeader*)(rawData + readPos);
- int rpcSize = header.ByteCount + sizeof(RPCHeader);
- readPos += rpcSize;
+ var header = _remoteCallInfos[i].Header;
+ int rpcSize = header.ByteCount + sizeof(RPCHeader);
+ int dictId = ushort.MaxValue + header.Id;
- int dictId = ushort.MaxValue + header.Id;
-
- if(!diagnosticDataDict.TryGetValue(dictId, out LESDiagnosticDataEntry entry))
- {
- entry = new LESDiagnosticDataEntry { IsRPC = true, Count = 1, Name = $"{header.Id}", Size = rpcSize };
- }
- else
- {
- entry.Count++;
- entry.Size += rpcSize;
- }
- diagnosticDataDict[dictId] = entry;
+ if (!diagnosticDataDict.TryGetValue(dictId, out LESDiagnosticDataEntry entry))
+ {
+ entry = new LESDiagnosticDataEntry
+ { IsRPC = true, Count = 1, Name = $"{header.Id}", Size = rpcSize };
}
+ else
+ {
+ entry.Count++;
+ entry.Size += rpcSize;
+ }
+
+ diagnosticDataDict[dictId] = entry;
}
+ var entityDict = _entityManager.EntitiesDict;
+ var classDatas = _entityManager.ClassDataDict;
+
for (int bytesRead = _dataOffset; bytesRead < _dataOffset + _dataSize;)
{
int initialReaderPosition = bytesRead;
@@ -112,22 +121,6 @@ public unsafe void GetDiagnosticData(
? classDatas[classId].ClassEnumName
: "Unknown";
- /*
- if (classId >= 0)
- {
- ref var classData = ref classDatas[classId];
- int entityFieldsOffset = initialReaderPosition + StateSerializer.DiffHeaderSize;
- //interpolated fields goes first so can skip some checks
- for (int i = 0; i < classData.FieldsCount; i++)
- {
- if (!Utils.IsBitSet(Data, entityFieldsOffset, i))
- continue;
- ref var field = ref classData.Fields[i];
- Logger.Log($"{field.Name}({i}) - {field.Size}");
- }
- }
- */
-
if(!diagnosticDataDict.TryGetValue(classId, out LESDiagnosticDataEntry entry))
{
entry = new LESDiagnosticDataEntry { Count = 1, Name = name, Size = totalSize };
@@ -169,7 +162,7 @@ public void Preload(InternalEntity[] entityDict)
}
}
- private void PreloadInterpolation(InternalEntity entity, int offset)
+ private unsafe void PreloadInterpolation(InternalEntity entity, int offset)
{
if (!entity.IsRemoteControlled)
return;
@@ -180,26 +173,27 @@ private void PreloadInterpolation(InternalEntity entity, int offset)
int entityFieldsOffset = offset + StateSerializer.DiffHeaderSize;
int stateReaderOffset = entityFieldsOffset + classData.FieldsFlagsSize;
-
- //preload interpolation info
- Utils.ResizeIfFull(ref _interpolatedCaches, _interpolatedCachesCount + classData.InterpolatedCount);
//interpolated fields goes first so can skip some checks
- for (int i = 0; i < classData.InterpolatedCount; i++)
+ fixed (byte* rawData = Data)
{
- if (!Utils.IsBitSet(Data, entityFieldsOffset, i))
- continue;
- ref var field = ref classData.Fields[i];
- _interpolatedCaches[_interpolatedCachesCount++] = new InterpolatedCache(entity, ref field, stateReaderOffset);
- stateReaderOffset += field.IntSize;
+ for (int i = 0; i < classData.InterpolatedCount; i++)
+ {
+ if (!Utils.IsBitSet(Data, entityFieldsOffset, i))
+ continue;
+ ref var field = ref classData.Fields[i];
+ var target = field.GetTargetObjectAndOffset(entity, out int fieldOffset);
+ field.TypeProcessor.SetInterpValue(target, fieldOffset, rawData + stateReaderOffset);
+ stateReaderOffset += field.IntSize;
+ }
}
}
- public unsafe void RemoteInterpolation(InternalEntity[] entityDict, float logicLerpMsec)
+ public void PreloadInterpolationForNewEntities()
{
for (int i = 0; i < _nullEntitiesCount; i++)
{
- var entity = entityDict[_nullEntitiesData[i].EntityId];
+ var entity = _entityManager.EntitiesDict[_nullEntitiesData[i].EntityId];
if (entity == null)
continue;
@@ -212,92 +206,95 @@ public unsafe void RemoteInterpolation(InternalEntity[] entityDict, float logicL
_nullEntitiesData[i] = _nullEntitiesData[_nullEntitiesCount];
i--;
}
-
- for(int i = 0; i < _interpolatedCachesCount; i++)
- {
- ref var interpolatedCache = ref _interpolatedCaches[i];
- fixed (byte* initialDataPtr = interpolatedCache.Entity.ClassData.ClientInterpolatedNextData(interpolatedCache.Entity), nextDataPtr = Data)
- interpolatedCache.TypeProcessor.SetInterpolation(
- interpolatedCache.Entity,
- interpolatedCache.FieldOffset,
- initialDataPtr + interpolatedCache.FieldFixedOffset,
- nextDataPtr + interpolatedCache.StateReaderOffset,
- logicLerpMsec);
- }
}
- public unsafe void ExecuteRpcs(ClientEntityManager entityManager, ushort minimalTick, bool firstSync)
+ public unsafe void ExecuteRpcs(ushort minimalTick, RPCExecuteMode executeMode)
{
- _syncablesSet.Clear();
+ if (SyncablesBufferSet == null)
+ SyncablesBufferSet = new HashSet();
+ else
+ SyncablesBufferSet.Clear();
+
//if(_remoteCallsCount > 0)
// Logger.Log($"Executing rpcs (ST: {Tick}) for tick: {entityManager.ServerTick}, Min: {minimalTick}, Count: {_remoteCallsCount}");
+
+ int initialRpcIndex = _rpcIndex;
+ if (executeMode == RPCExecuteMode.OnNextState)
+ _rpcIndex = 0;
+
+ _entityManager.IsExecutingRPC = true;
+
fixed (byte* rawData = Data)
{
- while (_rpcReadPos < _rpcEndPos)
+ for(;_rpcIndex < _remoteCallsCount; _rpcIndex++)
{
- if (_rpcEndPos - _rpcReadPos < sizeof(RPCHeader))
- {
- Logger.LogError("Broken rpcs sizes?");
- break;
- }
+ var remoteCallInfo = _remoteCallInfos[_rpcIndex];
+ var header = remoteCallInfo.Header;
- var header = *(RPCHeader*)(rawData + _rpcReadPos);
- if (!firstSync)
+ if (executeMode != RPCExecuteMode.FirstSync)
{
- if (Utils.SequenceDiff(header.Tick, entityManager.ServerTick) > 0)
+ if (executeMode == RPCExecuteMode.BetweenStates)
{
- //Logger.Log($"Skip rpc. Entity: {header.EntityId}. Tick {header.Tick} > ServerTick: {entityManager.ServerTick}. Id: {header.Id}.");
- break;
+ if (remoteCallInfo.ExecuteOnNextState)
+ {
+ continue;
+ }
+ if (Utils.SequenceDiff(header.Tick, _entityManager.ServerTick) > 0)
+ {
+ //Logger.Log($"Skip rpc. Entity: {header.EntityId}. Tick {header.Tick} > ServerTick: {entityManager.ServerTick}. Id: {header.Id}.");
+ break;
+ }
+ }
+ //skip executed inside interpolation
+ else if (executeMode == RPCExecuteMode.OnNextState && remoteCallInfo.ExecuteOnNextState == false && _rpcIndex < initialRpcIndex)
+ {
+ //Logger.Log($"Skip rpc. Entity: {header.EntityId}. _rpcIndex {_rpcIndex} < initialRpcIndex: {initialRpcIndex}. Id: {header.Id}.");
+ continue;
}
if (Utils.SequenceDiff(header.Tick, minimalTick) <= 0)
{
- _rpcReadPos += header.ByteCount + sizeof(RPCHeader);
//Logger.Log($"Skip rpc. Entity: {header.EntityId}. Tick {header.Tick} <= MinimalTick: {minimalTick}. Id: {header.Id}. StateATick: {entityManager.RawServerTick}. StateBTick: {entityManager.RawTargetServerTick}");
continue;
}
}
-
- int rpcDataStart = _rpcReadPos + sizeof(RPCHeader);
- _rpcReadPos += header.ByteCount + sizeof(RPCHeader);
+ if (header.Id == (ushort)InternalRPCType.New ||
+ header.Id == (ushort)InternalRPCType.NewOwned)
+ {
+ _entityManager.ReadNewRPC(header.EntityId, rawData + remoteCallInfo.DataOffset, remoteCallInfo.Header.ByteCount);
+ continue;
+ }
+
//Logger.Log($"Executing rpc. Entity: {header.EntityId}. Tick {header.Tick}. Id: {header.Id}");
- var entity = entityManager.EntitiesDict[header.EntityId];
+ var entity = _entityManager.EntitiesDict[header.EntityId];
if (entity == null)
{
- if (header.Id == RemoteCallPacket.NewRPCId)
- {
- entityManager.ReadNewRPC(header.EntityId, rawData + rpcDataStart);
- continue;
- }
-
Logger.LogError($"Entity is null: {header.EntityId}. RPCId: {header.Id}");
continue;
}
- entityManager.CurrentRPCTick = header.Tick;
-
- var rpcFieldInfo = entityManager.ClassDataDict[entity.ClassId].RemoteCallsClient[header.Id];
+ var rpcFieldInfo = _entityManager.ClassDataDict[entity.ClassId].RemoteCallsClient[header.Id];
if (rpcFieldInfo.SyncableOffset == -1)
{
try
{
- if (header.Id == RemoteCallPacket.NewRPCId)
- {
- entityManager.ReadNewRPC(header.EntityId, rawData + rpcDataStart);
- }
- else if (header.Id == RemoteCallPacket.ConstructRPCId)
- {
- //Logger.Log($"ConstructRPC for entity: {header.EntityId}, RpcReadPos: {_rpcReadPos}, Tick: {header.Tick}");
- entityManager.ReadConstructRPC(header.EntityId, rawData, rpcDataStart);
- }
- else if (header.Id == RemoteCallPacket.DestroyRPCId)
- {
- entity.DestroyInternal();
- }
- else
+ switch ((InternalRPCType)header.Id)
{
- rpcFieldInfo.Method(entity, new ReadOnlySpan(rawData + rpcDataStart, header.ByteCount));
+ case InternalRPCType.Construct:
+ //Logger.Log($"ConstructRPC for entity: {header.EntityId}, Size: {header.ByteCount}, RpcReadPos: {remoteCallInfo.DataOffset}, Tick: {header.Tick}");
+ //Logger.Log($"CRPCData: {Utils.BytesToHexString(new ReadOnlySpan(rawData + remoteCallInfo.DataOffset, header.ByteCount))}");
+ _entityManager.ReadConstructRPC(header.EntityId, rawData, remoteCallInfo.DataOffset);
+ break;
+
+ case InternalRPCType.Destroy:
+ //Logger.Log($"DestroyRPC for {header.EntityId}");
+ entity.DestroyInternal();
+ break;
+
+ default:
+ rpcFieldInfo.Method(entity, new ReadOnlySpan(rawData + remoteCallInfo.DataOffset, header.ByteCount));
+ break;
}
}
catch (Exception e)
@@ -307,14 +304,13 @@ public unsafe void ExecuteRpcs(ClientEntityManager entityManager, ushort minimal
}
else
{
- var syncableField = RefMagic.RefFieldValue(entity, rpcFieldInfo.SyncableOffset);
- if (_syncablesSet.Add(syncableField))
- {
- syncableField.BeforeReadRPC();
- }
+ var syncableField = RefMagic.GetFieldValue(entity, rpcFieldInfo.SyncableOffset);
+ if (syncableField is SyncableFieldCustomRollback sf && SyncablesBufferSet.Add(sf))
+ sf.BeforeReadRPC();
+
try
{
- rpcFieldInfo.Method(syncableField, new ReadOnlySpan(rawData + rpcDataStart, header.ByteCount));
+ rpcFieldInfo.Method(syncableField, new ReadOnlySpan(rawData + remoteCallInfo.DataOffset, header.ByteCount));
}
catch (Exception e)
{
@@ -323,22 +319,75 @@ public unsafe void ExecuteRpcs(ClientEntityManager entityManager, ushort minimal
}
}
}
- foreach (var syncableField in _syncablesSet)
+
+ foreach (var syncableField in SyncablesBufferSet)
syncableField.AfterReadRPC();
+
+ _entityManager.IsExecutingRPC = false;
}
public void Reset(ushort tick)
{
Tick = tick;
_receivedParts.SetAll(false);
- _interpolatedCachesCount = 0;
_maxReceivedPart = 0;
_receivedPartsCount = 0;
_totalPartsCount = 0;
Size = 0;
_partMtu = 0;
_nullEntitiesCount = 0;
- _rpcReadPos = 0;
+ }
+
+ private unsafe void PreloadRPCs(int rpcsSize)
+ {
+ if (LocalEntitiesBuffer == null)
+ LocalEntitiesBuffer = new HashSet();
+ else
+ LocalEntitiesBuffer.Clear();
+
+ //parse and preload rpc infos
+ int rpcReadPos = 0;
+ _rpcIndex = 0;
+ _remoteCallsCount = 0;
+ _rpcDeltaCompressor.Init();
+ while (rpcReadPos < rpcsSize)
+ {
+ if (rpcsSize - rpcReadPos < _rpcDeltaCompressor.MinDeltaSize)
+ {
+ Logger.LogError("Broken rpcs sizes?");
+ break;
+ }
+
+ RPCHeader header = new();
+ int encodedHeaderSize = _rpcDeltaCompressor.Decode(
+ new ReadOnlySpan(Data, rpcReadPos, rpcsSize - rpcReadPos),
+ new Span(&header, sizeof(RPCHeader)));
+
+ //Logger.Log($"ReadRPC: EID: {header.EntityId}, RPCID: {header.Id}, BC: {header.ByteCount}");
+
+ rpcReadPos += encodedHeaderSize;
+
+ bool executeOnNextState;
+ if (LocalEntitiesBuffer.Contains(header.EntityId))
+ {
+ executeOnNextState = true;
+ }
+ else if (header.Id == (ushort)InternalRPCType.NewOwned ||
+ _entityManager.EntitiesDict[header.EntityId]?.InternalOwnerId.Value == _entityManager.InternalPlayerId)
+ {
+ LocalEntitiesBuffer.Add(header.EntityId);
+ executeOnNextState = true;
+ }
+ else
+ {
+ executeOnNextState = false;
+ }
+
+ Utils.ResizeIfFull(ref _remoteCallInfos, _remoteCallsCount);
+ _remoteCallInfos[_remoteCallsCount++] = new RemoteCallInfo(header, rpcReadPos, executeOnNextState);
+
+ rpcReadPos += header.ByteCount;
+ }
}
public unsafe bool ReadBaseline(BaselineDataHeader header, byte* rawData, int fullSize)
@@ -348,8 +397,6 @@ public unsafe bool ReadBaseline(BaselineDataHeader header, byte* rawData, int fu
Data = new byte[header.OriginalLength];
_dataOffset = header.EventsSize;
_dataSize = header.OriginalLength - header.EventsSize;
- _rpcEndPos = header.EventsSize;
- _rpcReadPos = 0;
fixed (byte* stateData = Data)
{
int decodedBytes = LZ4Codec.Decode(
@@ -363,6 +410,7 @@ public unsafe bool ReadBaseline(BaselineDataHeader header, byte* rawData, int fu
return false;
}
}
+ PreloadRPCs(header.EventsSize);
return true;
}
@@ -383,7 +431,6 @@ public unsafe bool ReadPart(DiffPartHeader partHeader, byte* rawData, int partSi
ProcessedTick = lastPartData.LastProcessedTick;
BufferedInputsCount = lastPartData.BufferedInputsCount;
_dataOffset = lastPartData.EventsSize;
- _rpcEndPos = lastPartData.EventsSize;
//Logger.Log($"TPC: {partHeader.Part} {_partMtu}, LastReceivedTick: {LastReceivedTick}, LastProcessedTick: {ProcessedTick}");
}
partSize -= sizeof(DiffPartHeader);
@@ -402,6 +449,8 @@ public unsafe bool ReadPart(DiffPartHeader partHeader, byte* rawData, int partSi
if (_receivedPartsCount == _totalPartsCount)
{
_dataSize = Size - _dataOffset;
+ //rpc before data - so data offset equals size
+ PreloadRPCs(_dataOffset);
return true;
}
return false;
diff --git a/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs b/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs
index 4ce8e74..9d54726 100644
--- a/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs
+++ b/Assets/Plugins/LiteEntitySystem/Internal/StateSerializer.cs
@@ -4,7 +4,7 @@ namespace LiteEntitySystem.Internal
{
internal struct StateSerializer
{
- public static readonly int HeaderSize = Utils.SizeOfStruct();
+ public static readonly ushort HeaderSize = (ushort)Utils.SizeOfStruct();
public const int DiffHeaderSize = 4;
public const int MaxStateSize = 32767; //half of ushort
@@ -19,12 +19,18 @@ internal struct StateSerializer
private ushort[] _fieldChangeTicks;
private ushort _versionChangedTick;
private uint _fullDataSize;
+ private DateTime _lastRefreshedTime;
+ private int _secondsBetweenRefresh;
public byte NextVersion;
public ushort LastChangedTick;
- private DateTime _lastRefreshedTime;
- private int _secondsBetweenRefresh;
+ ///
+ /// Get maximum delta size
+ ///
+ /// FullDataSize + ushort(size of data) + ushort(entityId) + _fieldFlagsSize
+ public int MaximumSize =>
+ _entity == null ? 0 : (int)_fullDataSize + sizeof(ushort) + sizeof(ushort) + _fieldsFlagsSize;
public void AllocateMemory(ref EntityClassData classData, byte[] ioBuffer)
{
@@ -78,7 +84,7 @@ public void MarkFieldsChanged(ushort minimalTick, ushort tick, SyncFlags onlyWit
public void MarkChanged(ushort minimalTick, ushort tick)
{
LastChangedTick = tick;
- //refresh every X seconds to prevent wrap-arounded bugs
+ //refresh every X seconds to prevent wrap-around bugs
DateTime currentTime = DateTime.UtcNow;
if ((currentTime - _lastRefreshedTime).TotalSeconds > _secondsBetweenRefresh)
{
@@ -90,29 +96,99 @@ public void MarkChanged(ushort minimalTick, ushort tick)
}
}
- public int GetMaximumSize() =>
- _entity == null ? 0 : (int)_fullDataSize + sizeof(ushort) + sizeof(ushort) + _fieldsFlagsSize;
-
- public void MakeNewRPC()
+ public RemoteCallPacket MakeNewRPC()
{
- _entity.ServerManager.AddRemoteCall(
+ //add rpc
+ return _entity.ServerManager.AddRemoteCall(
_entity,
- new ReadOnlySpan(_latestEntityData, 0, HeaderSize),
- RemoteCallPacket.NewRPCId,
+ (ushort)InternalRPCType.New,
ExecuteFlags.SendToAll);
}
+
+ public unsafe void RefreshNewRPC(RemoteCallPacket packet)
+ {
+ //skip cases when no packet generated
+ if (packet == null)
+ return;
+
+ Utils.ResizeOrCreate(ref packet.Data, MaximumSize);
+
+ fixed (byte* lastEntityData = _latestEntityData, resultData = packet.Data)
+ {
+ //copy header
+ RefMagic.CopyBlock(resultData, lastEntityData, HeaderSize);
+ //make diff between default data
+ byte* entityDataAfterHeader = lastEntityData + HeaderSize;
+
+ // -1 for cycle
+ int writePosition = HeaderSize + _fieldsFlagsSize;
+ byte* fields = resultData + HeaderSize - 1;
+ int readPosition = 0;
+
+ //write fields
+ for (int i = 0; i < _fieldsCount; i++)
+ {
+ if (i % 8 == 0) //reset next byte each 8 bits
+ *++fields = 0;
+
+ ref var field = ref _fields[i];
+
+ /*
+ if (((field.Flags & SyncFlags.OnlyForOwner) != 0 && !isOwned) ||
+ ((field.Flags & SyncFlags.OnlyForOtherPlayers) != 0 && isOwned))
+ {
+ //Logger.Log($"SkipSync: {field.Name}, isOwned: {isOwned}");
+ continue;
+ }
+
+
+ if(!isOwned && SyncGroupUtils.IsSyncVarDisabled(enabledSyncGroups, field.Flags))
+ {
+ //IgnoreDiffSyncSettings
+ continue;
+ }
+ */
+ //compare with 0
+ if (Utils.IsZero(lastEntityData + HeaderSize + readPosition, field.IntSize))
+ {
+ readPosition += field.IntSize;
+ continue;
+ }
+ readPosition += field.IntSize;
+
+ *fields |= (byte)(1 << i % 8);
+ //Logger.Log($"WriteNewChanges. Entity: {_entity}, {field.Name}");
+
+ RefMagic.CopyBlock(resultData + writePosition, entityDataAfterHeader + field.FixedOffset, field.Size);
+ writePosition += field.IntSize;
+ }
+
+ if (writePosition > MaxStateSize)
+ {
+ Logger.LogError($"Entity {_entity.Id}, Class: {_entity.ClassId} state size is more than: {MaxStateSize}");
+ writePosition = HeaderSize;
+ }
+
+ //update info after resize for correct buffer allocation
+ int prevTotalSize = packet.TotalSize;
+ packet.Header.ByteCount = (ushort)writePosition;
+ _entity.ServerManager.NotifyRPCResized(prevTotalSize, packet.TotalSize);
+
+ //if (writePosition > HeaderSize + _fieldsFlagsSize)
+ // Logger.Log($"NewRPC bytes (server) eid:{_entity.Id} cls:{_entity.ClassId} len:{writePosition} data:{Utils.BytesToHexString(new ReadOnlySpan(resultData, writePosition))}");
+ }
+ }
//refresh construct rpc with latest values (old behaviour)
- public unsafe void RefreshConstructedRPC(RemoteCallPacket packet, NetPlayer player)
+ public unsafe void RefreshConstructedRPC(RemoteCallPacket packet)
{
- RefreshSyncGroupsVariable(player);
fixed (byte* sourceData = _latestEntityData, rawData = packet.Data)
{
- RefMagic.CopyBlock(rawData, sourceData + HeaderSize, (uint)(_fullDataSize - HeaderSize));
+ RefMagic.CopyBlock(rawData, sourceData + HeaderSize, _fullDataSize - HeaderSize);
}
}
-
- private SyncGroup RefreshSyncGroupsVariable(NetPlayer player)
+
+ public SyncGroup RefreshSyncGroupsVariable(NetPlayer player, Span target)
{
SyncGroup enabledGroups = SyncGroup.All;
if (_entity is EntityLogic el)
@@ -127,7 +203,7 @@ private SyncGroup RefreshSyncGroupsVariable(NetPlayer player)
//if no data it "never" changed
_fieldChangeTicks[el.IsSyncEnabledFieldId] = _versionChangedTick;
}
- _latestEntityData[HeaderSize + _fields[el.IsSyncEnabledFieldId].FixedOffset] = (byte)enabledGroups;
+ target[_fields[el.IsSyncEnabledFieldId].FixedOffset] = (byte)enabledGroups;
}
return enabledGroups;
@@ -140,8 +216,7 @@ public void MakeConstructedRPC(NetPlayer player)
{
var syncableFields = _entity.ClassData.SyncableFields;
for (int i = 0; i < syncableFields.Length; i++)
- RefMagic.RefFieldValue(_entity, syncableFields[i].Offset)
- .OnSyncRequested();
+ RefMagic.GetFieldValue(_entity, syncableFields[i].Offset).OnSyncRequested();
_entity.OnSyncRequested();
}
catch (Exception e)
@@ -150,14 +225,16 @@ public void MakeConstructedRPC(NetPlayer player)
}
//it can be null on entity creation
+ var entityDataSpan = new Span(_latestEntityData, HeaderSize, (int)(_fullDataSize - HeaderSize));
+
if(player != null)
- RefreshSyncGroupsVariable(player);
+ RefreshSyncGroupsVariable(player, entityDataSpan);
//actual on constructed rpc
_entity.ServerManager.AddRemoteCall(
_entity,
- new ReadOnlySpan(_latestEntityData, HeaderSize, (int)(_fullDataSize - HeaderSize)),
- RemoteCallPacket.ConstructRPCId,
+ (ReadOnlySpan)entityDataSpan,
+ (ushort)InternalRPCType.Construct,
ExecuteFlags.SendToAll);
//Logger.Log($"Added constructed RPC: {_entity}");
}
@@ -168,7 +245,7 @@ public void MakeDestroyedRPC(ushort tick)
LastChangedTick = tick;
_entity.ServerManager.AddRemoteCall(
_entity,
- RemoteCallPacket.DestroyRPCId,
+ (ushort)InternalRPCType.Destroy,
ExecuteFlags.SendToAll);
}
@@ -224,7 +301,7 @@ public unsafe bool MakeDiff(NetPlayer player, ushort minimalTick, byte* resultDa
: player.CurrentServerTick;
//overwrite IsSyncEnabled for each player
- SyncGroup enabledSyncGroups = RefreshSyncGroupsVariable(player);
+ SyncGroup enabledSyncGroups = RefreshSyncGroupsVariable(player, new Span(_latestEntityData, HeaderSize, (int)(_fullDataSize - HeaderSize)));
fixed (byte* lastEntityData = _latestEntityData) //make diff
{
@@ -240,17 +317,8 @@ public unsafe bool MakeDiff(NetPlayer player, ushort minimalTick, byte* resultDa
//write fields
for (int i = 0; i < _fieldsCount; i++)
{
- if (i % 8 == 0)
- {
- fields++;
- *fields = 0;
- }
-
- //skip very old
- if (Utils.SequenceDiff(_fieldChangeTicks[i], minimalTick) <= 0)
- {
- continue;
- }
+ if (i % 8 == 0) //reset next byte each 8 bits
+ *++fields = 0;
//not actual
if (Utils.SequenceDiff(_fieldChangeTicks[i], compareToTick) <= 0)
diff --git a/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs b/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs
index a15a9a9..899fac2 100644
--- a/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs
+++ b/Assets/Plugins/LiteEntitySystem/Internal/ValueTypeProcessor.cs
@@ -16,8 +16,11 @@ internal abstract unsafe class ValueTypeProcessor
internal abstract void InitSyncVar(InternalBaseClass obj, int offset, InternalEntity entity, ushort fieldId);
internal abstract void SetFrom(InternalBaseClass obj, int offset, byte* data);
internal abstract bool SetFromAndSync(InternalBaseClass obj, int offset, byte* data);
+ internal abstract void SetFromAndSync(InternalBaseClass obj, int offset, byte* data, MethodCallDelegate onSyncDelegate);
+ internal abstract void SetInterpValue(InternalBaseClass obj, int offset, byte* data);
+ internal abstract void SetInterpValueFromCurrentValue(InternalBaseClass obj, int offset);
internal abstract void WriteTo(InternalBaseClass obj, int offset, byte* data);
- internal abstract void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer);
+ internal abstract void CopyFrom(InternalBaseClass toObj, InternalBaseClass fromObj, int offset);
internal abstract void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime);
internal abstract int GetHashCode(InternalBaseClass obj, int offset);
internal abstract string ToString(InternalBaseClass obj, int offset);
@@ -27,100 +30,95 @@ internal unsafe class ValueTypeProcessor : ValueTypeProcessor where T : unman
{
public ValueTypeProcessor() : base(sizeof(T)) { }
- internal override void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) =>
- throw new Exception($"This type: {typeof(T)} can't be interpolated");
+ internal virtual T GetInterpolatedValue(T prev, T current, float t) => current;
- internal override void InitSyncVar(InternalBaseClass obj, int offset, InternalEntity entity, ushort fieldId) =>
- RefMagic.RefFieldValue>(obj, offset).Init(entity, fieldId);
-
- internal override void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime)
+ internal sealed override void InitSyncVar(InternalBaseClass obj, int offset, InternalEntity entity, ushort fieldId)
{
- ref var a = ref RefMagic.RefFieldValue>(obj, offset);
- *(T*)tempHistory = a;
- a.SetDirect(*(T*)historyA);
+ var sv = RefMagic.GetFieldValue>(obj, offset);
+ sv.Init(entity, fieldId);
+ RefMagic.SetFieldValue(obj, offset, sv);
}
+
+ internal override void CopyFrom(InternalBaseClass toObj, InternalBaseClass fromObj, int offset) =>
+ RefMagic.SyncVarSetDirect>(toObj, offset, RefMagic.GetFieldValue>(fromObj, offset).Value);
+
+ internal override void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) =>
+ RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset, *(T*)historyA, out *(T*)tempHistory);
- internal override void SetFrom(InternalBaseClass obj, int offset, byte* data) =>
- RefMagic.RefFieldValue>(obj, offset).SetDirect(*(T*)data);
+ internal sealed override void SetFrom(InternalBaseClass obj, int offset, byte* data) =>
+ RefMagic.SyncVarSetDirect>(obj, offset, *(T*)data);
- internal override bool SetFromAndSync(InternalBaseClass obj, int offset, byte* data) =>
- RefMagic.RefFieldValue>(obj, offset).SetFromAndSync(data);
+ internal sealed override bool SetFromAndSync(InternalBaseClass obj, int offset, byte* data) =>
+ RefMagic.SyncVarSetFromAndSync>(obj, offset, ref *(T*)data);
- internal override void WriteTo(InternalBaseClass obj, int offset, byte* data) =>
- *(T*)data = RefMagic.RefFieldValue>(obj, offset);
+ internal sealed override void SetFromAndSync(InternalBaseClass obj, int offset, byte* data, MethodCallDelegate onSyncDelegate)
+ {
+ var tempData = *(T*)data;
+ if(RefMagic.SyncVarSetFromAndSync>(obj, offset, ref tempData))
+ onSyncDelegate(obj, new ReadOnlySpan(&tempData, Size));
+ }
- internal override int GetHashCode(InternalBaseClass obj, int offset) =>
- RefMagic.RefFieldValue>(obj, offset).GetHashCode();
+ internal sealed override void SetInterpValue(InternalBaseClass obj, int offset, byte* data) =>
+ RefMagic.SyncVarSetInterp>(obj, offset, *(T*)data);
- internal override string ToString(InternalBaseClass obj, int offset) =>
- RefMagic.RefFieldValue>(obj, offset).ToString();
+ internal sealed override void SetInterpValueFromCurrentValue(InternalBaseClass obj, int offset) =>
+ RefMagic.SyncVarSetInterpFromCurrent>(obj, offset);
+
+ internal sealed override void WriteTo(InternalBaseClass obj, int offset, byte* data) =>
+ *(T*)data = RefMagic.GetFieldValue>(obj, offset);
+
+ internal sealed override int GetHashCode(InternalBaseClass obj, int offset) =>
+ RefMagic.GetFieldValue>(obj, offset).GetHashCode();
+
+ internal sealed override string ToString(InternalBaseClass obj, int offset) =>
+ RefMagic.GetFieldValue>(obj, offset).ToString();
}
internal class ValueTypeProcessorInt : ValueTypeProcessor
{
- internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) =>
- RefMagic.RefFieldValue>(obj, offset).SetDirect(Utils.Lerp(*(int*)prev, *(int*)current, fTimer));
+ internal override int GetInterpolatedValue(int prev, int current, float t) => Utils.Lerp(prev, current, t);
- internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime)
- {
- ref var a = ref RefMagic.RefFieldValue>(obj, offset);
- *(int*)tempHistory = a;
- a.SetDirect(Utils.Lerp(*(int*)historyA, *(int*)historyB, lerpTime));
- }
+ internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) =>
+ RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset,
+ Utils.Lerp(*(int*)historyA, *(int*)historyB, lerpTime), out *(int*)tempHistory);
}
internal class ValueTypeProcessorLong : ValueTypeProcessor
{
- internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) =>
- RefMagic.RefFieldValue>(obj, offset).SetDirect(Utils.Lerp(*(long*)prev, *(long*)current, fTimer));
+ internal override long GetInterpolatedValue(long prev, long current, float t) => Utils.Lerp(prev, current, t);
- internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime)
- {
- ref var a = ref RefMagic.RefFieldValue>(obj, offset);
- *(long*)tempHistory = a;
- a.SetDirect(Utils.Lerp(*(long*)historyA, *(long*)historyB, lerpTime));
- }
+ internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) =>
+ RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset,
+ Utils.Lerp(*(long*)historyA, *(long*)historyB, lerpTime), out *(long*)tempHistory);
}
internal class ValueTypeProcessorFloat : ValueTypeProcessor
{
- internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) =>
- RefMagic.RefFieldValue>(obj, offset).SetDirect(Utils.Lerp(*(float*)prev, *(float*)current, fTimer));
+ internal override float GetInterpolatedValue(float prev, float current, float t) => Utils.Lerp(prev, current, t);
- internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime)
- {
- ref var a = ref RefMagic.RefFieldValue>(obj, offset);
- *(float*)tempHistory = a;
- a.SetDirect(Utils.Lerp(*(float*)historyA, *(float*)historyB, lerpTime));
- }
+ internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) =>
+ RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset,
+ Utils.Lerp(*(float*)historyA, *(float*)historyB, lerpTime), out *(float*)tempHistory);
}
internal class ValueTypeProcessorDouble : ValueTypeProcessor
{
- internal override unsafe void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) =>
- RefMagic.RefFieldValue>(obj, offset).SetDirect(Utils.Lerp(*(double*)prev, *(double*)current, fTimer));
+ internal override double GetInterpolatedValue(double prev, double current, float t) => Utils.Lerp(prev, current, t);
- internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime)
- {
- ref var a = ref RefMagic.RefFieldValue>(obj, offset);
- *(double*)tempHistory = a;
- a.SetDirect(Utils.Lerp(*(double*)historyA, *(double*)historyB, lerpTime));
- }
+ internal override unsafe void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) =>
+ RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset,
+ Utils.Lerp(*(double*)historyA, *(double*)historyB, lerpTime), out *(double*)tempHistory);
}
internal unsafe class UserTypeProcessor : ValueTypeProcessor where T : unmanaged
{
private readonly InterpolatorDelegateWithReturn _interpDelegate;
- internal override void SetInterpolation(InternalBaseClass obj, int offset, byte* prev, byte* current, float fTimer) =>
- RefMagic.RefFieldValue>(obj, offset).SetDirect(_interpDelegate?.Invoke(*(T*)prev, *(T*)current, fTimer) ?? *(T*)prev);
+ internal override T GetInterpolatedValue(T prev, T current, float t) => _interpDelegate?.Invoke(prev, current, t) ?? current;
- internal override void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime)
- {
- ref var a = ref RefMagic.RefFieldValue>(obj, offset);
- *(T*)tempHistory = a;
- a.SetDirect(_interpDelegate?.Invoke(*(T*)historyA, *(T*)historyB, lerpTime) ?? *(T*)historyA);
- }
+ internal override void LoadHistory(InternalBaseClass obj, int offset, byte* tempHistory, byte* historyA, byte* historyB, float lerpTime) =>
+ RefMagic.SyncVarSetDirectAndStorePrev>(obj, offset,
+ _interpDelegate?.Invoke(*(T*)historyA, *(T*)historyB, lerpTime) ?? *(T*)historyA, out *(T*)tempHistory);
public UserTypeProcessor(InterpolatorDelegateWithReturn interpolationDelegate) =>
_interpDelegate = interpolationDelegate;
diff --git a/Assets/Plugins/LiteEntitySystem/NetPlayer.cs b/Assets/Plugins/LiteEntitySystem/NetPlayer.cs
index c429905..9a73532 100644
--- a/Assets/Plugins/LiteEntitySystem/NetPlayer.cs
+++ b/Assets/Plugins/LiteEntitySystem/NetPlayer.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using LiteEntitySystem.Collections;
using LiteEntitySystem.Internal;
using LiteEntitySystem.Transport;
@@ -43,7 +44,8 @@ public class NetPlayer
internal NetPlayerState State;
internal readonly SequenceBinaryHeap AvailableInput;
internal readonly Dictionary EntitySyncInfo;
-
+ internal DateTime ServerTickChangedTime;
+
internal NetPlayer(AbstractNetPeer peer, byte id)
{
Id = id;
diff --git a/Assets/Plugins/LiteEntitySystem/PawnLogic.cs b/Assets/Plugins/LiteEntitySystem/PawnLogic.cs
index 8e0a781..dd492ea 100644
--- a/Assets/Plugins/LiteEntitySystem/PawnLogic.cs
+++ b/Assets/Plugins/LiteEntitySystem/PawnLogic.cs
@@ -14,6 +14,9 @@ public ControllerLogic Controller
get => EntityManager.GetEntityById(_controller);
internal set
{
+ if(IsDestroyed)
+ return;
+
byte ownerId = EntityManager.ServerPlayerId;
if (value != null)
{
@@ -27,7 +30,6 @@ internal set
protected internal override void Update()
{
- base.Update();
Controller?.BeforeControlledUpdate();
}
diff --git a/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs b/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs
new file mode 100644
index 0000000..6e4c0e7
--- /dev/null
+++ b/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs
@@ -0,0 +1,65 @@
+namespace LiteEntitySystem
+{
+ ///
+ /// Entity that can be spawned on client prediction
+ ///
+ public abstract class PredictableEntityLogic : EntityLogic
+ {
+ private struct InitialData
+ {
+ public ushort PredictedId;
+ public EntitySharedReference Parent;
+ }
+
+ private ushort _predictedId;
+ private EntitySharedReference _initialParent;
+ private static RemoteCall SyncRPC;
+
+ //used for spawn prediction
+ internal readonly ushort CreatedAtTick;
+
+ ///
+ /// Is entity is recrated from server when created using AddPredictedEntity
+ /// Can be true only on client
+ ///
+ public bool IsRecreated { get; internal set; }
+
+ internal void InitEntity(ushort predictedId, EntitySharedReference initialParent)
+ {
+ //Logger.Log($"InitEntity. PredId: {predictedId}. Id: {Id}, Class: {ClassData.ClassEnumName}. Mode: {EntityManager.Mode}. InitalParrent: {initialParent}");
+ _predictedId = predictedId;
+ _initialParent = initialParent;
+ }
+
+ protected PredictableEntityLogic(EntityParams entityParams) : base(entityParams)
+ {
+ CreatedAtTick = entityParams.EntityManager.Tick;
+ }
+
+ internal bool IsSameAsLocal(PredictableEntityLogic other) =>
+ _predictedId == other._predictedId && _initialParent == other._initialParent && ClassId == other.ClassId;
+
+ //used for finding entity in rollback
+ internal bool IsEntityMatch(ushort predictedId, ushort parentId, ushort createdAtTick) =>
+ _predictedId == predictedId && _initialParent.Id == parentId && CreatedAtTick == createdAtTick;
+
+ protected override void RegisterRPC(ref RPCRegistrator r)
+ {
+ base.RegisterRPC(ref r);
+ r.CreateRPCAction(this, OnSyncRPC, ref SyncRPC, ExecuteFlags.SendToOwner);
+ }
+
+ private void OnSyncRPC(InitialData initialData)
+ {
+ _predictedId = initialData.PredictedId;
+ _initialParent = initialData.Parent;
+ //Logger.Log($"OnSyncRPC called. pred id: {_predictedId}, initial parent: {_initialParent}");
+ }
+
+ protected internal override void OnSyncRequested()
+ {
+ base.OnSyncRequested();
+ ExecuteRPC(SyncRPC, new InitialData { PredictedId = _predictedId, Parent = ParentId });
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs.meta b/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs.meta
new file mode 100644
index 0000000..5509d33
--- /dev/null
+++ b/Assets/Plugins/LiteEntitySystem/PredictableEntityLogic.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 25deb888ed71d176b8e1726b2999fbed
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs b/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs
index e42f400..40cc174 100644
--- a/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs
+++ b/Assets/Plugins/LiteEntitySystem/RPCRegistrator.cs
@@ -5,13 +5,48 @@
namespace LiteEntitySystem
{
+ [Flags]
+ public enum BindOnChangeFlags
+ {
+ ///
+ /// Execute in OnSync stage. Mostly useful for RemoteControlled entities to trigger change notification
+ ///
+ ExecuteOnSync = 1 << 0,
+
+ ///
+ /// Execute on local prediction on Client and on rollback
+ ///
+ ExecuteOnPrediction = 1 << 1,
+
+ ///
+ /// Execute when value changed on server
+ ///
+ ExecuteOnServer = 1 << 2,
+
+ ///
+ /// Execute when SyncVar values reset in rollback (before OnRollback() called)
+ ///
+ ExecuteOnRollbackReset = 1 << 3,
+
+ ///
+ /// Execute after entity new() called and initial state read before OnConstructed
+ ///
+ ExecuteOnNew = 1 << 4,
+
+ ///
+ /// Combines ExecuteOnSync, ExecuteOnPrediction and ExecuteOnServer flags
+ ///
+ ExecuteAlways = ExecuteOnSync | ExecuteOnPrediction | ExecuteOnServer | ExecuteOnRollbackReset
+ }
+
public delegate void SpanAction(ReadOnlySpan data);
public delegate void SpanAction(TCaller caller, ReadOnlySpan data);
- internal delegate void MethodCallDelegate(object classPtr, ReadOnlySpan buffer);
+ internal delegate void MethodCallDelegate(InternalBaseClass classPtr, ReadOnlySpan buffer);
public readonly struct RemoteCall
{
- internal static MethodCallDelegate CreateMCD(Action methodToCall) => (classPtr, _) => methodToCall((TClass)classPtr);
+ internal static MethodCallDelegate CreateMCD(Action methodToCall) where TClass : InternalBaseClass =>
+ (classPtr, _) => methodToCall((TClass)classPtr);
internal readonly Action CachedAction;
internal readonly ushort Id;
@@ -29,7 +64,7 @@ internal RemoteCall(Action action, ushort rpcId, ExecuteFlags fl
public readonly struct RemoteCall where T : unmanaged
{
- internal static unsafe MethodCallDelegate CreateMCD(Action methodToCall) =>
+ internal static unsafe MethodCallDelegate CreateMCD(Action methodToCall) where TClass : InternalBaseClass =>
(classPtr, buffer) =>
{
fixed (byte* data = buffer)
@@ -52,7 +87,7 @@ internal RemoteCall(Action action, ushort rpcId, ExecuteFlags
public readonly struct RemoteCallSpan where T : unmanaged
{
- internal static MethodCallDelegate CreateMCD(SpanAction methodToCall) =>
+ internal static MethodCallDelegate CreateMCD(SpanAction methodToCall) where TClass : InternalBaseClass =>
(classPtr, buffer) => methodToCall((TClass)classPtr, MemoryMarshal.Cast(buffer));
internal readonly SpanAction CachedAction;
@@ -71,7 +106,7 @@ internal RemoteCallSpan(SpanAction action, ushort rpcId, Exec
public readonly struct RemoteCallSerializable where T : struct, ISpanSerializable
{
- internal static MethodCallDelegate CreateMCD(Action methodToCall) =>
+ internal static MethodCallDelegate CreateMCD(Action methodToCall) where TClass : InternalBaseClass =>
(classPtr, buffer) =>
{
var t = default(T);
@@ -116,10 +151,10 @@ internal static void CheckTarget(object ent, object target)
///
/// Variable to bind
/// Action that will be called when variable changes by sync
- /// order of execution
- public void BindOnChange(ref SyncVar syncVar, Action onChangedAction) where T : unmanaged where TEntity : InternalEntity
+ public void BindOnChange(ref SyncVar syncVar, Action onChangedAction, BindOnChangeFlags flags = BindOnChangeFlags.ExecuteOnSync) where T : unmanaged where TEntity : InternalEntity
{
_fields[syncVar.FieldId].OnSync = RemoteCall.CreateMCD(onChangedAction);
+ _fields[syncVar.FieldId].OnSyncFlags = flags;
}
///
@@ -128,11 +163,10 @@ public void BindOnChange(ref SyncVar syncVar, Action
/// Target entity for binding
/// Variable to bind
/// Action that will be called when variable changes by sync
- /// order of execution
- public void BindOnChange(TEntity self, ref SyncVar syncVar, Action onChangedAction) where T : unmanaged where TEntity : InternalEntity
+ public void BindOnChange(TEntity self, ref SyncVar syncVar, Action onChangedAction, BindOnChangeFlags flags = BindOnChangeFlags.ExecuteOnSync) where T : unmanaged where TEntity : InternalEntity
{
CheckTarget(self, onChangedAction.Target);
- BindOnChange(ref syncVar, onChangedAction.Method.CreateDelegateHelper>());
+ BindOnChange(ref syncVar, onChangedAction.Method.CreateDelegateHelper>(), flags);
}
///
@@ -191,6 +225,7 @@ public void CreateRPCAction(TEntity self, Action methodToCall, re
///
/// Creates cached rpc action
+ /// This method can be used for virtual methods
///
/// RPC method to call
/// output handle that should be used to call rpc
@@ -204,6 +239,7 @@ public void CreateRPCAction(Action methodToCall, ref RemoteCal
///
/// Creates cached rpc action with valueType argument
+ /// This method can be used for virtual methods
///
/// RPC method to call
/// output handle that should be used to call rpc
@@ -217,6 +253,7 @@ public void CreateRPCAction(Action methodToCall, ref Rem
///
/// Creates cached rpc action with Span argument
+ /// This method can be used for virtual methods
///
/// RPC method to call
/// output handle that should be used to call rpc
@@ -230,6 +267,7 @@ public void CreateRPCAction(SpanAction methodToCall, ref
///
/// Creates cached rpc action with ISpanSerializable argument
+ /// This method can be used for virtual methods
///
/// RPC method to call
/// output handle that should be used to call rpc
@@ -246,12 +284,14 @@ public void CreateRPCAction(Action methodToCall, ref Rem
public readonly ref struct SyncableRPCRegistrator
{
+ private readonly EntityFieldInfo[] _fields;
private readonly List _calls;
private readonly int _syncableOffset;
private readonly ushort _initialCallsSize;
- internal SyncableRPCRegistrator(int syncableOffset, List remoteCallsList)
+ internal SyncableRPCRegistrator(int syncableOffset, List remoteCallsList, EntityFieldInfo[] fields)
{
+ _fields = fields;
_calls = remoteCallsList;
_initialCallsSize = (ushort)_calls.Count;
_syncableOffset = syncableOffset;
@@ -308,5 +348,28 @@ public void CreateClientAction(Action methodToCall
remoteCallHandle = new RemoteCallSerializable(null, (ushort)(_calls.Count - _initialCallsSize), 0);
_calls.Add(new RpcFieldInfo(_syncableOffset, RemoteCallSerializable.CreateMCD(methodToCall)));
}
+
+ ///
+ /// Bind notification of SyncVar changes to action
+ ///
+ /// Variable to bind
+ /// Action that will be called when variable changes by sync
+ public void BindOnChange(ref SyncVar syncVar, Action onChangedAction, BindOnChangeFlags flags = BindOnChangeFlags.ExecuteOnSync) where T : unmanaged where TSyncField : SyncableField
+ {
+ _fields[syncVar.FieldId].OnSync = RemoteCall.CreateMCD(onChangedAction);
+ _fields[syncVar.FieldId].OnSyncFlags = flags;
+ }
+
+ ///
+ /// Bind notification of SyncVar changes to action
+ ///
+ /// Target entity for binding
+ /// Variable to bind
+ /// Action that will be called when variable changes by sync
+ public void BindOnChange(TSyncField self, ref SyncVar syncVar, Action onChangedAction, BindOnChangeFlags flags = BindOnChangeFlags.ExecuteOnSync) where T : unmanaged where TSyncField : SyncableField
+ {
+ RPCRegistrator.CheckTarget(self, onChangedAction.Target);
+ BindOnChange(ref syncVar, onChangedAction.Method.CreateDelegateHelper>(), flags);
+ }
}
}
\ No newline at end of file
diff --git a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs
index d65ec5f..85dbdfd 100644
--- a/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs
+++ b/Assets/Plugins/LiteEntitySystem/ServerEntityManager.cs
@@ -9,10 +9,24 @@
namespace LiteEntitySystem
{
+ ///
+ /// Rate at which server sends state packets
+ ///
public enum ServerSendRate : byte
{
+ ///
+ /// Send rate is equal to Tickrate
+ ///
EqualToFPS = 1,
+
+ ///
+ /// Send rate is Tickrate / 2
+ ///
HalfOfFPS = 2,
+
+ ///
+ /// Send rate is Tickrate / 3
+ ///
ThirdOfFPS = 3
}
@@ -34,7 +48,7 @@ public sealed class ServerEntityManager : EntityManager
private readonly byte[] _inputDecodeBuffer = new byte[NetConstants.MaxUnreliableDataSize];
private readonly NetDataReader _requestsReader = new();
private readonly Queue _pendingRPCs = new();
-
+
private NetPlayer _syncForPlayer;
private int _maxDataSize;
@@ -47,6 +61,11 @@ public sealed class ServerEntityManager : EntityManager
/// Network players count
///
public int PlayersCount => _netPlayers.Count;
+
+ ///
+ /// Timeout after which player will receive baseline state if player cannot receive big partial state
+ ///
+ public float PlayerResyncTimeout = 4.0f;
///
/// Rate at which server will make and send packets
@@ -84,6 +103,9 @@ public ServerEntityManager(
SetTickrate(framesPerSecond);
}
+ public override string GetCurrentFrameDebugInfo(DebugFrameModes modes) =>
+ modes.HasFlagFast(DebugFrameModes.Server) ? $"[Server] Tick {_tick}" : string.Empty;
+
public override void Reset()
{
base.Reset();
@@ -271,7 +293,7 @@ public T AddController(NetPlayer owner, Action initMethod = null) where T
///
/// Player that owns this controller
/// pawn that will be controlled
- /// Method that will be called after entity construction
+ /// Method that will be called before entity OnConstructed
/// Entity type
/// Created entity or null in case of limit
public T AddController(NetPlayer owner, PawnLogic entityToControl, Action initMethod = null) where T : HumanControllerLogic =>
@@ -285,7 +307,7 @@ public T AddController(NetPlayer owner, PawnLogic entityToControl, Action
///
/// Add new AI controller entity
///
- /// Method that will be called after entity construction
+ /// Method that will be called before entity OnConstructed
/// Entity type
/// Created entity or null in case of limit
public T AddAIController(Action initMethod = null) where T : AiControllerLogic =>
@@ -294,7 +316,7 @@ public T AddAIController(Action initMethod = null) where T : AiControllerL
///
/// Add new entity
///
- /// Method that will be called after entity construction
+ /// Method that will be called before entity OnConstructed
/// Entity type
/// Created entity or null in case of limit
public T AddSingleton(Action initMethod = null) where T : SingletonEntityLogic =>
@@ -303,7 +325,7 @@ public T AddSingleton(Action initMethod = null) where T : SingletonEntityL
///
/// Add new entity
///
- /// Method that will be called after entity construction
+ /// Method that will be called before entity OnConstructed
/// Entity type
/// Created entity or null in case of limit
public T AddEntity(Action initMethod = null) where T : EntityLogic =>
@@ -313,7 +335,7 @@ public T AddEntity(Action initMethod = null) where T : EntityLogic =>
/// Add new entity and set parent entity
///
/// Parent entity
- /// Method that will be called after entity construction
+ /// Method that will be called before entity OnConstructed
/// Entity type
/// Created entity or null in case of limit
public T AddEntity(EntityLogic parent, Action initMethod = null) where T : EntityLogic =>
@@ -339,7 +361,7 @@ public DeserializeResult Deserialize(AbstractNetPeer peer, ReadOnlySpan in
/// incoming data with header
public unsafe DeserializeResult Deserialize(NetPlayer player, ReadOnlySpan inData)
{
- if (inData[0] != HeaderByte)
+ if (inData.Length == 0 || inData[0] != HeaderByte)
return DeserializeResult.HeaderCheckFailed;
inData = inData.Slice(1);
@@ -378,8 +400,11 @@ public unsafe DeserializeResult Deserialize(NetPlayer player, ReadOnlySpan
humanControllerLogic.DeltaDecodeInit();
}
+ if (player.State == NetPlayerState.WaitingForFirstInput)
+ player.AvailableInput.Clear();
+
ushort clientTick = BitConverter.ToUInt16(inData);
- inData = inData.Slice(2);
+ inData = inData.Slice(sizeof(ushort));
while (inData.Length >= InputPacketHeader.Size)
{
var inputInfo = new InputInfo{ Tick = clientTick };
@@ -412,7 +437,10 @@ public unsafe DeserializeResult Deserialize(NetPlayer player, ReadOnlySpan
}
inputInfo.Header.LerpMsec = Math.Clamp(inputInfo.Header.LerpMsec, 0f, 1f);
if (Utils.SequenceDiff(inputInfo.Header.StateB, player.CurrentServerTick) > 0)
+ {
player.CurrentServerTick = inputInfo.Header.StateB;
+ player.ServerTickChangedTime = DateTime.UtcNow;
+ }
//Logger.Log($"ReadInput: {clientTick} stateA: {inputInfo.Header.StateA}");
clientTick++;
@@ -468,17 +496,21 @@ private T Add(Action initMethod) where T : InternalEntity
stateSerializer.Init(entity, _tick);
//create new rpc
- stateSerializer.MakeNewRPC();
+ var newRPCPacket = stateSerializer.MakeNewRPC();
//init and construct
initMethod?.Invoke(entity);
+
+ //refresh with data set after init method
+ stateSerializer.RefreshNewRPC(newRPCPacket);
+
ConstructEntity(entity);
//create OnConstructed rpc
stateSerializer.MakeConstructedRPC(null);
_changedEntities.Add(entity);
- _maxDataSize += stateSerializer.GetMaximumSize();
+ _maxDataSize += stateSerializer.MaximumSize;
//Debug.Log($"[SEM] Entity create. clsId: {classData.ClassId}, id: {entityId}, v: {version}");
return entity;
@@ -487,7 +519,8 @@ private T Add(Action initMethod) where T : InternalEntity
internal override void OnEntityDestroyed(InternalEntity e)
{
//sync all disabled data before destroy
- if (e is EntityLogic el)
+ /*
+ if (_netPlayers.Count > 0 && e is EntityLogic el)
{
for(int i = 0; i < _netPlayers.Count; i++)
{
@@ -495,9 +528,14 @@ internal override void OnEntityDestroyed(InternalEntity e)
MarkFieldsChanged(e, SyncGroupUtils.ToSyncFlags(~syncGroupData.EnabledGroups));
}
}
+ */
- _stateSerializers[e.Id].MakeDestroyedRPC(_tick);
base.OnEntityDestroyed(e);
+
+ if(_netPlayers.Count == 0)
+ RemoveEntity(e);
+ else
+ _stateSerializers[e.Id].MakeDestroyedRPC(_tick);
}
protected override unsafe void OnLogicTick()
@@ -516,6 +554,8 @@ protected override unsafe void OnLogicTick()
_minimalTick = _tick;
bool resizeCompressionBuffer = false;
int playersCount = _netPlayers.Count;
+
+ //apply pending incoming inputs
for (int pidx = 0; pidx < playersCount; pidx++)
{
var player = _netPlayers.GetByIndex(pidx);
@@ -548,6 +588,7 @@ protected override unsafe void OnLogicTick()
}
}
+ //update entities
if (SafeEntityUpdate)
{
foreach (var aliveEntity in AliveEntities)
@@ -560,8 +601,16 @@ protected override unsafe void OnLogicTick()
if(!aliveEntity.IsDestroyed)
aliveEntity.Update();
}
-
+
ExecuteLateConstruct();
+ ExecuteLocalSingletonsLateUpdate();
+
+ //refresh construct rpc with latest updates to entity at creation tick
+ foreach (var rpcNode in _pendingRPCs)
+ {
+ if (rpcNode.Header.Id == (ushort)InternalRPCType.Construct && rpcNode.Header.Tick == _tick)
+ _stateSerializers[rpcNode.Header.EntityId].RefreshConstructedRPC(rpcNode);
+ }
foreach (var lagCompensatedEntity in LagCompensatedEntities)
ClassDataDict[lagCompensatedEntity.ClassId].WriteHistory(lagCompensatedEntity, _tick);
@@ -571,6 +620,8 @@ protected override unsafe void OnLogicTick()
//==================================================================
if (playersCount == 0 || _tick % (int) SendRate != 0)
return;
+
+ var currentTimeUtc = DateTime.UtcNow;
//remove old rpcs
while (_pendingRPCs.TryPeek(out var rpcNode) && Utils.SequenceDiff(rpcNode.Header.Tick, _minimalTick) < 0)
@@ -598,6 +649,9 @@ protected override unsafe void OnLogicTick()
var player = _netPlayers.GetByIndex(pidx);
_syncForPlayer = null;
int rpcSize = 0;
+ RPCHeader prevRpcHeader = new();
+
+ //if player need baseline state - send it reliably
if (player.State == NetPlayerState.RequestBaseline)
{
int originalLength = 0;
@@ -610,7 +664,8 @@ protected override unsafe void OnLogicTick()
{
if (_stateSerializers[e.Id].ShouldSync(player.Id, false))
{
- _stateSerializers[e.Id].MakeNewRPC();
+ var newRPC = _stateSerializers[e.Id].MakeNewRPC();
+ _stateSerializers[e.Id].RefreshNewRPC(newRPC);
_stateSerializers[e.Id].MakeConstructedRPC(player);
}
}
@@ -622,15 +677,15 @@ protected override unsafe void OnLogicTick()
continue;
var entity = EntitiesDict[rpcNode.Header.EntityId];
if(rpcNode.AllowToSendForPlayer(player.Id, entity.OwnerId))
- rpcNode.WriteTo(packetBuffer, ref originalLength);
+ rpcNode.WriteTo(packetBuffer, ref originalLength, ref prevRpcHeader);
}
rpcSize = originalLength;
}
else //like baseline but only queued rpcs and diff data
{
foreach (var rpcNode in _pendingRPCs)
- if(ShouldSendRPC(rpcNode, player))
- rpcNode.WriteTo(packetBuffer, ref originalLength);
+ if(ShouldSendRPC(rpcNode, player, true))
+ rpcNode.WriteTo(packetBuffer, ref originalLength, ref prevRpcHeader);
rpcSize = originalLength;
foreach (var e in GetEntities())
@@ -663,15 +718,25 @@ protected override unsafe void OnLogicTick()
player.StateBTick = _tick;
player.CurrentServerTick = _tick;
player.State = NetPlayerState.WaitingForFirstInput;
+ player.ServerTickChangedTime = DateTime.UtcNow;
Logger.Log($"[SEM] SendWorld to player {player.Id}. orig: {originalLength} b, compressed: {encodedLength} b, ExecutedTick: {_tick}");
continue;
}
+
if (player.State != NetPlayerState.Active)
{
//waiting to load initial state
continue;
}
+ //check if player cannot receive partial state due to high packet loss and large state size
+ if ((currentTimeUtc - player.ServerTickChangedTime).TotalSeconds > PlayerResyncTimeout)
+ {
+ Logger.Log($"P:{player.Id} Request baseline {_tick} because timeout");
+ player.State = NetPlayerState.RequestBaseline;
+ continue;
+ }
+
//Partial diff sync
var header = (DiffPartHeader*)packetBuffer;
header->UserHeader = HeaderByte;
@@ -683,11 +748,12 @@ protected override unsafe void OnLogicTick()
foreach (var rpcNode in _pendingRPCs)
{
- if(!ShouldSendRPC(rpcNode, player))
+ if(!ShouldSendRPC(rpcNode, player, false))
continue;
- rpcSize += rpcNode.TotalSize;
- rpcNode.WriteTo(packetBuffer, ref writePosition);
+ //use another approach to sum size because writePosition can be edited by CheckOverflow
+ //!!!
+ rpcSize += rpcNode.WriteTo(packetBuffer, ref writePosition, ref prevRpcHeader);
CheckOverflowAndSend(player, header, packetBuffer, ref writePosition, maxPartSize);
if(player.State == NetPlayerState.RequestBaseline)
break;
@@ -720,7 +786,7 @@ protected override unsafe void OnLogicTick()
//Logger.Log($"Removed highest order entity: {e.UpdateOrderNum}, new highest: {_nextOrderNum}");
}
_entityIdQueue.ReuseId(entity.Id);
- _maxDataSize -= stateSerializer.GetMaximumSize();
+ _maxDataSize -= stateSerializer.MaximumSize;
stateSerializer.Free();
//Logger.Log($"[SRV] RemoveEntity: {e.Id}");
RemoveEntity(entity);
@@ -761,6 +827,7 @@ protected override unsafe void OnLogicTick()
_netPlayers.GetByIndex(0).Peer.TriggerSend();
//Logger.Log($"ServerSendTick: {_tick}");
+ //Logger.Log($"SendTotal: {RemoteCallPacket.TotalWriteSizeNormal}, SendDelta: {RemoteCallPacket.TotalWriteSizeDelta}");
return;
void CheckOverflowAndSend(NetPlayer player, DiffPartHeader *header, byte* packetBuffer, ref int writePosition, int maxPartSize)
{
@@ -769,7 +836,7 @@ void CheckOverflowAndSend(NetPlayer player, DiffPartHeader *header, byte* packet
{
if (header->Part == MaxParts-1)
{
- Logger.Log($"P:{player.Id} Request baseline {_tick}");
+ Logger.Log($"P:{player.Id} Request baseline {_tick} because state size");
player.State = NetPlayerState.RequestBaseline;
break;
}
@@ -785,7 +852,7 @@ void CheckOverflowAndSend(NetPlayer player, DiffPartHeader *header, byte* packet
}
}
- bool ShouldSendRPC(RemoteCallPacket rpcNode, NetPlayer player)
+ bool ShouldSendRPC(RemoteCallPacket rpcNode, NetPlayer player, bool isBaseline)
{
//SyncForPlayer calls?
if (rpcNode.OnlyForPlayer != null && rpcNode.OnlyForPlayer != player)
@@ -808,11 +875,11 @@ bool ShouldSendRPC(RemoteCallPacket rpcNode, NetPlayer player)
return false;
var entity = EntitiesDict[rpcNode.Header.EntityId];
- if (!rpcNode.AllowToSendForPlayer(player.Id, entity.OwnerId))
+ if (!rpcNode.AllowToSendForPlayer(player.Id, entity.InternalOwnerId.Value))
return false;
//check sync groups
- if (entity.OwnerId != player.Id)
+ if (entity.InternalOwnerId.Value != player.Id)
{
if (entity is EntityLogic el &&
player.EntitySyncInfo.TryGetValue(el, out var syncGroups) &&
@@ -823,23 +890,46 @@ bool ShouldSendRPC(RemoteCallPacket rpcNode, NetPlayer player)
return false;
}
}
-
- if (rpcNode.Header.Id == RemoteCallPacket.ConstructRPCId)
- stateSerializer.RefreshConstructedRPC(rpcNode, player);
+
+ switch ((InternalRPCType)rpcNode.Header.Id)
+ {
+ case InternalRPCType.NewOwned when entity.InternalOwnerId.Value != player.Id || isBaseline:
+ rpcNode.Header.Id = (ushort)InternalRPCType.New;
+ break;
+
+ //do this change only for diff states for best local->remote entity swap
+ case InternalRPCType.New when entity.InternalOwnerId.Value == player.Id && !isBaseline:
+ rpcNode.Header.Id = (ushort)InternalRPCType.NewOwned;
+ break;
+
+ case InternalRPCType.Construct:
+ stateSerializer.RefreshSyncGroupsVariable(player, new Span(rpcNode.Data));
+ break;
+ }
return true;
}
}
- internal override void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue)
+ internal override unsafe void EntityFieldChanged(InternalEntity entity, ushort fieldId, ref T newValue, ref T oldValue, bool skipOnSync)
{
if (entity.IsRemoved)
{
//old freed entity
return;
}
+ if(entity is AiControllerLogic)
+ return;
+
_changedEntities.Add(entity);
_stateSerializers[entity.Id].UpdateFieldValue(fieldId, _minimalTick, _tick, ref newValue);
+
+ ref var fieldInfo = ref entity.ClassData.Fields[fieldId];
+ if (!skipOnSync && (fieldInfo.OnSyncFlags & BindOnChangeFlags.ExecuteOnServer) != 0)
+ {
+ T value = oldValue;
+ fieldInfo.OnSync(fieldInfo.GetTargetObject(entity), new ReadOnlySpan(&value, fieldInfo.IntSize));
+ }
}
internal void MarkFieldsChanged(InternalEntity entity, SyncFlags onlyWithFlags)
@@ -848,34 +938,41 @@ internal void MarkFieldsChanged(InternalEntity entity, SyncFlags onlyWithFlags)
_stateSerializers[entity.Id].MarkFieldsChanged(_minimalTick, _tick, onlyWithFlags);
}
- internal void AddRemoteCall(InternalEntity entity, ushort rpcId, ExecuteFlags flags)
+ internal RemoteCallPacket AddRemoteCall(InternalEntity entity, ushort rpcId, ExecuteFlags flags)
{
if (PlayersCount == 0 ||
entity.IsRemoved ||
entity is AiControllerLogic ||
(flags & ExecuteFlags.SendToAll) == 0)
- return;
+ return null;
var rpc = _rpcPool.Count > 0 ? _rpcPool.Dequeue() : new RemoteCallPacket();
rpc.Init(_syncForPlayer, entity, _tick, 0, rpcId, flags);
_pendingRPCs.Enqueue(rpc);
_maxDataSize += rpc.TotalSize;
+ return rpc;
+ }
+
+ internal void NotifyRPCResized(int prevTotalSize, int newTotalSize)
+ {
+ _maxDataSize -= prevTotalSize;
+ _maxDataSize += newTotalSize;
}
- internal unsafe void AddRemoteCall(InternalEntity entity, ReadOnlySpan value, ushort rpcId, ExecuteFlags flags) where T : unmanaged
+ internal unsafe RemoteCallPacket AddRemoteCall(InternalEntity entity, ReadOnlySpan value, ushort rpcId, ExecuteFlags flags) where T : unmanaged
{
if (PlayersCount == 0 ||
entity.IsRemoved ||
entity is AiControllerLogic ||
(flags & ExecuteFlags.SendToAll) == 0)
- return;
+ return null;
var rpc = _rpcPool.Count > 0 ? _rpcPool.Dequeue() : new RemoteCallPacket();
int dataSize = sizeof(T) * value.Length;
if (dataSize > ushort.MaxValue)
{
Logger.LogError($"DataSize on rpc: {rpcId}, entity: {entity} is more than {ushort.MaxValue}");
- return;
+ return null;
}
rpc.Init(_syncForPlayer, entity, _tick, (ushort)dataSize, rpcId, flags);
@@ -884,6 +981,7 @@ entity is AiControllerLogic ||
RefMagic.CopyBlock(rawData, rawValue, (uint)dataSize);
_pendingRPCs.Enqueue(rpc);
_maxDataSize += rpc.TotalSize;
+ return rpc;
}
}
}
diff --git a/Assets/Plugins/LiteEntitySystem/SpanReader.cs b/Assets/Plugins/LiteEntitySystem/SpanReader.cs
index a67fc01..74fe0c5 100644
--- a/Assets/Plugins/LiteEntitySystem/SpanReader.cs
+++ b/Assets/Plugins/LiteEntitySystem/SpanReader.cs
@@ -27,6 +27,17 @@ public void Get(out T result, Func constructor) where T : class, ISpanSeri
result = constructor();
result.Deserialize(ref this);
}
+
+ public void GetStruct(out T result) where T : unmanaged => result = GetStruct();
+
+ public unsafe T GetStruct() where T : unmanaged
+ {
+ T value;
+ fixed (byte* rawData = RawData)
+ value = *(T*)(rawData+Position);
+ Position += sizeof(T);
+ return value;
+ }
public void Get(out byte result) => result = GetByte();
public void Get(out sbyte result) => result = (sbyte)GetByte();
diff --git a/Assets/Plugins/LiteEntitySystem/SpanWriter.cs b/Assets/Plugins/LiteEntitySystem/SpanWriter.cs
index b5dbb47..319a793 100644
--- a/Assets/Plugins/LiteEntitySystem/SpanWriter.cs
+++ b/Assets/Plugins/LiteEntitySystem/SpanWriter.cs
@@ -99,6 +99,12 @@ public void Put(byte[] data)
new ReadOnlySpan(data).CopyTo(RawData.Slice(Position, data.Length));
Position += data.Length;
}
+
+ public void Put(ReadOnlySpan data)
+ {
+ data.CopyTo(RawData.Slice(Position, data.Length));
+ Position += data.Length;
+ }
public void PutSBytesWithLength(sbyte[] data, int offset, ushort length)
{
@@ -113,6 +119,21 @@ public void PutBytesWithLength(byte[] data, int offset, ushort length)
new ReadOnlySpan(data, offset, length).CopyTo(RawData.Slice(Position + 2, length));
Position += 2 + length;
}
+
+ public unsafe void PutStruct(T data) where T : unmanaged
+ {
+ Put(sizeof(T));
+ fixed (byte* rawData = RawData)
+ *(T*) (rawData + Position) = data;
+ Position += sizeof(T);
+ }
+
+ public void PutBytesWithLength(ReadOnlySpan data)
+ {
+ Put(data.Length);
+ data.CopyTo(RawData.Slice(Position + 2, data.Length));
+ Position += 2 + data.Length;
+ }
public void PutArray(T[] arr, int sz) where T : unmanaged
{
diff --git a/Assets/Plugins/LiteEntitySystem/SyncChilds.cs b/Assets/Plugins/LiteEntitySystem/SyncChilds.cs
index f84b71b..5345f94 100644
--- a/Assets/Plugins/LiteEntitySystem/SyncChilds.cs
+++ b/Assets/Plugins/LiteEntitySystem/SyncChilds.cs
@@ -4,7 +4,7 @@
namespace LiteEntitySystem
{
- public sealed class SyncChilds : SyncableField, IEnumerable
+ public sealed class SyncChilds : SyncableFieldCustomRollback, IEnumerable
{
class EqualityComparer : IEqualityComparer
{
@@ -27,8 +27,6 @@ class EqualityComparer : IEqualityComparer
private static RemoteCall _removeAction;
private static RemoteCallSpan _initAction;
- public override bool IsRollbackSupported => true;
-
protected internal override void RegisterRPC(ref SyncableRPCRegistrator r)
{
r.CreateClientAction(this, Add, ref _addAction);
@@ -39,17 +37,18 @@ protected internal override void RegisterRPC(ref SyncableRPCRegistrator r)
protected internal override void OnRollback()
{
- if (_data == null || _serverData == null)
+ if (_data == null)
return;
_data.Clear();
+ //Logger.Log($"ClearChilds: {ParentEntity.EntityManager.UpdateMode}");
+ if (_serverData == null)
+ return;
foreach (var x in _serverData)
_data.Add(x);
}
protected internal override void BeforeReadRPC()
{
- if (_data == null)
- return;
_serverData ??= new HashSet(SharedReferenceComparer);
_tempData = _data;
_data = _serverData;
@@ -57,9 +56,16 @@ protected internal override void BeforeReadRPC()
protected internal override void AfterReadRPC()
{
- if (_data == null || _tempData == null)
- return;
_data = _tempData;
+ _tempData = null;
+ if (_serverData == null)
+ return;
+ if (_data == null)
+ {
+ if (_serverData.Count == 0)
+ return;
+ _data = new HashSet(SharedReferenceComparer);
+ }
_data.Clear();
foreach (var kv in _serverData)
_data.Add(kv);
@@ -112,6 +118,9 @@ internal void Add(EntitySharedReference x)
_data ??= new HashSet(SharedReferenceComparer);
_data.Add(x);
ExecuteRPC(_addAction, x);
+ MarkAsChanged();
+ //if(IsClient)
+ // Logger.Log($"AddChild: {x} {ParentEntity.EntityManager.UpdateMode} at tick: {ParentEntity.ClientManager.Tick}");
}
internal void Clear()
@@ -120,6 +129,7 @@ internal void Clear()
return;
_data.Clear();
ExecuteRPC(_clearAction);
+ MarkAsChanged();
}
public bool Contains(EntitySharedReference x) => _data != null && _data.Contains(x);
@@ -131,6 +141,7 @@ internal bool Remove(EntitySharedReference key)
if (_data == null || !_data.Remove(key))
return false;
ExecuteRPC(_removeAction, key);
+ MarkAsChanged();
return true;
}
diff --git a/Assets/Plugins/LiteEntitySystem/SyncVar.cs b/Assets/Plugins/LiteEntitySystem/SyncVar.cs
index 114883e..8fd8673 100644
--- a/Assets/Plugins/LiteEntitySystem/SyncVar.cs
+++ b/Assets/Plugins/LiteEntitySystem/SyncVar.cs
@@ -68,44 +68,85 @@ public class SyncVarFlags : Attribute
public SyncVarFlags(SyncFlags flags) => Flags = flags;
}
+ ///
+ /// Synchronized variable
+ ///
+ /// Variable type
[StructLayout(LayoutKind.Sequential)]
- public struct SyncVar : IEquatable, IEquatable> where T : unmanaged
+ public struct SyncVar : ISyncVar, IEquatable, IEquatable> where T : unmanaged
{
private T _value;
+ private T _interpValue;
+
internal ushort FieldId;
internal InternalEntity Container;
+
+ ///
+ /// Interpolated value on client (on server equals to Value)
+ ///
+ public T InterpolatedValue => Container == null || Container.IsServer
+ ? _value
+ : Container.ClientManager.GetInterpolatedValue(ref this, _interpValue);
+
+ //for interpolation
+ void ISyncVar.SvSetInterpValue(T value) => _interpValue = value;
+ void ISyncVar.SvSetInterpValueFromCurrent() => _interpValue = _value;
+
+ void ISyncVar.SvSetDirect(T value) => _value = value;
- internal void SetDirect(T value) => _value = value;
+ void ISyncVar.SvSetDirectAndStorePrev(T value, out T prevValue)
+ {
+ prevValue = _value;
+ _value = value;
+ }
+
+ bool ISyncVar.SvSetFromAndSync(ref T value)
+ {
+ if (!Utils.FastEquals(ref _value, ref value))
+ {
+ // ReSharper disable once SwapViaDeconstruction
+ var oldValue = _value;
+ _value = value;
+ value = oldValue;
+ return true;
+ }
+ return false;
+ }
+ ///
+ /// Actual logical value
+ ///
public T Value
{
get => _value;
set
{
- if (Container != null && !Utils.FastEquals(ref value, ref _value))
- Container.EntityManager.EntityFieldChanged(Container, FieldId, ref value);
+ var oldValue = _value;
_value = value;
+ if (Container != null && !Utils.FastEquals(ref value, ref oldValue))
+ Container.EntityManager.EntityFieldChanged(Container, FieldId, ref value, ref oldValue, false);
}
}
+ ///
+ /// Set value without triggering local OnSync notifications if there is any
+ ///
+ ///
+ public void SetValueWithoutOnSyncNotification(T value)
+ {
+ var oldValue = _value;
+ _value = value;
+ if (Container != null && !Utils.FastEquals(ref value, ref oldValue))
+ Container.EntityManager.EntityFieldChanged(Container, FieldId, ref value, ref oldValue, true);
+ }
+
internal void Init(InternalEntity container, ushort fieldId)
{
Container = container;
FieldId = fieldId;
- Container?.EntityManager.EntityFieldChanged(Container, FieldId, ref _value);
- }
-
- internal unsafe bool SetFromAndSync(byte* data)
- {
- if (!Utils.FastEquals(ref _value, data))
- {
- var temp = _value;
- _value = *(T*)data;
- *(T*)data = temp;
- return true;
- }
- _value = *(T*)data;
- return false;
+ T defaultValue = default;
+ if(!Utils.FastEquals(ref _value, ref defaultValue))
+ Container.EntityManager.EntityFieldChanged(Container, FieldId, ref _value, ref defaultValue, false);
}
public static implicit operator T(SyncVar sv) => sv._value;
diff --git a/Assets/Plugins/LiteEntitySystem/SyncableField.cs b/Assets/Plugins/LiteEntitySystem/SyncableField.cs
index fbbaef5..93944b2 100644
--- a/Assets/Plugins/LiteEntitySystem/SyncableField.cs
+++ b/Assets/Plugins/LiteEntitySystem/SyncableField.cs
@@ -30,7 +30,6 @@ public abstract class SyncableField : InternalBaseClass
private InternalEntity _parentEntity;
private ExecuteFlags _executeFlags;
- internal int FieldId;
internal ushort RPCOffset;
///
@@ -43,11 +42,6 @@ public abstract class SyncableField : InternalBaseClass
///
protected internal bool IsServer => _parentEntity != null && _parentEntity.IsServer;
- ///
- /// Is supported rollback by this syncable field
- ///
- public virtual bool IsRollbackSupported => false;
-
///
/// Owner of this syncable field
///
@@ -55,6 +49,10 @@ public abstract class SyncableField : InternalBaseClass
internal void Init(InternalEntity parentEntity, SyncFlags fieldFlags)
{
+ //already initialized
+ if (_parentEntity != null)
+ return;
+
_parentEntity = parentEntity;
if (fieldFlags.HasFlagFast(SyncFlags.OnlyForOwner))
_executeFlags = ExecuteFlags.SendToOwner;
@@ -68,21 +66,6 @@ internal void Init(InternalEntity parentEntity, SyncFlags fieldFlags)
/// Owner of this syncable field casted to EntityLogic
///
protected EntityLogic ParentEntityLogic => _parentEntity as EntityLogic;
-
- protected internal virtual void BeforeReadRPC()
- {
-
- }
-
- protected internal virtual void AfterReadRPC()
- {
-
- }
-
- protected internal virtual void OnRollback()
- {
-
- }
protected internal virtual void RegisterRPC(ref SyncableRPCRegistrator r)
{
@@ -120,4 +103,25 @@ protected void ExecuteRPC(in RemoteCallSerializable rpc, T value) where T
}
}
}
+
+ ///
+ /// Syncable fields with custom rollback notifications and implementation
+ ///
+ public abstract class SyncableFieldCustomRollback : SyncableField
+ {
+ ///
+ /// Marks that SyncableField was modified on client and add parent entity to Rollback list
+ ///
+ protected void MarkAsChanged()
+ {
+ if(ParentEntity != null && ParentEntity.IsClient)
+ ParentEntity.ClientManager.MarkEntityChanged(ParentEntity);
+ }
+
+ protected internal abstract void BeforeReadRPC();
+
+ protected internal abstract void AfterReadRPC();
+
+ protected internal abstract void OnRollback();
+ }
}
\ No newline at end of file
diff --git a/Assets/Plugins/LiteEntitySystem/Transport/LiteNetLibNetPeer.cs b/Assets/Plugins/LiteEntitySystem/Transport/LiteNetLibNetPeer.cs
index 60217cb..8569165 100644
--- a/Assets/Plugins/LiteEntitySystem/Transport/LiteNetLibNetPeer.cs
+++ b/Assets/Plugins/LiteEntitySystem/Transport/LiteNetLibNetPeer.cs
@@ -5,9 +5,9 @@ namespace LiteEntitySystem.Transport
{
public class LiteNetLibNetPeer : AbstractNetPeer
{
- public readonly NetPeer NetPeer;
+ public readonly LiteNetPeer NetPeer;
- public LiteNetLibNetPeer(NetPeer netPeer, bool assignToTag)
+ public LiteNetLibNetPeer(LiteNetPeer netPeer, bool assignToTag)
{
NetPeer = netPeer;
if(assignToTag)
@@ -15,15 +15,15 @@ public LiteNetLibNetPeer(NetPeer netPeer, bool assignToTag)
}
public override void TriggerSend() => NetPeer.NetManager.TriggerUpdate();
- public override void SendReliableOrdered(ReadOnlySpan data) => NetPeer.Send(data, 0, DeliveryMethod.ReliableOrdered);
- public override void SendUnreliable(ReadOnlySpan data) => NetPeer.Send(data, 0, DeliveryMethod.Unreliable);
+ public override void SendReliableOrdered(ReadOnlySpan data) => NetPeer.Send(data, DeliveryMethod.ReliableOrdered);
+ public override void SendUnreliable(ReadOnlySpan data) => NetPeer.Send(data, DeliveryMethod.Unreliable);
public override int GetMaxUnreliablePacketSize() => NetPeer.GetMaxSinglePacketSize(DeliveryMethod.Unreliable);
public override string ToString() => NetPeer.ToString();
}
public static class LiteNetLibExtensions
{
- public static LiteNetLibNetPeer GetLiteNetLibNetPeerFromTag(this NetPeer peer) => (LiteNetLibNetPeer)peer.Tag;
+ public static LiteNetLibNetPeer GetLiteNetLibNetPeerFromTag(this LiteNetPeer peer) => (LiteNetLibNetPeer)peer.Tag;
public static LiteNetLibNetPeer GetLiteNetLibNetPeer(this NetPlayer player) => (LiteNetLibNetPeer)player.Peer;
}
}
\ No newline at end of file
diff --git a/Assets/Plugins/LiteEntitySystem/Utils.cs b/Assets/Plugins/LiteEntitySystem/Utils.cs
index 73ca22c..e436e39 100644
--- a/Assets/Plugins/LiteEntitySystem/Utils.cs
+++ b/Assets/Plugins/LiteEntitySystem/Utils.cs
@@ -153,6 +153,26 @@ public static unsafe bool FastEquals(ref T a, byte *x2) where T : unmanaged
}
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe bool FastEquals(byte *x1, byte *x2, int l)
+ {
+ for (int i=0; i < l/8; i++, x1+=8, x2+=8)
+ if (*(long*)x1 != *(long*)x2) return false;
+ if ((l & 4)!=0) { if (*(int*)x1!=*(int*)x2) return false; x1+=4; x2+=4; }
+ if ((l & 2)!=0) { if (*(short*)x1!=*(short*)x2) return false; x1+=2; x2+=2; }
+ return (l & 1) == 0 || *x1 == *x2;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe bool IsZero(byte *x1, int l)
+ {
+ for (int i=0; i < l/8; i++, x1+=8)
+ if (*(long*)x1 != 0) return false;
+ if ((l & 4)!=0) { if (*(int*)x1!=0) return false; x1+=4; }
+ if ((l & 2)!=0) { if (*(short*)x1!=0) return false; x1+=2; }
+ return (l & 1) == 0 || *x1 == 0;
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ResizeIfFull(ref T[] arr, int count)
{
@@ -216,6 +236,18 @@ public static ushort LerpSequence(ushort seq1, ushort seq2, float t) =>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static T CreateDelegateHelper(this MethodInfo method) where T : Delegate => (T)method.CreateDelegate(typeof(T));
+
+ public static string BytesToHexString(ReadOnlySpan bytes)
+ {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < bytes.Length; i++)
+ {
+ sb.Append(bytes[i].ToString("X2"));
+ if(i != bytes.Length - 1)
+ sb.Append('_');
+ }
+ return sb.ToString();
+ }
internal static Stack GetBaseTypes(Type ofType, Type until, bool includeSelf, bool includeLast)
{
@@ -305,7 +337,7 @@ static Utils()
var field = typeof(TestOffset).GetField("TestValue");
int offset = GetFieldOffset(field);
var to = new TestOffset();
- if (RefMagic.RefFieldValue(to, offset) != to.TestValue)
+ if (RefMagic.GetFieldValue(to, offset) != to.TestValue)
Logger.LogError("Unknown native field offset");
}
}
diff --git a/Assets/Scripts/Client/ClientLogic.cs b/Assets/Scripts/Client/ClientLogic.cs
index b247d21..7268822 100644
--- a/Assets/Scripts/Client/ClientLogic.cs
+++ b/Assets/Scripts/Client/ClientLogic.cs
@@ -12,7 +12,7 @@
namespace Code.Client
{
- public class ClientLogic : MonoBehaviour, INetEventListener
+ public class ClientLogic : MonoBehaviour, ILiteNetEventListener
{
[SerializeField] private ClientPlayerView _clientPlayerViewPrefab;
[SerializeField] private RemotePlayerView _remotePlayerViewPrefab;
@@ -24,12 +24,12 @@ public class ClientLogic : MonoBehaviour, INetEventListener
private GamePool _shootsPool;
private GamePool _hitsPool;
- private NetManager _netManager;
+ private LiteNetManager _netManager;
private NetDataWriter _writer;
private NetPacketProcessor _packetProcessor;
private string _userName;
- private NetPeer _server;
+ private LiteNetPeer _server;
private ClientEntityManager _entityManager;
private int _ping;
@@ -81,7 +81,7 @@ private void Awake()
_shootsPool = new GamePool(ShootEffectConstructor, 200);
_hitsPool = new GamePool(HitEffectConstructor, 200);
_packetProcessor = new NetPacketProcessor();
- _netManager = new NetManager(this)
+ _netManager = new LiteNetManager(this)
{
AutoRecycle = true,
EnableStatistics = true,
@@ -158,7 +158,7 @@ public void SpawnHit(Vector2 from)
_server.Send(_writer, deliveryMethod);
}
- void INetEventListener.OnPeerConnected(NetPeer peer)
+ void ILiteNetEventListener.OnPeerConnected(LiteNetPeer peer)
{
Debug.Log("[C] Connected to server: " + peer);
_server = peer;
@@ -192,7 +192,7 @@ void INetEventListener.OnPeerConnected(NetPeer peer)
}, true);
}
- void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo)
+ void ILiteNetEventListener.OnPeerDisconnected(LiteNetPeer peer, DisconnectInfo disconnectInfo)
{
_server = null;
_entityManager = null;
@@ -204,12 +204,12 @@ void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnec
}
}
- void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketError)
+ void ILiteNetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketError)
{
Debug.Log("[C] NetworkError: " + socketError);
}
- void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod)
+ void ILiteNetEventListener.OnNetworkReceive(LiteNetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod)
{
byte packetType = reader.PeekByte();
var pt = (PacketType) packetType;
@@ -230,18 +230,18 @@ void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, by
}
}
- void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader,
+ void ILiteNetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader,
UnconnectedMessageType messageType)
{
}
- void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency)
+ void ILiteNetEventListener.OnNetworkLatencyUpdate(LiteNetPeer peer, int latency)
{
_ping = latency;
}
- void INetEventListener.OnConnectionRequest(ConnectionRequest request)
+ void ILiteNetEventListener.OnConnectionRequest(ConnectionRequest request)
{
request.Reject();
}
diff --git a/Assets/Scripts/Client/RemotePlayerView.cs b/Assets/Scripts/Client/RemotePlayerView.cs
index 7197510..96d756c 100644
--- a/Assets/Scripts/Client/RemotePlayerView.cs
+++ b/Assets/Scripts/Client/RemotePlayerView.cs
@@ -16,6 +16,7 @@ public static RemotePlayerView Create(RemotePlayerView prefab, BasePlayer player
obj._player = player;
var textObj = new GameObject($"text_{player.Name}_{player.Id}");
textObj.transform.SetParent(obj.transform);
+ textObj.transform.localPosition = Vector3.zero;
obj._health = textObj.AddComponent();
obj._health.characterSize = 0.3f;
obj._health.anchor = TextAnchor.MiddleCenter;
diff --git a/Assets/Scripts/Server/ServerLogic.cs b/Assets/Scripts/Server/ServerLogic.cs
index e393898..ae8216c 100644
--- a/Assets/Scripts/Server/ServerLogic.cs
+++ b/Assets/Scripts/Server/ServerLogic.cs
@@ -11,9 +11,9 @@
namespace Code.Server
{
- public class ServerLogic : MonoBehaviour, INetEventListener
+ public class ServerLogic : MonoBehaviour, ILiteNetEventListener
{
- private NetManager _netManager;
+ private LiteNetManager _netManager;
private NetPacketProcessor _packetProcessor;
public ushort Tick => _serverEntityManager.Tick;
private ServerEntityManager _serverEntityManager;
@@ -27,7 +27,7 @@ static ServerLogic()
private void Awake()
{
EntityManager.RegisterFieldType(Vector2.Lerp);
- _netManager = new NetManager(this)
+ _netManager = new LiteNetManager(this)
{
AutoRecycle = true,
PacketPoolSize = 1000,
@@ -43,7 +43,7 @@ private void Awake()
#endif
_packetProcessor = new NetPacketProcessor();
- _packetProcessor.SubscribeReusable(OnJoinReceived);
+ _packetProcessor.SubscribeReusable(OnJoinReceived);
var typesMap = new EntityTypesMap()
.Register(GameEntities.Player, e => new BasePlayer(e))
@@ -89,7 +89,7 @@ private void Update()
_serverEntityManager?.Update();
}
- private void OnJoinReceived(JoinPacket joinPacket, NetPeer peer)
+ private void OnJoinReceived(JoinPacket joinPacket, LiteNetPeer peer)
{
Debug.Log("[S] Join packet received: " + joinPacket.UserName);
@@ -109,12 +109,12 @@ private void OnJoinReceived(JoinPacket joinPacket, NetPeer peer)
_serverEntityManager.AddController(serverPlayer, player);
}
- void INetEventListener.OnPeerConnected(NetPeer peer)
+ void ILiteNetEventListener.OnPeerConnected(LiteNetPeer peer)
{
Debug.Log("[S] Player connected: " + peer);
}
- void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo)
+ void ILiteNetEventListener.OnPeerDisconnected(LiteNetPeer peer, DisconnectInfo disconnectInfo)
{
Debug.Log("[S] Player disconnected: " + disconnectInfo.Reason);
@@ -124,12 +124,12 @@ void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnec
}
}
- void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketError)
+ void ILiteNetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketError)
{
Debug.Log("[S] NetworkError: " + socketError);
}
- void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod)
+ void ILiteNetEventListener.OnNetworkReceive(LiteNetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod)
{
byte packetType = reader.PeekByte();
switch ((PacketType)packetType)
@@ -149,18 +149,18 @@ void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, by
}
}
- void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader,
+ void ILiteNetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader,
UnconnectedMessageType messageType)
{
}
- void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency)
+ void ILiteNetEventListener.OnNetworkLatencyUpdate(LiteNetPeer peer, int latency)
{
}
- void INetEventListener.OnConnectionRequest(ConnectionRequest request)
+ void ILiteNetEventListener.OnConnectionRequest(ConnectionRequest request)
{
request.AcceptIfKey("ExampleGame");
}
diff --git a/Assets/Scripts/Shared/BasePlayer.cs b/Assets/Scripts/Shared/BasePlayer.cs
index cb5e247..a5d8fb7 100644
--- a/Assets/Scripts/Shared/BasePlayer.cs
+++ b/Assets/Scripts/Shared/BasePlayer.cs
@@ -35,8 +35,8 @@ public class BasePlayer : PawnLogic
private bool _projectileFire;
public byte Health => _health;
- public Vector2 Position => _position;
- public float Rotation => _rotation.Value;
+ public Vector2 Position => _position.InterpolatedValue;
+ public float Rotation => _rotation.InterpolatedValue;
public GameObject UnityObject;
public GameObject View;
@@ -63,7 +63,7 @@ protected override void OnConstructed()
_rigidbody.isKinematic = true;
_collider.isTrigger = true;
_collider.size = Vector2.one * 0.66f;
- UnityObject.AddComponent().AttachedPlayer = this;
+ UnityObject.AddComponent().AttachedPlayer = this;
Debug.Log($"Player joined: {Name.Value}");
}
@@ -177,7 +177,7 @@ protected override void Update()
for (int i = 0; i < hitsCount; i++)
{
ref var hit = ref RaycastHits[i];
- if (hit.transform.TryGetComponent(out var playerProxy) && playerProxy.AttachedPlayer != this )
+ if (hit.transform.TryGetComponent(out var playerProxy) && playerProxy.AttachedPlayer != this )
{
if (EntityManager.InNormalState)
ExecuteRPC(_hitRemoteCall, new HitPacket { Position = hit.point });
diff --git a/Assets/Scripts/Shared/BasePlayerView.cs b/Assets/Scripts/Shared/PlayerProxy.cs
similarity index 85%
rename from Assets/Scripts/Shared/BasePlayerView.cs
rename to Assets/Scripts/Shared/PlayerProxy.cs
index 1a2281b..f23a3ee 100644
--- a/Assets/Scripts/Shared/BasePlayerView.cs
+++ b/Assets/Scripts/Shared/PlayerProxy.cs
@@ -2,7 +2,7 @@
namespace Code.Shared
{
- public class BasePlayerView : MonoBehaviour
+ public class PlayerProxy : MonoBehaviour
{
public BasePlayer AttachedPlayer;
diff --git a/Assets/Scripts/Shared/BasePlayerView.cs.meta b/Assets/Scripts/Shared/PlayerProxy.cs.meta
similarity index 100%
rename from Assets/Scripts/Shared/BasePlayerView.cs.meta
rename to Assets/Scripts/Shared/PlayerProxy.cs.meta
diff --git a/Assets/Scripts/Shared/SimpleProjectile.cs b/Assets/Scripts/Shared/SimpleProjectile.cs
index 31912bb..2c0e377 100644
--- a/Assets/Scripts/Shared/SimpleProjectile.cs
+++ b/Assets/Scripts/Shared/SimpleProjectile.cs
@@ -13,7 +13,7 @@ public struct ProjectileInitParams
}
[EntityFlags(EntityFlags.UpdateOnClient)]
- public class SimpleProjectile : EntityLogic
+ public class SimpleProjectile : PredictableEntityLogic
{
private static readonly RaycastHit2D[] RaycastHits = new RaycastHit2D[10];
@@ -87,7 +87,7 @@ protected override void Update()
for (int i = 0; i < hitsCount; i++)
{
ref var hit = ref RaycastHits[i];
- if (hit.transform.TryGetComponent(out var playerProxy) && playerProxy.AttachedPlayer.SharedReference != ShooterPlayer )
+ if (hit.transform.TryGetComponent(out var playerProxy) && playerProxy.AttachedPlayer.SharedReference != ShooterPlayer )
{
playerProxy.AttachedPlayer.Damage(25);
if (EntityManager.IsClient && EntityManager.InNormalState)
@@ -107,7 +107,7 @@ protected override void Update()
protected override void VisualUpdate()
{
- UnityObject.transform.position = Position.Value;
+ UnityObject.transform.position = Position.InterpolatedValue;
}
}
}
\ No newline at end of file
diff --git a/Packages/manifest.json b/Packages/manifest.json
index 30ec5d6..0cbb338 100644
--- a/Packages/manifest.json
+++ b/Packages/manifest.json
@@ -1,12 +1,11 @@
{
"dependencies": {
- "com.revenantx.litenetlib": "1.3.3",
+ "com.revenantx.litenetlib": "2.0.0",
"com.unity.2d.sprite": "1.0.0",
"com.unity.2d.tilemap": "1.0.0",
- "com.unity.ai.navigation": "1.1.6",
"com.unity.ext.nunit": "1.0.6",
- "com.unity.ide.rider": "3.0.36",
- "com.unity.ide.visualstudio": "2.0.23",
+ "com.unity.ide.rider": "3.0.38",
+ "com.unity.ide.visualstudio": "2.0.25",
"com.unity.ide.vscode": "1.2.5",
"com.unity.test-framework": "1.1.33",
"com.unity.textmeshpro": "3.0.9",
@@ -15,7 +14,6 @@
"com.unity.toolchain.macos-x86_64-linux-x86_64": "2.0.10",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
"com.unity.ugui": "1.0.0",
- "com.unity.xr.legacyinputhelpers": "2.1.12",
"com.unity.modules.ai": "1.0.0",
"com.unity.modules.androidjni": "1.0.0",
"com.unity.modules.animation": "1.0.0",
diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json
index a1fb5dc..dba2226 100644
--- a/Packages/packages-lock.json
+++ b/Packages/packages-lock.json
@@ -1,7 +1,7 @@
{
"dependencies": {
"com.revenantx.litenetlib": {
- "version": "1.3.3",
+ "version": "2.0.0",
"depth": 0,
"source": "registry",
"dependencies": {},
@@ -22,15 +22,6 @@
"com.unity.modules.uielements": "1.0.0"
}
},
- "com.unity.ai.navigation": {
- "version": "1.1.6",
- "depth": 0,
- "source": "registry",
- "dependencies": {
- "com.unity.modules.ai": "1.0.0"
- },
- "url": "https://packages.unity.com"
- },
"com.unity.ext.nunit": {
"version": "1.0.6",
"depth": 0,
@@ -39,7 +30,7 @@
"url": "https://packages.unity.com"
},
"com.unity.ide.rider": {
- "version": "3.0.36",
+ "version": "3.0.38",
"depth": 0,
"source": "registry",
"dependencies": {
@@ -48,11 +39,11 @@
"url": "https://packages.unity.com"
},
"com.unity.ide.visualstudio": {
- "version": "2.0.23",
+ "version": "2.0.25",
"depth": 0,
"source": "registry",
"dependencies": {
- "com.unity.test-framework": "1.1.9"
+ "com.unity.test-framework": "1.1.31"
},
"url": "https://packages.unity.com"
},
@@ -150,16 +141,6 @@
"com.unity.modules.imgui": "1.0.0"
}
},
- "com.unity.xr.legacyinputhelpers": {
- "version": "2.1.12",
- "depth": 0,
- "source": "registry",
- "dependencies": {
- "com.unity.modules.vr": "1.0.0",
- "com.unity.modules.xr": "1.0.0"
- },
- "url": "https://packages.unity.com"
- },
"com.unity.modules.ai": {
"version": "1.0.0",
"depth": 0,
diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt
index 7a6f40b..587f809 100644
--- a/ProjectSettings/ProjectVersion.txt
+++ b/ProjectSettings/ProjectVersion.txt
@@ -1,2 +1,2 @@
-m_EditorVersion: 2022.3.62f1
-m_EditorVersionWithRevision: 2022.3.62f1 (4af31df58517)
+m_EditorVersion: 2022.3.62f3
+m_EditorVersionWithRevision: 2022.3.62f3 (96770f904ca7)