Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add WeakEntityReference #5577

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
37 changes: 37 additions & 0 deletions Robust.Shared/GameObjects/EntityManager.Components.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,43 @@ public CompRegistryEntityEnumerator CompRegistryQueryEnumerator(ComponentRegistr
return new CompRegistryEntityEnumerator(this, trait1, registry);
}

/// <inheritdoc />
public EntityUid? Resolve(WeakEntityReference weakRef)
{
if (weakRef.Entity != NetEntity.Invalid
&& TryGetEntity(weakRef.Entity, out var ent))
{
return ent.Value;
}

return null;
}

/// <inheritdoc />
public EntityUid? Resolve(WeakEntityReference? weakRef)
{
return weakRef == null ? null : Resolve(weakRef.Value);
}

/// <inheritdoc />
public Entity<T>? Resolve<T>(WeakEntityReference<T> weakRef) where T : IComponent
{
if (weakRef.Entity != NetEntity.Invalid
&& TryGetEntity(weakRef.Entity, out var ent)
&& TryGetComponent(ent.Value, out T? comp))
{
return new(ent.Value, comp);
}

return null;
}

/// <inheritdoc />
public Entity<T>? Resolve<T>(WeakEntityReference<T>? weakRef) where T : IComponent
{
return weakRef == null ? null : Resolve(weakRef.Value);
}

public AllEntityQueryEnumerator<IComponent> AllEntityQueryEnumerator(Type comp)
{
DebugTools.Assert(comp.IsAssignableTo(typeof(IComponent)));
Expand Down
32 changes: 32 additions & 0 deletions Robust.Shared/GameObjects/EntitySystem.Proxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1447,4 +1447,36 @@ protected NetCoordinates[] GetNetCoordinatesArray(EntityCoordinates[] entities)
}

#endregion

#region WeakEntityReference

/// <inheritdoc cref="IEntityManager.Resolve(WeakEntityReference)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid? Resolve(ref WeakEntityReference weakRef)
{
return EntityManager.Resolve(weakRef);
}

/// <inheritdoc cref="IEntityManager.Resolve(WeakEntityReference)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid? Resolve(ref WeakEntityReference? weakRef)
{
return EntityManager.Resolve(weakRef);
}

/// <inheritdoc cref="IEntityManager.Resolve{T}(ref WeakEntityReference{T})"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Entity<T>? Resolve<T>(ref WeakEntityReference<T> weakRef) where T : IComponent
{
return EntityManager.Resolve(weakRef);
}

/// <inheritdoc cref="IEntityManager.Resolve{T}(WeakEntityReference{T})"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Entity<T>? Resolve<T>(WeakEntityReference<T>? weakRef) where T : IComponent
{
return EntityManager.Resolve(weakRef);
}
#endregion

}
20 changes: 20 additions & 0 deletions Robust.Shared/GameObjects/IEntityManager.Components.cs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,26 @@ public partial interface IEntityManager
/// </summary>
public CompRegistryEntityEnumerator CompRegistryQueryEnumerator(ComponentRegistry registry);

/// <summary>
/// Attempts to resolve the given <see cref="WeakEntityReference"/> into an <see cref="EntityUid"/> that
/// 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.
/// </summary>
public EntityUid? Resolve(WeakEntityReference weakRef);

/// <inheritdoc cref="Resolve(WeakEntityReference)"/>
public EntityUid? Resolve(WeakEntityReference? weakRef);

/// <summary>
/// Attempts to resolve the given <see cref="WeakEntityReference"/> into an existing entity with the specified
/// component and return the <see cref="Entity{T}"/>. If this fails, the entity has either been deleted, or for
/// clients, the entity may not yet have been sent to them.
/// </summary>
public Entity<T>? Resolve<T>(WeakEntityReference<T> weakRef) where T : IComponent;

/// <inheritdoc cref="Resolve{T}(WeakEntityReference{T})"/>
public Entity<T>? Resolve<T>(WeakEntityReference<T>? weakRef) where T : IComponent;

AllEntityQueryEnumerator<IComponent> AllEntityQueryEnumerator(Type comp);

AllEntityQueryEnumerator<TComp1> AllEntityQueryEnumerator<TComp1>()
Expand Down
38 changes: 38 additions & 0 deletions Robust.Shared/GameObjects/WeakEntityReference.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;

namespace Robust.Shared.GameObjects;

/// <summary>
/// This struct is just a wrapper around a <see cref="NetEntity"/> 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).
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <remarks>
/// When saving a map, any weak references to entities that are not being included in the save file are automatically
/// ignored.
/// </remarks>
[CopyByRef, Serializable, NetSerializable]
public record struct WeakEntityReference(NetEntity Entity)
{
public override int GetHashCode() => Entity.GetHashCode();
public static readonly WeakEntityReference Invalid = new(NetEntity.Invalid);
}

/// <summary>
/// Variant of <see cref="WeakEntityReference"/> that is only considered valid if the entity exists and still has the
/// specified component.
/// </summary>
[CopyByRef]
public record struct WeakEntityReference<T>(NetEntity Entity) where T : IComponent
{
public override int GetHashCode() => Entity.GetHashCode();
public static readonly WeakEntityReference<T> Invalid = new(NetEntity.Invalid);
}
53 changes: 51 additions & 2 deletions Robust.Shared/Map/MapSerializationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
namespace Robust.Shared.Map;

