Adds basic delta state support (#3492)

This commit is contained in:
Leon Friedrich
2022-12-12 09:36:49 +13:00
committed by GitHub
parent ca83543f9e
commit 445a3aa8fb
8 changed files with 86 additions and 16 deletions

View File

@@ -192,7 +192,7 @@ namespace Robust.Client.GameStates
AckGameState(message.State.ToSequence);
}
public void UpdateFullRep(GameState state) => _processor.UpdateFullRep(state);
public void UpdateFullRep(GameState state, bool cloneDelta = false) => _processor.UpdateFullRep(state, cloneDelta);
private void HandlePvsLeaveMessage(MsgStateLeavePvs message)
{
@@ -578,8 +578,12 @@ namespace Robust.Client.GameStates
foreach (var (netId, component) in _entityManager.GetNetComponents(createdEntity))
{
if (component.NetSyncEnabled)
compData.Add(netId, _entityManager.GetComponentState(bus, component, _players.LocalPlayer?.Session));
if (!component.NetSyncEnabled)
continue;
var state = _entityManager.GetComponentState(bus, component, _players.LocalPlayer?.Session, GameTick.Zero);
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
compData.Add(netId, state);
}
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Client.Timing;
@@ -152,7 +151,7 @@ namespace Robust.Client.GameStates
return applyNextState;
}
public void UpdateFullRep(GameState state)
public void UpdateFullRep(GameState state, bool cloneDelta = false)
{
// Note: the most recently received server state currently doesn't include pvs-leave messages (detaching
// transform to null-space). This is because a client should never predict an entity being moved back from
@@ -177,11 +176,36 @@ namespace Robust.Client.GameStates
{
compData = new Dictionary<ushort, ComponentState>();
_lastStateFullRep.Add(entityState.Uid, compData);
#if DEBUG
foreach (var comp in entityState.ComponentChanges.Span)
{
DebugTools.Assert(comp.State is not IComponentDeltaState delta || delta.FullState);
}
#endif
}
foreach (var change in entityState.ComponentChanges.Span)
{
compData[change.NetID] = change.State;
var compState = change.State;
if (compState is IComponentDeltaState delta && !delta.FullState)
{
var old = compData[change.NetID];
if (cloneDelta)
{
compState = delta.CreateNewFullState(old);
}
else
{
delta.ApplyToFullState(old);
compState = old;
}
DebugTools.Assert(compState is IComponentDeltaState newState && newState.FullState);
}
compData[change.NetID] = compState;
}
if (entityState.NetComponents == null)

View File

@@ -95,7 +95,12 @@ namespace Robust.Client.GameStates
uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs;
void UpdateFullRep(GameState state);
/// <summary>
/// Updates the cached game sates that are used to reset predicted entities.
/// </summary>
/// <param name="cloneDelta">If true, this will clone old states while applying delta states, rather than
/// modifying them directly. Useful if they are still cached elsewhere (e.g., replays).</param>
void UpdateFullRep(GameState state, bool cloneDelta = false);
/// <summary>
/// This will perform some setup in order to reset the game to an earlier state by deleting entities and

View File

@@ -1154,7 +1154,8 @@ internal sealed partial class PVSSystem : EntitySystem
if (component.SessionSpecific && !EntityManager.CanGetComponentState(bus, component, player))
continue;
var state = EntityManager.GetComponentState(bus, component, component.SessionSpecific ? player : null);
var state = EntityManager.GetComponentState(bus, component, player, fromTick);
DebugTools.Assert(fromTick > component.CreationTick || state is not IComponentDeltaState delta || delta.FullState);
changed.Add(new ComponentChange(netId, state, component.LastModifiedTick));
if (sendCompList)
@@ -1188,7 +1189,9 @@ internal sealed partial class PVSSystem : EntitySystem
if (component.SessionSpecific && !EntityManager.CanGetComponentState(bus, component, player))
continue;
changed.Add(new ComponentChange(netId, EntityManager.GetComponentState(bus, component, component.SessionSpecific ? player : null), component.LastModifiedTick));
var state = EntityManager.GetComponentState(bus, component, player, GameTick.Zero);
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
changed.Add(new ComponentChange(netId, state, component.LastModifiedTick));
netComps.Add(netId);
}

View File

@@ -1,5 +1,4 @@
using System;
using Robust.Shared.Analyzers;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects
@@ -8,4 +7,26 @@ namespace Robust.Shared.GameObjects
[Serializable, NetSerializable]
[Virtual]
public class ComponentState { }
/// <summary>
/// Interface for components that support delta-states.
/// </summary>
public interface IComponentDeltaState
{
/// <summary>
/// Whether this state is a delta or full state.
/// </summary>
bool FullState { get; }
/// <summary>
/// This function will apply the current delta state to the provided full state, modifying it in the process.
/// </summary>
public void ApplyToFullState(ComponentState fullState);
/// <summary>
/// This function should take in a full state and return a new full state with the current delta applied,
/// WITHOUT modifying the original input state.
/// </summary>
public ComponentState CreateNewFullState(ComponentState fullState);
}
}

View File

@@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
using Robust.Shared.Log;
using System.Diagnostics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
#if EXCEPTION_TOLERANCE
using Robust.Shared.Exceptions;
#endif
@@ -1270,10 +1271,10 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public ComponentState GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? session)
public ComponentState GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? session, GameTick fromTick)
{
DebugTools.Assert(component.NetSyncEnabled, $"Attempting to get component state for an un-synced component: {component.GetType()}");
var getState = new ComponentGetState(session);
var getState = new ComponentGetState(session, fromTick);
eventBus.RaiseComponentEvent(component, ref getState);
return getState.State ?? component.GetComponentState();

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Players;
using Robust.Shared.Timing;
namespace Robust.Shared.GameObjects
{
@@ -371,9 +372,10 @@ namespace Robust.Shared.GameObjects
/// </summary>
/// <param name="eventBus">A reference to the event bus instance.</param>
/// <param name="component">Component to generate the state for.</param>
/// <param name="player">The player that is going to receive this state. Null implies that this state is for a replay.</param>
/// <param name="fromTick">The from tick, which indicates the range of data that must be included for delta-states.</param>
/// <returns>The component state of the component.</returns>
///
ComponentState GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? player);
ComponentState GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? player, GameTick fromTick);
/// <summary>
/// Checks if a certain player should get a component state.

View File

@@ -1,5 +1,6 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Players;
using Robust.Shared.Timing;
namespace Robust.Shared.GameStates
{
@@ -22,19 +23,28 @@ namespace Robust.Shared.GameStates
[ByRefEvent, ComponentEvent]
public struct ComponentGetState
{
public GameTick FromTick { get; }
/// <summary>
/// Output parameter. Set this to the component's state for the player.
/// </summary>
public ComponentState? State { get; set; }
/// <summary>
/// Input parameter. The player the state is being sent to.
/// If true, this state is intended for replays or some other server spectator entity, not for specific
/// clients.
/// </summary>
public bool ReplayState => Player == null;
/// <summary>
/// The player the state is being sent to. Null implies the state is for a replay or some spectator entity.
/// </summary>
public readonly ICommonSession? Player;
public ComponentGetState(ICommonSession? player)
public ComponentGetState(ICommonSession? player, GameTick fromTick)
{
Player = player;
FromTick = fromTick;
State = null;
}
}