mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"Comp State Generator": {
|
||||
"commandName": "DebugRoslynComponent",
|
||||
"targetProject": "../../Content.Shared/Content.Shared.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
78
Robust.Shared/GameObjects/EntityManager.ComponentDeltas.cs
Normal file
78
Robust.Shared/GameObjects/EntityManager.ComponentDeltas.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user