Add sourcegenned field deltas (#5155)

* Remove full "delta" states

* Update MapGridComponentState

* abstract ComponentState

* Release notes

* Fix tests

* Fix nullable errors

* A

* Sourcegen component deltas

* Audio deltas + methids

* Also eye

* Optimise out the dictionary

* Minor fixes

* Physics deltas

* Also this

* Fix field deltas

* remove old release notes

* Make IComponentDelta implement IComponent

* add sourcegen launch settings

* make silent error loud

* Review

* UI deltas

* Slimmer

* Sourcegen bandaid

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
This commit is contained in:
metalgearsloth
2024-12-21 15:48:33 +11:00
committed by GitHub
parent ac30ad1820
commit 9837c33de7
28 changed files with 1057 additions and 327 deletions

View File

@@ -504,7 +504,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
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);
}

View File

@@ -26,6 +26,7 @@ namespace Robust.Client.GameStates
[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.
@@ -78,7 +79,7 @@ namespace Robust.Client.GameStates
string? entStateString = null;
string? entDelString = null;
var conShell = IoCManager.Resolve<IConsoleHost>().LocalShell;
var conShell = _host.LocalShell;
var entStates = args.AppliedState.EntityStates;
if (entStates.HasContents)

View File

@@ -1,4 +1,5 @@
using System.Text;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -13,6 +14,8 @@ public class Generator : IIncrementalGenerator
private const string TypeCopierInterfaceNamespace = "Robust.Shared.Serialization.TypeSerializers.Interfaces.ITypeCopier";
private const string TypeCopyCreatorInterfaceNamespace = "Robust.Shared.Serialization.TypeSerializers.Interfaces.ITypeCopyCreator";
private const string SerializationHooksNamespace = "Robust.Shared.Serialization.ISerializationHooks";
private const string AutoStateAttributeName = "Robust.Shared.Analyzers.AutoGenerateComponentStateAttribute";
private const string ComponentDeltaInterfaceName = "Robust.Shared.GameObjects.IComponentDelta";
public void Initialize(IncrementalGeneratorInitializationContext initContext)
{
@@ -35,6 +38,7 @@ public class Generator : IIncrementalGenerator
var builder = new StringBuilder();
var containingTypes = new Stack<INamedTypeSymbol>();
var declarationsGenerated = new HashSet<string>();
var deltaType = compilation.GetTypeByMetadataName(ComponentDeltaInterfaceName)!;
foreach (var declaration in declarations)
{
@@ -106,9 +110,9 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
{
{{GetConstructor(definition)}}
{{GetCopyMethods(definition)}}
{{GetCopyMethods(definition, deltaType)}}
{{GetInstantiators(definition)}}
{{GetInstantiators(definition, deltaType)}}
}
{{containingTypesEnd}}
@@ -192,7 +196,7 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
return builder.ToString();
}
private static string GetCopyMethods(DataDefinition definition)
private static string GetCopyMethods(DataDefinition definition, ITypeSymbol deltaType)
{
var builder = new StringBuilder();
@@ -263,7 +267,7 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
{{baseCopy}}
""");
foreach (var @interface in GetImplicitDataDefinitionInterfaces(definition.Type, true))
foreach (var @interface in InternalGetImplicitDataDefinitionInterfaces(definition.Type, true, deltaType))
{
var interfaceModifiers = baseType != null && baseType.AllInterfaces.Contains(@interface, SymbolEqualityComparer.Default)
? "override "
@@ -292,7 +296,7 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
return builder.ToString();
}
private static string GetInstantiators(DataDefinition definition)
private static string GetInstantiators(DataDefinition definition, ITypeSymbol deltaType)
{
var builder = new StringBuilder();
var modifiers = string.Empty;
@@ -326,7 +330,7 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
""");
}
foreach (var @interface in GetImplicitDataDefinitionInterfaces(definition.Type, false))
foreach (var @interface in InternalGetImplicitDataDefinitionInterfaces(definition.Type, false, deltaType))
{
var interfaceName = @interface.ToDisplayString();
builder.AppendLine($$"""
@@ -345,6 +349,31 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
return builder.ToString();
}
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
private static IEnumerable<ITypeSymbol> InternalGetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all, ITypeSymbol deltaType)
{
var symbols = GetImplicitDataDefinitionInterfaces(type, all);
// TODO SOURCE GEN
// fix this jank
// The comp-state source generator will add an "IComponentDelta" interface to classes with the auto state
// attribute, and this generator creates methods that those classes then have to implement because
// IComponentDelta a DataDefinition via the ImplicitDataDefinitionForInheritorsAttribute on IComponent.
if (!TryGetAttribute(type, AutoStateAttributeName, out var data))
return symbols;
// If it doesn't have field deltas then ignore
if (data.ConstructorArguments[1] is not { Value: bool fields and true })
{
return symbols;
}
if (symbols.Any(x => x.ToDisplayString() == deltaType.ToDisplayString()))
return symbols;
return symbols.Append(deltaType);
}
// TODO serveronly? do we care? who knows!!
private static StringBuilder CopyDataFields(DataDefinition definition)
{

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -302,6 +303,21 @@ internal static class Types
return false;
}
internal static bool TryGetAttribute(ISymbol symbol, string attributeName, [NotNullWhen(true)] out AttributeData? data)
{
foreach (var attribute in symbol.GetAttributes())
{
if (attribute.AttributeClass?.ToDisplayString() == attributeName)
{
data = attribute;
return true;
}
}
data = null;
return false;
}
internal static IEnumerable<ITypeSymbol> GetBaseTypes(ITypeSymbol type)
{
var baseType = type.BaseType;

View File

@@ -36,7 +36,9 @@ namespace Robust.Shared.CompNetworkGenerator
private static readonly SymbolDisplayFormat FullNullableFormat =
FullyQualifiedFormat.WithMiscellaneousOptions(IncludeNullableReferenceTypeModifier);
private static string? GenerateSource(in GeneratorExecutionContext context, INamedTypeSymbol classSymbol, CSharpCompilation comp, bool raiseAfterAutoHandle)
private static string? GenerateSource(in GeneratorExecutionContext context, INamedTypeSymbol classSymbol, CSharpCompilation comp,
bool raiseAfterAutoHandle,
bool fieldDeltas)
{
var nameSpace = classSymbol.ContainingNamespace.ToDisplayString();
var componentName = classSymbol.Name;
@@ -134,54 +136,154 @@ namespace Robust.Shared.CompNetworkGenerator
// component.Count = state.Count;
var handleStateSetters = new StringBuilder();
// Builds the string for copying delta fields in IComponentDelta to a new full state.
var deltaCreate = new StringBuilder();
// Delta field states
var deltaGetFields = new StringBuilder();
var deltaHandleFields = new StringBuilder();
// Apply the delta field to the full state.
var deltaApply = new List<string>();
var index = -1;
var fieldsStr = new StringBuilder();
var fieldStates = new StringBuilder();
var networkedTypes = new List<string>();
foreach (var (type, name) in fields)
{
index++;
if (index == 0)
{
fieldsStr.Append(@$"""{name}""");
}
else
{
fieldsStr.Append(@$", ""{name}""");
}
var typeDisplayStr = type.ToDisplayString(FullNullableFormat);
var nullable = type.NullableAnnotation == NullableAnnotation.Annotated;
var nullableAnnotation = nullable ? "?" : string.Empty;
string deltaStateName = $"{name}_FieldComponentState";
// The type used for networking, e.g. EntityUid -> NetEntity
string networkedType;
string getField;
string? cast;
// TODO: Uhh I just need casts or something.
var castString = typeDisplayStr.Substring(8);
deltaGetFields.Append(@$"
case {Math.Pow(2, index)}:
args.State = new {deltaStateName}()
{{
");
deltaHandleFields.Append(@$"
case {deltaStateName} {deltaStateName}_State:
{{
");
var fieldHandleValue = $"{deltaStateName}_State.{name}!";
switch (typeDisplayStr)
{
case GlobalEntityUidName:
case GlobalNullableEntityUidName:
stateFields.Append($@"
public NetEntity{nullableAnnotation} {name} = default!;");
networkedType = $"NetEntity{nullableAnnotation}";
stateFields.Append($@"
public {networkedType} {name} = default!;");
getField = $"GetNetEntity(component.{name})";
cast = $"(NetEntity{nullableAnnotation})";
getStateInit.Append($@"
{name} = GetNetEntity(component.{name}),");
handleStateSetters.Append($@"
component.{name} = EnsureEntity<{componentName}>(state.{name}, uid);");
component.{name} = EnsureEntity<{componentName}>(state.{name}, uid);");
deltaHandleFields.Append($@"
component.{name} = EnsureEntity<{componentName}>({cast} {fieldHandleValue}, uid);");
deltaCreate.Append($@"
{name} = fullState.{name},");
deltaApply.Add($@"
fullState.{name} = {name};");
break;
case GlobalEntityCoordinatesName:
case GlobalNullableEntityCoordinatesName:
stateFields.Append($@"
public NetCoordinates{nullableAnnotation} {name} = default!;");
networkedType = $"NetCoordinates{nullableAnnotation}";
stateFields.Append($@"
public {networkedType} {name} = default!;");
getField = $"GetNetCoordinates(component.{name})";
cast = $"(NetCoordinates{nullableAnnotation})";
getStateInit.Append($@"
{name} = GetNetCoordinates(component.{name}),");
handleStateSetters.Append($@"
component.{name} = EnsureCoordinates<{componentName}>(state.{name}, uid);");
component.{name} = EnsureCoordinates<{componentName}>(state.{name}, uid);");
deltaHandleFields.Append($@"
component.{name} = EnsureCoordinates<{componentName}>({cast} {fieldHandleValue}, uid);");
deltaCreate.Append($@"
{name} = fullState.{name},");
deltaApply.Add($@"
fullState.{name} = {name};");
break;
case GlobalEntityUidSetName:
stateFields.Append($@"
public {GlobalNetEntityUidSetName} {name} = default!;");
networkedType = $"{GlobalNetEntityUidSetName}";
stateFields.Append($@"
public {networkedType} {name} = default!;");
getField = $"GetNetEntitySet(component.{name})";
cast = $"({GlobalNetEntityUidSetName})";
getStateInit.Append($@"
{name} = GetNetEntitySet(component.{name}),");
handleStateSetters.Append($@"
EnsureEntitySet<{componentName}>(state.{name}, uid, component.{name});");
EnsureEntitySet<{componentName}>(state.{name}, uid, component.{name});");
deltaHandleFields.Append($@"
EnsureEntitySet<{componentName}>({cast} {fieldHandleValue}, uid, component.{name});");
deltaCreate.Append($@"
{name} = new(fullState.{name}),");
deltaApply.Add($@"
fullState.{name} = {name};");
break;
case GlobalEntityUidListName:
stateFields.Append($@"
public {GlobalNetEntityUidListName} {name} = default!;");
networkedType = $"{GlobalNetEntityUidListName}";
stateFields.Append($@"
public {networkedType} {name} = default!;");
getField = $"GetNetEntityList(component.{name})";
cast = $"({GlobalNetEntityUidListName})";
getStateInit.Append($@"
{name} = GetNetEntityList(component.{name}),");
handleStateSetters.Append($@"
EnsureEntityList<{componentName}>(state.{name}, uid, component.{name});");
EnsureEntityList<{componentName}>(state.{name}, uid, component.{name});");
deltaHandleFields.Append($@"
EnsureEntityList<{componentName}>({cast} {fieldHandleValue}, uid, component.{name});");
deltaCreate.Append($@"
{name} = new(fullState.{name}),");
deltaApply.Add($@"
fullState.{name} = {name};");
break;
default:
@@ -205,69 +307,150 @@ namespace Robust.Shared.CompNetworkGenerator
ensureGeneric = componentName;
}
stateFields.Append($@"
public Dictionary<{key}, {value}> {name} = default!;");
networkedType = $"Dictionary<{key}, {value}>";
getStateInit.Append($@"
{name} = GetNetEntityDictionary(component.{name}),");
stateFields.Append($@"
public {networkedType} {name} = default!;");
getField = $"GetNetEntityDictionary(component.{name})";
if (valueNullable && value is not GlobalNetEntityName and not GlobalNetEntityNullableName)
{
cast = $"(Dictionary<{key}, {value}>)";
handleStateSetters.Append($@"
EnsureEntityDictionaryNullableValue<{componentName}, {value}>(state.{name}, uid, component.{name});");
EnsureEntityDictionaryNullableValue<{componentName}, {value}>(state.{name}, uid, component.{name});");
deltaHandleFields.Append($@"
EnsureEntityDictionaryNullableValue<{componentName}, {value}>({cast} {fieldHandleValue}, uid, component.{name});");
}
else
{
cast = $"({castString})";
handleStateSetters.Append($@"
EnsureEntityDictionary<{ensureGeneric}>(state.{name}, uid, component.{name});");
EnsureEntityDictionary<{ensureGeneric}>(state.{name}, uid, component.{name})");
deltaHandleFields.Append($@"
EnsureEntityDictionary<{ensureGeneric}>({cast} {fieldHandleValue}, uid, component.{name});");
}
deltaCreate.Append($@"
{name} = new(fullState.{name}),");
deltaApply.Add($@"
fullState.{name} = {name};");
break;
}
if (value is GlobalEntityUidName or GlobalNullableEntityUidName)
{
networkedType = $"Dictionary<{key}, {value}>";
value = valueNullable ? GlobalNetEntityNullableName : GlobalNetEntityName;
stateFields.Append($@"
public Dictionary<{key}, {value}> {name} = default!;");
public {networkedType} {name} = default!;");
getField = $"GetNetEntityDictionary(component.{name})";
cast = $"(Dictionary<{key}, {value}>)";
getStateInit.Append($@"
{name} = GetNetEntityDictionary(component.{name}),");
handleStateSetters.Append($@"
EnsureEntityDictionary<{componentName}, {key}>(state.{name}, uid, component.{name});");
EnsureEntityDictionary<{componentName}, {key}>(state.{name}, uid, component.{name});");
deltaHandleFields.Append($@"
EnsureEntityDictionary<{componentName}, {key}>({cast} {fieldHandleValue}, uid, component.{name});");
deltaCreate.Append($@"
{name} = new(fullState.{name}),");
deltaApply.Add($@"
fullState.{name} = {name};");
break;
}
}
networkedType = $"{typeDisplayStr}";
stateFields.Append($@"
public {typeDisplayStr} {name} = default!;");
public {networkedType} {name} = default!;");
if (IsCloneType(type))
{
// get first ctor arg of the field attribute, which determines whether the field should be cloned
// (like if its a dict or list)
getStateInit.Append($@"
{name} = component.{name},");
getField = $"component.{name}";
cast = $"({castString})";
var nullCast = nullable ? castString.Substring(0, castString.Length - 1) : castString;
handleStateSetters.Append($@"
if (state.{name} == null)
component.{name} = null!;
else
component.{name} = new(state.{name});");
deltaHandleFields.Append($@"
var {name}Value = {cast} {fieldHandleValue};
if ({name}Value == null)
component.{name} = null!;
else
component.{name} = new {nullCast}({name}Value);");
if (nullable)
{
deltaCreate.Append($@"
{name} = fullState.{name} == null ? null : new(fullState.{name}),");
}
else
{
deltaCreate.Append($@"
{name} = new(fullState.{name}),");
}
deltaApply.Add($@"
if ({name} == null)
fullState.{name} = null!;
else
fullState.{name} = new({name});");
}
else
{
getStateInit.Append($@"
{name} = component.{name},");
getField = $"component.{name}";
cast = $"({castString})";
handleStateSetters.Append($@"
component.{name} = state.{name};");
deltaHandleFields.Append($@"
component.{name} = {cast} {fieldHandleValue};");
deltaCreate.Append($@"
{name} = fullState.{name},");
deltaApply.Add($@"
fullState.{name} = {name};");
}
break;
}
/*
* End loop stuff
*/
networkedTypes.Add(networkedType);
getStateInit.Append($@"
{name} = {getField},");
deltaGetFields.Append(@$" {name} = {getField}
}};
return;");
deltaHandleFields.Append($@"
}}
break;");
}
var eventRaise = "";
@@ -278,19 +461,94 @@ namespace Robust.Shared.CompNetworkGenerator
EntityManager.EventBus.RaiseComponentEvent(uid, component, ref ev);";
}
var deltaGetState = "";
var deltaHandleState = "";
var deltaInterface = "";
var deltaCompFields = "";
var deltaNetRegister = "";
if (fieldDeltas)
{
for (var i = 0; i < fields.Count; i++)
{
var name = fields[i].FieldName;
string deltaStateName = $"{name}_FieldComponentState";
var networkedType = networkedTypes[i];
var apply = deltaApply[i];
// Creates a state per field
fieldStates.Append($@"
[Serializable, NetSerializable]
public sealed class {deltaStateName} : IComponentDeltaState<{stateName}>
{{
public {networkedType} {name} = default!;
public void ApplyToFullState({stateName} fullState)
{{{apply}
}}
public {stateName} CreateNewFullState({stateName} fullState)
{{
var newState = new {stateName}
{{{deltaCreate}
}};
{apply}
return newState;
}}
}}
");
}
deltaNetRegister = $@"EntityManager.ComponentFactory.RegisterNetworkedFields<{classSymbol}>({fieldsStr});";
deltaGetState = @$"// Delta state
if (component is IComponentDelta delta && args.FromTick > component.CreationTick && delta.LastFieldUpdate >= args.FromTick)
{{
var fields = EntityManager.GetModifiedFields(component, args.FromTick);
// Try and get a matching delta state for the relevant dirty fields, otherwise fall back to full state.
switch (fields)
{{{deltaGetFields}
default:
break;
}}
}}";
deltaHandleState = $@"switch(args.Current)
{{{deltaHandleFields}
default:
break;
}}";
deltaInterface = " : IComponentDelta";
deltaCompFields = @$"/// <inheritdoc />
public GameTick LastFieldUpdate {{ get; set; }} = GameTick.Zero;
/// <inheritdoc />
public GameTick[] LastModifiedFields {{ get; set; }} = Array.Empty<GameTick>();";
}
return $@"// <auto-generated />
#nullable enable
using System;
using Robust.Shared.GameStates;
using Robust.Shared.GameObjects;
using Robust.Shared.Analyzers;
using Robust.Shared.Collections;
using Robust.Shared.Serialization;
using Robust.Shared.Map;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Collections.Generic;
namespace {nameSpace};
public partial class {componentName}
public partial class {componentName}{deltaInterface}
{{
{deltaCompFields}
[System.Serializable, NetSerializable]
public sealed class {stateName} : IComponentState
{{{stateFields}
@@ -301,12 +559,16 @@ public partial class {componentName}
{{
public override void Initialize()
{{
{deltaNetRegister}
SubscribeLocalEvent<{componentName}, ComponentGetState>(OnGetState);
SubscribeLocalEvent<{componentName}, ComponentHandleState>(OnHandleState);
}}
private void OnGetState(EntityUid uid, {componentName} component, ref ComponentGetState args)
{{
{deltaGetState}
// Get full state
args.State = new {stateName}
{{{getStateInit}
}};
@@ -314,11 +576,15 @@ public partial class {componentName}
private void OnHandleState(EntityUid uid, {componentName} component, ref ComponentHandleState args)
{{
{deltaHandleState}
if (args.Current is not {stateName} state)
return;
{handleStateSetters}{eventRaise}
}}
}}
{fieldStates}
}}
";
}
@@ -341,13 +607,15 @@ public partial class {componentName}
{
var attr = type.Attribute;
var raiseEv = false;
if (attr.ConstructorArguments is [{Value: bool raise}])
var fieldDeltas = false;
if (attr.ConstructorArguments is [{Value: bool raise}, {Value: bool fields}])
{
// Get the afterautohandle bool, which is first constructor arg
raiseEv = raise;
fieldDeltas = fields;
}
var source = GenerateSource(context, type.Type, comp, raiseEv);
var source = GenerateSource(context, type.Type, comp, raiseEv, fieldDeltas);
// can be null if no members marked with network field, which already has a diagnostic, so
// just continue
if (source == null)

View File

@@ -0,0 +1,9 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"Comp State Generator": {
"commandName": "DebugRoslynComponent",
"targetProject": "../../Content.Shared/Content.Shared.csproj"
}
}
}

View File

@@ -19,9 +19,15 @@ public sealed class AutoGenerateComponentStateAttribute : Attribute
/// </summary>
public bool RaiseAfterAutoHandleState;
public AutoGenerateComponentStateAttribute(bool raiseAfterAutoHandleState = false)
/// <summary>
/// Should delta states be generated for every field.
/// </summary>
public bool FieldDeltas;
public AutoGenerateComponentStateAttribute(bool raiseAfterAutoHandleState = false, bool fieldDeltas = false)
{
RaiseAfterAutoHandleState = raiseAfterAutoHandleState;
FieldDeltas = fieldDeltas;
}
}

View File

@@ -16,10 +16,10 @@ namespace Robust.Shared.Audio.Components;
/// <summary>
/// Stores the audio data for an audio entity.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedAudioSystem))]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true, fieldDeltas: true), Access(typeof(SharedAudioSystem))]
public sealed partial class AudioComponent : Component, IAudioSource
{
[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField, DataField, Access(Other = AccessPermissions.ReadWriteExecute)]
[AutoNetworkedField, DataField, Access(Other = AccessPermissions.ReadWriteExecute)]
public AudioFlags Flags = AudioFlags.None;
#region Filter

View File

@@ -7,7 +7,7 @@ namespace Robust.Shared.Audio.Components;
/// <summary>
/// Marks this entity as being spawned for audio presets in case we need to reload.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedAudioSystem))]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), Access(typeof(SharedAudioSystem))]
public sealed partial class AudioPresetComponent : Component
{
[AutoNetworkedField]

View File

@@ -105,6 +105,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
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.
}
@@ -112,6 +113,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
{
// 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))
@@ -121,8 +123,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
}
entity.Comp.PlaybackPosition = position;
// Network the new playback position.
Dirty(entity);
}
/// <summary>
@@ -191,6 +191,9 @@ public abstract partial class SharedAudioSystem : EntitySystem
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.
@@ -198,6 +201,9 @@ public abstract partial class SharedAudioSystem : EntitySystem
{
component.AudioStart = Timing.CurTime;
component.PauseTime = null;
DirtyField(entity.Value, component, nameof(AudioComponent.AudioStart));
DirtyField(entity.Value, component, nameof(AudioComponent.PauseTime));
}
switch (state)
@@ -205,17 +211,21 @@ public abstract partial class SharedAudioSystem : EntitySystem
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.
@@ -230,7 +240,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
}
component.State = state;
Dirty(entity.Value, component);
DirtyField(entity.Value, component, nameof(AudioComponent.State));
}
protected void SetZOffset(float value)
@@ -375,7 +385,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
component.Params.Volume = value;
component.Volume = value;
Dirty(entity.Value, component);
DirtyField(entity.Value, component, nameof(AudioComponent.Params));
}
#endregion

View File

@@ -175,6 +175,41 @@ namespace Robust.Shared.GameObjects
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;
DebugTools.Assert(fields.Length <= 32);
if (fields.Length > 32)
{
throw new NotSupportedException(
"Components with more than 32 networked fields unsupported! Consider splitting it up or making a pr for 64-bit flags");
}
compReg.NetworkedFields = fields;
var lookup = new Dictionary<string, int>(fields.Length);
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)

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using Robust.Shared.Collections;
using Robust.Shared.GameStates;
@@ -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)

View File

@@ -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
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, AutoGenerateComponentState]
[Access(typeof(CollisionWakeSystem))]
public sealed partial class CollisionWakeComponent : Component
{
/// <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; }
public CollisionWakeState(bool enabled)
{
Enabled = enabled;
}
}
}
[DataField, AutoNetworkedField]
public bool Enabled = true;
}

View File

@@ -8,7 +8,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
[RegisterComponent, NetworkedComponent, Access(typeof(SharedEyeSystem)), AutoGenerateComponentState(true)]
[RegisterComponent, NetworkedComponent, Access(typeof(SharedEyeSystem)), AutoGenerateComponentState(true, fieldDeltas: true)]
public sealed partial class EyeComponent : Component
{
public const int DefaultVisibilityMask = 1;
@@ -24,33 +24,33 @@ namespace Robust.Shared.GameObjects
/// 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]
[AutoNetworkedField]
public bool DrawLight = true;
// yes it's not networked, don't ask.
[ViewVariables(VVAccess.ReadWrite), DataField("rotation")]
[DataField]
public Angle Rotation;
[ViewVariables(VVAccess.ReadWrite), DataField("zoom")]
[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>
/// The visibility mask for this eye.
/// The player will be able to get updates for entities whose layers match the mask.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("visMask", customTypeSerializer:typeof(FlagSerializer<VisibilityMaskLayer>)), AutoNetworkedField]
[DataField("visMask", customTypeSerializer:typeof(FlagSerializer<VisibilityMaskLayer>)), AutoNetworkedField]
public int VisibilityMask = DefaultVisibilityMask;
/// <summary>

View File

@@ -3,14 +3,22 @@ using System.Collections.Generic;
using Robust.Shared.GameStates;
using Robust.Shared.Player;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
[RegisterComponent, NetworkedComponent, Access(typeof(SharedUserInterfaceSystem))]
public sealed partial class UserInterfaceComponent : Component
public sealed partial class UserInterfaceComponent : Component, IComponentDelta
{
/// <inheritdoc />
public GameTick LastFieldUpdate { get; set; }
/// <inheritdoc />
public GameTick[] LastModifiedFields { get; set; }
/// <summary>
/// The currently open interfaces. Used clientside to store the UI.
/// </summary>
@@ -30,19 +38,71 @@ namespace Robust.Shared.GameObjects
/// Legacy data, new BUIs should be using comp states.
/// </summary>
public Dictionary<Enum, BoundUserInterfaceState> States = new();
}
[Serializable, NetSerializable]
internal sealed class UserInterfaceComponentState(
Dictionary<Enum, List<NetEntity>> actors,
Dictionary<Enum, BoundUserInterfaceState> states,
Dictionary<Enum, InterfaceData> data)
: IComponentState
[Serializable, NetSerializable]
internal sealed class UserInterfaceComponentState(
Dictionary<Enum, List<NetEntity>> actors,
Dictionary<Enum, BoundUserInterfaceState> states,
Dictionary<Enum, InterfaceData> data)
: IComponentState
{
public Dictionary<Enum, List<NetEntity>> Actors = actors;
public Dictionary<Enum, BoundUserInterfaceState> States = states;
public Dictionary<Enum, InterfaceData> Data = data;
}
[Serializable, NetSerializable]
internal sealed class UserInterfaceActorsDeltaState : IComponentDeltaState<UserInterfaceComponentState>
{
public Dictionary<Enum, List<NetEntity>> Actors = new();
public void ApplyToFullState(UserInterfaceComponentState fullState)
{
public Dictionary<Enum, List<NetEntity>> Actors = actors;
fullState.Actors.Clear();
public Dictionary<Enum, BoundUserInterfaceState> States = states;
foreach (var (key, value) in Actors)
{
fullState.Actors.Add(key, value);
}
}
public Dictionary<Enum, InterfaceData> Data = data;
public UserInterfaceComponentState CreateNewFullState(UserInterfaceComponentState fullState)
{
var newState = new UserInterfaceComponentState(
new Dictionary<Enum, List<NetEntity>>(Actors),
new(fullState.States),
new(fullState.Data));
return newState;
}
}
[Serializable, NetSerializable]
internal sealed class UserInterfaceStatesDeltaState : IComponentDeltaState<UserInterfaceComponentState>
{
public Dictionary<Enum, BoundUserInterfaceState> States = new();
public void ApplyToFullState(UserInterfaceComponentState fullState)
{
fullState.States.Clear();
foreach (var (key, value) in States)
{
fullState.States.Add(key, value);
}
}
public UserInterfaceComponentState CreateNewFullState(UserInterfaceComponentState fullState)
{
var newState = new UserInterfaceComponentState(
new Dictionary<Enum, List<NetEntity>>(fullState.Actors),
new(States),
new(fullState.Data));
return newState;
}
}

View File

@@ -0,0 +1,78 @@
using Robust.Shared.Timing;
namespace Robust.Shared.GameObjects;
public abstract partial class EntityManager
{
public uint GetModifiedFields(IComponentDelta delta, GameTick fromTick)
{
uint fields = 0;
for (var i = 0; i < delta.LastModifiedFields.Length; i++)
{
var lastUpdate = delta.LastModifiedFields[i];
// Field not dirty
if (lastUpdate < fromTick)
continue;
fields |= (uint) (1 << i);
}
return fields;
}
public void DirtyField(EntityUid uid, IComponentDelta comp, string fieldName, MetaDataComponent? metadata = null)
{
var compReg = ComponentFactory.GetRegistration(comp);
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;
comp.LastFieldUpdate = curTick;
comp.LastModifiedFields[idx] = curTick;
Dirty(uid, comp, metadata);
}
public void DirtyField<T>(EntityUid uid, T comp, string fieldName, MetaDataComponent? metadata = null)
where T : IComponentDelta
{
var compReg = ComponentFactory.GetRegistration(CompIdx.Index<T>());
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;
comp.LastFieldUpdate = curTick;
comp.LastModifiedFields[idx] = curTick;
Dirty(uid, comp, metadata);
}
}
/// <summary>
/// Indicates this component supports delta states.
/// </summary>
public partial interface IComponentDelta : IComponent
{
// 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;
}
}

View File

@@ -372,6 +372,13 @@ namespace Robust.Shared.GameObjects
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);
@@ -718,7 +725,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
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);
}
@@ -735,7 +742,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool HasComponent(EntityUid? uid, Type type)
public bool HasComponent([NotNullWhen(true)] EntityUid? uid, Type type)
{
if (!uid.HasValue)
{
@@ -760,7 +767,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
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)
{
@@ -1590,7 +1597,7 @@ namespace Robust.Shared.GameObjects
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool HasComp(EntityUid? uid) => HasComponent(uid);
public bool HasComp([NotNullWhen(true)] EntityUid? uid) => HasComponent(uid);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
@@ -1601,7 +1608,7 @@ namespace Robust.Shared.GameObjects
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool HasComponent(EntityUid? uid)
public bool HasComponent([NotNullWhen(true)] EntityUid? uid)
{
return uid != null && HasComponent(uid.Value);
}

View File

@@ -148,6 +148,29 @@ public partial class EntitySystem
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 : 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 : IComponentDelta
{
EntityManager.DirtyField(uid, component, fieldName, meta);
}
/// <summary>
/// Marks a component as dirty. This also implicitly dirties the entity this component belongs to.
/// </summary>

View File

@@ -77,6 +77,10 @@ namespace Robust.Shared.GameObjects
/// <returns>The availability of the component.</returns>
ComponentAvailability GetComponentAvailability(string componentName, bool ignoreCase = false);
public void RegisterNetworkedFields<T>(params string[] fields) where T : IComponent;
public void RegisterNetworkedFields(ComponentRegistration compReg, params string[] fields);
/// <summary>
/// Slow-path for Type -> CompIdx mapping without generics.
/// </summary>

View File

@@ -6,6 +6,11 @@ namespace Robust.Shared.GameObjects;
public partial interface IEntityManager
{
public void DirtyField(EntityUid uid, IComponentDelta delta, string fieldName, MetaDataComponent? metadata = null);
public void DirtyField<T>(EntityUid uid, T component, string fieldName, MetaDataComponent? metadata = null)
where T : IComponentDelta;
/// <summary>
/// Tries to parse a string as a NetEntity and return the relevant EntityUid.
/// </summary>

View File

@@ -1,4 +1,3 @@
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Physics;
@@ -17,9 +16,6 @@ namespace Robust.Shared.GameObjects
base.Initialize();
SubscribeLocalEvent<CollisionWakeComponent, ComponentShutdown>(OnRemove);
SubscribeLocalEvent<CollisionWakeComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<CollisionWakeComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<CollisionWakeComponent, JointAddedEvent>(OnJointAdd);
SubscribeLocalEvent<CollisionWakeComponent, JointRemovedEvent>(OnJointRemove);
@@ -43,22 +39,6 @@ namespace Robust.Shared.GameObjects
Dirty(uid, component);
}
private void OnHandleState(EntityUid uid, CollisionWakeComponent component, ref ComponentHandleState args)
{
if (args.Current is CollisionWakeComponent.CollisionWakeState state)
component.Enabled = state.Enabled;
// Note, this explicitly does not update PhysicsComponent.CanCollide. The physics component should perform
// its own state-handling logic. Additionally, if we wanted to set it you would have to ensure that things
// like the join-component and physics component have already handled their states, otherwise CanCollide may
// be set incorrectly and leave the client with a bad state.
}
private void OnGetState(EntityUid uid, CollisionWakeComponent component, ref ComponentGetState args)
{
args.State = new CollisionWakeComponent.CollisionWakeState(component.Enabled);
}
private void OnRemove(EntityUid uid, CollisionWakeComponent component, ComponentShutdown args)
{
if (component.Enabled

View File

@@ -63,7 +63,7 @@ public abstract class SharedEyeSystem : EntitySystem
eyeComponent.Offset = value;
eyeComponent.Eye.Offset = value;
Dirty(uid, eyeComponent);
DirtyField(uid, eyeComponent, nameof(EyeComponent.Offset));
}
public void SetDrawFov(EntityUid uid, bool value, EyeComponent? eyeComponent = null)
@@ -76,7 +76,7 @@ public abstract class SharedEyeSystem : EntitySystem
eyeComponent.DrawFov = value;
eyeComponent.Eye.DrawFov = value;
Dirty(uid, eyeComponent);
DirtyField(uid, eyeComponent, nameof(EyeComponent.DrawFov));
}
public void SetDrawLight(Entity<EyeComponent?> entity, bool value)
@@ -89,7 +89,7 @@ public abstract class SharedEyeSystem : EntitySystem
entity.Comp.DrawLight = value;
entity.Comp.Eye.DrawLight = value;
Dirty(entity);
DirtyField(entity, nameof(EyeComponent.DrawLight));
}
public void SetRotation(EntityUid uid, Angle rotation, EyeComponent? eyeComponent = null)
@@ -127,7 +127,7 @@ public abstract class SharedEyeSystem : EntitySystem
}
eyeComponent.Target = value;
Dirty(uid, eyeComponent);
DirtyField(uid, eyeComponent, nameof(EyeComponent.Target));
}
public void SetZoom(EntityUid uid, Vector2 value, EyeComponent? eyeComponent = null)
@@ -168,6 +168,6 @@ public abstract class SharedEyeSystem : EntitySystem
return;
eyeComponent.VisibilityMask = value;
Dirty(uid, eyeComponent);
DirtyField(uid, eyeComponent, nameof(EyeComponent.VisibilityMask));
}
}

View File

@@ -44,6 +44,11 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
{
base.Initialize();
EntityManager.ComponentFactory.RegisterNetworkedFields<UserInterfaceComponent>(
nameof(UserInterfaceComponent.Actors),
nameof(UserInterfaceComponent.Interfaces),
nameof(UserInterfaceComponent.States));
_ignoreUIRangeQuery = GetEntityQuery<IgnoreUIRangeComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
UIQuery = GetEntityQuery<UserInterfaceComponent>();
@@ -204,7 +209,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
if (actors.Count == 0)
ent.Comp.Actors.Remove(key);
Dirty(ent);
DirtyField(ent, nameof(UserInterfaceComponent.Actors));
// If the actor is also deleting then don't worry about updating what they have open.
if (!TerminatingOrDeleted(actor)
@@ -256,7 +261,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
// Let state handling open the UI clientside.
actorComp.OpenInterfaces.GetOrNew(ent.Owner).Add(key);
ent.Comp.Actors.GetOrNew(key).Add(actor);
Dirty(ent);
DirtyField(ent, nameof(UserInterfaceComponent.Actors));
var ev = new BoundUIOpenedEvent(key, ent.Owner, actor);
RaiseLocalEvent(ent.Owner, ev);
@@ -301,20 +306,49 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
private void OnUserInterfaceGetState(Entity<UserInterfaceComponent> ent, ref ComponentGetState args)
{
// TODO delta states.
// I.e., don't resend the whole BUI state just because a new user opened it.
if (ent.Comp.LastFieldUpdate >= args.FromTick)
{
var fields = EntityManager.GetModifiedFields(ent.Comp, args.FromTick);
switch (fields)
{
case 1 << 0:
{
var state = new UserInterfaceActorsDeltaState();
AddActors(ent, state.Actors, ref args);
args.State = state;
return;
}
case 1 << 2:
{
var state = new UserInterfaceStatesDeltaState()
{
States = new Dictionary<Enum, BoundUserInterfaceState>(ent.Comp.States),
};
args.State = state;
return;
}
}
}
var actors = new Dictionary<Enum, List<NetEntity>>();
var dataCopy = new Dictionary<Enum, InterfaceData>();
var dataCopy = new Dictionary<Enum, InterfaceData>(ent.Comp.Interfaces.Count);
foreach (var (weh, a) in ent.Comp.Interfaces)
{
dataCopy[weh] = new InterfaceData(a);
}
args.State = new UserInterfaceComponent.UserInterfaceComponentState(actors, ent.Comp.States, dataCopy);
args.State = new UserInterfaceComponentState(actors, new(ent.Comp.States), dataCopy);
// Ensure that only the player that currently has the UI open gets to know what they have it open.
AddActors(ent, actors, ref args);
}
private void AddActors(Entity<UserInterfaceComponent> ent, Dictionary<Enum, List<NetEntity>> actors, ref ComponentGetState args)
{
// Ensure that only the player that currently has the UI open gets to know what they have it open.
if (args.ReplayState)
{
@@ -336,107 +370,140 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
private void OnUserInterfaceHandleState(Entity<UserInterfaceComponent> ent, ref ComponentHandleState args)
{
if (args.Current is not UserInterfaceComponent.UserInterfaceComponentState state)
Dictionary<Enum, List<NetEntity>>? stateActors = null;
Dictionary<Enum, InterfaceData>? stateData = null;
Dictionary<Enum, BoundUserInterfaceState>? stateStates = null;
if (args.Current is UserInterfaceComponentState state)
{
stateActors = state.Actors;
stateData = state.Data;
stateStates = state.States;
}
else if (args.Current is UserInterfaceActorsDeltaState actorDelta)
{
stateActors = actorDelta.Actors;
}
else if (args.Current is UserInterfaceStatesDeltaState stateDelta)
{
stateStates = stateDelta.States;
}
else
{
return;
ent.Comp.Interfaces.Clear();
foreach (var data in state.Data)
{
ent.Comp.Interfaces[data.Key] = new(data.Value);
}
foreach (var key in ent.Comp.Actors.Keys)
// Interfaces
if (stateData != null)
{
if (!state.Actors.ContainsKey(key))
CloseUi(ent!, key);
}
ent.Comp.Interfaces.Clear();
var toRemoveActors = new ValueList<EntityUid>();
var newSet = new HashSet<EntityUid>();
foreach (var (key, stateActors) in state.Actors)
{
var actors = ent.Comp.Actors.GetOrNew(key);
newSet.Clear();
foreach (var netEntity in stateActors)
foreach (var data in stateData)
{
var uid = EnsureEntity<UserInterfaceComponent>(netEntity, ent.Owner);
if (uid.IsValid())
newSet.Add(uid);
ent.Comp.Interfaces[data.Key] = new(data.Value);
}
foreach (var actor in newSet)
{
if (!actors.Contains(actor))
OpenUiInternal(ent!, key, actor);
}
foreach (var actor in actors)
{
if (!newSet.Contains(actor))
toRemoveActors.Add(actor);
}
foreach (var actor in toRemoveActors)
{
CloseUiInternal(ent!, key, actor);
}
}
foreach (var key in ent.Comp.States.Keys)
{
if (!state.States.ContainsKey(key))
ent.Comp.States.Remove(key);
}
var attachedEnt = Player.LocalEntity;
var clientBuis = new ValueList<Enum>(ent.Comp.ClientOpenInterfaces.Keys);
// Check if the UI is open by us, otherwise dispose of it.
foreach (var key in clientBuis)
// Actors
if (stateActors != null)
{
if (ent.Comp.Actors.TryGetValue(key, out var actors) &&
(attachedEnt == null || actors.Contains(attachedEnt.Value)))
foreach (var key in ent.Comp.Actors.Keys)
{
continue;
if (!stateActors.ContainsKey(key))
CloseUi(ent!, key);
}
var bui = ent.Comp.ClientOpenInterfaces[key];
if (bui.DeferredClose)
_queuedCloses.Add(bui);
else
var toRemoveActors = new ValueList<EntityUid>();
var newSet = new HashSet<EntityUid>();
foreach (var (key, acts) in stateActors)
{
ent.Comp.ClientOpenInterfaces.Remove(key);
bui.Dispose();
var actors = ent.Comp.Actors.GetOrNew(key);
newSet.Clear();
foreach (var netEntity in acts)
{
var uid = EnsureEntity<UserInterfaceComponent>(netEntity, ent.Owner);
if (uid.IsValid())
newSet.Add(uid);
}
foreach (var actor in newSet)
{
if (!actors.Contains(actor))
OpenUiInternal(ent!, key, actor);
}
foreach (var actor in actors)
{
if (!newSet.Contains(actor))
toRemoveActors.Add(actor);
}
foreach (var actor in toRemoveActors)
{
CloseUiInternal(ent!, key, actor);
}
}
var clientBuis = new ValueList<Enum>(ent.Comp.ClientOpenInterfaces.Keys);
// Check if the UI is open by us, otherwise dispose of it.
foreach (var key in clientBuis)
{
if (ent.Comp.Actors.TryGetValue(key, out var actors) &&
(attachedEnt == null || actors.Contains(attachedEnt.Value)))
{
continue;
}
var bui = ent.Comp.ClientOpenInterfaces[key];
if (bui.DeferredClose)
_queuedCloses.Add(bui);
else
{
ent.Comp.ClientOpenInterfaces.Remove(key);
bui.Dispose();
}
}
}
// update any states we have open
foreach (var (key, buiState) in state.States)
// States
if (stateStates != null)
{
if (ent.Comp.States.TryGetValue(key, out var existing) &&
existing.Equals(buiState))
foreach (var key in ent.Comp.States.Keys)
{
continue;
if (!stateStates.ContainsKey(key))
ent.Comp.States.Remove(key);
}
ent.Comp.States[key] = buiState;
// update any states we have open
foreach (var (key, buiState) in stateStates)
{
if (ent.Comp.States.TryGetValue(key, out var existing) &&
existing.Equals(buiState))
{
continue;
}
if (!ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui))
continue;
ent.Comp.States[key] = buiState;
cBui.State = buiState;
cBui.UpdateState(buiState);
cBui.Update();
if (!ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui))
continue;
cBui.State = buiState;
cBui.UpdateState(buiState);
cBui.Update();
}
}
// If UI not open then open it
// If we get the first state for an ent coming in then don't open BUIs yet, just defer it until later.
var open = ent.Comp.LifeStage > ComponentLifeStage.Added;
if (attachedEnt != null)
if (attachedEnt != null && stateActors != null)
{
foreach (var (key, value) in ent.Comp.Interfaces)
{
@@ -672,7 +739,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
if (!entity.Comp.States.Remove(key))
return;
Dirty(entity);
DirtyField(entity, nameof(UserInterfaceComponent.States));
}
// Non-null state, check if it matches existing.
else
@@ -692,7 +759,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
bui.Update();
}
Dirty(entity);
DirtyField(entity, nameof(UserInterfaceComponent.States));
}
/// <summary>
@@ -1034,7 +1101,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
ent.Comp = AddComp<UserInterfaceComponent>(ent);
ent.Comp.Interfaces[key] = data;
Dirty(ent, ent.Comp);
DirtyField(ent, nameof(UserInterfaceComponent.Interfaces));
}
public bool TryGetUiState<T>(Entity<UserInterfaceComponent?> ent, Enum key, [NotNullWhen(true)] out T? state) where T : BoundUserInterfaceState

View File

@@ -30,13 +30,17 @@ using Robust.Shared.Maths;
using Robust.Shared.Physics.Dynamics.Contacts;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Physics.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class PhysicsComponent : Component
public sealed partial class PhysicsComponent : Component, IComponentDelta
{
public GameTick LastFieldUpdate { get; set; }
public GameTick[] LastModifiedFields { get; set; }
/// <summary>
/// Has this body been added to an island previously in this tick.
/// </summary>
@@ -57,10 +61,10 @@ public sealed partial class PhysicsComponent : Component
/// </summary>
internal readonly LinkedList<Contact> Contacts = new();
[DataField("ignorePaused"), ViewVariables(VVAccess.ReadWrite)]
[DataField]
public bool IgnorePaused;
[DataField("bodyType"), Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)]
[DataField, Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)]
public BodyType BodyType = BodyType.Static;
// We'll also block Static bodies from ever being awake given they don't need to move.
@@ -74,13 +78,11 @@ public sealed partial class PhysicsComponent : Component
/// body will be woken.
/// </summary>
/// <value><c>true</c> if sleeping is allowed; otherwise, <c>false</c>.</value>
[ViewVariables(VVAccess.ReadWrite), DataField("sleepingAllowed"),
Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute,
[DataField, Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute,
Other = AccessPermissions.Read)]
public bool SleepingAllowed = true;
[ViewVariables(VVAccess.ReadWrite), DataField("sleepTime"),
Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute,
[DataField, Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute,
Other = AccessPermissions.Read)]
public float SleepTime = 0f;
@@ -90,8 +92,7 @@ public sealed partial class PhysicsComponent : Component
/// <remarks>
/// Also known as Enabled in Box2D
/// </remarks>
[ViewVariables(VVAccess.ReadWrite), DataField("canCollide"),
Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute,
[DataField, Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute,
Other = AccessPermissions.Read)]
public bool CanCollide = true;
@@ -168,7 +169,7 @@ public sealed partial class PhysicsComponent : Component
/// <summary>
/// Is the body allowed to have angular velocity.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("fixedRotation"),
[ViewVariables(VVAccess.ReadWrite), DataField,
Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)]
public bool FixedRotation = true;
@@ -189,7 +190,7 @@ public sealed partial class PhysicsComponent : Component
/// The force is applied to the center of mass.
/// https://en.wikipedia.org/wiki/Force
/// </remarks>
[ViewVariables(VVAccess.ReadWrite), DataField("force"),
[ViewVariables(VVAccess.ReadWrite), DataField,
Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)]
public Vector2 Force;
@@ -200,7 +201,7 @@ public sealed partial class PhysicsComponent : Component
/// The torque rotates around the Z axis on the object.
/// https://en.wikipedia.org/wiki/Torque
/// </remarks>
[ViewVariables(VVAccess.ReadWrite), DataField("torque"),
[ViewVariables(VVAccess.ReadWrite), DataField,
Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)]
public float Torque;
@@ -216,7 +217,7 @@ public sealed partial class PhysicsComponent : Component
/// This is a set amount that the body's linear velocity is reduced by every tick.
/// Combined with the tile friction.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("linearDamping"),
[DataField,
Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute,
Other = AccessPermissions.Read)]
public float LinearDamping = 0.2f;
@@ -226,7 +227,7 @@ public sealed partial class PhysicsComponent : Component
/// Combined with the tile friction.
/// </summary>
/// <returns></returns>
[ViewVariables(VVAccess.ReadWrite), DataField("angularDamping"),
[DataField,
Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute,
Other = AccessPermissions.Read)]
public float AngularDamping = 0.2f;
@@ -260,7 +261,7 @@ public sealed partial class PhysicsComponent : Component
/// <summary>
/// The current status of the object
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("bodyStatus"), Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)]
[DataField, Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)]
public BodyStatus BodyStatus { get; set; }
[ViewVariables, Access(typeof(SharedPhysicsSystem))]

View File

@@ -1,35 +1,97 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
namespace Robust.Shared.Physics.Components;
/// <summary>
/// Average use-case of only linear velocity update
/// </summary>
[Serializable, NetSerializable]
public readonly record struct PhysicsComponentState(
bool CanCollide,
bool SleepingAllowed,
bool FixedRotation,
BodyStatus Status,
Vector2 LinearVelocity,
float AngularVelocity,
BodyType BodyType,
float Friction,
float LinearDamping,
float AngularDamping)
: IComponentState
public record struct PhysicsLinearVelocityDeltaState : IComponentDeltaState<PhysicsComponentState>
{
public readonly bool CanCollide = CanCollide;
public readonly bool SleepingAllowed = SleepingAllowed;
public readonly bool FixedRotation = FixedRotation;
public readonly BodyStatus Status = Status;
public Vector2 LinearVelocity;
public readonly Vector2 LinearVelocity = LinearVelocity;
public readonly float AngularVelocity = AngularVelocity;
public readonly BodyType BodyType = BodyType;
public void ApplyToFullState(PhysicsComponentState fullState)
{
fullState.LinearVelocity = LinearVelocity;
}
public readonly float Friction = Friction;
public readonly float LinearDamping = LinearDamping;
public readonly float AngularDamping = AngularDamping;
public PhysicsComponentState CreateNewFullState(PhysicsComponentState fullState)
{
var copy = new PhysicsComponentState(fullState)
{
LinearVelocity = LinearVelocity,
};
return copy;
}
}
/// <summary>
/// 2nd-most typical usecase of just velocity updates
/// </summary>
[Serializable, NetSerializable]
public record struct PhysicsVelocityDeltaState : IComponentDeltaState<PhysicsComponentState>
{
public Vector2 LinearVelocity;
public float AngularVelocity;
public void ApplyToFullState(PhysicsComponentState fullState)
{
fullState.LinearVelocity = LinearVelocity;
fullState.AngularVelocity = AngularVelocity;
}
public PhysicsComponentState CreateNewFullState(PhysicsComponentState fullState)
{
var copy = new PhysicsComponentState(fullState)
{
LinearVelocity = LinearVelocity,
AngularVelocity = AngularVelocity
};
return copy;
}
}
[Serializable, NetSerializable]
public sealed class PhysicsComponentState : IComponentState
{
public bool CanCollide;
public bool SleepingAllowed;
public bool FixedRotation;
public BodyStatus Status;
public Vector2 LinearVelocity;
public float AngularVelocity;
public BodyType BodyType;
public float Friction;
public float LinearDamping;
public float AngularDamping;
public Vector2 Force;
public float Torque;
public PhysicsComponentState() {}
public PhysicsComponentState(PhysicsComponentState existing)
{
CanCollide = existing.CanCollide;
SleepingAllowed = existing.SleepingAllowed;
FixedRotation = existing.FixedRotation;
Status = existing.Status;
LinearVelocity = existing.LinearVelocity;
AngularVelocity = existing.AngularVelocity;
BodyType = existing.BodyType;
Friction = existing.Friction;
LinearDamping = existing.LinearDamping;
AngularDamping = existing.AngularDamping;
Force = existing.Force;
Torque = existing.Torque;
}
}

View File

@@ -23,7 +23,9 @@
*/
using System;
using System.Collections.Generic;
using System.Numerics;
using Robust.Shared.Collections;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
@@ -69,39 +71,97 @@ public partial class SharedPhysicsSystem
private void OnPhysicsGetState(EntityUid uid, PhysicsComponent component, ref ComponentGetState args)
{
args.State = new PhysicsComponentState(
component.CanCollide,
component.SleepingAllowed,
component.FixedRotation,
component.BodyStatus,
component.LinearVelocity,
component.AngularVelocity,
component.BodyType,
component._friction,
component.LinearDamping,
component.AngularDamping);
if (args.FromTick > component.CreationTick && component.LastFieldUpdate >= args.FromTick)
{
var slowPath = false;
for (var i = 0; i < _angularVelocityIndex; i++)
{
var field = component.LastModifiedFields[i];
if (field < args.FromTick)
continue;
slowPath = true;
break;
}
// We can do a smaller delta with no list index overhead.
if (!slowPath)
{
var angularDirty = component.LastModifiedFields[_angularVelocityIndex] >= args.FromTick;
if (angularDirty)
{
args.State = new PhysicsVelocityDeltaState()
{
AngularVelocity = component.AngularVelocity,
LinearVelocity = component.LinearVelocity,
};
}
else
{
args.State = new PhysicsLinearVelocityDeltaState()
{
LinearVelocity = component.LinearVelocity,
};
}
return;
}
}
args.State = new PhysicsComponentState
{
CanCollide = component.CanCollide,
SleepingAllowed = component.SleepingAllowed,
FixedRotation = component.FixedRotation,
Status = component.BodyStatus,
LinearVelocity = component.LinearVelocity,
AngularVelocity = component.AngularVelocity,
BodyType = component.BodyType,
Friction = component._friction,
LinearDamping = component.LinearDamping,
AngularDamping = component.AngularDamping,
Force = component.Force,
Torque = component.Torque,
};
}
private void OnPhysicsHandleState(EntityUid uid, PhysicsComponent component, ref ComponentHandleState args)
{
if (args.Current is not PhysicsComponentState newState)
if (args.Current == null)
return;
SetSleepingAllowed(uid, component, newState.SleepingAllowed);
SetFixedRotation(uid, newState.FixedRotation, body: component);
SetCanCollide(uid, newState.CanCollide, body: component);
component.BodyStatus = newState.Status;
// So transform doesn't apply MapId in the HandleComponentState because ??? so MapId can still be 0.
// Fucking kill me, please. You have no idea deep the rabbit hole of shitcode goes to make this work.
TryComp<FixturesComponent>(uid, out var manager);
_fixturesQuery.TryComp(uid, out var manager);
SetLinearVelocity(uid, newState.LinearVelocity, body: component, manager: manager);
SetAngularVelocity(uid, newState.AngularVelocity, body: component, manager: manager);
SetBodyType(uid, newState.BodyType, manager, component);
SetFriction(uid, component, newState.Friction);
SetLinearDamping(uid, component, newState.LinearDamping);
SetAngularDamping(uid, component, newState.AngularDamping);
if (args.Current is PhysicsLinearVelocityDeltaState linearState)
{
SetLinearVelocity(uid, linearState.LinearVelocity, body: component, manager: manager);
}
else if (args.Current is PhysicsVelocityDeltaState velocityState)
{
SetLinearVelocity(uid, velocityState.LinearVelocity, body: component, manager: manager);
SetAngularVelocity(uid, velocityState.AngularVelocity, body: component, manager: manager);
}
else if (args.Current is PhysicsComponentState newState)
{
SetSleepingAllowed(uid, component, newState.SleepingAllowed);
SetFixedRotation(uid, newState.FixedRotation, body: component);
SetCanCollide(uid, newState.CanCollide, body: component);
component.BodyStatus = newState.Status;
SetLinearVelocity(uid, newState.LinearVelocity, body: component, manager: manager);
SetAngularVelocity(uid, newState.AngularVelocity, body: component, manager: manager);
SetBodyType(uid, newState.BodyType, manager, component);
SetFriction(uid, component, newState.Friction);
SetLinearDamping(uid, component, newState.LinearDamping);
SetAngularDamping(uid, component, newState.AngularDamping);
component.Force = newState.Force;
component.Torque = newState.Torque;
}
}
#endregion
@@ -132,7 +192,6 @@ public partial class SharedPhysicsSystem
body.Force += force;
body.Torque += Vector2Helpers.Cross(point - body._localCenter, force);
Dirty(uid, body);
}
public void ApplyForce(EntityUid uid, Vector2 force, FixturesComponent? manager = null, PhysicsComponent? body = null)
@@ -143,7 +202,6 @@ public partial class SharedPhysicsSystem
}
body.Force += force;
Dirty(uid, body);
}
public void ApplyTorque(EntityUid uid, float torque, FixturesComponent? manager = null, PhysicsComponent? body = null)
@@ -154,7 +212,7 @@ public partial class SharedPhysicsSystem
}
body.Torque += torque;
Dirty(uid, body);
DirtyField(uid, body, nameof(PhysicsComponent.Torque));
}
public void ApplyLinearImpulse(EntityUid uid, Vector2 impulse, FixturesComponent? manager = null, PhysicsComponent? body = null)
@@ -212,34 +270,29 @@ public partial class SharedPhysicsSystem
/// </summary>
public void ResetDynamics(EntityUid uid, PhysicsComponent body, bool dirty = true)
{
var updated = false;
if (body.Torque != 0f)
{
body.Torque = 0f;
updated = true;
DirtyField(uid, body, nameof(PhysicsComponent.Torque));
}
if (body.AngularVelocity != 0f)
{
body.AngularVelocity = 0f;
updated = true;
DirtyField(uid, body, nameof(PhysicsComponent.AngularVelocity));
}
if (body.Force != Vector2.Zero)
{
body.Force = Vector2.Zero;
updated = true;
DirtyField(uid, body, nameof(PhysicsComponent.Force));
}
if (body.LinearVelocity != Vector2.Zero)
{
body.LinearVelocity = Vector2.Zero;
updated = true;
DirtyField(uid, body, nameof(PhysicsComponent.LinearVelocity));
}
if (updated && dirty)
Dirty(uid, body);
}
public void ResetMassData(EntityUid uid, FixturesComponent? manager = null, PhysicsComponent? body = null)
@@ -275,7 +328,6 @@ public partial class SharedPhysicsSystem
if (((int) body.BodyType & (int) (BodyType.Kinematic | BodyType.Static)) != 0)
{
body._localCenter = Vector2.Zero;
Dirty(uid, body);
return;
}
@@ -309,8 +361,13 @@ public partial class SharedPhysicsSystem
body._localCenter = localCenter;
// Update center of mass velocity.
body.LinearVelocity += Vector2Helpers.Cross(body.AngularVelocity, localCenter - oldCenter);
Dirty(uid, body);
var comVelocityDiff = Vector2Helpers.Cross(body.AngularVelocity, localCenter - oldCenter);
if (comVelocityDiff != Vector2.Zero)
{
body.LinearVelocity += comVelocityDiff;
DirtyField(uid, body, nameof(PhysicsComponent.LinearVelocity));
}
if (body._mass == oldMass && body._inertia == oldInertia && oldCenter == localCenter)
return;
@@ -340,9 +397,7 @@ public partial class SharedPhysicsSystem
return false;
body.AngularVelocity = value;
if (dirty)
Dirty(uid, body);
DirtyField(uid, body, nameof(PhysicsComponent.AngularVelocity));
return true;
}
@@ -368,10 +423,7 @@ public partial class SharedPhysicsSystem
return false;
body.LinearVelocity = velocity;
if (dirty)
Dirty(uid, body);
DirtyField(uid, body, nameof(PhysicsComponent.LinearVelocity));
return true;
}
@@ -381,9 +433,7 @@ public partial class SharedPhysicsSystem
return;
body.AngularDamping = value;
if (dirty)
Dirty(uid, body);
DirtyField(uid, body, nameof(PhysicsComponent.AngularDamping));
}
public void SetLinearDamping(EntityUid uid, PhysicsComponent body, float value, bool dirty = true)
@@ -392,9 +442,7 @@ public partial class SharedPhysicsSystem
return;
body.LinearDamping = value;
if (dirty)
Dirty(uid, body);
DirtyField(uid, body, nameof(PhysicsComponent.LinearDamping));
}
[Obsolete("Use SetAwake with EntityUid<PhysicsComponent>")]
@@ -446,7 +494,6 @@ public partial class SharedPhysicsSystem
}
UpdateMapAwakeState(uid, body);
Dirty(ent);
}
public void TrySetBodyType(EntityUid uid, BodyType value, FixturesComponent? manager = null, PhysicsComponent? body = null, TransformComponent? xform = null)
@@ -474,8 +521,18 @@ public partial class SharedPhysicsSystem
if (body.BodyType == BodyType.Static)
{
SetAwake((uid, body), false);
body.LinearVelocity = Vector2.Zero;
body.AngularVelocity = 0.0f;
if (body.LinearVelocity != Vector2.Zero)
{
body.LinearVelocity = Vector2.Zero;
DirtyField(uid, body, nameof(PhysicsComponent.LinearVelocity));
}
if (body.AngularVelocity != 0f)
{
body.AngularVelocity = 0f;
DirtyField(uid, body, nameof(PhysicsComponent.AngularVelocity));
}
}
// Even if it's dynamic if it can't collide then don't force it awake.
else if (body.CanCollide)
@@ -483,8 +540,11 @@ public partial class SharedPhysicsSystem
SetAwake((uid, body), true);
}
body.Force = Vector2.Zero;
body.Torque = 0.0f;
if (body.Torque != 0f)
{
body.Torque = 0f;
DirtyField(uid, body, nameof(PhysicsComponent.Torque));
}
_broadphase.RegenerateContacts(uid, body, manager, xform);
@@ -501,9 +561,7 @@ public partial class SharedPhysicsSystem
return;
body.BodyStatus = status;
if (dirty)
Dirty(uid, body);
DirtyField(uid, body, nameof(PhysicsComponent.BodyStatus));
}
/// <summary>
@@ -554,10 +612,7 @@ public partial class SharedPhysicsSystem
var ev = new CollisionChangeEvent(uid, body, value);
RaiseLocalEvent(ref ev);
}
if (dirty)
Dirty(uid, body);
DirtyField(uid, body, nameof(PhysicsComponent.CanCollide));
return value;
}
@@ -567,11 +622,15 @@ public partial class SharedPhysicsSystem
return;
body.FixedRotation = value;
body.AngularVelocity = 0.0f;
ResetMassData(uid, manager: manager, body: body);
DirtyField(uid, body, nameof(PhysicsComponent.FixedRotation));
if (dirty)
Dirty(uid, body);
if (body.AngularVelocity != 0f)
{
body.AngularVelocity = 0.0f;
DirtyField(uid, body, nameof(PhysicsComponent.AngularVelocity));
}
ResetMassData(uid, manager: manager, body: body);
}
public void SetFriction(EntityUid uid, PhysicsComponent body, float value, bool dirty = true)
@@ -580,9 +639,7 @@ public partial class SharedPhysicsSystem
return;
body._friction = value;
if (dirty)
Dirty(uid, body);
DirtyField(uid, body, nameof(PhysicsComponent.Friction));
}
public void SetInertia(EntityUid uid, PhysicsComponent body, float value, bool dirty = true)
@@ -598,9 +655,7 @@ public partial class SharedPhysicsSystem
body._inertia = value - body.Mass * Vector2.Dot(body._localCenter, body._localCenter);
DebugTools.Assert(body._inertia > 0.0f);
body.InvI = 1.0f / body._inertia;
if (dirty)
Dirty(uid, body);
// Not networked
}
}
@@ -611,6 +666,7 @@ public partial class SharedPhysicsSystem
if (value.EqualsApprox(body._localCenter)) return;
body._localCenter = value;
// Not networked
}
public void SetSleepingAllowed(EntityUid uid, PhysicsComponent body, bool value, bool dirty = true)
@@ -622,9 +678,7 @@ public partial class SharedPhysicsSystem
SetAwake((uid, body), true);
body.SleepingAllowed = value;
if (dirty)
Dirty(uid, body);
DirtyField(uid, body, nameof(PhysicsComponent.SleepingAllowed));
}
public void SetSleepTime(PhysicsComponent body, float value)

View File

@@ -74,10 +74,32 @@ namespace Robust.Shared.Physics.Systems
protected EntityQuery<PhysicsMapComponent> PhysMapQuery;
protected EntityQuery<MapComponent> MapQuery;
private ComponentRegistration _physicsReg = default!;
private byte _angularVelocityIndex;
public override void Initialize()
{
base.Initialize();
_physicsReg = EntityManager.ComponentFactory.GetRegistration(CompIdx.Index<PhysicsComponent>());
// If you update this then update the delta state + GetState + HandleState!
EntityManager.ComponentFactory.RegisterNetworkedFields(_physicsReg,
nameof(PhysicsComponent.CanCollide),
nameof(PhysicsComponent.BodyStatus),
nameof(PhysicsComponent.BodyType),
nameof(PhysicsComponent.SleepingAllowed),
nameof(PhysicsComponent.FixedRotation),
nameof(PhysicsComponent.Friction),
nameof(PhysicsComponent.Force),
nameof(PhysicsComponent.Torque),
nameof(PhysicsComponent.LinearDamping),
nameof(PhysicsComponent.AngularDamping),
nameof(PhysicsComponent.AngularVelocity),
nameof(PhysicsComponent.LinearVelocity));
_angularVelocityIndex = 10;
_fixturesQuery = GetEntityQuery<FixturesComponent>();
PhysicsQuery = GetEntityQuery<PhysicsComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();

View File

@@ -70,7 +70,7 @@ namespace Robust.Shared.Serialization.Manager
{
foreach (var child in _reflectionManager.GetAllChildren(type))
{
if (child.IsAbstract || child.IsGenericTypeDefinition)
if (child.IsAbstract || child.IsGenericTypeDefinition || child.IsInterface)
continue;
yield return child;