Skip to content

Commit

Permalink
EntityStates (space-wizards#798)
Browse files Browse the repository at this point in the history
* Added a new special MetaDataComponent to store entity data that isn't specific to a component.

* Adds ExposeData to MetaDataComponent.
Name of the entity now defaults to the prototype name.

* EntityStates changed so that the list of networked components is not sent every tick for a modified entity.

* Code cleanup & doc comments.

* MetaDataComponent Name and Description are taken from the prototype before storing them in the component.

* Adds the EntityState serializer benchmark. This isn't a test, but is really useful.

* Redesigned the logic in ServerGameStateManager.

* Adds MetaDataComponent registration to the UnitTesting project.
  • Loading branch information
Acruid authored and PJB3005 committed Apr 26, 2019
1 parent dabc319 commit 5fe0bd6
Show file tree
Hide file tree
Showing 21 changed files with 586 additions and 238 deletions.
2 changes: 1 addition & 1 deletion Robust.Client/BaseClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ private void OnNetDisconnect(object sender, NetChannelArgs args)

_stateManager.RequestStateChange<MainScreen>();

_gameStates.Shutdown();
_gameStates.Reset();
_playMan.Shutdown();
_entityManager.Shutdown();
_mapManager.Shutdown();
Expand Down
10 changes: 8 additions & 2 deletions Robust.Client/GameObjects/ClientComponentFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,21 @@ public class ClientComponentFactory : ComponentFactory
{
public ClientComponentFactory()
{
// Required for the engine to work
Register<MetaDataComponent>();
RegisterReference<MetaDataComponent, IMetaDataComponent>();

// Required for the engine to work
Register<TransformComponent>();
RegisterReference<TransformComponent, ITransformComponent>();

Register<CollidableComponent>();
RegisterReference<CollidableComponent, ICollidable>();
RegisterReference<CollidableComponent, ICollidableComponent>();
Register<IconComponent>();
RegisterIgnore("KeyBindingInput");
Register<PointLightComponent>();
Register<PhysicsComponent>();
Register<TransformComponent>();
RegisterReference<TransformComponent, ITransformComponent>();

Register<InputComponent>();

Expand Down
9 changes: 5 additions & 4 deletions Robust.Client/GameObjects/ClientEntityManager.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Client.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
Expand Down Expand Up @@ -120,14 +121,14 @@ public void ApplyEntityStates(List<EntityState> curEntStates, IEnumerable<Entity
foreach (var es in curEntStates)
{
//Known entities
if (Entities.TryGetValue(es.StateData.Uid, out var entity))
if (Entities.TryGetValue(es.Uid, out var entity))
{
toApply.Add(entity, (es, null));
}
else //Unknown entities
{
var newEntity = CreateEntity(es.StateData.TemplateName, es.StateData.Uid);
newEntity.Name = es.StateData.Name;
var metaState = (MetaDataComponentState)es.ComponentStates.First(c => c.NetID == NetIDs.META_DATA);
var newEntity = CreateEntity(metaState.PrototypeId, es.Uid);
toApply.Add(newEntity, (es, null));
toInitialize.Add(newEntity);
}
Expand All @@ -138,7 +139,7 @@ public void ApplyEntityStates(List<EntityState> curEntStates, IEnumerable<Entity
{
foreach (var es in nextEntStates)
{
if (Entities.TryGetValue(es.StateData.Uid, out var entity))
if (Entities.TryGetValue(es.Uid, out var entity))
{
if (toApply.TryGetValue(entity, out var state))
{
Expand Down
211 changes: 108 additions & 103 deletions Robust.Client/GameStates/ClientGameStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,29 @@

namespace Robust.Client.GameStates
{
/// <inheritdoc />
public class ClientGameStateManager : IClientGameStateManager
{
private readonly List<GameState> _stateBuffer = new List<GameState>();
private GameState _lastFullState;
private bool _waitingForFull = true;
private bool _interpEnabled;
private int _interpRatio;
private bool _logging;

[Dependency]
private readonly IClientEntityManager _entities;
[Dependency]
private readonly IPlayerManager _players;
[Dependency]
private readonly IClientNetManager _network;
[Dependency]
private readonly IBaseClient _client;
[Dependency]
private readonly IMapManager _mapManager;
[Dependency]
private readonly IGameTiming _timing;
[Dependency]
private readonly IConfigurationManager _config;
[Dependency] private readonly IClientEntityManager _entities;
[Dependency] private readonly IPlayerManager _players;
[Dependency] private readonly IClientNetManager _network;
[Dependency] private readonly IBaseClient _client;
[Dependency] private readonly IMapManager _mapManager;
[Dependency] private readonly IGameTiming _timing;
[Dependency] private readonly IConfigurationManager _config;

/// <inheritdoc />
public int MinBufferSize => _interpEnabled ? 3 : 2;

/// <inheritdoc />
public int TargetBufferSize => MinBufferSize + _interpRatio;

/// <inheritdoc />
public void Initialize()
Expand All @@ -47,19 +49,24 @@ public void Initialize()
_client.RunLevelChanged += RunLevelChanged;

if(!_config.IsCVarRegistered("net.interp"))
_config.RegisterCVar("net.interp", false, CVar.ARCHIVE);
_config.RegisterCVar("net.interp", false, CVar.ARCHIVE, b => _interpEnabled = b);

if (!_config.IsCVarRegistered("net.interp_ratio"))
_config.RegisterCVar("net.interp_ratio", 0, CVar.ARCHIVE);
_config.RegisterCVar("net.interp_ratio", 0, CVar.ARCHIVE, i => _interpRatio = i < 0 ? 0 : i);

if (!_config.IsCVarRegistered("net.logging"))
_config.RegisterCVar("net.logging", false, CVar.ARCHIVE, b => _logging = b);

_interpEnabled = _config.GetCVar<bool>("net.interp");

_interpRatio = _config.GetCVar<int>("net.interp_ratio");
_interpRatio = _interpRatio < 0 ? 0 : _interpRatio; // min bound, < 0 makes no sense

_logging = _config.GetCVar<bool>("net.logging");
}

/// <inheritdoc />
public void Shutdown()
public void Reset()
{
_stateBuffer.Clear();
_lastFullState = null;
Expand Down Expand Up @@ -135,23 +142,10 @@ private void HandleStateMessage(MsgState message)
Logger.DebugS("net.state", $"Received New GameState: cTick={_timing.CurTick}, fSeq={state.FromSequence}, tSeq={state.ToSequence}, sz={message.MsgSize}, buf={_stateBuffer.Count}");
}

/// <inheritdoc />
public void ApplyGameState()
{
var doInterp = _config.GetCVar<bool>("net.interp");
var bufSz = _config.GetCVar<int>("net.interp_ratio");
bufSz = bufSz < 0 ? 0 : bufSz; // min bound, < 0 makes no sense

int targetBufferSize;
if(doInterp)
{
targetBufferSize = 3 + bufSz; // absolute minimum is 3 states in buffer for the system to work (last, cur, next)
}
else
{
targetBufferSize = 2 + bufSz; // only need to buffer last and cur
}

if (CalculateNextStates(_timing.CurTick, out var curState, out var nextState, targetBufferSize))
if (CalculateNextStates(_timing.CurTick, out var curState, out var nextState, TargetBufferSize))
{
if (_logging)
Logger.DebugS("net.state", $"Applying State: ext={curState.Extrapolated}, cTick={_timing.CurTick}, fSeq={curState.FromSequence}, tSeq={curState.ToSequence}, buf={_stateBuffer.Count}");
Expand All @@ -164,7 +158,7 @@ public void ApplyGameState()
// This will slightly speed up or slow down the client tickrate based on the contents of the buffer.
// CalcNextState should have just cleaned out any old states, so the buffer contains [t-1(last), t+0(cur), t+1(next), t+2, t+3, ..., t+n]
// we can use this info to properly time our tickrate so it does not run fast or slow compared to the server.
_timing.TickTimingAdjustment = (_stateBuffer.Count - (float) targetBufferSize) * 0.10f;
_timing.TickTimingAdjustment = (_stateBuffer.Count - (float) TargetBufferSize) * 0.10f;
}
else
{
Expand All @@ -174,110 +168,121 @@ public void ApplyGameState()

private bool CalculateNextStates(GameTick curTick, out GameState curState, out GameState nextState, int targetBufferSize)
{
var interpolate = targetBufferSize >= 3;

if (_waitingForFull)
{
if (_lastFullState != null)
{
if (_logging)
Logger.DebugS("net", $"Resync CurTick to: {_lastFullState.ToSequence}");
return CalculateFullState(out curState, out nextState, targetBufferSize);
}
else // this will be how almost all states are calculated
{
return CalculateDeltaState(curTick, out curState, out nextState);
}
}

curTick = _timing.CurTick = _lastFullState.ToSequence;
private bool CalculateFullState(out GameState curState, out GameState nextState, int targetBufferSize)
{
if (_lastFullState != null)
{
if (_logging)
Logger.DebugS("net", $"Resync CurTick to: {_lastFullState.ToSequence}");

if(interpolate)
{
// look for the next state
var lastTick = new GameTick(curTick.Value - 1);
var nextTick = new GameTick(curTick.Value + 1);
nextState = null;
var curTick = _timing.CurTick = _lastFullState.ToSequence;

if (_interpEnabled)
{
// look for the next state
var lastTick = new GameTick(curTick.Value - 1);
var nextTick = new GameTick(curTick.Value + 1);
nextState = null;

for (var i = 0; i < _stateBuffer.Count; i++)
for (var i = 0; i < _stateBuffer.Count; i++)
{
var state = _stateBuffer[i];
if (state.ToSequence == nextTick)
{
var state = _stateBuffer[i];
if (state.ToSequence == nextTick)
{
nextState = state;
}
else if (state.ToSequence < lastTick) // remove any old states we find to keep the buffer clean
{
_stateBuffer.RemoveSwap(i);
i--;
}
nextState = state;
}

// we let the buffer fill up before starting to tick
if (nextState != null && _stateBuffer.Count >= targetBufferSize)
else if (state.ToSequence < lastTick) // remove any old states we find to keep the buffer clean
{
curState = _lastFullState;
_waitingForFull = false;
return true;
_stateBuffer.RemoveSwap(i);
i--;
}
}
else

// we let the buffer fill up before starting to tick
if (nextState != null && _stateBuffer.Count >= targetBufferSize)
{
curState = _lastFullState;
nextState = default;
_waitingForFull = false;
return true;
}
}

// We just have to wait...
curState = default;
nextState = default;
return false;
else if (_stateBuffer.Count >= targetBufferSize)
{
curState = _lastFullState;
nextState = default;
_waitingForFull = false;
return true;
}
}
else // this will be how almost all states are calculated
{
var lastTick = new GameTick(curTick.Value - 1);
var nextTick = new GameTick(curTick.Value + 1);

curState = null;
nextState = null;
if (_logging)
Logger.DebugS("net", $"Have FullState, filling buffer... ({_stateBuffer.Count}/{targetBufferSize})");

for (var i = 0; i < _stateBuffer.Count; i++)
{
var state = _stateBuffer[i];
// waiting for full state or buffer to fill
curState = default;
nextState = default;
return false;
}

// remember there are no duplicate ToSequence states in the list.
if (state.ToSequence == curTick)
{
curState = state;
}
else if (interpolate && state.ToSequence == nextTick)
{
nextState = state;
}
else if(state.ToSequence < lastTick) // remove any old states we find to keep the buffer clean
{
_stateBuffer.RemoveSwap(i);
i--;
}
}
private bool CalculateDeltaState(GameTick curTick, out GameState curState, out GameState nextState)
{
var lastTick = new GameTick(curTick.Value - 1);
var nextTick = new GameTick(curTick.Value + 1);

// we found both the states to lerp between, this should be true almost always.
if ((interpolate && curState != null) || (!interpolate && curState != null && nextState != null))
return true;
curState = null;
nextState = null;

for (var i = 0; i < _stateBuffer.Count; i++)
{
var state = _stateBuffer[i];

if (curState == null)
// remember there are no duplicate ToSequence states in the list.
if (state.ToSequence == curTick)
{
curState = ExtrapolateState(lastTick, curTick);
curState = state;
}

if (nextState == null && interpolate)
else if (_interpEnabled && state.ToSequence == nextTick)
{
nextState = state;
}
else if (state.ToSequence < lastTick) // remove any old states we find to keep the buffer clean
{
nextState = ExtrapolateState(curTick, nextTick);
_stateBuffer.RemoveSwap(i);
i--;
}
}

// we found both the states to interpolate between, this should almost always be true.
if ((_interpEnabled && curState != null) || (!_interpEnabled && curState != null && nextState != null))
return true;

if (curState == null)
{
curState = ExtrapolateState(lastTick, curTick);
}

if (nextState == null && _interpEnabled)
{
nextState = ExtrapolateState(curTick, nextTick);
}

return true;
}

/// <summary>
/// Generates a completely fake GameState.
/// </summary>
private GameState ExtrapolateState(GameTick fromSequence, GameTick toSequence)
private static GameState ExtrapolateState(GameTick fromSequence, GameTick toSequence)
{
var state = new GameState(fromSequence, toSequence, null, null, null, null);
state.Extrapolated = true;
Expand Down
Loading

0 comments on commit 5fe0bd6

Please sign in to comment.