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);
}
}