From e5e8a2f8fbfb934c54cb7f47a59969effd92ad83 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 27 Dec 2024 17:38:37 +1300 Subject: [PATCH 1/4] Add WeakEntityReference --- .../GameObjects/EntityManager.Components.cs | 46 +++++++++++++++ .../GameObjects/EntitySystem.Proxy.cs | 32 +++++++++++ .../GameObjects/IEntityManager.Components.cs | 19 +++++++ .../GameObjects/WeakEntityReference.cs | 52 +++++++++++++++++ Robust.Shared/Map/MapSerializationContext.cs | 46 ++++++++++++++- Robust.Shared/Prototypes/EntProtoId.cs | 5 +- Robust.Shared/Prototypes/ProtoId.cs | 3 +- .../Prototypes/YamlValidationContext.cs | 41 ++++++++++++-- .../Exceptions/CopyToFailedException.cs | 5 +- .../Implementations/EntProtoIdSerializer.cs | 14 +---- .../Generic/ProtoIdSerializer.cs | 7 +-- .../Generic/WeakEntityReferenceSerializer.cs | 56 +++++++++++++++++++ 12 files changed, 296 insertions(+), 30 deletions(-) create mode 100644 Robust.Shared/GameObjects/WeakEntityReference.cs create mode 100644 Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/WeakEntityReferenceSerializer.cs diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index fa3798c2280..2a5edb048da 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -1242,6 +1242,52 @@ public CompRegistryEntityEnumerator CompRegistryQueryEnumerator(ComponentRegistr return new CompRegistryEntityEnumerator(this, trait1, registry); } + /// + public EntityUid? Resolve(ref WeakEntityReference weakRef) + { + if (weakRef.Entity != EntityUid.Invalid && EntityExists(weakRef.Entity)) + return weakRef.Entity; + + weakRef.Entity = EntityUid.Invalid; + return null; + } + + /// + public EntityUid? Resolve(ref WeakEntityReference? weakRef) + { + if (weakRef is not { } value) + return null; + + if (value.Entity != EntityUid.Invalid && EntityExists(value.Entity)) + return value.Entity; + + weakRef = null; + return null; + } + + /// + public Entity? Resolve(ref WeakEntityReference weakRef) where T : IComponent + { + if (weakRef.Entity != EntityUid.Invalid && TryGetComponent(weakRef.Entity, out T? comp)) + return new(weakRef.Entity, comp); + + weakRef.Entity = EntityUid.Invalid; + return null; + } + + /// + public Entity? Resolve(ref WeakEntityReference? weakRef) where T : IComponent + { + if (weakRef is not { } value) + return null; + + if (value.Entity != EntityUid.Invalid && TryGetComponent(value.Entity, out T? comp)) + return new(value.Entity, comp); + + weakRef = null; + return null; + } + public AllEntityQueryEnumerator AllEntityQueryEnumerator(Type comp) { DebugTools.Assert(comp.IsAssignableTo(typeof(IComponent))); diff --git a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs index a22b3225d51..69b9c40ed28 100644 --- a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs +++ b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs @@ -1447,4 +1447,36 @@ protected NetCoordinates[] GetNetCoordinatesArray(EntityCoordinates[] entities) } #endregion + + #region WeakEntityReference + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected EntityUid? Resolve(ref WeakEntityReference weakRef) + { + return EntityManager.Resolve(ref weakRef); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected EntityUid? Resolve(ref WeakEntityReference? weakRef) + { + return EntityManager.Resolve(ref weakRef); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected Entity? Resolve(ref WeakEntityReference weakRef) where T : IComponent + { + return EntityManager.Resolve(ref weakRef); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected Entity? Resolve(ref WeakEntityReference? weakRef) where T : IComponent + { + return EntityManager.Resolve(ref weakRef); + } + #endregion + } diff --git a/Robust.Shared/GameObjects/IEntityManager.Components.cs b/Robust.Shared/GameObjects/IEntityManager.Components.cs index 84b47c53f50..bab7fbd5124 100644 --- a/Robust.Shared/GameObjects/IEntityManager.Components.cs +++ b/Robust.Shared/GameObjects/IEntityManager.Components.cs @@ -450,6 +450,25 @@ public partial interface IEntityManager /// public CompRegistryEntityEnumerator CompRegistryQueryEnumerator(ComponentRegistry registry); + /// + /// Attempts to resolve the given into an that + /// corresponds to an existing entity. If the entity does not exist, the weak reference is invalidated. + /// + public EntityUid? Resolve(ref WeakEntityReference weakRef); + + /// + public EntityUid? Resolve(ref WeakEntityReference? weakRef); + + /// + /// Attempts to resolve the given into an existing entity with the specified + /// component and return the . If the entity does not exist or doesn't have the component, + /// the weak reference will get invalidated. + /// + public Entity? Resolve(ref WeakEntityReference weakRef) where T : IComponent; + + /// + public Entity? Resolve(ref WeakEntityReference? weakRef) where T : IComponent; + AllEntityQueryEnumerator AllEntityQueryEnumerator(Type comp); AllEntityQueryEnumerator AllEntityQueryEnumerator() diff --git a/Robust.Shared/GameObjects/WeakEntityReference.cs b/Robust.Shared/GameObjects/WeakEntityReference.cs new file mode 100644 index 00000000000..7f0575db197 --- /dev/null +++ b/Robust.Shared/GameObjects/WeakEntityReference.cs @@ -0,0 +1,52 @@ +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Robust.Shared.GameObjects; + +/// +/// This struct is just a wrapper around a that is intended to be used to store references to +/// entities in a context where there is no expectation that the entity still exists (has not been deleted). +/// +/// +/// The current convention is that a a non-null nullable EntityUid stored on a component should correspond to an +/// existing entity. Generally, if such an entity has since been deleted or had a relevant component removed, this +/// can result in errors being logged. This struct exists to for cases where you want to store an entity reference, +/// while making it clear that there is no expectation that it should continue to be valid, which also means you do not +/// need to clean up any references upon deletion or component removal. +/// +/// +/// When saving a map, any weak references to entities that are not being included in the save file are automatically +/// ignored. +/// +[CopyByRef] +public record struct WeakEntityReference +{ + // Internal to dissuade anyone from accessing the field directly. + // If made public to be more permissive for whatever reason, maybe add [Obsolete] do something else to generate a + // warning to prevent accidental misuse? + [ViewVariables] internal EntityUid Entity; + public override int GetHashCode() => Entity.GetHashCode(); + public static readonly WeakEntityReference Invalid = new(EntityUid.Invalid); + + public WeakEntityReference(EntityUid uid) + { + Entity = uid; + } +} + +/// +/// Variant of that is only considered valid if the entity exists and still has the +/// specified component. +/// +[CopyByRef] +public record struct WeakEntityReference where T : IComponent +{ + [ViewVariables] internal EntityUid Entity; + public override int GetHashCode() => Entity.GetHashCode(); + public static readonly WeakEntityReference Invalid = new(EntityUid.Invalid); + + public WeakEntityReference(EntityUid uid) + { + Entity = uid; + } +} diff --git a/Robust.Shared/Map/MapSerializationContext.cs b/Robust.Shared/Map/MapSerializationContext.cs index 72d106d4597..0f5d234f5f6 100644 --- a/Robust.Shared/Map/MapSerializationContext.cs +++ b/Robust.Shared/Map/MapSerializationContext.cs @@ -17,7 +17,8 @@ namespace Robust.Shared.Map; internal sealed class MapSerializationContext : ISerializationContext, IEntityLoadContext, - ITypeSerializer + ITypeSerializer, + ITypeSerializer { public SerializationManager.SerializerProvider SerializerProvider { get; } = new(); @@ -160,7 +161,6 @@ EntityUid ITypeReader.Read(ISerializationManager seria .Error("Error in map file: found local entity UID '{0}' which does not exist.", val); return EntityUid.Invalid; - } [MustUseReturnValue] @@ -170,4 +170,46 @@ public EntityUid Copy(ISerializationManager serializationManager, EntityUid sour { return new((int)source); } + + public WeakEntityReference Read( + ISerializationManager serializationManager, + ValueDataNode node, + IDependencyCollection dependencies, + SerializationHookContext hookCtx, + ISerializationContext? context = null, + ISerializationManager.InstantiationDelegate? instanceProvider = null) + { + if (node.Value != "null" && int.TryParse(node.Value, out var val) && _uidEntityMap.TryGetValue(val, out var entity)) + return new(entity); + + return WeakEntityReference.Invalid; + } + + public DataNode Write( + ISerializationManager serializationManager, + WeakEntityReference value, + IDependencyCollection dependencies, + bool alwaysWrite = false, + ISerializationContext? context = null) + { + if (!_entityUidMap.TryGetValue(value.Entity, out var entityUidMapped)) + return new ValueDataNode("invalid"); + + return new ValueDataNode(entityUidMapped.ToString(CultureInfo.InvariantCulture)); + } + + ValidationNode ITypeValidator.Validate( + ISerializationManager serializationManager, + ValueDataNode node, + IDependencyCollection dependencies, + ISerializationContext? context) + { + if (node.Value is "invalid") + return new ValidatedValueNode(node); + + if (!int.TryParse(node.Value, out var val)) + return new ErrorNode(node, "Invalid WeakEntityReference", true); + + return new ValidatedValueNode(node); + } } diff --git a/Robust.Shared/Prototypes/EntProtoId.cs b/Robust.Shared/Prototypes/EntProtoId.cs index f0ba69dd2db..21583f97bd3 100644 --- a/Robust.Shared/Prototypes/EntProtoId.cs +++ b/Robust.Shared/Prototypes/EntProtoId.cs @@ -3,6 +3,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.TypeSerializers.Implementations; using Robust.Shared.Toolshed.TypeParsers; @@ -16,7 +17,7 @@ namespace Robust.Shared.Prototypes; /// This will be automatically validated by if used in data fields. /// /// for a wrapper of other prototype kinds. -[Serializable, NetSerializable] +[Serializable, NetSerializable, CopyByRef] public readonly record struct EntProtoId(string Id) : IEquatable, IComparable, IAsType { public static implicit operator string(EntProtoId protoId) @@ -55,7 +56,7 @@ public int CompareTo(EntProtoId other) } /// -[Serializable] +[Serializable, CopyByRef] public readonly record struct EntProtoId(string Id) : IEquatable, IComparable where T : IComponent, new() { public static implicit operator string(EntProtoId protoId) diff --git a/Robust.Shared/Prototypes/ProtoId.cs b/Robust.Shared/Prototypes/ProtoId.cs index 8d69ad02b17..2be07ad2ced 100644 --- a/Robust.Shared/Prototypes/ProtoId.cs +++ b/Robust.Shared/Prototypes/ProtoId.cs @@ -1,4 +1,5 @@ using System; +using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; using Robust.Shared.Toolshed.TypeParsers; @@ -13,7 +14,7 @@ namespace Robust.Shared.Prototypes; /// This will be automatically validated by if used in data fields. /// /// for an alias. -[Serializable] +[Serializable, CopyByRef] [PreferOtherType(typeof(EntityPrototype), typeof(EntProtoId))] public readonly record struct ProtoId(string Id) : IEquatable, diff --git a/Robust.Shared/Prototypes/YamlValidationContext.cs b/Robust.Shared/Prototypes/YamlValidationContext.cs index 1838f9e8807..04060660988 100644 --- a/Robust.Shared/Prototypes/YamlValidationContext.cs +++ b/Robust.Shared/Prototypes/YamlValidationContext.cs @@ -1,5 +1,4 @@ using System.Globalization; -using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Serialization; @@ -11,7 +10,9 @@ namespace Robust.Shared.Prototypes; -internal sealed class YamlValidationContext : ISerializationContext, ITypeSerializer +internal sealed class YamlValidationContext : ISerializationContext, + ITypeSerializer, + ITypeSerializer { public SerializationManager.SerializerProvider SerializerProvider { get; } = new(); public bool WritingReadingPrototypes => true; @@ -52,11 +53,39 @@ EntityUid ITypeReader.Read(ISerializationManager seria return EntityUid.Parse(node.Value); } - [MustUseReturnValue] - public EntityUid Copy(ISerializationManager serializationManager, EntityUid source, EntityUid target, - bool skipHook, + public ValidationNode Validate( + ISerializationManager serializationManager, + ValueDataNode node, + IDependencyCollection dependencies, ISerializationContext? context = null) { - return new((int)source); + if (node.Value == "invalid") + return new ValidatedValueNode(node); + + return new ErrorNode(node, "Prototypes should not contain EntityUids", true); + } + + public WeakEntityReference Read( + ISerializationManager serializationManager, + ValueDataNode node, + IDependencyCollection dependencies, + SerializationHookContext hookCtx, + ISerializationContext? context = null, + ISerializationManager.InstantiationDelegate? instanceProvider = null) + { + throw new System.NotImplementedException(); + } + + public DataNode Write( + ISerializationManager serializationManager, + WeakEntityReference value, + IDependencyCollection dependencies, + bool alwaysWrite = false, + ISerializationContext? context = null) + { + if (!value.Entity.Valid) + return new ValueDataNode("invalid"); + + return new ValueDataNode(value.Entity.Id.ToString(CultureInfo.InvariantCulture)); } } diff --git a/Robust.Shared/Serialization/Manager/Exceptions/CopyToFailedException.cs b/Robust.Shared/Serialization/Manager/Exceptions/CopyToFailedException.cs index edbb06b9645..f552b90fb9b 100644 --- a/Robust.Shared/Serialization/Manager/Exceptions/CopyToFailedException.cs +++ b/Robust.Shared/Serialization/Manager/Exceptions/CopyToFailedException.cs @@ -1,8 +1,11 @@ using System; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.TypeSerializers.Interfaces; namespace Robust.Shared.Serialization.Manager.Exceptions; public sealed class CopyToFailedException : Exception { - public override string Message => $"Failed performing CopyTo for Type {typeof(T)}"; + public override string Message + => $"Failed performing CopyTo for Type {typeof(T)}. Did you forget to create a {nameof(ITypeCopier)} implementation? Or maybe {typeof(T)} should have the {nameof(CopyByRefAttribute)}?"; } diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/EntProtoIdSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/EntProtoIdSerializer.cs index 491d611ee02..6b2a938a1fe 100644 --- a/Robust.Shared/Serialization/TypeSerializers/Implementations/EntProtoIdSerializer.cs +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/EntProtoIdSerializer.cs @@ -17,7 +17,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations; /// Serializer used automatically for types. /// [TypeSerializer] -public sealed class EntProtoIdSerializer : ITypeSerializer, ITypeCopyCreator +public sealed class EntProtoIdSerializer : ITypeSerializer { public ValidationNode Validate(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null) { @@ -37,18 +37,13 @@ public DataNode Write(ISerializationManager serialization, EntProtoId value, IDe { return new ValueDataNode(value.Id); } - - public EntProtoId CreateCopy(ISerializationManager serializationManager, EntProtoId source, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null) - { - return source; - } } /// /// Serializer used automatically for types. /// [TypeSerializer] -public sealed class EntProtoIdSerializer : ITypeSerializer, ValueDataNode>, ITypeCopyCreator> where T : IComponent, new() +public sealed class EntProtoIdSerializer : ITypeSerializer, ValueDataNode> where T : IComponent, new() { public ValidationNode Validate(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null) { @@ -83,9 +78,4 @@ public DataNode Write(ISerializationManager serialization, EntProtoId value, { return new ValueDataNode(value.Id); } - - public EntProtoId CreateCopy(ISerializationManager serializationManager, EntProtoId source, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null) - { - return source; - } } diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/ProtoIdSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/ProtoIdSerializer.cs index 5b0ba295c6c..00c2fd24045 100644 --- a/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/ProtoIdSerializer.cs +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/ProtoIdSerializer.cs @@ -15,7 +15,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; /// /// The type of the prototype for which the id is stored. [TypeSerializer] -public sealed class ProtoIdSerializer : ITypeSerializer, ValueDataNode>, ITypeCopyCreator> where T : class, IPrototype +public sealed class ProtoIdSerializer : ITypeSerializer, ValueDataNode> where T : class, IPrototype { public ValidationNode Validate(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null) { @@ -35,9 +35,4 @@ public DataNode Write(ISerializationManager serialization, ProtoId value, IDe { return new ValueDataNode(value.Id); } - - public ProtoId CreateCopy(ISerializationManager serializationManager, ProtoId source, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null) - { - return source; - } } diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/WeakEntityReferenceSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/WeakEntityReferenceSerializer.cs new file mode 100644 index 00000000000..57615d7f335 --- /dev/null +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/WeakEntityReferenceSerializer.cs @@ -0,0 +1,56 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.Markdown; +using Robust.Shared.Serialization.Markdown.Validation; +using Robust.Shared.Serialization.Markdown.Value; +using Robust.Shared.Serialization.TypeSerializers.Interfaces; +using static Robust.Shared.Serialization.Manager.ISerializationManager; + +namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; + +// This specifically implements WeakEntityReference, but not WeakEntityReference for the same reason that there is no +// EntityUid serializer: So that it can be implemented by the entity (de)serialization context. +// Ideally I'd also leave that there instead of here, but it needs generics... +[TypeSerializer] +public sealed class WeakEntityReferenceSerializer : + ITypeSerializer, ValueDataNode> + where T : class, IComponent +{ + public WeakEntityReference Read( + ISerializationManager serializationManager, + ValueDataNode node, + IDependencyCollection dependencies, + SerializationHookContext hookCtx, + ISerializationContext? context = null, + InstantiationDelegate>? instanceProvider = null) + { + var val = serializationManager.Read(node, hookCtx, context); + return new(val.Entity); + } + + public DataNode Write( + ISerializationManager serializationManager, + WeakEntityReference value, + IDependencyCollection dependencies, + bool alwaysWrite = false, + ISerializationContext? context = null) + { + var uid = context is MapSerializationContext ctx && !ctx.EntityManager.HasComponent(value.Entity) + ? EntityUid.Invalid + : value.Entity; + + return serializationManager.WriteValue(new WeakEntityReference(uid), alwaysWrite, context); + } + + public ValidationNode Validate( + ISerializationManager serializationManager, + ValueDataNode node, + IDependencyCollection dependencies, + ISerializationContext? context) + { + return serializationManager.ValidateNode(node, context); + } +} From 13720da8072b138e5832e8cc251ef391426ba935 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 27 Dec 2024 19:28:30 +1300 Subject: [PATCH 2/4] Use NetEntity --- .../GameObjects/EntityManager.Components.cs | 43 ++++++++----------- .../GameObjects/EntitySystem.Proxy.cs | 18 ++++---- .../GameObjects/IEntityManager.Components.cs | 19 ++++---- .../GameObjects/WeakEntityReference.cs | 32 ++++---------- 4 files changed, 45 insertions(+), 67 deletions(-) diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index 2a5edb048da..fa5094ff355 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -1243,49 +1243,40 @@ public CompRegistryEntityEnumerator CompRegistryQueryEnumerator(ComponentRegistr } /// - public EntityUid? Resolve(ref WeakEntityReference weakRef) + public EntityUid? Resolve(WeakEntityReference weakRef) { - if (weakRef.Entity != EntityUid.Invalid && EntityExists(weakRef.Entity)) - return weakRef.Entity; + if (weakRef.Entity != NetEntity.Invalid + && TryGetEntity(weakRef.Entity, out var ent)) + { + return ent.Value; + } - weakRef.Entity = EntityUid.Invalid; return null; } /// - public EntityUid? Resolve(ref WeakEntityReference? weakRef) + public EntityUid? Resolve(WeakEntityReference? weakRef) { - if (weakRef is not { } value) - return null; - - if (value.Entity != EntityUid.Invalid && EntityExists(value.Entity)) - return value.Entity; - - weakRef = null; - return null; + return weakRef == null ? null : Resolve(weakRef.Value); } /// - public Entity? Resolve(ref WeakEntityReference weakRef) where T : IComponent + public Entity? Resolve(WeakEntityReference weakRef) where T : IComponent { - if (weakRef.Entity != EntityUid.Invalid && TryGetComponent(weakRef.Entity, out T? comp)) - return new(weakRef.Entity, comp); + if (weakRef.Entity != NetEntity.Invalid + && TryGetEntity(weakRef.Entity, out var ent) + && TryGetComponent(ent.Value, out T? comp)) + { + return new(ent.Value, comp); + } - weakRef.Entity = EntityUid.Invalid; return null; } /// - public Entity? Resolve(ref WeakEntityReference? weakRef) where T : IComponent + public Entity? Resolve(WeakEntityReference? weakRef) where T : IComponent { - if (weakRef is not { } value) - return null; - - if (value.Entity != EntityUid.Invalid && TryGetComponent(value.Entity, out T? comp)) - return new(value.Entity, comp); - - weakRef = null; - return null; + return weakRef == null ? null : Resolve(weakRef.Value); } public AllEntityQueryEnumerator AllEntityQueryEnumerator(Type comp) diff --git a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs index 69b9c40ed28..f0c33262e3c 100644 --- a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs +++ b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs @@ -1450,32 +1450,32 @@ protected NetCoordinates[] GetNetCoordinatesArray(EntityCoordinates[] entities) #region WeakEntityReference - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected EntityUid? Resolve(ref WeakEntityReference weakRef) { - return EntityManager.Resolve(ref weakRef); + return EntityManager.Resolve(weakRef); } - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected EntityUid? Resolve(ref WeakEntityReference? weakRef) { - return EntityManager.Resolve(ref weakRef); + return EntityManager.Resolve(weakRef); } - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected Entity? Resolve(ref WeakEntityReference weakRef) where T : IComponent { - return EntityManager.Resolve(ref weakRef); + return EntityManager.Resolve(weakRef); } - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected Entity? Resolve(ref WeakEntityReference? weakRef) where T : IComponent + protected Entity? Resolve(WeakEntityReference? weakRef) where T : IComponent { - return EntityManager.Resolve(ref weakRef); + return EntityManager.Resolve(weakRef); } #endregion diff --git a/Robust.Shared/GameObjects/IEntityManager.Components.cs b/Robust.Shared/GameObjects/IEntityManager.Components.cs index bab7fbd5124..aaca2068017 100644 --- a/Robust.Shared/GameObjects/IEntityManager.Components.cs +++ b/Robust.Shared/GameObjects/IEntityManager.Components.cs @@ -452,22 +452,23 @@ public partial interface IEntityManager /// /// Attempts to resolve the given into an that - /// corresponds to an existing entity. If the entity does not exist, the weak reference is invalidated. + /// corresponds to an existing entity. If this fails, the entity has either been deleted, or for clients, the + /// entity may not yet have been sent to them. /// - public EntityUid? Resolve(ref WeakEntityReference weakRef); + public EntityUid? Resolve(WeakEntityReference weakRef); - /// - public EntityUid? Resolve(ref WeakEntityReference? weakRef); + /// + public EntityUid? Resolve(WeakEntityReference? weakRef); /// /// Attempts to resolve the given into an existing entity with the specified - /// component and return the . If the entity does not exist or doesn't have the component, - /// the weak reference will get invalidated. + /// component and return the . If this fails, the entity has either been deleted, or for + /// clients, the entity may not yet have been sent to them. /// - public Entity? Resolve(ref WeakEntityReference weakRef) where T : IComponent; + public Entity? Resolve(WeakEntityReference weakRef) where T : IComponent; - /// - public Entity? Resolve(ref WeakEntityReference? weakRef) where T : IComponent; + /// + public Entity? Resolve(WeakEntityReference? weakRef) where T : IComponent; AllEntityQueryEnumerator AllEntityQueryEnumerator(Type comp); diff --git a/Robust.Shared/GameObjects/WeakEntityReference.cs b/Robust.Shared/GameObjects/WeakEntityReference.cs index 7f0575db197..9b2ffec2ced 100644 --- a/Robust.Shared/GameObjects/WeakEntityReference.cs +++ b/Robust.Shared/GameObjects/WeakEntityReference.cs @@ -1,10 +1,11 @@ -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.ViewVariables; +using System; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; namespace Robust.Shared.GameObjects; /// -/// This struct is just a wrapper around a that is intended to be used to store references to +/// This struct is just a wrapper around a that is intended to be used to store references to /// entities in a context where there is no expectation that the entity still exists (has not been deleted). /// /// @@ -18,20 +19,11 @@ namespace Robust.Shared.GameObjects; /// When saving a map, any weak references to entities that are not being included in the save file are automatically /// ignored. /// -[CopyByRef] -public record struct WeakEntityReference +[CopyByRef, Serializable, NetSerializable] +public record struct WeakEntityReference(NetEntity Entity) { - // Internal to dissuade anyone from accessing the field directly. - // If made public to be more permissive for whatever reason, maybe add [Obsolete] do something else to generate a - // warning to prevent accidental misuse? - [ViewVariables] internal EntityUid Entity; public override int GetHashCode() => Entity.GetHashCode(); - public static readonly WeakEntityReference Invalid = new(EntityUid.Invalid); - - public WeakEntityReference(EntityUid uid) - { - Entity = uid; - } + public static readonly WeakEntityReference Invalid = new(NetEntity.Invalid); } /// @@ -39,14 +31,8 @@ public WeakEntityReference(EntityUid uid) /// specified component. /// [CopyByRef] -public record struct WeakEntityReference where T : IComponent +public record struct WeakEntityReference(NetEntity Entity) where T : IComponent { - [ViewVariables] internal EntityUid Entity; public override int GetHashCode() => Entity.GetHashCode(); - public static readonly WeakEntityReference Invalid = new(EntityUid.Invalid); - - public WeakEntityReference(EntityUid uid) - { - Entity = uid; - } + public static readonly WeakEntityReference Invalid = new(NetEntity.Invalid); } From 7246b6835ba0cd176e3d791dfac8168305aa92b2 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 27 Dec 2024 20:17:39 +1300 Subject: [PATCH 3/4] release notes --- RELEASE-NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index a0daaf128e8..a2ca8c15796 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* Added a new `WeakEntityReference` struct that is intended to be used by component data-fields to refer to entities that may or may not still exist. ### Bugfixes From a59beae3e72e224c43b64a273cd932acebf2fbd5 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 27 Dec 2024 20:32:23 +1300 Subject: [PATCH 4/4] A --- Robust.Shared/Map/MapSerializationContext.cs | 13 ++++++++++--- Robust.Shared/Prototypes/YamlValidationContext.cs | 5 ++++- .../Generic/WeakEntityReferenceSerializer.cs | 12 ++++++++---- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Robust.Shared/Map/MapSerializationContext.cs b/Robust.Shared/Map/MapSerializationContext.cs index 0f5d234f5f6..08f585c6c3b 100644 --- a/Robust.Shared/Map/MapSerializationContext.cs +++ b/Robust.Shared/Map/MapSerializationContext.cs @@ -179,8 +179,12 @@ public WeakEntityReference Read( ISerializationContext? context = null, ISerializationManager.InstantiationDelegate? instanceProvider = null) { - if (node.Value != "null" && int.TryParse(node.Value, out var val) && _uidEntityMap.TryGetValue(val, out var entity)) - return new(entity); + if (node.Value != "null" + && int.TryParse(node.Value, out var val) + && _uidEntityMap.TryGetValue(val, out var entity)) + { + return new(EntityManager.GetNetEntity(entity)); + } return WeakEntityReference.Invalid; } @@ -192,8 +196,11 @@ public DataNode Write( bool alwaysWrite = false, ISerializationContext? context = null) { - if (!_entityUidMap.TryGetValue(value.Entity, out var entityUidMapped)) + if (!EntityManager.TryGetEntity(value.Entity, out var uid) + || !_entityUidMap.TryGetValue(uid.Value, out var entityUidMapped)) + { return new ValueDataNode("invalid"); + } return new ValueDataNode(entityUidMapped.ToString(CultureInfo.InvariantCulture)); } diff --git a/Robust.Shared/Prototypes/YamlValidationContext.cs b/Robust.Shared/Prototypes/YamlValidationContext.cs index 04060660988..491e4937bea 100644 --- a/Robust.Shared/Prototypes/YamlValidationContext.cs +++ b/Robust.Shared/Prototypes/YamlValidationContext.cs @@ -73,7 +73,10 @@ public WeakEntityReference Read( ISerializationContext? context = null, ISerializationManager.InstantiationDelegate? instanceProvider = null) { - throw new System.NotImplementedException(); + if (node.Value == "invalid") + return WeakEntityReference.Invalid; + + return new WeakEntityReference(NetEntity.Parse(node.Value)); } public DataNode Write( diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/WeakEntityReferenceSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/WeakEntityReferenceSerializer.cs index 57615d7f335..78c3bc64fc0 100644 --- a/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/WeakEntityReferenceSerializer.cs +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/WeakEntityReferenceSerializer.cs @@ -38,11 +38,15 @@ public DataNode Write( bool alwaysWrite = false, ISerializationContext? context = null) { - var uid = context is MapSerializationContext ctx && !ctx.EntityManager.HasComponent(value.Entity) - ? EntityUid.Invalid - : value.Entity; + NetEntity val = value.Entity; - return serializationManager.WriteValue(new WeakEntityReference(uid), alwaysWrite, context); + if (context is MapSerializationContext ctx) + { + if (!ctx.EntityManager.TryGetEntity(val, out var uid) || !ctx.EntityManager.HasComponent(uid)) + val = NetEntity.Invalid; + } + + return serializationManager.WriteValue(new WeakEntityReference(val), alwaysWrite, context); } public ValidationNode Validate(