/// 清理无效的快照 public void CleanUselessSnapshot(int checkedTick) { if (checkedTick < 2) { return; } //_timeMachineService.Clean(checkedTick-1); var snapshotIndices = _snapshotContext.GetEntities(SnapshotMatcher.Tick) .Where(entity => entity.tick.value <= checkedTick).Select(entity => entity.tick.value).ToList(); if (snapshotIndices.Count == 0) { return; } snapshotIndices.Sort(); int i = snapshotIndices.Count - 1; for (; i >= 0; i--) { if (snapshotIndices[i] <= checkedTick) { break; } } if (i < 0) { return; } var resultTick = snapshotIndices[i]; //将太后 和太前的snapshot 删除掉 foreach (var invalidBackupEntity in _actorContext.GetEntities(ActorMatcher.Backup) .Where(e => e.backup.tick < (resultTick))) { invalidBackupEntity.Destroy(); } foreach (var invalidBackupEntity in _gameContext.GetEntities(GameMatcher.Backup) .Where(e => e.backup.tick < (resultTick))) { invalidBackupEntity.Destroy(); } foreach (var snapshotEntity in _snapshotContext.GetEntities(SnapshotMatcher.Tick) .Where(e => e.tick.value < (resultTick))) { snapshotEntity.Destroy(); } _systems.Cleanup(); }
public void Initialize() { foreach (var actor in _actorContext.GetEntities()) { actor.AddEntityCount(0); } }
protected override void Execute(List <GameStateEntity> entities) { var currentTick = _gameStateContext.tick.value; //Register the tick for which a snapshot is created _snapshotIndexService.AddIndex(currentTick); var actors = _actorContext.GetEntities(ActorMatcher.Id); var gameEnts = _activeEntities.GetEntities(); foreach (var actor in actors) { var shadowActor = _actorContext.CreateEntity(); //LocalId is primary index => don't copy foreach (var index in actor.GetComponentIndices().Except(new[] { ActorComponentsLookup.Id })) { var component1 = actor.GetComponent(index); var component2 = shadowActor.CreateComponent(index, component1.GetType()); component1.CopyPublicMemberValues(component2); shadowActor.AddComponent(index, component2); } shadowActor.AddBackup(actor.id.value, currentTick); } foreach (var e in gameEnts) { var shadowEntity = _gameContext.CreateEntity(); _services.Get <IDebugService>().Register(currentTick, e.localId.value, e.position.value); //LocalId is primary index => don't copy; id+actorId should be readonly and stay the same throughout the game foreach (var index in e.GetComponentIndices().Except(new[] { GameComponentsLookup.LocalId, GameComponentsLookup.Id, GameComponentsLookup.ActorId })) { var component1 = e.GetComponent(index); var component2 = shadowEntity.CreateComponent(index, component1.GetType()); component1.CopyPublicMemberValues(component2); shadowEntity.AddComponent(index, component2); } shadowEntity.AddBackup(e.localId.value, currentTick); } _services.Get <ILogService>().Warn("New backup for " + currentTick + "(" + actors.Length + " actors, " + gameEnts.Length + " entities)"); }
/// <summary> /// Revert all changes that were done during or after the given tick /// </summary> /// <param name="tick"></param> public void RevertToTick(uint tick) { Services.Get <ILogService>().Trace("Rollback to " + tick); //Get the actual tick that we have a snapshot for var resultTick = Services.Get <ISnapshotIndexService>().GetFirstIndexBefore(tick); /* * ====================== Revert actors ====================== * most importantly: the entity-count per actor gets reverted so the composite key (Id + ActorId) of GameEntities stays in sync */ var backedUpActors = _actorContext.GetEntities(ActorMatcher.Backup).Where(e => e.backup.tick == resultTick); foreach (var backedUpActor in backedUpActors) { backedUpActor.CopyTo( _actorContext.GetEntityWithId(backedUpActor.backup.actorId), //Current Actor true, //Replace components backedUpActor.GetComponentIndices().Except(new [] { ActorComponentsLookup.Backup }).ToArray()); //Copy everything except the backup-component } /* * ====================== Revert game-entities ====================== */ var currentEntities = _gameContext.GetEntities(GameMatcher.LocalId); var backupEntities = _gameContext.GetEntities(GameMatcher.Backup).Where(e => e.backup.tick == resultTick).ToList(); var backupEntityIds = backupEntities.Select(entity => entity.backup.localEntityId); //Entities that were created in the prediction have to be destroyed var invalidEntities = currentEntities.Where(entity => !backupEntityIds.Contains(entity.localId.value)).ToList(); foreach (var invalidEntity in invalidEntities) { //Here we have the actual entities, we can safely refer to them via the internal id _view.DeleteView(invalidEntity.localId.value); invalidEntity.Destroy(); } foreach (var invalidBackupEntity in _gameContext.GetEntities(GameMatcher.Backup).Where(e => e.backup.tick > resultTick)) { Services.Get <ISnapshotIndexService>().RemoveIndex(invalidBackupEntity.backup.tick); invalidBackupEntity.Destroy(); } //Copy old state to the entity foreach (var backupEntity in backupEntities) { var target = _gameContext.GetEntityWithLocalId(backupEntity.backup.localEntityId); var additionalComponentIndices = target.GetComponentIndices().Except( backupEntity .GetComponentIndices() .Except(new[] { GameComponentsLookup.Backup }) .Concat(new[] { GameComponentsLookup.Id, GameComponentsLookup.ActorId, GameComponentsLookup.LocalId })) ; foreach (var index in additionalComponentIndices) { target.RemoveComponent(index); } backupEntity.CopyTo(target, true, backupEntity.GetComponentIndices().Except(new [] { GameComponentsLookup.Backup }).ToArray()); if (!Services.Get <IDebugService>().Validate(resultTick, backupEntity.backup.localEntityId, target.position.value)) { throw new Exception(); } } //TODO: restore locally destroyed entities Contexts.gameState.ReplaceTick(resultTick); }
protected override void DoRollbackTo(int tick, int missFrameTick, bool isNeedClear = true) { var snapshotIndices = _snapshotContext.GetEntities(SnapshotMatcher.Tick) .Where(entity => entity.tick.value <= tick).Select(entity => entity.tick.value).ToList(); if (snapshotIndices.Count <= 0) { return; } snapshotIndices.Sort(); Logging.Debug.Assert(snapshotIndices.Count > 0 && snapshotIndices[0] <= tick, $"Error! no correct history frame to revert minTick{(snapshotIndices.Count > 0 ? snapshotIndices[0] : 0)} targetTick {tick}"); int i = snapshotIndices.Count - 1; for (; i >= 0; i--) { if (snapshotIndices[i] <= tick) { break; } } var resultTick = snapshotIndices[i]; if (resultTick == Tick) { Logging.Debug.Log("SelfTick should not rollback"); return; } var snaps = ""; foreach (var idx in snapshotIndices) { snaps += idx + " "; } Logging.Debug.Log( $"Rolling back {Tick}->{tick} :final from {resultTick} to {_gameStateContext.tick.value} " + $"missTick:{missFrameTick} total:{Tick - resultTick} "); /* * ====================== Revert actors ====================== * most importantly: the entity-count per actor gets reverted so the composite key (Id + ActorId) of GameEntities stays in sync */ var backedUpActors = _actorContext.GetEntities(ActorMatcher.Backup).Where(e => e.backup.tick == resultTick); foreach (var backedUpActor in backedUpActors) { var target = _actorContext.GetEntityWithActorId(backedUpActor.backup.actorId); if (target == null) { target = _actorContext.CreateEntity(); target.AddActorId(backedUpActor.backup.actorId); } //CopyTo does NOT remove additional existing components, so remove them first var additionalComponentIndices = target.GetComponentIndices().Except( backedUpActor.GetComponentIndices() .Except(new[] { ActorComponentsLookup.Backup }) .Concat(new[] { ActorComponentsLookup.ActorId })); foreach (var index in additionalComponentIndices) { target.RemoveComponent(index); } backedUpActor.CopyTo(target, true, backedUpActor.GetComponentIndices().Except(new[] { ActorComponentsLookup.Backup }).ToArray()); } /* * ====================== Revert game-entities ====================== */ var currentEntities = _gameContext.GetEntities(GameMatcher.EntityId); var backupEntities = _gameContext.GetEntities(GameMatcher.Backup).Where(e => e.backup.tick == resultTick) .ToList(); var backupEntityIds = backupEntities.Select(entity => entity.backup.entityId); //Entities that were created in the prediction have to be destroyed var invalidEntities = currentEntities.Where(entity => !backupEntityIds.Contains(entity.entityId.value)) .ToList(); foreach (var invalidEntity in invalidEntities) { invalidEntity.isDestroyed = true; } //Copy old state to the entity foreach (var backupEntity in backupEntities) { var target = _gameContext.GetEntityWithEntityId(backupEntity.backup.entityId); if (target == null) { target = _gameContext.CreateEntity(); target.AddEntityId(backupEntity.backup.entityId); } //CopyTo does NOT remove additional existing components, so remove them first var additionalComponentIndices = target.GetComponentIndices().Except( backupEntity.GetComponentIndices() .Except(new[] { GameComponentsLookup.Backup }) .Concat(new[] { GameComponentsLookup.EntityId })); foreach (var index in additionalComponentIndices) { target.RemoveComponent(index); } backupEntity.CopyTo(target, true, backupEntity.GetComponentIndices().Except(new[] { GameComponentsLookup.Backup }).ToArray()); } //将太后 和太前的snapshot 删除掉 if (isNeedClear) { foreach (var invalidBackupEntity in _actorContext.GetEntities(ActorMatcher.Backup) .Where(e => e.backup.tick != resultTick)) { invalidBackupEntity.Destroy(); } foreach (var invalidBackupEntity in _gameContext.GetEntities(GameMatcher.Backup) .Where(e => e.backup.tick != resultTick)) { invalidBackupEntity.Destroy(); } foreach (var snapshotEntity in _snapshotContext.GetEntities(SnapshotMatcher.Tick) .Where(e => e.tick.value != resultTick)) { snapshotEntity.Destroy(); } } //TODO: restore locally destroyed entities //Cleanup game-entities that are marked as destroyed _systems.Cleanup(); _gameStateContext.ReplaceTick(resultTick); _timeMachineService.RollbackTo(resultTick); }