Compare commits

...

12 Commits

Author SHA1 Message Date
Jezithyr
35ab0b8cc8 Version: 221.1.0 2024-04-30 12:51:22 -07:00
metalgearsloth
5a14e939bf TileChangedEvent bool (#5089)
Shows whether IsEmpty is different, useful in circumstances.

Also NotNullWhen null handling consistency.
2024-04-30 07:28:29 -07:00
T-Stalker
ccba6b5d1c Reduce default sound range to 15 (#5085) 2024-04-30 15:41:00 +10:00
DrSmugleaf
254a5987c7 Fix Array.Resize sandbox signature (#5084) 2024-04-30 02:14:56 +02:00
metalgearsloth
8550056e68 Version: 221.0.0 2024-04-29 18:46:57 +10:00
Leon Friedrich
25211e3781 Improve transform & state handling exception tolerance (#5081)
* Improve transform & state exception tolerance

* release notes

* Fix pvs assert

* Fix velocity conservation
2024-04-29 18:42:05 +10:00
Leon Friedrich
3500abfd47 Add IUserInterfaceManager.UpdateHovered() (#5083)
* Add `IUserInterfaceManager.UpdateHovered()`

* Try fix tests
2024-04-29 18:37:52 +10:00
Leon Friedrich
7d1915096a Use more entity queries in physics systems & entity manager (#5082) 2024-04-29 13:46:10 +10:00
Nemanja
4504731588 Add a method in SharedTransformSystem for swapping the position of two entities (#4988)
* swap pos method

* no forcing

* Sluth review

* weh
2024-04-29 13:45:12 +10:00
Leon Friedrich
701fa95a82 Temporarily disable macos tests (#5079) 2024-04-29 02:35:04 +10:00
ShadowCommander
40a9048704 Add margin input value order as a comment (#5067)
* Add margin input value order as a comment

* Make a better comment and move value to remark
2024-04-27 20:17:11 +02:00
Leon Friedrich
cee8d42776 Improve MergeImplicitData exception tolerance (#5075) 2024-04-28 02:23:56 +10:00
31 changed files with 460 additions and 265 deletions

View File

@@ -10,7 +10,7 @@ jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
os: [ubuntu-latest, windows-latest ] # , macos-latest] - temporarily disabled due to libfreetype.dll errors.
runs-on: ${{ matrix.os }}

View File

@@ -1,4 +1,4 @@
<Project>
<!-- This file automatically reset by Tools/version.py -->
<!-- This file automatically reset by Tools/version.py -->

View File

@@ -54,6 +54,35 @@ END TEMPLATE-->
*None yet*
## 221.1.0
## 221.0.0
### Breaking changes
* `EntParentChangedMessage.OldMapId` is now an `EntityUid` instead of `MapId`
* `TransformSystem.DetachParentToNull()` is being renamed to `DetachEntity`
* The order in which `MoveEvent` handlers are invoked has been changed to prioritise engine subscriptions
### New features
* Added `UpdateHovered()` and `SetHovered()` to `IUserInterfaceManager`, for updating or modifying the currently hovered control.
* Add SwapPositions to TransformSystem to swap two entity's transforms.
### Bugfixes
* Improve client gamestate exception tolerance.
### Other
* If the currently hovered control is disposed, `UserInterfaceManager` will now look for a new control, rather than just setting the hovered control to null.
### Internal
* Use more `EntityQuery<T>` internally in EntityManager and PhysicsSystem.
## 220.2.0
### New features

View File

@@ -125,6 +125,8 @@ namespace Robust.Client.GameStates
#endif
private bool _resettingPredictedEntities;
private readonly List<EntityUid> _brokenEnts = new();
private readonly List<(EntityUid, NetEntity)> _toStart = new();
/// <inheritdoc />
public void Initialize()
@@ -667,7 +669,16 @@ namespace Robust.Client.GameStates
foreach (var netEntity in createdEntities)
{
#if EXCEPTION_TOLERANCE
if (!_entityManager.TryGetEntityData(netEntity, out _, out var meta))
{
_sawmill.Error($"Encountered deleted entity while merging implicit data! NetEntity: {netEntity}");
continue;
}
#else
var (_, meta) = _entityManager.GetEntityData(netEntity);
#endif
var compData = _compDataPool.Get();
_outputData.Add(netEntity, compData);
@@ -876,9 +887,22 @@ namespace Robust.Client.GameStates
{
foreach (var (entity, data) in _toApply)
{
#if EXCEPTION_TOLERANCE
try
{
#endif
HandleEntityState(entity, data.NetEntity, data.Meta, _entities.EventBus, data.curState,
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
_sawmill.Error($"Caught exception while applying entity state. Entity: {_entities.ToPrettyString(entity)}. Exception: {e}");
_entityManager.DeleteEntity(entity);
RequestFullState();
continue;
}
#endif
if (!data.EnteringPvs)
continue;
@@ -917,7 +941,7 @@ namespace Robust.Client.GameStates
{
try
{
ProcessDeletions(delSpan, xforms, xformSys);
ProcessDeletions(delSpan, xforms, metas, xformSys);
}
catch (Exception e)
{
@@ -962,6 +986,7 @@ namespace Robust.Client.GameStates
}
var xforms = _entities.GetEntityQuery<TransformComponent>();
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
_toDelete.Clear();
@@ -990,12 +1015,12 @@ namespace Robust.Client.GameStates
// This entity is going to get deleted, but maybe some if its children won't be, so lets detach them to
// null. First we will detach the parent in order to reduce the number of broadphase/lookup updates.
xformSys.DetachParentToNull(ent, xform);
xformSys.DetachEntity(ent, xform);
// Then detach all children.
foreach (var child in xform._children)
{
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
xformSys.DetachEntity(child, xforms.Get(child), metas.Get(child), xform);
if (deleteClientChildren
&& !deleteClientEntities // don't add duplicates
@@ -1014,9 +1039,9 @@ namespace Robust.Client.GameStates
}
}
private void ProcessDeletions(
ReadOnlySpan<NetEntity> delSpan,
private void ProcessDeletions(ReadOnlySpan<NetEntity> delSpan,
EntityQuery<TransformComponent> xforms,
EntityQuery<MetaDataComponent> metas,
SharedTransformSystem xformSys)
{
// Processing deletions is non-trivial, because by default deletions will also delete all child entities.
@@ -1043,13 +1068,13 @@ namespace Robust.Client.GameStates
continue; // Already deleted? or never sent to us?
// First, a single recursive map change
xformSys.DetachParentToNull(id.Value, xform);
xformSys.DetachEntity(id.Value, xform);
// Then detach all children.
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
xformSys.DetachEntity(child, xforms.Get(child), metas.Get(child), xform);
}
// Finally, delete the entity.
@@ -1144,7 +1169,7 @@ namespace Robust.Client.GameStates
}
meta._flags |= MetaDataFlags.Detached;
xformSys.DetachParentToNull(ent.Value, xform);
xformSys.DetachEntity(ent.Value, xform);
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
if (container != null)
@@ -1157,63 +1182,58 @@ namespace Robust.Client.GameStates
private void InitializeAndStart(Dictionary<NetEntity, EntityState> toCreate)
{
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
_toStart.Clear();
#if EXCEPTION_TOLERANCE
var brokenEnts = new List<EntityUid>();
#endif
using (_prof.Group("Initialize Entity"))
{
EntityUid entity = default;
foreach (var netEntity in toCreate.Keys)
{
var entity = _entityManager.GetEntity(netEntity);
#if EXCEPTION_TOLERANCE
try
{
#endif
_entities.InitializeEntity(entity, metaQuery.GetComponent(entity));
#if EXCEPTION_TOLERANCE
(entity, var meta) = _entityManager.GetEntityData(netEntity);
_entities.InitializeEntity(entity, meta);
_toStart.Add((entity, netEntity));
}
catch (Exception e)
{
_sawmill.Error($"Server entity threw in Init: ent={_entities.ToPrettyString(entity)}");
_sawmill.Error($"Server entity threw in Init: nent={netEntity}, ent={_entities.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
brokenEnts.Add(entity);
toCreate.Remove(netEntity);
}
_toCreate.Remove(netEntity);
_brokenEnts.Add(entity);
#if !EXCEPTION_TOLERANCE
throw;
#endif
}
}
}
using (_prof.Group("Start Entity"))
{
foreach (var netEntity in toCreate.Keys)
foreach (var (entity, netEntity) in _toStart)
{
var entity = _entityManager.GetEntity(netEntity);
#if EXCEPTION_TOLERANCE
try
{
#endif
_entities.StartEntity(entity);
#if EXCEPTION_TOLERANCE
_entities.StartEntity(entity);
}
catch (Exception e)
{
_sawmill.Error($"Server entity threw in Start: ent={_entityManager.ToPrettyString(entity)}");
_sawmill.Error($"Server entity threw in Start: nent={netEntity}, ent={_entityManager.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
brokenEnts.Add(entity);
toCreate.Remove(netEntity);
}
_toCreate.Remove(netEntity);
_brokenEnts.Add(entity);
#if !EXCEPTION_TOLERANCE
throw;
#endif
}
}
}
#if EXCEPTION_TOLERANCE
foreach (var entity in brokenEnts)
foreach (var entity in _brokenEnts)
{
_entityManager.DeleteEntity(entity);
}
#endif
_brokenEnts.Clear();
}
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
@@ -1402,7 +1422,7 @@ namespace Robust.Client.GameStates
containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container);
}
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachParentToNull(uid, xform);
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachEntity(uid, xform);
if (container != null)
containerSys.AddExpectedEntity(_entities.GetNetEntity(uid), container);

View File

@@ -52,6 +52,10 @@ namespace Robust.Client.UserInterface
[ViewVariables] public bool IsMeasureValid { get; private set; }
[ViewVariables] public bool IsArrangeValid { get; private set; }
/// <summary>
/// Controls the amount of empty space in virtual pixels around the control.
/// </summary>
/// <remarks>Values can be provided as "All" or "Horizontal, Vertical" or "Left, Top, Right, Bottom"</remarks>
[ViewVariables]
public Thickness Margin
{

View File

@@ -135,6 +135,17 @@ namespace Robust.Client.UserInterface
/// Plays the UI hover sound if relevant.
/// </summary>
void HoverSound();
/// <summary>
/// Sets <see cref="CurrentlyHovered"/> to the given control.
/// </summary>
void SetHovered(Control? control);
/// <summary>
/// Forces <see cref="CurrentlyHovered"/> to get updated. This is done automatically when the mouse is moved,
/// but not necessarily a new or existing control is rearranged.
/// </summary>
void UpdateHovered();
}
public readonly struct PostDrawUIRootEventArgs

View File

@@ -9,6 +9,7 @@ using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface;
@@ -20,9 +21,10 @@ internal partial class UserInterfaceManager
private bool _needUpdateActiveCursor;
[ViewVariables] public Control? KeyboardFocused { get; private set; }
[ViewVariables] public Control? CurrentlyHovered { get; private set; } = default!;
[ViewVariables] public Control? CurrentlyHovered { get; private set; }
private Control? _controlFocused;
[ViewVariables]
public Control? ControlFocused
{
@@ -100,6 +102,7 @@ internal partial class UserInterfaceManager
return;
}
var guiArgs = new GUIBoundKeyEventArgs(args.Function, args.State, args.PointerLocation, args.CanFocus,
args.PointerLocation.Position / control.UIScale - control.GlobalPosition,
args.PointerLocation.Position - control.GlobalPixelPosition);
@@ -111,16 +114,18 @@ internal partial class UserInterfaceManager
args.Handle();
}
// Attempt to ensure that keybind-up events only get raised after a single keybind-down.
DebugTools.Assert(!_focusedControls.ContainsKey(args.Function));
_focusedControls[args.Function] = control;
OnKeyBindDown?.Invoke(control);
}
public void KeyBindUp(BoundKeyEventArgs args)
{
if (!_focusedControls.TryGetValue(args.Function, out var control))
{
// Only raise keybind-up for the control on which we previously raised keybind-down
if (!_focusedControls.Remove(args.Function, out var control) || control.Disposed)
return;
}
var guiArgs = new GUIBoundKeyEventArgs(args.Function, args.State, args.PointerLocation, args.CanFocus,
args.PointerLocation.Position / control.UIScale - control.GlobalPosition,
@@ -131,7 +136,6 @@ internal partial class UserInterfaceManager
// Always mark this as handled.
// The only case it should not be is if we do not have a control to click on,
// in which case we never reach this.
_focusedControls.Remove(args.Function);
args.Handle();
}
@@ -140,23 +144,7 @@ internal partial class UserInterfaceManager
_resetTooltipTimer();
// Update which control is considered hovered.
var newHovered = MouseGetControl(mouseMoveEventArgs.Position);
if (newHovered != CurrentlyHovered)
{
_clearTooltip();
CurrentlyHovered?.MouseExited();
CurrentlyHovered = newHovered;
CurrentlyHovered?.MouseEntered();
if (CurrentlyHovered != null)
{
_tooltipDelay = CurrentlyHovered.TooltipDelay ?? TooltipDelay;
}
else
{
_tooltipDelay = null;
}
_needUpdateActiveCursor = true;
}
SetHovered(newHovered);
var target = ControlFocused ?? newHovered;
if (target != null)
@@ -172,6 +160,33 @@ internal partial class UserInterfaceManager
}
}
public void UpdateHovered()
{
var ctrl = MouseGetControl(_inputManager.MouseScreenPosition);
SetHovered(ctrl);
}
public void SetHovered(Control? control)
{
if (control == CurrentlyHovered)
return;
_clearTooltip();
CurrentlyHovered?.MouseExited();
CurrentlyHovered = control;
CurrentlyHovered?.MouseEntered();
if (CurrentlyHovered != null)
{
_tooltipDelay = CurrentlyHovered.TooltipDelay ?? TooltipDelay;
}
else
{
_tooltipDelay = null;
}
_needUpdateActiveCursor = true;
}
private void UpdateActiveCursor()
{
// Consider mouse input focus first so that dragging windows don't act up etc.

View File

@@ -77,15 +77,12 @@ internal sealed partial class UserInterfaceManager
ReleaseKeyboardFocus(control);
RemoveModal(control);
if (control == CurrentlyHovered)
{
control.MouseExited();
CurrentlyHovered = null;
_clearTooltip();
}
if (control != ControlFocused) return;
ControlFocused = null;
if (control == ControlFocused)
ControlFocused = null;
if (control == CurrentlyHovered)
UpdateHovered();
}
public void PushModal(Control modal)

View File

@@ -55,13 +55,10 @@ internal sealed partial class PvsSystem
return;
}
var root = (xform.GridUid ?? xform.MapUid);
DebugTools.AssertNotNull(root);
if (xform.ParentUid != root)
if (xform.ParentUid != xform.GridUid && xform.ParentUid != xform.MapUid)
return;
var location = new PvsChunkLocation(root.Value, GetChunkIndices(xform._localPosition));
var location = new PvsChunkLocation(xform.ParentUid, GetChunkIndices(xform._localPosition));
if (meta.LastPvsLocation == location)
return;

View File

@@ -134,7 +134,7 @@ internal sealed partial class PvsSystem : EntitySystem
SubscribeLocalEvent<TransformComponent, TransformStartupEvent>(OnTransformStartup);
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
_transform.OnGlobalMoveEvent += OnEntityMove;
_transform.OnBeforeMoveEvent += OnEntityMove;
EntityManager.EntityAdded += OnEntityAdded;
EntityManager.EntityDeleted += OnEntityDeleted;
EntityManager.AfterEntityFlush += AfterEntityFlush;
@@ -159,7 +159,7 @@ internal sealed partial class PvsSystem : EntitySystem
base.Shutdown();
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
_transform.OnGlobalMoveEvent -= OnEntityMove;
_transform.OnBeforeMoveEvent -= OnEntityMove;
EntityManager.EntityAdded -= OnEntityAdded;
EntityManager.EntityDeleted -= OnEntityDeleted;
EntityManager.AfterEntityFlush -= AfterEntityFlush;

View File

@@ -36,7 +36,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <summary>
/// Default max range at which the sound can be heard.
/// </summary>
public const float DefaultSoundRange = 20;
public const float DefaultSoundRange = 15;
/// <summary>
/// Used in the PAS to designate the physics collision mask of occluders.

View File

@@ -71,6 +71,8 @@ public abstract class ComponentTreeSystem<TTreeComp, TComp> : EntitySystem
}
else
{
// TODO EXCEPTION TOLERANCE
// Ensure lookup trees update before content code handles move events.
SubscribeLocalEvent<TComp, MoveEvent>(HandleMove);
}

View File

@@ -33,7 +33,7 @@ internal sealed class RecursiveMoveSystem : EntitySystem
public override void Shutdown()
{
if (_subscribed)
_transform.OnGlobalMoveEvent -= AnythingMoved;
_transform.OnBeforeMoveEvent -= AnythingMoved;
_subscribed = false;
}
@@ -44,7 +44,7 @@ internal sealed class RecursiveMoveSystem : EntitySystem
return;
_subscribed = true;
_transform.OnGlobalMoveEvent += AnythingMoved;
_transform.OnBeforeMoveEvent += AnythingMoved;
}
private void AnythingMoved(ref MoveEvent args)

View File

@@ -770,7 +770,7 @@ Types:
Array:
Methods:
- "!!0 Find<>(!!0[], System.Predicate`1<!!0>)"
- "!!0 Resize<>(!!0[], int)"
- "void Resize<>(ref !!0[], int)"
- "!!1 ConvertAll<,>(!!0[], System.Converter`2<!!0, !!1>)"
- "!!0[] Empty<>()"
- "!!0[] FindAll<>(!!0[], System.Predicate`1<!!0>)"

View File

@@ -157,9 +157,7 @@ namespace Robust.Shared.GameObjects
if (!Initialized)
return;
var moveEvent = new MoveEvent((Owner, this, meta), Coordinates, Coordinates, oldRotation, _localRotation, _gameTiming.ApplyingState);
_entMan.EventBus.RaiseLocalEvent(Owner, ref moveEvent);
_entMan.System<SharedTransformSystem>().InvokeGlobalMoveEvent(ref moveEvent);
_entMan.System<SharedTransformSystem>().RaiseMoveEvent((Owner, this, meta), _parent, _localPosition, oldRotation, MapUid);
}
}
@@ -334,7 +332,9 @@ namespace Robust.Shared.GameObjects
if (_localPosition.EqualsApprox(value))
return;
var oldGridPos = Coordinates;
var oldParent = _parent;
var oldPos = _localPosition;
_localPosition = value;
var meta = _entMan.GetComponent<MetaDataComponent>(Owner);
_entMan.Dirty(Owner, this, meta);
@@ -343,9 +343,7 @@ namespace Robust.Shared.GameObjects
if (!Initialized)
return;
var moveEvent = new MoveEvent((Owner, this, meta), oldGridPos, Coordinates, _localRotation, _localRotation, _gameTiming.ApplyingState);
_entMan.EventBus.RaiseLocalEvent(Owner, ref moveEvent);
_entMan.System<SharedTransformSystem>().InvokeGlobalMoveEvent(ref moveEvent);
_entMan.System<SharedTransformSystem>().RaiseMoveEvent((Owner, this, meta), oldParent, oldPos, _localRotation, MapUid);
}
}
@@ -602,8 +600,12 @@ namespace Robust.Shared.GameObjects
/// move events, subscribe to the <see cref="SharedTransformSystem.OnGlobalMoveEvent"/>.
/// </summary>
[ByRefEvent]
public readonly struct MoveEvent(Entity<TransformComponent, MetaDataComponent> entity, EntityCoordinates oldPos,
EntityCoordinates newPos, Angle oldRotation, Angle newRotation, bool stateHandling = false)
public readonly struct MoveEvent(
Entity<TransformComponent, MetaDataComponent> entity,
EntityCoordinates oldPos,
EntityCoordinates newPos,
Angle oldRotation,
Angle newRotation)
{
public readonly Entity<TransformComponent, MetaDataComponent> Entity = entity;
public readonly EntityCoordinates OldPosition = oldPos;
@@ -615,15 +617,6 @@ namespace Robust.Shared.GameObjects
public TransformComponent Component => Entity.Comp1;
public bool ParentChanged => NewPosition.EntityId != OldPosition.EntityId;
[Obsolete("Check IGameTiming.ApplyingState")]
public readonly bool FromStateHandling = stateHandling;
[Obsolete]
public MoveEvent(EntityUid uid, EntityCoordinates oldPos, EntityCoordinates newPos, Angle oldRot, Angle newRot, TransformComponent xform, bool state)
: this((uid, xform, default!), oldPos, newPos, oldRot, newRot)
{
}
}
public struct TransformChildrenEnumerator : IDisposable

View File

@@ -114,7 +114,7 @@ namespace Robust.Shared.GameObjects
public void InitializeComponents(EntityUid uid, MetaDataComponent? metadata = null)
{
DebugTools.AssertOwner(uid, metadata);
metadata ??= GetComponent<MetaDataComponent>(uid);
metadata ??= MetaQuery.GetComponent(uid);
DebugTools.Assert(metadata.EntityLifeStage == EntityLifeStage.PreInit);
SetLifeStage(metadata, EntityLifeStage.Initializing);
@@ -158,13 +158,12 @@ namespace Robust.Shared.GameObjects
// TODO: please for the love of god remove these initialization order hacks.
// Init transform first, we always have it.
var transform = GetComponent<TransformComponent>(uid);
var transform = TransformQuery.GetComponent(uid);
if (transform.LifeStage == ComponentLifeStage.Initialized)
LifeStartup(transform);
// Init physics second if it exists.
if (TryGetComponent<PhysicsComponent>(uid, out var phys)
&& phys.LifeStage == ComponentLifeStage.Initialized)
if (_physicsQuery.TryComp(uid, out var phys) && phys.LifeStage == ComponentLifeStage.Initialized)
{
LifeStartup(phys);
}
@@ -294,7 +293,7 @@ namespace Robust.Shared.GameObjects
if (!uid.IsValid() || !EntityExists(uid))
throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
AddComponentInternal(uid, newComponent, false, true);
AddComponentInternal(uid, newComponent, false, true, null);
return new CompInitializeHandle<T>(this, uid, newComponent, reg.Idx);
}
@@ -302,10 +301,11 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public void AddComponent<T>(EntityUid uid, T component, bool overwrite = false, MetaDataComponent? metadata = null) where T : IComponent
{
if (!uid.IsValid() || !EntityExists(uid))
if (!MetaQuery.Resolve(uid, ref metadata, false))
throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
if (component == null) throw new ArgumentNullException(nameof(component));
if (component == null)
throw new ArgumentNullException(nameof(component));
#pragma warning disable CS0618 // Type or member is obsolete
if (component.Owner == default)
@@ -321,14 +321,17 @@ namespace Robust.Shared.GameObjects
AddComponentInternal(uid, component, overwrite, false, metadata);
}
private void AddComponentInternal<T>(EntityUid uid, T component, bool overwrite, bool skipInit, MetaDataComponent? metadata = null) where T : IComponent
private void AddComponentInternal<T>(EntityUid uid, T component, bool overwrite, bool skipInit, MetaDataComponent? metadata) where T : IComponent
{
if (!MetaQuery.ResolveInternal(uid, ref metadata, false))
throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
// get interface aliases for mapping
var reg = _componentFactory.GetRegistration(component);
AddComponentInternal(uid, component, reg, overwrite, skipInit, metadata);
}
private void AddComponentInternal<T>(EntityUid uid, T component, ComponentRegistration reg, bool overwrite, bool skipInit, MetaDataComponent? metadata = null) where T : IComponent
private void AddComponentInternal<T>(EntityUid uid, T component, ComponentRegistration reg, bool overwrite, bool skipInit, MetaDataComponent metadata) where T : IComponent
{
// We can't use typeof(T) here in case T is just Component
DebugTools.Assert(component is MetaDataComponent ||
@@ -642,13 +645,14 @@ namespace Robust.Shared.GameObjects
_runtimeLog.LogException(e, nameof(CullRemovedComponents));
}
#endif
DeleteComponent(uid, component, false);
var meta = MetaQuery.GetComponent(uid);
DeleteComponent(uid, component, false, meta);
}
_deleteSet.Clear();
}
private void DeleteComponent(EntityUid entityUid, IComponent component, bool terminating, MetaDataComponent? metadata = null)
private void DeleteComponent(EntityUid entityUid, IComponent component, bool terminating, MetaDataComponent? metadata)
{
if (!MetaQuery.ResolveInternal(entityUid, ref metadata))
return;
@@ -1519,7 +1523,7 @@ namespace Robust.Shared.GameObjects
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool TryComp(EntityUid? uid, [NotNullWhen(true)] out TComp1? component)
public bool TryComp([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component)
=> TryGetComponent(uid, out component);
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -11,6 +11,7 @@ using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Profiling;
using Robust.Shared.Prototypes;
@@ -47,6 +48,7 @@ namespace Robust.Shared.GameObjects
public EntityQuery<MetaDataComponent> MetaQuery;
public EntityQuery<TransformComponent> TransformQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<ActorComponent> _actorQuery;
#endregion Dependencies
@@ -210,6 +212,7 @@ namespace Robust.Shared.GameObjects
_containers = System<SharedContainerSystem>();
MetaQuery = GetEntityQuery<MetaDataComponent>();
TransformQuery = GetEntityQuery<TransformComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_actorQuery = GetEntityQuery<ActorComponent>();
}
@@ -551,17 +554,7 @@ namespace Robust.Shared.GameObjects
// Detach the base entity to null before iterating over children
// This also ensures that the entity-lookup updates don't have to be re-run for every child (which recurses up the transform hierarchy).
if (transform.ParentUid != EntityUid.Invalid)
{
try
{
_xforms.DetachParentToNull((uid, transform, metadata), parentXform, true);
}
catch (Exception e)
{
_sawmill.Error($"Caught exception while trying to detach parent of entity '{ToPrettyString(uid, metadata)}' to null.\n{e}");
}
}
_xforms.DetachEntity(uid, transform, metadata, parentXform, true);
foreach (var child in transform._children)
{

View File

@@ -19,26 +19,26 @@ namespace Robust.Shared.GameObjects
public EntityUid? OldParent { get; }
/// <summary>
/// The map Id that the entity was on before its parent changed.
/// The map that the entity was on before its parent changed.
/// </summary>
/// <remarks>
/// If the old parent was detached to null without manually updating the map ID of its children, then this
/// is required as we cannot simply use the old parent's map ID. Also avoids having to fetch the old
/// parent's transform component.
/// </remarks>
public MapId OldMapId { get; }
public readonly EntityUid? OldMapId;
public TransformComponent Transform { get; }
/// <summary>
/// Creates a new instance of <see cref="EntParentChangedMessage"/>.
/// </summary>
public EntParentChangedMessage(EntityUid entity, EntityUid? oldParent, MapId oldMapId, TransformComponent xform)
public EntParentChangedMessage(EntityUid entity, EntityUid? oldParent, EntityUid? oldMapId, TransformComponent xform)
{
Entity = entity;
OldParent = oldParent;
OldMapId = oldMapId;
Transform = xform;
OldMapId = oldMapId;
}
}
}

View File

@@ -125,7 +125,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
SubscribeLocalEvent<GridAddEvent>(OnGridAdd);
SubscribeLocalEvent<MapChangedEvent>(OnMapChange);
_transform.OnGlobalMoveEvent += OnMove;
_transform.OnBeforeMoveEvent += OnMove;
EntityManager.EntityInitialized += OnEntityInit;
SubscribeLocalEvent<TransformComponent, PhysicsBodyTypeChangedEvent>(OnBodyTypeChange);
@@ -142,7 +142,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
{
base.Shutdown();
EntityManager.EntityInitialized -= OnEntityInit;
_transform.OnGlobalMoveEvent -= OnMove;
_transform.OnBeforeMoveEvent -= OnMove;
}
#region DynamicTree

View File

@@ -26,7 +26,6 @@ internal sealed class SharedGridTraversalSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<TransformStartupEvent>(OnStartup);
_transform.OnGlobalMoveEvent += OnMove;
}
private void OnStartup(ref TransformStartupEvent ev)
@@ -34,17 +33,6 @@ internal sealed class SharedGridTraversalSystem : EntitySystem
CheckTraverse(ev.Entity.Owner, ev.Entity.Comp);
}
public override void Shutdown()
{
_transform.OnGlobalMoveEvent -= OnMove;
}
private void OnMove(ref MoveEvent moveEv)
{
CheckTraverse(moveEv.Sender, moveEv.Component);
}
internal void CheckTraverse(EntityUid uid, TransformComponent xform)
{
if (!Enabled || _timing.ApplyingState)

View File

@@ -145,6 +145,11 @@ namespace Robust.Shared.GameObjects
ChunkIndex = chunkIndex;
}
/// <summary>
/// Was the tile previously empty or is it now empty.
/// </summary>
public bool EmptyChanged => OldTile.IsEmpty != NewTile.Tile.IsEmpty;
/// <summary>
/// EntityUid of the grid with the tile-change. TileRef stores the GridId.
/// </summary>