internal sealed class MapSerializationContext : ISerializationContext, IEntityLoadContext,
ITypeSerializer<EntityUid, ValueDataNode>
ITypeSerializer<EntityUid, ValueDataNode>,
ITypeSerializer<WeakEntityReference, ValueDataNode>
{
public SerializationManager.SerializerProvider SerializerProvider { get; } = new();

Expand Down Expand Up @@ -160,7 +161,6 @@ EntityUid ITypeReader<EntityUid, ValueDataNode>.Read(ISerializationManager seria
.Error("Error in map file: found local entity UID '{0}' which does not exist.", val);

return EntityUid.Invalid;

}

[MustUseReturnValue]
Expand All @@ -170,4 +170,53 @@ 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<WeakEntityReference>? instanceProvider = null)
{
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;
}

public DataNode Write(
ISerializationManager serializationManager,
WeakEntityReference value,
IDependencyCollection dependencies,
bool alwaysWrite = false,
ISerializationContext? context = null)
{
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));
}

ValidationNode ITypeValidator<WeakEntityReference, ValueDataNode>.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);
}
}
5 changes: 3 additions & 2 deletions Robust.Shared/Prototypes/EntProtoId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -16,7 +17,7 @@ namespace Robust.Shared.Prototypes;
/// This will be automatically validated by <see cref="EntProtoIdSerializer"/> if used in data fields.
/// </remarks>
/// <remarks><seealso cref="ProtoId{T}"/> for a wrapper of other prototype kinds.</remarks>
[Serializable, NetSerializable]
[Serializable, NetSerializable, CopyByRef]
public readonly record struct EntProtoId(string Id) : IEquatable<string>, IComparable<EntProtoId>, IAsType<string>
{
public static implicit operator string(EntProtoId protoId)
Expand Down Expand Up @@ -55,7 +56,7 @@ public int CompareTo(EntProtoId other)
}

/// <inheritdoc cref="EntProtoId"/>
[Serializable]
[Serializable, CopyByRef]
public readonly record struct EntProtoId<T>(string Id) : IEquatable<string>, IComparable<EntProtoId> where T : IComponent, new()
{
public static implicit operator string(EntProtoId<T> protoId)
Expand Down
3 changes: 2 additions & 1 deletion Robust.Shared/Prototypes/ProtoId.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
using Robust.Shared.Toolshed.TypeParsers;

Expand All @@ -13,7 +14,7 @@ namespace Robust.Shared.Prototypes;
/// This will be automatically validated by <see cref="ProtoIdSerializer{T}"/> if used in data fields.
/// </remarks>
/// <remarks><seealso cref="EntProtoId"/> for an <see cref="EntityPrototype"/> alias.</remarks>
[Serializable]
[Serializable, CopyByRef]
[PreferOtherType(typeof(EntityPrototype), typeof(EntProtoId))]
public readonly record struct ProtoId<T>(string Id) :
IEquatable<string>,
Expand Down
44 changes: 38 additions & 6 deletions Robust.Shared/Prototypes/YamlValidationContext.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Globalization;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
Expand All @@ -11,7 +10,9 @@

namespace Robust.Shared.Prototypes;

internal sealed class YamlValidationContext : ISerializationContext, ITypeSerializer<EntityUid, ValueDataNode>
internal sealed class YamlValidationContext : ISerializationContext,
ITypeSerializer<EntityUid, ValueDataNode>,
ITypeSerializer<WeakEntityReference, ValueDataNode>
{
public SerializationManager.SerializerProvider SerializerProvider { get; } = new();
public bool WritingReadingPrototypes => true;
Expand Down Expand Up @@ -52,11 +53,42 @@ EntityUid ITypeReader<EntityUid, ValueDataNode>.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<WeakEntityReference>? instanceProvider = null)
{
if (node.Value == "invalid")
return WeakEntityReference.Invalid;

return new WeakEntityReference(NetEntity.Parse(node.Value));
}

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));
}
}
Original file line number Diff line number Diff line change
@@ -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<T> : 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<T>)} implementation? Or maybe {typeof(T)} should have the {nameof(CopyByRefAttribute)}?";
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations;
/// Serializer used automatically for <see cref="EntProtoId"/> types.
/// </summary>
[TypeSerializer]
public sealed class EntProtoIdSerializer : ITypeSerializer<EntProtoId, ValueDataNode>, ITypeCopyCreator<EntProtoId>
public sealed class EntProtoIdSerializer : ITypeSerializer<EntProtoId, ValueDataNode>
{
public ValidationNode Validate(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null)
{
Expand All @@ -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;
}
}

/// <summary>
/// Serializer used automatically for <see cref="EntProtoId"/> types.
/// </summary>
[TypeSerializer]
public sealed class EntProtoIdSerializer<T> : ITypeSerializer<EntProtoId<T>, ValueDataNode>, ITypeCopyCreator<EntProtoId<T>> where T : IComponent, new()
public sealed class EntProtoIdSerializer<T> : ITypeSerializer<EntProtoId<T>, ValueDataNode> where T : IComponent, new()
{
public ValidationNode Validate(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null)
{
Expand Down Expand Up @@ -83,9 +78,4 @@ public DataNode Write(ISerializationManager serialization, EntProtoId<T> value,
{
return new ValueDataNode(value.Id);
}

public EntProtoId<T> CreateCopy(ISerializationManager serializationManager, EntProtoId<T> source, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
return source;
}
}
Loading
Loading