diff --git a/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs b/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs index c060b398a7..72484167f6 100644 --- a/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs @@ -1,23 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Robust.Client.Player; using Robust.Shared.Collections; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; -using Robust.Shared.Log; using Robust.Shared.Map; +using Robust.Shared.Network; using Robust.Shared.Serialization; using Robust.Shared.Utility; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using static Robust.Shared.Containers.ContainerManagerComponent; namespace Robust.Client.GameObjects { public sealed class ContainerSystem : SharedContainerSystem { + [Dependency] private readonly INetManager _netMan = default!; [Dependency] private readonly IRobustSerializer _serializer = default!; [Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!; @@ -35,6 +35,11 @@ namespace Robust.Client.GameObjects UpdatesBefore.Add(typeof(SpriteSystem)); } + protected override void ValidateMissingEntity(EntityUid uid, IContainer cont, EntityUid missing) + { + DebugTools.Assert(ExpectedEntities.TryGetValue(missing, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(missing)); + } + private void HandleEntityInitialized(EntityInitializedMessage ev) { if (!RemoveExpectedEntity(ev.Entity, out var container)) @@ -58,8 +63,8 @@ namespace Robust.Client.GameObjects if (cast.Containers.ContainsKey(id)) continue; - EmptyContainer(container, true); - container.Shutdown(); + EmptyContainer(container, true, null, false, EntityManager); + container.Shutdown(EntityManager, _netMan); toDelete.Add(id); } diff --git a/Robust.Client/GameObjects/EntitySystems/MapSystem.cs b/Robust.Client/GameObjects/EntitySystems/MapSystem.cs index 7ea32e0020..e480e73936 100644 --- a/Robust.Client/GameObjects/EntitySystems/MapSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/MapSystem.cs @@ -1,9 +1,13 @@ +using Robust.Client.Physics; using Robust.Shared.GameObjects; namespace Robust.Client.GameObjects { internal sealed class MapSystem : SharedMapSystem { - + protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args) + { + EnsureComp(uid); + } } } diff --git a/Robust.Client/GameStates/ClientGameStateManager.cs b/Robust.Client/GameStates/ClientGameStateManager.cs index cfc029418b..41455d9677 100644 --- a/Robust.Client/GameStates/ClientGameStateManager.cs +++ b/Robust.Client/GameStates/ClientGameStateManager.cs @@ -663,6 +663,7 @@ namespace Robust.Client.GameStates // Detach entities to null space var xformSys = _entitySystemManager.GetEntitySystem(); var containerSys = _entitySystemManager.GetEntitySystem(); + var lookupSys = _entitySystemManager.GetEntitySystem(); var detached = ProcessPvsDeparture(curState.ToSequence, metas, xforms, xformSys, containerSys); // Check next state (AFTER having created new entities introduced in curstate) @@ -693,6 +694,9 @@ namespace Robust.Client.GameStates { HandleEntityState(entity, _entities.EventBus, data.curState, data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs); + + if (data.EnteringPvs) + lookupSys.FindAndAddToEntityTree(entity); } _prof.WriteValue("Count", ProfData.Int32(toApply.Count)); } @@ -794,7 +798,7 @@ namespace Robust.Client.GameStates (containerMeta.Flags & MetaDataFlags.Detached) == 0 && containerSys.TryGetContainingContainer(xform.ParentUid, ent, out container, null, true)) { - container.ForceRemove(ent, _entities, meta); + container.Remove(ent, _entities, xform, meta, false, false, true); } meta._flags |= MetaDataFlags.Detached; diff --git a/Robust.Client/Physics/PhysicsSystem.cs b/Robust.Client/Physics/PhysicsSystem.cs index 87c22f6ab5..7f36897e21 100644 --- a/Robust.Client/Physics/PhysicsSystem.cs +++ b/Robust.Client/Physics/PhysicsSystem.cs @@ -1,7 +1,5 @@ using JetBrains.Annotations; -using Robust.Shared.GameObjects; using Robust.Shared.IoC; -using Robust.Shared.Map; using Robust.Shared.Physics.Systems; using Robust.Shared.Timing; @@ -16,11 +14,5 @@ namespace Robust.Client.Physics { SimulateWorld(frameTime, _gameTiming.InPrediction); } - - protected override void OnMapAdded(ref MapChangedEvent eventArgs) - { - if (eventArgs.Map == MapId.Nullspace) return; - EnsureComp(MapManager.GetMapEntityId(eventArgs.Map)); - } } } diff --git a/Robust.Server/Containers/ContainerSystem.cs b/Robust.Server/Containers/ContainerSystem.cs index 4f853718d4..2423e72730 100644 --- a/Robust.Server/Containers/ContainerSystem.cs +++ b/Robust.Server/Containers/ContainerSystem.cs @@ -1,10 +1,15 @@ using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.Log; namespace Robust.Server.Containers { public sealed class ContainerSystem : SharedContainerSystem { - // Seems like shared EntitySystems aren't registered, so this is here to register it on the server. - // Registering the SharedContainerSystem causes conflicts on client where two entity systems are registered. + protected override void ValidateMissingEntity(EntityUid uid, IContainer cont, EntityUid missing) + { + Logger.Error($"Missing entity for container {ToPrettyString(uid)}. Missing uid: {missing}"); + //cont.InternalRemove(ent); + } } } diff --git a/Robust.Server/GameObjects/EntitySystems/MapSystem.cs b/Robust.Server/GameObjects/EntitySystems/MapSystem.cs index 3496dba5f9..5326d90abd 100644 --- a/Robust.Server/GameObjects/EntitySystems/MapSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/MapSystem.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Robust.Server.Physics; using Robust.Shared; using Robust.Shared.Configuration; using Robust.Shared.GameObjects; @@ -21,6 +22,11 @@ namespace Robust.Server.GameObjects configManager.OnValueChanged(CVars.GameDeleteEmptyGrids, SetGridDeletion, true); } + protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args) + { + EnsureComp(uid); + } + private void SetGridDeletion(bool value) { _deleteEmptyGrids = value; diff --git a/Robust.Server/GameObjects/EntitySystems/PhysicsSystem.cs b/Robust.Server/GameObjects/EntitySystems/PhysicsSystem.cs index 625426d665..3a938fcbbc 100644 --- a/Robust.Server/GameObjects/EntitySystems/PhysicsSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/PhysicsSystem.cs @@ -39,12 +39,6 @@ namespace Robust.Server.GameObjects collideComp.BodyType = BodyType.Static; } - protected override void OnMapAdded(ref MapChangedEvent eventArgs) - { - if (eventArgs.Map == MapId.Nullspace) return; - EnsureComp(MapManager.GetMapEntityId(eventArgs.Map)); - } - /// public override void Update(float frameTime) { diff --git a/Robust.Server/GameStates/PVSSystem.cs b/Robust.Server/GameStates/PVSSystem.cs index 83975cc96c..b138a8f767 100644 --- a/Robust.Server/GameStates/PVSSystem.cs +++ b/Robust.Server/GameStates/PVSSystem.cs @@ -15,10 +15,8 @@ using Robust.Shared.Timing; using Robust.Shared.Utility; using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; -using System.Security.Cryptography; using System.Text; namespace Robust.Server.GameStates; @@ -787,6 +785,10 @@ internal sealed partial class PVSSystem : EntitySystem foreach (var (uid, visiblity) in visibleEnts) { + // if an entity is visible, its parents should always be visible. This currently sometimes fails. + DebugTools.Assert((tQuery.GetComponent(uid).ParentUid is not { Valid: true } parent) || visibleEnts.ContainsKey(parent), + $"Attempted to send an entity without sending it's parents. Entity: {ToPrettyString(uid)}."); + if (sessionData.RequestedFull) { entityStates.Add(GetFullEntityState(session, uid, mQuery.GetComponent(uid))); diff --git a/Robust.Shared/Containers/BaseContainer.cs b/Robust.Shared/Containers/BaseContainer.cs index 573be48711..9abbe5162f 100644 --- a/Robust.Shared/Containers/BaseContainer.cs +++ b/Robust.Shared/Containers/BaseContainer.cs @@ -1,13 +1,15 @@ -using System.Collections.Generic; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Map; 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.Serialization.Manager.Attributes; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; +using System.Collections.Generic; namespace Robust.Shared.Containers { @@ -35,7 +37,7 @@ namespace Robust.Shared.Containers public string ID { get; internal set; } = default!; // Make sure you set me in init /// - public IContainerManager Manager { get; internal set; } = default!; // Make sure you set me in init + public ContainerManagerComponent Manager { get; internal set; } = default!; // Make sure you set me in init /// [ViewVariables(VVAccess.ReadWrite)] @@ -86,6 +88,10 @@ namespace Robust.Shared.Containers var physicsSys = entMan.EntitySysManager.GetEntitySystem(); var jointSys = entMan.EntitySysManager.GetEntitySystem(); + // Please somebody ecs containers + var lookupSys = entMan.EntitySysManager.GetEntitySystem(); + var xformSys = entMan.EntitySysManager.GetEntitySystem(); + transform ??= transformQuery.GetComponent(toinsert); meta ??= entMan.GetComponent(toinsert); @@ -93,45 +99,58 @@ namespace Robust.Shared.Containers if ((meta.Flags & MetaDataFlags.InContainer) != 0 && entMan.TryGetComponent(transform.ParentUid, out ContainerManagerComponent? oldManager) && oldManager.TryGetContainer(toinsert, out var oldContainer) && - !oldContainer.Remove(toinsert, entMan, transform, meta, false)) + !oldContainer.Remove(toinsert, entMan, transform, meta, false, false)) { // failed to remove from container --> cannot insert. return false; } // Update metadata first, so that parent change events can check IsInContainer. + DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0); meta.Flags |= MetaDataFlags.InContainer; + // Remove the entity and any children from broadphases. + // This is done before changing can collide to avoid unecceary updates. + // TODO maybe combine with RecursivelyUpdatePhysics to avoid fetching components and iterating parents twice? + lookupSys.RemoveFromEntityTree(toinsert, transform, transformQuery); + + // Unanchor the entity (without changing physics body types). + xformSys.Unanchor(transform, false); + // Next, update physics. Note that this cannot just be done in the physics system via parent change events, // because the insertion may not result in a parent change. This could alternatively be done via a // got-inserted event, but really that event should run after the entity was actually inserted (so that // parent/map have updated). But we are better of disabling collision before doing map/parent changes. - if (physics == null) - physicsQuery.TryGetComponent(toinsert, out physics); + physicsQuery.Resolve(toinsert, ref physics, false); RecursivelyUpdatePhysics(transform, physics, physicsSys, jointSys, physicsQuery, transformQuery, jointQuery); - ownerTransform ??= transformQuery.GetComponent(Owner); + // Attach to new parent var oldParent = transform.ParentUid; - transform.AttachParent(ownerTransform); - InternalInsert(toinsert, oldParent, entMan); + xformSys.SetCoordinates(transform, new EntityCoordinates(Owner, Vector2.Zero), Angle.Zero); // the transform.AttachParent() could previously result in the flag being unset, so check that this hasn't happened. DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0); - // This is an edge case where the parent grid is the container being inserted into, so AttachParent would not unanchor. - if (transform.Anchored) - transform.Anchored = false; + // Implementation specific insert logic + InternalInsert(toinsert, entMan); - // spatially move the object to the location of the container. If you don't want this functionality, the - // calling code can save the local position before calling this function, and apply it afterwords. - transform.LocalPosition = Vector2.Zero; - transform.LocalRotation = Angle.Zero; + // Raise container events (after re-parenting and internal remove). + entMan.EventBus.RaiseLocalEvent(Owner, new EntInsertedIntoContainerMessage(toinsert, oldParent, this), true); + entMan.EventBus.RaiseLocalEvent(toinsert, new EntGotInsertedIntoContainerMessage(toinsert, this), true); - DebugTools.Assert(!physicsQuery.TryGetComponent(toinsert, out var phys) || !phys.Awake); + // The sheer number of asserts tells you about how little I trust container and parenting code. + DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0); + DebugTools.Assert(!transform.Anchored); + DebugTools.Assert(transform.LocalPosition == Vector2.Zero); + DebugTools.Assert(transform.LocalRotation == Angle.Zero); + DebugTools.Assert(transform.Broadphase == null); + DebugTools.Assert(!physicsQuery.TryGetComponent(toinsert, out var phys) || (!phys.Awake && !phys.CanCollide)); + + entMan.Dirty(Manager); return true; } - private void RecursivelyUpdatePhysics(TransformComponent xform, + internal void RecursivelyUpdatePhysics(TransformComponent xform, PhysicsComponent? physics, SharedPhysicsSystem physicsSys, SharedJointSystem jointSys, @@ -199,58 +218,101 @@ namespace Robust.Shared.Containers } /// - public bool Remove(EntityUid toremove, IEntityManager? entMan = null, TransformComponent? xform = null, MetaDataComponent? meta = null, bool reparent = true) + public bool Remove( + EntityUid toRemove, + IEntityManager? entMan = null, + TransformComponent? xform = null, + MetaDataComponent? meta = null, + bool reparent = true, + bool addToBroadphase = true, + bool force = false, + EntityCoordinates? destination = null, + Angle? localRotation = null) { + IoCManager.Resolve(ref entMan); DebugTools.Assert(!Deleted); DebugTools.AssertNotNull(Manager); - DebugTools.AssertNotNull(toremove); - IoCManager.Resolve(ref entMan); - DebugTools.Assert(entMan.EntityExists(toremove)); - DebugTools.Assert(xform == null || xform.Owner == toremove); - DebugTools.Assert(entMan.GetComponent(Owner).EntityLifeStage < EntityLifeStage.Terminating); + DebugTools.Assert(entMan.EntityExists(toRemove)); + DebugTools.Assert(xform == null || xform.Owner == toRemove); + DebugTools.Assert(meta == null || meta.Owner == toRemove); - if (!CanRemove(toremove, entMan)) return false; - InternalRemove(toremove, entMan, meta); + xform ??= entMan.GetComponent(toRemove); + meta ??= entMan.GetComponent(toRemove); - if (reparent) + if (!force && !CanRemove(toRemove, entMan)) + return false; + + if (force && !Contains(toRemove)) { - xform ??= entMan.GetComponent(toremove); - xform.AttachParentToContainerOrGrid(entMan); + DebugTools.Assert("Attempted to force remove an entity that was never inside of the container."); + return false; } + DebugTools.Assert(meta.EntityLifeStage < EntityLifeStage.Terminating || (force && !reparent)); + DebugTools.Assert(xform.Broadphase == null); + DebugTools.Assert(!xform.Anchored); + DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0x0); + DebugTools.Assert(!entMan.TryGetComponent(toRemove, out PhysicsComponent? phys) || (!phys.Awake && !phys.CanCollide)); + + // Unset flag (before parent change events are raised). + meta.Flags &= ~MetaDataFlags.InContainer; + + // Implementation specific remove logic + InternalRemove(toRemove, entMan); + + DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0x0); + + if (destination != null) + { + // Container ECS when. + entMan.EntitySysManager.GetEntitySystem().SetCoordinates(xform, destination.Value, localRotation); + } + else if (reparent) + { + // Container ECS when. + entMan.EntitySysManager.GetEntitySystem().AttachParentToContainerOrGrid(xform); + if (localRotation != null) + entMan.EntitySysManager.GetEntitySystem().SetLocalRotation(xform, localRotation.Value); + } + + if (addToBroadphase) + { + // Container ECS when. + entMan.EntitySysManager.GetEntitySystem().FindAndAddToEntityTree(toRemove, xform); + } + + // Raise container events (after re-parenting and internal remove). + entMan.EventBus.RaiseLocalEvent(Owner, new EntRemovedFromContainerMessage(toRemove, this), true); + entMan.EventBus.RaiseLocalEvent(toRemove, new EntGotRemovedFromContainerMessage(toRemove, this), false); + + entMan.Dirty(Manager); return true; } /// public void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null, MetaDataComponent? meta = null) { - DebugTools.Assert(!Deleted); - DebugTools.AssertNotNull(Manager); - DebugTools.AssertNotNull(toRemove); - IoCManager.Resolve(ref entMan); - DebugTools.Assert(entMan.EntityExists(toRemove)); - - InternalRemove(toRemove, entMan, meta); + Remove(toRemove, entMan, null, meta, false, true); } /// - public virtual bool CanRemove(EntityUid toremove, IEntityManager? entMan = null) + public virtual bool CanRemove(EntityUid toRemove, IEntityManager? entMan = null) { DebugTools.Assert(!Deleted); - if (!Contains(toremove)) + if (!Contains(toRemove)) return false; IoCManager.Resolve(ref entMan); //raise events - var removeAttemptEvent = new ContainerIsRemovingAttemptEvent(this, toremove); + var removeAttemptEvent = new ContainerIsRemovingAttemptEvent(this, toRemove); entMan.EventBus.RaiseLocalEvent(Owner, removeAttemptEvent, true); if (removeAttemptEvent.Cancelled) return false; - var gettingRemovedAttemptEvent = new ContainerGettingRemovedAttemptEvent(this, toremove); - entMan.EventBus.RaiseLocalEvent(toremove, gettingRemovedAttemptEvent, true); + var gettingRemovedAttemptEvent = new ContainerGettingRemovedAttemptEvent(this, toRemove); + entMan.EventBus.RaiseLocalEvent(toRemove, gettingRemovedAttemptEvent, true); if (gettingRemovedAttemptEvent.Cancelled) return false; @@ -261,44 +323,29 @@ namespace Robust.Shared.Containers public abstract bool Contains(EntityUid contained); /// - public virtual void Shutdown() + public void Shutdown(IEntityManager? entMan = null, INetManager? netMan = null) { - Manager.InternalContainerShutdown(this); + IoCManager.Resolve(ref entMan, ref netMan); + InternalShutdown(entMan, netMan.IsClient); + Manager.Containers.Remove(ID); Deleted = true; } + /// + protected abstract void InternalShutdown(IEntityManager entMan, bool isClient); + /// /// Implement to store the reference in whatever form you want /// - /// + /// /// - protected virtual void InternalInsert(EntityUid toinsert, EntityUid oldParent, IEntityManager entMan) - { - DebugTools.Assert(!Deleted); - entMan.EventBus.RaiseLocalEvent(Owner, new EntInsertedIntoContainerMessage(toinsert, oldParent, this), true); - var gotInsertedEvent = new EntGotInsertedIntoContainerMessage(toinsert, this); - entMan.EventBus.RaiseLocalEvent(toinsert, gotInsertedEvent, true); - Manager.Dirty(entMan); - } + protected abstract void InternalInsert(EntityUid toInsert, IEntityManager entMan); /// /// Implement to remove the reference you used to store the entity /// - /// + /// /// - protected virtual void InternalRemove(EntityUid toremove, IEntityManager entMan, MetaDataComponent? meta = null) - { - DebugTools.Assert(!Deleted); - DebugTools.AssertNotNull(Manager); - DebugTools.AssertNotNull(toremove); - DebugTools.Assert(entMan.EntityExists(toremove)); - DebugTools.Assert(meta == null || meta.Owner == toremove); - - meta ??= entMan.GetComponent(toremove); - meta.Flags &= ~MetaDataFlags.InContainer; - entMan.EventBus.RaiseLocalEvent(Owner, new EntRemovedFromContainerMessage(toremove, this), true); - entMan.EventBus.RaiseLocalEvent(toremove, new EntGotRemovedFromContainerMessage(toremove, this), false); - Manager.Dirty(entMan); - } + protected abstract void InternalRemove(EntityUid toRemove, IEntityManager entMan); } } diff --git a/Robust.Shared/Containers/Container.cs b/Robust.Shared/Containers/Container.cs index 305875c045..1dc9996831 100644 --- a/Robust.Shared/Containers/Container.cs +++ b/Robust.Shared/Containers/Container.cs @@ -37,36 +37,39 @@ namespace Robust.Shared.Containers public override string ContainerType => ClassName; /// - protected override void InternalInsert(EntityUid toinsert, EntityUid oldParent, IEntityManager entMan) + protected override void InternalInsert(EntityUid toInsert, IEntityManager entMan) { - // Why TF is this even a list?????? - DebugTools.Assert(!_containerList.Contains(toinsert)); - _containerList.Add(toinsert); - base.InternalInsert(toinsert, oldParent, entMan); + DebugTools.Assert(!_containerList.Contains(toInsert)); + _containerList.Add(toInsert); } /// - protected override void InternalRemove(EntityUid toremove, IEntityManager entMan, MetaDataComponent? meta = null) + protected override void InternalRemove(EntityUid toRemove, IEntityManager entMan) { - _containerList.Remove(toremove); - base.InternalRemove(toremove, entMan, meta); + _containerList.Remove(toRemove); } /// public override bool Contains(EntityUid contained) { - return _containerList.Contains(contained); + if (!_containerList.Contains(contained)) + return false; + + var flags = IoCManager.Resolve().GetComponent(contained).Flags; + DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0); + + return true; } /// - public override void Shutdown() + protected override void InternalShutdown(IEntityManager entMan, bool isClient) { - base.Shutdown(); - - var entMan = IoCManager.Resolve(); foreach (var entity in _containerList) { - entMan.DeleteEntity(entity); + if (!isClient) + entMan.DeleteEntity(entity); + else if (entMan.EntityExists(entity)) + Remove(entity, entMan, reparent: false, addToBroadphase: true, force: true); } } } diff --git a/Robust.Shared/Containers/ContainerManagerComponent.cs b/Robust.Shared/Containers/ContainerManagerComponent.cs index 7b2fb959c8..2e7b3bdc65 100644 --- a/Robust.Shared/Containers/ContainerManagerComponent.cs +++ b/Robust.Shared/Containers/ContainerManagerComponent.cs @@ -6,8 +6,10 @@ using System.Linq; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Network; using Robust.Shared.Serialization; -using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -22,6 +24,8 @@ namespace Robust.Shared.Containers public sealed class ContainerManagerComponent : Component, IContainerManager, ISerializationHooks { [Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!; + [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly INetManager _netMan = default!; [ViewVariables] [DataField("containers")] @@ -45,10 +49,9 @@ namespace Robust.Shared.Containers { base.OnRemove(); - // IContainer.Shutdown modifies the _containers collection - foreach (var container in Containers.Values.ToArray()) + foreach (var container in Containers.Values) { - container.Shutdown(); + container.Shutdown(_entMan, _netMan); } Containers.Clear(); @@ -131,30 +134,19 @@ namespace Robust.Shared.Containers } /// - public void ForceRemove(EntityUid entity) - { - foreach (var container in Containers.Values) - { - if (container.Contains(entity)) - { - container.ForceRemove(entity); - return; - } - } - } - - /// - public void InternalContainerShutdown(IContainer container) - { - Containers.Remove(container.ID); - } - - /// - public bool Remove(EntityUid entity) + public bool Remove(EntityUid toremove, + TransformComponent? xform = null, + MetaDataComponent? meta = null, + bool reparent = true, + bool addToBroadphase = true, + bool force = false, + EntityCoordinates? destination = null, + Angle? localRotation = null) { foreach (var containers in Containers.Values) { - if (containers.Contains(entity)) return containers.Remove(entity); + if (containers.Contains(toremove)) + return containers.Remove(toremove, _entMan, xform, meta, reparent, addToBroadphase, force, destination, localRotation); } return true; // If we don't contain the entity, it will always be removed diff --git a/Robust.Shared/Containers/ContainerSlot.cs b/Robust.Shared/Containers/ContainerSlot.cs index 5bb8c1af29..671f2e012c 100644 --- a/Robust.Shared/Containers/ContainerSlot.cs +++ b/Robust.Shared/Containers/ContainerSlot.cs @@ -5,6 +5,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Robust.Shared.Containers @@ -73,34 +74,40 @@ namespace Robust.Shared.Containers /// public override bool Contains(EntityUid contained) { - if (contained == ContainedEntity) - return true; - return false; + if (contained != ContainedEntity) + return false; + + + var flags = IoCManager.Resolve().GetComponent(contained).Flags; + DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0); + + return true; } /// - protected override void InternalInsert(EntityUid toinsert, EntityUid oldParent, IEntityManager entMan) + protected override void InternalInsert(EntityUid toInsert, IEntityManager entMan) { - ContainedEntity = toinsert; - base.InternalInsert(toinsert, oldParent, entMan); + DebugTools.Assert(ContainedEntity == null); + ContainedEntity = toInsert; } /// - protected override void InternalRemove(EntityUid toremove, IEntityManager entMan, MetaDataComponent? meta = null) + protected override void InternalRemove(EntityUid toRemove, IEntityManager entMan) { + DebugTools.Assert(ContainedEntity == toRemove); ContainedEntity = null; - base.InternalRemove(toremove, entMan, meta); } /// - public override void Shutdown() + protected override void InternalShutdown(IEntityManager entMan, bool isClient) { - base.Shutdown(); + if (ContainedEntity is not { } entity) + return; - if (ContainedEntity is {} contained) - { - IoCManager.Resolve().DeleteEntity(contained); - } + if (!isClient) + entMan.DeleteEntity(entity); + else if (entMan.EntityExists(entity)) + Remove(entity, entMan, reparent: false, addToBroadphase: true, force: true); } } } diff --git a/Robust.Shared/Containers/IContainer.cs b/Robust.Shared/Containers/IContainer.cs index da5b549ca1..e12f759e35 100644 --- a/Robust.Shared/Containers/IContainer.cs +++ b/Robust.Shared/Containers/IContainer.cs @@ -2,6 +2,9 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Network; using Robust.Shared.Physics.Components; using Robust.Shared.Serialization.Manager.Attributes; @@ -51,11 +54,6 @@ namespace Robust.Shared.Containers /// string ID { get; } - /// - /// The container manager owning this container. - /// - IContainerManager Manager { get; } - /// /// Prevents light from escaping the container, from ex. a flashlight. /// @@ -112,19 +110,29 @@ namespace Robust.Shared.Containers /// /// Attempts to remove the entity from this container. /// - /// The entity to attempt to remove. - /// - /// If true, will attempt to re-parent the entity to the container's parent, or the grid/map. If false, will not update the transform. - /// True if the entity was removed, false otherwise. - bool Remove(EntityUid toremove, IEntityManager? entMan = null, TransformComponent? xform = null, MetaDataComponent? meta = null, bool reparent = true); + /// If false, this operation will not rigger a move or parent change event. Ignored if + /// destination is not null + /// If false, this entity will not get re-added to broadphases after removal + /// Useful if the entity is about to move or be re-inserted into another container. + /// If true, this will not perform can-remove checks. + /// Where to place the entity after removing. Avoids unnecessary broadphase updates. + /// If not specified, and reparent option is true, then the entity will either be inserted into a parent + /// container, the grid, or the map. + /// Optional final local rotation after removal. Avoids redundant move events. + bool Remove( + EntityUid toremove, + IEntityManager? entMan = null, + TransformComponent? xform = null, + MetaDataComponent? meta = null, + bool reparent = true, + bool addToBroadphase = true, + bool force = false, + EntityCoordinates? destination = null, + Angle? localRotation = null); - /// - /// Forcefully removes an entity from the container. Normally you would want to use , - /// this function should be avoided. - /// - /// The entity to attempt to remove. - /// - void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null, MetaDataComponent? meta = null); + [Obsolete("use force option in Remove()")] + void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null, MetaDataComponent? meta = null) + => Remove(toRemove, entMan, meta: meta, reparent: false, force: true); /// /// Checks if the entity is contained in this container. @@ -137,6 +145,6 @@ namespace Robust.Shared.Containers /// /// Clears the container and marks it as deleted. /// - void Shutdown(); + void Shutdown(IEntityManager? entMan = null, INetManager? netMan = null); } } diff --git a/Robust.Shared/Containers/IContainerManager.cs b/Robust.Shared/Containers/IContainerManager.cs index 4d7a0082e4..4c430895c7 100644 --- a/Robust.Shared/Containers/IContainerManager.cs +++ b/Robust.Shared/Containers/IContainerManager.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Maths; namespace Robust.Shared.Containers { @@ -22,12 +24,25 @@ namespace Robust.Shared.Containers where T : IContainer; /// - /// Attempts to remove contained inside the owning entity, - /// finding the container containing it automatically, if it is actually contained. + /// Attempts to remove the entity from some container on this entity. /// - /// The entity to remove. - /// True if the entity was successfuly removed. - bool Remove(EntityUid entity); + /// If false, this operation will not rigger a move or parent change event. Ignored if + /// destination is not null + /// If false, this entity will not get re-added to broadphases after removal + /// Useful if the entity is about to move or be re-inserted into another container. + /// If true, this will not perform can-remove checks. + /// Where to place the entity after removing. Avoids unnecessary broadphase updates. + /// If not specified, and reparent option is true, then the entity will either be inserted into a parent + /// container, the grid, or the map. + /// Optional final local rotation after removal. Avoids redundant move events. + bool Remove(EntityUid toremove, + TransformComponent? xform = null, + MetaDataComponent? meta = null, + bool reparent = true, + bool addToBroadphase = true, + bool force = false, + EntityCoordinates? destination = null, + Angle? localRotation = null); /// /// Gets the container with the specified ID. @@ -62,12 +77,5 @@ namespace Robust.Shared.Containers bool TryGetContainer(EntityUid entity, [NotNullWhen(true)] out IContainer? container); bool ContainsEntity(EntityUid entity); - - void ForceRemove(EntityUid entity); - - /// - /// DO NOT CALL THIS DIRECTLY. Call instead. - /// - void InternalContainerShutdown(IContainer container); } } diff --git a/Robust.Shared/Containers/SharedContainerSystem.Validation.cs b/Robust.Shared/Containers/SharedContainerSystem.Validation.cs new file mode 100644 index 0000000000..36a53ecf85 --- /dev/null +++ b/Robust.Shared/Containers/SharedContainerSystem.Validation.cs @@ -0,0 +1,77 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Log; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Utility; +using System; + +namespace Robust.Shared.Containers; + + +// This partial class just exists for debug asserts and bug fixing +public abstract partial class SharedContainerSystem : EntitySystem +{ + private void OnStartupValidation(EntityUid uid, ContainerManagerComponent component, ComponentStartup args) + { + var metaQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); + var physicsQuery = GetEntityQuery(); + var jointQuery = GetEntityQuery(); + foreach (var cont in component.Containers.Values) + { + foreach (var ent in cont.ContainedEntities) + { + if (!metaQuery.TryGetComponent(ent, out var meta)) + { + ValidateMissingEntity(uid, cont, ent); + continue; + } + + var xform = xformQuery.GetComponent(ent); + physicsQuery.TryGetComponent(ent, out var physics); + + DebugTools.Assert(xform.ParentUid == uid, + $"Entity not parented to its container. Entity: {ToPrettyString(ent)}, parent: {ToPrettyString(uid)}"); + DebugTools.Assert(!xform.Anchored, + $"Contained entity is anchored, Entity: {ToPrettyString(ent)}, parent: {ToPrettyString(uid)}"); + //DebugTools.Assert(physics == null || (!physics.Awake && !physics.CanCollide), + // $"Contained entity is can collide, Entity: {ToPrettyString(ent)}, parent: {ToPrettyString(uid)}"); + //DebugTools.Assert(xform.Broadphase == null, + // $"Contained entity is has non-null broadphase, Entity: {ToPrettyString(ent)}, parent: {ToPrettyString(uid)}"); + //DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0, + // $"Contained entity is is missing container flag? Entity: {ToPrettyString(ent)}, parent: {ToPrettyString(uid)}"); + + // TODO remove all the following and just have the above assert all wrapped in an #if DEBUG this is just here cause I + // CBF updating all maps. meta-data flags now get saved, eventually we should be able to just initialize + // entities in containers without having to "re-insert" them. + meta.Flags |= MetaDataFlags.InContainer; + _lookup.RemoveFromEntityTree(ent, xform, xformQuery); + ((BaseContainer)cont).RecursivelyUpdatePhysics(xform, physics, _physics, _joint, physicsQuery, xformQuery, jointQuery); + + // assert children have correct properties + ValidateChildren(xform, xformQuery, physicsQuery); + } + } + } + + protected abstract void ValidateMissingEntity(EntityUid uid, IContainer cont, EntityUid missing); + + private void ValidateChildren(TransformComponent xform, EntityQuery xformQuery, EntityQuery physicsQuery) + { + var enumerator = xform.ChildEnumerator; + while (enumerator.MoveNext(out var child)) + { + if (!xformQuery.TryGetComponent(child, out var childXform)) + continue; + + DebugTools.Assert(!xform.Anchored, + $"Child of contained entity is anchored, Entity: {ToPrettyString(child.Value)}"); + DebugTools.Assert(!physicsQuery.TryGetComponent(child, out var physics) || (!physics.Awake && !physics.CanCollide), + $"Child of contained entity is can collide, Entity: {ToPrettyString(child.Value)}"); + DebugTools.Assert(xform.Broadphase == null, + $"Child of contained entity is has non-null broadphase, Entity: {ToPrettyString(child.Value)}"); + ValidateChildren(childXform, xformQuery, physicsQuery); + } + } + +} diff --git a/Robust.Shared/Containers/SharedContainerSystem.cs b/Robust.Shared/Containers/SharedContainerSystem.cs index de3053e606..cf31fd9dad 100644 --- a/Robust.Shared/Containers/SharedContainerSystem.cs +++ b/Robust.Shared/Containers/SharedContainerSystem.cs @@ -4,20 +4,28 @@ using System.Linq; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Network; +using Robust.Shared.Physics.Systems; using Robust.Shared.Utility; namespace Robust.Shared.Containers { - public abstract class SharedContainerSystem : EntitySystem + public abstract partial class SharedContainerSystem : EntitySystem { [Dependency] private readonly SharedTransformSystem _xforms = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedJointSystem _joint = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly INetManager _netMan = default!; /// public override void Initialize() { base.Initialize(); - + SubscribeLocalEvent(OnParentChanged); + SubscribeLocalEvent(OnStartupValidation); } // TODO: Make ContainerManagerComponent ECS and make these proxy methods the real deal. @@ -101,15 +109,22 @@ namespace Robust.Shared.Containers return containerManager.ContainsEntity(containedUid); } - public void RemoveEntity(EntityUid uid, EntityUid containedUid, bool force = false, ContainerManagerComponent? containerManager = null) + public void RemoveEntity( + EntityUid uid, + EntityUid toremove, + ContainerManagerComponent? containerManager = null, + TransformComponent? containedXform = null, + MetaDataComponent? containedMeta = null, + bool reparent = true, + bool addToBroadphase = true, + bool force = false, + EntityCoordinates? destination = null, + Angle? localRotation = null) { - if (!Resolve(uid, ref containerManager) || !EntityManager.EntityExists(containedUid)) + if (!Resolve(uid, ref containerManager) || !Resolve(toremove, ref containedMeta, ref containedXform)) return; - if (force) - containerManager.ForceRemove(containedUid); - else - containerManager.Remove(containedUid); + containerManager.Remove(toremove, containedXform, containedMeta, reparent, addToBroadphase, force, destination, localRotation); } public ContainerManagerComponent.AllContainersEnumerable GetAllContainers(EntityUid uid, ContainerManagerComponent? containerManager = null) @@ -355,28 +370,14 @@ namespace Robust.Shared.Containers /// Attempts to remove all entities in a container. /// public void EmptyContainer(IContainer container, bool force = false, EntityCoordinates? moveTo = null, - bool attachToGridOrMap = false) + bool attachToGridOrMap = false, IEntityManager? entMan = null) { + IoCManager.Resolve(ref entMan); + var query = entMan.GetEntityQuery(); foreach (var entity in container.ContainedEntities.ToArray()) { - if (Deleted(entity)) - continue; - - if (force) - container.ForceRemove(entity); - else - container.Remove(entity); - - if (moveTo.HasValue || attachToGridOrMap) - { - var xform = Transform(entity); - - if (moveTo.HasValue) - _xforms.SetCoordinates(xform, moveTo.Value); - - if (attachToGridOrMap) - xform.AttachToGridOrMap(); - } + if (query.TryGetComponent(entity, out var xform)) + container.Remove(entity, entMan, xform, null, attachToGridOrMap, true, force, moveTo); } } @@ -429,16 +430,15 @@ namespace Robust.Shared.Containers #endregion - // Eject entities from their parent container if the parent change is done by the transform only. protected virtual void OnParentChanged(ref EntParentChangedMessage message) { - var oldParentEntity = message.OldParent; - - if (oldParentEntity == null || !EntityManager.EntityExists(oldParentEntity!.Value)) + var meta = MetaData(message.Entity); + if ((meta.Flags & MetaDataFlags.InContainer) == 0) return; - if (EntityManager.TryGetComponent(oldParentEntity!.Value, out IContainerManager? containerManager)) - containerManager.ForceRemove(message.Entity); + // Eject entities from their parent container if the parent change is done via setting the transform. + if (TryComp(message.OldParent, out ContainerManagerComponent? containerManager)) + containerManager.Remove(message.Entity, message.Transform, meta, reparent: false, force: true); } } } diff --git a/Robust.Shared/GameObjects/Components/MetaDataComponent.cs b/Robust.Shared/GameObjects/Components/MetaDataComponent.cs index f37841e8d0..283f9f867f 100644 --- a/Robust.Shared/GameObjects/Components/MetaDataComponent.cs +++ b/Robust.Shared/GameObjects/Components/MetaDataComponent.cs @@ -1,14 +1,13 @@ -using System; using JetBrains.Annotations; using Robust.Shared.GameStates; using Robust.Shared.IoC; -using Robust.Shared.Players; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Timing; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; +using System; namespace Robust.Shared.GameObjects { @@ -158,7 +157,7 @@ namespace Robust.Shared.GameObjects [ViewVariables] public EntityLifeStage EntityLifeStage { get; internal set; } - [ViewVariables] + [DataField("flags")] public MetaDataFlags Flags { get => _flags; diff --git a/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs b/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs index 011f35228a..2422a9ab26 100644 --- a/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs +++ b/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using Robust.Shared.Animations; using Robust.Shared.GameStates; using Robust.Shared.IoC; @@ -11,6 +8,9 @@ using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Timing; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace Robust.Shared.GameObjects { @@ -30,6 +30,15 @@ namespace Robust.Shared.GameObjects [DataField("anchored")] internal bool _anchored; + /// + /// The broadphase that this entity is currently stored on, if any. + /// + /// + /// Maybe this should be moved to its own component eventually, but at least currently comps are not structs + /// and this data is required whenever any entity moves, so this will just save a component lookup. + /// + internal BroadphaseData? Broadphase; + internal bool MatricesDirty = false; private Matrix3 _localMatrix = Matrix3.Identity; private Matrix3 _invLocalMatrix = Matrix3.Identity; @@ -829,4 +838,12 @@ namespace Robust.Shared.GameObjects TilePos = tilePos; } } + + /// + /// Data used to store information about the broad-phase that any given entity is currently on. + /// + internal record struct BroadphaseData(EntityUid Uid, bool CanCollide, bool Static) + { + // TODO include MapId if ever grids are allowed to enter null-space (leave PVS). + } } diff --git a/Robust.Shared/GameObjects/Systems/CollideOnAnchorSystem.cs b/Robust.Shared/GameObjects/Systems/CollideOnAnchorSystem.cs index 97f8b8df0d..1c239de6f2 100644 --- a/Robust.Shared/GameObjects/Systems/CollideOnAnchorSystem.cs +++ b/Robust.Shared/GameObjects/Systems/CollideOnAnchorSystem.cs @@ -25,7 +25,8 @@ namespace Robust.Shared.GameObjects private void OnAnchor(EntityUid uid, CollideOnAnchorComponent component, ref AnchorStateChangedEvent args) { - SetCollide(uid, component, args.Anchored); + if (!args.Detaching) + SetCollide(uid, component, args.Anchored); } private void SetCollide(EntityUid uid, CollideOnAnchorComponent component, bool anchored) diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs index 7f0b2e816c..b6a7950597 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs @@ -1,18 +1,20 @@ -using JetBrains.Annotations; using Robust.Shared.Configuration; using Robust.Shared.Containers; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; +using Robust.Shared.Network; using Robust.Shared.Physics; using Robust.Shared.Physics.BroadPhase; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Events; +using Robust.Shared.Timing; using Robust.Shared.Utility; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace Robust.Shared.GameObjects { @@ -60,6 +62,8 @@ namespace Robust.Shared.GameObjects public sealed partial class EntityLookupSystem : EntitySystem { [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly INetManager _netMan = default!; [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; @@ -73,15 +77,14 @@ namespace Robust.Shared.GameObjects base.Initialize(); var configManager = IoCManager.Resolve(); + SubscribeLocalEvent(OnBroadphaseTerminating); SubscribeLocalEvent(OnBroadphaseAdd); SubscribeLocalEvent(OnGridAdd); SubscribeLocalEvent(OnMapChange); SubscribeLocalEvent(OnMove); - SubscribeLocalEvent(OnContainerInsert); - SubscribeLocalEvent(OnContainerRemove); - SubscribeLocalEvent(OnBodyTypeChange); + SubscribeLocalEvent(OnBodyTypeChange); SubscribeLocalEvent(OnPhysicsUpdate); EntityManager.EntityInitialized += OnEntityInit; @@ -92,44 +95,17 @@ namespace Robust.Shared.GameObjects base.Shutdown(); EntityManager.EntityInitialized -= OnEntityInit; } - - /// - /// Updates the entity's AABB. Uses - /// - [UsedImplicitly] - public void UpdateBounds(EntityUid uid, TransformComponent? xform = null, MetaDataComponent? meta = null) - { - if (_container.IsEntityInContainer(uid, meta)) - return; - - var xformQuery = GetEntityQuery(); - - if (!xformQuery.Resolve(uid, ref xform)) - return; - - // also ensure that no parent is in a container. - DebugTools.Assert(!_container.IsEntityOrParentInContainer(uid, meta, xform, null, xformQuery)); - - var broadQuery = GetEntityQuery(); - - // TODO combine mover-coordinate fetching with BroadphaseComponent fetching. They're kinda the same thing. - var lookup = GetBroadphase(uid, xform, broadQuery, xformQuery); - - if (lookup == null) return; - - var lookupRotation = _transform.GetWorldRotation(lookup.Owner, xformQuery); - var (coordinates, rotation) = _transform.GetMoverCoordinateRotation(xform, xformQuery); - var relativeRotation = rotation - lookupRotation; - var aabb = GetAABBNoContainer(xform.Owner, coordinates.Position, relativeRotation); - - - var metaQuery = GetEntityQuery(); - var contQuery = GetEntityQuery(); - // TODO: Only container children need updating so could manually do this slightly better. - AddToEntityTree(lookup, xform, aabb, xformQuery, metaQuery, contQuery, lookupRotation); - } #region DynamicTree + private void OnBroadphaseTerminating(EntityUid uid, BroadphaseComponent component, ref EntityTerminatingEvent args) + { + // The broadphase entity terminating and all of its children are about to get detached. Instead of updating + // the broad-phase as that happens, we will just remove it. In principle, some of the null-space checks + // already effectively stop that, but again someday the client might send grids to null-space and we can't + // use those anymore. + RemComp(uid, component); + } + private void OnMapChange(MapChangedEvent ev) { if (ev.Created && ev.Map != MapId.Nullspace) @@ -176,190 +152,96 @@ namespace Robust.Shared.GameObjects return treeXform.InvWorldMatrix.TransformBox(GetWorldAABB(entity, xform)); } - internal void CreateProxies(Fixture fixture, Vector2 worldPos, Angle worldRot) + internal void CreateProxies(TransformComponent xform, Fixture fixture) { - // TODO: Grids on broadphasecomponent - if (_mapManager.IsGrid(fixture.Body.Owner)) + if (!TryGetCurrentBroadphase(xform, out var broadphase)) return; - var xformQuery = GetEntityQuery(); - var broadQuery = GetEntityQuery(); - var xform = xformQuery.GetComponent(fixture.Body.Owner); - var broadphase = GetBroadphase(fixture.Body.Owner, xformQuery.GetComponent(fixture.Body.Owner), broadQuery, xformQuery); - - if (broadphase == null || xform.MapUid == null) - { + if (!TryComp(xform.MapUid, out SharedPhysicsMapComponent? physMap)) throw new InvalidOperationException(); - } + var xformQuery = GetEntityQuery(); + var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform, xformQuery); var mapTransform = new Transform(worldPos, worldRot); + var (_, broadWorldRot, _, broadInvMatrix) = xformQuery.GetComponent(broadphase.Owner).GetWorldPositionRotationMatrixWithInv(); var broadphaseTransform = new Transform(broadInvMatrix.Transform(mapTransform.Position), mapTransform.Quaternion2D.Angle - broadWorldRot); - var moveBuffer = Comp(xform.MapUid.Value).MoveBuffer; var tree = fixture.Body.BodyType == BodyType.Static ? broadphase.StaticTree : broadphase.DynamicTree; DebugTools.Assert(fixture.ProxyCount == 0); - AddOrMoveProxies(fixture, tree, broadphaseTransform, mapTransform, moveBuffer); + AddOrMoveProxies(fixture, tree, broadphaseTransform, mapTransform, physMap.MoveBuffer); } - internal void DestroyProxies(Fixture fixture, TransformComponent xform) + internal void DestroyProxies(Fixture fixture, TransformComponent xform, SharedPhysicsMapComponent physicsMap) { - if (_mapManager.IsGrid(fixture.Body.Owner)) - return; - if (fixture.ProxyCount == 0) { Logger.Warning($"Tried to destroy fixture {fixture.ID} on {ToPrettyString(fixture.Body.Owner)} that already has no proxies?"); return; } - var xformQuery = GetEntityQuery(); - var broadQuery = GetEntityQuery(); - var broadphase = GetBroadphase(fixture.Body.Owner, xformQuery.GetComponent(fixture.Body.Owner), broadQuery, xformQuery); - - if (broadphase == null || xform.MapUid == null) - { - throw new InvalidOperationException(); - } + if (!TryGetCurrentBroadphase(xform, out var broadphase)) + return; var tree = fixture.Body.BodyType == BodyType.Static ? broadphase.StaticTree : broadphase.DynamicTree; - var moveBuffer = Comp(xform.MapUid.Value).MoveBuffer; - DestroyProxies(fixture, tree, moveBuffer); + DestroyProxies(fixture, tree, physicsMap.MoveBuffer); } #endregion - #region Entity Updating - private void UpdatePosition(TransformComponent xform, BroadphaseComponent? lookup, EntityQuery xformQuery) - { - if (lookup == null) - return; - - var lookupRotation = _transform.GetWorldRotation(lookup.Owner, xformQuery); - AddToEntityTree(lookup, xform, xformQuery, lookupRotation); - } - - private void UpdateParent(EntityUid uid, - TransformComponent xform, - BroadphaseComponent? lookup, - EntityQuery xformQuery, - EntityQuery broadQuery, - EntityUid oldParent) - { - BroadphaseComponent? oldLookup = null; - if (oldParent.IsValid() - && oldParent != uid // implies the entity was in null-space - && xformQuery.TryGetComponent(oldParent, out var parentXform) - && parentXform.MapID != MapId.Nullspace // see comment below - && !broadQuery.TryGetComponent(oldParent, out oldLookup)) - { - oldLookup = GetBroadphase(oldParent, parentXform, broadQuery, xformQuery); - } - - // Note that the parentXform.MapID != MapId.Nullspace is required because currently grids are not allowed to - // ever enter null-space. If they are in null-space, we assume that the grid is being deleted, as otherwise - // RemoveFromEntityTree() will explode. This may eventually have to change if we stop universally sending - // all grids to all players (i.e., out-of view grids will need to get sent to null-space) - // - // This also means the queries above can be reverted (check broadQuery, then xformQuery, as this will - // generally save a component lookup. - - // If lookup remained unchanged we just update the position as normal - if (oldLookup == lookup) - { - UpdatePosition(xform, lookup, xformQuery); - return; - } - - RemoveFromEntityTree(oldLookup, xform, xformQuery); - - if (lookup != null) - AddToEntityTree(lookup, xform, xformQuery, _transform.GetWorldRotation(lookup.Owner, xformQuery)); - } - #endregion - #region Entity events private void OnPhysicsUpdate(ref CollisionChangeEvent ev) { - var xformQuery = GetEntityQuery(); - var xform = xformQuery.GetComponent(ev.Body.Owner); + var xform = Transform(ev.Body.Owner); + UpdatePhysicsBroadphase(ev.Body.Owner, xform, ev.Body); - if (xform.GridUid == ev.Body.Owner) - return; - DebugTools.Assert(!_mapManager.IsGrid(ev.Body.Owner)); - - if (!ev.CanCollide && _container.IsEntityOrParentInContainer(ev.Body.Owner, null, xform, null, xformQuery)) - { - // getting inserted, skip sundries insertion and just let container insertion handle tree removal. - - // TODO: for whatever fucking cursed reason, this is currently required. - // FIX THIS, this is a hotfix - var b = GetBroadphase(ev.Body.Owner, xform, GetEntityQuery(), xformQuery); - if (b != null) - RemoveBroadTree(ev.Body, b, ev.Body.BodyType); - - return; - } - - var broadQuery = GetEntityQuery(); - var broadphase = GetBroadphase(ev.Body.Owner, xform, broadQuery, xformQuery); - - if (broadphase == null) - return; - - if (ev.CanCollide) - { - RemoveSundriesTree(ev.Body.Owner, broadphase, ev.Body.BodyType); - AddBroadTree(ev.Body, broadphase, ev.Body.BodyType, xform: xform); - } - else - { - RemoveBroadTree(ev.Body, broadphase, ev.Body.BodyType); - AddSundriesTree(ev.Body.Owner, broadphase, ev.Body.BodyType); - } + // ensure that the cached broadphase is correct. + DebugTools.Assert(_timing.ApplyingState + || xform.Broadphase == null + || ((xform.Broadphase.Value.CanCollide == ev.Body.CanCollide) + && (xform.Broadphase.Value.Static == (ev.Body.BodyType == BodyType.Static)))); } - private void OnBodyTypeChange(EntityUid uid, PhysicsComponent component, ref PhysicsBodyTypeChangedEvent args) + private void OnBodyTypeChange(EntityUid uid, TransformComponent xform, ref PhysicsBodyTypeChangedEvent args) { - // only matters if we swapped from static to non-static. + // only matters if we swapped from static to non-static or vice versa. if (args.Old != BodyType.Static && args.New != BodyType.Static) return; - var xformQuery = GetEntityQuery(); - var xform = xformQuery.GetComponent(uid); + UpdatePhysicsBroadphase(uid, xform, args.Component); + } + private void UpdatePhysicsBroadphase(EntityUid uid, TransformComponent xform, PhysicsComponent body) + { if (xform.GridUid == uid) return; DebugTools.Assert(!_mapManager.IsGrid(uid)); - // fun fact: container insertion tries to update the fucking lookups like 3 or more times, each time iterating through all of its parents. - if (_container.IsEntityOrParentInContainer(uid, null, xform, null, xformQuery)) - return; + if (xform.Broadphase is not { } old) + return; // entity is not on any broadphase + + xform.Broadphase = null; - var broadQuery = GetEntityQuery(); - var broadphase = GetBroadphase(uid, xform, broadQuery, xformQuery); + if (!TryComp(old.Uid, out BroadphaseComponent? broadphase)) + return; // broadphase probably got deleted. - if (broadphase == null) - return; - - if (component.CanCollide) - { - RemoveBroadTree(component, broadphase, args.Old); - AddBroadTree(component, broadphase, component.BodyType); - } + // remove from the old broadphase + var fixtures = Comp(uid); + if (old.CanCollide) + RemoveBroadTree(broadphase, fixtures, old.Static); else - { - RemoveSundriesTree(uid, broadphase, args.Old); - AddSundriesTree(uid, broadphase, component.BodyType); - } + (old.Static ? broadphase.StaticSundriesTree : broadphase.SundriesTree).Remove(uid); + + // Add to new broadphase + if (body.CanCollide) + AddPhysicsTree(old.Uid, broadphase, xform, body, fixtures); + else + AddSundriesTree(old.Uid, broadphase, uid, xform, body.BodyType == BodyType.Static); } - private void RemoveBroadTree(PhysicsComponent body, BroadphaseComponent lookup, BodyType bodyType, FixturesComponent? manager = null) + private void RemoveBroadTree(BroadphaseComponent lookup, FixturesComponent manager, bool staticBody) { - if (!Resolve(body.Owner, ref manager)) - return; - if (!TryComp(lookup.Owner, out var lookupXform)) { throw new InvalidOperationException("Lookup does not exist?"); @@ -372,7 +254,7 @@ namespace Robust.Shared.GameObjects throw new NotSupportedException("Nullspace lookups are not supported."); } - var tree = bodyType == BodyType.Static ? lookup.StaticTree : lookup.DynamicTree; + var tree = staticBody ? lookup.StaticTree : lookup.DynamicTree; var moveBuffer = Comp(map.Value).MoveBuffer; foreach (var fixture in manager.Fixtures.Values) @@ -394,30 +276,47 @@ namespace Robust.Shared.GameObjects fixture.Proxies = Array.Empty(); } - private void AddBroadTree(PhysicsComponent body, BroadphaseComponent lookup, BodyType bodyType, FixturesComponent? manager = null, TransformComponent? xform = null) + private void AddPhysicsTree(EntityUid broadUid, BroadphaseComponent broadphase, TransformComponent xform, PhysicsComponent body, FixturesComponent fixtures) { - if (!Resolve(body.Owner, ref manager, ref xform)) + var xformQuery = GetEntityQuery(); + var broadphaseXform = xformQuery.GetComponent(broadUid); + + if (broadphaseXform.MapID == MapId.Nullspace) return; - var tree = bodyType == BodyType.Static ? lookup.StaticTree : lookup.DynamicTree; - var xformQuery = GetEntityQuery(); + if (!TryComp(broadphaseXform.MapUid, out SharedPhysicsMapComponent? physMap)) + throw new InvalidOperationException($"Physics Broadphase is missing physics map. {ToPrettyString(broadUid)}"); + AddPhysicsTree(broadUid, broadphase, broadphaseXform, physMap, xform, body, fixtures, xformQuery); + } + + private void AddPhysicsTree( + EntityUid broadUid, + BroadphaseComponent broadphase, + TransformComponent broadphaseXform, + SharedPhysicsMapComponent physicsMap, + TransformComponent xform, + PhysicsComponent body, + FixturesComponent manager, + EntityQuery xformQuery) + { DebugTools.Assert(!_container.IsEntityOrParentInContainer(body.Owner, null, xform, null, xformQuery)); + DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadphase.Owner, body.CanCollide, body.BodyType == BodyType.Static)); + DebugTools.Assert(broadphase.Owner == broadUid); - if (!TryComp(lookup.Owner, out var lookupXform) || lookupXform.MapUid == null) - { - throw new InvalidOperationException(); - } + xform.Broadphase ??= new(broadUid, body.CanCollide, body.BodyType == BodyType.Static); + var tree = body.BodyType == BodyType.Static ? broadphase.StaticTree : broadphase.DynamicTree; + // TOOD optimize this. This function iterates UP through parents, while we are currently iterating down. var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform, xformQuery); var mapTransform = new Transform(worldPos, worldRot); - var (_, broadWorldRot, _, broadInvMatrix) = xformQuery.GetComponent(lookup.Owner).GetWorldPositionRotationMatrixWithInv(); - var broadphaseTransform = new Transform(broadInvMatrix.Transform(mapTransform.Position), mapTransform.Quaternion2D.Angle - broadWorldRot); - var moveBuffer = Comp(lookupXform.MapUid.Value).MoveBuffer; - foreach (var (_, fixture) in manager.Fixtures) + // TODO BROADPHASE PARENTING this just assumes local = world + var broadphaseTransform = new Transform(broadphaseXform.InvLocalMatrix.Transform(mapTransform.Position), mapTransform.Quaternion2D.Angle - broadphaseXform.LocalRotation); + + foreach (var fixture in manager.Fixtures.Values) { - AddOrMoveProxies(fixture, tree, broadphaseTransform, mapTransform, moveBuffer); + AddOrMoveProxies(fixture, tree, broadphaseTransform, mapTransform, physicsMap.MoveBuffer); } } @@ -462,289 +361,371 @@ namespace Robust.Shared.GameObjects fixture.ProxyCount = count; } - private void AddSundriesTree(EntityUid uid, BroadphaseComponent lookup, BodyType bodyType) + private void AddSundriesTree(EntityUid broadUid, BroadphaseComponent broadphase, EntityUid uid, TransformComponent xform, bool staticBody, Box2? aabb = null) { DebugTools.Assert(!_container.IsEntityOrParentInContainer(uid)); - var tree = bodyType == BodyType.Static ? lookup.StaticSundriesTree : lookup.SundriesTree; - tree.Add(uid); - } - - private void RemoveSundriesTree(EntityUid uid, BroadphaseComponent lookup, BodyType bodyType) - { - var tree = bodyType == BodyType.Static ? lookup.StaticSundriesTree : lookup.SundriesTree; - tree.Remove(uid); + DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadUid, false, staticBody)); + xform.Broadphase ??= new(broadUid, false, staticBody); + (staticBody ? broadphase.StaticSundriesTree : broadphase.SundriesTree).Add(uid, aabb); } private void OnEntityInit(EntityUid uid) { - var xformQuery = GetEntityQuery(); - - if (!xformQuery.TryGetComponent(uid, out var xform)) - { - return; - } - - if (_container.IsEntityOrParentInContainer(uid, null, xform, null, xformQuery)) + if (_container.IsEntityOrParentInContainer(uid) || _mapManager.IsMap(uid) || _mapManager.IsGrid(uid)) return; - if (_mapManager.IsMap(uid) || - _mapManager.IsGrid(uid)) - { - return; - } - - var broadQuery = GetEntityQuery(); - var lookup = GetBroadphase(uid, xform, broadQuery, xformQuery); - - // If nullspace or the likes. - if (lookup == null) return; - - var lookupRotation = _transform.GetWorldRotation(lookup.Owner, xformQuery); - var (coordinates, rotation) = _transform.GetMoverCoordinateRotation(xform, xformQuery); - var relativeRotation = rotation - lookupRotation; - DebugTools.Assert(coordinates.EntityId == lookup.Owner); - - var metaQuery = GetEntityQuery(); - var contQuery = GetEntityQuery(); - // If we're contained then LocalRotation should be 0 anyway. - var aabb = GetAABBNoContainer(uid, coordinates.Position, relativeRotation); - - // Any child entities should be handled by their own OnEntityInit - AddToEntityTree(lookup, xform, aabb, xformQuery, metaQuery, contQuery, lookupRotation, false); + // TODO can this just be done implicitly via transform startup? + // or do things need to be in trees for other component startup logic? + FindAndAddToEntityTree(uid); } private void OnMove(ref MoveEvent args) { - var xformQuery = GetEntityQuery(); - - // Is this a grid? if (args.Component.GridUid == args.Sender) return; DebugTools.Assert(!_mapManager.IsGrid(args.Sender)); - var metaQuery = GetEntityQuery(); - var meta = metaQuery.GetComponent(args.Sender); - - if (meta.EntityLifeStage < EntityLifeStage.Initialized) - return; - - if (_container.IsEntityOrParentInContainer(args.Sender, meta, args.Component, metaQuery, xformQuery)) - { - // This move might be due to a parent change as a result of getting inserted into a container. In that - // case, we will just let the container insert event handle that. Note that the in-container flag gets - // set BEFORE insert parent change, and gets unset before the container removal parent-change. So if it - // is set here, this must mean we are getting inserted. - // - // However, this means that this method will still get run in full on container removal. Additionally, - // because not all container removals are guaranteed to result in a parent change, container removal - // events also need to add the entity to a tree. So if an entity gets ejected/teleported to some other - // grid this results in add-to-tree -> remove-from-tree -> add-to-tree. - // - // TODO IMPROVE CONTAINER REMOVAL HANDLING - return; - } - if (args.Component.MapUid == args.Sender) return; DebugTools.Assert(!_mapManager.IsMap(args.Sender)); - var broadQuery = GetEntityQuery(); - var lookup = GetBroadphase(args.Sender, args.Component, broadQuery, xformQuery); - if (args.ParentChanged) - UpdateParent(args.Sender, args.Component, lookup, xformQuery, broadQuery, args.OldPosition.EntityId); + UpdateParent(args.Sender, args.Component, args.OldPosition.EntityId); else - UpdatePosition(args.Component, lookup, xformQuery); + UpdateEntityTree(args.Sender, args.Component); } - private void OnContainerRemove(EntRemovedFromContainerMessage ev) + private void UpdateParent(EntityUid uid, TransformComponent xform, EntityUid oldParent) { - var broadQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - var xform = xformQuery.GetComponent(ev.Entity); - var lookup = GetBroadphase(ev.Entity, xform, broadQuery, xformQuery); + if (!TryGetCurrentBroadphase(xform, out var oldBroadphase)) + return; // If the entity was not already in a broadphase, parent changes will not automatically add it. - if (lookup == null) return; - - AddToEntityTree(lookup, xform, xformQuery, _transform.GetWorldRotation(lookup.Owner, xformQuery)); - } - - private void OnContainerInsert(EntInsertedIntoContainerMessage ev) - { - var xformQuery = GetEntityQuery(); - var broadQuery = GetEntityQuery(); - BroadphaseComponent? lookup; - - if (ev.OldParent == EntityUid.Invalid) - return; - - if (!broadQuery.TryGetComponent(ev.OldParent, out lookup)) + if (oldBroadphase != null && Transform(oldParent).MapID == MapId.Nullspace) { - if (!xformQuery.TryGetComponent(ev.OldParent, out var parentXform)) - return; - - lookup = GetBroadphase(ev.OldParent, parentXform, broadQuery, xformQuery); + oldBroadphase = null; + // Note that the parentXform.MapID != MapId.Nullspace is required because currently grids are not allowed to + // ever enter null-space. If they are in null-space, we assume that the grid is being deleted, as otherwise + // RemoveFromEntityTree() will explode. This may eventually have to change if we stop universally sending + // all grids to all players (i.e., out-of view grids will need to get sent to null-space) + // + // This also means the queries above can be reverted (check broadQuery, then xformQuery, as this will + // generally save a component lookup. } - RemoveFromEntityTree(lookup, xformQuery.GetComponent(ev.Entity), xformQuery); - } + var broadQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); + var physicsQuery = GetEntityQuery(); + var fixturesQuery = GetEntityQuery(); + TryFindBroadphase(xform, broadQuery, xformQuery, out var newBroadphase); - private void AddToEntityTree( - BroadphaseComponent? lookup, - TransformComponent xform, - EntityQuery xformQuery, - Angle lookupRotation, - bool recursive = true) - { - if (lookup == null) + if (oldBroadphase != null && oldBroadphase != newBroadphase) + { + + var oldBroadphaseXform = xformQuery.GetComponent(oldBroadphase.Owner); + if (!TryComp(oldBroadphaseXform.MapUid, out SharedPhysicsMapComponent? oldPhysMap)) + { + throw new InvalidOperationException( + $"Old broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(oldBroadphase.Owner)}"); + } + + RemoveFromEntityTree(oldBroadphase.Owner, oldBroadphase, oldBroadphaseXform, oldPhysMap, uid, xform, xformQuery, physicsQuery, fixturesQuery); + } + + if (newBroadphase == null) return; - // TODO combine mover-coordinate fetching with BroadphaseComponent fetching. They're kinda the same thing. - var (coordinates, rotation) = _transform.GetMoverCoordinateRotation(xform, xformQuery); - var relativeRotation = rotation - lookupRotation; - var aabb = GetAABBNoContainer(xform.Owner, coordinates.Position, relativeRotation); var metaQuery = GetEntityQuery(); var contQuery = GetEntityQuery(); - AddToEntityTree(lookup, xform, aabb, xformQuery, metaQuery, contQuery, lookupRotation, recursive); + + var newBroadphaseXform = xformQuery.GetComponent(newBroadphase.Owner); + if (!TryComp(newBroadphaseXform.MapUid, out SharedPhysicsMapComponent? physMap)) + { + throw new InvalidOperationException( + $"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(newBroadphase.Owner)}"); + } + + AddToEntityTree( + newBroadphase.Owner, + newBroadphase, + newBroadphaseXform, + physMap, + uid, + xform, + xformQuery, + metaQuery, + contQuery, + physicsQuery, + fixturesQuery); + } + + public void FindAndAddToEntityTree(EntityUid uid, TransformComponent? xform = null) + { + var xformQuery = GetEntityQuery(); + if (!xformQuery.Resolve(uid, ref xform)) + return; + + var broadQuery = GetEntityQuery(); + if (!TryFindBroadphase(xform, broadQuery, xformQuery, out var broadphase)) + return; + + AddToEntityTree(broadphase, uid, xform, xformQuery); + } + + /// + /// Variant of that just re-adds the entity to the current tree (updates positions). + /// + public void UpdateEntityTree(EntityUid uid, TransformComponent? xform = null) + { + var xformQuery = GetEntityQuery(); + if (!xformQuery.Resolve(uid, ref xform)) + return; + + if (!TryGetCurrentBroadphase(xform, out var broadphase)) + return; + + AddToEntityTree(broadphase, uid, xform, xformQuery); } private void AddToEntityTree( - BroadphaseComponent? lookup, + BroadphaseComponent broadphase, + EntityUid uid, + TransformComponent xform, + EntityQuery xformQuery, + bool recursive = true) + { + var metaQuery = GetEntityQuery(); + var contQuery = GetEntityQuery(); + var physicsQuery = GetEntityQuery(); + var fixturesQuery = GetEntityQuery(); + + var broadphaseXform = xformQuery.GetComponent(broadphase.Owner); + if (!TryComp(broadphaseXform.MapUid, out SharedPhysicsMapComponent? physMap)) + { + throw new InvalidOperationException( + $"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadphase.Owner)}"); + } + + AddToEntityTree( + broadphase.Owner, + broadphase, + broadphaseXform, + physMap, + uid, + xform, + xformQuery, + metaQuery, + contQuery, + physicsQuery, + fixturesQuery, + recursive); + } + + private void AddToEntityTree( + EntityUid broadUid, + BroadphaseComponent broadphase, + TransformComponent broadphaseXform, + SharedPhysicsMapComponent physicsMap, + EntityUid uid, TransformComponent xform, - Box2 aabb, EntityQuery xformQuery, EntityQuery metaQuery, EntityQuery contQuery, - Angle lookupRotation, + EntityQuery physicsQuery, + EntityQuery fixturesQuery, bool recursive = true) { - // If entity is in nullspace then no point keeping track of data structure. - if (lookup == null) return; - - AddTree(xform.Owner, lookup, aabb, xform: xform); - - var childEnumerator = xform.ChildEnumerator; - - if (xform.ChildCount == 0 || !recursive) return; - - if (contQuery.HasComponent(xform.Owner)) + if (!physicsQuery.TryGetComponent(uid, out var body) || !body.CanCollide) { - while (childEnumerator.MoveNext(out var child)) - { - if ((metaQuery.GetComponent(child.Value).Flags & MetaDataFlags.InContainer) != 0x0) - continue; + // TOOD optimize this. This function iterates UP through parents, while we are currently iterating down. + var (coordinates, rotation) = _transform.GetMoverCoordinateRotation(xform, xformQuery); - var childXform = xformQuery.GetComponent(child.Value); - // TOOD optimize this. This function iterates UP through parents, while we are currently iterating down. - var (coordinates, rotation) = _transform.GetMoverCoordinateRotation(childXform, xformQuery); - var relativeRotation = rotation - lookupRotation; - var childAABB = GetAABBNoContainer(child.Value, coordinates.Position, relativeRotation); - AddToEntityTree(lookup, childXform, childAABB, xformQuery, metaQuery, contQuery, lookupRotation); - } + // TODO BROADPHASE PARENTING this just assumes local = world + var relativeRotation = rotation - broadphaseXform.LocalRotation; + + var aabb = GetAABBNoContainer(uid, coordinates.Position, relativeRotation); + AddSundriesTree(broadUid, broadphase, uid, xform, body?.BodyType == BodyType.Static, aabb); } else + { + AddPhysicsTree(broadUid, broadphase, broadphaseXform, physicsMap, xform, body, fixturesQuery.GetComponent(uid), xformQuery); + } + + var childEnumerator = xform.ChildEnumerator; + if (xform.ChildCount == 0 || !recursive) + return; + + if (!contQuery.HasComponent(xform.Owner)) { while (childEnumerator.MoveNext(out var child)) { var childXform = xformQuery.GetComponent(child.Value); - - // TOOD optimize this. This function iterates UP through parents, while we are currently iterating down. - var (coordinates, rotation) = _transform.GetMoverCoordinateRotation(childXform, xformQuery); - var relativeRotation = rotation - lookupRotation; - var childAABB = GetAABBNoContainer(child.Value, coordinates.Position, relativeRotation); - AddToEntityTree(lookup, childXform, childAABB, xformQuery, metaQuery, contQuery, lookupRotation); + AddToEntityTree(broadUid, broadphase, broadphaseXform, physicsMap, child.Value, childXform, xformQuery, metaQuery, contQuery, physicsQuery, fixturesQuery); } - } - } - - private void AddTree(EntityUid uid, BroadphaseComponent broadphase, Box2 aabb, PhysicsComponent? body = null, TransformComponent? xform = null) - { - if (!Resolve(uid, ref body, false) || !body.CanCollide) - { - if (body?.BodyType == BodyType.Static) - broadphase.StaticSundriesTree.AddOrUpdate(uid, aabb); - else - broadphase.SundriesTree.AddOrUpdate(uid, aabb); return; } - AddBroadTree(body, broadphase, body.BodyType, xform: xform); - } - - private void RemoveTree(EntityUid uid, BroadphaseComponent broadphase, PhysicsComponent? body = null) - { - if (!Resolve(uid, ref body, false) || !body.CanCollide) - { - if (body?.BodyType == BodyType.Static) - broadphase.StaticSundriesTree.Remove(uid); - else - broadphase.SundriesTree.Remove(uid); - return; - } - - RemoveBroadTree(body, broadphase, body.BodyType); - } - - /// - /// Recursively iterates through this entity's children and removes them from the entitylookupcomponent. - /// - private void RemoveFromEntityTree(BroadphaseComponent? lookup, TransformComponent xform, EntityQuery xformQuery, bool recursive = true) - { - // TODO: Move this out of the loop - if (lookup == null) return; - - RemoveTree(xform.Owner, lookup); - - if (!recursive) return; - - var childEnumerator = xform.ChildEnumerator; - while (childEnumerator.MoveNext(out var child)) { - RemoveFromEntityTree(lookup, xformQuery.GetComponent(child.Value), xformQuery); + if ((metaQuery.GetComponent(child.Value).Flags & MetaDataFlags.InContainer) != 0x0) + continue; + + var childXform = xformQuery.GetComponent(child.Value); + AddToEntityTree(broadUid, broadphase, broadphaseXform, physicsMap, child.Value, childXform, xformQuery, metaQuery, contQuery, physicsQuery, fixturesQuery); } } /// - /// Attempt to get the relevant broadphase for this entity. - /// Can return null if it's the map entity. + /// Recursively iterates through this entity's children and removes them from the BroadphaseComponent. /// - private BroadphaseComponent? GetBroadphase(TransformComponent xform) + public void RemoveFromEntityTree(EntityUid uid, TransformComponent xform, EntityQuery xformQuery) { - if (xform.MapID == MapId.Nullspace) return null; + if (!TryGetCurrentBroadphase(xform, out var broadphase)) + return; - var broadQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - return GetBroadphase(xform.Owner, xform, broadQuery, xformQuery); + var physicsQuery = GetEntityQuery(); + var fixturesQuery = GetEntityQuery(); + + var broadphaseXform = xformQuery.GetComponent(broadphase.Owner); + if (broadphaseXform.MapID == MapId.Nullspace) + return; + + if (!TryComp(broadphaseXform.MapUid, out SharedPhysicsMapComponent? physMap)) + { + throw new InvalidOperationException( + $"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadphase.Owner)}"); + } + + RemoveFromEntityTree(broadphase.Owner, broadphase, broadphaseXform, physMap, uid, xform, xformQuery, physicsQuery, fixturesQuery); } - public BroadphaseComponent? GetBroadphase(EntityUid uid) + /// + /// Recursively iterates through this entity's children and removes them from the BroadphaseComponent. + /// + private void RemoveFromEntityTree( + EntityUid broadUid, + BroadphaseComponent broadphase, + TransformComponent broadphaseXform, + SharedPhysicsMapComponent physicsMap, + EntityUid uid, + TransformComponent xform, + EntityQuery xformQuery, + EntityQuery physicsQuery, + EntityQuery fixturesQuery, + bool recursive = true) + { + if (xform.Broadphase == null) + { + // this entity was probably inside of a container during a recursive iteration. This should mean all of + // its own children are also not on any broadphase. + return; + } + + if (xform.Broadphase.Value.Uid != broadUid) + { + // This may happen when the client has deferred broadphase updates, where maybe an entity from one + // broadphase was parented to one from another. + DebugTools.Assert(_netMan.IsClient); + broadUid = xform.Broadphase.Value.Uid; + broadphaseXform = xformQuery.GetComponent(broadUid); + if (broadphaseXform.MapID == MapId.Nullspace) + return; + + if (!TryComp(broadphaseXform.MapUid, out SharedPhysicsMapComponent? map)) + { + throw new InvalidOperationException( + $"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadUid)}"); + } + physicsMap = map; + } + + if (xform.Broadphase.Value.CanCollide) + RemoveBroadTree(broadphase, fixturesQuery.GetComponent(uid), xform.Broadphase.Value.Static); + else if (xform.Broadphase.Value.Static) + broadphase.StaticSundriesTree.Remove(uid); + else + broadphase.SundriesTree.Remove(uid); + + xform.Broadphase = null; + if (!recursive) + return; + + var childEnumerator = xform.ChildEnumerator; + while (childEnumerator.MoveNext(out var child)) + { + RemoveFromEntityTree( + broadUid, + broadphase, + broadphaseXform, + physicsMap, + child.Value, + xformQuery.GetComponent(child.Value), + xformQuery, + physicsQuery, + fixturesQuery); + } + } + + public bool TryGetCurrentBroadphase(TransformComponent xform, [NotNullWhen(true)] out BroadphaseComponent? broadphase) + { + broadphase = null; + if (xform.Broadphase == null) + return false; + + if (!TryComp(xform.Broadphase.Value.Uid, out broadphase)) + { + // broadphase was probably deleted during + xform.Broadphase = null; + return false; + } + + return true; + } + + public BroadphaseComponent? GetCurrentBroadphase(TransformComponent xform) + { + TryGetCurrentBroadphase(xform, out var broadphase); + return broadphase; + } + + public BroadphaseComponent? FindBroadphase(EntityUid uid) + { + TryFindBroadphase(uid, out var broadphase); + return broadphase; + } + + public bool TryFindBroadphase(EntityUid uid, [NotNullWhen(true)] out BroadphaseComponent? broadphase) { var broadQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); - return GetBroadphase(uid, xformQuery.GetComponent(uid), broadQuery, xformQuery); + return TryFindBroadphase(xformQuery.GetComponent(uid), broadQuery, xformQuery, out broadphase); } - public BroadphaseComponent? GetBroadphase(EntityUid uid, TransformComponent xform, EntityQuery broadQuery, EntityQuery xformQuery) + public bool TryFindBroadphase( + TransformComponent xform, + EntityQuery broadQuery, + EntityQuery xformQuery, + [NotNullWhen(true)] out BroadphaseComponent? broadphase) { - if (xform.MapID == MapId.Nullspace) return null; + if (xform.MapID == MapId.Nullspace || _container.IsEntityOrParentInContainer(xform.Owner, null, xform, null, xformQuery)) + { + broadphase = null; + return false; + } var parent = xform.ParentUid; - // if it's map (or in null-space) return null. Grids should return the map's broadphase. - // TODO provide variant that also returns world rotation (and maybe position). Avoids having to iterate though parents twice. while (parent.IsValid()) { - if (broadQuery.TryGetComponent(parent, out var comp)) - return comp; + if (broadQuery.TryGetComponent(parent, out broadphase)) + return true; parent = xformQuery.GetComponent(parent).ParentUid; } - return null; + broadphase = null; + return false; } - #endregion #region Bounds @@ -752,7 +733,7 @@ namespace Robust.Shared.GameObjects /// /// Get the AABB of an entity with the supplied position and angle. Tries to consider if the entity is in a container. /// - internal Box2 GetAABB(EntityUid uid, Vector2 position, Angle angle, TransformComponent xform, EntityQuery xformQuery) + public Box2 GetAABB(EntityUid uid, Vector2 position, Angle angle, TransformComponent xform, EntityQuery xformQuery) { // If we're in a container then we just use the container's bounds. if (_container.TryGetOuterContainer(uid, xform, out var container, xformQuery)) @@ -766,7 +747,7 @@ namespace Robust.Shared.GameObjects /// /// Get the AABB of an entity with the supplied position and angle without considering containers. /// - private Box2 GetAABBNoContainer(EntityUid uid, Vector2 position, Angle angle) + public Box2 GetAABBNoContainer(EntityUid uid, Vector2 position, Angle angle) { if (TryComp(uid, out var worldLookup)) { diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs index 71754be4cb..99fdd48966 100644 --- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs @@ -1,9 +1,8 @@ -using System; -using System.Collections.Generic; using JetBrains.Annotations; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; +using System.Collections.Generic; namespace Robust.Shared.GameObjects { @@ -16,6 +15,7 @@ namespace Robust.Shared.GameObjects { base.Initialize(); + SubscribeLocalEvent(OnMapAdd); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnMapRemoved); @@ -25,6 +25,8 @@ namespace Robust.Shared.GameObjects SubscribeLocalEvent(OnGridRemove); } + protected abstract void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args); + private void OnMapInit(EntityUid uid, MapComponent component, ComponentInit args) { var msg = new MapChangedEvent(component.WorldMap, true); diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index 3565d31597..7352d033c7 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -96,14 +96,16 @@ public abstract partial class SharedTransformSystem && AnchorEntity(xform, grid, grid.TileIndicesFor(xform.Coordinates)); } - public void Unanchor(TransformComponent xform) + public void Unanchor(TransformComponent xform, bool setPhysics = true) { if (!xform._anchored) return; Dirty(xform); xform._anchored = false; - _physics.TrySetBodyType(xform.Owner, BodyType.Dynamic); + + if (setPhysics) + _physics.TrySetBodyType(xform.Owner, BodyType.Dynamic); if (xform.LifeStage < ComponentLifeStage.Initialized) return; @@ -412,10 +414,18 @@ public abstract partial class SharedTransformSystem if (value.EntityId != xform._parent) { var xformQuery = GetEntityQuery(); - newParent ??= xformQuery.GetComponent(value.EntityId); - DebugTools.Assert(newParent.Owner == value.EntityId); - DebugTools.Assert(value.EntityId != xform.Owner, $"Can't parent a {nameof(TransformComponent)} to itself."); + if (!xformQuery.Resolve(value.EntityId, ref newParent)) + { + QueueDel(xform.Owner); + throw new InvalidOperationException($"Attempted to parent entity {ToPrettyString(xform.Owner)} to non-existent entity {value.EntityId}"); + } + + if (value.EntityId == xform.Owner) + { + QueueDel(xform.Owner); + throw new InvalidOperationException($"Attempted to parent an entity to itself: {ToPrettyString(xform.Owner)}"); + } if (newParent.LifeStage > ComponentLifeStage.Running || LifeStage(value.EntityId) > EntityLifeStage.MapInitialized) { @@ -517,6 +527,7 @@ public abstract partial class SharedTransformSystem internal void OnGetState(EntityUid uid, TransformComponent component, ref ComponentGetState args) { + DebugTools.Assert(!component.ParentUid.IsValid() || (!Deleted(component.ParentUid) && !EntityManager.IsQueuedForDeletion(component.ParentUid))); args.State = new TransformComponentState( component.LocalPosition, component.LocalRotation, @@ -919,14 +930,16 @@ public abstract partial class SharedTransformSystem public void DetachParentToNull(TransformComponent xform, EntityQuery xformQuery, EntityQuery metaQuery, TransformComponent? oldConcrete = null) { var oldParent = xform._parent; - - // Even though they may already be in nullspace we may want to deparent them anyway if (!oldParent.IsValid()) { DebugTools.Assert(!xform.Anchored); + DebugTools.Assert((MetaData(xform.Owner).Flags & MetaDataFlags.InContainer) == 0x0); return; } + // Before making any changes to physics or transforms, remove from the current broadphase + _lookup.RemoveFromEntityTree(xform.Owner, xform, xformQuery); + // Stop any active lerps xform._nextPosition = null; xform._nextRotation = null; @@ -967,6 +980,7 @@ public abstract partial class SharedTransformSystem var ev = new MoveEvent(xform.Owner, oldPos, default, oldRot, default, xform, _gameTiming.ApplyingState); RaiseLocalEvent(xform.Owner, ref ev, true); Dirty(xform); + DebugTools.Assert((MetaData(xform.Owner).Flags & MetaDataFlags.InContainer) == 0x0); } #endregion } diff --git a/Robust.Shared/Map/MapManager.MapCollection.cs b/Robust.Shared/Map/MapManager.MapCollection.cs index fcf9d6abaa..adb0d0b2e9 100644 --- a/Robust.Shared/Map/MapManager.MapCollection.cs +++ b/Robust.Shared/Map/MapManager.MapCollection.cs @@ -70,10 +70,6 @@ internal partial class MapManager MapDestroyed?.Invoke(this, args); _mapEntities.Remove(mapId); } - else - { - _mapEntities[mapId] = EntityUid.Invalid; - } Logger.InfoS("map", $"Deleting map {mapId}"); } @@ -93,6 +89,7 @@ internal partial class MapManager /// public EntityUid CreateNewMapEntity(MapId mapId) { + DebugTools.Assert(mapId != MapId.Nullspace); #if DEBUG DebugTools.Assert(_dbgGuardRunning); #endif @@ -146,8 +143,6 @@ internal partial class MapManager //Note: This prevents setting a subgraph as the root, since the subgraph will be deleted EntityManager.DeleteEntity(oldEntId); } - else - _mapEntities.Add(mapId, EntityUid.Invalid); var raiseEvent = false; @@ -279,10 +274,6 @@ internal partial class MapManager Logger.DebugS("map", $"Binding map {actualId} to entity {newEnt}"); } } - else - { - _mapEntities.Add(MapId.Nullspace, EntityUid.Invalid); - } var args = new MapEventArgs(actualId); OnMapCreatedGridTree(args); diff --git a/Robust.Shared/Physics/Dynamics/ContactManager.cs b/Robust.Shared/Physics/Dynamics/ContactManager.cs index 6c3792325b..340e1a1449 100644 --- a/Robust.Shared/Physics/Dynamics/ContactManager.cs +++ b/Robust.Shared/Physics/Dynamics/ContactManager.cs @@ -433,9 +433,8 @@ namespace Robust.Shared.Physics.Dynamics var proxyA = fixtureA.Proxies[indexA]; var proxyB = fixtureB.Proxies[indexB]; - var broadQuery = _entityManager.GetEntityQuery(); - var broadphaseA = _lookup.GetBroadphase(bodyA.Owner, xformQuery.GetComponent(bodyA.Owner), broadQuery, xformQuery); - var broadphaseB = _lookup.GetBroadphase(bodyB.Owner, xformQuery.GetComponent(bodyB.Owner), broadQuery, xformQuery); + var broadphaseA = _lookup.GetCurrentBroadphase(xformQuery.GetComponent(bodyA.Owner)); + var broadphaseB = _lookup.GetCurrentBroadphase(xformQuery.GetComponent(bodyB.Owner)); var overlap = false; // We can have cross-broadphase proxies hence need to change them to worldspace diff --git a/Robust.Shared/Physics/Events/CollisionChangeEvent.cs b/Robust.Shared/Physics/Events/CollisionChangeEvent.cs index 0e6f7dbfad..6ef20f83ab 100644 --- a/Robust.Shared/Physics/Events/CollisionChangeEvent.cs +++ b/Robust.Shared/Physics/Events/CollisionChangeEvent.cs @@ -3,6 +3,9 @@ using Robust.Shared.Physics.Components; namespace Robust.Shared.Physics.Events { + /// + /// These events are broadcast (not directed) whenever an entity's ability to collide changes. + /// [ByRefEvent] public readonly struct CollisionChangeEvent { diff --git a/Robust.Shared/Physics/Systems/FixtureSystem.cs b/Robust.Shared/Physics/Systems/FixtureSystem.cs index 9591a4f9cc..b9de585a8e 100644 --- a/Robust.Shared/Physics/Systems/FixtureSystem.cs +++ b/Robust.Shared/Physics/Systems/FixtureSystem.cs @@ -86,8 +86,7 @@ namespace Robust.Shared.Physics.Systems if (body.CanCollide) { - var (worldPos, worldRot) = xform.GetWorldPositionRotation(); - _lookup.CreateProxies(fixture, worldPos, worldRot); + _lookup.CreateProxies(xform, fixture); } // Supposed to be wrapped in density but eh @@ -204,11 +203,11 @@ namespace Robust.Shared.Physics.Systems { physicsMap.ContactManager.Destroy(contact); } - } - if (body.CanCollide) - { - _lookup.DestroyProxies(fixture, xform); + if (body.CanCollide && xform.GridUid != xform.Owner) + { + _lookup.DestroyProxies(fixture, xform, physicsMap); + } } if (updates) diff --git a/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs b/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs index c0903149b5..dd6368a131 100644 --- a/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs @@ -319,9 +319,7 @@ namespace Robust.Shared.Physics.Systems // Logger.DebugS("physics", $"Checking proxy for {proxy.Fixture.Body.Owner} on {broadphase.Owner}"); Box2 aabb; - var proxyBroad = _lookup.GetBroadphase(proxy.Fixture.Body.Owner, xform, broadphaseQuery, xformQuery); - - if (proxyBroad == null) + if (!_lookup.TryGetCurrentBroadphase(xform, out var proxyBroad)) { _logger.Error($"Found null broadphase for {ToPrettyString(proxy.Fixture.Body.Owner)}"); DebugTools.Assert(false); @@ -387,22 +385,18 @@ namespace Robust.Shared.Physics.Systems }, aabb, true); } - public void RegenerateContacts(PhysicsComponent body) + public void RegenerateContacts(PhysicsComponent body, TransformComponent? xform = null, FixturesComponent? fixtures = null) { _physicsSystem.DestroyContacts(body); - var broadQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); + if (!Resolve(body.Owner, ref xform, ref fixtures)) + return; - var broadphase = _lookup.GetBroadphase(body.Owner, xformQuery.GetComponent(body.Owner), broadQuery, xformQuery); + if (!_lookup.TryGetCurrentBroadphase(xform, out var broadphase)) + return; - if (broadphase != null) + foreach (var fixture in fixtures.Fixtures.Values) { - var mapId = EntityManager.GetComponent(body.Owner).MapID; - - foreach (var fixture in EntityManager.GetComponent(body.Owner).Fixtures.Values) - { - TouchProxies(mapId, broadphase, fixture); - } + TouchProxies(xform.MapID, broadphase, fixture); } } @@ -426,7 +420,7 @@ namespace Robust.Shared.Physics.Systems physicsMap.MoveBuffer[proxy] = aabb; } - public void Refilter(Fixture fixture) + public void Refilter(Fixture fixture, TransformComponent? xform = null) { // TODO: Call this method whenever collisionmask / collisionlayer changes // TODO: This should never becalled when body is null. @@ -441,14 +435,13 @@ namespace Robust.Shared.Physics.Systems contact.Flags |= ContactFlags.Filter; } - var broadQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - var broadphase = _lookup.GetBroadphase(fixture.Body.Owner, xformQuery.GetComponent(fixture.Body.Owner), broadQuery, xformQuery); + if (!Resolve(fixture.Body.Owner, ref xform)) + return; - // If nullspace or whatever ignore it. - if (broadphase == null) return; + if (!_lookup.TryGetCurrentBroadphase(xform, out var broadphase)) + return; - TouchProxies(Transform(fixture.Body.Owner).MapID, broadphase, fixture); + TouchProxies(xform.MapID, broadphase, fixture); } // TODO: The below is slow and should just query the map's broadphase directly. The problem is that diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs index 9b41fd9a01..636ad6285d 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs @@ -88,18 +88,15 @@ namespace Robust.Shared.Physics.Systems int collisionMask, bool approximate = true, PhysicsComponent? body = null, - FixturesComponent? fixtureComp = null) + FixturesComponent? fixtureComp = null, + TransformComponent? xform = null) { var entities = new HashSet(); - if (!Resolve(uid, ref body, ref fixtureComp, false)) + if (!Resolve(uid, ref body, ref fixtureComp, ref xform, false)) return entities; - var broadQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - var broadphase = _lookup.GetBroadphase(uid, xformQuery.GetComponent(uid), broadQuery, xformQuery); - - if (broadphase == null) + if (!_lookup.TryGetCurrentBroadphase(xform, out var broadphase)) return entities; var state = (body, entities); diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs index 0d1f2daad9..598e8299a5 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs @@ -64,12 +64,6 @@ namespace Robust.Shared.Physics.Systems _sawmill = Logger.GetSawmill("physics"); _sawmill.Level = LogLevel.Info; - SubscribeLocalEvent(ev => - { - if (ev.Created) - OnMapAdded(ref ev); - }); - SubscribeLocalEvent(HandleGridInit); SubscribeLocalEvent(OnWake); SubscribeLocalEvent(OnSleep); @@ -266,8 +260,6 @@ namespace Robust.Shared.Physics.Systems configManager.UnsubValueChanged(CVars.AutoClearForces, OnAutoClearChange); } - protected abstract void OnMapAdded(ref MapChangedEvent eventArgs); - private void OnWake(ref PhysicsWakeEvent @event) { var mapId = EntityManager.GetComponent(@event.Body.Owner).MapID; diff --git a/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs b/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs index 1679d38feb..de233cfd33 100644 --- a/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs +++ b/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs @@ -6,6 +6,7 @@ using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; +using Robust.Shared.Utility; // ReSharper disable AccessToStaticMemberViaDerivedType @@ -305,35 +306,37 @@ namespace Robust.UnitTesting.Server.GameObjects.Components public override List ExpectedEntities => _expectedEntities; /// - protected override void InternalInsert(EntityUid toinsert, EntityUid oldParent, IEntityManager entMan) + protected override void InternalInsert(EntityUid toInsert, IEntityManager entMan) { - _containerList.Add(toinsert); - base.InternalInsert(toinsert, oldParent, entMan); + _containerList.Add(toInsert); } /// - protected override void InternalRemove(EntityUid toremove, IEntityManager entMan, MetaDataComponent? meta = null) + protected override void InternalRemove(EntityUid toRemove, IEntityManager entMan) { - _containerList.Remove(toremove); - base.InternalRemove(toremove, entMan, meta); + _containerList.Remove(toRemove); } /// public override bool Contains(EntityUid contained) { - return _containerList.Contains(contained); + if (!_containerList.Contains(contained)) + return false; + + var flags = IoCManager.Resolve().GetComponent(contained).Flags; + DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0); + return true; } /// - public override void Shutdown() + protected override void InternalShutdown(IEntityManager entMan, bool isClient) { - base.Shutdown(); - - var entMan = IoCManager.Resolve(); - foreach (var entity in _containerList) { - entMan.DeleteEntity(entity); + if (!isClient) + entMan.DeleteEntity(entity); + else if (entMan.EntityExists(entity)) + Remove(entity, entMan, reparent: false, addToBroadphase: true, force: true); } } diff --git a/Robust.UnitTesting/Shared/Map/MapManager_Tests.cs b/Robust.UnitTesting/Shared/Map/MapManager_Tests.cs index 72efc40b15..a3f49e7791 100644 --- a/Robust.UnitTesting/Shared/Map/MapManager_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/MapManager_Tests.cs @@ -3,6 +3,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.UnitTesting.Server; +using System.Management; namespace Robust.UnitTesting.Shared.Map { @@ -61,17 +62,9 @@ namespace Robust.UnitTesting.Shared.Map { var sim = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); - - mapMan.CreateNewMapEntity(MapId.Nullspace); - var oldEntity = entMan.CreateEntityUninitialized(null, MapCoordinates.Nullspace); entMan.InitializeComponents(oldEntity); - - mapMan.Restart(); - - Assert.That(mapMan.MapExists(MapId.Nullspace), Is.True); - Assert.That(mapMan.GridExists(EntityUid.Invalid), Is.False); + entMan.Shutdown(); Assert.That(entMan.Deleted(oldEntity), Is.True); } @@ -114,9 +107,6 @@ namespace Robust.UnitTesting.Shared.Map // Arrange var sim = SimulationFactory(); var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); - - mapMan.CreateNewMapEntity(MapId.Nullspace); // Act var newEntity = entMan.SpawnEntity(null, MapCoordinates.Nullspace); @@ -131,14 +121,10 @@ namespace Robust.UnitTesting.Shared.Map var sim = SimulationFactory(); var entMan = sim.Resolve(); var mapMan = sim.Resolve(); - - var entity = mapMan.CreateNewMapEntity(MapId.Nullspace); - + var map = mapMan.CreateMap(); + var entity = mapMan.GetMapEntityId(map); mapMan.Restart(); - - Assert.That(mapMan.MapExists(MapId.Nullspace), Is.True); Assert.That((!entMan.EntityExists(entity) ? EntityLifeStage.Deleted : entMan.GetComponent(entity).EntityLifeStage) >= EntityLifeStage.Deleted, Is.True); - Assert.That(mapMan.GetMapEntityId(MapId.Nullspace), Is.EqualTo(EntityUid.Invalid)); } } } diff --git a/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs b/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs index fd8d24b227..10abfc09a2 100644 --- a/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs @@ -82,14 +82,14 @@ public sealed class Broadphase_Test Assert.That(child1Xform.ParentUid, Is.EqualTo(parent)); Assert.That(child2Xform.ParentUid, Is.EqualTo(child1)); - Assert.That(lookup.GetBroadphase(parent), Is.EqualTo(gridBroadphase)); - Assert.That(lookup.GetBroadphase(child1), Is.EqualTo(gridBroadphase)); + Assert.That(lookup.FindBroadphase(parent), Is.EqualTo(gridBroadphase)); + Assert.That(lookup.FindBroadphase(child1), Is.EqualTo(gridBroadphase)); // They should get deparented to the map and updated to the map's broadphase instead. grid.SetTile(Vector2i.Zero, Tile.Empty); - Assert.That(lookup.GetBroadphase(parent), Is.EqualTo(mapBroadphase)); - Assert.That(lookup.GetBroadphase(child1), Is.EqualTo(mapBroadphase)); - Assert.That(lookup.GetBroadphase(child2Body.Owner), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.FindBroadphase(parent), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.FindBroadphase(child1), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.FindBroadphase(child2Body.Owner), Is.EqualTo(mapBroadphase)); } /// @@ -129,22 +129,22 @@ public sealed class Broadphase_Test Assert.That(child1Xform.ParentUid, Is.EqualTo(parent)); Assert.That(child2Xform.ParentUid, Is.EqualTo(child1)); - Assert.That(lookup.GetBroadphase(parentBody.Owner), Is.EqualTo(mapBroadphase)); - Assert.That(lookup.GetBroadphase(child1Body.Owner), Is.EqualTo(mapBroadphase)); - Assert.That(lookup.GetBroadphase(child2Body.Owner), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.FindBroadphase(parentBody.Owner), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.FindBroadphase(child1Body.Owner), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.FindBroadphase(child2Body.Owner), Is.EqualTo(mapBroadphase)); // They should get deparented to the map and updated to the map's broadphase instead. xformSystem.DetachParentToNull(parentXform); - Assert.That(lookup.GetBroadphase(parentBody.Owner), Is.EqualTo(null)); - Assert.That(lookup.GetBroadphase(child1Body.Owner), Is.EqualTo(null)); - Assert.That(lookup.GetBroadphase(child2Body.Owner), Is.EqualTo(null)); + Assert.That(lookup.FindBroadphase(parentBody.Owner), Is.EqualTo(null)); + Assert.That(lookup.FindBroadphase(child1Body.Owner), Is.EqualTo(null)); + Assert.That(lookup.FindBroadphase(child2Body.Owner), Is.EqualTo(null)); // Can't assert CanCollide because they may still want to be valid when coming out of nullspace. // Check it goes back to normal parentXform.AttachParent(mapUid); - Assert.That(lookup.GetBroadphase(parentBody.Owner), Is.EqualTo(mapBroadphase)); - Assert.That(lookup.GetBroadphase(child1Body.Owner), Is.EqualTo(mapBroadphase)); - Assert.That(lookup.GetBroadphase(child2Body.Owner), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.FindBroadphase(parentBody.Owner), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.FindBroadphase(child1Body.Owner), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.FindBroadphase(child2Body.Owner), Is.EqualTo(mapBroadphase)); } } diff --git a/Robust.UnitTesting/Shared/Prototypes/HotReloadTest.cs b/Robust.UnitTesting/Shared/Prototypes/HotReloadTest.cs index 62a73675c7..46fe06fb01 100644 --- a/Robust.UnitTesting/Shared/Prototypes/HotReloadTest.cs +++ b/Robust.UnitTesting/Shared/Prototypes/HotReloadTest.cs @@ -58,8 +58,8 @@ namespace Robust.UnitTesting.Shared.Prototypes [Test] public void TestHotReload() { - _maps.CreateNewMapEntity(new MapId(0)); - var entity = _entities.SpawnEntity(DummyId, MapCoordinates.Nullspace); + var id = IoCManager.Resolve().CreateMap(); + var entity = _entities.SpawnEntity(DummyId, new MapCoordinates(default, id)); var entityComponent = IoCManager.Resolve().GetComponent(entity); Assert.That(entityComponent.Value, Is.EqualTo(5));