Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sourcegenned field deltas #5155

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ END TEMPLATE-->

### Breaking changes

*None yet*
* The `ComponentState` class is now abstract. Networked components that don't have state information now just return a null state.
* The way that delta component states work has changed. It now expects there to be two different state classes, only one of which should implement `IComponentDeltaState<TFulState>`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor spelling mistake

Suggested change
* The way that delta component states work has changed. It now expects there to be two different state classes, only one of which should implement `IComponentDeltaState<TFulState>`
* The way that delta component states work has changed. It now expects there to be two different state classes, only one of which should implement `IComponentDeltaState<TFullState>`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

review the other pr you absolute fool


### New features

Expand Down
2 changes: 1 addition & 1 deletion Robust.Client/Audio/AudioSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun
var (entity, component) = CreateAndStartPlayingStream(audioParams, stream);
component.Global = true;
component.Source.Global = true;
Dirty(entity, component);
DirtyField(entity, component, nameof(AudioComponent.Global));
return (entity, component);
}

Expand Down
3 changes: 2 additions & 1 deletion Robust.Client/GameStates/NetGraphOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ internal sealed class NetGraphOverlay : Overlay
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly IConsoleHost _host = default!;
[Dependency] private readonly IEntityManager _entManager = default!;

private const int HistorySize = 60 * 5; // number of ticks to keep in history.
Expand Down Expand Up @@ -79,7 +80,7 @@ private void HandleGameStateApplied(GameStateAppliedArgs args)

string? entStateString = null;
string? entDelString = null;
var conShell = IoCManager.Resolve<IConsoleHost>().LocalShell;
var conShell = _host.LocalShell;

var entStates = args.AppliedState.EntityStates;
if (entStates.HasContents)
Expand Down
357 changes: 335 additions & 22 deletions Robust.Shared.CompNetworkGenerator/ComponentNetworkGenerator.cs

Large diffs are not rendered by default.

18 changes: 14 additions & 4 deletions Robust.Shared/Audio/Systems/SharedAudioSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,15 @@ public void SetPlaybackPosition(Entity<AudioComponent?>? nullEntity, float posit
if (entity.Comp.PauseTime != null)
{
entity.Comp.PauseTime = entity.Comp.PauseTime.Value + timeOffset;
DirtyField(entity, nameof(AudioComponent.PauseTime));

// Paused audio doesn't have TimedDespawn so.
}
else
{
// Bump it back so the actual playback positions moves forward
entity.Comp.AudioStart -= timeOffset;
DirtyField(entity, nameof(AudioComponent.AudioStart));

// need to ensure it doesn't despawn too early.
if (TryComp(entity.Owner, out TimedDespawnComponent? despawn))
Expand All @@ -121,8 +123,6 @@ public void SetPlaybackPosition(Entity<AudioComponent?>? nullEntity, float posit
}

entity.Comp.PlaybackPosition = position;
// Network the new playback position.
Dirty(entity);
}

/// <summary>
Expand Down Expand Up @@ -191,31 +191,41 @@ public void SetState(EntityUid? entity, AudioState state, bool force = false, Au
var pauseOffset = Timing.CurTime - component.PauseTime;
component.AudioStart += pauseOffset ?? TimeSpan.Zero;
component.PlaybackPosition = (float) (Timing.CurTime - component.AudioStart).TotalSeconds;

DirtyField(entity.Value, component, nameof(AudioComponent.AudioStart));
DirtyField(entity.Value, component, nameof(AudioComponent.PlaybackPosition));
}

// If we were stopped then played then restart audiostart to now.
if (component.State == AudioState.Stopped && state == AudioState.Playing)
{
component.AudioStart = Timing.CurTime;
component.PauseTime = null;

DirtyField(entity.Value, component, nameof(AudioComponent.AudioStart));
DirtyField(entity.Value, component, nameof(AudioComponent.PauseTime));
}

switch (state)
{
case AudioState.Stopped:
component.AudioStart = Timing.CurTime;
component.PauseTime = null;
DirtyField(entity.Value, component, nameof(AudioComponent.AudioStart));
DirtyField(entity.Value, component, nameof(AudioComponent.PauseTime));
component.StopPlaying();
RemComp<TimedDespawnComponent>(entity.Value);
break;
case AudioState.Paused:
// Set it to current time so we can easily unpause it later.
component.PauseTime = Timing.CurTime;
DirtyField(entity.Value, component, nameof(AudioComponent.PauseTime));
component.Pause();
RemComp<TimedDespawnComponent>(entity.Value);
break;
case AudioState.Playing:
component.PauseTime = null;
DirtyField(entity.Value, component, nameof(AudioComponent.PauseTime));
component.StartPlaying();

// Reset TimedDespawn so the audio still gets cleaned up.
Expand All @@ -230,7 +240,7 @@ public void SetState(EntityUid? entity, AudioState state, bool force = false, Au
}

component.State = state;
Dirty(entity.Value, component);
DirtyField(entity.Value, component, nameof(AudioComponent.State));
}

protected void SetZOffset(float value)
Expand Down Expand Up @@ -374,7 +384,7 @@ public void SetVolume(EntityUid? entity, float value, AudioComponent? component

component.Params.Volume = value;
component.Volume = value;
Dirty(entity.Value, component);
DirtyField(entity.Value, component, nameof(AudioComponent.Params));
}

#endregion
Expand Down
27 changes: 27 additions & 0 deletions Robust.Shared/GameObjects/ComponentFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,33 @@ private static string CalculateComponentName(Type type)
return name;
}