View File

@@ -43,21 +43,13 @@ public abstract partial class SharedTransformSystem
xform._anchored = true;
var oldPos = xform._localPosition;
var oldRot = xform._localRotation;
var oldMap = xform.MapUid;
xform._localPosition = tilePos + newGrid.TileSizeHalfVector;
xform._localRotation += rotation;
SetGridId(uid, xform, newGridUid, XformQuery);
var reParent = new EntParentChangedMessage(uid, oldGridUid, xform.MapID, xform);
RaiseLocalEvent(uid, ref reParent, true);
var meta = MetaData(uid);
var movEevee = new MoveEvent((uid, xform, meta),
new EntityCoordinates(oldGridUid, oldPos),
new EntityCoordinates(newGridUid, xform._localPosition),
oldRot,
xform.LocalRotation,
_gameTiming.ApplyingState);
RaiseLocalEvent(uid, ref movEevee);
InvokeGlobalMoveEvent(ref movEevee);
RaiseMoveEvent((uid, xform, meta), oldGridUid, oldPos, oldRot, oldMap);
DebugTools.Assert(XformQuery.GetComponent(oldGridUid).MapID == XformQuery.GetComponent(newGridUid).MapID);
DebugTools.Assert(xform._anchored);
@@ -321,7 +313,7 @@ public abstract partial class SharedTransformSystem
// I hate this too. Once again, required for shit like containers because they CBF doing their own init logic
// and rely on parent changed messages instead. Might also be used by broadphase stuff?
var parentEv = new EntParentChangedMessage(uid, null, MapId.Nullspace, xform);
var parentEv = new EntParentChangedMessage(uid, null, null, xform);
RaiseLocalEvent(uid, ref parentEv, true);
var ev = new TransformStartupEvent((uid, xform));
@@ -449,9 +441,6 @@ public abstract partial class SharedTransformSystem
return;
}
var oldPosition = xform._parent.IsValid() ? new EntityCoordinates(xform._parent, xform._localPosition) : default;
var oldRotation = xform._localRotation;
if (xform.Anchored && unanchor)
Unanchor(uid, xform);
@@ -470,6 +459,11 @@ public abstract partial class SharedTransformSystem
}
}
var oldParentUid = xform._parent;
var oldPosition = xform._localPosition;
var oldRotation = xform._localRotation;
var oldMap = xform.MapUid;
// Set new values
Dirty(uid, xform, meta);
xform.MatricesDirty = true;
@@ -485,7 +479,7 @@ public abstract partial class SharedTransformSystem
{
if (value.EntityId == uid)
{
DetachParentToNull(uid, xform);
DetachEntity(uid, xform);
if (_netMan.IsServer || IsClientSide(uid))
QueueDel(uid);
throw new InvalidOperationException($"Attempted to parent an entity to itself: {ToPrettyString(uid)}");
@@ -495,7 +489,7 @@ public abstract partial class SharedTransformSystem
{
if (!XformQuery.Resolve(value.EntityId, ref newParent, false))
{
DetachParentToNull(uid, xform);
DetachEntity(uid, xform);
if (_netMan.IsServer || IsClientSide(uid))
QueueDel(uid);
throw new InvalidOperationException($"Attempted to parent entity {ToPrettyString(uid)} to non-existent entity {value.EntityId}");
@@ -503,7 +497,7 @@ public abstract partial class SharedTransformSystem
if (newParent.LifeStage >= ComponentLifeStage.Stopping || LifeStage(value.EntityId) >= EntityLifeStage.Terminating)
{
DetachParentToNull(uid, xform);
DetachEntity(uid, xform);
if (_netMan.IsServer || IsClientSide(uid))
QueueDel(uid);
throw new InvalidOperationException($"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(uid)}, new parent: {ToPrettyString(value.EntityId)}");
@@ -528,7 +522,7 @@ public abstract partial class SharedTransformSystem
// Even though its temporary, this can still cause the client to get stuck in infinite loops while applying the game state.
// So we will just break the loop by detaching to null and just trusting that the loop wasn't actually a real feature of the server state.
Log.Warning($"Encountered circular transform hierarchy while applying state for entity: {ToPrettyString(uid)}. Detaching child to null: {ToPrettyString(recursiveUid)}");
DetachParentToNull(recursiveUid, recursiveXform);
DetachEntity(recursiveUid, recursiveXform);
break;
}
@@ -545,7 +539,6 @@ public abstract partial class SharedTransformSystem
newParent?._children.Add(uid);
xform._parent = value.EntityId;
var oldMapId = xform.MapID;
if (newParent != null)
{
@@ -576,24 +569,18 @@ public abstract partial class SharedTransformSystem
xform._localRotation += GetWorldRotation(oldParent) - GetWorldRotation(newParent);
DebugTools.Assert(!xform.NoLocalRotation || xform.LocalRotation == 0);
var entParentChangedMessage = new EntParentChangedMessage(uid, oldParent?.Owner, oldMapId, xform);
RaiseLocalEvent(uid, ref entParentChangedMessage, true);
}
}
if (!xform.Initialized)
return;
var newPosition = xform._parent.IsValid() ? new EntityCoordinates(xform._parent, xform._localPosition) : default;
#if DEBUG
// If an entity is parented to the map, its grid uid should be null (unless it is itself a grid or we have a map-grid)
if (xform.ParentUid == xform.MapUid)
DebugTools.Assert(xform.GridUid == null || xform.GridUid == uid || xform.GridUid == xform.MapUid);
#endif
var moveEvent = new MoveEvent((uid, xform, meta), oldPosition, newPosition, oldRotation, xform._localRotation, _gameTiming.ApplyingState);
RaiseLocalEvent(uid, ref moveEvent);
InvokeGlobalMoveEvent(ref moveEvent);
RaiseMoveEvent(entity, oldParentUid, oldPosition, oldRotation, oldMap);
}
public void SetCoordinates(
@@ -668,13 +655,13 @@ public abstract partial class SharedTransformSystem
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, EntityQuery<TransformComponent> xformQuery, TransformComponent? parentXform = null)
{
DebugTools.Assert(uid == xform.Owner);
DebugTools.AssertOwner(uid, xform);
if (xform.ParentUid == parent)
return;
if (!parent.IsValid())
{
DetachParentToNull(uid, xform);
DetachEntity(uid, xform);
return;
}
@@ -1143,7 +1130,8 @@ public abstract partial class SharedTransformSystem
if (xform._localPosition.EqualsApprox(pos) && xform.LocalRotation.EqualsApprox(rot))
return;
var oldPosition = xform.Coordinates;
var oldParent = xform._parent;
var oldPosition = xform._localPosition;
var oldRotation = xform.LocalRotation;
if (!xform.Anchored)
@@ -1161,9 +1149,7 @@ public abstract partial class SharedTransformSystem
if (!xform.Initialized)
return;
var moveEvent = new MoveEvent((uid, xform, meta), oldPosition, xform.Coordinates, oldRotation, rot, _gameTiming.ApplyingState);
RaiseLocalEvent(uid, ref moveEvent);
InvokeGlobalMoveEvent(ref moveEvent);
RaiseMoveEvent((uid, xform, meta), oldParent, oldPosition, oldRotation, xform.MapUid);
}
#endregion
@@ -1326,7 +1312,7 @@ public abstract partial class SharedTransformSystem
if (!_mapManager.IsMap(uid))
Log.Warning($"Failed to attach entity to map or grid. Entity: ({ToPrettyString(uid)}). Trace: {Environment.StackTrace}");
DetachParentToNull(uid, xform);
DetachEntity(uid, xform);
return;
}
@@ -1362,21 +1348,50 @@ public abstract partial class SharedTransformSystem
#region State Handling
[Obsolete("Use DetachEntity")]
public void DetachParentToNull(EntityUid uid, TransformComponent xform)
=> DetachEntity(uid, xform);
/// <inheritdoc cref="DetachEntityInternal"/>
public void DetachEntity(EntityUid uid, TransformComponent xform)
{
XformQuery.TryGetComponent(xform.ParentUid, out var oldXform);
DetachParentToNull(uid, xform, oldXform);
DetachEntity(uid, xform, MetaData(uid), oldXform);
}
public void DetachParentToNull(EntityUid uid, TransformComponent xform, TransformComponent? oldXform)
/// <inheritdoc cref="DetachEntityInternal"/>
public void DetachEntity(
EntityUid uid,
TransformComponent xform,
MetaDataComponent meta,
TransformComponent? oldXform,
bool terminating = false)
{
DetachParentToNull((uid, xform, MetaData(uid)), oldXform);
#if !EXCEPTION_TOLERANCE
DetachEntityInternal(uid, xform, meta, oldXform, terminating);
#else
try
{
DetachEntityInternal(uid, xform, meta, oldXform, terminating);
}
catch (Exception e)
{
Log.Error($"Caught exception while attempting to detach an entity to nullspace. Entity: {ToPrettyString(uid, meta)}. Exception: {e}");
// TODO detach without content event handling.
}
#endif
}
public void DetachParentToNull(Entity<TransformComponent,MetaDataComponent> entity, TransformComponent? oldXform, bool terminating = false)
/// <summary>
/// Remove an entity from the transform hierarchy and send it to null space
/// </summary>
internal void DetachEntityInternal(
EntityUid uid,
TransformComponent xform,
MetaDataComponent meta,
TransformComponent? oldXform,
bool terminating = false)
{
var (uid, xform, meta) = entity;
if (!terminating && meta.EntityLifeStage >= EntityLifeStage.Terminating)
{
// Something is attempting to remove the entity from this entity's parent while it is in the process of being deleted.
@@ -1393,15 +1408,14 @@ public abstract partial class SharedTransformSystem
DebugTools.Assert((MetaData(uid).Flags & MetaDataFlags.InContainer) == 0x0,
$"Entity is in a container but has no parent? Entity: {ToPrettyString(uid)}");
if (xform.Broadphase != null)
{
DebugTools.Assert(
xform.Broadphase == BroadphaseData.Invalid
|| xform.Broadphase.Value.Uid == uid
|| Deleted(xform.Broadphase.Value.Uid)
|| Terminating(xform.Broadphase.Value.Uid),
$"Entity has no parent but is on some broadphase? Entity: {ToPrettyString(uid)}. Broadphase: {ToPrettyString(xform.Broadphase.Value.Uid)}");
}
DebugTools.Assert(
xform.Broadphase == null
|| xform.Broadphase == BroadphaseData.Invalid
|| xform.Broadphase.Value.Uid == uid
|| Deleted(xform.Broadphase.Value.Uid)
|| Terminating(xform.Broadphase.Value.Uid),
$"Entity has no parent but is on some broadphase? Entity: {ToPrettyString(uid)}. Broadphase: {ToPrettyString(xform.Broadphase!.Value.Uid)}");
return;
}
@@ -1425,7 +1439,7 @@ public abstract partial class SharedTransformSystem
RaiseLocalEvent(uid, ref anchorStateChangedEvent, true);
}
SetCoordinates(entity, default, Angle.Zero, oldParent: oldXform);
SetCoordinates((uid, xform, meta), default, Angle.Zero, oldParent: oldXform);
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0x0,
$"Entity is in a container after having been detached to null-space? Entity: {ToPrettyString(uid)}");
@@ -1460,7 +1474,7 @@ public abstract partial class SharedTransformSystem
var targetXform = target.Comp;
if (!XformQuery.Resolve(target, ref targetXform) || !targetXform.ParentUid.IsValid())
{
DetachParentToNull(entity, xform);
DetachEntity(entity, xform);
return;
}
@@ -1498,7 +1512,7 @@ public abstract partial class SharedTransformSystem
var targetXform = target.Comp;
if (!XformQuery.Resolve(target, ref targetXform) || !targetXform.ParentUid.IsValid())
{
DetachParentToNull(entity, xform);
DetachEntity(entity, xform);
return;
}
@@ -1518,4 +1532,90 @@ public abstract partial class SharedTransformSystem
PlaceNextTo((entity, xform), targetXform.ParentUid);
}
}
/// <summary>
/// Swaps the position of two entities, placing them inside of containers when applicable.
/// </summary>
/// <returns>Returns if the entities can have their positions swapped. Fails if the entities are parented to one another</returns>
/// <exception cref="InvalidOperationException"></exception>
public bool SwapPositions(Entity<TransformComponent?> entity1, Entity<TransformComponent?> entity2)
{
if (!XformQuery.Resolve(entity1, ref entity1.Comp) || !XformQuery.Resolve(entity2, ref entity2.Comp))
return false;
// save ourselves the hassle and just don't move anything.
if (entity1 == entity2)
return true;
// don't parent things to each other by accident
if (IsParentOf(entity1.Comp, entity2) || IsParentOf(entity2.Comp, entity1))
return false;
MapCoordinates? pos1 = null;
MapCoordinates? pos2 = null;
if (_container.TryGetContainingContainer(entity1, out var container1))
_container.Remove(entity1, container1, force: true);
else
pos1 = GetMapCoordinates(entity1.Comp);
if (_container.TryGetContainingContainer(entity2, out var container2))
_container.Remove(entity2, container2, force: true);
else
pos2 = GetMapCoordinates(entity2.Comp);
// making sure we don't accidentally place something inside of itself
if (container1 != null && container1.Owner == entity2.Owner)
return false;
if (container2 != null && container2.Owner == entity1.Owner)
return false;
if (container2 != null)
{
_container.Insert(entity1, container2);
}
else if (pos2 != null)
{
var mapUid = _mapManager.GetMapEntityId(pos2.Value.MapId);
if (!_gridQuery.HasComponent(entity1) && _mapManager.TryFindGridAt(mapUid, pos2.Value.Position, out var targetGrid, out _))
{
var invWorldMatrix = GetInvWorldMatrix(targetGrid);
SetCoordinates(entity1, new EntityCoordinates(targetGrid, invWorldMatrix.Transform(pos2.Value.Position)));
}
else
{
SetCoordinates(entity1, new EntityCoordinates(mapUid, pos2.Value.Position));
}
}
else
{
throw new InvalidOperationException();
}
if (container1 != null)
{
_container.Insert(entity2, container1);
}
else if (pos1 != null)
{
var mapUid = _mapManager.GetMapEntityId(pos1.Value.MapId);
if (!_gridQuery.HasComponent(entity1) && _mapManager.TryFindGridAt(mapUid, pos1.Value.Position, out var targetGrid, out _))
{
var invWorldMatrix = GetInvWorldMatrix(targetGrid);
SetCoordinates(entity2, new EntityCoordinates(targetGrid, invWorldMatrix.Transform(pos1.Value.Position)));
}
else
{
SetCoordinates(entity2, new EntityCoordinates(mapUid, pos1.Value.Position));
}
}
else
{
throw new InvalidOperationException();
}
return true;
}
}

