diff --git a/Engine/Client/Client.csproj b/Engine/Client/Client.csproj index 0913d7e..6d84b08 100644 --- a/Engine/Client/Client.csproj +++ b/Engine/Client/Client.csproj @@ -11,6 +11,18 @@ + + + ..\Dependencies\BEPUutilities.dll + + + ..\Dependencies\Entitas.dll + + + ..\Dependencies\FixMath.NET.dll + + + diff --git a/Unity/Assets/Scripts/Commands/NavigateCommand.cs b/Engine/Client/Commands/NavigateCommand.cs similarity index 87% rename from Unity/Assets/Scripts/Commands/NavigateCommand.cs rename to Engine/Client/Commands/NavigateCommand.cs index ff7c4f3..2142c68 100644 --- a/Unity/Assets/Scripts/Commands/NavigateCommand.cs +++ b/Engine/Client/Commands/NavigateCommand.cs @@ -1,9 +1,11 @@ -using BEPUutilities; -using Lockstep.Client.Interfaces; +using System; +using BEPUutilities; +using Lockstep.Client.Interfaces; using Lockstep.Network.Utils; -namespace Lockstep.Commands +namespace Lockstep.Client.Commands { + [Serializable] public class NavigateCommand : ISerializableCommand { public ushort Tag => 1; diff --git a/Unity/Assets/Scripts/Commands/SpawnCommand.cs b/Engine/Client/Commands/SpawnCommand.cs similarity index 90% rename from Unity/Assets/Scripts/Commands/SpawnCommand.cs rename to Engine/Client/Commands/SpawnCommand.cs index afbf829..41ed269 100644 --- a/Unity/Assets/Scripts/Commands/SpawnCommand.cs +++ b/Engine/Client/Commands/SpawnCommand.cs @@ -1,9 +1,11 @@ -using BEPUutilities; +using System; +using BEPUutilities; using Lockstep.Client.Interfaces; using Lockstep.Network.Utils; -namespace Lockstep.Commands +namespace Lockstep.Client.Commands { + [Serializable] public class SpawnCommand : ISerializableCommand { public ushort Tag => 2; diff --git a/Engine/Client/Implementations/NetworkCommandBuffer.cs b/Engine/Client/Implementations/NetworkCommandBuffer.cs index fbf5a7f..53fab91 100644 --- a/Engine/Client/Implementations/NetworkCommandBuffer.cs +++ b/Engine/Client/Implementations/NetworkCommandBuffer.cs @@ -9,10 +9,10 @@ namespace Lockstep.Client.Implementations { - public class NetworkCommandBuffer : CommandBuffer + public sealed class NetworkCommandBuffer : CommandBuffer { - //TODO: refactor: don't receive meta information through commandbuffer - public event Action InitReceived; + //TODO: refactor: don't do meta stuff through commandbuffer just because it has INetwork + public event Action InitReceived; private readonly INetwork _network; private readonly IDictionary> _commandFactories = new Dictionary>(); @@ -28,7 +28,7 @@ public void RegisterCommand(Func commandFactory) var tag = commandFactory.Invoke().Tag; if (_commandFactories.ContainsKey(tag)) { - throw new InvalidDataException("The command tag " + tag + " is already registered. Every command tag must be unique."); + throw new InvalidDataException($"The command tag {tag} is already registered. Every command tag must be unique."); } _commandFactories.Add(tag, commandFactory); } @@ -51,7 +51,8 @@ public override void Insert(uint frameNumber, byte commanderId, ICommand[] comma } _network.Send(Compressor.Compress(writer)); - } + } + private void OnDataReceived(byte[] data) { diff --git a/Engine/Client/Interfaces/ICommandBuffer.cs b/Engine/Client/Interfaces/ICommandBuffer.cs deleted file mode 100644 index 86554a0..0000000 --- a/Engine/Client/Interfaces/ICommandBuffer.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using Lockstep.Core.Interfaces; - -namespace Lockstep.Client.Interfaces -{ - public interface ICommandBuffer - { - uint LastInsertedFrame { get; } - - void Insert(uint frame, byte commanderId, ICommand[] commands); - - Dictionary> Get(uint frame); - } -} \ No newline at end of file diff --git a/Engine/Client/Simulation.cs b/Engine/Client/Simulation.cs index 29d0020..97fac90 100644 --- a/Engine/Client/Simulation.cs +++ b/Engine/Client/Simulation.cs @@ -1,8 +1,10 @@ using System.Collections.Generic; +using System.Linq; using System.Threading; -using Lockstep.Client.Interfaces; +using Lockstep.Client.Interfaces; using Lockstep.Core.Interfaces; using Lockstep.Network.Messages; +using Lockstep.Network.Utils; namespace Lockstep.Client { @@ -20,8 +22,7 @@ public class Simulation public byte LocalPlayerId { get; private set; } private float _tickDt; - private float _accumulatedTime; - private uint _lastValidatedFrame; + private float _accumulatedTime; private readonly IWorld _world; private readonly ICommandBuffer _remoteCommandBuffer; @@ -51,6 +52,13 @@ public void Execute(ICommand command) return; } + if (command is ISerializableCommand s) + { + var er = new Serializer(); + s.Serialize(er); + s.Deserialize(new Deserializer(er.Data)); + } + lock (_commandCache) { _commandCache.Add(command); @@ -97,35 +105,24 @@ private void Tick() private void SyncCommandBuffer() { - var currentRemoteFrame = _remoteCommandBuffer.LastInsertedFrame; + var commands = _remoteCommandBuffer.GetChanges(); - if (_lastValidatedFrame < currentRemoteFrame) + if (commands.Count > 0) { //We guess everything was predicted correctly (except the last received frame) - var firstMispredictedFrame = currentRemoteFrame; - - for (var remoteFrame = _lastValidatedFrame + 1; remoteFrame <= currentRemoteFrame; remoteFrame++) - { - //All frames that have no commands were predicted correctly => increase remote frame - var allPlayerCommands = _remoteCommandBuffer.Get(remoteFrame); - if (allPlayerCommands.Count == 0) - { - continue; - } + var firstMispredictedFrame = commands.Keys.Min(); + var lastInputFrame = commands.Keys.Max(); - if (firstMispredictedFrame > remoteFrame) - { - //Set the first mispredicted frame to the first frame which contains commands - firstMispredictedFrame = remoteFrame; - } - //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.Services.Get().Trace(">>>Input from " + firstMispredictedFrame + " to " + lastInputFrame); + + foreach (var tick in commands.Keys) + { + foreach (var actorId in commands[tick].Keys) { - _world.AddInput(remoteFrame, playerCommands.Key, playerCommands.Value); + _world.AddInput(tick, actorId, commands[tick][actorId]); } - } + } //Only rollback if the mispredicted frame was in the past (the frame can be in the future due to high lag compensation) if (firstMispredictedFrame <= _world.CurrentTick) @@ -134,19 +131,20 @@ private void SyncCommandBuffer() _world.RevertToTick(firstMispredictedFrame); - while (_world.CurrentTick < firstMispredictedFrame) + //Restore last local state + while (_world.CurrentTick <= lastInputFrame) { _world.Simulate(); } - - //Restore last local state + + _world.Services.Get().Trace(">>>Predicting up to " + targetTick); while (_world.CurrentTick < targetTick) { _world.Predict(); } - } - _lastValidatedFrame = currentRemoteFrame; + _world.Services.Get().Trace(">>>Done: now at " + _world.CurrentTick); + } } } diff --git a/Engine/Client/Implementations/CommandBuffer.cs b/Engine/Core/CommandBuffer.cs similarity index 58% rename from Engine/Client/Implementations/CommandBuffer.cs rename to Engine/Core/CommandBuffer.cs index 8f23850..1f66be2 100644 --- a/Engine/Client/Implementations/CommandBuffer.cs +++ b/Engine/Core/CommandBuffer.cs @@ -1,17 +1,17 @@ -using System.Collections.Generic; -using Lockstep.Client.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; using Lockstep.Core.Interfaces; namespace Lockstep.Client.Implementations -{ +{ + [Serializable] public class CommandBuffer : ICommandBuffer { /// /// Mapping: FrameNumber -> Commands per player(Id) /// - protected Dictionary>> Buffer { get; } = new Dictionary>>(5000); - - public uint LastInsertedFrame { get; private set; } + public Dictionary>> Buffer { get; } = new Dictionary>>(5000); public virtual void Insert(uint frameNumber, byte commanderId, ICommand[] commands) { @@ -27,24 +27,18 @@ public virtual void Insert(uint frameNumber, byte commanderId, ICommand[] comman Buffer[frameNumber].Add(commanderId, new List(5)); //Initial size of 5 commands per frame per player } - Buffer[frameNumber][commanderId].AddRange(commands); - - LastInsertedFrame = frameNumber; + Buffer[frameNumber][commanderId].AddRange(commands); } } - public Dictionary> Get(uint frame) + public Dictionary>> GetChanges() { lock (Buffer) - { - //If no commands were inserted then return an empty list - if (!Buffer.ContainsKey(frame)) - { - Buffer.Add(frame, new Dictionary>()); - } - - return Buffer[frame]; + { + var result = Buffer.ToDictionary(pair => pair.Key, pair => pair.Value); + Buffer.Clear(); + return result; } - } + } } } diff --git a/Engine/Core/Components/Input/TargetPlayerIdComponent.cs b/Engine/Core/Components/Input/TargetPlayerIdComponent.cs index 12453ae..2b826c5 100644 --- a/Engine/Core/Components/Input/TargetPlayerIdComponent.cs +++ b/Engine/Core/Components/Input/TargetPlayerIdComponent.cs @@ -3,7 +3,7 @@ namespace Lockstep.Core.Components.Input { [Input] - public class TargetPlayerIdComponent : IComponent + public class TargetActorIdComponent : IComponent { public byte value; } diff --git a/Engine/Core/Core.csproj b/Engine/Core/Core.csproj index 9a990c7..c3087c4 100644 --- a/Engine/Core/Core.csproj +++ b/Engine/Core/Core.csproj @@ -1,4 +1,4 @@ - + @@ -56,6 +56,9 @@ + + + @@ -78,7 +81,7 @@ - + @@ -152,7 +155,10 @@ + + + @@ -191,7 +197,6 @@ - @@ -207,10 +212,10 @@ - + - + diff --git a/Engine/Core/DefaultServices/DefaultHashService.cs b/Engine/Core/DefaultServices/DefaultHashService.cs index eced90f..2ac029a 100644 --- a/Engine/Core/DefaultServices/DefaultHashService.cs +++ b/Engine/Core/DefaultServices/DefaultHashService.cs @@ -1,19 +1,32 @@ using System.Collections.Generic; +using System.Linq; using Lockstep.Core.Interfaces; namespace Lockstep.Core.DefaultServices { - class DefaultHashService : IHashService + public class DefaultHashService : IHashService { - public long CalculateHashCode(IEnumerable entities) + public long CalculateHashCode(IEnumerable hashableEntities, GameStateContext context, ILogService logger) { long hashCode = 0; - foreach (var entity in entities) + foreach (var entity in hashableEntities.OrderBy(entity => entity.localId.value)) { - hashCode ^= entity.position.value.X.RawValue; - hashCode ^= entity.position.value.Y.RawValue; + hashCode ^= CalculateHashCode(entity); + if (context.tick.value > 170 && context.tick.value < 175 && entity.actorId.value == 2 && entity.id.value == 89) + //if (context.tick.value == 38) + { + logger.Warn(entity.actorId.value + "/" + entity.id.value + ": (" + entity.position.value + ")"); + } } return hashCode; } + + public long CalculateHashCode(GameEntity entity) + { + long hashCode = 0; + hashCode ^= entity.position.value.X.RawValue; + hashCode ^= entity.position.value.Y.RawValue; + return hashCode; + } } } diff --git a/Engine/Core/DefaultServices/DefaultIDebugService.cs b/Engine/Core/DefaultServices/DefaultIDebugService.cs new file mode 100644 index 0000000..5ebdd88 --- /dev/null +++ b/Engine/Core/DefaultServices/DefaultIDebugService.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using BEPUutilities; +using Lockstep.Core.Interfaces; + +namespace Lockstep.Core.DefaultServices +{ + public class DefaultIDebugService : IDebugService + { + public Dictionary> Buffer { get; } = new Dictionary>(5000); + public Dictionary Hashes { get; } = new Dictionary(5000); + + + public void Register(uint tick, long hash) + { + Hashes.Add(tick, hash); + } + + public long GetHash(uint tick) + { + return Hashes[tick]; + } + + public bool HasHash(uint tick) + { + return Hashes.ContainsKey(tick); + } + + public void Register(uint tick, uint entityId, Vector2 pos) + { + if (!Buffer.ContainsKey(tick)) + { + Buffer.Add(tick, new Dictionary(10)); + } + + if (!Buffer[tick].ContainsKey(entityId)) + { + Buffer[tick].Add(entityId, pos); //Initial size of 5 commands per frame per player + } + else + { + throw new Exception(); + } + } + + public bool Validate(uint tick, uint entityId, Vector2 pos) + { + return Buffer[tick][entityId].X.RawValue == pos.X.RawValue && + Buffer[tick][entityId].Y.RawValue == pos.Y.RawValue; + } + } +} diff --git a/Engine/Core/DefaultServices/DefaultSnapshotIndexService.cs b/Engine/Core/DefaultServices/DefaultSnapshotIndexService.cs index 1daf360..1bc8f9c 100644 --- a/Engine/Core/DefaultServices/DefaultSnapshotIndexService.cs +++ b/Engine/Core/DefaultServices/DefaultSnapshotIndexService.cs @@ -13,9 +13,17 @@ public void AddIndex(uint value) _snapShotIndices.Add(value); } + public void RemoveIndex(uint value) + { + if (_snapShotIndices.Contains(value)) + { + _snapShotIndices.Remove(value); + } + } + public uint GetFirstIndexBefore(uint value) { - return _snapShotIndices.Where(index => index <= value).Max(); + return _snapShotIndices.Any() ? _snapShotIndices.Where(index => index <= value).Max() : 0; } } } diff --git a/Engine/Core/Features/HashCodeFeature.cs b/Engine/Core/Features/HashCodeFeature.cs deleted file mode 100644 index 2fa9101..0000000 --- a/Engine/Core/Features/HashCodeFeature.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Lockstep.Core.Interfaces; -using Lockstep.Core.Systems; - -namespace Lockstep.Core.Features -{ - public sealed class HashCodeFeature : Feature - { - public HashCodeFeature(Contexts contexts, Services services) - { - Add(new CalculateHashCode(contexts, services.Get())); - } - } -} diff --git a/Engine/Core/Features/InputFeature.cs b/Engine/Core/Features/InputFeature.cs index 54c2c58..28bb448 100644 --- a/Engine/Core/Features/InputFeature.cs +++ b/Engine/Core/Features/InputFeature.cs @@ -8,7 +8,7 @@ public InputFeature(Contexts contexts, Services services) { //TODO: Add InputValidationSystem - Add(new OnSpawnInputCreateEntity(contexts, services)); + Add(new ExecuteSpawnInput(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 3913454..bb15160 100644 --- a/Engine/Core/Features/NavigationFeature.cs +++ b/Engine/Core/Features/NavigationFeature.cs @@ -10,7 +10,7 @@ public NavigationFeature(Contexts contexts, Services services) var navigationService = services.Get(); //Add(new OnNavigableDoRegisterAgent(contexts, navigationService)); - Add(new OnNavigationInputDoSetDestination(contexts, navigationService)); + Add(new ExecuteNavigationInput(contexts, services)); Add(new NavigationTick(contexts, navigationService)); //Add(new SyncAgentVelocity(contexts, navigationService)); //Add(new UpdateAgentPosition(contexts, navigationService)); diff --git a/Engine/Core/GameLog.cs b/Engine/Core/GameLog.cs new file mode 100644 index 0000000..a311703 --- /dev/null +++ b/Engine/Core/GameLog.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Lockstep.Core.Interfaces; + +namespace Lockstep.Core +{ + [Serializable] + public class GameLog + { + public Dictionary>>> Log { get; } = new Dictionary>>>(); + public void Add(uint tickId, uint targetTickId, byte actorId, IEnumerable commands) + { + if (!Log.ContainsKey(tickId)) + { + Log.Add(tickId, new Dictionary>>()); + } + + if (!Log[tickId].ContainsKey(targetTickId)) + { + Log[tickId].Add(targetTickId, new Dictionary>()); + } + + if (!Log[tickId][targetTickId].ContainsKey(actorId)) + { + Log[tickId][targetTickId].Add(actorId, new List()); + } + + Log[tickId][targetTickId][actorId].AddRange(commands); + } + + public List>>>> GetAllCommandsForFrame(uint frame) + { + return Log.Reverse().Where(pair => pair.Key == frame).ToList(); + } + } +} diff --git a/Engine/Core/Generated/Input/Components/InputTargetActorIdComponent.cs b/Engine/Core/Generated/Input/Components/InputTargetActorIdComponent.cs new file mode 100644 index 0000000..c5e0d97 --- /dev/null +++ b/Engine/Core/Generated/Input/Components/InputTargetActorIdComponent.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 InputEntity { + + public Lockstep.Core.Components.Input.TargetActorIdComponent targetActorId { get { return (Lockstep.Core.Components.Input.TargetActorIdComponent)GetComponent(InputComponentsLookup.TargetActorId); } } + public bool hasTargetActorId { get { return HasComponent(InputComponentsLookup.TargetActorId); } } + + public void AddTargetActorId(byte newValue) { + var index = InputComponentsLookup.TargetActorId; + var component = (Lockstep.Core.Components.Input.TargetActorIdComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Input.TargetActorIdComponent)); + component.value = newValue; + AddComponent(index, component); + } + + public void ReplaceTargetActorId(byte newValue) { + var index = InputComponentsLookup.TargetActorId; + var component = (Lockstep.Core.Components.Input.TargetActorIdComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Input.TargetActorIdComponent)); + component.value = newValue; + ReplaceComponent(index, component); + } + + public void RemoveTargetActorId() { + RemoveComponent(InputComponentsLookup.TargetActorId); + } +} + +//------------------------------------------------------------------------------ +// +// 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 InputMatcher { + + static Entitas.IMatcher _matcherTargetActorId; + + public static Entitas.IMatcher TargetActorId { + get { + if (_matcherTargetActorId == null) { + var matcher = (Entitas.Matcher)Entitas.Matcher.AllOf(InputComponentsLookup.TargetActorId); + matcher.componentNames = InputComponentsLookup.componentNames; + _matcherTargetActorId = matcher; + } + + return _matcherTargetActorId; + } + } +} diff --git a/Engine/Core/Generated/Input/Components/InputTargetPlayerIdComponent.cs b/Engine/Core/Generated/Input/Components/InputTargetPlayerIdComponent.cs deleted file mode 100644 index c87c43b..0000000 --- a/Engine/Core/Generated/Input/Components/InputTargetPlayerIdComponent.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 InputEntity { - - public Lockstep.Core.Components.Input.TargetPlayerIdComponent targetPlayerId { get { return (Lockstep.Core.Components.Input.TargetPlayerIdComponent)GetComponent(InputComponentsLookup.TargetPlayerId); } } - public bool hasTargetPlayerId { get { return HasComponent(InputComponentsLookup.TargetPlayerId); } } - - public void AddTargetPlayerId(byte newValue) { - var index = InputComponentsLookup.TargetPlayerId; - var component = (Lockstep.Core.Components.Input.TargetPlayerIdComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Input.TargetPlayerIdComponent)); - component.value = newValue; - AddComponent(index, component); - } - - public void ReplaceTargetPlayerId(byte newValue) { - var index = InputComponentsLookup.TargetPlayerId; - var component = (Lockstep.Core.Components.Input.TargetPlayerIdComponent)CreateComponent(index, typeof(Lockstep.Core.Components.Input.TargetPlayerIdComponent)); - component.value = newValue; - ReplaceComponent(index, component); - } - - public void RemoveTargetPlayerId() { - RemoveComponent(InputComponentsLookup.TargetPlayerId); - } -} - -//------------------------------------------------------------------------------ -// -// 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 InputMatcher { - - static Entitas.IMatcher _matcherTargetPlayerId; - - public static Entitas.IMatcher TargetPlayerId { - get { - if (_matcherTargetPlayerId == null) { - var matcher = (Entitas.Matcher)Entitas.Matcher.AllOf(InputComponentsLookup.TargetPlayerId); - matcher.componentNames = InputComponentsLookup.componentNames; - _matcherTargetPlayerId = matcher; - } - - return _matcherTargetPlayerId; - } - } -} diff --git a/Engine/Core/Generated/Input/InputComponentsLookup.cs b/Engine/Core/Generated/Input/InputComponentsLookup.cs index 2464fd6..ff77cbc 100644 --- a/Engine/Core/Generated/Input/InputComponentsLookup.cs +++ b/Engine/Core/Generated/Input/InputComponentsLookup.cs @@ -12,7 +12,7 @@ public static class InputComponentsLookup { public const int Coordinate = 1; public const int EntityConfigId = 2; public const int Selection = 3; - public const int TargetPlayerId = 4; + public const int TargetActorId = 4; public const int Tick = 5; public const int TotalComponents = 6; @@ -22,7 +22,7 @@ public static class InputComponentsLookup { "Coordinate", "EntityConfigId", "Selection", - "TargetPlayerId", + "TargetActorId", "Tick" }; @@ -31,7 +31,7 @@ public static class InputComponentsLookup { typeof(Lockstep.Core.Components.Input.CoordinateComponent), typeof(Lockstep.Core.Components.Input.EntityConfigIdComponent), typeof(Lockstep.Core.Components.Input.SelectionComponent), - typeof(Lockstep.Core.Components.Input.TargetPlayerIdComponent), + typeof(Lockstep.Core.Components.Input.TargetActorIdComponent), typeof(Lockstep.Core.Components.Input.TickComponent) }; } diff --git a/Engine/Core/Interfaces/ICommandBuffer.cs b/Engine/Core/Interfaces/ICommandBuffer.cs new file mode 100644 index 0000000..0d00bdd --- /dev/null +++ b/Engine/Core/Interfaces/ICommandBuffer.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Lockstep.Core.Interfaces +{ + public interface ICommandBuffer + { + Dictionary>> Buffer { get; } + + void Insert(uint frame, byte commanderId, ICommand[] commands); + + Dictionary>> GetChanges(); + } +} \ No newline at end of file diff --git a/Engine/Core/Interfaces/IDebugService.cs b/Engine/Core/Interfaces/IDebugService.cs new file mode 100644 index 0000000..f371b85 --- /dev/null +++ b/Engine/Core/Interfaces/IDebugService.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BEPUutilities; + +namespace Lockstep.Core.Interfaces +{ + public interface IDebugService : IService + { + void Register(uint tick, long hash); + long GetHash(uint tick); + bool HasHash(uint tick); + + void Register(uint tick, uint entityId, Vector2 pos); + + bool Validate(uint tick, uint entityId, Vector2 pos); + } +} diff --git a/Engine/Core/Interfaces/IHashService.cs b/Engine/Core/Interfaces/IHashService.cs index c9b9002..5361115 100644 --- a/Engine/Core/Interfaces/IHashService.cs +++ b/Engine/Core/Interfaces/IHashService.cs @@ -3,7 +3,9 @@ namespace Lockstep.Core.Interfaces { public interface IHashService : IService - { - long CalculateHashCode(IEnumerable hashableEntities); + { + long CalculateHashCode(IEnumerable hashableEntities, GameStateContext context, ILogService logger); + + long CalculateHashCode(GameEntity entity); } } \ No newline at end of file diff --git a/Engine/Core/Interfaces/ILogService.cs b/Engine/Core/Interfaces/ILogService.cs index f71449d..155ec63 100644 --- a/Engine/Core/Interfaces/ILogService.cs +++ b/Engine/Core/Interfaces/ILogService.cs @@ -3,5 +3,6 @@ public interface ILogService : IService { void Warn(object message); + void Trace(object message); } } \ No newline at end of file diff --git a/Engine/Core/Interfaces/ISnapshotIndexService.cs b/Engine/Core/Interfaces/ISnapshotIndexService.cs index 3ae9770..c6a1bf7 100644 --- a/Engine/Core/Interfaces/ISnapshotIndexService.cs +++ b/Engine/Core/Interfaces/ISnapshotIndexService.cs @@ -7,6 +7,8 @@ public interface ISnapshotIndexService : IService { void AddIndex(uint value); + void RemoveIndex(uint value); + uint GetFirstIndexBefore(uint value); } } diff --git a/Engine/Core/Interfaces/IWorld.cs b/Engine/Core/Interfaces/IWorld.cs index f61f0c9..c721c51 100644 --- a/Engine/Core/Interfaces/IWorld.cs +++ b/Engine/Core/Interfaces/IWorld.cs @@ -1,9 +1,12 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using Lockstep.Client.Implementations; namespace Lockstep.Core.Interfaces { public interface IWorld { + GameLog GameLog { get; } + Services Services { get; } int EntitiesInCurrentTick { get; } diff --git a/Engine/Core/Services.cs b/Engine/Core/Services.cs index c624c58..453beb0 100644 --- a/Engine/Core/Services.cs +++ b/Engine/Core/Services.cs @@ -17,6 +17,7 @@ public Services() RegisterDefault(new DefaultViewService()); RegisterDefault(new DefaultNavigationService()); RegisterDefault(new DefaultSnapshotIndexService()); + RegisterDefault(new DefaultIDebugService()); } public void Register(IService instance) diff --git a/Engine/Core/Systems/CalculateHashCode.cs b/Engine/Core/Systems/CalculateHashCode.cs index 200fd03..fa80d54 100644 --- a/Engine/Core/Systems/CalculateHashCode.cs +++ b/Engine/Core/Systems/CalculateHashCode.cs @@ -5,27 +5,30 @@ namespace Lockstep.Core.Systems { public class CalculateHashCode : IInitializeSystem, IExecuteSystem { + private readonly Services _services; private readonly IHashService _hashService; private readonly IGroup _hashableEntities; private readonly GameStateContext _gameStateContext; - public CalculateHashCode(Contexts contexts, IHashService hashService) + public CalculateHashCode(Contexts contexts, Services services) { - _hashService = hashService; + _services = services; + _hashService = services.Get(); + _gameStateContext = contexts.gameState; - _hashableEntities = contexts.game.GetGroup(GameMatcher.AllOf(GameMatcher.LocalId, GameMatcher.Hashable)); + _hashableEntities = contexts.game.GetGroup(GameMatcher.AllOf(GameMatcher.LocalId, GameMatcher.Position).NoneOf(GameMatcher.Backup)); } public void Initialize() { - _gameStateContext.SetHashCode(0); + _gameStateContext.ReplaceHashCode(0); } public void Execute() { - _gameStateContext.ReplaceHashCode(_hashService.CalculateHashCode(_hashableEntities.GetEntities())); + _gameStateContext.ReplaceHashCode(_hashService.CalculateHashCode(_hashableEntities.GetEntities(), _gameStateContext, _services.Get())); } } } diff --git a/Engine/Core/Systems/Debugging/VerifySelectionIdExists.cs b/Engine/Core/Systems/Debugging/VerifySelectionIdExists.cs new file mode 100644 index 0000000..4f56cdc --- /dev/null +++ b/Engine/Core/Systems/Debugging/VerifySelectionIdExists.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Entitas; +using Lockstep.Core.Interfaces; + +namespace Lockstep.Core.Systems.Debugging +{ + public class VerifySelectionIdExists : IExecuteSystem + { + private readonly Services _services; + private readonly GameContext _gameContext; + private readonly InputContext _inputContext; + private readonly GameStateContext _gameStateContext; + + public VerifySelectionIdExists(Contexts contexts, Services services) + { + _services = services; + _gameContext = contexts.game; + _inputContext = contexts.input; + _gameStateContext = contexts.gameState; + } + + public void Execute() + { + foreach (var input in _inputContext.GetEntities( + InputMatcher.AllOf( + InputMatcher.Tick, + InputMatcher.Coordinate, + InputMatcher.Selection, + InputMatcher.ActorId)) + .Where(entity => entity.tick.value < _gameStateContext.tick.value)) + { + var ents = _gameContext.GetEntities(GameMatcher.LocalId) + .Where(entity => entity.actorId.value == input.actorId.value).Select(entity => entity.id.value); + var missing = input.selection.entityIds.Where(u => !ents.Contains(u)).ToList(); + if (missing.Any()) + { + _services.Get().Warn(missing.Count + " missing for actor: " + input.actorId.value); + } + + } + } + } +} diff --git a/Engine/Core/Systems/Input/OnSpawnInputCreateEntity.cs b/Engine/Core/Systems/Input/ExecuteSpawnInput.cs similarity index 73% rename from Engine/Core/Systems/Input/OnSpawnInputCreateEntity.cs rename to Engine/Core/Systems/Input/ExecuteSpawnInput.cs index 67c66a1..d16b360 100644 --- a/Engine/Core/Systems/Input/OnSpawnInputCreateEntity.cs +++ b/Engine/Core/Systems/Input/ExecuteSpawnInput.cs @@ -5,8 +5,9 @@ namespace Lockstep.Core.Systems.Input { - public class OnSpawnInputCreateEntity : IExecuteSystem - { + public class ExecuteSpawnInput : IExecuteSystem + { + private readonly Services _services; private readonly IViewService _viewService; private readonly GameContext _gameContext; private readonly GameStateContext _gameStateContext; @@ -15,8 +16,9 @@ public class OnSpawnInputCreateEntity : IExecuteSystem private uint _localIdCounter; private readonly ActorContext _actorContext; - public OnSpawnInputCreateEntity(Contexts contexts, Services services) + public ExecuteSpawnInput(Contexts contexts, Services services) { + _services = services; _viewService = services.Get(); _gameContext = contexts.game; _gameStateContext = contexts.gameState; @@ -31,17 +33,17 @@ public OnSpawnInputCreateEntity(Contexts contexts, Services services) } public void Execute() - { - //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)) + { + foreach (var input in _spawnInputs.GetEntities().Where(entity => entity.tick.value == _gameStateContext.tick.value)) { var actor = _actorContext.GetEntityWithId(input.actorId.value); - var nextEntityId = actor.entityCount.value + 1; + var nextEntityId = actor.entityCount.value; var e = _gameContext.CreateEntity(); e.isNew = true; - + _services.Get().Trace(actor.id.value + " -> " + nextEntityId); + //composite primary key e.AddId(nextEntityId); e.AddActorId(input.actorId.value); @@ -55,7 +57,7 @@ public void Execute() _viewService.LoadView(e, input.entityConfigId.value); - actor.ReplaceEntityCount(nextEntityId); + actor.ReplaceEntityCount(nextEntityId + 1); _localIdCounter += 1; } } diff --git a/Engine/Core/Systems/Navigation/ExecuteNavigationInput.cs b/Engine/Core/Systems/Navigation/ExecuteNavigationInput.cs new file mode 100644 index 0000000..4f05975 --- /dev/null +++ b/Engine/Core/Systems/Navigation/ExecuteNavigationInput.cs @@ -0,0 +1,55 @@ +using System.Linq; +using Entitas; +using Lockstep.Core.Interfaces; + +namespace Lockstep.Core.Systems.Navigation +{ + public class ExecuteNavigationInput : IExecuteSystem + { + private readonly Services _services; + private readonly INavigationService _navigationService; + private readonly GameContext _gameContext; + readonly IGroup _navigationInput; + private readonly GameStateContext _gameStateContext; + + public ExecuteNavigationInput(Contexts contexts, Services services) + { + _services = services; + _navigationService = services.Get(); + _gameContext = contexts.game; + _gameStateContext = contexts.gameState; + + _navigationInput = contexts.input.GetGroup(InputMatcher.AllOf( + InputMatcher.Coordinate, + InputMatcher.Selection, + InputMatcher.ActorId, + InputMatcher.Tick)); + } + + + public void Execute() + { + foreach (var input in _navigationInput.GetEntities().Where(entity => entity.tick.value == _gameStateContext.tick.value)) + { + var destination = input.coordinate.value; + var targetActorId = input.hasTargetActorId ? input.targetActorId.value : input.actorId.value; + + var selectedEntities = _gameContext + .GetEntities(GameMatcher.LocalId) + .Where(entity => + input.selection.entityIds.Contains(entity.id.value) && + entity.actorId.value == targetActorId); + + + _services.Get().Trace(targetActorId + " moving " + string.Join(", ", selectedEntities.Select(entity => entity.id.value))); + + foreach (var entity in selectedEntities) + { + entity.ReplaceDestination(destination); + + //_navigationService.SetAgentDestination(entityId, destination); + } + } + } + } +} diff --git a/Engine/Core/Systems/Navigation/NavigationTick.cs b/Engine/Core/Systems/Navigation/NavigationTick.cs index bf22499..c687d55 100644 --- a/Engine/Core/Systems/Navigation/NavigationTick.cs +++ b/Engine/Core/Systems/Navigation/NavigationTick.cs @@ -29,9 +29,11 @@ public void Execute() velocity.Normalize(); } - if ((entity.destination.value - entity.position.value).LengthSquared() > 2) + if ((entity.destination.value - entity.position.value).LengthSquared() > 1) { entity.ReplacePosition(entity.position.value + velocity); + //TODO: undo after debugging + //entity.ReplacePosition(entity.destination.value); } } } diff --git a/Engine/Core/Systems/Navigation/OnNavigationInputDoSetDestination.cs b/Engine/Core/Systems/Navigation/OnNavigationInputDoSetDestination.cs deleted file mode 100644 index 5a24a60..0000000 --- a/Engine/Core/Systems/Navigation/OnNavigationInputDoSetDestination.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Entitas; -using Lockstep.Core.Interfaces; - -namespace Lockstep.Core.Systems.Navigation -{ - public class OnNavigationInputDoSetDestination : ReactiveSystem - { - private readonly INavigationService _navigationService; - private readonly GameContext _contextsGame; - - public OnNavigationInputDoSetDestination(Contexts contexts, INavigationService navigationService) : base(contexts.input) - { - _navigationService = navigationService; - _contextsGame = contexts.game; - } - - protected override ICollector GetTrigger(IContext context) - { - return context.CreateCollector(InputMatcher.AllOf(InputMatcher.Coordinate, InputMatcher.Selection, InputMatcher.ActorId)); - } - - protected override bool Filter(InputEntity entity) - { - return entity.hasCoordinate && entity.hasSelection && entity.hasActorId; - } - - protected override void Execute(List inputs) - { - foreach (var input in inputs) - { - var destination = input.coordinate.value; - - var selectedEntities = _contextsGame - .GetEntities(GameMatcher.LocalId) - .Where(entity => - input.selection.entityIds.Contains(entity.id.value) && - entity.actorId.value == input.actorId.value); - - foreach (var entity in selectedEntities) - { - entity.ReplaceDestination(destination); - - //_navigationService.SetAgentDestination(entityId, destination); - } - } - } - } -} diff --git a/Engine/Core/Systems/Navigation/UpdateAgentPosition.cs b/Engine/Core/Systems/Navigation/UpdateAgentPosition.cs index 8be62bc..9fe8ee9 100644 --- a/Engine/Core/Systems/Navigation/UpdateAgentPosition.cs +++ b/Engine/Core/Systems/Navigation/UpdateAgentPosition.cs @@ -23,12 +23,11 @@ public void Execute() foreach (var entity in _gameContext.GetEntities().Where(e => e.hasId)) { - if (entity.velocity.value != Vector2.Zero) - { - entity.ReplacePosition(entity.position.value + entity.velocity.value); + if (entity.velocity.value == Vector2.Zero) + continue; + entity.ReplacePosition(entity.position.value + entity.velocity.value); - updatedPositions.Add(entity.localId.value, entity.position.value); - } + updatedPositions.Add(entity.localId.value, entity.position.value); } _navigationService.SetAgentPositions(updatedPositions); diff --git a/Engine/Core/Systems/OnNewPredictionCreateBackup.cs b/Engine/Core/Systems/OnNewPredictionCreateBackup.cs index 997c859..cae9ab9 100644 --- a/Engine/Core/Systems/OnNewPredictionCreateBackup.cs +++ b/Engine/Core/Systems/OnNewPredictionCreateBackup.cs @@ -68,7 +68,9 @@ protected override void Execute(List entities) foreach (var e in gameEnts) { var shadowEntity = _gameContext.CreateEntity(); - + + _services.Get().Register(currentTick, e.localId.value, e.position.value); + //LocalId is primary index => don't copy; id+actorId should be readonly and stay the same throughout the game foreach (var index in e.GetComponentIndices().Except(new[] { GameComponentsLookup.LocalId, GameComponentsLookup.Id, GameComponentsLookup.ActorId })) { @@ -80,8 +82,7 @@ protected override void Execute(List entities) shadowEntity.AddBackup(e.localId.value, currentTick); } - - + _services.Get().Warn("New backup for " + currentTick + "(" + actors.Length + " actors, " + gameEnts.Length + " entities)"); } } diff --git a/Engine/Core/World.cs b/Engine/Core/World.cs index 55c2cb1..38e32bc 100644 --- a/Engine/Core/World.cs +++ b/Engine/Core/World.cs @@ -1,6 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; -using Entitas; +using Entitas; using Lockstep.Core.Features; using Lockstep.Core.Interfaces; using Lockstep.Core.Systems; @@ -13,6 +14,8 @@ public sealed class World : Feature, IWorld { private Contexts Contexts { get; } + public GameLog GameLog { get; } = new GameLog(); + public Services Services { get; } public uint CurrentTick => Contexts.gameState.tick.value; @@ -23,6 +26,7 @@ public sealed class World : Feature, IWorld private readonly GameContext _gameContext; private readonly INavigationService _navigation; private readonly ActorContext _actorContext; + public World(Contexts contexts, params IService[] additionalServices) { @@ -45,15 +49,19 @@ public World(Contexts contexts, params IService[] additionalServices) private void AddFeatures(Contexts contexts) { + Add(new CalculateHashCode(contexts, Services)); + Add(new OnNewPredictionCreateBackup(contexts, Services)); Add(new InputFeature(contexts, Services)); + Add(new VerifySelectionIdExists(contexts, Services)); + Add(new NavigationFeature(contexts, Services)); Add(new GameEventSystems(contexts)); - Add(new HashCodeFeature(contexts, Services)); + Add(new CalculateHashCode(contexts, Services)); Add(new RemoveNewFlag(contexts)); @@ -75,6 +83,8 @@ public void Initialize(byte[] allActorIds) public void AddInput(uint tickId, byte actor, List input) { + GameLog.Add(CurrentTick, tickId, actor, input.ToArray()); + foreach (var command in input) { var inputEntity = Contexts.input.CreateEntity(); @@ -83,6 +93,10 @@ public void AddInput(uint tickId, byte actor, List input) inputEntity.AddTick(tickId); inputEntity.AddActorId(actor); } + + //TODO: after adding input, order input by timestamp => if commands intersect, the first one should win, timestamp should be added by server, RTT has to be considered + //ordering by timestamp requires loopback functionality because we have to wait for server-response; at the moment commands get distributed to all clients except oneself + //if a command comes back from server and it was our own command, the local command has to be overwritten instead of just adding it (as it is at the moment) } public void Predict() @@ -92,6 +106,7 @@ public void Predict() Contexts.gameState.isPredicting = true; } + Services.Get().Trace("Predict " + CurrentTick); Execute(); Cleanup(); } @@ -101,10 +116,15 @@ public void Simulate() if (Contexts.gameState.isPredicting) { Contexts.gameState.isPredicting = false; - } + } + + Services.Get().Trace("Simulate " + CurrentTick); Execute(); Cleanup(); + + Services.Get().Register(Contexts.gameState.tick.value, Contexts.gameState.hashCode.value); + } /// @@ -112,7 +132,9 @@ public void Simulate() /// /// public void RevertToTick(uint tick) - { + { + + Services.Get().Trace("Rollback to " + tick); //Get the actual tick that we have a snapshot for var resultTick = Services.Get().GetFirstIndexBefore(tick); @@ -125,66 +147,62 @@ public void RevertToTick(uint tick) foreach (var backedUpActor in backedUpActors) { backedUpActor.CopyTo( - _actorContext.GetEntityWithId(backedUpActor.backup.actorId), //Current Actor - true, //Replace components + _actorContext.GetEntityWithId(backedUpActor.backup.actorId), //Current Actor + true, //Replace components backedUpActor.GetComponentIndices().Except(new []{ ActorComponentsLookup.Backup }).ToArray()); //Copy everything except the backup-component } + /* * ====================== Revert game-entities ====================== */ var currentEntities = _gameContext.GetEntities(GameMatcher.LocalId); - var 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)); + var backupEntities = _gameContext.GetEntities(GameMatcher.Backup).Where(e => e.backup.tick == resultTick).ToList(); + var backupEntityIds = backupEntities.Select(entity => entity.backup.localEntityId); + + //Entities that were created in the prediction have to be destroyed + var invalidEntities = currentEntities.Where(entity => !backupEntityIds.Contains(entity.localId.value)).ToList(); foreach (var invalidEntity in invalidEntities) { //Here we have the actual entities, we can safely refer to them via the internal id _view.DeleteView(invalidEntity.localId.value); - _gameContext.GetEntityWithLocalId(invalidEntity.localId.value).Destroy(); + invalidEntity.Destroy(); + } + + foreach (var invalidBackupEntity in _gameContext.GetEntities(GameMatcher.Backup).Where(e => e.backup.tick > resultTick)) + { + Services.Get().RemoveIndex(invalidBackupEntity.backup.tick); + invalidBackupEntity.Destroy(); } + //Copy old state to the entity + foreach (var backupEntity in backupEntities) + { + var target = _gameContext.GetEntityWithLocalId(backupEntity.backup.localEntityId); + var additionalComponentIndices = target.GetComponentIndices().Except( + backupEntity + .GetComponentIndices() + .Except(new[] {GameComponentsLookup.Backup}) + .Concat(new[] {GameComponentsLookup.Id, GameComponentsLookup.ActorId, GameComponentsLookup.LocalId})) + ; + foreach (var index in additionalComponentIndices) + { + target.RemoveComponent(index); + } + + backupEntity.CopyTo(target, true, backupEntity.GetComponentIndices().Except(new []{GameComponentsLookup.Backup}).ToArray()); + + + if (!Services.Get().Validate(resultTick, backupEntity.backup.localEntityId, target.position.value)) + { + throw new Exception(); + } + } + //TODO: restore locally destroyed entities - - //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(resultTick); + Contexts.gameState.ReplaceTick(resultTick); } } } \ No newline at end of file diff --git a/Engine/Dependencies/BEPUutilities.dll b/Engine/Dependencies/BEPUutilities.dll index ef002e3..5805c07 100644 Binary files a/Engine/Dependencies/BEPUutilities.dll and b/Engine/Dependencies/BEPUutilities.dll differ diff --git a/Engine/Dependencies/BEPUutilities.pdb b/Engine/Dependencies/BEPUutilities.pdb index ae6365c..7393618 100644 Binary files a/Engine/Dependencies/BEPUutilities.pdb and b/Engine/Dependencies/BEPUutilities.pdb differ diff --git a/Engine/Dependencies/FixMath.NET.dll b/Engine/Dependencies/FixMath.NET.dll index 13aafbd..c01fa24 100644 Binary files a/Engine/Dependencies/FixMath.NET.dll and b/Engine/Dependencies/FixMath.NET.dll differ diff --git a/Engine/Dependencies/FixMath.NET.pdb b/Engine/Dependencies/FixMath.NET.pdb index a80fa73..961310d 100644 Binary files a/Engine/Dependencies/FixMath.NET.pdb and b/Engine/Dependencies/FixMath.NET.pdb differ diff --git a/Engine/Test/Converter.cs b/Engine/Test/Converter.cs index bdc85d9..01c612a 100644 --- a/Engine/Test/Converter.cs +++ b/Engine/Test/Converter.cs @@ -37,5 +37,10 @@ public void Warn(object message) { _outputHelper.WriteLine($"Warn: {message}"); } + + public void Trace(object message) + { + _outputHelper.WriteLine($"Trace: {message}"); + } } } \ No newline at end of file diff --git a/Engine/Test/DumpTests.cs b/Engine/Test/DumpTests.cs new file mode 100644 index 0000000..9c2618b --- /dev/null +++ b/Engine/Test/DumpTests.cs @@ -0,0 +1,169 @@ +using System; +using System.IO; +using System.Linq; +using System.Net.WebSockets; +using System.Reflection; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using Lockstep.Client; +using Lockstep.Client.Commands; +using Lockstep.Client.Implementations; +using Lockstep.Core; +using Lockstep.Core.Interfaces; +using Lockstep.Network.Messages; +using Lockstep.Network.Utils; +using Shouldly; +using Xunit; +using Xunit.Abstractions; + +namespace Test +{ + public class GettingSerious + { + private readonly ITestOutputHelper _output; + + public GettingSerious(ITestOutputHelper output) + { + _output = output; + Console.SetOut(new Converter(output)); + } + + [Fact] + public void TestDump1() + { + TestDump("2_-535026177646_log"); + } + + [Fact] + public void TestDump2() + { + TestDump("37_-546443594864_log"); + } + + private void TestDump(string fileName) + { + var contexts = new Contexts(); + var systems = new World(contexts, new TestLogger(_output)); + var commandBuffer = new CommandBuffer(); + + var codeBaseUrl = new Uri(Assembly.GetExecutingAssembly().CodeBase); + var codeBasePath = Uri.UnescapeDataString(codeBaseUrl.AbsolutePath); + var dirPath = Path.GetDirectoryName(codeBasePath); + + var data = ReadFile($@"{dirPath}\Dumps\{fileName}.txt"); + var deserializer = new Deserializer(data); + var hashCode = deserializer.GetLong(); + var tick = deserializer.GetUInt(); + var localActorId = deserializer.GetByte(); + var allActors = deserializer.GetBytesWithLength(); + + IFormatter formatter = new BinaryFormatter(); + GameLog log; + using (var stream = new MemoryStream(deserializer.GetRemainingBytes())) + { + log = (GameLog) formatter.Deserialize(stream); + } + + var sim = new Simulation(systems, commandBuffer) {LagCompensation = 0, SendCommandsToBuffer = false}; + sim.Initialize(new Init {TargetFPS = 1000, AllActors = allActors, ActorID = localActorId}); + + for (uint i = 0; i < tick; i++) + { + if (log.Log.ContainsKey(i)) + { + var tickCommands = log.Log[i]; + { + foreach (var (tickId, allCommands) in tickCommands) + { + foreach (var (actorId, commands) in allCommands) + { + if (actorId == localActorId) + { + _output.WriteLine("Local: " + commands.Count + " commands"); + + systems.AddInput(tickId, actorId, commands); + } + else + { + commandBuffer.Insert(tickId, actorId, commands.ToArray()); + } + } + } + } + } + + sim.Update(1); + } + + contexts.gameState.hashCode.value.ShouldBe(hashCode); + commandBuffer.Buffer.ShouldBeEmpty(); + + + contexts.Reset(); + var debug = systems.Services.Get(); + systems = new World(contexts, new TestLogger(_output)); + sim = new Simulation(systems, commandBuffer) { LagCompensation = 0, SendCommandsToBuffer = false }; + sim.Initialize(new Init { TargetFPS = 1000, AllActors = allActors, ActorID = localActorId }); + + foreach (var (occurTickId, tickCommands) in log.Log) + { + foreach (var (tickId, allCommands) in tickCommands) + { + foreach (var (actorId, commands) in allCommands) + { + if (commands.Any(command => command is NavigateCommand)) + { + } + if (actorId == localActorId) + { + _output.WriteLine("Local: " + commands.Count + " commands"); + + systems.AddInput(tickId, actorId, commands); + } + else + { + commandBuffer.Insert(tickId, actorId, commands.ToArray()); + } + } + } + } + + var debug2 = systems.Services.Get(); + debug.ShouldNotBeSameAs(debug2); + + for (uint i = 0; i < tick; i++) + { + sim.Update(1); + if (debug.HasHash(systems.CurrentTick)) + { + debug.GetHash(systems.CurrentTick).ShouldBe(contexts.gameState.hashCode.value); + } + } + + contexts.gameState.hashCode.value.ShouldBe(hashCode); + } + + + public static byte[] ReadFile(string filePath) + { + byte[] buffer; + FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); + try + { + int length = (int)fileStream.Length; // get file length + buffer = new byte[length]; // create buffer + int count; // actual number of bytes read + int sum = 0; // total number of bytes read + + // read until Read method returns 0 (end of the stream has been reached) + while ((count = fileStream.Read(buffer, sum, length - sum)) > 0) + sum += count; // sum is a buffer offset for next reading + } + finally + { + fileStream.Close(); + } + return buffer; + } + } +} diff --git a/Engine/Test/Dumps/2_-535026177646_log.txt b/Engine/Test/Dumps/2_-535026177646_log.txt new file mode 100644 index 0000000..f4a465a Binary files /dev/null and b/Engine/Test/Dumps/2_-535026177646_log.txt differ diff --git a/Engine/Test/Dumps/3_-533374750893_log.txt b/Engine/Test/Dumps/3_-533374750893_log.txt new file mode 100644 index 0000000..d75d4e7 Binary files /dev/null and b/Engine/Test/Dumps/3_-533374750893_log.txt differ diff --git a/Engine/Test/InputTests.cs b/Engine/Test/InputTests.cs index c7a644d..a9193c6 100644 --- a/Engine/Test/InputTests.cs +++ b/Engine/Test/InputTests.cs @@ -22,25 +22,7 @@ public InputParseTest(ITestOutputHelper output) { _output = output; Console.SetOut(new Converter(output)); - } - - [Fact] - public void TestGameEntityHasUniqueId() - { - - var contexts = new Contexts(); - - const int numEntities = 10; - - for (uint i = 0; i < numEntities; i++) - { - contexts.game.CreateEntity(); - } - - contexts.game.count.ShouldBe(numEntities); - contexts.game.GetEntities().Select(entity => entity.hasId).ShouldAllBe(b => true); - contexts.game.GetEntities().Select(entity => entity.id.value).ShouldBeUnique(); - } + } [Fact] public void TestCreateEntityRollbackLocal() @@ -52,89 +34,72 @@ public void TestCreateEntityRollbackLocal() 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 }); systems.CurrentTick.ShouldBe((uint)0); sim.Update(1000); //0 systems.CurrentTick.ShouldBe((uint)1); - for (int i = 0; i < 1; i++) - { - sim.Execute(new Spawn()); - } + + sim.Execute(new Spawn()); sim.Update(1000); //1 systems.CurrentTick.ShouldBe((uint)2); ExpectEntityCount(contexts, 1); - ExpectBackupCount(contexts, 1); + ExpectBackupCount(contexts, 0); sim.Update(1000); //2 systems.CurrentTick.ShouldBe((uint)3); ExpectEntityCount(contexts, 1); - ExpectBackupCount(contexts, 1); + ExpectBackupCount(contexts, 0); commandBuffer.Insert(1, 1, new ICommand[] { }); sim.Update(1000); //3 systems.CurrentTick.ShouldBe((uint)4); ExpectEntityCount(contexts, 1); - ExpectBackupCount(contexts, 1); - - - commandBuffer.Insert(2, 1, new ICommand[] { new MoveAll(contexts.game), }); + ExpectBackupCount(contexts, 1); + commandBuffer.Insert(2, 1, new ICommand[] { new MoveAll(contexts.game, 1) }); 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 Spawn()); - } + sim.Execute(new Spawn()); sim.Update(1000); //5 systems.CurrentTick.ShouldBe((uint)6); ExpectEntityCount(contexts, 2); - ExpectBackupCount(contexts, 3); + ExpectBackupCount(contexts, 2); - - for (int i = 0; i < 1; i++) - { - sim.Execute(new Spawn()); - } + sim.Execute(new Spawn()); sim.Update(1000); //6 ExpectEntityCount(contexts, 3); - ExpectBackupCount(contexts, 4); + ExpectBackupCount(contexts, 2); commandBuffer.Insert(3, 1, new ICommand[] { }); //Revert to 3 sim.Update(1000); ExpectEntityCount(contexts, 3); - ExpectBackupCount(contexts, 4); + ExpectBackupCount(contexts, 3); sim.Update(1000); commandBuffer.Insert(4, 1, new ICommand[] { }); - for (int i = 0; i < 1; i++) - { - sim.Execute(new Spawn()); - } + sim.Execute(new Spawn()); + sim.Update(1000); ExpectEntityCount(contexts, 4); - ExpectBackupCount(contexts, 5); + ExpectBackupCount(contexts, 4); sim.Update(1000); sim.Update(1000); - for (int i = 0; i < 1; i++) - { - commandBuffer.Insert(5, 1, new ICommand[] { new Spawn() }); - } - sim.Update(1000); + commandBuffer.Insert(5, 1, new ICommand[] { new Spawn() }); + sim.Update(1000); ExpectEntityCount(contexts, 5); } @@ -258,7 +223,7 @@ public void TestEntityRollbackWithLocalChanges() GameEntityCountMatchesActorEntityCount(contexts, 0, 1); GameEntityCountMatchesActorEntityCount(contexts, 1, 0); - commandBuffer.Insert(2, 1, new ICommand[] { new MoveAll(contexts.game), }); + commandBuffer.Insert(2, 1, new ICommand[] { new MoveAll(contexts.game, 1) }); sim.Update(1000); //4 systems.CurrentTick.ShouldBe(frameCounter++); @@ -271,7 +236,7 @@ public void TestEntityRollbackWithLocalChanges() ExpectEntityCount(contexts, 1); ExpectBackupCount(contexts, 1); - sim.Execute(new MoveEntitesOfSpecificActor(contexts.game, 0)); + sim.Execute(new MoveEntitesOfSpecificActor(contexts.game, 0, Vector2.Zero)); sim.Update(1000); //6 systems.CurrentTick.ShouldBe(frameCounter++); @@ -285,7 +250,7 @@ public void TestEntityRollbackWithLocalChanges() sim.Update(1000); //7 systems.CurrentTick.ShouldBe(frameCounter++); ExpectEntityCount(contexts, 2); - ExpectBackupCount(contexts, 2); + ExpectBackupCount(contexts, 3); GameEntityCountMatchesActorEntityCount(contexts, 0, 1); GameEntityCountMatchesActorEntityCount(contexts, 1, 1); @@ -294,7 +259,7 @@ public void TestEntityRollbackWithLocalChanges() sim.Update(1000); //8 systems.CurrentTick.ShouldBe(frameCounter++); ExpectEntityCount(contexts, 3); - ExpectBackupCount(contexts, 4); + ExpectBackupCount(contexts, 6); GameEntityCountMatchesActorEntityCount(contexts, 0, 1); GameEntityCountMatchesActorEntityCount(contexts, 1, 2); @@ -303,7 +268,7 @@ public void TestEntityRollbackWithLocalChanges() sim.Update(1000); systems.CurrentTick.ShouldBe(frameCounter++); ExpectEntityCount(contexts, 9); - ExpectBackupCount(contexts, 7); + ExpectBackupCount(contexts, 15); GameEntityCountMatchesActorEntityCount(contexts, 0, 1); GameEntityCountMatchesActorEntityCount(contexts, 1, 8); @@ -315,22 +280,259 @@ public void TestEntityRollbackWithLocalChanges() systems.CurrentTick.ShouldBe(frameCounter++); sim.Execute(new Spawn()); - commandBuffer.Insert(6, 1, new ICommand[] { new MoveEntitesOfSpecificActor(contexts.game, 1), }); + commandBuffer.Insert(6, 1, new ICommand[] { new MoveEntitesOfSpecificActor(contexts.game, 1, Vector2.Zero) }); sim.Update(1000); ExpectEntityCount(contexts, 10); - ExpectBackupCount(contexts, 16); + ExpectBackupCount(contexts, 24); GameEntityCountMatchesActorEntityCount(contexts, 0, 2); GameEntityCountMatchesActorEntityCount(contexts, 1, 8); sim.Execute(new Spawn()); - commandBuffer.Insert(11, 1, new ICommand[] { new MoveEntitesOfSpecificActor(contexts.game, 1), }); + commandBuffer.Insert(11, 1, new ICommand[] { new MoveEntitesOfSpecificActor(contexts.game, 1, new Vector2(3,4)) }); sim.Update(1000); ExpectEntityCount(contexts, 11); - ExpectBackupCount(contexts, 25); + ExpectBackupCount(contexts, 33); GameEntityCountMatchesActorEntityCount(contexts, 0, 3); GameEntityCountMatchesActorEntityCount(contexts, 1, 8); + + _output.WriteLine("========================================"); + + var input = systems.GameLog.Log; + var finalTick = systems.CurrentTick; + var finalHash = contexts.gameState.hashCode.value; + + commandBuffer.Buffer.ShouldBeEmpty(); + var debug = systems.Services.Get(); + + contexts.Reset(); + systems = new World(contexts, new TestLogger(_output)); + sim = new Simulation(systems, commandBuffer) { LagCompensation = 0, SendCommandsToBuffer = false }; + sim.Initialize(new Init { TargetFPS = 1, AllActors = new byte[] { 0, 1 }, ActorID = 0 }); + + foreach (var (occurTickId, tickCommands) in input) + { + foreach (var (tickId, allCommands) in tickCommands) + { + foreach (var (actorId, commands) in allCommands) + { + if (actorId == 0) + { + _output.WriteLine("Local: " + commands.Count + " commands"); + + systems.AddInput(tickId, actorId, commands); + } + else + { + commandBuffer.Insert(tickId, actorId, commands.ToArray()); + } + } + } + } + + while (systems.CurrentTick < finalTick) + { + sim.Update(1); + if (debug.HasHash(systems.CurrentTick)) + { + debug.GetHash(systems.CurrentTick).ShouldBe(contexts.gameState.hashCode.value); + } + } + + contexts.gameState.hashCode.value.ShouldBe(finalHash); + } + [Fact] + public void TestGameLogReplay() + { + 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 }); + + sim.Update(1000); //0 + + sim.Execute(new Spawn()); + + sim.Update(1000); //1 + sim.Update(1000); //2 + sim.Update(1000); //3 + + commandBuffer.Insert(2, 1, new ICommand[] { new MoveAll(contexts.game, 1) }); + + sim.Update(1000); //4 + sim.Update(1000); //5 + + sim.Execute(new MoveEntitesOfSpecificActor(contexts.game, 0, Vector2.Zero)); + + sim.Update(1000); //6 + + commandBuffer.Insert(3, 1, new ICommand[] { new Spawn() }); //Revert to 3 + + sim.Update(1000); //7 + + commandBuffer.Insert(4, 1, new ICommand[] { new Spawn() }); //Revert to 4 + + sim.Update(1000); //8 + + commandBuffer.Insert(5, 1, new ICommand[] { new Spawn(), new Spawn(), new Spawn(), new Spawn(), new Spawn(), new Spawn(), new Spawn() }); //Revert to 5 + + sim.Update(1000); + sim.Update(1000); + sim.Update(1000); + sim.Update(1000); + + sim.Execute(new Spawn()); + sim.Execute(new MoveAll(contexts.game, 0)); + commandBuffer.Insert(6, 1, new ICommand[] { new MoveEntitesOfSpecificActor(contexts.game, 1, Vector2.Zero) }); + + sim.Update(1000); + + sim.Execute(new Spawn()); + commandBuffer.Insert(11, 1, new ICommand[] { new MoveEntitesOfSpecificActor(contexts.game, 1, new Vector2(3, 4)) }); + sim.Update(1000); + + _output.WriteLine("========================================"); + + var input = systems.GameLog.Log; + var finalTick = systems.CurrentTick; + var finalHash = contexts.gameState.hashCode.value; + + commandBuffer.Buffer.ShouldBeEmpty(); + var debug = systems.Services.Get(); + + contexts.Reset(); + systems = new World(contexts, new TestLogger(_output)); + sim = new Simulation(systems, commandBuffer) { LagCompensation = 0, SendCommandsToBuffer = false }; + sim.Initialize(new Init { TargetFPS = 1, AllActors = new byte[] { 0, 1 }, ActorID = 0 }); + + foreach (var (occurTickId, tickCommands) in input) + { + foreach (var (tickId, allCommands) in tickCommands) + { + foreach (var (actorId, commands) in allCommands) + { + if (actorId == 0) + { + _output.WriteLine("Local: " + commands.Count + " commands"); + + systems.AddInput(tickId, actorId, commands); + } + else + { + commandBuffer.Insert(tickId, actorId, commands.ToArray()); + } + } + } + } + + while (systems.CurrentTick < finalTick) + { + sim.Update(1); + if (debug.HasHash(systems.CurrentTick)) + { + debug.GetHash(systems.CurrentTick).ShouldBe(contexts.gameState.hashCode.value); + } + } + + contexts.gameState.hashCode.value.ShouldBe(finalHash); + } + [Fact] + //This test requires a navigation-service that just sets the position to destination: entity.ReplacePosition(entity.destination.value); + public void TestCommandUsesCorrectEntityIds() + { + 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 }); + + + sim.Update(1000); + sim.Update(1000); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Update(1000); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Update(1000); + + var selection = new uint[] { 0, 1, 3, 13 }; + var destination = new Vector2(14 , 15); + + commandBuffer.Insert(1, 1, new ICommand[] { + new Spawn(), + new Spawn(), + new Spawn(), + new Spawn(), + new Spawn(), + new Spawn(), + new Spawn(), + new Spawn(), + new Spawn(), + new Spawn() }); + + commandBuffer.Insert(8, 1, new ICommand[] { + new Spawn(), + new Spawn(), + new Spawn(), + new Spawn(), + new Spawn(), + new Spawn(), + new Spawn(), + new Spawn() }); + commandBuffer.Insert(9, 1, new ICommand[] + { + new MoveSelection(selection, destination) + }); + + sim.Update(1000); + sim.Update(1000); + sim.Update(1000); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Execute(new Spawn()); + sim.Update(1000); + + sim.Execute(new MoveSelection(new uint[]{1,4,2,8,3}, destination)); + sim.Update(1000); + sim.Update(1000); + sim.Update(1000); + sim.Update(1000); + contexts.game.GetEntities(GameMatcher.Id).Where(entity => entity.actorId.value == 1).Select(entity => entity.id.value).ShouldBeUnique(); + contexts.game.GetEntities(GameMatcher.Id) + .Where(entity => entity.actorId.value == 1 && selection.Contains(entity.id.value)) + .Select(entity => entity.position.value).ShouldAllBe(vector2 => vector2.X == destination.X && vector2.Y == destination.Y); + + destination = new Vector2(5, 15); + commandBuffer.Insert(10, 1, new ICommand[] + { + new MoveSelection(selection, destination) + }); + + sim.Update(1000); + contexts.game.GetEntities(GameMatcher.Id).Where(entity => entity.actorId.value == 1).Select(entity => entity.id.value).ShouldBeUnique(); + contexts.game.GetEntities(GameMatcher.Id) + .Where(entity => entity.actorId.value == 1 && selection.Contains(entity.id.value)) + .Select(entity => entity.position.value).ShouldAllBe(vector2 => vector2.X == destination.X && vector2.Y == destination.Y); } [Fact] @@ -382,44 +584,70 @@ public void Execute(InputEntity e) } } - //Hacky commands, don't do this in production. Commands should only modify the given input-entity public class MoveAll : ICommand - { + { private readonly GameContext _contexts; + private readonly byte _actorId; + private uint[] selection; - public MoveAll(GameContext contexts) + public MoveAll(GameContext contexts, byte actorId) { _contexts = contexts; + _actorId = actorId; + selection = _contexts.GetEntities(GameMatcher.LocalId).Where(entity => entity.actorId.value == _actorId) + .Select(entity => entity.id.value).ToArray(); } public void Execute(InputEntity e) { - foreach (var gameEntity in _contexts.GetEntities(GameMatcher.LocalId)) - { - gameEntity.ReplacePosition(new Vector2(2, 2)); - } - } + e.AddSelection(selection); + e.AddCoordinate(new Vector2(2, 2)); + } } + public class MoveEntitesOfSpecificActor : ICommand { private readonly GameContext _contexts; private readonly byte _actorId; + private readonly Vector2 _dest; + private uint[] selection; - public MoveEntitesOfSpecificActor(GameContext contexts, byte actorId) + public MoveEntitesOfSpecificActor(GameContext contexts, byte actorId, Vector2 dest) { _contexts = contexts; _actorId = actorId; + _dest = dest; + + selection = _contexts.GetEntities(GameMatcher.LocalId).Where(entity => entity.actorId.value == _actorId).Select(entity => entity.id.value).ToArray(); } 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)); - } + e.AddSelection(selection); + e.AddCoordinate(_dest); + e.AddTargetActorId(_actorId); + + } + } + + public class MoveSelection : ICommand + { + private readonly uint[] _selection; + private readonly Vector2 _destination; + + public MoveSelection(uint[] selection, Vector2 destination) + { + _selection = selection; + _destination = destination; } + public void Execute(InputEntity e) + { + e.AddSelection(_selection); + e.AddCoordinate(_destination); + } } } + } diff --git a/Engine/Test/Test.csproj b/Engine/Test/Test.csproj index 78e5889..9f8db19 100644 --- a/Engine/Test/Test.csproj +++ b/Engine/Test/Test.csproj @@ -34,4 +34,13 @@ + + + PreserveNewest + + + PreserveNewest + + + diff --git a/Unity/Assets/Integration/BEPUutilities.dll b/Unity/Assets/Integration/BEPUutilities.dll index ef002e3..5805c07 100644 Binary files a/Unity/Assets/Integration/BEPUutilities.dll and b/Unity/Assets/Integration/BEPUutilities.dll differ diff --git a/Unity/Assets/Integration/BEPUutilities.dll.mdb b/Unity/Assets/Integration/BEPUutilities.dll.mdb new file mode 100644 index 0000000..6c53d1f Binary files /dev/null and b/Unity/Assets/Integration/BEPUutilities.dll.mdb differ diff --git a/Unity/Assets/Integration/Lockstep.Core.dll.mdb.meta b/Unity/Assets/Integration/BEPUutilities.dll.mdb.meta similarity index 74% rename from Unity/Assets/Integration/Lockstep.Core.dll.mdb.meta rename to Unity/Assets/Integration/BEPUutilities.dll.mdb.meta index ed189eb..c07c2c9 100644 --- a/Unity/Assets/Integration/Lockstep.Core.dll.mdb.meta +++ b/Unity/Assets/Integration/BEPUutilities.dll.mdb.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: e5525b9d03bec99428536dbc44bc43c5 +guid: 519cd96ebdb47f54c94f3ee4617e170d DefaultImporter: externalObjects: {} userData: diff --git a/Unity/Assets/Integration/FixMath.NET.dll b/Unity/Assets/Integration/FixMath.NET.dll index 13aafbd..c01fa24 100644 Binary files a/Unity/Assets/Integration/FixMath.NET.dll and b/Unity/Assets/Integration/FixMath.NET.dll differ diff --git a/Unity/Assets/Integration/FixMath.NET.dll.mdb b/Unity/Assets/Integration/FixMath.NET.dll.mdb new file mode 100644 index 0000000..1308cfd Binary files /dev/null and b/Unity/Assets/Integration/FixMath.NET.dll.mdb differ diff --git a/Unity/Assets/Scripts/Commands.meta b/Unity/Assets/Integration/FixMath.NET.dll.mdb.meta similarity index 67% rename from Unity/Assets/Scripts/Commands.meta rename to Unity/Assets/Integration/FixMath.NET.dll.mdb.meta index d72543e..5efcfd9 100644 --- a/Unity/Assets/Scripts/Commands.meta +++ b/Unity/Assets/Integration/FixMath.NET.dll.mdb.meta @@ -1,6 +1,5 @@ fileFormatVersion: 2 -guid: 1ac998c1e1661fd42af4b3bda201f9f8 -folderAsset: yes +guid: 8bb751eecd3ab7a47aa22761b49051fc DefaultImporter: externalObjects: {} userData: diff --git a/Unity/Assets/Integration/Lockstep.Client.deps.json b/Unity/Assets/Integration/Lockstep.Client.deps.json index 4cfc5a2..c3a95ac 100644 --- a/Unity/Assets/Integration/Lockstep.Client.deps.json +++ b/Unity/Assets/Integration/Lockstep.Client.deps.json @@ -11,7 +11,10 @@ "dependencies": { "Core": "1.0.0", "Lockstep.Network": "1.0.0", - "NETStandard.Library": "2.0.3" + "NETStandard.Library": "2.0.3", + "BEPUutilities": "1.5.0.0", + "Entitas": "0.0.0.0", + "FixMath.NET": "1.0.0.0" }, "runtime": { "Lockstep.Client.dll": {} @@ -25,9 +28,6 @@ }, "Core/1.0.0": { "dependencies": { - "Entitas": "0.0.0.0", - "BEPUutilities": "1.5.0.0", - "FixMath.NET": "1.0.0.0", "Entitas.CodeGeneration.Attributes": "0.0.0.0", "DesperateDevs.Utils": "0.0.0.0" }, @@ -40,23 +40,23 @@ "Lockstep.Network.dll": {} } }, - "Entitas.Reference/0.0.0.0": { + "BEPUutilities/1.5.0.0": { "runtime": { - "Entitas.dll": { - "assemblyVersion": "0.0.0.0", + "BEPUutilities.dll": { + "assemblyVersion": "1.5.0.0", "fileVersion": "0.0.0.0" } } }, - "BEPUutilities.Reference/1.5.0.0": { + "Entitas/0.0.0.0": { "runtime": { - "BEPUutilities.dll": { - "assemblyVersion": "1.5.0.0", + "Entitas.dll": { + "assemblyVersion": "0.0.0.0", "fileVersion": "0.0.0.0" } } }, - "FixMath.NET.Reference/1.0.0.0": { + "FixMath.NET/1.0.0.0": { "runtime": { "FixMath.NET.dll": { "assemblyVersion": "1.0.0.0", @@ -112,17 +112,17 @@ "serviceable": false, "sha512": "" }, - "Entitas.Reference/0.0.0.0": { + "BEPUutilities/1.5.0.0": { "type": "reference", "serviceable": false, "sha512": "" }, - "BEPUutilities.Reference/1.5.0.0": { + "Entitas/0.0.0.0": { "type": "reference", "serviceable": false, "sha512": "" }, - "FixMath.NET.Reference/1.0.0.0": { + "FixMath.NET/1.0.0.0": { "type": "reference", "serviceable": false, "sha512": "" diff --git a/Unity/Assets/Integration/Lockstep.Client.deps.json.meta b/Unity/Assets/Integration/Lockstep.Client.deps.json.meta index d3bf87f..3e61fc2 100644 --- a/Unity/Assets/Integration/Lockstep.Client.deps.json.meta +++ b/Unity/Assets/Integration/Lockstep.Client.deps.json.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: f0f6a6a10e51e01458ac0f2b82f6e87d +guid: 92438ca6807eda74c946d800dd0772c3 TextScriptImporter: externalObjects: {} userData: diff --git a/Unity/Assets/Integration/Lockstep.Client.dll b/Unity/Assets/Integration/Lockstep.Client.dll index 0d9db6b..820ae1d 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.Client.dll.meta b/Unity/Assets/Integration/Lockstep.Client.dll.meta index b1b4dc1..bf9a493 100644 --- a/Unity/Assets/Integration/Lockstep.Client.dll.meta +++ b/Unity/Assets/Integration/Lockstep.Client.dll.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ca1d17b043bd96b45b16484310e0c175 +guid: b590b5c1953ff4a498947c8e1b6077e9 PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/Unity/Assets/Integration/Lockstep.Client.pdb.meta b/Unity/Assets/Integration/Lockstep.Client.pdb.meta index 88d0bdd..398ce19 100644 --- a/Unity/Assets/Integration/Lockstep.Client.pdb.meta +++ b/Unity/Assets/Integration/Lockstep.Client.pdb.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a181bebce9a5a474eb3f809f4fd8ffa8 +guid: 424d8ccc96e61164ca327661360064b2 DefaultImporter: externalObjects: {} userData: diff --git a/Unity/Assets/Integration/Lockstep.Core.dll b/Unity/Assets/Integration/Lockstep.Core.dll index 271b9fa..c7bc3fa 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 9aebcf4..b7acf1c 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.Core.dll.meta b/Unity/Assets/Integration/Lockstep.Core.dll.meta index 6fa39c8..5d3391a 100644 --- a/Unity/Assets/Integration/Lockstep.Core.dll.meta +++ b/Unity/Assets/Integration/Lockstep.Core.dll.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 8e72ba2a3ed579a488597effe3f8e208 +guid: b1b3238031e73d448af7f633056a804a PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/Unity/Assets/Integration/Lockstep.Core.pdb.meta b/Unity/Assets/Integration/Lockstep.Core.pdb.meta index c03bdae..5c235f4 100644 --- a/Unity/Assets/Integration/Lockstep.Core.pdb.meta +++ b/Unity/Assets/Integration/Lockstep.Core.pdb.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: bd0d65c2573aa3d4ca4030c9de4c17fb +guid: 5fa3cd151dd86bf41ac3b64248cac38d DefaultImporter: externalObjects: {} userData: diff --git a/Unity/Assets/Scenes/SampleScene.unity b/Unity/Assets/Scenes/SampleScene.unity index 062f323..5ef91c2 100644 --- a/Unity/Assets/Scenes/SampleScene.unity +++ b/Unity/Assets/Scenes/SampleScene.unity @@ -372,6 +372,7 @@ RectTransform: - {fileID: 1549690252} - {fileID: 855587569} - {fileID: 1923207267} + - {fileID: 904096880} m_Father: {fileID: 0} m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} @@ -612,6 +613,214 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 855587568} m_CullTransparentMesh: 0 +--- !u!1 &904096879 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 904096880} + - component: {fileID: 904096883} + - component: {fileID: 904096882} + - component: {fileID: 904096881} + m_Layer: 5 + m_Name: Log input to file + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &904096880 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 904096879} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1416586627} + m_Father: {fileID: 97464393} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 209, y: 182} + m_SizeDelta: {x: 200, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &904096881 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 904096879} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1392445389, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 904096882} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1524441391} + m_MethodName: DumpInputContext + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 + m_TypeName: UnityEngine.UI.Button+ButtonClickedEvent, UnityEngine.UI, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null +--- !u!114 &904096882 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 904096879} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 +--- !u!222 &904096883 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 904096879} + m_CullTransparentMesh: 0 +--- !u!1 &1416586626 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1416586627} + - component: {fileID: 1416586629} + - component: {fileID: 1416586628} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1416586627 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1416586626} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 904096880} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1416586628 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1416586626} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Dump InputContext to file +--- !u!222 &1416586629 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1416586626} + m_CullTransparentMesh: 0 --- !u!1 &1524441390 GameObject: m_ObjectHideFlags: 0 diff --git a/Unity/Assets/Scripts/Commands/CommandTag.cs b/Unity/Assets/Scripts/Commands/CommandTag.cs deleted file mode 100644 index 6e05086..0000000 --- a/Unity/Assets/Scripts/Commands/CommandTag.cs +++ /dev/null @@ -1,5 +0,0 @@ -public enum CommandTag : ushort -{ - Spawn, - Navigate -} diff --git a/Unity/Assets/Scripts/Commands/CommandTag.cs.meta b/Unity/Assets/Scripts/Commands/CommandTag.cs.meta deleted file mode 100644 index 63232bc..0000000 --- a/Unity/Assets/Scripts/Commands/CommandTag.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e4223b766c0ceb143b394e520d8c33dc -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Unity/Assets/Scripts/Commands/NavigateCommand.cs.meta b/Unity/Assets/Scripts/Commands/NavigateCommand.cs.meta deleted file mode 100644 index 437f104..0000000 --- a/Unity/Assets/Scripts/Commands/NavigateCommand.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: efee9af8b5ab4be4fa819c2fea33a97d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Unity/Assets/Scripts/Commands/SpawnCommand.cs.meta b/Unity/Assets/Scripts/Commands/SpawnCommand.cs.meta deleted file mode 100644 index d1dca46..0000000 --- a/Unity/Assets/Scripts/Commands/SpawnCommand.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d1aa00ba0d55f9f47a91de3c76383b40 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Unity/Assets/Scripts/RTSEntitySpawner.cs b/Unity/Assets/Scripts/RTSEntitySpawner.cs index be54a2b..2ad4ca4 100644 --- a/Unity/Assets/Scripts/RTSEntitySpawner.cs +++ b/Unity/Assets/Scripts/RTSEntitySpawner.cs @@ -1,4 +1,4 @@ -using Lockstep.Commands; +using Lockstep.Client.Commands; using UnityEngine; using Vector2 = BEPUutilities.Vector2; diff --git a/Unity/Assets/Scripts/RTSNetworkedSimulation.cs b/Unity/Assets/Scripts/RTSNetworkedSimulation.cs index f18640e..845a1b7 100644 --- a/Unity/Assets/Scripts/RTSNetworkedSimulation.cs +++ b/Unity/Assets/Scripts/RTSNetworkedSimulation.cs @@ -1,11 +1,19 @@ -using System.Collections; + +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using Entitas; using Lockstep.Client; +using Lockstep.Client.Commands; using Lockstep.Client.Implementations; using Lockstep.Client.Interfaces; -using Lockstep.Commands; using Lockstep.Core; using Lockstep.Core.Interfaces; using Lockstep.Network.Messages; +using Lockstep.Network.Utils; using UnityEngine; public class RTSNetworkedSimulation : MonoBehaviour @@ -21,6 +29,9 @@ public class RTSNetworkedSimulation : MonoBehaviour public bool Connected => _client.Connected; + public byte PlayerId { get; private set; } + public byte[] AllActorIds { get; private set; } + private NetworkCommandBuffer _remoteCommandBuffer; private readonly LiteNetLibClient _client = new LiteNetLibClient(); @@ -45,7 +56,9 @@ private void Awake() } private void StartSimulation(Init data) - { + { + PlayerId = data.ActorID; + AllActorIds = data.AllActors; Debug.Log($"Starting simulation. Total actors: {data.AllActors.Length}. Local ActorID: {data.ActorID}"); Simulation.Initialize(data); @@ -53,6 +66,45 @@ private void StartSimulation(Init data) } + public void DumpInputContext() + { + var serializer = new Serializer(); + serializer.Put(Contexts.sharedInstance.gameState.hashCode.value); + serializer.Put(Contexts.sharedInstance.gameState.tick.value); + serializer.Put(PlayerId); + serializer.PutBytesWithLength(AllActorIds); + IFormatter formatter = new BinaryFormatter(); + + Stream stream = new FileStream(@"C:\Log\"+ PlayerId + "_"+ Contexts.sharedInstance.gameState.hashCode.value+"_log.txt", FileMode.Create, FileAccess.Write); + stream.Write(serializer.Data, 0, serializer.Length); + formatter.Serialize(stream, Systems.GameLog); + stream.Close(); + + + var data = new List(); + foreach (var entity in Contexts.sharedInstance.game.GetEntities(GameMatcher.AllOf(GameMatcher.Id, GameMatcher.ActorId)).OrderBy(entity => entity.actorId.value).ThenBy(entity => entity.id.value)) + { + data.Add(entity.actorId.value.ToString()); + data.Add(entity.id.value.ToString()); + data.Add(entity.position.value.ToString()); + data.Add("----------"); + } + File.WriteAllLines(@"C:\Log\" + PlayerId + "_" + Contexts.sharedInstance.gameState.hashCode.value + "_Ents.txt", data); + + + data = new List(); + foreach (var entity in Contexts.sharedInstance.input.GetEntities()) + { + data.Add(entity.actorId.value.ToString()); + data.Add(entity.tick.value.ToString()); + data.Add(entity.hasCoordinate.ToString()); + data.Add(entity.hasSelection.ToString()); + data.Add("----------"); + } + File.WriteAllLines(@"C:\Log\" + PlayerId + "_" + Contexts.sharedInstance.gameState.hashCode.value + "_Input.txt", data); + } + + public void Execute(ISerializableCommand command) { Simulation.Execute(command); @@ -67,8 +119,7 @@ private void Start() private void OnDestroy() { _client.Stop(); - } - + } void Update() { @@ -86,4 +137,4 @@ public IEnumerator AutoConnect() yield return null; } -} +} diff --git a/Unity/Assets/Scripts/UnityInput.cs b/Unity/Assets/Scripts/UnityInput.cs index 253d252..1b92a7e 100644 --- a/Unity/Assets/Scripts/UnityInput.cs +++ b/Unity/Assets/Scripts/UnityInput.cs @@ -1,7 +1,7 @@ using System.Linq; using Entitas; using FixMath.NET; -using Lockstep.Commands; +using Lockstep.Client.Commands; using UnityEngine; public class UnityInput : MonoBehaviour @@ -27,9 +27,14 @@ void Update() if (Input.GetKeyDown(KeyCode.X)) { - var e = Contexts.sharedInstance.game.GetEntities(GameMatcher.LocalId).Select(entity => entity.id.value).ToArray(); + var e = Contexts.sharedInstance.game + .GetEntities(GameMatcher.AllOf( + GameMatcher.Id, + GameMatcher.ActorId)) + .Where(entity => entity.actorId.value == RTSNetworkedSimulation.Instance.PlayerId) + .Select(entity => entity.id.value).ToArray(); - Debug.Log("Navigating: " + string.Join(", ", e)); + //Debug.Log("Navigating: " + string.Join(", ", e)); RTSNetworkedSimulation.Instance.Execute(new NavigateCommand { diff --git a/Unity/Assets/Scripts/UnityServices.cs b/Unity/Assets/Scripts/UnityServices.cs index a2bd81b..0bdd756 100644 --- a/Unity/Assets/Scripts/UnityServices.cs +++ b/Unity/Assets/Scripts/UnityServices.cs @@ -70,7 +70,12 @@ public void DeleteView(uint entityId) public class UnityLogger : ILogService { public void Warn(object message) - { + { Debug.LogWarning(message); } + + public void Trace(object message) + { + //Debug.Log(message); + } } \ No newline at end of file