/// <inheritdoc />
public void RegisterNetworkedFields<T>(params string[] fields) where T : IComponent
{
var compReg = GetRegistration(CompIdx.Index<T>());
RegisterNetworkedFields(compReg, fields);
}

/// <inheritdoc />
public void RegisterNetworkedFields(ComponentRegistration compReg, params string[] fields)
{
// Nothing to do.
if (compReg.NetworkedFields.Length > 0 || fields.Length == 0)
return;

compReg.NetworkedFields = fields;
var lookup = new Dictionary<string, int>();
var i = 0;

foreach (var field in fields)
{
lookup[field] = i;
i++;
}

compReg.NetworkedFieldLookup = lookup.ToFrozenDictionary();
}

public void IgnoreMissingComponents(string postfix = "")
{
if (_ignoreMissingComponentPostfix != null && _ignoreMissingComponentPostfix != postfix)
Expand Down
8 changes: 8 additions & 0 deletions Robust.Shared/GameObjects/ComponentRegistration.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using Robust.Shared.Collections;
using Robust.Shared.GameStates;
Expand Down Expand Up @@ -39,6 +40,13 @@ public sealed class ComponentRegistration
/// </summary>
public Type Type { get; }

/// <summary>
/// Fields that are networked for this component. Used for delta states.
/// </summary>
public string[] NetworkedFields = [];

public FrozenDictionary<string, int> NetworkedFieldLookup = FrozenDictionary<string, int>.Empty;

// Internal for sandboxing.
// Avoid content passing an instance of this to ComponentFactory to get any type they want instantiated.
internal ComponentRegistration(string name, Type type, CompIdx idx, bool unsaved = false)
Expand Down
35 changes: 10 additions & 25 deletions Robust.Shared/GameObjects/Components/CollisionWakeComponent.cs
Original file line number Diff line number Diff line change
@@ -1,30 +1,15 @@
using System;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;

namespace Robust.Shared.GameObjects
{
/// <summary>
/// An optimisation component for stuff that should be set as collidable when it's awake and non-collidable when asleep.
/// </summary>
[RegisterComponent, NetworkedComponent()]
[Access(typeof(CollisionWakeSystem))]
public sealed partial class CollisionWakeComponent : Component
{
[DataField("enabled")]
public bool Enabled = true;

[Serializable, NetSerializable]
public sealed class CollisionWakeState : ComponentState
{
public bool Enabled { get; }
namespace Robust.Shared.GameObjects;

public CollisionWakeState(bool enabled)
{
Enabled = enabled;
}
}
}
/// <summary>
/// An optimisation component for stuff that should be set as collidable when it's awake and non-collidable when asleep.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(CollisionWakeSystem))]
public sealed partial class CollisionWakeComponent : Component
{
[DataField, AutoNetworkedField]
public bool Enabled = true;
}
10 changes: 5 additions & 5 deletions Robust.Shared/GameObjects/Components/Eye/EyeComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,26 @@ public sealed partial class EyeComponent : Component
/// that without messing with the main viewport's eye. This is important as there are some overlays that are
/// only be drawn if that viewport's eye belongs to the currently controlled entity.
/// </remarks>
[ViewVariables, DataField("target"), AutoNetworkedField]
[DataField, AutoNetworkedField]
public EntityUid? Target;

[ViewVariables(VVAccess.ReadWrite), DataField("drawFov"), AutoNetworkedField]
[DataField, AutoNetworkedField]
public bool DrawFov = true;

[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public bool DrawLight = true;

// yes it's not networked, don't ask.
[ViewVariables(VVAccess.ReadWrite), DataField("rotation")]
[ViewVariables(VVAccess.ReadWrite), DataField]
public Angle Rotation;

[ViewVariables(VVAccess.ReadWrite), DataField("zoom")]
[ViewVariables(VVAccess.ReadWrite), DataField]
public Vector2 Zoom = Vector2.One;

/// <summary>
/// Eye offset, relative to the map, and not affected by <see cref="Rotation"/>
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("offset"), AutoNetworkedField]
[DataField, AutoNetworkedField]
public Vector2 Offset;

/// <summary>
Expand Down
61 changes: 61 additions & 0 deletions Robust.Shared/GameObjects/EntityManager.ComponentDeltas.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Robust.Shared.Timing;

namespace Robust.Shared.GameObjects;

public abstract partial class EntityManager
{
public void DirtyField(EntityUid uid, IComponentDelta delta, string fieldName, MetaDataComponent? metadata = null)
{
// Much more likely for this to accidentally not be implemented than vice versa.
if (delta is not IComponent component)
{
_sawmill.Error($"Tried to dirty component field for {delta.GetType()} which does not implement {nameof(IComponent)}");
return;
}

var compReg = ComponentFactory.GetRegistration(component);
InternalDirty(uid, component, delta, compReg, fieldName, metadata);
}

public void DirtyField<T>(EntityUid uid, T component, string fieldName, MetaDataComponent? metadata = null) where T : IComponent, IComponentDelta
{
var delta = (IComponentDelta)component;
var compReg = ComponentFactory.GetRegistration(CompIdx.Index<T>());
InternalDirty(uid, component, delta, compReg, fieldName, metadata);
}

private void InternalDirty(EntityUid uid, IComponent comp, IComponentDelta delta, ComponentRegistration compReg, string fieldName, MetaDataComponent? metadata = null)
{
if (!compReg.NetworkedFieldLookup.TryGetValue(fieldName, out var idx))
{
_sawmill.Error($"Tried to dirty delta field {fieldName} on {ToPrettyString(uid)} that isn't implemented.");
return;
}

var curTick = _gameTiming.CurTick;
delta.LastFieldUpdate = curTick;
delta.LastModifiedFields[idx] = curTick;
Dirty(uid, comp, metadata);
}
}

/// <summary>
/// Indicates this component supports delta states.
/// </summary>
public interface IComponentDelta
{
// TODO: This isn't entirely robust but not sure how else to handle this?
/// <summary>
/// Track last time a field was dirtied. if the full component dirty exceeds this then we send a full state update.
/// </summary>
public GameTick LastFieldUpdate { get; set; }

/// <summary>
/// Stores the last modified tick for fields.
/// </summary>
public GameTick[] LastModifiedFields
{
get;
set;
}
}
17 changes: 12 additions & 5 deletions Robust.Shared/GameObjects/EntityManager.Components.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,13 @@ public CompInitializeHandle<T> AddComponentUninitialized<T>(EntityUid uid) where
metadata.NetComponents.Add(netId, component);
}

if (component is IComponentDelta delta)
{
var curTick = _gameTiming.CurTick;
delta.LastModifiedFields = new GameTick[reg.NetworkedFields.Length];
Array.Fill(delta.LastModifiedFields, curTick);
}

component.Networked = reg.NetID != null;

var eventArgs = new AddedComponentEventArgs(new ComponentEventArgs(component, uid), reg);
Expand Down Expand Up @@ -718,7 +725,7 @@ public void CullRemovedComponents()

/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasComponent<T>(EntityUid? uid) where T : IComponent
public bool HasComponent<T>([NotNullWhen(true)] EntityUid? uid) where T : IComponent
{
return uid.HasValue && HasComponent<T>(uid.Value);
}
Expand All @@ -733,7 +740,7 @@ public bool HasComponent(EntityUid uid, Type type)

/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasComponent(EntityUid? uid, Type type)
public bool HasComponent([NotNullWhen(true)] EntityUid? uid, Type type)
{
if (!uid.HasValue)
{
Expand All @@ -756,7 +763,7 @@ public bool HasComponent(EntityUid uid, ushort netId, MetaDataComponent? meta =

/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasComponent(EntityUid? uid, ushort netId, MetaDataComponent? meta = null)
public bool HasComponent([NotNullWhen(true)] EntityUid? uid, ushort netId, MetaDataComponent? meta = null)
{
if (!uid.HasValue)
{
Expand Down Expand Up @@ -1552,7 +1559,7 @@ public bool TryComp([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out

[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool HasComp(EntityUid? uid) => HasComponent(uid);
public bool HasComp([NotNullWhen(true)] EntityUid? uid) => HasComponent(uid);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
Expand All @@ -1563,7 +1570,7 @@ public bool HasComponent(EntityUid uid)

[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool HasComponent(EntityUid? uid)
public bool HasComponent([NotNullWhen(true)] EntityUid? uid)
{
return uid != null && HasComponent(uid.Value);
}
Expand Down
22 changes: 22 additions & 0 deletions Robust.Shared/GameObjects/EntitySystem.Proxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,28 @@ protected void Dirty(EntityUid uid, IComponent component, MetaDataComponent? met
EntityManager.Dirty(uid, component, meta);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void DirtyField(EntityUid uid, IComponentDelta delta, string fieldName, MetaDataComponent? meta = null)
{
EntityManager.DirtyField(uid, delta, fieldName, meta);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void DirtyField<T>(Entity<T?> entity, string fieldName, MetaDataComponent? meta = null)
where T : IComponent, IComponentDelta
{
if (!Resolve(entity.Owner, ref entity.Comp))
return;

EntityManager.DirtyField(entity.Owner, entity.Comp, fieldName, meta);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void DirtyField<T>(EntityUid uid, T component, string fieldName, MetaDataComponent? meta = null) where T : IComponent, IComponentDelta
{
EntityManager.DirtyField(uid, component, fieldName, meta);
}

/// <summary>
/// Marks a component as dirty. This also implicitly dirties the entity this component belongs to.
/// </summary>
Expand Down
Loading
Loading