View File

@@ -25,6 +25,7 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedGridTraversalSystem _traversal = default!;
private EntityQuery<MapComponent> _mapQuery;
private EntityQuery<MapGridComponent> _gridQuery;
@@ -40,10 +41,12 @@ namespace Robust.Shared.GameObjects
/// </summary>
public event MoveEventHandler? OnGlobalMoveEvent;
public void InvokeGlobalMoveEvent(ref MoveEvent ev)
{
OnGlobalMoveEvent?.Invoke(ref ev);
}
/// <summary>
/// Internal move event handlers. This gets invoked before the global & directed move events. This is mainly
/// for exception tolerance, we want to ensure that PVS, physics & entity lookups get updated before some
/// content code throws an exception.
/// </summary>
internal event MoveEventHandler? OnBeforeMoveEvent;
public override void Initialize()
{
@@ -104,7 +107,7 @@ namespace Robust.Shared.GameObjects
// If a tile is being removed due to an explosion or somesuch, some entities are likely being deleted.
// Avoid unnecessary entity updates.
if (EntityManager.IsQueuedForDeletion(entity))
DetachParentToNull(entity, xform, gridXform);
DetachEntity(entity, xform, MetaData(entity), gridXform);
else
SetParent(entity, xform, gridXform.MapUid.Value, mapTransform);
}
@@ -255,6 +258,44 @@ namespace Robust.Shared.GameObjects
indices = _map.CoordinatesToTile(xform.GridUid.Value, grid, xform.Coordinates);
return true;
}
public void RaiseMoveEvent(
Entity<TransformComponent, MetaDataComponent> ent,
EntityUid oldParent,
Vector2 oldPosition,
Angle oldRotation,
EntityUid? oldMap)
{
var pos = ent.Comp1._parent == EntityUid.Invalid
? default
: new EntityCoordinates(ent.Comp1._parent, ent.Comp1._localPosition);
var oldPos = oldParent == EntityUid.Invalid
? default
: new EntityCoordinates(oldParent, oldPosition);
var ev = new MoveEvent(ent, oldPos, pos, oldRotation, ent.Comp1._localRotation);
if (oldParent != ent.Comp1._parent)
{
_physics.OnParentChange(ent, oldParent, oldMap);
OnBeforeMoveEvent?.Invoke(ref ev);
var entParentChangedMessage = new EntParentChangedMessage(ev.Sender, oldParent, oldMap, ev.Component);
RaiseLocalEvent(ev.Sender, ref entParentChangedMessage, true);
}
else
{
OnBeforeMoveEvent?.Invoke(ref ev);
}
RaiseLocalEvent(ev.Sender, ref ev);
OnGlobalMoveEvent?.Invoke(ref ev);
// Finally, handle grid traversal. This is handled separately to avoid out-of-order move events.
// I.e., if the traversal raises its own move event, this ensures that all the old move event handlers
// have finished running first. Ideally this shouldn't be required, but this is here just in case
_traversal.CheckTraverse(ent.Owner, ent.Comp1);
}
}
[ByRefEvent]

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using Robust.Shared.Collections;

