diff --git a/Engine/Client/Simulation.cs b/Engine/Client/Simulation.cs index 97e5f13..29d0020 100644 --- a/Engine/Client/Simulation.cs +++ b/Engine/Client/Simulation.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using System.Threading; using Lockstep.Client.Interfaces; using Lockstep.Core.Interfaces; using Lockstep.Network.Messages; @@ -12,6 +13,8 @@ public class Simulation /// public uint LagCompensation { get; set; } + public bool SendCommandsToBuffer { get; set; } = true; + public bool Running { get; private set; } public byte LocalPlayerId { get; private set; } @@ -34,12 +37,12 @@ public Simulation(IWorld world, ICommandBuffer remoteCommandBuffer) public void Initialize(Init init) { _tickDt = 1000f / init.TargetFPS; - LocalPlayerId = init.PlayerID; + LocalPlayerId = init.ActorID; - _world.Initialize(LocalPlayerId); + _world.Initialize(init.AllActors); Running = true; - } + } public void Execute(ICommand command) { @@ -54,7 +57,7 @@ public void Execute(ICommand command) } } - public void Update(float deltaTime) + public void Update(float elapsedMilliseconds) { if (!Running) { @@ -63,7 +66,7 @@ public void Update(float deltaTime) SyncCommandBuffer(); - _accumulatedTime += deltaTime; + _accumulatedTime += elapsedMilliseconds; while (_accumulatedTime >= _tickDt) { @@ -80,13 +83,16 @@ private void Tick() if (_commandCache.Count > 0) { _world.AddInput(_world.CurrentTick + LagCompensation, LocalPlayerId, _commandCache); - _remoteCommandBuffer.Insert(_world.CurrentTick + LagCompensation, LocalPlayerId, _commandCache.ToArray()); + if (SendCommandsToBuffer) + { + _remoteCommandBuffer.Insert(_world.CurrentTick + LagCompensation, LocalPlayerId, _commandCache.ToArray()); + } _commandCache.Clear(); } - } + } - _world.Tick(); + _world.Predict(); } private void SyncCommandBuffer() @@ -113,8 +119,8 @@ private void SyncCommandBuffer() firstMispredictedFrame = remoteFrame; } - //TODO: if command contains entity-ids (which can be predicted) and due to rollback we generated local ids, the command's entity-ids have to be adjusted - //https://github.com/proepkes/UnityLockstep/wiki/Rollback-WIP-Log + //TODO: if command contains entity-ids (which can be predicted) and due to rollback->fast-forward we generated local ids, the command's entity-ids have to be adjusted + //https://github.com/proepkes/UnityLockstep/wiki/Rollback-devlog foreach (var playerCommands in allPlayerCommands) { _world.AddInput(remoteFrame, playerCommands.Key, playerCommands.Value); @@ -128,18 +134,45 @@ private void SyncCommandBuffer() _world.RevertToTick(firstMispredictedFrame); - var validInputFrame = firstMispredictedFrame; + while (_world.CurrentTick < firstMispredictedFrame) + { + _world.Simulate(); + } - //Execute all commands again, beginning from the first frame that contains remote input up to our last local state - while (validInputFrame <= targetTick) + //Restore last local state + while (_world.CurrentTick < targetTick) { - _world.Tick(); - validInputFrame++; + _world.Predict(); } } _lastValidatedFrame = currentRemoteFrame; } } + + + /// + /// Experimental + /// + public void StartAsThread() + { + new Thread(Loop) { IsBackground = true }.Start(); + } + + private void Loop() + { + var timer = new Timer(); + + Running = true; + + timer.Start(); + + while (Running) + { + Update(timer.Tick()); + + Thread.Sleep(1); + } + } } } \ No newline at end of file diff --git a/Engine/Server/Timer.cs b/Engine/Client/Timer.cs similarity index 95% rename from Engine/Server/Timer.cs rename to Engine/Client/Timer.cs index ab6d195..160b381 100644 --- a/Engine/Server/Timer.cs +++ b/Engine/Client/Timer.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -namespace Server +namespace Lockstep.Client { public class Timer { diff --git a/Engine/Core/Components/Actor/BackupComponent.cs b/Engine/Core/Components/Actor/BackupComponent.cs new file mode 100644 index 0000000..9bcd2e5 --- /dev/null +++ b/Engine/Core/Components/Actor/BackupComponent.cs @@ -0,0 +1,13 @@ +using Entitas; + +namespace Lockstep.Core.Components.Actor +{ + [Actor] + //An ActorEntity with BackupComponent refers to an actor in the past + public class BackupComponent : IComponent + { + public byte actorId; + + public uint tick; + } +} diff --git a/Engine/Core/Components/Actor/EntityCountComponent.cs b/Engine/Core/Components/Actor/EntityCountComponent.cs new file mode 100644 index 0000000..fcde8c3 --- /dev/null +++ b/Engine/Core/Components/Actor/EntityCountComponent.cs @@ -0,0 +1,10 @@ +using Entitas; + +namespace Lockstep.Core.Components.Actor +{ + [Actor] + public class EntityCountComponent : IComponent + { + public uint value; + } +} diff --git a/Engine/Core/Components/Actor/IdComponent.cs b/Engine/Core/Components/Actor/IdComponent.cs new file mode 100644 index 0000000..6a47354 --- /dev/null +++ b/Engine/Core/Components/Actor/IdComponent.cs @@ -0,0 +1,12 @@ +using Entitas; +using Entitas.CodeGeneration.Attributes; + +namespace Lockstep.Core.Components.Actor +{ + [Actor] + public sealed class IdComponent : IComponent + { + [PrimaryEntityIndex] + public byte value; + } +} \ No newline at end of file diff --git a/Engine/Core/Components/Game/OwnerIdComponent.cs b/Engine/Core/Components/Game/ActorIdComponent.cs similarity index 76% rename from Engine/Core/Components/Game/OwnerIdComponent.cs rename to Engine/Core/Components/Game/ActorIdComponent.cs index 9261b89..3f546a2 100644 --- a/Engine/Core/Components/Game/OwnerIdComponent.cs +++ b/Engine/Core/Components/Game/ActorIdComponent.cs @@ -3,7 +3,7 @@ namespace Lockstep.Core.Components.Game { [Game] - public class OwnerIdComponent : IComponent + public class ActorIdComponent : IComponent { public byte value; } diff --git a/Engine/Core/Components/Game/BackupComponent.cs b/Engine/Core/Components/Game/BackupComponent.cs new file mode 100644 index 0000000..24a997f --- /dev/null +++ b/Engine/Core/Components/Game/BackupComponent.cs @@ -0,0 +1,13 @@ +using Entitas; + +namespace Lockstep.Core.Components.Game +{ + [Game] + //A GameEntity with BackupComponent refers to an entity in the past + public class BackupComponent : IComponent + { + public uint localEntityId; + + public uint tick; + } +} diff --git a/Engine/Core/Components/Game/IdComponent.cs b/Engine/Core/Components/Game/IdComponent.cs index b60992a..88f9efa 100644 --- a/Engine/Core/Components/Game/IdComponent.cs +++ b/Engine/Core/Components/Game/IdComponent.cs @@ -4,7 +4,7 @@ namespace Lockstep.Core.Components.Game { [Game] public sealed class IdComponent : IComponent - { + { public uint value; } } \ No newline at end of file diff --git a/Engine/Core/Components/Game/InternalIdComponent.cs b/Engine/Core/Components/Game/LocalIdComponent.cs similarity index 100% rename from Engine/Core/Components/Game/InternalIdComponent.cs rename to Engine/Core/Components/Game/LocalIdComponent.cs diff --git a/Engine/Core/Components/Game/NewComponent.cs b/Engine/Core/Components/Game/NewComponent.cs index dd412a1..15b33a9 100644 --- a/Engine/Core/Components/Game/NewComponent.cs +++ b/Engine/Core/Components/Game/NewComponent.cs @@ -1,7 +1,10 @@ using Entitas; namespace Lockstep.Core.Components.Game -{ +{ + /// + /// Flags an entity as newly created. Useful for rollback: if a shadow marked as new, the referenced entity just gets destroyed. + /// public class NewComponent : IComponent { } diff --git a/Engine/Core/Components/Game/ShadowComponent.cs b/Engine/Core/Components/Game/ShadowComponent.cs deleted file mode 100644 index f7a7319..0000000 --- a/Engine/Core/Components/Game/ShadowComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Entitas; - -namespace Lockstep.Core.Components.Game -{ - [Game] - //A shadow refers to an entity in the past - public class ShadowComponent : IComponent - { - } -} diff --git a/Engine/Core/Components/Game/TickComponent.cs b/Engine/Core/Components/Game/TickComponent.cs deleted file mode 100644 index d908879..0000000 --- a/Engine/Core/Components/Game/TickComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Entitas; - -namespace Lockstep.Core.Components.Game -{ - [Game] - public class TickComponent : IComponent - { - public uint value; - } -} diff --git a/Engine/Core/Components/GameState/PlayerIdComponent.cs b/Engine/Core/Components/GameState/PredictingComponent.cs similarity index 63% rename from Engine/Core/Components/GameState/PlayerIdComponent.cs rename to Engine/Core/Components/GameState/PredictingComponent.cs index c0ef16b..f1c4ad2 100644 --- a/Engine/Core/Components/GameState/PlayerIdComponent.cs +++ b/Engine/Core/Components/GameState/PredictingComponent.cs @@ -4,8 +4,7 @@ namespace Lockstep.Core.Components.GameState { [GameState, Unique] - public sealed class PlayerIdComponent : IComponent + public class PredictingComponent : IComponent { - public byte value; } -} \ No newline at end of file +} diff --git a/Engine/Core/Components/Input/CommanderId.cs b/Engine/Core/Components/Input/ActorId.cs similarity index 81% rename from Engine/Core/Components/Input/CommanderId.cs rename to Engine/Core/Components/Input/ActorId.cs index e55f276..846c680 100644 --- a/Engine/Core/Components/Input/CommanderId.cs +++ b/Engine/Core/Components/Input/ActorId.cs @@ -3,7 +3,7 @@ namespace Lockstep.Core.Components.Input { [Input] - public class PlayerId : IComponent + public class ActorId : IComponent { public byte value; } diff --git a/Engine/Core/Components/Input/TickComponent.cs b/Engine/Core/Components/Input/TickComponent.cs index 7259512..6e1e2da 100644 --- a/Engine/Core/Components/Input/TickComponent.cs +++ b/Engine/Core/Components/Input/TickComponent.cs @@ -1,7 +1,7 @@ using Entitas; namespace Lockstep.Core.Components.Input -{ +{ [Input] public class TickComponent : IComponent { diff --git a/Engine/Core/Core.csproj b/Engine/Core/Core.csproj index 7d759e9..9a990c7 100644 --- a/Engine/Core/Core.csproj +++ b/Engine/Core/Core.csproj @@ -1,4 +1,4 @@ - + @@ -56,16 +56,18 @@ - + - - + + + + - + @@ -73,7 +75,6 @@ - @@ -81,28 +82,33 @@ - + + + - + + + + @@ -116,10 +122,23 @@ - + + + + + + + + + + + + + + - + @@ -132,8 +151,10 @@ - - + + + + @@ -144,7 +165,6 @@ - @@ -153,14 +173,13 @@ - - + @@ -183,10 +202,9 @@ - + - diff --git a/Engine/Core/CoreSystems.cs b/Engine/Core/CoreSystems.cs index 2c89c7b..aa3921c 100644 --- a/Engine/Core/CoreSystems.cs +++ b/Engine/Core/CoreSystems.cs @@ -2,17 +2,10 @@ namespace Lockstep.Core { - public sealed class CoreSystems : Entitas.Systems + public sealed class CoreSystems : Feature { - public CoreSystems(Contexts contexts, ServiceContainer services) - { - Add(new InputFeature(contexts, services)); - - Add(new NavigationFeature(contexts, services)); - - Add(new GameEventSystems(contexts)); - - Add(new HashCodeFeature(contexts, services)); + public CoreSystems(Contexts contexts, Services services) + { } } } \ No newline at end of file diff --git a/Engine/Core/DefaultServices/DefaultPlayerEntityIdProvider.cs b/Engine/Core/DefaultServices/DefaultPlayerEntityIdProvider.cs deleted file mode 100644 index ef3c90f..0000000 --- a/Engine/Core/DefaultServices/DefaultPlayerEntityIdProvider.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using Lockstep.Core.Interfaces; - -namespace Lockstep.Core.DefaultServices -{ - /// - /// Every player has its own id-counter - /// - public class DefaultPlayerEntityIdProvider : IPlayerEntityIdProvider - { - private readonly Dictionary _idMap = new Dictionary(); - - public uint Get(byte key) - { - return _idMap[key]; - } - - public uint GetNext(byte key) - { - if (!_idMap.ContainsKey(key)) - { - _idMap.Add(key, 0); - } - - var nextId = _idMap[key]; - _idMap[key] += 1; - return nextId; - } - - public void SetNext(byte key, uint value) - { - _idMap[key] = value; - } - } -} diff --git a/Engine/Core/DefaultServices/DefaultSnapshotIndexService.cs b/Engine/Core/DefaultServices/DefaultSnapshotIndexService.cs new file mode 100644 index 0000000..1daf360 --- /dev/null +++ b/Engine/Core/DefaultServices/DefaultSnapshotIndexService.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; +using Lockstep.Core.Interfaces; + +namespace Lockstep.Core.DefaultServices +{ + public class DefaultSnapshotIndexService : ISnapshotIndexService + { + private readonly List _snapShotIndices = new List(); + + public void AddIndex(uint value) + { + _snapShotIndices.Add(value); + } + + public uint GetFirstIndexBefore(uint value) + { + return _snapShotIndices.Where(index => index <= value).Max(); + } + } +} diff --git a/Engine/Core/Features/HashCodeFeature.cs b/Engine/Core/Features/HashCodeFeature.cs index 6047f74..2fa9101 100644 --- a/Engine/Core/Features/HashCodeFeature.cs +++ b/Engine/Core/Features/HashCodeFeature.cs @@ -5,9 +5,9 @@ namespace Lockstep.Core.Features { public sealed class HashCodeFeature : Feature { - public HashCodeFeature(Contexts contexts, ServiceContainer serviceContainer) + public HashCodeFeature(Contexts contexts, Services services) { - Add(new CalculateHashCode(contexts, serviceContainer.Get())); + Add(new CalculateHashCode(contexts, services.Get())); } } } diff --git a/Engine/Core/Features/InputFeature.cs b/Engine/Core/Features/InputFeature.cs index 2497335..54c2c58 100644 --- a/Engine/Core/Features/InputFeature.cs +++ b/Engine/Core/Features/InputFeature.cs @@ -4,11 +4,11 @@ namespace Lockstep.Core.Features { public sealed class InputFeature : Feature { - public InputFeature(Contexts contexts, ServiceContainer serviceContainer) + public InputFeature(Contexts contexts, Services services) { //TODO: Add InputValidationSystem - Add(new OnSpawnInputCreateEntity(contexts, serviceContainer)); + Add(new OnSpawnInputCreateEntity(contexts, services)); //TODO: Add CleanupInput that removes input of validated frames (no rollback required => can be removed) diff --git a/Engine/Core/Features/NavigationFeature.cs b/Engine/Core/Features/NavigationFeature.cs index 4b2fdc8..3913454 100644 --- a/Engine/Core/Features/NavigationFeature.cs +++ b/Engine/Core/Features/NavigationFeature.cs @@ -5,9 +5,9 @@ namespace Lockstep.Core.Features { public sealed class NavigationFeature : Feature { - public NavigationFeature(Contexts contexts, ServiceContainer serviceContainer) + public NavigationFeature(Contexts contexts, Services services) { - var navigationService = serviceContainer.Get(); + var navigationService = services.Get(); //Add(new OnNavigableDoRegisterAgent(contexts, navigationService)); Add(new OnNavigationInputDoSetDestination(contexts, navigationService)); diff --git a/Engine/Core/Generated/Actor/ActorAttribute.cs b/Engine/Core/Generated/Actor/ActorAttribute.cs new file mode 100644 index 0000000..dfffb9b --- /dev/null +++ b/Engine/Core/Generated/Actor/ActorAttribute.cs @@ -0,0 +1,13 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ContextAttributeGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public sealed class ActorAttribute : Entitas.CodeGeneration.Attributes.ContextAttribute { + + public ActorAttribute() : base("Actor") { + } +} diff --git a/Engine/Core/Generated/Actor/ActorComponentsLookup.cs b/Engine/Core/Generated/Actor/ActorComponentsLookup.cs new file mode 100644 index 0000000..772045f --- /dev/null +++ b/Engine/Core/Generated/Actor/ActorComponentsLookup.cs @@ -0,0 +1,28 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ComponentLookupGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public static class ActorComponentsLookup { + + public const int Backup = 0; + public const int EntityCount = 1; + public const int Id = 2; + + public const int TotalComponents = 3; + + public static readonly string[] componentNames = { + "Backup", + "EntityCount", + "Id" + }; + + public static readonly System.Type[] componentTypes = { + typeof(Lockstep.Core.Components.Actor.BackupComponent), + typeof(Lockstep.Core.Components.Actor.EntityCountComponent), + typeof(Lockstep.Core.Components.Actor.IdComponent) + }; +} diff --git a/Engine/Core/Generated/Actor/ActorContext.cs b/Engine/Core/Generated/Actor/ActorContext.cs new file mode 100644 index 0000000..fdbd9e7 --- /dev/null +++ b/Engine/Core/Generated/Actor/ActorContext.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ContextGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public sealed partial class ActorContext : Entitas.Context { + + public ActorContext() + : base( + ActorComponentsLookup.TotalComponents, + 0, + new Entitas.ContextInfo( + "Actor", + ActorComponentsLookup.componentNames, + ActorComponentsLookup.componentTypes + ), + (entity) => + +#if (ENTITAS_FAST_AND_UNSAFE) + new Entitas.UnsafeAERC(), +#else + new Entitas.SafeAERC(entity), +#endif + () => new ActorEntity() + ) { + } +} diff --git a/Engine/Core/Generated/Actor/ActorEntity.cs b/Engine/Core/Generated/Actor/ActorEntity.cs new file mode 100644 index 0000000..079b601 --- /dev/null +++ b/Engine/Core/Generated/Actor/ActorEntity.cs @@ -0,0 +1,10 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.EntityGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public sealed partial class ActorEntity : Entitas.Entity { +} diff --git a/Engine/Core/Generated/Actor/ActorMatcher.cs b/Engine/Core/Generated/Actor/ActorMatcher.cs new file mode 100644 index 0000000..a541ece --- /dev/null +++ b/Engine/Core/Generated/Actor/ActorMatcher.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ContextMatcherGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public sealed partial class ActorMatcher { + + public static Entitas.IAllOfMatcher AllOf(params int[] indices) { + return Entitas.Matcher.AllOf(indices); + } + + public static Entitas.IAllOfMatcher AllOf(params Entitas.IMatcher[] matchers) { + return Entitas.Matcher.AllOf(matchers); + } + + public static Entitas.IAnyOfMatcher AnyOf(params int[] indices) { + return Entitas.Matcher.AnyOf(indices); + } + + public static Entitas.IAnyOfMatcher AnyOf(params Entitas.IMatcher[] matchers) { + return Entitas.Matcher.AnyOf(matchers); + } +} diff --git a/Engine/Core/Generated/Actor/Components/ActorBackupComponent.cs b/Engine/Core/Generated/Actor/Components/ActorBackupComponent.cs new file mode 100644 index 0000000..27e1121 --- /dev/null +++ b/Engine/Core/Generated/Actor/Components/ActorBackupComponent.cs @@ -0,0 +1,58 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ComponentEntityApiGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public partial class ActorEntity { + + public Lockstep.Core.Components.Actor.BackupComponent backup { get { return (Lockstep.Core.Components.Actor.BackupComponent)GetComponent(ActorComponentsLookup.Backup); } } + public bool hasBackup { get { return HasComponent(ActorComponentsLookup.Backup); } } + + public void AddBackup(byte newActorId, uint newTick) { + var index = ActorComponentsLookup.Backup; + var component = (Lockstep.Core.Components.Actor.BackupComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Actor.BackupComponent)); + component.actorId = newActorId; + component.tick = newTick; + AddComponent(index, component); + } + + public void ReplaceBackup(byte newActorId, uint newTick) { + var index = ActorComponentsLookup.Backup; + var component = (Lockstep.Core.Components.Actor.BackupComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Actor.BackupComponent)); + component.actorId = newActorId; + component.tick = newTick; + ReplaceComponent(index, component); + } + + public void RemoveBackup() { + RemoveComponent(ActorComponentsLookup.Backup); + } +} + +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ComponentMatcherApiGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public sealed partial class ActorMatcher { + + static Entitas.IMatcher _matcherBackup; + + public static Entitas.IMatcher Backup { + get { + if (_matcherBackup == null) { + var matcher = (Entitas.Matcher)Entitas.Matcher.AllOf(ActorComponentsLookup.Backup); + matcher.componentNames = ActorComponentsLookup.componentNames; + _matcherBackup = matcher; + } + + return _matcherBackup; + } + } +} diff --git a/Engine/Core/Generated/Actor/Components/ActorEntityCountComponent.cs b/Engine/Core/Generated/Actor/Components/ActorEntityCountComponent.cs new file mode 100644 index 0000000..5c1961b --- /dev/null +++ b/Engine/Core/Generated/Actor/Components/ActorEntityCountComponent.cs @@ -0,0 +1,56 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ComponentEntityApiGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public partial class ActorEntity { + + public Lockstep.Core.Components.Actor.EntityCountComponent entityCount { get { return (Lockstep.Core.Components.Actor.EntityCountComponent)GetComponent(ActorComponentsLookup.EntityCount); } } + public bool hasEntityCount { get { return HasComponent(ActorComponentsLookup.EntityCount); } } + + public void AddEntityCount(uint newValue) { + var index = ActorComponentsLookup.EntityCount; + var component = (Lockstep.Core.Components.Actor.EntityCountComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Actor.EntityCountComponent)); + component.value = newValue; + AddComponent(index, component); + } + + public void ReplaceEntityCount(uint newValue) { + var index = ActorComponentsLookup.EntityCount; + var component = (Lockstep.Core.Components.Actor.EntityCountComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Actor.EntityCountComponent)); + component.value = newValue; + ReplaceComponent(index, component); + } + + public void RemoveEntityCount() { + RemoveComponent(ActorComponentsLookup.EntityCount); + } +} + +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ComponentMatcherApiGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public sealed partial class ActorMatcher { + + static Entitas.IMatcher _matcherEntityCount; + + public static Entitas.IMatcher EntityCount { + get { + if (_matcherEntityCount == null) { + var matcher = (Entitas.Matcher)Entitas.Matcher.AllOf(ActorComponentsLookup.EntityCount); + matcher.componentNames = ActorComponentsLookup.componentNames; + _matcherEntityCount = matcher; + } + + return _matcherEntityCount; + } + } +} diff --git a/Engine/Core/Generated/Actor/Components/ActorIdComponent.cs b/Engine/Core/Generated/Actor/Components/ActorIdComponent.cs new file mode 100644 index 0000000..cf5503b --- /dev/null +++ b/Engine/Core/Generated/Actor/Components/ActorIdComponent.cs @@ -0,0 +1,56 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ComponentEntityApiGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public partial class ActorEntity { + + public Lockstep.Core.Components.Actor.IdComponent id { get { return (Lockstep.Core.Components.Actor.IdComponent)GetComponent(ActorComponentsLookup.Id); } } + public bool hasId { get { return HasComponent(ActorComponentsLookup.Id); } } + + public void AddId(byte newValue) { + var index = ActorComponentsLookup.Id; + var component = (Lockstep.Core.Components.Actor.IdComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Actor.IdComponent)); + component.value = newValue; + AddComponent(index, component); + } + + public void ReplaceId(byte newValue) { + var index = ActorComponentsLookup.Id; + var component = (Lockstep.Core.Components.Actor.IdComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Actor.IdComponent)); + component.value = newValue; + ReplaceComponent(index, component); + } + + public void RemoveId() { + RemoveComponent(ActorComponentsLookup.Id); + } +} + +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ComponentMatcherApiGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public sealed partial class ActorMatcher { + + static Entitas.IMatcher _matcherId; + + public static Entitas.IMatcher Id { + get { + if (_matcherId == null) { + var matcher = (Entitas.Matcher)Entitas.Matcher.AllOf(ActorComponentsLookup.Id); + matcher.componentNames = ActorComponentsLookup.componentNames; + _matcherId = matcher; + } + + return _matcherId; + } + } +} diff --git a/Engine/Core/Generated/Contexts.cs b/Engine/Core/Generated/Contexts.cs index f2b9d32..c0fd426 100644 --- a/Engine/Core/Generated/Contexts.cs +++ b/Engine/Core/Generated/Contexts.cs @@ -21,14 +21,16 @@ public static Contexts sharedInstance { static Contexts _sharedInstance; + public ActorContext actor { get; set; } public ConfigContext config { get; set; } public GameContext game { get; set; } public GameStateContext gameState { get; set; } public InputContext input { get; set; } - public Entitas.IContext[] allContexts { get { return new Entitas.IContext [] { config, game, gameState, input }; } } + public Entitas.IContext[] allContexts { get { return new Entitas.IContext [] { actor, config, game, gameState, input }; } } public Contexts() { + actor = new ActorContext(); config = new ConfigContext(); game = new GameContext(); gameState = new GameStateContext(); @@ -62,10 +64,16 @@ public void Reset() { //------------------------------------------------------------------------------ public partial class Contexts { + public const string Id = "Id"; public const string LocalId = "LocalId"; [Entitas.CodeGeneration.Attributes.PostConstructor] public void InitializeEntityIndices() { + actor.AddEntityIndex(new Entitas.PrimaryEntityIndex( + Id, + actor.GetGroup(ActorMatcher.Id), + (e, c) => ((Lockstep.Core.Components.Actor.IdComponent)c).value)); + game.AddEntityIndex(new Entitas.PrimaryEntityIndex( LocalId, game.GetGroup(GameMatcher.LocalId), @@ -75,6 +83,10 @@ public void InitializeEntityIndices() { public static class ContextsExtensions { + public static ActorEntity GetEntityWithId(this ActorContext context, byte value) { + return ((Entitas.PrimaryEntityIndex)context.GetEntityIndex(Contexts.Id)).GetEntity(value); + } + public static GameEntity GetEntityWithLocalId(this GameContext context, uint value) { return ((Entitas.PrimaryEntityIndex)context.GetEntityIndex(Contexts.LocalId)).GetEntity(value); } @@ -94,6 +106,7 @@ public partial class Contexts { [Entitas.CodeGeneration.Attributes.PostConstructor] public void InitializeContextObservers() { try { + CreateContextObserver(actor); CreateContextObserver(config); CreateContextObserver(game); CreateContextObserver(gameState); diff --git a/Engine/Core/Generated/Game/Components/GameOwnerIdComponent.cs b/Engine/Core/Generated/Game/Components/GameActorIdComponent.cs similarity index 57% rename from Engine/Core/Generated/Game/Components/GameOwnerIdComponent.cs rename to Engine/Core/Generated/Game/Components/GameActorIdComponent.cs index c87429e..ff0a07a 100644 --- a/Engine/Core/Generated/Game/Components/GameOwnerIdComponent.cs +++ b/Engine/Core/Generated/Game/Components/GameActorIdComponent.cs @@ -8,25 +8,25 @@ //------------------------------------------------------------------------------ public partial class GameEntity { - public Lockstep.Core.Components.Game.OwnerIdComponent ownerId { get { return (Lockstep.Core.Components.Game.OwnerIdComponent)GetComponent(GameComponentsLookup.OwnerId); } } - public bool hasOwnerId { get { return HasComponent(GameComponentsLookup.OwnerId); } } + public Lockstep.Core.Components.Game.ActorIdComponent actorId { get { return (Lockstep.Core.Components.Game.ActorIdComponent)GetComponent(GameComponentsLookup.ActorId); } } + public bool hasActorId { get { return HasComponent(GameComponentsLookup.ActorId); } } - public void AddOwnerId(byte newValue) { - var index = GameComponentsLookup.OwnerId; - var component = (Lockstep.Core.Components.Game.OwnerIdComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Game.OwnerIdComponent)); + public void AddActorId(byte newValue) { + var index = GameComponentsLookup.ActorId; + var component = (Lockstep.Core.Components.Game.ActorIdComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Game.ActorIdComponent)); component.value = newValue; AddComponent(index, component); } - public void ReplaceOwnerId(byte newValue) { - var index = GameComponentsLookup.OwnerId; - var component = (Lockstep.Core.Components.Game.OwnerIdComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Game.OwnerIdComponent)); + public void ReplaceActorId(byte newValue) { + var index = GameComponentsLookup.ActorId; + var component = (Lockstep.Core.Components.Game.ActorIdComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Game.ActorIdComponent)); component.value = newValue; ReplaceComponent(index, component); } - public void RemoveOwnerId() { - RemoveComponent(GameComponentsLookup.OwnerId); + public void RemoveActorId() { + RemoveComponent(GameComponentsLookup.ActorId); } } @@ -40,17 +40,17 @@ public void RemoveOwnerId() { //------------------------------------------------------------------------------ public sealed partial class GameMatcher { - static Entitas.IMatcher _matcherOwnerId; + static Entitas.IMatcher _matcherActorId; - public static Entitas.IMatcher OwnerId { + public static Entitas.IMatcher ActorId { get { - if (_matcherOwnerId == null) { - var matcher = (Entitas.Matcher)Entitas.Matcher.AllOf(GameComponentsLookup.OwnerId); + if (_matcherActorId == null) { + var matcher = (Entitas.Matcher)Entitas.Matcher.AllOf(GameComponentsLookup.ActorId); matcher.componentNames = GameComponentsLookup.componentNames; - _matcherOwnerId = matcher; + _matcherActorId = matcher; } - return _matcherOwnerId; + return _matcherActorId; } } } diff --git a/Engine/Core/Generated/Game/Components/GameBackupComponent.cs b/Engine/Core/Generated/Game/Components/GameBackupComponent.cs new file mode 100644 index 0000000..666a0eb --- /dev/null +++ b/Engine/Core/Generated/Game/Components/GameBackupComponent.cs @@ -0,0 +1,58 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ComponentEntityApiGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public partial class GameEntity { + + public Lockstep.Core.Components.Game.BackupComponent backup { get { return (Lockstep.Core.Components.Game.BackupComponent)GetComponent(GameComponentsLookup.Backup); } } + public bool hasBackup { get { return HasComponent(GameComponentsLookup.Backup); } } + + public void AddBackup(uint newLocalEntityId, uint newTick) { + var index = GameComponentsLookup.Backup; + var component = (Lockstep.Core.Components.Game.BackupComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Game.BackupComponent)); + component.localEntityId = newLocalEntityId; + component.tick = newTick; + AddComponent(index, component); + } + + public void ReplaceBackup(uint newLocalEntityId, uint newTick) { + var index = GameComponentsLookup.Backup; + var component = (Lockstep.Core.Components.Game.BackupComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Game.BackupComponent)); + component.localEntityId = newLocalEntityId; + component.tick = newTick; + ReplaceComponent(index, component); + } + + public void RemoveBackup() { + RemoveComponent(GameComponentsLookup.Backup); + } +} + +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ComponentMatcherApiGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public sealed partial class GameMatcher { + + static Entitas.IMatcher _matcherBackup; + + public static Entitas.IMatcher Backup { + get { + if (_matcherBackup == null) { + var matcher = (Entitas.Matcher)Entitas.Matcher.AllOf(GameComponentsLookup.Backup); + matcher.componentNames = GameComponentsLookup.componentNames; + _matcherBackup = matcher; + } + + return _matcherBackup; + } + } +} diff --git a/Engine/Core/Generated/Game/Components/GameShadowComponent.cs b/Engine/Core/Generated/Game/Components/GameShadowComponent.cs deleted file mode 100644 index 73ae429..0000000 --- a/Engine/Core/Generated/Game/Components/GameShadowComponent.cs +++ /dev/null @@ -1,56 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by Entitas.CodeGeneration.Plugins.ComponentEntityApiGenerator. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ -public partial class GameEntity { - - static readonly Lockstep.Core.Components.Game.ShadowComponent shadowComponent = new Lockstep.Core.Components.Game.ShadowComponent(); - - public bool isShadow { - get { return HasComponent(GameComponentsLookup.Shadow); } - set { - if (value != isShadow) { - var index = GameComponentsLookup.Shadow; - if (value) { - var componentPool = GetComponentPool(index); - var component = componentPool.Count > 0 - ? componentPool.Pop() - : shadowComponent; - - AddComponent(index, component); - } else { - RemoveComponent(index); - } - } - } - } -} - -//------------------------------------------------------------------------------ -// -// This code was generated by Entitas.CodeGeneration.Plugins.ComponentMatcherApiGenerator. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ -public sealed partial class GameMatcher { - - static Entitas.IMatcher _matcherShadow; - - public static Entitas.IMatcher Shadow { - get { - if (_matcherShadow == null) { - var matcher = (Entitas.Matcher)Entitas.Matcher.AllOf(GameComponentsLookup.Shadow); - matcher.componentNames = GameComponentsLookup.componentNames; - _matcherShadow = matcher; - } - - return _matcherShadow; - } - } -} diff --git a/Engine/Core/Generated/Game/Components/GameTickComponent.cs b/Engine/Core/Generated/Game/Components/GameTickComponent.cs deleted file mode 100644 index 3fa4fd1..0000000 --- a/Engine/Core/Generated/Game/Components/GameTickComponent.cs +++ /dev/null @@ -1,56 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by Entitas.CodeGeneration.Plugins.ComponentEntityApiGenerator. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ -public partial class GameEntity { - - public Lockstep.Core.Components.Game.TickComponent tick { get { return (Lockstep.Core.Components.Game.TickComponent)GetComponent(GameComponentsLookup.Tick); } } - public bool hasTick { get { return HasComponent(GameComponentsLookup.Tick); } } - - public void AddTick(uint newValue) { - var index = GameComponentsLookup.Tick; - var component = (Lockstep.Core.Components.Game.TickComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Game.TickComponent)); - component.value = newValue; - AddComponent(index, component); - } - - public void ReplaceTick(uint newValue) { - var index = GameComponentsLookup.Tick; - var component = (Lockstep.Core.Components.Game.TickComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Game.TickComponent)); - component.value = newValue; - ReplaceComponent(index, component); - } - - public void RemoveTick() { - RemoveComponent(GameComponentsLookup.Tick); - } -} - -//------------------------------------------------------------------------------ -// -// This code was generated by Entitas.CodeGeneration.Plugins.ComponentMatcherApiGenerator. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ -public sealed partial class GameMatcher { - - static Entitas.IMatcher _matcherTick; - - public static Entitas.IMatcher Tick { - get { - if (_matcherTick == null) { - var matcher = (Entitas.Matcher)Entitas.Matcher.AllOf(GameComponentsLookup.Tick); - matcher.componentNames = GameComponentsLookup.componentNames; - _matcherTick = matcher; - } - - return _matcherTick; - } - } -} diff --git a/Engine/Core/Generated/Game/GameComponentsLookup.cs b/Engine/Core/Generated/Game/GameComponentsLookup.cs index 9527733..d76afaf 100644 --- a/Engine/Core/Generated/Game/GameComponentsLookup.cs +++ b/Engine/Core/Generated/Game/GameComponentsLookup.cs @@ -9,29 +9,30 @@ public static class GameComponentsLookup { public const int DestinationListener = 0; - public const int Asset = 1; - public const int Controllable = 2; - public const int Destination = 3; - public const int Hashable = 4; - public const int Health = 5; - public const int Id = 6; - public const int LocalId = 7; - public const int MaxSpeed = 8; - public const int Navigable = 9; - public const int New = 10; - public const int OwnerId = 11; - public const int Position = 12; - public const int Shadow = 13; + public const int ActorId = 1; + public const int Asset = 2; + public const int Backup = 3; + public const int Controllable = 4; + public const int Destination = 5; + public const int Hashable = 6; + public const int Health = 7; + public const int Id = 8; + public const int LocalId = 9; + public const int MaxSpeed = 10; + public const int Navigable = 11; + public const int New = 12; + public const int Position = 13; public const int Team = 14; - public const int Tick = 15; - public const int Velocity = 16; - public const int PositionListener = 17; + public const int Velocity = 15; + public const int PositionListener = 16; - public const int TotalComponents = 18; + public const int TotalComponents = 17; public static readonly string[] componentNames = { "DestinationListener", + "ActorId", "Asset", + "Backup", "Controllable", "Destination", "Hashable", @@ -41,18 +42,17 @@ public static class GameComponentsLookup { "MaxSpeed", "Navigable", "New", - "OwnerId", "Position", - "Shadow", "Team", - "Tick", "Velocity", "PositionListener" }; public static readonly System.Type[] componentTypes = { typeof(DestinationListenerComponent), + typeof(Lockstep.Core.Components.Game.ActorIdComponent), typeof(Lockstep.Core.Components.Game.AssetComponent), + typeof(Lockstep.Core.Components.Game.BackupComponent), typeof(Lockstep.Core.Components.Game.ControllableComponent), typeof(Lockstep.Core.Components.Game.DestinationComponent), typeof(Lockstep.Core.Components.Game.HashableComponent), @@ -62,11 +62,8 @@ public static class GameComponentsLookup { typeof(Lockstep.Core.Components.Game.MaxSpeedComponent), typeof(Lockstep.Core.Components.Game.NavigableComponent), typeof(Lockstep.Core.Components.Game.NewComponent), - typeof(Lockstep.Core.Components.Game.OwnerIdComponent), typeof(Lockstep.Core.Components.Game.PositionComponent), - typeof(Lockstep.Core.Components.Game.ShadowComponent), typeof(Lockstep.Core.Components.Game.TeamComponent), - typeof(Lockstep.Core.Components.Game.TickComponent), typeof(Lockstep.Core.Components.Game.VelocityComponent), typeof(PositionListenerComponent) }; diff --git a/Engine/Core/Generated/GameState/Components/GameStatePlayerIdComponent.cs b/Engine/Core/Generated/GameState/Components/GameStatePlayerIdComponent.cs deleted file mode 100644 index 04df9c5..0000000 --- a/Engine/Core/Generated/GameState/Components/GameStatePlayerIdComponent.cs +++ /dev/null @@ -1,94 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by Entitas.CodeGeneration.Plugins.ComponentContextApiGenerator. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ -public partial class GameStateContext { - - public GameStateEntity playerIdEntity { get { return GetGroup(GameStateMatcher.PlayerId).GetSingleEntity(); } } - public Lockstep.Core.Components.GameState.PlayerIdComponent playerId { get { return playerIdEntity.playerId; } } - public bool hasPlayerId { get { return playerIdEntity != null; } } - - public GameStateEntity SetPlayerId(byte newValue) { - if (hasPlayerId) { - throw new Entitas.EntitasException("Could not set PlayerId!\n" + this + " already has an entity with Lockstep.Core.Components.GameState.PlayerIdComponent!", - "You should check if the context already has a playerIdEntity before setting it or use context.ReplacePlayerId()."); - } - var entity = CreateEntity(); - entity.AddPlayerId(newValue); - return entity; - } - - public void ReplacePlayerId(byte newValue) { - var entity = playerIdEntity; - if (entity == null) { - entity = SetPlayerId(newValue); - } else { - entity.ReplacePlayerId(newValue); - } - } - - public void RemovePlayerId() { - playerIdEntity.Destroy(); - } -} - -//------------------------------------------------------------------------------ -// -// This code was generated by Entitas.CodeGeneration.Plugins.ComponentEntityApiGenerator. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ -public partial class GameStateEntity { - - public Lockstep.Core.Components.GameState.PlayerIdComponent playerId { get { return (Lockstep.Core.Components.GameState.PlayerIdComponent)GetComponent(GameStateComponentsLookup.PlayerId); } } - public bool hasPlayerId { get { return HasComponent(GameStateComponentsLookup.PlayerId); } } - - public void AddPlayerId(byte newValue) { - var index = GameStateComponentsLookup.PlayerId; - var component = (Lockstep.Core.Components.GameState.PlayerIdComponent)CreateComponent(index, typeof(Lockstep.Core.Components.GameState.PlayerIdComponent)); - component.value = newValue; - AddComponent(index, component); - } - - public void ReplacePlayerId(byte newValue) { - var index = GameStateComponentsLookup.PlayerId; - var component = (Lockstep.Core.Components.GameState.PlayerIdComponent)CreateComponent(index, typeof(Lockstep.Core.Components.GameState.PlayerIdComponent)); - component.value = newValue; - ReplaceComponent(index, component); - } - - public void RemovePlayerId() { - RemoveComponent(GameStateComponentsLookup.PlayerId); - } -} - -//------------------------------------------------------------------------------ -// -// This code was generated by Entitas.CodeGeneration.Plugins.ComponentMatcherApiGenerator. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ -public sealed partial class GameStateMatcher { - - static Entitas.IMatcher _matcherPlayerId; - - public static Entitas.IMatcher PlayerId { - get { - if (_matcherPlayerId == null) { - var matcher = (Entitas.Matcher)Entitas.Matcher.AllOf(GameStateComponentsLookup.PlayerId); - matcher.componentNames = GameStateComponentsLookup.componentNames; - _matcherPlayerId = matcher; - } - - return _matcherPlayerId; - } - } -} diff --git a/Engine/Core/Generated/GameState/Components/GameStatePredictingComponent.cs b/Engine/Core/Generated/GameState/Components/GameStatePredictingComponent.cs new file mode 100644 index 0000000..de210da --- /dev/null +++ b/Engine/Core/Generated/GameState/Components/GameStatePredictingComponent.cs @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ComponentContextApiGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public partial class GameStateContext { + + public GameStateEntity predictingEntity { get { return GetGroup(GameStateMatcher.Predicting).GetSingleEntity(); } } + + public bool isPredicting { + get { return predictingEntity != null; } + set { + var entity = predictingEntity; + if (value != (entity != null)) { + if (value) { + CreateEntity().isPredicting = true; + } else { + entity.Destroy(); + } + } + } + } +} + +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ComponentEntityApiGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public partial class GameStateEntity { + + static readonly Lockstep.Core.Components.GameState.PredictingComponent predictingComponent = new Lockstep.Core.Components.GameState.PredictingComponent(); + + public bool isPredicting { + get { return HasComponent(GameStateComponentsLookup.Predicting); } + set { + if (value != isPredicting) { + var index = GameStateComponentsLookup.Predicting; + if (value) { + var componentPool = GetComponentPool(index); + var component = componentPool.Count > 0 + ? componentPool.Pop() + : predictingComponent; + + AddComponent(index, component); + } else { + RemoveComponent(index); + } + } + } + } +} + +//------------------------------------------------------------------------------ +// +// This code was generated by Entitas.CodeGeneration.Plugins.ComponentMatcherApiGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +public sealed partial class GameStateMatcher { + + static Entitas.IMatcher _matcherPredicting; + + public static Entitas.IMatcher Predicting { + get { + if (_matcherPredicting == null) { + var matcher = (Entitas.Matcher)Entitas.Matcher.AllOf(GameStateComponentsLookup.Predicting); + matcher.componentNames = GameStateComponentsLookup.componentNames; + _matcherPredicting = matcher; + } + + return _matcherPredicting; + } + } +} diff --git a/Engine/Core/Generated/GameState/GameStateComponentsLookup.cs b/Engine/Core/Generated/GameState/GameStateComponentsLookup.cs index 3d284dc..3c5b2c7 100644 --- a/Engine/Core/Generated/GameState/GameStateComponentsLookup.cs +++ b/Engine/Core/Generated/GameState/GameStateComponentsLookup.cs @@ -10,7 +10,7 @@ public static class GameStateComponentsLookup { public const int HashCode = 0; public const int Paused = 1; - public const int PlayerId = 2; + public const int Predicting = 2; public const int Tick = 3; public const int TotalComponents = 4; @@ -18,14 +18,14 @@ public static class GameStateComponentsLookup { public static readonly string[] componentNames = { "HashCode", "Paused", - "PlayerId", + "Predicting", "Tick" }; public static readonly System.Type[] componentTypes = { typeof(Lockstep.Core.Components.GameState.HashCodeComponent), typeof(Lockstep.Core.Components.GameState.PausedComponent), - typeof(Lockstep.Core.Components.GameState.PlayerIdComponent), + typeof(Lockstep.Core.Components.GameState.PredictingComponent), typeof(Lockstep.Core.Components.GameState.TickComponent) }; } diff --git a/Engine/Core/Generated/Input/Components/InputPlayerIdComponent.cs b/Engine/Core/Generated/Input/Components/InputActorIdComponent.cs similarity index 52% rename from Engine/Core/Generated/Input/Components/InputPlayerIdComponent.cs rename to Engine/Core/Generated/Input/Components/InputActorIdComponent.cs index c26c09f..7081662 100644 --- a/Engine/Core/Generated/Input/Components/InputPlayerIdComponent.cs +++ b/Engine/Core/Generated/Input/Components/InputActorIdComponent.cs @@ -8,25 +8,25 @@ //------------------------------------------------------------------------------ public partial class InputEntity { - public Lockstep.Core.Components.Input.PlayerId playerId { get { return (Lockstep.Core.Components.Input.PlayerId)GetComponent(InputComponentsLookup.PlayerId); } } - public bool hasPlayerId { get { return HasComponent(InputComponentsLookup.PlayerId); } } + public Lockstep.Core.Components.Input.ActorId actorId { get { return (Lockstep.Core.Components.Input.ActorId)GetComponent(InputComponentsLookup.ActorId); } } + public bool hasActorId { get { return HasComponent(InputComponentsLookup.ActorId); } } - public void AddPlayerId(byte newValue) { - var index = InputComponentsLookup.PlayerId; - var component = (Lockstep.Core.Components.Input.PlayerId)CreateComponent(index, typeof(Lockstep.Core.Components.Input.PlayerId)); + public void AddActorId(byte newValue) { + var index = InputComponentsLookup.ActorId; + var component = (Lockstep.Core.Components.Input.ActorId)CreateComponent(index, typeof(Lockstep.Core.Components.Input.ActorId)); component.value = newValue; AddComponent(index, component); } - public void ReplacePlayerId(byte newValue) { - var index = InputComponentsLookup.PlayerId; - var component = (Lockstep.Core.Components.Input.PlayerId)CreateComponent(index, typeof(Lockstep.Core.Components.Input.PlayerId)); + public void ReplaceActorId(byte newValue) { + var index = InputComponentsLookup.ActorId; + var component = (Lockstep.Core.Components.Input.ActorId)CreateComponent(index, typeof(Lockstep.Core.Components.Input.ActorId)); component.value = newValue; ReplaceComponent(index, component); } - public void RemovePlayerId() { - RemoveComponent(InputComponentsLookup.PlayerId); + public void RemoveActorId() { + RemoveComponent(InputComponentsLookup.ActorId); } } @@ -40,17 +40,17 @@ public void RemovePlayerId() { //------------------------------------------------------------------------------ public sealed partial class InputMatcher { - static Entitas.IMatcher _matcherPlayerId; + static Entitas.IMatcher _matcherActorId; - public static Entitas.IMatcher PlayerId { + public static Entitas.IMatcher ActorId { get { - if (_matcherPlayerId == null) { - var matcher = (Entitas.Matcher)Entitas.Matcher.AllOf(InputComponentsLookup.PlayerId); + if (_matcherActorId == null) { + var matcher = (Entitas.Matcher)Entitas.Matcher.AllOf(InputComponentsLookup.ActorId); matcher.componentNames = InputComponentsLookup.componentNames; - _matcherPlayerId = matcher; + _matcherActorId = matcher; } - return _matcherPlayerId; + return _matcherActorId; } } } diff --git a/Engine/Core/Generated/Input/InputComponentsLookup.cs b/Engine/Core/Generated/Input/InputComponentsLookup.cs index dbfc699..2464fd6 100644 --- a/Engine/Core/Generated/Input/InputComponentsLookup.cs +++ b/Engine/Core/Generated/Input/InputComponentsLookup.cs @@ -8,9 +8,9 @@ //------------------------------------------------------------------------------ public static class InputComponentsLookup { - public const int Coordinate = 0; - public const int EntityConfigId = 1; - public const int PlayerId = 2; + public const int ActorId = 0; + public const int Coordinate = 1; + public const int EntityConfigId = 2; public const int Selection = 3; public const int TargetPlayerId = 4; public const int Tick = 5; @@ -18,18 +18,18 @@ public static class InputComponentsLookup { public const int TotalComponents = 6; public static readonly string[] componentNames = { + "ActorId", "Coordinate", "EntityConfigId", - "PlayerId", "Selection", "TargetPlayerId", "Tick" }; public static readonly System.Type[] componentTypes = { + typeof(Lockstep.Core.Components.Input.ActorId), typeof(Lockstep.Core.Components.Input.CoordinateComponent), typeof(Lockstep.Core.Components.Input.EntityConfigIdComponent), - typeof(Lockstep.Core.Components.Input.PlayerId), typeof(Lockstep.Core.Components.Input.SelectionComponent), typeof(Lockstep.Core.Components.Input.TargetPlayerIdComponent), typeof(Lockstep.Core.Components.Input.TickComponent) diff --git a/Engine/Core/Interfaces/IIdProviderService.cs b/Engine/Core/Interfaces/IIdProviderService.cs deleted file mode 100644 index e0f054f..0000000 --- a/Engine/Core/Interfaces/IIdProviderService.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Lockstep.Core.Interfaces -{ - public interface IIdProviderService : IService - { - TValue Get(TKey key); - - TValue GetNext(TKey key); - - void SetNext(TKey key, TValue value); - } - - public interface IPlayerEntityIdProvider : IIdProviderService - { - - } -} diff --git a/Engine/Core/Interfaces/ISnapshotIndexService.cs b/Engine/Core/Interfaces/ISnapshotIndexService.cs new file mode 100644 index 0000000..3ae9770 --- /dev/null +++ b/Engine/Core/Interfaces/ISnapshotIndexService.cs @@ -0,0 +1,12 @@ +namespace Lockstep.Core.Interfaces +{ + /// + /// A service to manage snapshot-indices + /// + public interface ISnapshotIndexService : IService + { + void AddIndex(uint value); + + uint GetFirstIndexBefore(uint value); + } +} diff --git a/Engine/Core/Interfaces/IWorld.cs b/Engine/Core/Interfaces/IWorld.cs index ae78bdd..f61f0c9 100644 --- a/Engine/Core/Interfaces/IWorld.cs +++ b/Engine/Core/Interfaces/IWorld.cs @@ -4,16 +4,19 @@ namespace Lockstep.Core.Interfaces { public interface IWorld { - ServiceContainer Services { get; } + Services Services { get; } + int EntitiesInCurrentTick { get; } - uint CurrentTick { get; } - - void Initialize(byte playerId); + uint CurrentTick { get; } + + void Initialize(byte[] allActorIds); + + void AddInput(uint tickId, byte actorId, List input); - void AddInput(uint tickId, byte player, List input); + void Predict(); - void Tick(); + void Simulate(); void RevertToTick(uint tick); } diff --git a/Engine/Core/Jenny.properties b/Engine/Core/Jenny.properties index 9099622..8b84854 100644 --- a/Engine/Core/Jenny.properties +++ b/Engine/Core/Jenny.properties @@ -49,6 +49,7 @@ Jenny.Server.Port = 3333 Jenny.Client.Host = localhost DesperateDevs.CodeGeneration.Plugins.ProjectPath = Core.csproj Entitas.CodeGeneration.Plugins.Contexts = Game, \ + Actor, \ Input, \ GameState, \ Config diff --git a/Engine/Core/ServiceContainer.cs b/Engine/Core/Services.cs similarity index 90% rename from Engine/Core/ServiceContainer.cs rename to Engine/Core/Services.cs index a3c74ab..c624c58 100644 --- a/Engine/Core/ServiceContainer.cs +++ b/Engine/Core/Services.cs @@ -6,17 +6,17 @@ namespace Lockstep.Core { - public class ServiceContainer + public class Services { private readonly Dictionary _instances = new Dictionary(); private readonly Dictionary _defaults = new Dictionary(); - public ServiceContainer() + public Services() { RegisterDefault(new DefaultHashService()); RegisterDefault(new DefaultViewService()); - RegisterDefault(new DefaultNavigationService()); - RegisterDefault(new DefaultPlayerEntityIdProvider()); + RegisterDefault(new DefaultNavigationService()); + RegisterDefault(new DefaultSnapshotIndexService()); } public void Register(IService instance) diff --git a/Engine/Core/Systems/Debugging/VerifyNoDuplicateBackups.cs b/Engine/Core/Systems/Debugging/VerifyNoDuplicateBackups.cs new file mode 100644 index 0000000..f112c87 --- /dev/null +++ b/Engine/Core/Systems/Debugging/VerifyNoDuplicateBackups.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using Entitas; +using Lockstep.Core.Interfaces; + +namespace Lockstep.Core.Systems.Debugging +{ + public class VerifyNoDuplicateBackups : IExecuteSystem + { + private readonly Services _services; + private readonly IGroup _backups; + + public VerifyNoDuplicateBackups(Contexts contexts, Services services) + { + _services = services; + _backups = contexts.game.GetGroup(GameMatcher.Backup); + } + public void Execute() + { + var temp = new Dictionary>(); + foreach (var entity in _backups) + { + if (temp.ContainsKey(entity.backup.tick)) + { + if (temp[entity.backup.tick].Contains(entity.backup.localEntityId)) + { + _services.Get().Warn("Backup duplicate: " + temp[entity.backup.tick].Count + " backups in tick "+ entity.backup.tick +" are already pointing to " + entity.backup.localEntityId); + } + } + else + { + temp.Add(entity.backup.tick, new List()); + } + + temp[entity.backup.tick].Add(entity.backup.localEntityId); + } + } + } +} diff --git a/Engine/Core/Systems/Input/OnSpawnInputCreateEntity.cs b/Engine/Core/Systems/Input/OnSpawnInputCreateEntity.cs index f846d04..67c66a1 100644 --- a/Engine/Core/Systems/Input/OnSpawnInputCreateEntity.cs +++ b/Engine/Core/Systems/Input/OnSpawnInputCreateEntity.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; using BEPUutilities; using Entitas; using Lockstep.Core.Interfaces; @@ -11,45 +10,52 @@ public class OnSpawnInputCreateEntity : IExecuteSystem private readonly IViewService _viewService; private readonly GameContext _gameContext; private readonly GameStateContext _gameStateContext; - private readonly IGroup _spawnInputs; - private readonly IPlayerEntityIdProvider _idService; + private readonly IGroup _spawnInputs; - private uint _localIdCounter; + private uint _localIdCounter; + private readonly ActorContext _actorContext; - public OnSpawnInputCreateEntity(Contexts contexts, ServiceContainer services) + public OnSpawnInputCreateEntity(Contexts contexts, Services services) { - _viewService = services.Get(); - _idService = services.Get(); + _viewService = services.Get(); _gameContext = contexts.game; - _gameStateContext = contexts.gameState; + _gameStateContext = contexts.gameState; + _actorContext = contexts.actor; _spawnInputs = contexts.input.GetGroup( InputMatcher.AllOf( InputMatcher.EntityConfigId, - InputMatcher.PlayerId, + InputMatcher.ActorId, InputMatcher.Coordinate, InputMatcher.Tick)); } public void Execute() - { - //TODO: order by timestamp instead of playerId => if commands intersect, the first one should win, timestamp should be added by server, RTT has to be considered - foreach (var input in _spawnInputs.GetEntities().Where(entity => entity.tick.value == _gameStateContext.tick.value).OrderBy(entity => entity.playerId.value)) - { + { + //TODO: order by timestamp instead of actorId => if commands intersect, the first one should win, timestamp should be added by server, RTT has to be considered + foreach (var input in _spawnInputs.GetEntities().Where(entity => entity.tick.value == _gameStateContext.tick.value).OrderBy(entity => entity.actorId.value)) + { + var actor = _actorContext.GetEntityWithId(input.actorId.value); + var nextEntityId = actor.entityCount.value + 1; + var e = _gameContext.CreateEntity(); e.isNew = true; - + //composite primary key - e.AddId(_idService.GetNext(input.playerId.value)); - e.AddOwnerId(input.playerId.value); + e.AddId(nextEntityId); + e.AddActorId(input.actorId.value); - e.AddLocalId(_localIdCounter); + //unique id for internal usage + e.AddLocalId(_localIdCounter); + + //some default components that every game-entity must have e.AddVelocity(Vector2.Zero); e.AddPosition(input.coordinate.value); _viewService.LoadView(e, input.entityConfigId.value); + actor.ReplaceEntityCount(nextEntityId); _localIdCounter += 1; } } diff --git a/Engine/Core/Systems/Navigation/NavigationTick.cs b/Engine/Core/Systems/Navigation/NavigationTick.cs index 47217c1..bf22499 100644 --- a/Engine/Core/Systems/Navigation/NavigationTick.cs +++ b/Engine/Core/Systems/Navigation/NavigationTick.cs @@ -21,7 +21,7 @@ public void Execute() //All registered (navigable) entities have to be updated, because avoidance could move other entities aside //_navigationService.Tick(); - foreach (var entity in _contexts.game.GetEntities().Where(e => e.hasId && e.hasDestination)) + foreach (var entity in _contexts.game.GetEntities(GameMatcher.AllOf(GameMatcher.Destination).NoneOf(GameMatcher.Backup))) { var velocity = entity.destination.value - entity.position.value; if (velocity.LengthSquared() > Fix64.One) diff --git a/Engine/Core/Systems/Navigation/OnNavigableDoRegisterAgent.cs b/Engine/Core/Systems/Navigation/OnNavigableDoRegisterAgent.cs index a4088a7..9e0fcce 100644 --- a/Engine/Core/Systems/Navigation/OnNavigableDoRegisterAgent.cs +++ b/Engine/Core/Systems/Navigation/OnNavigableDoRegisterAgent.cs @@ -27,7 +27,7 @@ protected override void Execute(List entities) { foreach (var entity in entities) { - _navigationService.AddAgent(entity.id.value, entity.position.value); + _navigationService.AddAgent(entity.localId.value, entity.position.value); } } } diff --git a/Engine/Core/Systems/Navigation/OnNavigationInputDoSetDestination.cs b/Engine/Core/Systems/Navigation/OnNavigationInputDoSetDestination.cs index 074e20c..5a24a60 100644 --- a/Engine/Core/Systems/Navigation/OnNavigationInputDoSetDestination.cs +++ b/Engine/Core/Systems/Navigation/OnNavigationInputDoSetDestination.cs @@ -18,12 +18,12 @@ public OnNavigationInputDoSetDestination(Contexts contexts, INavigationService n protected override ICollector GetTrigger(IContext context) { - return context.CreateCollector(InputMatcher.AllOf(InputMatcher.Coordinate, InputMatcher.Selection, InputMatcher.PlayerId)); + return context.CreateCollector(InputMatcher.AllOf(InputMatcher.Coordinate, InputMatcher.Selection, InputMatcher.ActorId)); } protected override bool Filter(InputEntity entity) { - return entity.hasCoordinate && entity.hasSelection && entity.hasPlayerId; + return entity.hasCoordinate && entity.hasSelection && entity.hasActorId; } protected override void Execute(List inputs) @@ -32,11 +32,11 @@ protected override void Execute(List inputs) { var destination = input.coordinate.value; - var selectedEntities = _contextsGame.GetEntities( - GameMatcher.AllOf(GameMatcher.OwnerId, GameMatcher.Id)) - .Where(entity => + var selectedEntities = _contextsGame + .GetEntities(GameMatcher.LocalId) + .Where(entity => input.selection.entityIds.Contains(entity.id.value) && - entity.ownerId.value == input.playerId.value); + entity.actorId.value == input.actorId.value); foreach (var entity in selectedEntities) { diff --git a/Engine/Core/Systems/Navigation/UpdateAgentPosition.cs b/Engine/Core/Systems/Navigation/UpdateAgentPosition.cs index ac9d545..8be62bc 100644 --- a/Engine/Core/Systems/Navigation/UpdateAgentPosition.cs +++ b/Engine/Core/Systems/Navigation/UpdateAgentPosition.cs @@ -27,7 +27,7 @@ public void Execute() { entity.ReplacePosition(entity.position.value + entity.velocity.value); - updatedPositions.Add(entity.id.value, entity.position.value); + updatedPositions.Add(entity.localId.value, entity.position.value); } } diff --git a/Engine/Core/Systems/OnNewPredictionCreateBackup.cs b/Engine/Core/Systems/OnNewPredictionCreateBackup.cs new file mode 100644 index 0000000..997c859 --- /dev/null +++ b/Engine/Core/Systems/OnNewPredictionCreateBackup.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DesperateDevs.Utils; +using Entitas; +using Lockstep.Core.Interfaces; + +namespace Lockstep.Core.Systems +{ + public class OnNewPredictionCreateBackup : ReactiveSystem + { + private readonly Services _services; + private readonly IGroup _activeEntities; + private readonly ActorContext _actorContext; + private readonly GameContext _gameContext; + private readonly ISnapshotIndexService _snapshotIndexService; + private GameStateContext _gameStateContext; + + public OnNewPredictionCreateBackup(Contexts contexts, Services services) : base(contexts.gameState) + { + _services = services; + _gameContext = contexts.game; + _actorContext = contexts.actor; + _gameStateContext = contexts.gameState; + _snapshotIndexService = services.Get(); + + _activeEntities = contexts.game.GetGroup(GameMatcher.LocalId); + } + + protected override ICollector GetTrigger(IContext context) + { + //Create a snapshot as soon as prediction starts + return context.CreateCollector(GameStateMatcher.Predicting.Added()); + } + + protected override bool Filter(GameStateEntity gameState) + { + return gameState.isPredicting; + } + + protected override void Execute(List 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(); + + //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().Warn("New backup for " + currentTick + "(" + actors.Length + " actors, " + gameEnts.Length + " entities)"); + } + } +} diff --git a/Engine/Core/Systems/RemoveNewFlag.cs b/Engine/Core/Systems/RemoveNewFlag.cs index 4448906..174402c 100644 --- a/Engine/Core/Systems/RemoveNewFlag.cs +++ b/Engine/Core/Systems/RemoveNewFlag.cs @@ -10,7 +10,7 @@ public sealed class RemoveNewFlag : ICleanupSystem public RemoveNewFlag(Contexts contexts) { - _group = contexts.game.GetGroup(GameMatcher.AllOf(GameMatcher.New).NoneOf(GameMatcher.Shadow)); + _group = contexts.game.GetGroup(GameMatcher.AllOf(GameMatcher.New).NoneOf(GameMatcher.Backup)); } public void Cleanup() diff --git a/Engine/Core/Systems/StoreNewOrChangedEntities.cs b/Engine/Core/Systems/StoreNewOrChangedEntities.cs deleted file mode 100644 index ca5d1f6..0000000 --- a/Engine/Core/Systems/StoreNewOrChangedEntities.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using DesperateDevs.Utils; -using Entitas; - -namespace Lockstep.Core.Systems -{ - public class StoreNewOrChangedEntities : ReactiveSystem - { - private readonly GameStateContext _gameStateContext; - private readonly GameContext _gameContext; - private int[] _componentIndices; - - public StoreNewOrChangedEntities(Contexts contexts) : base(contexts.game) - { - _gameContext = contexts.game; - _gameStateContext = contexts.gameState; - } - - protected override ICollector GetTrigger(IContext context) - { - //Listen for changes on all components except IdReference - _componentIndices = GameComponentsLookup.componentNames - .Except(new[] { GameComponentsLookup.componentNames[GameComponentsLookup.Shadow] }) - .Select(componentName => (int)typeof(GameComponentsLookup) - .GetFields() - .First(info => info.Name == componentName) - .GetRawConstantValue()) - .ToArray(); - - return context.CreateCollector(GameMatcher.AnyOf(_componentIndices).NoneOf(GameMatcher.Shadow)); - } - - protected override bool Filter(GameEntity entity) - { - return !entity.isShadow; - } - - protected override void Execute(List entities) - { - foreach (var e in entities) - { - var shadowEntity = _gameContext.CreateEntity(); - - //TODO: find out a way to only copy components that actually changed - //LocalId is primary index => don't copy. LocalId is inside _componentIndices because we need to catch new entities that may only have a LocalId-component and I'm too lazy to create a separate componentIndicesWithoutLocalIdArray - foreach (var index in _componentIndices.Where(i => i != GameComponentsLookup.LocalId && e.HasComponent(i))) - { - var component1 = e.GetComponent(index); - var component2 = shadowEntity.CreateComponent(index, component1.GetType()); - component1.CopyPublicMemberValues(component2); - shadowEntity.AddComponent(index, component2); - } - - shadowEntity.isShadow = true; - shadowEntity.AddTick(_gameStateContext.tick.value); - } - } - } -} diff --git a/Engine/Core/World.cs b/Engine/Core/World.cs index b5276a5..5b629ec 100644 --- a/Engine/Core/World.cs +++ b/Engine/Core/World.cs @@ -1,58 +1,80 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Entitas; +using Lockstep.Core.Features; using Lockstep.Core.Interfaces; using Lockstep.Core.Systems; +using Lockstep.Core.Systems.Debugging; using Lockstep.Core.Systems.GameState; namespace Lockstep.Core { - public sealed class World : Entitas.Systems, IWorld + public sealed class World : Feature, IWorld { private Contexts Contexts { get; } - public ServiceContainer Services { get; } + public Services Services { get; } public uint CurrentTick => Contexts.gameState.tick.value; - public int EntitiesInCurrentTick => Contexts.game.GetEntities().Count(e => !e.isShadow); + public int EntitiesInCurrentTick => Contexts.game.GetEntities(GameMatcher.LocalId).Length; private readonly IViewService _view; private readonly GameContext _gameContext; - private readonly INavigationService _navigation; - private readonly IPlayerEntityIdProvider _idProvider; + private readonly INavigationService _navigation; + private readonly ActorContext _actorContext; public World(Contexts contexts, params IService[] additionalServices) { Contexts = contexts; - Services = new ServiceContainer(); + Services = new Services(); foreach (var service in additionalServices) { Services.Register(service); } _view = Services.Get(); - _navigation = Services.Get(); - _idProvider = Services.Get(); + _navigation = Services.Get(); + _gameContext = contexts.game; + _actorContext = contexts.actor; + + AddFeatures(contexts); + } + + private void AddFeatures(Contexts contexts) + { + Add(new OnNewPredictionCreateBackup(contexts, Services)); - Add(new CoreSystems(contexts, Services)); + Add(new InputFeature(contexts, Services)); - Add(new StoreNewOrChangedEntities(contexts)); + Add(new NavigationFeature(contexts, Services)); + + Add(new GameEventSystems(contexts)); + + Add(new HashCodeFeature(contexts, Services)); Add(new RemoveNewFlag(contexts)); - Add(new IncrementTick(Contexts)); - } + Add(new IncrementTick(contexts)); - public void Initialize(byte playerId) - { + Add(new VerifyNoDuplicateBackups(contexts, Services)); + } + + public void Initialize(byte[] allActorIds) + { Initialize(); - Contexts.gameState.SetPlayerId(playerId); + foreach (var actorId in allActorIds) + { + var actor = Contexts.actor.CreateEntity(); + actor.AddId(actorId); + actor.AddEntityCount(0); + } } - public void AddInput(uint tickId, byte player, List input) + public void AddInput(uint tickId, byte actor, List input) { foreach (var command in input) { @@ -60,47 +82,64 @@ public void AddInput(uint tickId, byte player, List input) command.Execute(inputEntity); inputEntity.AddTick(tickId); - inputEntity.AddPlayerId(player); + inputEntity.AddActorId(actor); } } - public void Tick() + public void Predict() { + if (!Contexts.gameState.isPredicting) + { + Contexts.gameState.isPredicting = true; + } + Execute(); Cleanup(); } + public void Simulate() + { + if (Contexts.gameState.isPredicting) + { + Contexts.gameState.isPredicting = false; + } + + Execute(); + Cleanup(); + } + /// /// Revert all changes that were done during or after the given tick /// /// public void RevertToTick(uint tick) - { - var shadows = _gameContext.GetEntities().Where(e => e.isShadow && e.tick.value >= tick).ToList(); - - var spawnedShadows = shadows.Where(e => e.isNew); - - //A shadow refers to its entity through ownerId + id - var shadowsOfNewEntitiesPerPlayer = shadows.Where(e => e.isNew).ToLookup(e => e.ownerId.value, e => e.id.value); - - var invalidEntities = new List(200); - foreach (var shadowsPerOwner in shadowsOfNewEntitiesPerPlayer) - { - var ownerId = shadowsPerOwner.Key; - - var invalidShadowsOfOwner = _gameContext.GetEntities( - GameMatcher.AllOf( - GameMatcher.Id, - GameMatcher.OwnerId) - .NoneOf(GameMatcher.Shadow)) - .Where(entity => ownerId == entity.ownerId.value && shadowsPerOwner.Contains(entity.id.value)).ToList(); + { + //Get the actual tick that we have a snapshot for + var resultTick = Services.Get().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 + } - invalidEntities.AddRange(invalidShadowsOfOwner); + /* + * ====================== Revert game-entities ====================== + */ - //Reset id service for the player to last valid state, since we created IDs that may not match with other players - _idProvider.SetNext(ownerId, _idProvider.Get(ownerId) - (uint)invalidShadowsOfOwner.Count); - } + var currentEntities = _gameContext.GetEntities(GameMatcher.LocalId); + var backedUpEntities = _gameContext.GetEntities(GameMatcher.Backup).Where(e => e.backup.tick == resultTick).Select(entity => entity.backup.localEntityId).ToList(); + //Entities that were created in the prediction have to be destroyed + var invalidEntities = currentEntities.Where(entity => !backedUpEntities.Contains(entity.localId.value)); foreach (var invalidEntity in invalidEntities) { //Here we have the actual entities, we can safely refer to them via the internal id @@ -108,62 +147,45 @@ public void RevertToTick(uint tick) _gameContext.GetEntityWithLocalId(invalidEntity.localId.value).Destroy(); } - //shadowsOfChangedEntities could contain spawnedShadows when a created entity changes in later ticks => 'e.isNew' is false one tick after an entity. - //That Entity has already been destroyed above so these changes don't have to be considered - var shadowsOfChangedEntities = shadows.Where(e => !e.isNew).Except(spawnedShadows); - foreach (var entity in shadows.Where(e => !e.isNew)) - { - //var referencedEntity = _gameContext.GetEntityWithId(entity.shadow.entityId); - ////Check if the entity got destroyed locally - //if (referencedEntity == null) - //{ - // //TODO: restore locally destroyed entities - //} - //else - //{ - // //Entity is in the game locally, revert to old state - // var currentComponents = referencedEntity.GetComponentIndices(); - // var previousComponents = entity.GetComponentIndices(); - - // var sameComponents = previousComponents.Intersect(currentComponents); - // var onlyLocalComponents = currentComponents.Except(new[] {GameComponentsLookup.Id }).Except(previousComponents); - // var missingComponents = previousComponents.Except(new []{ GameComponentsLookup.Shadow }).Except(currentComponents); - - - // Services.Get().Warn("sameComponents: " + sameComponents.Count()); - // Services.Get().Warn("onlyLocalComponents: " + onlyLocalComponents.Count()); - // Services.Get().Warn("missingComponents: " + missingComponents.Count()); - // foreach (var index in sameComponents) - // { - // referencedEntity.ReplaceComponent(index, entity.GetComponent(index)); - // } - - // foreach (var index in onlyLocalComponents) - // { - // referencedEntity.RemoveComponent(index); - // } - - // foreach (var index in missingComponents) - // { - // referencedEntity.AddComponent(index, entity.GetComponent(index)); - // } - //} - } - //TODO: cleanup shadow-entities < last validated tick - //for (var i = tick; i <= Contexts.gameState.tick.value; i++) - //{ - // _storage.RemoveChanges(i); - //} - //Reverted to a tick in the past => all predictions are invalid now, delete them - foreach (var entity in shadows) - { - entity.Destroy(); - } + //Apply old values to the components + //foreach (var shadow in shadows.Except(spawnedShadows)) + //{ + // var referencedEntity = currentEntities.FirstOrDefault(e => e.hasId && e.hasOwnerId && e.id.value == shadow.id.value && e.ownerId.value == shadow.ownerId.value); + + // //Check if the entity got destroyed locally + // if (referencedEntity == null) + // { + // //TODO: restore locally destroyed entities + // } + // else + // { + // //Entity is in the game locally, revert to old state + // var currentComponents = referencedEntity.GetComponentIndices(); + // var previousComponents = shadow.GetComponentIndices().Except(new[] { GameComponentsLookup.Shadow, GameComponentsLookup.Tick }).ToArray(); + + // var sameComponents = previousComponents.Intersect(currentComponents); + // var missingComponents = previousComponents.Except(currentComponents).ToArray(); + // var onlyLocalComponents = currentComponents.Except(new[] { GameComponentsLookup.LocalId }).Except(previousComponents); + + // shadow.CopyTo(referencedEntity, true, sameComponents.ToArray()); + + // //CopyTo with 0 params would copy all... + // if (missingComponents.Length > 0) + // { + // shadow.CopyTo(referencedEntity, false, missingComponents); + // } + + // foreach (var index in onlyLocalComponents) + // { + // referencedEntity.RemoveComponent(index); + // } + // } + //} - Contexts.gameState.ReplaceTick(tick); + Contexts.gameState.ReplaceTick(resultTick); } - } + } } \ No newline at end of file diff --git a/Engine/Network/Messages/Init.cs b/Engine/Network/Messages/Init.cs index 282465e..940244d 100644 --- a/Engine/Network/Messages/Init.cs +++ b/Engine/Network/Messages/Init.cs @@ -6,21 +6,25 @@ public class Init : ISerializable { public int Seed { get; set; } - public byte PlayerID { get; set; } + public byte ActorID { get; set; } + + public byte[] AllActors { get; set; } public int TargetFPS { get; set; } public void Serialize(Serializer writer) { writer.Put(Seed); - writer.Put(PlayerID); + writer.Put(ActorID); + writer.PutBytesWithLength(AllActors); writer.Put(TargetFPS); } public void Deserialize(Deserializer reader) { Seed = reader.GetInt(); - PlayerID = reader.GetByte(); + ActorID = reader.GetByte(); + AllActors = reader.GetBytesWithLength(); TargetFPS = reader.GetInt(); } } diff --git a/Engine/Server/BufferedRoom.cs b/Engine/Server/BufferedRoom.cs deleted file mode 100644 index d1ad838..0000000 --- a/Engine/Server/BufferedRoom.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using Lockstep.Network; -using Lockstep.Network.Messages; -using Lockstep.Network.Utils; - -namespace Server -{ - //Buffers input and distributes it in fixed timesteps, probably legacy and no longer required. - class BufferedRoom - { - private const int TargetFps = 20; - - private byte _nextPlayerId; - private readonly int _size; - - private InputBuffer _inputBuffer; - private readonly IServer _server; - - public bool Running { get; private set; } - - /// - /// Mapping: clientId -> playerId - /// - private readonly Dictionary _playerIds = new Dictionary(); - - /// - /// Mapping: Framenumber -> received hashcode - /// - private readonly Dictionary _hashCodes = new Dictionary(); - - public BufferedRoom(IServer server, int size) - { - _server = server; - _size = size; - } - public void Open(int port) - { - _server.ClientConnected += OnClientConnected; - _server.ClientDisconnected += OnClientDisconnected; - _server.DataReceived += OnDataReceived; - - _server.Run(port); - - Console.WriteLine("Server started. Waiting for " + _size + " players..."); - } - - private void OnClientConnected(int clientId) - { - _playerIds.Add(clientId, _nextPlayerId++); - - if (_playerIds.Count == _size) - { - Console.WriteLine("Room is full, starting new simulation..."); - new Thread(Loop) { IsBackground = true }.Start(); - } - else - { - Console.WriteLine(_playerIds.Count + " / " + _size + " players have connected."); - } - } - - private void OnDataReceived(int clientId, byte[] data) - { - var reader = new Deserializer(Compressor.Decompress(data)); - var messageTag = (MessageTag)reader.GetByte(); - switch (messageTag) - { - case MessageTag.Input: - _inputBuffer?.AddInput(reader.GetRemainingBytes()); - break; - case MessageTag.HashCode: - var pkt = new HashCode(); - pkt.Deserialize(reader); - if (!_hashCodes.ContainsKey(pkt.FrameNumber)) - { - _hashCodes[pkt.FrameNumber] = pkt.Value; - } - else - { - Console.WriteLine((_hashCodes[pkt.FrameNumber] == pkt.Value ? "HashCode valid" : "Desync") + ": " + pkt.Value); - } - break; - } - } - - private void OnClientDisconnected(int clientId) - { - _playerIds.Remove(clientId); - if (_playerIds.Count == 0) - { - Console.WriteLine("All players left, stopping current simulation..."); - Running = false; - } - else - { - Console.WriteLine(_playerIds.Count + " players remaining."); - } - } - - private void Loop() - { - var timer = new Timer(); - var dt = 1000.0 / TargetFps; - - var accumulatedTime = 0.0; - - _hashCodes.Clear(); - var serializer = new Serializer(); - _inputBuffer = new InputBuffer(); - - Running = true; - - StartSimulationOnConnectedPeers(serializer); - - timer.Start(); - - Console.WriteLine("Simulation started"); - - while (Running) - { - accumulatedTime += timer.Tick(); - - while (accumulatedTime >= dt) - { - serializer.Reset(); - - _inputBuffer.Pack(serializer); - //_server.Distribute(Compressor.Compress(serializer)); - - accumulatedTime -= dt; - } - - Thread.Sleep(1); - } - - Console.WriteLine("Simulation stopped"); - } - - private void StartSimulationOnConnectedPeers(Serializer writer) - { - //Create a new seed and send it with a start-message to all clients - //The message also contains the respective player-id and the servers' frame rate - var seed = new Random().Next(int.MinValue, int.MaxValue); - - foreach (var player in _playerIds) - { - writer.Reset(); - writer.Put((byte)MessageTag.StartSimulation); - new Init - { - Seed = seed, - TargetFPS = TargetFps, - PlayerID = player.Value - }.Serialize(writer); - - _server.Send(player.Key, Compressor.Compress(writer)); - } - } - } -} diff --git a/Engine/Server/Room.cs b/Engine/Server/Room.cs index f0273da..780017f 100644 --- a/Engine/Server/Room.cs +++ b/Engine/Server/Room.cs @@ -1,5 +1,6 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; using Lockstep.Network; using Lockstep.Network.Messages; using Lockstep.Network.Utils; @@ -11,7 +12,7 @@ namespace Server /// public class Room { - private const int TargetFps = 40; + private const int TargetFps = 30; private byte _nextPlayerId; private readonly int _size; @@ -119,8 +120,9 @@ private void StartSimulationOnConnectedPeers() new Init { Seed = seed, - TargetFPS = TargetFps, - PlayerID = player.Value + ActorID = player.Value, + AllActors = _playerIds.Values.ToArray(), + TargetFPS = TargetFps }.Serialize(writer); _server.Send(player.Key, Compressor.Compress(writer)); diff --git a/Engine/Test/InputTests.cs b/Engine/Test/InputTests.cs index 5f74c3a..c7a644d 100644 --- a/Engine/Test/InputTests.cs +++ b/Engine/Test/InputTests.cs @@ -1,16 +1,13 @@ -using System; -using System.Collections.Generic; +using System; using System.Linq; -using BEPUutilities; +using System.Threading; +using BEPUutilities; +using Entitas; using Lockstep.Client; -using Lockstep.Client.Implementations; -using Lockstep.Client.Interfaces; -using Lockstep.Core; -using Lockstep.Core.DefaultServices; -using Lockstep.Core.Interfaces; -using Lockstep.Core.Systems; -using Lockstep.Network.Messages; -using Moq; +using Lockstep.Client.Implementations; +using Lockstep.Core; +using Lockstep.Core.Interfaces; +using Lockstep.Network.Messages; using Shouldly; using Xunit; using Xunit.Abstractions; @@ -43,9 +40,8 @@ public void TestGameEntityHasUniqueId() contexts.game.count.ShouldBe(numEntities); contexts.game.GetEntities().Select(entity => entity.hasId).ShouldAllBe(b => true); contexts.game.GetEntities().Select(entity => entity.id.value).ShouldBeUnique(); - } - - //Tests regarding Rollback currently require to remove the line from Simulation where input gets added to the remoteBuffer. Otherwise the input loops back into the simulation + } + [Fact] public void TestCreateEntityRollbackLocal() { @@ -54,91 +50,111 @@ public void TestCreateEntityRollbackLocal() var systems = new World(contexts, new TestLogger(_output)); var commandBuffer = new CommandBuffer(); - var sim = new Simulation(systems, commandBuffer) { LagCompensation = 0 }; + var sim = new Simulation(systems, commandBuffer) { LagCompensation = 0, SendCommandsToBuffer = false }; sim.Initialize(new Init { TargetFPS = 1}); - sim.Update(1000); - sim.Update(1000); - sim.Update(1000); + systems.CurrentTick.ShouldBe((uint)0); - for (int i = 0; i < 10; i++) + sim.Update(1000); //0 + systems.CurrentTick.ShouldBe((uint)1); + for (int i = 0; i < 1; i++) { - sim.Execute(new SpawnCommand()); + sim.Execute(new Spawn()); } - commandBuffer.Insert(2, 1, new ICommand[] { }); + sim.Update(1000); //1 + systems.CurrentTick.ShouldBe((uint)2); + ExpectEntityCount(contexts, 1); + ExpectBackupCount(contexts, 1); + + sim.Update(1000); //2 + systems.CurrentTick.ShouldBe((uint)3); + ExpectEntityCount(contexts, 1); + ExpectBackupCount(contexts, 1); + + commandBuffer.Insert(1, 1, new ICommand[] { }); - sim.Update(1000); //3 = 30 - contexts.game.GetEntities().Count(entity => !entity.isShadow).ShouldBe(10); + sim.Update(1000); //3 + systems.CurrentTick.ShouldBe((uint)4); + ExpectEntityCount(contexts, 1); + ExpectBackupCount(contexts, 1); - sim.Update(1000); - for (int i = 0; i < 10; i++) + commandBuffer.Insert(2, 1, new ICommand[] { new MoveAll(contexts.game), }); + + + sim.Update(1000); //4 + systems.CurrentTick.ShouldBe((uint)5); + ExpectEntityCount(contexts, 1); + ExpectBackupCount(contexts, 2); + + for (int i = 0; i < 1; i++) { - sim.Execute(new SpawnCommand()); + sim.Execute(new Spawn()); } - sim.Update(1000); + sim.Update(1000); //5 + systems.CurrentTick.ShouldBe((uint)6); - contexts.game.GetEntities().Count(entity => !entity.isShadow).ShouldBe(20); + ExpectEntityCount(contexts, 2); + ExpectBackupCount(contexts, 3); - for (int i = 0; i < 10; i++) + for (int i = 0; i < 1; i++) { - sim.Execute(new SpawnCommand()); + sim.Execute(new Spawn()); } - sim.Update(1000); - contexts.game.GetEntities().Count(entity => !entity.isShadow).ShouldBe(30); - - _output.WriteLine("Revert to 3"); - commandBuffer.Insert(3, 1, new ICommand[] { }); + sim.Update(1000); //6 + ExpectEntityCount(contexts, 3); + ExpectBackupCount(contexts, 4); + + commandBuffer.Insert(3, 1, new ICommand[] { }); //Revert to 3 sim.Update(1000); - - contexts.game.GetEntities().Count(entity => !entity.isShadow).ShouldBe(30); - + ExpectEntityCount(contexts, 3); + ExpectBackupCount(contexts, 4); + sim.Update(1000); commandBuffer.Insert(4, 1, new ICommand[] { }); - for (int i = 0; i < 10; i++) + for (int i = 0; i < 1; i++) { - sim.Execute(new SpawnCommand()); + sim.Execute(new Spawn()); } sim.Update(1000); - - contexts.game.GetEntities().Count(entity => !entity.isShadow).ShouldBe(40); + ExpectEntityCount(contexts, 4); + ExpectBackupCount(contexts, 5); sim.Update(1000); sim.Update(1000); - for (int i = 0; i < 10; i++) + for (int i = 0; i < 1; i++) { - commandBuffer.Insert(5, 1, new ICommand[] { new SpawnCommand() }); + commandBuffer.Insert(5, 1, new ICommand[] { new Spawn() }); } sim.Update(1000); - contexts.game.GetEntities().Count(entity => !entity.isShadow).ShouldBe(50); + ExpectEntityCount(contexts, 5); } [Fact] public void TestCreateEntityRollbackRemote() - { - + { var contexts = new Contexts(); var systems = new World(contexts, new TestLogger(_output)); var commandBuffer = new CommandBuffer(); - var sim = new Simulation(systems, commandBuffer) { LagCompensation = 0 }; + var sim = new Simulation(systems, commandBuffer) { LagCompensation = 0, SendCommandsToBuffer = false }; - sim.Initialize(new Init { TargetFPS = 1 }); + sim.Initialize(new Init { TargetFPS = 1, AllActors = new byte[] { 0, 1 }, ActorID = 0 }); sim.Update(1000); for (int i = 0; i < 10; i++) { - sim.Execute(new SpawnCommand()); + sim.Execute(new Spawn()); } sim.Update(1000); sim.Update(1000); @@ -147,14 +163,14 @@ public void TestCreateEntityRollbackRemote() for (int i = 0; i < 10; i++) { - commandBuffer.Insert(2, 1, new ICommand[] { new SpawnCommand() }); + commandBuffer.Insert(2, 1, new ICommand[] { new Spawn() }); } sim.Update(1000); for (int i = 0; i < 10; i++) { - commandBuffer.Insert(4, 1, new ICommand[] { new SpawnCommand() }); + commandBuffer.Insert(4, 1, new ICommand[] { new Spawn() }); } @@ -165,7 +181,7 @@ public void TestCreateEntityRollbackRemote() for (int i = 0; i < 10; i++) { - commandBuffer.Insert(5, 1, new ICommand[] { new SpawnCommand() }); + commandBuffer.Insert(5, 1, new ICommand[] { new Spawn() }); } sim.Update(1000); @@ -175,7 +191,7 @@ public void TestCreateEntityRollbackRemote() for (int i = 0; i < 10; i++) { - commandBuffer.Insert(7, 1, new ICommand[] { new SpawnCommand() }); + commandBuffer.Insert(7, 1, new ICommand[] { new Spawn() }); } sim.Update(1000); @@ -184,34 +200,177 @@ public void TestCreateEntityRollbackRemote() sim.Update(1000); sim.Update(1000); sim.Update(1000); - - _output.WriteLine("Count: " + contexts.game.count); + _output.WriteLine("Revert to 3"); for (int i = 0; i < 10; i++) { - commandBuffer.Insert(8, 1, new ICommand[] { new SpawnCommand() }); + commandBuffer.Insert(8, 1, new ICommand[] { new Spawn() }); } - sim.Update(1000); - _output.WriteLine("Count: " + contexts.game.count); + sim.Update(1000); sim.Update(1000); sim.Update(1000); sim.Update(1000); sim.Update(1000); for (int i = 0; i < 10; i++) { - commandBuffer.Insert(9, 1, new ICommand[] { new SpawnCommand() }); + commandBuffer.Insert(9, 1, new ICommand[] { new Spawn() }); } - sim.Update(1000); + sim.Update(1000); + + ExpectEntityCount(contexts, 70); + } + + [Fact] + public void TestEntityRollbackWithLocalChanges() + { + var contexts = new Contexts(); + + var systems = new World(contexts, new TestLogger(_output)); + var commandBuffer = new CommandBuffer(); + + var sim = new Simulation(systems, commandBuffer) { LagCompensation = 0, SendCommandsToBuffer = false }; + + sim.Initialize(new Init { TargetFPS = 1, AllActors = new byte[] { 0, 1 }, ActorID = 0 }); + + uint frameCounter = 0; + + systems.CurrentTick.ShouldBe(frameCounter++); + + sim.Update(1000); //0 + systems.CurrentTick.ShouldBe(frameCounter++); + + sim.Execute(new Spawn()); + + sim.Update(1000); //1 + systems.CurrentTick.ShouldBe(frameCounter++); + ExpectEntityCount(contexts, 1); + ExpectBackupCount(contexts, 0); + + sim.Update(1000); //2 + systems.CurrentTick.ShouldBe(frameCounter++); + + sim.Update(1000); //3 + systems.CurrentTick.ShouldBe(frameCounter++); + ExpectEntityCount(contexts, 1); + ExpectBackupCount(contexts, 0); + + GameEntityCountMatchesActorEntityCount(contexts, 0, 1); + GameEntityCountMatchesActorEntityCount(contexts, 1, 0); + + commandBuffer.Insert(2, 1, new ICommand[] { new MoveAll(contexts.game), }); + + sim.Update(1000); //4 + systems.CurrentTick.ShouldBe(frameCounter++); + ExpectEntityCount(contexts, 1); + ExpectBackupCount(contexts, 1); + + sim.Update(1000); //5 + systems.CurrentTick.ShouldBe(frameCounter++); + + ExpectEntityCount(contexts, 1); + ExpectBackupCount(contexts, 1); + + sim.Execute(new MoveEntitesOfSpecificActor(contexts.game, 0)); + + sim.Update(1000); //6 + systems.CurrentTick.ShouldBe(frameCounter++); + ExpectEntityCount(contexts, 1); + ExpectBackupCount(contexts, 1); + GameEntityCountMatchesActorEntityCount(contexts, 0, 1); + GameEntityCountMatchesActorEntityCount(contexts, 1, 0); - contexts.game.count.ShouldBe(70); + commandBuffer.Insert(3, 1, new ICommand[] { new Spawn() }); //Revert to 3 + + sim.Update(1000); //7 + systems.CurrentTick.ShouldBe(frameCounter++); + ExpectEntityCount(contexts, 2); + ExpectBackupCount(contexts, 2); + GameEntityCountMatchesActorEntityCount(contexts, 0, 1); + GameEntityCountMatchesActorEntityCount(contexts, 1, 1); + + commandBuffer.Insert(4, 1, new ICommand[] { new Spawn() }); //Revert to 4 + + sim.Update(1000); //8 + systems.CurrentTick.ShouldBe(frameCounter++); + ExpectEntityCount(contexts, 3); + ExpectBackupCount(contexts, 4); + GameEntityCountMatchesActorEntityCount(contexts, 0, 1); + GameEntityCountMatchesActorEntityCount(contexts, 1, 2); + + commandBuffer.Insert(5, 1, new ICommand[] { new Spawn(), new Spawn(), new Spawn(), new Spawn(), new Spawn(), new Spawn() }); //Revert to 5 + + sim.Update(1000); + systems.CurrentTick.ShouldBe(frameCounter++); + ExpectEntityCount(contexts, 9); + ExpectBackupCount(contexts, 7); + GameEntityCountMatchesActorEntityCount(contexts, 0, 1); + GameEntityCountMatchesActorEntityCount(contexts, 1, 8); + + sim.Update(1000); + systems.CurrentTick.ShouldBe(frameCounter++); + sim.Update(1000); + systems.CurrentTick.ShouldBe(frameCounter++); + sim.Update(1000); + systems.CurrentTick.ShouldBe(frameCounter++); + + sim.Execute(new Spawn()); + commandBuffer.Insert(6, 1, new ICommand[] { new MoveEntitesOfSpecificActor(contexts.game, 1), }); + + sim.Update(1000); + ExpectEntityCount(contexts, 10); + ExpectBackupCount(contexts, 16); + GameEntityCountMatchesActorEntityCount(contexts, 0, 2); + GameEntityCountMatchesActorEntityCount(contexts, 1, 8); + + sim.Execute(new Spawn()); + commandBuffer.Insert(11, 1, new ICommand[] { new MoveEntitesOfSpecificActor(contexts.game, 1), }); + sim.Update(1000); + + ExpectEntityCount(contexts, 11); + ExpectBackupCount(contexts, 25); + GameEntityCountMatchesActorEntityCount(contexts, 0, 3); + GameEntityCountMatchesActorEntityCount(contexts, 1, 8); } + [Fact] + public void TestThread() + { + var contexts = new Contexts(); + + var systems = new World(contexts, new TestLogger(_output)); + var commandBuffer = new CommandBuffer(); + + var sim = new Simulation(systems, commandBuffer) { LagCompensation = 0, SendCommandsToBuffer = false }; + + sim.Initialize(new Init { TargetFPS = 10, AllActors = new byte[] { 0, 1 }, ActorID = 0 }); + sim.StartAsThread(); + + Thread.Sleep(1000); + + systems.CurrentTick.ShouldBeInRange(9u, 11u); + } - public class SpawnCommand : ICommand + private void ExpectEntityCount(Contexts contexts, int value) { - public ushort Tag => 2; + contexts.game.GetEntities(GameMatcher.LocalId).Length.ShouldBe(value); + } + + private void ExpectBackupCount(Contexts contexts, int value) + { + contexts.game.GetEntities(GameMatcher.Backup).Length.ShouldBe(value); + } + + private void GameEntityCountMatchesActorEntityCount(Contexts contexts, byte actorId, int expectedCount) + { + var gameEntityCount = contexts.game.GetEntities(GameMatcher.LocalId).Count(entity => entity.actorId.value == actorId); + gameEntityCount.ShouldBe(expectedCount); + gameEntityCount.ShouldBe((int)contexts.actor.GetEntities(ActorMatcher.Id).First(actor => actor.id.value == actorId).entityCount.value); + } + + public class Spawn : ICommand + { public int EntityConfigId; public Vector2 Position; @@ -220,7 +379,46 @@ public void Execute(InputEntity e) { e.AddCoordinate(Position); e.AddEntityConfigId(EntityConfigId); + } + } + + //Hacky commands, don't do this in production. Commands should only modify the given input-entity + public class MoveAll : ICommand + { + private readonly GameContext _contexts; + + public MoveAll(GameContext contexts) + { + _contexts = contexts; + } + + public void Execute(InputEntity e) + { + foreach (var gameEntity in _contexts.GetEntities(GameMatcher.LocalId)) + { + gameEntity.ReplacePosition(new Vector2(2, 2)); + } } + } + + public class MoveEntitesOfSpecificActor : ICommand + { + private readonly GameContext _contexts; + private readonly byte _actorId; + + public MoveEntitesOfSpecificActor(GameContext contexts, byte actorId) + { + _contexts = contexts; + _actorId = actorId; + } + + public void Execute(InputEntity e) + { + foreach (var gameEntity in _contexts.GetEntities(GameMatcher.LocalId).Where(entity => entity.actorId.value == _actorId)) + { + gameEntity.ReplacePosition(new Vector2(2, 2)); + } + } } } diff --git a/Server.LiteNetLib/Integration/Lockstep.Network.dll b/Server.LiteNetLib/Integration/Lockstep.Network.dll index 9ee85ab..b1cfcea 100644 Binary files a/Server.LiteNetLib/Integration/Lockstep.Network.dll and b/Server.LiteNetLib/Integration/Lockstep.Network.dll differ diff --git a/Server.LiteNetLib/Integration/Lockstep.Network.pdb b/Server.LiteNetLib/Integration/Lockstep.Network.pdb index 91bb101..46a464e 100644 Binary files a/Server.LiteNetLib/Integration/Lockstep.Network.pdb and b/Server.LiteNetLib/Integration/Lockstep.Network.pdb differ diff --git a/Server.LiteNetLib/Integration/Server.dll b/Server.LiteNetLib/Integration/Server.dll index c828384..72a5fa3 100644 Binary files a/Server.LiteNetLib/Integration/Server.dll and b/Server.LiteNetLib/Integration/Server.dll differ diff --git a/Server.LiteNetLib/Integration/Server.pdb b/Server.LiteNetLib/Integration/Server.pdb index ae18c3d..8523aa2 100644 Binary files a/Server.LiteNetLib/Integration/Server.pdb and b/Server.LiteNetLib/Integration/Server.pdb differ diff --git a/Unity/Assets/Integration/Lockstep.Client.dll b/Unity/Assets/Integration/Lockstep.Client.dll index b0feee9..0d9db6b 100644 Binary files a/Unity/Assets/Integration/Lockstep.Client.dll and b/Unity/Assets/Integration/Lockstep.Client.dll differ diff --git a/Unity/Assets/Integration/Lockstep.Core.dll b/Unity/Assets/Integration/Lockstep.Core.dll index b229587..271b9fa 100644 Binary files a/Unity/Assets/Integration/Lockstep.Core.dll and b/Unity/Assets/Integration/Lockstep.Core.dll differ diff --git a/Unity/Assets/Integration/Lockstep.Core.dll.mdb b/Unity/Assets/Integration/Lockstep.Core.dll.mdb index 186fd71..aa56b42 100644 Binary files a/Unity/Assets/Integration/Lockstep.Core.dll.mdb and b/Unity/Assets/Integration/Lockstep.Core.dll.mdb differ diff --git a/Unity/Assets/Integration/Lockstep.Network.dll b/Unity/Assets/Integration/Lockstep.Network.dll index 9ee85ab..b1cfcea 100644 Binary files a/Unity/Assets/Integration/Lockstep.Network.dll and b/Unity/Assets/Integration/Lockstep.Network.dll differ diff --git a/Unity/Assets/Integration/Lockstep.Network.pdb b/Unity/Assets/Integration/Lockstep.Network.pdb index 91bb101..46a464e 100644 Binary files a/Unity/Assets/Integration/Lockstep.Network.pdb and b/Unity/Assets/Integration/Lockstep.Network.pdb differ diff --git a/Unity/Assets/Scenes/SampleScene.unity b/Unity/Assets/Scenes/SampleScene.unity index e79a7f6..062f323 100644 --- a/Unity/Assets/Scenes/SampleScene.unity +++ b/Unity/Assets/Scenes/SampleScene.unity @@ -623,6 +623,7 @@ GameObject: - component: {fileID: 1524441394} - component: {fileID: 1524441391} - component: {fileID: 1524441392} + - component: {fileID: 1524441393} m_Layer: 0 m_Name: GameManager m_TagString: Untagged @@ -660,6 +661,18 @@ MonoBehaviour: Count: 1 Prefab: {fileID: 6983145694885263932, guid: 24e2acaaa3c159748824ae994e0c01a3, type: 3} EntityDatabase: {fileID: 11400000, guid: a0d4789ca6cc5ab42a1264102206ac3f, type: 2} +--- !u!114 &1524441393 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1524441390} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a293066cc7061ad48afb9b084294d5a7, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!4 &1524441394 Transform: m_ObjectHideFlags: 0 diff --git a/Unity/Assets/Scripts/Dispatcher.cs b/Unity/Assets/Scripts/Dispatcher.cs new file mode 100644 index 0000000..5fefa94 --- /dev/null +++ b/Unity/Assets/Scripts/Dispatcher.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +public class Dispatcher : MonoBehaviour +{ + private static Dispatcher instance; + + private List pending = new List(); + + public static Dispatcher Instance + { + get + { + return instance; + } + } + + public void Invoke(Action fn) + { + lock (this.pending) + { + this.pending.Add(fn); + } + } + + private void InvokePending() + { + lock (this.pending) + { + foreach (Action action in this.pending) + { + action(); + } + + this.pending.Clear(); + } + } + + private void Awake() + { + if (instance != null && instance != this) + { + Destroy(this.gameObject); + } + else + { + instance = this; + } + } + + private void Update() + { + this.InvokePending(); + } +} \ No newline at end of file diff --git a/Unity/Assets/Scripts/Dispatcher.cs.meta b/Unity/Assets/Scripts/Dispatcher.cs.meta new file mode 100644 index 0000000..4c45e42 --- /dev/null +++ b/Unity/Assets/Scripts/Dispatcher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a293066cc7061ad48afb9b084294d5a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity/Assets/Scripts/Listeners/PositionListener.cs b/Unity/Assets/Scripts/Listeners/PositionListener.cs index 4620ca2..31c301a 100644 --- a/Unity/Assets/Scripts/Listeners/PositionListener.cs +++ b/Unity/Assets/Scripts/Listeners/PositionListener.cs @@ -11,6 +11,11 @@ public void RegisterListeners(GameEntity entity) _entity.AddPositionListener(this); } + public void UnregisterListeners() + { + _entity.RemovePositionListener(this); + } + public void OnPosition(GameEntity entity, BEPUutilities.Vector2 newPosition) { transform.position = new Vector3((float) newPosition.X, 1, (float) newPosition.Y); diff --git a/Unity/Assets/Scripts/RTSNetworkedSimulation.cs b/Unity/Assets/Scripts/RTSNetworkedSimulation.cs index 648985f..f18640e 100644 --- a/Unity/Assets/Scripts/RTSNetworkedSimulation.cs +++ b/Unity/Assets/Scripts/RTSNetworkedSimulation.cs @@ -46,8 +46,8 @@ private void Awake() private void StartSimulation(Init data) { - Debug.Log("Starting as player:" + data.PlayerID); - Simulation.Initialize(data); + Debug.Log($"Starting simulation. Total actors: {data.AllActors.Length}. Local ActorID: {data.ActorID}"); + Simulation.Initialize(data); _remoteCommandBuffer.InitReceived -= StartSimulation; } @@ -73,7 +73,6 @@ private void OnDestroy() void Update() { _client.Update(); - Simulation.Update(Time.deltaTime * 1000); } diff --git a/Unity/Assets/Scripts/UnityInput.cs b/Unity/Assets/Scripts/UnityInput.cs index 6c21ac2..253d252 100644 --- a/Unity/Assets/Scripts/UnityInput.cs +++ b/Unity/Assets/Scripts/UnityInput.cs @@ -1,4 +1,5 @@ using System.Linq; +using Entitas; using FixMath.NET; using Lockstep.Commands; using UnityEngine; @@ -25,11 +26,15 @@ void Update() } if (Input.GetKeyDown(KeyCode.X)) - { + { + var e = Contexts.sharedInstance.game.GetEntities(GameMatcher.LocalId).Select(entity => entity.id.value).ToArray(); + + Debug.Log("Navigating: " + string.Join(", ", e)); + RTSNetworkedSimulation.Instance.Execute(new NavigateCommand { Destination = GetWorldPos(Input.mousePosition), - Selection = Contexts.sharedInstance.game.GetEntities().Where(entity => entity.hasId).Select(entity => entity.id.value).ToArray() + Selection = e }); } } diff --git a/Unity/Assets/Scripts/UnityServices.cs b/Unity/Assets/Scripts/UnityServices.cs index 644a25b..a2bd81b 100644 --- a/Unity/Assets/Scripts/UnityServices.cs +++ b/Unity/Assets/Scripts/UnityServices.cs @@ -8,6 +8,8 @@ public interface IEventListener { void RegisterListeners(GameEntity entity); + void UnregisterListeners(); + } public interface IComponentSetter @@ -27,7 +29,7 @@ public UnityGameService(RTSEntityDatabase entityDatabase) public void LoadView(GameEntity entity, int configId) { - //TODO: pooling + //TODO: pooling var viewGo = Object.Instantiate(_entityDatabase.Entities[configId]).gameObject; if (viewGo != null) { @@ -37,7 +39,7 @@ public void LoadView(GameEntity entity, int configId) foreach (var componentSetter in componentSetters) { componentSetter.SetComponent(entity); - Object.Destroy((MonoBehaviour) componentSetter); + Object.Destroy((MonoBehaviour)componentSetter); } var eventListeners = viewGo.GetComponents(); @@ -47,14 +49,21 @@ public void LoadView(GameEntity entity, int configId) } linkedEntities.Add(entity.localId.value, viewGo); - } + } } public void DeleteView(uint entityId) - { + { + var viewGo = linkedEntities[entityId]; + var eventListeners = viewGo.GetComponents(); + foreach (var listener in eventListeners) + { + listener.UnregisterListeners(); + } + linkedEntities[entityId].Unlink(); linkedEntities[entityId].DestroyGameObject(); - linkedEntities.Remove(entityId); + linkedEntities.Remove(entityId); } }