Files
RobustToolbox/Robust.Shared/Containers/SharedContainerSystem.cs
T
83c2a1be11 [Dependency] source generator part 2 (#6550)
* [Dependency] source generator

No more reflection, no more codegen at runtime

Also various changes to Roslyn helpers to make this easier to write.

Requires all types with dependencies to be partial and not have readonly dependency fields. An analyzer enforces this at warning level, the previous injection strategies have remained in the code *for now* as a fallback.

No fallback is available for [field: Dependency] properties, due to a Roslyn bug.

Code Fixes exist. We love Roslyn

* Apply dependencies generator changes to all code

* Release notes

* Preprocessor got hands

* Handle nullable dependencies

These are bad but gotta deal with it.

* Apply suggestions from code review

Co-authored-by: Moony <moony@hellomouse.net>

* Fine, let's not use collection expressions

---------

Co-authored-by: Moony <moony@hellomouse.net>
2026-05-08 12:38:33 +02:00

703 lines
28 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.Containers
{
public abstract partial class SharedContainerSystem
{
[Dependency] private IDynamicTypeFactoryInternal _dynFactory = default!;
[Dependency] private INetManager _net = default!;
[Dependency] private SharedPhysicsSystem _physics = default!;
[Dependency] private EntityLookupSystem _lookup = default!;
[Dependency] private SharedTransformSystem _transform = default!;
[Dependency] private SharedJointSystem _joint = default!;
[Dependency] private IGameTiming _timing = default!;
private EntityQuery<ContainerManagerComponent> _managerQuery;
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<MapComponent> _mapQuery;
protected EntityQuery<MetaDataComponent> MetaQuery;
protected EntityQuery<PhysicsComponent> PhysicsQuery;
protected EntityQuery<JointComponent> JointQuery;
protected EntityQuery<TransformComponent> TransformQuery;
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChanged);
SubscribeLocalEvent<ContainerManagerComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<ContainerManagerComponent, ComponentStartup>(OnStartupValidation);
SubscribeLocalEvent<ContainerManagerComponent, ComponentGetState>(OnContainerGetState);
SubscribeLocalEvent<ContainerManagerComponent, ComponentRemove>(OnContainerManagerRemove);
_managerQuery = GetEntityQuery<ContainerManagerComponent>();
_gridQuery = GetEntityQuery<MapGridComponent>();
_mapQuery = GetEntityQuery<MapComponent>();
MetaQuery = GetEntityQuery<MetaDataComponent>();
PhysicsQuery = GetEntityQuery<PhysicsComponent>();
JointQuery = GetEntityQuery<JointComponent>();
TransformQuery = GetEntityQuery<TransformComponent>();
}
private void OnInit(Entity<ContainerManagerComponent> ent, ref ComponentInit args)
{
foreach (var (id, container) in ent.Comp.Containers)
{
container.Init(this, id, ent);
}
}
private void OnContainerGetState(EntityUid uid, ContainerManagerComponent component, ref ComponentGetState args)
{
Dictionary<string, ContainerManagerComponent.ContainerManagerComponentState.ContainerData> containerSet =
new(component.Containers.Count);
foreach (var container in component.Containers.Values)
{
var uidArr = new NetEntity[container.ContainedEntities.Count];
for (var index = 0; index < container.ContainedEntities.Count; index++)
{
uidArr[index] = GetNetEntity(container.ContainedEntities[index]);
}
var sContainer =
new ContainerManagerComponent.ContainerManagerComponentState.ContainerData(container.GetType().Name,
container.ShowContents,
container.OccludesLight,
uidArr);
containerSet.Add(container.ID, sContainer);
}
args.State = new ContainerManagerComponent.ContainerManagerComponentState(containerSet);
}
private void OnContainerManagerRemove(EntityUid uid, ContainerManagerComponent component, ComponentRemove args)
{
foreach (var container in component.Containers.Values)
{
ShutdownContainer(container);
}
component.Containers.Clear();
}
// TODO: Make ContainerManagerComponent ECS and make these proxy methods the real deal.
#region Proxy Methods
public T MakeContainer<T>(EntityUid uid, string id, ContainerManagerComponent? containerManager = null)
where T : BaseContainer
{
if (!Resolve(uid, ref containerManager, false))
containerManager = AddComp<ContainerManagerComponent>(uid); // Happy Vera.
if (HasContainer(uid, id, containerManager))
throw new ArgumentException($"Container with specified ID already exists: '{id}'");
var container = _dynFactory.CreateInstanceUnchecked<T>(typeof(T), inject: false);
container.Init(this, id, (uid, containerManager));
containerManager.Containers[id] = container;
Dirty(uid, containerManager);
return container;
}
public virtual void ShutdownContainer(BaseContainer container)
{
container.InternalShutdown(EntityManager, this, _net.IsClient);
container.Manager.Containers.Remove(container.ID);
container.ExpectedEntities.Clear();
}
public T EnsureContainer<T>(
EntityUid uid,
string id,
out bool alreadyExisted,
ContainerManagerComponent? containerManager = null)
where T : BaseContainer
{
if (!Resolve(uid, ref containerManager, false))
containerManager = AddComp<ContainerManagerComponent>(uid);
if (TryGetContainer(uid, id, out var container, containerManager))
{
alreadyExisted = true;
if (container is T cast)
return cast;
throw new InvalidOperationException(
$"The container exists but is of a different type: {container.GetType()}");
}
alreadyExisted = false;
return MakeContainer<T>(uid, id, containerManager);
}
public T EnsureContainer<T>(EntityUid uid, string id, ContainerManagerComponent? containerManager = null)
where T : BaseContainer
{
return EnsureContainer<T>(uid, id, out _, containerManager);
}
public BaseContainer GetContainer(EntityUid uid, string id, ContainerManagerComponent? containerManager = null)
{
if (!Resolve(uid, ref containerManager))
throw new ArgumentException("Entity does not have a ContainerManagerComponent!", nameof(uid));
return containerManager.Containers[id];
}
public bool HasContainer(EntityUid uid, string id, ContainerManagerComponent? containerManager)
{
if (!Resolve(uid, ref containerManager, false))
return false;
return containerManager.Containers.ContainsKey(id);
}
public bool TryGetContainer(
EntityUid uid,
string id,
[NotNullWhen(true)] out BaseContainer? container,
ContainerManagerComponent? containerManager = null)
{
if (!Resolve(uid, ref containerManager, false))
{
container = null;
return false;
}
if (!containerManager.Containers.TryGetValue(id, out container))
return false;
DebugTools.AssertEqual(container.ID, id);
DebugTools.AssertNotNull(container.Manager);
DebugTools.AssertNotEqual(container.Owner, EntityUid.Invalid);
return true;
}
public bool TryGetContainingContainer(
EntityUid uid,
EntityUid containedUid,
[NotNullWhen(true)] out BaseContainer? container,
ContainerManagerComponent? containerManager = null)
{
DebugTools.Assert(Exists(containedUid));
if (!Resolve(uid, ref containerManager, false))
{
container = null;
return false;
}
foreach (var contain in containerManager.Containers.Values)
{
if (contain.Contains(containedUid))
{
container = contain;
return true;
}
}
container = default;
return false;
}
public bool ContainsEntity(
EntityUid uid,
EntityUid containedUid,
ContainerManagerComponent? containerManager = null)
{
DebugTools.Assert(Exists(containedUid));
if (!Resolve(uid, ref containerManager, false))
return false;
foreach (var container in containerManager.Containers.Values)
{
if (container.Contains(containedUid))
return true;
}
return false;
}
public bool RemoveEntity(
EntityUid uid,
EntityUid toremove,
ContainerManagerComponent? containerManager = null,
TransformComponent? containedXform = null,
MetaDataComponent? containedMeta = null,
bool reparent = true,
bool force = false,
EntityCoordinates? destination = null,
Angle? localRotation = null)
{
if (!Resolve(uid, ref containerManager) || !Resolve(toremove, ref containedMeta, ref containedXform))
return false;
foreach (var containers in containerManager.Containers.Values)
{
if (containers.Contains(toremove))
return Remove((toremove, containedXform, containedMeta),
containers,
reparent,
force,
destination,
localRotation);
}
return true; // If we don't contain the entity, it will always be removed
}
public ContainerManagerComponent.AllContainersEnumerable GetAllContainers(
EntityUid uid,
ContainerManagerComponent? containerManager = null)
{
if (!Resolve(uid, ref containerManager))
return new ContainerManagerComponent.AllContainersEnumerable();
return new(containerManager);
}
#endregion
#region Container Helpers
public bool TryGetContainingContainer(
Entity<TransformComponent?, MetaDataComponent?> ent,
[NotNullWhen(true)] out BaseContainer? container)
{
container = null;
if (!Resolve(ent, ref ent.Comp2, false))
return false;
if ((ent.Comp2.Flags & MetaDataFlags.InContainer) == MetaDataFlags.None)
return false;
if (!Resolve(ent, ref ent.Comp1, false))
return false;
return TryGetContainingContainer(ent.Comp1.ParentUid, ent, out container);
}
/// <summary>
/// Checks whether the given entity is inside of a container. This will only check if this entity's direct
/// parent is containing it. To recursively if the entity, or any parent, is inside a container, use <see
/// cref="IsEntityOrParentInContainer"/>
/// </summary>
/// <returns>If the entity is inside of a container.</returns>
public bool IsEntityInContainer(EntityUid uid, MetaDataComponent? meta = null)
{
if (!Resolve(uid, ref meta, false))
return false;
return (meta.Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer;
}
/// <summary>
/// Recursively check if the entity or any parent is inside of a container.
/// </summary>
/// <returns>If the entity is inside of a container.</returns>
public bool IsEntityOrParentInContainer(
EntityUid uid,
MetaDataComponent? meta = null,
TransformComponent? xform = null)
{
if (!MetaQuery.Resolve(uid, ref meta))
return false;
if ((meta.Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer)
return true;
if (!TransformQuery.Resolve(uid, ref xform))
return false;
if (!xform.ParentUid.Valid)
return false;
return IsEntityOrParentInContainer(xform.ParentUid);
}
/// <summary>
/// Finds the first instance of a component on the recursive parented containers that hold an entity
/// </summary>
public bool TryFindComponentOnEntityContainerOrParent<T>(
EntityUid uid,
EntityQuery<T> entityQuery,
[NotNullWhen(true)] ref T? foundComponent,
MetaDataComponent? meta = null,
TransformComponent? xform = null) where T : IComponent
{
if (!MetaQuery.Resolve(uid, ref meta))
return false;
if ((meta.Flags & MetaDataFlags.InContainer) != MetaDataFlags.InContainer)
return false;
if (!TransformQuery.Resolve(uid, ref xform))
return false;
if (!xform.ParentUid.Valid)
return false;
if (entityQuery.TryComp(xform.ParentUid, out foundComponent))
return true;
return TryFindComponentOnEntityContainerOrParent(xform.ParentUid, entityQuery, ref foundComponent);
}
/// <summary>
/// Finds all instances of a component on the recursive parented containers that hold an entity
/// </summary>
public bool TryFindComponentsOnEntityContainerOrParent<T>(
EntityUid uid,
EntityQuery<T> entityQuery,
List<T> foundComponents,
MetaDataComponent? meta = null,
TransformComponent? xform = null) where T : IComponent
{
if (!MetaQuery.Resolve(uid, ref meta))
return foundComponents.Any();
if ((meta.Flags & MetaDataFlags.InContainer) != MetaDataFlags.InContainer)
return foundComponents.Any();
if (!TransformQuery.Resolve(uid, ref xform))
return foundComponents.Any();
if (!xform.ParentUid.Valid)
return foundComponents.Any();
if (TryComp(xform.ParentUid, out T? foundComponent))
foundComponents.Add(foundComponent);
return TryFindComponentsOnEntityContainerOrParent(xform.ParentUid, entityQuery, foundComponents);
}
/// <summary>
/// Returns true if the two entities are not contained, or are contained in the same container.
/// </summary>
public bool IsInSameOrNoContainer(
Entity<TransformComponent?, MetaDataComponent?> user,
Entity<TransformComponent?, MetaDataComponent?> other)
{
var isUserContained = TryGetContainingContainer(user, out var userContainer);
var isOtherContained = TryGetContainingContainer(other, out var otherContainer);
// Both entities are not in a container
if (!isUserContained && !isOtherContained) return true;
// Both entities are in different contained states
if (isUserContained != isOtherContained) return false;
// Both entities are in the same container
return userContainer == otherContainer;
}
/// <summary>
/// Returns true if the two entities are not contained, or are contained in the same container, or if one
/// entity contains the other (i.e., is the parent).
/// </summary>
public bool IsInSameOrParentContainer(
Entity<TransformComponent?, MetaDataComponent?> user,
Entity<TransformComponent?, MetaDataComponent?> other)
{
return IsInSameOrParentContainer(user, other, out _, out _);
}
/// <inheritdoc cref="IsInSameOrParentContainer(Robust.Shared.GameObjects.Entity{Robust.Shared.GameObjects.TransformComponent?},Robust.Shared.GameObjects.Entity{Robust.Shared.GameObjects.TransformComponent?})"/>
public bool IsInSameOrParentContainer(
Entity<TransformComponent?, MetaDataComponent?> user,
Entity<TransformComponent?, MetaDataComponent?> other,
out BaseContainer? userContainer,
out BaseContainer? otherContainer)
{
var isUserContained = TryGetContainingContainer(user, out userContainer);
var isOtherContained = TryGetContainingContainer(other, out otherContainer);
// Both entities are not in a container
if (!isUserContained && !isOtherContained) return true;
// One contains the other
if (userContainer?.Owner == other || otherContainer?.Owner == user) return true;
// Both entities are in different contained states
if (isUserContained != isOtherContained) return false;
// Both entities are in the same container
return userContainer == otherContainer;
}
/// <summary>
/// Check whether a given entity can see another entity despite whatever containers they may be in.
/// </summary>
/// <remarks>
/// This is effectively a variant of <see cref="IsInSameOrParentContainer"/> that also checks whether the
/// containers are transparent. Additionally, an entity can "see" the entity that contains it, but unless
/// otherwise specified the containing entity cannot see into itself. For example, a human in a locker can
/// see the locker and other items in that locker, but the human cannot see their own organs. Note that
/// this means that the two entity arguments are NOT interchangeable.
/// </remarks>
public bool IsInSameOrTransparentContainer(
Entity<TransformComponent?, MetaDataComponent?> user,
Entity<TransformComponent?, MetaDataComponent?> other,
BaseContainer? userContainer = null,
BaseContainer? otherContainer = null,
bool userSeeInsideSelf = false)
{
if (userContainer == null)
TryGetContainingContainer(user, out userContainer);
if (otherContainer == null)
TryGetContainingContainer(other, out otherContainer);
// Are both entities in the same container (or none)?
if (userContainer == otherContainer) return true;
// Is the user contained in the other entity?
if (userContainer?.Owner == other) return true;
// Does the user contain the other and can they see through themselves?
if (userSeeInsideSelf && otherContainer?.Owner == user) return true;
// Next we check for see-through containers. This uses some recursion, but it should be fine unless people
// start spawning in glass matryoshka dolls.
// Is the user in a see-through container?
if (userContainer?.ShowContents ?? false)
return IsInSameOrTransparentContainer((userContainer.Owner, null, null), other, otherContainer: otherContainer);
// Is the other entity in a see-through container?
if (otherContainer?.ShowContents ?? false)
return IsInSameOrTransparentContainer(user, (otherContainer.Owner, null, null), userContainer: userContainer, userSeeInsideSelf: userSeeInsideSelf);
return false;
}
/// <summary>
/// Returns the full chain of containers containing the entity passed in, from innermost to outermost.
/// </summary>
/// <remarks>
/// The resulting collection includes the container directly containing the entity (if any),
/// the container containing that container, and so on until reaching the outermost container.
/// </remarks>
public IEnumerable<BaseContainer> GetContainingContainers(Entity<TransformComponent?> ent)
{
if (!ent.Owner.IsValid())
yield break;
if (!Resolve(ent, ref ent.Comp))
yield break;
var child = ent.Owner;
var parent = ent.Comp.ParentUid;
while (parent.IsValid())
{
if (((MetaQuery.GetComponent(child).Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer) &&
_managerQuery.TryGetComponent(parent, out var conManager) &&
TryGetContainingContainer(parent, child, out var parentContainer, conManager))
{
yield return parentContainer;
}
var parentXform = TransformQuery.GetComponent(parent);
child = parent;
parent = parentXform.ParentUid;
}
}
/// <summary>
/// Gets the top-most container in the hierarchy for this entity, if it exists.
/// </summary>
public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform, [NotNullWhen(true)] out BaseContainer? container)
{
return TryGetOuterContainer(uid, xform, out container, TransformQuery);
}
public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform,
[NotNullWhen(true)] out BaseContainer? container, EntityQuery<TransformComponent> xformQuery)
{
container = null;
if (!uid.IsValid())
return false;
var child = uid;
var parent = xform.ParentUid;
while (parent.IsValid())
{
if (((MetaQuery.GetComponent(child).Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer) &&
_managerQuery.TryGetComponent(parent, out var conManager) &&
TryGetContainingContainer(parent, child, out var parentContainer, conManager))
{
container = parentContainer;
}
var parentXform = xformQuery.GetComponent(parent);
child = parent;
parent = parentXform.ParentUid;
}
return container != null;
}
/// <summary>
/// Attempts to remove an entity from its container, if any.
/// </summary>
/// <param name="entity">Entity that might be inside a container.</param>
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
/// <param name="wasInContainer">Whether the entity was actually inside a container or not.</param>
/// <returns>If the entity could be removed. Also returns false if it wasn't inside a container.</returns>
public bool TryRemoveFromContainer(Entity<TransformComponent?, MetaDataComponent?> entity, bool force, out bool wasInContainer)
{
DebugTools.Assert(Exists(entity));
if (TryGetContainingContainer(entity, out var container))
{
wasInContainer = true;
if (!force)
return Remove(entity, container);
Remove(entity, container, force: true);
return true;
}
wasInContainer = false;
return false;
}
/// <summary>
/// Attempts to remove an entity from its container, if any.
/// </summary>
/// <param name="entity">Entity that might be inside a container.</param>
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
/// <returns>If the entity could be removed. Also returns false if it wasn't inside a container.</returns>
public bool TryRemoveFromContainer(Entity<TransformComponent?, MetaDataComponent?> entity, bool force = false)
{
return TryRemoveFromContainer(entity, force, out _);
}
/// <summary>
/// Attempts to remove all entities in a container. Returns removed entities.
/// </summary>
public List<EntityUid> EmptyContainer(
BaseContainer container,
bool force = false,
EntityCoordinates? destination = null,
bool reparent = true)
{
var removed = new List<EntityUid>(container.ContainedEntities);
for (var i = removed.Count - 1; i >= 0; i--)
{
if (Remove(removed[i], container, reparent: reparent, force: force, destination: destination))
continue;
// failed to remove entity.
DebugTools.Assert(container.Contains(removed[i]));
removed.RemoveSwap(i);
}
return removed;
}
/// <summary>
/// Attempts to remove and delete all entities in a container.
/// </summary>
public void CleanContainer(BaseContainer container)
{
foreach (var ent in container.ContainedEntities.ToArray())
{
if (Deleted(ent))
continue;
Remove(ent, container, force: true);
PredictedDel(ent);
}
}
public void AttachParentToContainerOrGrid(Entity<TransformComponent> transform)
{
// TODO make this check upwards for any container, and parent to that.
// Currently this just checks the direct parent, so entities will still teleport through containers.
if (!transform.Comp.ParentUid.IsValid()
|| !TryGetContainingContainer((transform.Comp.ParentUid, Transform(transform.Comp.ParentUid)), out var container)
|| !TryInsertIntoContainer(transform, container))
{
_transform.AttachToGridOrMap(transform, transform.Comp);
}
}
private bool TryInsertIntoContainer(Entity<TransformComponent> transform, BaseContainer container)
{
if (Insert((transform.Owner, transform.Comp, null, null), container))
return true;
var ownerXform = Transform(container.Owner);
if (ownerXform.ParentUid.IsValid()
&& TryGetContainingContainer((container.Owner, ownerXform), out var newContainer))
return TryInsertIntoContainer(transform, newContainer);
return false;
}
internal bool TryGetManagerComp(EntityUid entity, [NotNullWhen(true)] out ContainerManagerComponent? manager)
{
DebugTools.Assert(Exists(entity));
if (TryComp(entity, out manager))
return true;
// RECURSION ALERT
var transform = Transform(entity);
if (transform.ParentUid.IsValid())
return TryGetManagerComp(transform.ParentUid, out manager);
return false;
}
#endregion
protected virtual void OnParentChanged(ref EntParentChangedMessage message)
{
var meta = MetaData(message.Entity);
if ((meta.Flags & MetaDataFlags.InContainer) == 0)
return;
// Eject entities from their parent container if the parent change is done via setting the transform.
if (TryComp(message.OldParent, out ContainerManagerComponent? containerManager))
RemoveEntity(message.OldParent.Value, message.Entity, containerManager, message.Transform, meta, reparent: false, force: true);
}
[Conditional("DEBUG"), Access(typeof(BaseContainer))]
public void AssertInContainer(EntityUid uid, BaseContainer container)
{
if (_timing.ApplyingState)
return; // Entity might not yet have had its state updated.
var flags = MetaData(uid).Flags;
DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0,
$"Entity has bad container flags. Ent: {ToPrettyString(uid)}. Container: {container.ID}, Owner: {ToPrettyString(container.Owner)}");
}
}
}