View File

@@ -21,6 +21,7 @@ public abstract partial class SharedJointSystem : EntitySystem
[Dependency] private readonly IGameTiming _gameTiming = default!;
private EntityQuery<JointComponent> _jointsQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<JointRelayTargetComponent> _relayQuery;
private EntityQuery<TransformComponent> _xformQuery;
@@ -37,6 +38,7 @@ public abstract partial class SharedJointSystem : EntitySystem
_jointsQuery = GetEntityQuery<JointComponent>();
_relayQuery = GetEntityQuery<JointRelayTargetComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
UpdatesOutsidePrediction = true;
UpdatesBefore.Add(typeof(SharedPhysicsSystem));
@@ -136,7 +138,7 @@ public abstract partial class SharedJointSystem : EntitySystem
var aUid = joint.BodyAUid;
var bUid = joint.BodyBUid;
if (!Resolve(aUid, ref bodyA, false) || !Resolve(bUid, ref bodyB, false))
if (!_physicsQuery.Resolve(aUid, ref bodyA, false) || !_physicsQuery.Resolve(bUid, ref bodyB, false))
return;
DebugTools.Assert(Transform(aUid).MapID == Transform(bUid).MapID, "Attempted to initialize cross-map joint");
@@ -311,7 +313,7 @@ public abstract partial class SharedJointSystem : EntitySystem
public WeldJoint GetOrCreateWeldJoint(EntityUid bodyA, EntityUid bodyB, string? id = null)
{
if (id != null &&
EntityManager.TryGetComponent(bodyA, out JointComponent? jointComponent) &&
_jointsQuery.TryComp(bodyA, out JointComponent? jointComponent) &&
jointComponent.Joints.TryGetValue(id, out var weldJoint))
{
return (WeldJoint) weldJoint;
@@ -404,17 +406,17 @@ public abstract partial class SharedJointSystem : EntitySystem
protected void AddJoint(Joint joint, PhysicsComponent? bodyA = null, PhysicsComponent? bodyB = null)
{
if (!Resolve(joint.BodyAUid, ref bodyA) || !Resolve(joint.BodyBUid, ref bodyB))
if (!_physicsQuery.Resolve(joint.BodyAUid, ref bodyA) || !_physicsQuery.Resolve(joint.BodyBUid, ref bodyB))
return;
if (!joint.CollideConnected)
FilterContactsForJoint(joint, bodyA, bodyB);
// Maybe make this method AddOrUpdate so we can have an Add one that explicitly throws if present?
var mapidA = EntityManager.GetComponent<TransformComponent>(joint.BodyAUid).MapID;
var mapidA = Transform(joint.BodyAUid).MapID;
if (mapidA == MapId.Nullspace ||
mapidA != EntityManager.GetComponent<TransformComponent>(joint.BodyBUid).MapID)
mapidA != Transform(joint.BodyBUid).MapID)
{
Log.Error($"Tried to add joint to ineligible bodies");
return;
@@ -447,7 +449,8 @@ public abstract partial class SharedJointSystem : EntitySystem
if (!Resolve(uid, ref xform))
return;
Resolve(uid, ref component, ref relay, false);
_jointsQuery.Resolve(uid, ref component, false);
_relayQuery.Resolve(uid, ref relay, false);
if (relay != null)
{
@@ -471,7 +474,7 @@ public abstract partial class SharedJointSystem : EntitySystem
/// </summary>
public void ClearJoints(EntityUid uid, JointComponent? component = null)
{
if (!Resolve(uid, ref component, false))
if (!_jointsQuery.Resolve(uid, ref component, false))
return;
// TODO PERFORMANCE
@@ -497,15 +500,9 @@ public abstract partial class SharedJointSystem : EntitySystem
}
}
[Obsolete("Use the other ClearJoints overload")]
public void ClearJoints(JointComponent joint)
{
ClearJoints(joint.Owner, joint);
}
public void RemoveJoint(EntityUid uid, string id)
{
if (!TryComp<JointComponent>(uid, out var jointComp))
if (!_jointsQuery.TryComp(uid, out var jointComp))
return;
if (!jointComp.Joints.TryGetValue(id, out var joint))
@@ -522,12 +519,12 @@ public abstract partial class SharedJointSystem : EntitySystem
// Originally I logged these but because of prediction the client can just nuke them multiple times in a row
// because each body has its own JointComponent, bleh.
if (!TryComp<JointComponent>(bodyAUid, out var jointComponentA))
if (!_jointsQuery.TryComp(bodyAUid, out var jointComponentA))
{
return;
}
if (!TryComp<JointComponent>(bodyBUid, out var jointComponentB))
if (!_jointsQuery.TryComp(bodyBUid, out var jointComponentB))
{
return;
}
@@ -543,7 +540,7 @@ public abstract partial class SharedJointSystem : EntitySystem
}
// Wake up connected bodies.
if (EntityManager.TryGetComponent<PhysicsComponent>(bodyAUid, out var bodyA) &&
if (_physicsQuery.TryComp(bodyAUid, out var bodyA) &&
MetaData(bodyAUid).EntityLifeStage < EntityLifeStage.Terminating)
{
var uidA = jointComponentA.Relay ?? bodyAUid;
@@ -607,7 +604,7 @@ public abstract partial class SharedJointSystem : EntitySystem
internal void FilterContactsForJoint(Joint joint, PhysicsComponent? bodyA = null, PhysicsComponent? bodyB = null)
{
if (!Resolve(joint.BodyBUid, ref bodyB))
if (!_physicsQuery.Resolve(joint.BodyBUid, ref bodyB))
return;
var node = bodyB.Contacts.First;

View File

@@ -115,7 +115,7 @@ public partial class SharedPhysicsSystem
public void ApplyAngularImpulse(EntityUid uid, float impulse, FixturesComponent? manager = null, PhysicsComponent? body = null)
{
if (!Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
if (!PhysicsQuery.Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
{
return;
}
@@ -125,7 +125,7 @@ public partial class SharedPhysicsSystem
public void ApplyForce(EntityUid uid, Vector2 force, Vector2 point, FixturesComponent? manager = null, PhysicsComponent? body = null)
{
if (!Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
if (!PhysicsQuery.Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
{
return;
}
@@ -137,7 +137,7 @@ public partial class SharedPhysicsSystem
public void ApplyForce(EntityUid uid, Vector2 force, FixturesComponent? manager = null, PhysicsComponent? body = null)
{
if (!Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
if (!PhysicsQuery.Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
{
return;
}
@@ -148,7 +148,7 @@ public partial class SharedPhysicsSystem
public void ApplyTorque(EntityUid uid, float torque, FixturesComponent? manager = null, PhysicsComponent? body = null)
{
if (!Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
if (!PhysicsQuery.Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
{
return;
}
@@ -159,7 +159,7 @@ public partial class SharedPhysicsSystem
public void ApplyLinearImpulse(EntityUid uid, Vector2 impulse, FixturesComponent? manager = null, PhysicsComponent? body = null)
{
if (!Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
if (!PhysicsQuery.Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
{
return;
}
@@ -169,7 +169,7 @@ public partial class SharedPhysicsSystem
public void ApplyLinearImpulse(EntityUid uid, Vector2 impulse, Vector2 point, FixturesComponent? manager = null, PhysicsComponent? body = null)
{
if (!Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
if (!PhysicsQuery.Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
{
return;
}
@@ -250,7 +250,10 @@ public partial class SharedPhysicsSystem
public void ResetMassData(EntityUid uid, FixturesComponent? manager = null, PhysicsComponent? body = null)
{
if (!Resolve(uid, ref manager, ref body))
if (!PhysicsQuery.Resolve(uid, ref body))
return;
if (!_fixturesQuery.Resolve(uid, ref manager))
return;
body._mass = 0.0f;
@@ -315,7 +318,7 @@ public partial class SharedPhysicsSystem
public bool SetAngularVelocity(EntityUid uid, float value, bool dirty = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
{
if (!Resolve(uid, ref body))
if (!PhysicsQuery.Resolve(uid, ref body))
return false;
if (body.BodyType == BodyType.Static)
@@ -346,7 +349,7 @@ public partial class SharedPhysicsSystem
/// </summary>
public bool SetLinearVelocity(EntityUid uid, Vector2 velocity, bool dirty = true, bool wakeBody = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
{
if (!Resolve(uid, ref body))
if (!PhysicsQuery.Resolve(uid, ref body))
return false;
if (body.BodyType == BodyType.Static)
@@ -467,7 +470,7 @@ public partial class SharedPhysicsSystem
public void SetBodyType(EntityUid uid, BodyType value, FixturesComponent? manager = null, PhysicsComponent? body = null, TransformComponent? xform = null)
{
if (!Resolve(uid, ref body))
if (!PhysicsQuery.Resolve(uid, ref body))
return;
if (body.BodyType == value)
@@ -531,7 +534,7 @@ public partial class SharedPhysicsSystem
FixturesComponent? manager = null,
PhysicsComponent? body = null)
{
if (!Resolve(uid, ref body))
if (!PhysicsQuery.Resolve(uid, ref body))
return false;
if (body.CanCollide == value)
@@ -545,7 +548,7 @@ public partial class SharedPhysicsSystem
if (_containerSystem.IsEntityOrParentInContainer(uid))
return false;
if (!Resolve(uid, ref manager) || manager.FixtureCount == 0 && !_mapManager.IsGrid(uid))
if (!_fixturesQuery.Resolve(uid, ref manager) || manager.FixtureCount == 0 && !_mapManager.IsGrid(uid))
return false;
}
else
@@ -575,7 +578,7 @@ public partial class SharedPhysicsSystem
public void SetFixedRotation(EntityUid uid, bool value, bool dirty = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
{
if (!Resolve(uid, ref body) || body.FixedRotation == value)
if (!PhysicsQuery.Resolve(uid, ref body) || body.FixedRotation == value)
return;
body.FixedRotation = value;
@@ -668,10 +671,13 @@ public partial class SharedPhysicsSystem
/// <returns>true if the body is collidable and awake</returns>
public bool WakeBody(EntityUid uid, bool force = false, FixturesComponent? manager = null, PhysicsComponent? body = null)
{
if (!SetCanCollide(uid, true, manager: manager, body: body, force: force) || !Resolve(uid, ref body))
if (!PhysicsQuery.Resolve(uid, ref body))
return false;
SetAwake(uid, body, true);
if (!SetCanCollide(uid, true, manager: manager, body: body, force: force))
return false;
SetAwake((uid, body), true);
return body.Awake;
}
@@ -715,7 +721,9 @@ public partial class SharedPhysicsSystem
public Box2 GetHardAABB(EntityUid uid, FixturesComponent? manager = null, PhysicsComponent? body = null, TransformComponent? xform = null)
{
if (!Resolve(uid, ref body, ref xform, ref manager))
if (!PhysicsQuery.Resolve(uid, ref body)
|| !_fixturesQuery.Resolve(uid, ref manager)
|| !Resolve(uid, ref xform))
{
return Box2.Empty;
}

View File

@@ -198,7 +198,7 @@ public abstract partial class SharedPhysicsSystem
return (linearVelocity + linearVelocityAngularContribution, angularVelocity);
}
private void HandleParentChangeVelocity(EntityUid uid, PhysicsComponent physics, ref EntParentChangedMessage args, TransformComponent xform)
private void HandleParentChangeVelocity(EntityUid uid, PhysicsComponent physics, EntityUid oldParent, TransformComponent xform)
{
// If parent changed due to state handling, don't modify velocities. The physics comp state will take care of itself..
if (_gameTiming.ApplyingState)
@@ -217,15 +217,13 @@ public abstract partial class SharedPhysicsSystem
// I guess the question becomes, what do you do with conservation of momentum in that case. I guess its the job
// of the teleporter to select a velocity at the after the parent has changed.
var xformQuery = GetEntityQuery<TransformComponent>();
var physicsQuery = GetEntityQuery<PhysicsComponent>();
FixturesComponent? manager = null;
// for the new velocities (that need to be updated), we can just use the existing function:
var (newLinear, newAngular) = GetMapVelocities(uid, physics, xform);
// for the old velocities, we need to re-implement this function while using the old parent and old local position:
if (args.OldParent is not { Valid: true } parent)
if (oldParent == EntityUid.Invalid)
{
// no previous parent --> simple
// Old velocity + (old velocity - new velocity)
@@ -234,7 +232,8 @@ public abstract partial class SharedPhysicsSystem
return;
}
TransformComponent? parentXform = xformQuery.GetComponent(parent);
var parent = oldParent;
TransformComponent? parentXform = _xformQuery.GetComponent(parent);
var localPos = _transform.GetInvWorldMatrix(parentXform).Transform(_transform.GetWorldPosition(xform));
var oldLinear = physics.LinearVelocity;
@@ -243,7 +242,7 @@ public abstract partial class SharedPhysicsSystem
do
{
if (physicsQuery.TryGetComponent(parent, out var body))
if (PhysicsQuery.TryGetComponent(parent, out var body))
{
oldAngular += body.AngularVelocity;
@@ -259,7 +258,7 @@ public abstract partial class SharedPhysicsSystem
localPos = parentXform.LocalPosition + parentXform.LocalRotation.RotateVec(localPos);
parent = parentXform.ParentUid;
} while (parent.IsValid() && xformQuery.TryGetComponent(parent, out parentXform));
} while (parent.IsValid() && _xformQuery.TryGetComponent(parent, out parentXform));
oldLinear += linearAngularContribution;

View File

@@ -90,7 +90,6 @@ namespace Robust.Shared.Physics.Systems
SubscribeLocalEvent<GridAddEvent>(OnGridAdd);
SubscribeLocalEvent<CollisionChangeEvent>(OnCollisionChange);
SubscribeLocalEvent<PhysicsComponent, EntGotRemovedFromContainerMessage>(HandleContainerRemoved);
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChange);
SubscribeLocalEvent<PhysicsMapComponent, ComponentInit>(HandlePhysicsMapInit);
SubscribeLocalEvent<PhysicsComponent, ComponentInit>(OnPhysicsInit);
SubscribeLocalEvent<PhysicsComponent, ComponentShutdown>(OnPhysicsShutdown);
@@ -150,51 +149,45 @@ namespace Robust.Shared.Physics.Systems
_substeps = (int)Math.Ceiling(targetMinTickrate / serverTickrate);
}
private void OnParentChange(ref EntParentChangedMessage args)
internal void OnParentChange(Entity<TransformComponent, MetaDataComponent> ent, EntityUid oldParent, EntityUid? oldMap)
{
// We do not have a directed/body subscription, because the entity changing parents may not have a physics component, but one of its children might.
var uid = args.Entity;
var xform = args.Transform;
var (uid, xform, meta) = ent;
// If this entity has yet to be initialized, then we can skip this as equivalent code will get run during
// init anyways. HOWEVER: it is possible that one of the children of this entity are already post-init, in
// which case they still need to handle map changes. This frequently happens when clients receives a server
// state where a known/old entity gets attached to a new, previously unknown, entity. The new entity will be
// uninitialized but have an initialized child.
if (xform.ChildCount == 0 && LifeStage(uid) < EntityLifeStage.Initialized)
if (xform.ChildCount == 0 && meta.EntityLifeStage < EntityLifeStage.Initialized)
return;
// Is this entity getting recursively detached after it's parent was already detached to null?
if (args.OldMapId == MapId.Nullspace && xform.MapID == MapId.Nullspace)
if (oldMap == null && xform.MapUid == null)
return;
var body = CompOrNull<PhysicsComponent>(uid);
var body = PhysicsQuery.CompOrNull(uid);
// Handle map changes
if (args.OldMapId != xform.MapID)
if (oldMap != xform.MapUid)
{
// This will also handle broadphase updating & joint clearing.
HandleMapChange(uid, xform, body, args.OldMapId, xform.MapID);
HandleMapChange(uid, xform, body, oldMap, xform.MapUid);
return;
}
if (args.OldMapId != xform.MapID)
return;
if (body != null)
HandleParentChangeVelocity(uid, body, ref args, xform);
HandleParentChangeVelocity(uid, body, oldParent, xform);
}
/// <summary>
/// Recursively add/remove from awake bodies, clear joints, remove from move buffer, and update broadphase.
/// </summary>
private void HandleMapChange(EntityUid uid, TransformComponent xform, PhysicsComponent? body, MapId oldMapId, MapId newMapId)
private void HandleMapChange(EntityUid uid, TransformComponent xform, PhysicsComponent? body, EntityUid? oldMapId, EntityUid? newMapId)
{
var jointQuery = GetEntityQuery<JointComponent>();
PhysMapQuery.TryGetComponent(_mapManager.GetMapEntityId(oldMapId), out var oldMap);
PhysMapQuery.TryGetComponent(_mapManager.GetMapEntityId(newMapId), out var newMap);
RecursiveMapUpdate(uid, xform, body, newMap, oldMap, jointQuery);
PhysMapQuery.TryGetComponent(oldMapId, out var oldMap);
PhysMapQuery.TryGetComponent(newMapId, out var newMap);
RecursiveMapUpdate(uid, xform, body, newMap, oldMap);
}
/// <summary>
@@ -205,8 +198,7 @@ namespace Robust.Shared.Physics.Systems
TransformComponent xform,
PhysicsComponent? body,
PhysicsMapComponent? newMap,
PhysicsMapComponent? oldMap,
EntityQuery<JointComponent> jointQuery)
PhysicsMapComponent? oldMap)
{
DebugTools.Assert(!Deleted(uid));
@@ -223,16 +215,14 @@ namespace Robust.Shared.Physics.Systems
DebugTools.Assert(oldMap?.AwakeBodies.Contains(body) != true);
}
if (jointQuery.TryGetComponent(uid, out var joint))
_joints.ClearJoints(uid, joint);
_joints.ClearJoints(uid);
foreach (var child in xform._children)
{
if (_xformQuery.TryGetComponent(child, out var childXform))
{
PhysicsQuery.TryGetComponent(child, out var childBody);
RecursiveMapUpdate(child, childXform, childBody, newMap, oldMap, jointQuery);
RecursiveMapUpdate(child, childXform, childBody, newMap, oldMap);
}
}
}

View File

@@ -97,6 +97,7 @@ namespace Robust.UnitTesting.Client.UserInterface
control4.OnKeyBindDown += _ => Assert.Fail("Control 4 should not get a mouse event.");
_userInterfaceManager.KeyBindDown(mouseEvent);
_userInterfaceManager.KeyBindUp(mouseEvent);
Assert.Multiple(() =>
{
@@ -124,6 +125,7 @@ namespace Robust.UnitTesting.Client.UserInterface
control2.MouseFilter = Control.MouseFilterMode.Pass;
_userInterfaceManager.KeyBindDown(mouseEvent);
_userInterfaceManager.KeyBindUp(mouseEvent);
Assert.Multiple(() =>
{
@@ -247,6 +249,7 @@ namespace Robust.UnitTesting.Client.UserInterface
pos, true, pos.Position / 1 - control.GlobalPosition, pos.Position - control.GlobalPixelPosition);
_userInterfaceManager.KeyBindDown(mouseEvent);
_userInterfaceManager.KeyBindUp(mouseEvent);
Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null);

View File

@@ -92,7 +92,7 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems
Assert.That(parentXform.MapID, Is.EqualTo(mapId));
Assert.That(childXform.MapID, Is.EqualTo(mapId));
xformSystem.DetachParentToNull(parent, parentXform);
xformSystem.DetachEntity(parent, parentXform);
Assert.That(parentXform.MapID, Is.EqualTo(MapId.Nullspace));
Assert.That(childXform.MapID, Is.EqualTo(MapId.Nullspace));
}

View File

@@ -354,7 +354,7 @@ public sealed class Broadphase_Test
Assert.That(lookup.FindBroadphase(child2), Is.EqualTo(mapBroadphase));
// They should get deparented to the map and updated to the map's broadphase instead.
xformSystem.DetachParentToNull(parent, parentXform);
xformSystem.DetachEntity(parent, parentXform);
Assert.That(lookup.FindBroadphase(parent), Is.EqualTo(null));
Assert.That(lookup.FindBroadphase(child1), Is.EqualTo(null));
Assert.That(lookup.FindBroadphase(child2), Is.EqualTo(null));