From 450473158893b1ffe991d7544e37610c3a31f080 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sun, 28 Apr 2024 23:45:12 -0400 Subject: [PATCH 01/59] Add a method in SharedTransformSystem for swapping the position of two entities (#4988) * swap pos method * no forcing * Sluth review * weh --- .../SharedTransformSystem.Component.cs | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index 7ef2ca10c..2b797ca4e 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -1518,4 +1518,90 @@ public abstract partial class SharedTransformSystem PlaceNextTo((entity, xform), targetXform.ParentUid); } } + + /// + /// Swaps the position of two entities, placing them inside of containers when applicable. + /// + /// Returns if the entities can have their positions swapped. Fails if the entities are parented to one another + /// + public bool SwapPositions(Entity entity1, Entity 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; + } } From 7d1915096a22f28d776764c245de67476933b046 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:46:10 +1200 Subject: [PATCH 02/59] Use more entity queries in physics systems & entity manager (#5082) --- .../GameObjects/EntityManager.Components.cs | 26 +++++++----- Robust.Shared/GameObjects/EntityManager.cs | 3 ++ .../Physics/Systems/SharedJointSystem.cs | 33 +++++++-------- .../Systems/SharedPhysicsSystem.Components.cs | 40 +++++++++++-------- 4 files changed, 57 insertions(+), 45 deletions(-) diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index 9dc9a57ab..4131b1038 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -114,7 +114,7 @@ namespace Robust.Shared.GameObjects public void InitializeComponents(EntityUid uid, MetaDataComponent? metadata = null) { DebugTools.AssertOwner(uid, metadata); - metadata ??= GetComponent(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(uid); + var transform = TransformQuery.GetComponent(uid); if (transform.LifeStage == ComponentLifeStage.Initialized) LifeStartup(transform); // Init physics second if it exists. - if (TryGetComponent(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(this, uid, newComponent, reg.Idx); } @@ -302,10 +301,11 @@ namespace Robust.Shared.GameObjects /// public void AddComponent(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(EntityUid uid, T component, bool overwrite, bool skipInit, MetaDataComponent? metadata = null) where T : IComponent + private void AddComponentInternal(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(EntityUid uid, T component, ComponentRegistration reg, bool overwrite, bool skipInit, MetaDataComponent? metadata = null) where T : IComponent + private void AddComponentInternal(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; diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index a540494d1..3e337a908 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -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 MetaQuery; public EntityQuery TransformQuery; + private EntityQuery _physicsQuery; private EntityQuery _actorQuery; #endregion Dependencies @@ -210,6 +212,7 @@ namespace Robust.Shared.GameObjects _containers = System(); MetaQuery = GetEntityQuery(); TransformQuery = GetEntityQuery(); + _physicsQuery = GetEntityQuery(); _actorQuery = GetEntityQuery(); } diff --git a/Robust.Shared/Physics/Systems/SharedJointSystem.cs b/Robust.Shared/Physics/Systems/SharedJointSystem.cs index 748137e70..18ef16746 100644 --- a/Robust.Shared/Physics/Systems/SharedJointSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedJointSystem.cs @@ -21,6 +21,7 @@ public abstract partial class SharedJointSystem : EntitySystem [Dependency] private readonly IGameTiming _gameTiming = default!; private EntityQuery _jointsQuery; + private EntityQuery _physicsQuery; private EntityQuery _relayQuery; private EntityQuery _xformQuery; @@ -37,6 +38,7 @@ public abstract partial class SharedJointSystem : EntitySystem _jointsQuery = GetEntityQuery(); _relayQuery = GetEntityQuery(); _xformQuery = GetEntityQuery(); + _physicsQuery = GetEntityQuery(); 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(joint.BodyAUid).MapID; + var mapidA = Transform(joint.BodyAUid).MapID; if (mapidA == MapId.Nullspace || - mapidA != EntityManager.GetComponent(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 /// 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(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(bodyAUid, out var jointComponentA)) + if (!_jointsQuery.TryComp(bodyAUid, out var jointComponentA)) { return; } - if (!TryComp(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(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; diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs index b9537b4d0..73e1b891f 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs @@ -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 /// 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 /// true if the body is collidable and awake 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; } From 3500abfd47214f614607eb5037c44a6f6ed5a48a Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 29 Apr 2024 20:37:52 +1200 Subject: [PATCH 03/59] Add `IUserInterfaceManager.UpdateHovered()` (#5083) * Add `IUserInterfaceManager.UpdateHovered()` * Try fix tests --- RELEASE-NOTES.md | 4 +- .../UserInterface/IUserInterfaceManager.cs | 11 ++++ .../UserInterfaceManager.Input.cs | 59 ++++++++++++------- .../UserInterfaceManager.Layout.cs | 13 ++-- .../UserInterface/UserInterfaceManagerTest.cs | 3 + 5 files changed, 58 insertions(+), 32 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 535730bb6..5b79ef52c 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* Added `UpdateHovered()` and `SetHovered()` to `IUserInterfaceManager`, for updating or modifying the currently hovered control. ### Bugfixes @@ -47,7 +47,7 @@ END TEMPLATE--> ### Other -*None yet* +* 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 diff --git a/Robust.Client/UserInterface/IUserInterfaceManager.cs b/Robust.Client/UserInterface/IUserInterfaceManager.cs index e7abb178c..6d83be28d 100644 --- a/Robust.Client/UserInterface/IUserInterfaceManager.cs +++ b/Robust.Client/UserInterface/IUserInterfaceManager.cs @@ -135,6 +135,17 @@ namespace Robust.Client.UserInterface /// Plays the UI hover sound if relevant. /// void HoverSound(); + + /// + /// Sets to the given control. + /// + void SetHovered(Control? control); + + /// + /// Forces to get updated. This is done automatically when the mouse is moved, + /// but not necessarily a new or existing control is rearranged. + /// + void UpdateHovered(); } public readonly struct PostDrawUIRootEventArgs diff --git a/Robust.Client/UserInterface/UserInterfaceManager.Input.cs b/Robust.Client/UserInterface/UserInterfaceManager.Input.cs index 93a475179..3df130bb3 100644 --- a/Robust.Client/UserInterface/UserInterfaceManager.Input.cs +++ b/Robust.Client/UserInterface/UserInterfaceManager.Input.cs @@ -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. diff --git a/Robust.Client/UserInterface/UserInterfaceManager.Layout.cs b/Robust.Client/UserInterface/UserInterfaceManager.Layout.cs index 9e4653b33..acf26becb 100644 --- a/Robust.Client/UserInterface/UserInterfaceManager.Layout.cs +++ b/Robust.Client/UserInterface/UserInterfaceManager.Layout.cs @@ -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) diff --git a/Robust.UnitTesting/Client/UserInterface/UserInterfaceManagerTest.cs b/Robust.UnitTesting/Client/UserInterface/UserInterfaceManagerTest.cs index 4bd361f36..512118f1f 100644 --- a/Robust.UnitTesting/Client/UserInterface/UserInterfaceManagerTest.cs +++ b/Robust.UnitTesting/Client/UserInterface/UserInterfaceManagerTest.cs @@ -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); From 25211e37819a2b11e7160038005259aafe8a64f0 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 29 Apr 2024 20:42:05 +1200 Subject: [PATCH 04/59] Improve transform & state handling exception tolerance (#5081) * Improve transform & state exception tolerance * release notes * Fix pvs assert * Fix velocity conservation --- RELEASE-NOTES.md | 4 +- .../GameStates/ClientGameStateManager.cs | 36 ++++-- Robust.Server/GameStates/PvsSystem.Entity.cs | 7 +- Robust.Server/GameStates/PvsSystem.cs | 4 +- .../ComponentTrees/ComponentTreeSystem.cs | 2 + .../ComponentTrees/RecursiveMoveSystem.cs | 4 +- .../Transform/TransformComponent.cs | 29 ++--- Robust.Shared/GameObjects/EntityManager.cs | 12 +- .../EntParentChangedMessage.cs | 8 +- .../GameObjects/Systems/EntityLookupSystem.cs | 4 +- .../Systems/SharedGridTraversalSystem.cs | 12 -- .../SharedTransformSystem.Component.cs | 116 ++++++++++-------- .../Systems/SharedTransformSystem.cs | 51 +++++++- .../Systems/SharedUserInterfaceSystem.cs | 1 - .../Systems/SharedPhysicsSystem.Velocities.cs | 13 +- .../Physics/Systems/SharedPhysicsSystem.cs | 42 +++---- .../Systems/TransformSystemTests.cs | 2 +- .../Shared/Physics/Broadphase_Test.cs | 2 +- 18 files changed, 189 insertions(+), 160 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 5b79ef52c..1555258a9 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,7 +35,9 @@ END TEMPLATE--> ### Breaking changes -*None yet* +* `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 diff --git a/Robust.Client/GameStates/ClientGameStateManager.cs b/Robust.Client/GameStates/ClientGameStateManager.cs index 472bf2b43..d581cd102 100644 --- a/Robust.Client/GameStates/ClientGameStateManager.cs +++ b/Robust.Client/GameStates/ClientGameStateManager.cs @@ -887,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; @@ -928,7 +941,7 @@ namespace Robust.Client.GameStates { try { - ProcessDeletions(delSpan, xforms, xformSys); + ProcessDeletions(delSpan, xforms, metas, xformSys); } catch (Exception e) { @@ -973,6 +986,7 @@ namespace Robust.Client.GameStates } var xforms = _entities.GetEntityQuery(); + var metas = _entities.GetEntityQuery(); var xformSys = _entitySystemManager.GetEntitySystem(); _toDelete.Clear(); @@ -1001,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 @@ -1025,9 +1039,9 @@ namespace Robust.Client.GameStates } } - private void ProcessDeletions( - ReadOnlySpan delSpan, + private void ProcessDeletions(ReadOnlySpan delSpan, EntityQuery xforms, + EntityQuery metas, SharedTransformSystem xformSys) { // Processing deletions is non-trivial, because by default deletions will also delete all child entities. @@ -1054,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. @@ -1155,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) @@ -1408,7 +1422,7 @@ namespace Robust.Client.GameStates containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container); } - _entities.EntitySysManager.GetEntitySystem().DetachParentToNull(uid, xform); + _entities.EntitySysManager.GetEntitySystem().DetachEntity(uid, xform); if (container != null) containerSys.AddExpectedEntity(_entities.GetNetEntity(uid), container); diff --git a/Robust.Server/GameStates/PvsSystem.Entity.cs b/Robust.Server/GameStates/PvsSystem.Entity.cs index c046b1e95..19774a4c8 100644 --- a/Robust.Server/GameStates/PvsSystem.Entity.cs +++ b/Robust.Server/GameStates/PvsSystem.Entity.cs @@ -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; diff --git a/Robust.Server/GameStates/PvsSystem.cs b/Robust.Server/GameStates/PvsSystem.cs index 184eb2f28..b3a051c09 100644 --- a/Robust.Server/GameStates/PvsSystem.cs +++ b/Robust.Server/GameStates/PvsSystem.cs @@ -134,7 +134,7 @@ internal sealed partial class PvsSystem : EntitySystem SubscribeLocalEvent(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; diff --git a/Robust.Shared/ComponentTrees/ComponentTreeSystem.cs b/Robust.Shared/ComponentTrees/ComponentTreeSystem.cs index abd3c8deb..6c64f34ee 100644 --- a/Robust.Shared/ComponentTrees/ComponentTreeSystem.cs +++ b/Robust.Shared/ComponentTrees/ComponentTreeSystem.cs @@ -71,6 +71,8 @@ public abstract class ComponentTreeSystem : EntitySystem } else { + // TODO EXCEPTION TOLERANCE + // Ensure lookup trees update before content code handles move events. SubscribeLocalEvent(HandleMove); } diff --git a/Robust.Shared/ComponentTrees/RecursiveMoveSystem.cs b/Robust.Shared/ComponentTrees/RecursiveMoveSystem.cs index cb039fdc7..92d84967d 100644 --- a/Robust.Shared/ComponentTrees/RecursiveMoveSystem.cs +++ b/Robust.Shared/ComponentTrees/RecursiveMoveSystem.cs @@ -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) diff --git a/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs b/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs index 97db53904..8256a6878 100644 --- a/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs +++ b/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs @@ -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().InvokeGlobalMoveEvent(ref moveEvent); + _entMan.System().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(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().InvokeGlobalMoveEvent(ref moveEvent); + _entMan.System().RaiseMoveEvent((Owner, this, meta), oldParent, oldPos, _localRotation, MapUid); } } @@ -602,8 +600,12 @@ namespace Robust.Shared.GameObjects /// move events, subscribe to the . /// [ByRefEvent] - public readonly struct MoveEvent(Entity entity, EntityCoordinates oldPos, - EntityCoordinates newPos, Angle oldRotation, Angle newRotation, bool stateHandling = false) + public readonly struct MoveEvent( + Entity entity, + EntityCoordinates oldPos, + EntityCoordinates newPos, + Angle oldRotation, + Angle newRotation) { public readonly Entity 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 diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index 3e337a908..56e616ec6 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -554,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) { diff --git a/Robust.Shared/GameObjects/EntitySystemMessages/EntParentChangedMessage.cs b/Robust.Shared/GameObjects/EntitySystemMessages/EntParentChangedMessage.cs index 6f78dbcb0..38d9d1c0b 100644 --- a/Robust.Shared/GameObjects/EntitySystemMessages/EntParentChangedMessage.cs +++ b/Robust.Shared/GameObjects/EntitySystemMessages/EntParentChangedMessage.cs @@ -19,26 +19,26 @@ namespace Robust.Shared.GameObjects public EntityUid? OldParent { get; } /// - /// The map Id that the entity was on before its parent changed. + /// The map that the entity was on before its parent changed. /// /// /// 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. /// - public MapId OldMapId { get; } + public readonly EntityUid? OldMapId; public TransformComponent Transform { get; } /// /// Creates a new instance of . /// - 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; } } } diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs index 9abe07e14..bd6494c00 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs @@ -125,7 +125,7 @@ public sealed partial class EntityLookupSystem : EntitySystem SubscribeLocalEvent(OnGridAdd); SubscribeLocalEvent(OnMapChange); - _transform.OnGlobalMoveEvent += OnMove; + _transform.OnBeforeMoveEvent += OnMove; EntityManager.EntityInitialized += OnEntityInit; SubscribeLocalEvent(OnBodyTypeChange); @@ -142,7 +142,7 @@ public sealed partial class EntityLookupSystem : EntitySystem { base.Shutdown(); EntityManager.EntityInitialized -= OnEntityInit; - _transform.OnGlobalMoveEvent -= OnMove; + _transform.OnBeforeMoveEvent -= OnMove; } #region DynamicTree diff --git a/Robust.Shared/GameObjects/Systems/SharedGridTraversalSystem.cs b/Robust.Shared/GameObjects/Systems/SharedGridTraversalSystem.cs index d16068c95..4048ac6c1 100644 --- a/Robust.Shared/GameObjects/Systems/SharedGridTraversalSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedGridTraversalSystem.cs @@ -26,7 +26,6 @@ internal sealed class SharedGridTraversalSystem : EntitySystem { base.Initialize(); SubscribeLocalEvent(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) diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index 2b797ca4e..14d7b32ce 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -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 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); + + /// + 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) + /// + 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 entity, TransformComponent? oldXform, bool terminating = false) + /// + /// Remove an entity from the transform hierarchy and send it to null space + /// + 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; } diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs index 3433c4ef6..5e0ab63e9 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs @@ -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 _mapQuery; private EntityQuery _gridQuery; @@ -40,10 +41,12 @@ namespace Robust.Shared.GameObjects /// public event MoveEventHandler? OnGlobalMoveEvent; - public void InvokeGlobalMoveEvent(ref MoveEvent ev) - { - OnGlobalMoveEvent?.Invoke(ref ev); - } + /// + /// 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. + /// + 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 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] diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 97cf42272..d2f6f877b 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -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; diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Velocities.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Velocities.cs index 73b205999..4a73e9f11 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Velocities.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Velocities.cs @@ -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(); - var physicsQuery = GetEntityQuery(); 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; diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs index 5e338ba00..e9152031e 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs @@ -90,7 +90,6 @@ namespace Robust.Shared.Physics.Systems SubscribeLocalEvent(OnGridAdd); SubscribeLocalEvent(OnCollisionChange); SubscribeLocalEvent(HandleContainerRemoved); - SubscribeLocalEvent(OnParentChange); SubscribeLocalEvent(HandlePhysicsMapInit); SubscribeLocalEvent(OnPhysicsInit); SubscribeLocalEvent(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 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(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); } /// /// Recursively add/remove from awake bodies, clear joints, remove from move buffer, and update broadphase. /// - 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(); - - 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); } /// @@ -205,8 +198,7 @@ namespace Robust.Shared.Physics.Systems TransformComponent xform, PhysicsComponent? body, PhysicsMapComponent? newMap, - PhysicsMapComponent? oldMap, - EntityQuery 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); } } } diff --git a/Robust.UnitTesting/Shared/GameObjects/Systems/TransformSystemTests.cs b/Robust.UnitTesting/Shared/GameObjects/Systems/TransformSystemTests.cs index e4431ec88..8a9ea2160 100644 --- a/Robust.UnitTesting/Shared/GameObjects/Systems/TransformSystemTests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/Systems/TransformSystemTests.cs @@ -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)); } diff --git a/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs b/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs index 0f0118b08..8aac3e906 100644 --- a/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs @@ -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)); From 8550056e685e6b758f460c18f5863b37787d3093 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 29 Apr 2024 18:46:57 +1000 Subject: [PATCH 05/59] Version: 221.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 34 ++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index d3d045acd..2fc8e8b7b 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 220.2.0 + 221.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 1555258a9..8d445689e 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,13 +35,11 @@ END TEMPLATE--> ### 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 +*None yet* ### New features -* Added `UpdateHovered()` and `SetHovered()` to `IUserInterfaceManager`, for updating or modifying the currently hovered control. +*None yet* ### Bugfixes @@ -49,13 +47,39 @@ END TEMPLATE--> ### 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. +*None yet* ### Internal *None yet* +## 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` internally in EntityManager and PhysicsSystem. + + ## 220.2.0 ### New features From 254a5987c7f9886c97bd8cf3a6b8aa265cab1bd3 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Mon, 29 Apr 2024 17:14:56 -0700 Subject: [PATCH 06/59] Fix Array.Resize sandbox signature (#5084) --- Robust.Shared/ContentPack/Sandbox.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Shared/ContentPack/Sandbox.yml b/Robust.Shared/ContentPack/Sandbox.yml index 201244c6e..d0828f9aa 100644 --- a/Robust.Shared/ContentPack/Sandbox.yml +++ b/Robust.Shared/ContentPack/Sandbox.yml @@ -770,7 +770,7 @@ Types: Array: Methods: - "!!0 Find<>(!!0[], System.Predicate`1)" - - "!!0 Resize<>(!!0[], int)" + - "void Resize<>(ref !!0[], int)" - "!!1 ConvertAll<,>(!!0[], System.Converter`2)" - "!!0[] Empty<>()" - "!!0[] FindAll<>(!!0[], System.Predicate`1)" From ccba6b5d1c05357f68b91085a929cc08f4a1584d Mon Sep 17 00:00:00 2001 From: T-Stalker <43253663+DogZeroX@users.noreply.github.com> Date: Tue, 30 Apr 2024 02:41:00 -0300 Subject: [PATCH 07/59] Reduce default sound range to 15 (#5085) --- Robust.Shared/Audio/Systems/SharedAudioSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs index 1013554cf..17aad8125 100644 --- a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs +++ b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs @@ -36,7 +36,7 @@ public abstract partial class SharedAudioSystem : EntitySystem /// /// Default max range at which the sound can be heard. /// - public const float DefaultSoundRange = 20; + public const float DefaultSoundRange = 15; /// /// Used in the PAS to designate the physics collision mask of occluders. From 5a14e939bf933a979a1ffc99a28ada8a0677045a Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 1 May 2024 00:28:29 +1000 Subject: [PATCH 08/59] TileChangedEvent bool (#5089) Shows whether IsEmpty is different, useful in circumstances. Also NotNullWhen null handling consistency. --- Robust.Shared/GameObjects/EntityManager.Components.cs | 2 +- Robust.Shared/GameObjects/Systems/SharedMapSystem.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index 4131b1038..18443a000 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -1523,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)] diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs index 6267c1eca..b99177cc1 100644 --- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs @@ -145,6 +145,11 @@ namespace Robust.Shared.GameObjects ChunkIndex = chunkIndex; } + /// + /// Was the tile previously empty or is it now empty. + /// + public bool EmptyChanged => OldTile.IsEmpty != NewTile.Tile.IsEmpty; + /// /// EntityUid of the grid with the tile-change. TileRef stores the GridId. /// From 35ab0b8cc8d8171ee885598552f346776942b835 Mon Sep 17 00:00:00 2001 From: Jezithyr Date: Tue, 30 Apr 2024 12:51:22 -0700 Subject: [PATCH 09/59] Version: 221.1.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 2fc8e8b7b..0e855fd0c 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 221.0.0 + 221.1.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 8d445689e..63cc93de4 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,9 @@ END TEMPLATE--> *None yet* +## 221.1.0 + + ## 221.0.0 ### Breaking changes From 0f97f366a6f5494fc4ca32918ab512a0ff24a73f Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 1 May 2024 23:45:58 +0200 Subject: [PATCH 10/59] Copy CopyToShaderParameters in SpriteComponent.CopyFrom. Fixes dragging displacement-mapped mobs in SS14 making the displacement map visible. --- RELEASE-NOTES.md | 6 +++--- .../GameObjects/Components/Renderable/SpriteComponent.cs | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 63cc93de4..88d258edc 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* `SpriteComponent.CopyFrom` now copies `CopyToShaderParameters` configuration. ### Other @@ -63,7 +63,7 @@ END TEMPLATE--> * `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 +* The order in which `MoveEvent` handlers are invoked has been changed to prioritise engine subscriptions ### New features @@ -108,7 +108,7 @@ END TEMPLATE--> ### Breaking changes -* Refactor UserInterfaceSystem. +* Refactor UserInterfaceSystem. - The API has been significantly cleaned up and standardised, most noticeably callers don't need to worry about TryGetUi and can rely on either HasUi, SetUiState, CloseUi, or OpenUi to handle their code as appropriate. - Interface data is now stored via key rather than as a flat list which is a breaking change for YAML. - BoundUserInterfaces can now be completely handled via Shared code. Existing Server-side callers will behave similarly to before. diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index f6467daa0..940e2a242 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -1681,6 +1681,8 @@ namespace Robust.Client.GameObjects DirOffset = toClone.DirOffset; _autoAnimated = toClone._autoAnimated; RenderingStrategy = toClone.RenderingStrategy; + if (toClone.CopyToShaderParameters is { } copyToShaderParameters) + CopyToShaderParameters = new CopyToShaderParameters(copyToShaderParameters); } void ISerializationHooks.AfterDeserialization() @@ -2155,6 +2157,12 @@ namespace Robust.Client.GameObjects public object LayerKey = layerKey; public string? ParameterTexture; public string? ParameterUV; + + public CopyToShaderParameters(CopyToShaderParameters toClone) : this(toClone.LayerKey) + { + ParameterTexture = toClone.ParameterTexture; + ParameterUV = toClone.ParameterUV; + } } void IAnimationProperties.SetAnimatableProperty(string name, object value) From d9d5ef7471941acfcb4125c30c8f952690eaa39e Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 2 May 2024 09:51:14 +1000 Subject: [PATCH 11/59] Add audio helpers for map-based audio (#5086) Doesn't need to be a flag because we just set it as global, whereas gridaudio cares about stuff every frame. --- Robust.Server/Audio/AudioSystem.cs | 11 +++++++++++ Robust.Shared/Audio/Systems/SharedAudioSystem.cs | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/Robust.Server/Audio/AudioSystem.cs b/Robust.Server/Audio/AudioSystem.cs index f89bc0c67..86b002b85 100644 --- a/Robust.Server/Audio/AudioSystem.cs +++ b/Robust.Server/Audio/AudioSystem.cs @@ -42,6 +42,17 @@ public sealed partial class AudioSystem : SharedAudioSystem component.Source = new DummyAudioSource(); } + public override void SetMapAudio(Entity? audio) + { + if (audio == null) + return; + + base.SetMapAudio(audio); + + // Also need a global override because clients not near 0,0 won't get the audio. + _pvs.AddGlobalOverride(audio.Value); + } + private void AddAudioFilter(EntityUid uid, AudioComponent component, Filter filter) { var count = filter.Count; diff --git a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs index 17aad8125..c96f19584 100644 --- a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs +++ b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs @@ -8,6 +8,7 @@ using Robust.Shared.GameStates; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -32,6 +33,7 @@ public abstract partial class SharedAudioSystem : EntitySystem [Dependency] private readonly INetManager _netManager = default!; [Dependency] protected readonly IPrototypeManager ProtoMan = default!; [Dependency] protected readonly IRobustRandom RandMan = default!; + [Dependency] protected readonly MetaDataSystem MetadataSys = default!; /// /// Default max range at which the sound can be heard. @@ -131,6 +133,18 @@ public abstract partial class SharedAudioSystem : EntitySystem return (float) (Timing.CurTime - (component.PauseTime ?? TimeSpan.Zero) - component.AudioStart).TotalSeconds; } + /// + /// Marks this audio as being map-based. + /// + public virtual void SetMapAudio(Entity? audio) + { + if (audio == null) + return; + + audio.Value.Comp.Global = true; + MetadataSys.AddFlag(audio.Value.Owner, MetaDataFlags.Undetachable); + } + /// /// Sets the shared state for an audio entity. /// From ae83e606d62f79fe7b51d86dff67216cb07a218e Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 2 May 2024 12:14:27 +1000 Subject: [PATCH 12/59] Add SetWorldRotNoLerp method (#5091) * Add SetWorldRotNoLerp method I neeeeed it. * Also this one * dum --- .../Systems/SharedTransformSystem.Component.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index 14d7b32ce..24e947af1 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -5,6 +5,7 @@ using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Utility; using System; +using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using Robust.Shared.Map.Components; @@ -963,7 +964,7 @@ public abstract partial class SharedTransformSystem // Entity was not actually in the transform hierarchy. This is probably a sign that something is wrong, or that the function is being misused. Log.Warning($"Target entity ({ToPrettyString(relative)}) not in transform hierarchy while calling {nameof(GetRelativePositionRotation)}."); var relXform = query.GetComponent(relative); - pos = relXform.InvWorldMatrix.Transform(pos); + pos = GetInvWorldMatrix(relXform).Transform(pos); break; } @@ -977,7 +978,6 @@ public abstract partial class SharedTransformSystem SetWorldPosition(xform, worldPos); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetWorldPosition(TransformComponent component, Vector2 worldPos) { @@ -1035,6 +1035,16 @@ public abstract partial class SharedTransformSystem return rotation; } + public void SetWorldRotationNoLerp(Entity entity, Angle angle) + { + if (!XformQuery.Resolve(entity.Owner, ref entity.Comp)) + return; + + var current = GetWorldRotation(entity.Comp); + var diff = angle - current; + SetLocalRotationNoLerp(entity, entity.Comp.LocalRotation + diff); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetWorldRotation(EntityUid uid, Angle angle) { From 7cb3aeccc23a894b149b4547e5fecb399c8c507f Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Thu, 2 May 2024 12:20:54 +1000 Subject: [PATCH 13/59] Version: 221.2.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 0e855fd0c..aa2b3dee9 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 221.1.0 + 221.2.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 88d258edc..173d4ff56 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -* `SpriteComponent.CopyFrom` now copies `CopyToShaderParameters` configuration. +*None yet* ### Other @@ -54,6 +54,18 @@ END TEMPLATE--> *None yet* +## 221.2.0 + +### New features + +* Add SetMapAudio helper to SharedAudioSystem to setup map-wide audio entities. +* Add SetWorldRotNoLerp method to SharedTransformSystem to avoid client lerping. + +### Bugfixes + +* `SpriteComponent.CopyFrom` now copies `CopyToShaderParameters` configuration. + + ## 221.1.0 From caf9e45ad93324761bdee3522298b1450ba58a71 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 3 May 2024 07:52:56 +1200 Subject: [PATCH 14/59] Fix PVS iterating over duplicate chunks when a a client has multiple viewers/eyes (#5094) --- Robust.Server/GameStates/PvsData.cs | 8 +++++++- Robust.Server/GameStates/PvsSystem.Chunks.cs | 12 +++++------- Robust.Server/GameStates/PvsSystem.Session.cs | 12 ++++++++---- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Robust.Server/GameStates/PvsData.cs b/Robust.Server/GameStates/PvsData.cs index a679edc0d..9f222460b 100644 --- a/Robust.Server/GameStates/PvsData.cs +++ b/Robust.Server/GameStates/PvsData.cs @@ -51,10 +51,15 @@ internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion

PreviouslySent)? LastSent; ///

- /// Visible chunks, sorted by proximity to the clients's viewers; + /// Visible chunks, sorted by proximity to the client's viewers. /// public readonly List<(PvsChunk Chunk, float ChebyshevDistance)> Chunks = new(); + /// + /// Unsorted set of visible chunks. Used to construct the list. + /// + public readonly HashSet ChunkSet = new(); + /// /// Squared distance ta all of the visible chunks. /// @@ -117,6 +122,7 @@ internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion

x.Chunk).ToHashSet().Count == session.Chunks.Count); } DebugTools.Assert(_dirtyChunks.ToHashSet().Count == _dirtyChunks.Count); DebugTools.Assert(_cleanChunks.ToHashSet().Count == _cleanChunks.Count); @@ -108,7 +106,7 @@ internal sealed partial class PvsSystem /// Get the chunks visible to a single entity and add them to a player's set of visible chunks. ///

private void GetVisibleChunks(Entity eye, - List<(PvsChunk Chunk, float ChebyshevDistance)> playerChunks) + HashSet chunks) { var (viewPos, range, mapUid) = CalcViewBounds(eye); if (mapUid is not {} map) @@ -121,7 +119,7 @@ internal sealed partial class PvsSystem if (!_chunks.TryGetValue(loc, out var chunk)) continue; - playerChunks.Add((chunk, default)); + chunks.Add(chunk); if (chunk.UpdateQueued) continue; @@ -147,7 +145,7 @@ internal sealed partial class PvsSystem if (!_chunks.TryGetValue(loc, out var chunk)) continue; - playerChunks.Add((chunk, default)); + chunks.Add(chunk); if (chunk.UpdateQueued) continue; diff --git a/Robust.Server/GameStates/PvsSystem.Session.cs b/Robust.Server/GameStates/PvsSystem.Session.cs index 55907512d..651c94496 100644 --- a/Robust.Server/GameStates/PvsSystem.Session.cs +++ b/Robust.Server/GameStates/PvsSystem.Session.cs @@ -137,15 +137,19 @@ internal sealed partial class PvsSystem if (!CullingEnabled || session.DisableCulling) return; + var chunkSet = session.ChunkSet; var chunks = session.Chunks; var distances = session.ChunkDistanceSq; + + DebugTools.AssertEqual(chunks.Count, 0); + distances.Clear(); - distances.EnsureCapacity(chunks.Count); + distances.EnsureCapacity(chunkSet.Count); + chunks.EnsureCapacity(chunkSet.Count); // Assemble list of chunks and their distances to the nearest eye. - foreach (ref var tuple in CollectionsMarshal.AsSpan(chunks)) + foreach(var chunk in chunkSet) { - var chunk = tuple.Chunk; var dist = float.MaxValue; var chebDist = float.MaxValue; @@ -165,7 +169,7 @@ internal sealed partial class PvsSystem } distances.Add(dist); - tuple.ChebyshevDistance = chebDist; + chunks.Add((chunk, chebDist)); } // Sort chunks based on distances From 7e331eaa75f6e578a47bb07d0f25677c5ad2263f Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 3 May 2024 12:58:19 +1000 Subject: [PATCH 15/59] Defer clientside BUI opens (#5073) * Defer clientside BUI opens Needs content fix first as storage UI breaks. * tweaks * Re-revert this because it seems needed --- .../Systems/SharedUserInterfaceSystem.cs | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index d2f6f877b..4a588af18 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -48,6 +48,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem SubscribeLocalEvent(OnUserInterfaceOpen); SubscribeLocalEvent(OnUserInterfaceClosed); + SubscribeLocalEvent(OnUserInterfaceStartup); SubscribeLocalEvent(OnUserInterfaceShutdown); SubscribeLocalEvent(OnUserInterfaceGetState); SubscribeLocalEvent(OnUserInterfaceHandleState); @@ -55,28 +56,10 @@ public abstract class SharedUserInterfaceSystem : EntitySystem SubscribeLocalEvent(OnPlayerAttached); SubscribeLocalEvent(OnPlayerDetached); + SubscribeLocalEvent(OnActorShutdown); SubscribeLocalEvent(OnGetStateAttempt); SubscribeLocalEvent(OnActorGetState); SubscribeLocalEvent(OnActorHandleState); - - _player.PlayerStatusChanged += OnStatusChange; - } - - private void OnStatusChange(object? sender, SessionStatusEventArgs e) - { - var attachedEnt = e.Session.AttachedEntity; - - if (attachedEnt == null) - return; - - // Content can't handle it yet sadly :( - CloseUserUis(attachedEnt.Value); - } - - public override void Shutdown() - { - base.Shutdown(); - _player.PlayerStatusChanged -= OnStatusChange; } /// @@ -135,6 +118,11 @@ public abstract class SharedUserInterfaceSystem : EntitySystem #region User + private void OnActorShutdown(Entity ent, ref ComponentShutdown args) + { + CloseUserUis((ent.Owner, ent.Comp)); + } + private void OnGetStateAttempt(Entity ent, ref ComponentGetStateAttemptEvent args) { if (args.Cancelled || args.Player?.AttachedEntity != ent.Owner) @@ -233,10 +221,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem Dirty(ent); // If the actor is also deleting then don't worry about updating what they have open. - if (!TerminatingOrDeleted(actor)) + if (!TerminatingOrDeleted(actor) && _userQuery.TryComp(actor, out var actorComp)) { - var actorComp = EnsureComp(actor); - if (actorComp.OpenInterfaces.TryGetValue(ent.Owner, out var keys)) { keys.Remove(args.UiKey); @@ -282,6 +268,20 @@ public abstract class SharedUserInterfaceSystem : EntitySystem EnsureClientBui(ent, args.UiKey, ent.Comp.Interfaces[args.UiKey]); } + private void OnUserInterfaceStartup(Entity ent, ref ComponentStartup args) + { + // PlayerAttachedEvent will catch some of these. + foreach (var (key, bui) in ent.Comp.ClientOpenInterfaces) + { + bui.Open(); + + if (ent.Comp.States.TryGetValue(key, out var state)) + { + bui.UpdateState(state); + } + } + } + private void OnUserInterfaceShutdown(EntityUid uid, UserInterfaceComponent component, ComponentShutdown args) { foreach (var bui in component.ClientOpenInterfaces.Values) @@ -393,11 +393,14 @@ public abstract class SharedUserInterfaceSystem : EntitySystem // If UI not open then open it var attachedEnt = _player.LocalEntity; + // If we get the first state for an ent coming in then don't open BUIs yet, just defer it until later. + var open = ent.Comp.LifeStage > ComponentLifeStage.Added; + if (attachedEnt != null) { foreach (var (key, value) in ent.Comp.Interfaces) { - EnsureClientBui(ent, key, value); + EnsureClientBui(ent, key, value, open); } } } @@ -405,7 +408,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem /// /// Opens a client's BUI if not already open and applies the state to it. /// - private void EnsureClientBui(Entity entity, Enum key, InterfaceData data) + private void EnsureClientBui(Entity entity, Enum key, InterfaceData data, bool open = true) { // If it's out BUI open it up and apply the state, otherwise do nothing. var player = _player.LocalEntity; @@ -428,6 +431,11 @@ public abstract class SharedUserInterfaceSystem : EntitySystem var boundUserInterface = (BoundUserInterface) _factory.CreateInstance(type, [entity.Owner, key]); entity.Comp.ClientOpenInterfaces[key] = boundUserInterface; + + // This is just so we don't open while applying UI states. + if (!open) + return; + boundUserInterface.Open(); if (entity.Comp.States.TryGetValue(key, out var buiState)) From beb1c4b1fbc4bb6f0c7c5e111f252579b070bf59 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sun, 5 May 2024 13:59:45 -0700 Subject: [PATCH 16/59] Add EntProtoId (#5097) * Add EntProtoId * Fix error messages * Shorten error messages * Make services non-optional --- Robust.Shared/Prototypes/EntProtoId.cs | 59 +++++++++++++++++++ .../Implementations/EntProtoIdSerializer.cs | 51 +++++++++++++++- 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/Robust.Shared/Prototypes/EntProtoId.cs b/Robust.Shared/Prototypes/EntProtoId.cs index 53bac6214..9bee36c38 100644 --- a/Robust.Shared/Prototypes/EntProtoId.cs +++ b/Robust.Shared/Prototypes/EntProtoId.cs @@ -1,4 +1,7 @@ using System; +using System.Diagnostics.CodeAnalysis; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations; @@ -42,3 +45,59 @@ public readonly record struct EntProtoId(string Id) : IEquatable, ICompa public override string ToString() => Id ?? string.Empty; } + +/// +[Serializable] +public readonly record struct EntProtoId(string Id) : IEquatable, IComparable where T : IComponent, new() +{ + public static implicit operator string(EntProtoId protoId) + { + return protoId.Id; + } + + public static implicit operator EntProtoId(EntProtoId protoId) + { + return new EntProtoId(protoId.Id); + } + + public static implicit operator EntProtoId(string id) + { + return new EntProtoId(id); + } + + public static implicit operator EntProtoId?(string? id) + { + return id == null ? default(EntProtoId?) : new EntProtoId(id); + } + + public bool Equals(string? other) + { + return Id == other; + } + + public int CompareTo(EntProtoId other) + { + return string.Compare(Id, other.Id, StringComparison.Ordinal); + } + + public override string ToString() => Id ?? string.Empty; + + public T Get(IPrototypeManager? prototypes, IComponentFactory? compFactory) + { + prototypes ??= IoCManager.Resolve(); + var proto = prototypes.Index(this); + if (!proto.TryGetComponent(out T? comp, compFactory)) + { + throw new ArgumentException($"{nameof(EntityPrototype)} {proto.ID} has no {nameof(T)}"); + } + + return comp; + } + + public bool TryGet([NotNullWhen(true)] out T? comp, IPrototypeManager? prototypes, IComponentFactory? compFactory) + { + prototypes ??= IoCManager.Resolve(); + var proto = prototypes.Index(this); + return proto.TryGetComponent(out comp, compFactory); + } +} diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/EntProtoIdSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/EntProtoIdSerializer.cs index 1ba9f5c4a..491d611ee 100644 --- a/Robust.Shared/Serialization/TypeSerializers/Implementations/EntProtoIdSerializer.cs +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/EntProtoIdSerializer.cs @@ -1,8 +1,11 @@ -using Robust.Shared.IoC; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Markdown; +using Robust.Shared.Serialization.Markdown.Mapping; +using Robust.Shared.Serialization.Markdown.Sequence; using Robust.Shared.Serialization.Markdown.Validation; using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Interfaces; @@ -40,3 +43,49 @@ public sealed class EntProtoIdSerializer : ITypeSerializer +/// Serializer used automatically for types. +/// +[TypeSerializer] +public sealed class EntProtoIdSerializer : ITypeSerializer, ValueDataNode>, ITypeCopyCreator> where T : IComponent, new() +{ + public ValidationNode Validate(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null) + { + var prototypes = dependencies.Resolve(); + if (!prototypes.TryGetMapping(typeof(EntityPrototype), node.Value, out var mapping)) + return new ErrorNode(node, $"No {nameof(EntityPrototype)} found with id {node.Value} that has a {typeof(T).Name}"); + + if (!mapping.TryGet("components", out SequenceDataNode? components)) + return new ErrorNode(node, $"{nameof(EntityPrototype)} {node.Value} doesn't have a {typeof(T).Name}."); + + var compFactory = dependencies.Resolve(); + var registration = compFactory.GetRegistration(); + foreach (var componentNode in components) + { + if (componentNode is MappingDataNode component && + component.TryGet("type", out ValueDataNode? compName) && + compName.Value == registration.Name) + { + return new ValidatedValueNode(node); + } + } + + return new ErrorNode(node, $"{nameof(EntityPrototype)} {node.Value} doesn't have a {typeof(T).Name}."); + } + + public EntProtoId Read(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null, InstantiationDelegate>? instanceProvider = null) + { + return new EntProtoId(node.Value); + } + + public DataNode Write(ISerializationManager serialization, EntProtoId value, IDependencyCollection dependencies, bool alwaysWrite = false, ISerializationContext? context = null) + { + return new ValueDataNode(value.Id); + } + + public EntProtoId CreateCopy(ISerializationManager serializationManager, EntProtoId source, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null) + { + return source; + } +} From 2dc610907dbf3367e5a2df74ada2ee420a2e5806 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 6 May 2024 09:00:10 +1200 Subject: [PATCH 17/59] Make `IComponentFactory` argument in EntityPrototype mandatory (#5101) --- Robust.Shared/GameObjects/ComponentFactory.cs | 8 +++++- .../GameObjects/IComponentFactory.cs | 3 ++ Robust.Shared/Prototypes/EntityPrototype.cs | 28 +++++++++++++------ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/Robust.Shared/GameObjects/ComponentFactory.cs b/Robust.Shared/GameObjects/ComponentFactory.cs index 3a2b836d9..735ca26ed 100644 --- a/Robust.Shared/GameObjects/ComponentFactory.cs +++ b/Robust.Shared/GameObjects/ComponentFactory.cs @@ -289,6 +289,12 @@ namespace Robust.Shared.GameObjects return GetRegistration(componentType).Name; } + [Pure] + public string GetComponentName() where T : IComponent, new() + { + return GetRegistration().Name; + } + [Pure] public string GetComponentName(ushort netID) { @@ -324,7 +330,7 @@ namespace Robust.Shared.GameObjects public ComponentRegistration GetRegistration() where T : IComponent, new() { - return GetRegistration(typeof(T)); + return GetRegistration(CompIdx.Index()); } public ComponentRegistration GetRegistration(IComponent component) diff --git a/Robust.Shared/GameObjects/IComponentFactory.cs b/Robust.Shared/GameObjects/IComponentFactory.cs index b7ade6461..d77f335bc 100644 --- a/Robust.Shared/GameObjects/IComponentFactory.cs +++ b/Robust.Shared/GameObjects/IComponentFactory.cs @@ -160,6 +160,9 @@ namespace Robust.Shared.GameObjects [Pure] string GetComponentName(Type componentType); + [Pure] + string GetComponentName() where T : IComponent, new(); + /// /// Gets the name of a component, throwing an exception if it does not exist. /// diff --git a/Robust.Shared/Prototypes/EntityPrototype.cs b/Robust.Shared/Prototypes/EntityPrototype.cs index 04f789f2e..7ba390559 100644 --- a/Robust.Shared/Prototypes/EntityPrototype.cs +++ b/Robust.Shared/Prototypes/EntityPrototype.cs @@ -11,6 +11,7 @@ using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; +using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Robust.Shared.Prototypes @@ -168,28 +169,37 @@ namespace Robust.Shared.Prototypes _loc = IoCManager.Resolve(); } - public bool TryGetComponent([NotNullWhen(true)] out T? component, IComponentFactory? factory = null) where T : IComponent + [Obsolete("Pass in IComponentFactory")] + public bool TryGetComponent([NotNullWhen(true)] out T? component) + where T : IComponent { - if (factory == null) - { - factory = IoCManager.Resolve(); - } + var compName = IoCManager.Resolve().GetComponentName(typeof(T)); + return TryGetComponent(compName, out component); + } - var compName = factory.GetComponentName(typeof(T)); + public bool TryGetComponent([NotNullWhen(true)] out T? component, IComponentFactory factory) where T : IComponent, new() + { + var compName = factory.GetComponentName(); return TryGetComponent(compName, out component); } public bool TryGetComponent(string name, [NotNullWhen(true)] out T? component) where T : IComponent { + DebugTools.AssertEqual(IoCManager.Resolve().GetComponentName(typeof(T)), name); + if (!Components.TryGetValue(name, out var componentUnCast)) { component = default; return false; } - // There are no duplicate component names - // TODO Sanity check with names being in an attribute of the type instead - component = (T) componentUnCast.Component; + if (componentUnCast.Component is not T cast) + { + component = default; + return false; + } + + component = cast; return true; } From 7d19ea93382d01e56c2524196564110ce35acadb Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 6 May 2024 00:32:54 +0200 Subject: [PATCH 18/59] Fix compiler error from merges EntProtoId PR was incompatible with the PR to change EntityPrototype methods to require IComponentFactory passed in --- Robust.Shared/Prototypes/EntProtoId.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Robust.Shared/Prototypes/EntProtoId.cs b/Robust.Shared/Prototypes/EntProtoId.cs index 9bee36c38..12902f755 100644 --- a/Robust.Shared/Prototypes/EntProtoId.cs +++ b/Robust.Shared/Prototypes/EntProtoId.cs @@ -82,7 +82,7 @@ public readonly record struct EntProtoId(string Id) : IEquatable, ICo public override string ToString() => Id ?? string.Empty; - public T Get(IPrototypeManager? prototypes, IComponentFactory? compFactory) + public T Get(IPrototypeManager? prototypes, IComponentFactory compFactory) { prototypes ??= IoCManager.Resolve(); var proto = prototypes.Index(this); @@ -94,7 +94,7 @@ public readonly record struct EntProtoId(string Id) : IEquatable, ICo return comp; } - public bool TryGet([NotNullWhen(true)] out T? comp, IPrototypeManager? prototypes, IComponentFactory? compFactory) + public bool TryGet([NotNullWhen(true)] out T? comp, IPrototypeManager? prototypes, IComponentFactory compFactory) { prototypes ??= IoCManager.Resolve(); var proto = prototypes.Index(this); From 4500669f6582898a2a0f06b56c9256e87339a5f2 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 6 May 2024 08:54:19 +1000 Subject: [PATCH 19/59] Version: 222.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index aa2b3dee9..ba29c1e4c 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 221.2.0 + 222.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 173d4ff56..535679d63 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,25 @@ END TEMPLATE--> *None yet* +## 222.0.0 + +### Breaking changes + +* Mark IComponentFactory argument in EntityPrototype as mandatory. + +### New features + +* Add `EntProtoId` to check for components on the attached entity as well. + +### Bugfixes + +* Fix PVS iterating duplicate chunks for multiple viewsubscriptions. + +### Other + +* Defer clientside BUI opens if it's the first state that comes in. + + ## 221.2.0 ### New features From fff42fb2b448d1b5693d508390ae5e0900c66aea Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 6 May 2024 11:29:34 +1200 Subject: [PATCH 20/59] Partially fix UI assert (#5100) * Partially fix UI assert * Avoid breaking change in BoundKeyEventArgs This is a public constructor, as much as it maybe shouldn't be. Adding this parameter is a breaking change. * Replace .Disposed checks with ! .VisibleInTree Control disposal should not be used anymore. * Release notes --------- Co-authored-by: Pieter-Jan Briers --- RELEASE-NOTES.md | 4 ++-- Robust.Client/Input/InputManager.cs | 7 +++--- .../UserInterfaceManager.Input.cs | 8 ++++--- Robust.Shared/Input/BoundKeyEventArgs.cs | 22 +++++++++++++++++++ 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 535679d63..9fdf31866 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,11 +39,11 @@ END TEMPLATE--> ### New features -*None yet* +* Added `BoundKeyEventArgs.IsRepeat`. ### Bugfixes -*None yet* +* Fix assert trip when holding repeatable keybinds. ### Other diff --git a/Robust.Client/Input/InputManager.cs b/Robust.Client/Input/InputManager.cs index a9641d594..4f7d8e8e8 100644 --- a/Robust.Client/Input/InputManager.cs +++ b/Robust.Client/Input/InputManager.cs @@ -346,7 +346,7 @@ namespace Robust.Client.Input { if (binding.CanRepeat) { - return SetBindState(binding, BoundKeyState.Down, uiOnly); + return SetBindState(binding, BoundKeyState.Down, uiOnly, isRepeat); } return true; @@ -375,7 +375,7 @@ namespace Robust.Client.Input SetBindState(binding, BoundKeyState.Up); } - private bool SetBindState(KeyBinding binding, BoundKeyState state, bool uiOnly = false) + private bool SetBindState(KeyBinding binding, BoundKeyState state, bool uiOnly = false, bool isRepeat = false) { if (binding.BindingType == KeyBindingType.Command && state == BoundKeyState.Down) { @@ -387,6 +387,7 @@ namespace Robust.Client.Input // I honestly have no idea what the best solution here is. // note from the future: context switches won't cause re-entrancy anymore because InputContextContainer defers context switches DebugTools.Assert(!_currentlyFindingViewport, "Re-entrant key events??"); + DebugTools.Assert(!isRepeat || binding.CanRepeat); try { @@ -399,7 +400,7 @@ namespace Robust.Client.Input binding.State = state; var eventArgs = new BoundKeyEventArgs(binding.Function, binding.State, - MouseScreenPosition, binding.CanFocus); + MouseScreenPosition, binding.CanFocus, isRepeat); // UI returns true here into blockPass if it wants to prevent us from giving input events // to the viewport, but doesn't want it hard-handled so we keep processing possible key actions. diff --git a/Robust.Client/UserInterface/UserInterfaceManager.Input.cs b/Robust.Client/UserInterface/UserInterfaceManager.Input.cs index 3df130bb3..a82b4c2a1 100644 --- a/Robust.Client/UserInterface/UserInterfaceManager.Input.cs +++ b/Robust.Client/UserInterface/UserInterfaceManager.Input.cs @@ -114,8 +114,10 @@ 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)); + // Attempt to ensure that keybind-up events get raised after a keybind-down. + DebugTools.Assert(!_focusedControls.TryGetValue(args.Function, out var existing) + || !existing.VisibleInTree + || args.IsRepeat && existing == control); _focusedControls[args.Function] = control; OnKeyBindDown?.Invoke(control); @@ -124,7 +126,7 @@ internal partial class UserInterfaceManager public void KeyBindUp(BoundKeyEventArgs args) { // Only raise keybind-up for the control on which we previously raised keybind-down - if (!_focusedControls.Remove(args.Function, out var control) || control.Disposed) + if (!_focusedControls.Remove(args.Function, out var control) || !control.VisibleInTree) return; var guiArgs = new GUIBoundKeyEventArgs(args.Function, args.State, args.PointerLocation, args.CanFocus, diff --git a/Robust.Shared/Input/BoundKeyEventArgs.cs b/Robust.Shared/Input/BoundKeyEventArgs.cs index 555cc453c..cf6a0cb86 100644 --- a/Robust.Shared/Input/BoundKeyEventArgs.cs +++ b/Robust.Shared/Input/BoundKeyEventArgs.cs @@ -33,6 +33,11 @@ namespace Robust.Shared.Input public bool Handled { get; private set; } + /// + /// Is this a repeated keypress (i.e., are they holding down the key)? + /// + public readonly bool IsRepeat; + /// /// Constructs a new instance of . /// @@ -47,6 +52,23 @@ namespace Robust.Shared.Input CanFocus = canFocus; } + /// + /// Constructs a new instance of . + /// + /// Bound key that that is changing. + /// New state of the function. + /// Current Pointer location in screen coordinates. + /// + public BoundKeyEventArgs( + BoundKeyFunction function, + BoundKeyState state, + ScreenCoordinates pointerLocation, + bool canFocus, + bool isRepeat = false) : this(function, state, pointerLocation, canFocus) + { + IsRepeat = isRepeat; + } + /// /// Mark this event as handled. /// From bd87a805d41a965afaa9e1d6615ed38b7ddd8f61 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 6 May 2024 01:47:27 +0200 Subject: [PATCH 21/59] Add CVars to turn Lidgren's error/warning logs off. Combined with upcoming Lidgren changes, this should make DDoS-induced warning log spam not cause huge server perf issues anymore. --- RELEASE-NOTES.md | 1 + Robust.Shared/CVars.cs | 15 +++++++++++++++ Robust.Shared/Network/NetManager.cs | 29 +++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 9fdf31866..28631df73 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -40,6 +40,7 @@ END TEMPLATE--> ### New features * Added `BoundKeyEventArgs.IsRepeat`. +* Added `net.lidgren_log_warning` and `net.lidgren_log_error` CVars. ### Bugfixes diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index 01ac866d4..a53f6a38e 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -368,6 +368,21 @@ namespace Robust.Shared public static readonly CVarDef NetHappyEyeballsDelay = CVarDef.Create("net.happy_eyeballs_delay", 0.025f, CVar.CLIENTONLY); + /// + /// Controls whether the networking library will log warning messages. + /// + /// + /// Disabling this should make the networking layer more resilient against some DDoS attacks. + /// + public static readonly CVarDef NetLidgrenLogWarning = + CVarDef.Create("net.lidgren_log_warning", true); + + /// + /// Controls whether the networking library will log error messages. + /// + public static readonly CVarDef NetLidgrenLogError = + CVarDef.Create("net.lidgren_log_error", true); + /** * SUS */ diff --git a/Robust.Shared/Network/NetManager.cs b/Robust.Shared/Network/NetManager.cs index e7b9a093e..1a814eed3 100644 --- a/Robust.Shared/Network/NetManager.cs +++ b/Robust.Shared/Network/NetManager.cs @@ -249,6 +249,9 @@ namespace Robust.Shared.Network IsServer = isServer; + _config.OnValueChanged(CVars.NetLidgrenLogWarning, LidgrenLogWarningChanged); + _config.OnValueChanged(CVars.NetLidgrenLogError, LidgrenLogErrorChanged); + _config.OnValueChanged(CVars.NetVerbose, NetVerboseChanged); if (isServer) { @@ -272,6 +275,22 @@ namespace Robust.Shared.Network } } + private void LidgrenLogWarningChanged(bool newValue) + { + foreach (var netPeer in _netPeers) + { + netPeer.Peer.Configuration.SetMessageTypeEnabled(NetIncomingMessageType.WarningMessage, newValue); + } + } + + private void LidgrenLogErrorChanged(bool newValue) + { + foreach (var netPeer in _netPeers) + { + netPeer.Peer.Configuration.SetMessageTypeEnabled(NetIncomingMessageType.ErrorMessage, newValue); + } + } + private void OnAuthModeChanged(int mode) { Auth = (AuthMode)mode; @@ -422,6 +441,8 @@ namespace Robust.Shared.Network _config.UnsubValueChanged(CVars.NetFakeLagMin, _fakeLagMinChanged); _config.UnsubValueChanged(CVars.NetFakeLagRand, _fakeLagRandomChanged); _config.UnsubValueChanged(CVars.NetFakeDuplicates, FakeDuplicatesChanged); + _config.UnsubValueChanged(CVars.NetLidgrenLogWarning, LidgrenLogWarningChanged); + _config.UnsubValueChanged(CVars.NetLidgrenLogError, LidgrenLogErrorChanged); _serializer.ClientHandshakeComplete -= OnSerializerOnClientHandshakeComplete; @@ -576,6 +597,14 @@ namespace Robust.Shared.Network // ping the client once per second. netConfig.PingInterval = 1f; + netConfig.SetMessageTypeEnabled( + NetIncomingMessageType.WarningMessage, + _config.GetCVar(CVars.NetLidgrenLogWarning)); + + netConfig.SetMessageTypeEnabled( + NetIncomingMessageType.ErrorMessage, + _config.GetCVar(CVars.NetLidgrenLogError)); + var poolSize = _config.GetCVar(CVars.NetPoolSize); if (poolSize <= 0) From c83720b163b6b9dcf4902df51d8c087c0d88eeea Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 6 May 2024 01:55:05 +0200 Subject: [PATCH 22/59] Update Lidgren to v0.3.1 --- Lidgren.Network/Lidgren.Network | 2 +- RELEASE-NOTES.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lidgren.Network/Lidgren.Network b/Lidgren.Network/Lidgren.Network index 61a56c60b..1d85b82e0 160000 --- a/Lidgren.Network/Lidgren.Network +++ b/Lidgren.Network/Lidgren.Network @@ -1 +1 @@ -Subproject commit 61a56c60bd99d000c25bd3f105b6de090076c98f +Subproject commit 1d85b82e058101b7ebd60cc8883af5359e4c263a diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 28631df73..3f9dc1d9e 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -48,7 +48,7 @@ END TEMPLATE--> ### Other -*None yet* +* Updated Lidgren to v0.3.1. This should provide performance improvements if warning/error logs are disabled. ### Internal From 4d528dd577059a51590d6ad75a38211e0d5ddfe1 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 6 May 2024 02:30:31 +0200 Subject: [PATCH 23/59] Analyzer to ban uncached regexes (#5107) Using static Regex functions that take in a pattern is bad, because they constantly have to be re-parsed. Cache the Regex instance. --- .../NoUncachedRegexAnalyzerTest.cs | 57 ++++++++++++++++ Robust.Analyzers/NoUncachedRegexAnalyzer.cs | 66 +++++++++++++++++++ Robust.Roslyn.Shared/Diagnostics.cs | 1 + .../LocalizationManager.Functions.cs | 4 +- 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 Robust.Analyzers.Tests/NoUncachedRegexAnalyzerTest.cs create mode 100644 Robust.Analyzers/NoUncachedRegexAnalyzer.cs diff --git a/Robust.Analyzers.Tests/NoUncachedRegexAnalyzerTest.cs b/Robust.Analyzers.Tests/NoUncachedRegexAnalyzerTest.cs new file mode 100644 index 000000000..ab4c6b68b --- /dev/null +++ b/Robust.Analyzers.Tests/NoUncachedRegexAnalyzerTest.cs @@ -0,0 +1,57 @@ +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using NUnit.Framework; +using VerifyCS = + Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier; + +namespace Robust.Analyzers.Tests; + +[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)] +[TestFixture] +public sealed class NoUncachedRegexAnalyzerTest +{ + private static Task Verifier(string code, params DiagnosticResult[] expected) + { + var test = new CSharpAnalyzerTest() + { + TestState = + { + Sources = { code } + }, + }; + + // ExpectedDiagnostics cannot be set, so we need to AddRange here... + test.TestState.ExpectedDiagnostics.AddRange(expected); + + return test.RunAsync(); + } + + [Test] + public async Task Test() + { + const string code = """ + using System.Text.RegularExpressions; + + public static class Foo + { + public static void Bad() + { + Regex.Replace("foo", "bar", "baz"); + } + + public static void Good() + { + var r = new Regex("bar"); + r.Replace("foo", "baz"); + } + } + """; + + await Verifier(code, + // /0/Test0.cs(7,9): warning RA0026: Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern. + VerifyCS.Diagnostic().WithSpan(7, 9, 7, 43) + ); + } +} diff --git a/Robust.Analyzers/NoUncachedRegexAnalyzer.cs b/Robust.Analyzers/NoUncachedRegexAnalyzer.cs new file mode 100644 index 000000000..4bcd07e29 --- /dev/null +++ b/Robust.Analyzers/NoUncachedRegexAnalyzer.cs @@ -0,0 +1,66 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Robust.Roslyn.Shared; + +namespace Robust.Analyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class NoUncachedRegexAnalyzer : DiagnosticAnalyzer +{ + private const string RegexTypeName = "Regex"; + private const string RegexType = $"System.Text.RegularExpressions.{RegexTypeName}"; + + private static readonly DiagnosticDescriptor Rule = new ( + Diagnostics.IdUncachedRegex, + "Use of uncached static Regex function", + "Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.", + "Usage", + DiagnosticSeverity.Warning, + true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public static readonly HashSet BadFunctions = + [ + "Count", + "EnumerateMatches", + "IsMatch", + "Match", + "Matches", + "Replace", + "Split" + ]; + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.RegisterOperationAction(CheckInvocation, OperationKind.Invocation); + } + + private static void CheckInvocation(OperationAnalysisContext context) + { + if (context.Operation is not IInvocationOperation invocation) + return; + + // All Regex functions we care about are static. + var targetMethod = invocation.TargetMethod; + if (!targetMethod.IsStatic) + return; + + // Bail early. + if (targetMethod.ContainingType.Name != "Regex") + return; + + var regexType = context.Compilation.GetTypeByMetadataName(RegexType); + if (!SymbolEqualityComparer.Default.Equals(regexType, targetMethod.ContainingType)) + return; + + if (!BadFunctions.Contains(targetMethod.Name)) + return; + + context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.Syntax.GetLocation())); + } +} diff --git a/Robust.Roslyn.Shared/Diagnostics.cs b/Robust.Roslyn.Shared/Diagnostics.cs index 94709a5f2..f1bd11192 100644 --- a/Robust.Roslyn.Shared/Diagnostics.cs +++ b/Robust.Roslyn.Shared/Diagnostics.cs @@ -29,6 +29,7 @@ public static class Diagnostics public const string IdComponentPauseNoParentAttribute = "RA0023"; public const string IdComponentPauseWrongTypeAttribute = "RA0024"; public const string IdDependencyFieldAssigned = "RA0025"; + public const string IdUncachedRegex = "RA0026"; public static SuppressionDescriptor MeansImplicitAssignment => new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned."); diff --git a/Robust.Shared/Localization/LocalizationManager.Functions.cs b/Robust.Shared/Localization/LocalizationManager.Functions.cs index 48e34bd95..db192ee06 100644 --- a/Robust.Shared/Localization/LocalizationManager.Functions.cs +++ b/Robust.Shared/Localization/LocalizationManager.Functions.cs @@ -17,6 +17,8 @@ namespace Robust.Shared.Localization { internal sealed partial class LocalizationManager { + private static readonly Regex RegexWordMatch = new Regex(@"\w+"); + private void AddBuiltInFunctions(FluentBundle bundle) { // Grammatical gender / pronouns @@ -108,7 +110,7 @@ namespace Robust.Shared.Localization var a = new LocValueString("a"); var an = new LocValueString("an"); - var m = Regex.Match(input, @"\w+"); + var m = RegexWordMatch.Match(input); if (m.Success) { word = m.Groups[0].Value; From 970da5f717c921bfa110aa5dc31e74edb96dcf99 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 6 May 2024 13:43:11 +1000 Subject: [PATCH 24/59] Version: 222.1.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index ba29c1e4c..8ef39cfd8 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 222.0.0 + 222.1.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 3f9dc1d9e..9d3f74313 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,6 +39,25 @@ END TEMPLATE--> ### New features +*None yet* + +### Bugfixes + +*None yet* + +### Other + +*None yet* + +### Internal + +*None yet* + + +## 222.1.0 + +### New features + * Added `BoundKeyEventArgs.IsRepeat`. * Added `net.lidgren_log_warning` and `net.lidgren_log_error` CVars. @@ -50,10 +69,6 @@ END TEMPLATE--> * Updated Lidgren to v0.3.1. This should provide performance improvements if warning/error logs are disabled. -### Internal - -*None yet* - ## 222.0.0 From ccbb6ddec78486d788078b95a843519fc4f3eac4 Mon Sep 17 00:00:00 2001 From: Amy <3855802+amylizzle@users.noreply.github.com> Date: Mon, 6 May 2024 19:39:32 +0100 Subject: [PATCH 25/59] Add method for getting type of var (#5070) Co-authored-by: amylizzle --- .../Manager/ISerializationManager.cs | 3 +++ .../Manager/SerializationManager.cs | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/Robust.Shared/Serialization/Manager/ISerializationManager.cs b/Robust.Shared/Serialization/Manager/ISerializationManager.cs index 1407e32bc..22fd2c56e 100644 --- a/Robust.Shared/Serialization/Manager/ISerializationManager.cs +++ b/Robust.Shared/Serialization/Manager/ISerializationManager.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using Robust.Shared.Reflection; using Robust.Shared.Serialization.Markdown; @@ -440,5 +441,7 @@ namespace Robust.Shared.Serialization.Manager } #endregion + + public bool TryGetVariableType(Type type, string variableName, [NotNullWhen(true)] out Type? variableType); } } diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.cs b/Robust.Shared/Serialization/Manager/SerializationManager.cs index 54410610d..d2f27ba96 100644 --- a/Robust.Shared/Serialization/Manager/SerializationManager.cs +++ b/Robust.Shared/Serialization/Manager/SerializationManager.cs @@ -264,6 +264,26 @@ namespace Robust.Shared.Serialization.Manager return dataDefinition != null; } + public bool TryGetVariableType(Type type, string variableName, [NotNullWhen(true)] out Type? variableType) + { + if (!TryGetDefinition(type, out var definition)) + { + variableType = null; + return false; + } + var foundFieldDef = definition.BaseFieldDefinitions.FirstOrDefault(fieldDef => fieldDef?.BackingField.Name==variableName, null); + if(foundFieldDef != null) + { + variableType = foundFieldDef.BackingField.FieldType; + return true; + } + else + { + variableType = null; + return false; + } + } + private Type ResolveConcreteType(Type baseType, string typeName) { var type = ReflectionManager.YamlTypeTagLookup(baseType, typeName); From 1153888bd1bd3ad92ac90a81981af947083966dc Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 7 May 2024 08:58:57 +1000 Subject: [PATCH 26/59] Add truncate for filesaving (#5098) * Add truncate for filesaving If I expose it to content I pretty much always want truncate to be honest. * Update Robust.Client/UserInterface/FileDialogManager.cs --------- Co-authored-by: Pieter-Jan Briers --- Robust.Client/UserInterface/DummyFileDialogManager.cs | 2 +- Robust.Client/UserInterface/FileDialogManager.cs | 4 ++-- Robust.Client/UserInterface/IFileDialogManager.cs | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Robust.Client/UserInterface/DummyFileDialogManager.cs b/Robust.Client/UserInterface/DummyFileDialogManager.cs index 35646c525..f22ee666e 100644 --- a/Robust.Client/UserInterface/DummyFileDialogManager.cs +++ b/Robust.Client/UserInterface/DummyFileDialogManager.cs @@ -13,7 +13,7 @@ namespace Robust.Client.UserInterface return Task.FromResult(null); } - public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null) + public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true) { return Task.FromResult<(Stream fileStream, bool alreadyExisted)?>(null); } diff --git a/Robust.Client/UserInterface/FileDialogManager.cs b/Robust.Client/UserInterface/FileDialogManager.cs index 095e8b347..d1aeafe04 100644 --- a/Robust.Client/UserInterface/FileDialogManager.cs +++ b/Robust.Client/UserInterface/FileDialogManager.cs @@ -51,7 +51,7 @@ namespace Robust.Client.UserInterface return await OpenFileNfd(filters); } - public async Task<(Stream, bool)?> SaveFile(FileDialogFilters? filters) + public async Task<(Stream, bool)?> SaveFile(FileDialogFilters? filters, bool truncate = true) { var name = await GetSaveFileName(filters); if (name == null) @@ -61,7 +61,7 @@ namespace Robust.Client.UserInterface try { - return (File.Open(name, FileMode.Open), true); + return (File.Open(name, truncate ? FileMode.Truncate : FileMode.Open), true); } catch (FileNotFoundException) { diff --git a/Robust.Client/UserInterface/IFileDialogManager.cs b/Robust.Client/UserInterface/IFileDialogManager.cs index 373399fff..e09be29f7 100644 --- a/Robust.Client/UserInterface/IFileDialogManager.cs +++ b/Robust.Client/UserInterface/IFileDialogManager.cs @@ -28,6 +28,7 @@ namespace Robust.Client.UserInterface /// The file stream the user chose to save to, and whether the file already existed. /// Null if the user cancelled the action. /// - Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null); + /// Should we truncate an existing file to 0-size then write or append. + Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true); } } From a0c1ad246f548d3c02954cbcb3365a8ed4ebe087 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Tue, 7 May 2024 20:51:26 -0700 Subject: [PATCH 27/59] Fix never setting BoundUserInterface.State (#5111) * Fix never setting BoundUserInterface.State * Make setter internal --- .../GameObjects/Components/UserInterface/BoundUserInterface.cs | 2 +- Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs index 00aa9f33a..3e09ae86c 100644 --- a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs +++ b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs @@ -19,7 +19,7 @@ namespace Robust.Shared.GameObjects /// /// The last received state object sent from the server. /// - protected BoundUserInterfaceState? State { get; private set; } + protected internal BoundUserInterfaceState? State { get; internal set; } protected BoundUserInterface(EntityUid owner, Enum uiKey) { diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 4a588af18..acb045ee7 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -387,6 +387,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem if (!ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui)) continue; + cBui.State = buiState; cBui.UpdateState(buiState); } @@ -440,6 +441,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem if (entity.Comp.States.TryGetValue(key, out var buiState)) { + boundUserInterface.State = buiState; boundUserInterface.UpdateState(buiState); } } From 702dfef5fce0dbf4520a408bbeb18dc3fef8fcef Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Tue, 7 May 2024 22:28:07 -0700 Subject: [PATCH 28/59] Version: 222.1.1 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 8ef39cfd8..98b305eba 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 222.1.0 + 222.1.1 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 9d3f74313..8d9b87189 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,18 @@ END TEMPLATE--> *None yet* +## 222.1.1 + +### Bugfixes + +* Fixed never setting BoundUserInterface.State. + +### Other + +* Add truncate for filesaving. +* Add method for getting the type of a data field by name from ISerializationManager. + + ## 222.1.0 ### New features From 51a0ef1e6089f80532aeee91f84e3d34b6613733 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Wed, 8 May 2024 02:21:55 -0700 Subject: [PATCH 29/59] Fix error when JointRelayTargetComponent shuts down while applying state (#5110) --- Robust.Shared/Physics/Systems/SharedJointSystem.Relay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Robust.Shared/Physics/Systems/SharedJointSystem.Relay.cs b/Robust.Shared/Physics/Systems/SharedJointSystem.Relay.cs index c1b30e2e8..4bc9d4b35 100644 --- a/Robust.Shared/Physics/Systems/SharedJointSystem.Relay.cs +++ b/Robust.Shared/Physics/Systems/SharedJointSystem.Relay.cs @@ -48,6 +48,9 @@ public abstract partial class SharedJointSystem private void OnRelayShutdown(EntityUid uid, JointRelayTargetComponent component, ComponentShutdown args) { + if (_gameTiming.ApplyingState) + return; + foreach (var relay in component.Relayed) { if (TerminatingOrDeleted(relay) || !_jointsQuery.TryGetComponent(relay, out var joint)) From fe051a35774883f789c90811fdbc8513f82eb777 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 8 May 2024 19:59:13 +1000 Subject: [PATCH 30/59] Minor bui thing (#5114) --- Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index acb045ee7..e842ed2b1 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -195,11 +195,10 @@ public abstract class SharedUserInterfaceSystem : EntitySystem foreach (var key in keys) { - if (!uiComp.ClientOpenInterfaces.TryGetValue(key, out var cBui)) + if (!uiComp.ClientOpenInterfaces.Remove(key, out var cBui)) continue; cBui.Dispose(); - uiComp.ClientOpenInterfaces.Remove(key); } } } From c229f2e3126072d20568631ad22e624a17610edd Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 9 May 2024 04:46:45 +1000 Subject: [PATCH 31/59] Fix valuelist ensurecapacity(0) throwing (#5113) _items is still null so. --- Robust.Shared/Collections/ValueList.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Robust.Shared/Collections/ValueList.cs b/Robust.Shared/Collections/ValueList.cs index 22b31f2f1..7ea17a6dc 100644 --- a/Robust.Shared/Collections/ValueList.cs +++ b/Robust.Shared/Collections/ValueList.cs @@ -294,6 +294,9 @@ public struct ValueList : IEnumerable if (Capacity < capacity) Grow(capacity); + if (capacity == 0) + return capacity; + return _items!.Length; } From 025d90d281b51f1338ea87307d433fddffda76ab Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 9 May 2024 09:48:16 +0200 Subject: [PATCH 32/59] Change build upload server to Suns --- .github/workflows/publish-client.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-client.yml b/.github/workflows/publish-client.yml index c5734660d..a2e138e28 100644 --- a/.github/workflows/publish-client.yml +++ b/.github/workflows/publish-client.yml @@ -33,10 +33,10 @@ jobs: mkdir "release/${{ steps.parse_version.outputs.version }}" mv release/*.zip "release/${{ steps.parse_version.outputs.version }}" - - name: Upload files to centcomm + - name: Upload files to Suns uses: appleboy/scp-action@master with: - host: centcomm.spacestation14.io + host: suns.spacestation14.com username: robust-build-push key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }} source: "release/${{ steps.parse_version.outputs.version }}" @@ -46,7 +46,7 @@ jobs: - name: Update manifest JSON uses: appleboy/ssh-action@master with: - host: centcomm.spacestation14.io + host: suns.spacestation14.com username: robust-build-push key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }} script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }} From d2a2afe82ed12b4d0a2f390fa3f767fc30540f48 Mon Sep 17 00:00:00 2001 From: ShadowCommander Date: Fri, 10 May 2024 08:58:38 -0700 Subject: [PATCH 33/59] Add audio stream name to entity name (#5121) --- Robust.Client/Audio/AudioSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Client/Audio/AudioSystem.cs b/Robust.Client/Audio/AudioSystem.cs index c52d99b13..9386ea835 100644 --- a/Robust.Client/Audio/AudioSystem.cs +++ b/Robust.Client/Audio/AudioSystem.cs @@ -671,7 +671,7 @@ public sealed partial class AudioSystem : SharedAudioSystem private (EntityUid Entity, AudioComponent Component) CreateAndStartPlayingStream(AudioParams? audioParams, AudioStream stream) { var audioP = audioParams ?? AudioParams.Default; - var entity = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace); + var entity = EntityManager.CreateEntityUninitialized($"Audio {stream.Name}", MapCoordinates.Nullspace); var comp = SetupAudio(entity, null, audioP, stream.Length); LoadStream((entity, comp), stream); EntityManager.InitializeAndStartEntity(entity); From 10aaaa65c5d74545f531bf481e4eb88832454f23 Mon Sep 17 00:00:00 2001 From: ShadowCommander Date: Sun, 12 May 2024 08:50:19 -0700 Subject: [PATCH 34/59] Add editorconfig wrapping settings (#5125) --- .editorconfig | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.editorconfig b/.editorconfig index 85cea687b..ef3f4a74e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,6 +7,18 @@ indent_size = 4 trim_trailing_whitespace = true charset = utf-8 +max_line_length = 120 + +# ReSharper properties +resharper_csharp_max_line_length = 120 +resharper_csharp_wrap_after_declaration_lpar = true +resharper_csharp_wrap_arguments_style = chop_if_long +resharper_csharp_wrap_parameters_style = chop_if_long +resharper_keep_existing_attribute_arrangement = true +resharper_place_field_attribute_on_same_line = if_owner_is_single_line +resharper_wrap_chained_binary_patterns = chop_if_long +resharper_wrap_chained_method_calls = chop_if_long + [*.{csproj,xml,yml,dll.config,targets,props}] indent_size = 2 From 211245215ea28cb618b48720876311cca5388aec Mon Sep 17 00:00:00 2001 From: Brandon Li <48413902+aspiringLich@users.noreply.github.com> Date: Sun, 12 May 2024 19:34:32 -0400 Subject: [PATCH 35/59] remove XAMLIL after generating populate method (#5126) --- Robust.Client.Injectors/XamlCompiler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Robust.Client.Injectors/XamlCompiler.cs b/Robust.Client.Injectors/XamlCompiler.cs index fabe4a8f0..0b4b87997 100644 --- a/Robust.Client.Injectors/XamlCompiler.cs +++ b/Robust.Client.Injectors/XamlCompiler.cs @@ -239,6 +239,7 @@ namespace Robust.Build.Tasks engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0, $"{res.FilePath}: {e.Message}", "", "CompileRobustXaml")); } + res.Remove(); } return true; } From 1654ab06f528ac02d057784852678f286bdcfafc Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Mon, 13 May 2024 10:53:30 +1000 Subject: [PATCH 36/59] Revert "Add audio stream name to entity name" (#5127) This reverts commit d2a2afe82ed12b4d0a2f390fa3f767fc30540f48. --- Robust.Client/Audio/AudioSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Client/Audio/AudioSystem.cs b/Robust.Client/Audio/AudioSystem.cs index 9386ea835..c52d99b13 100644 --- a/Robust.Client/Audio/AudioSystem.cs +++ b/Robust.Client/Audio/AudioSystem.cs @@ -671,7 +671,7 @@ public sealed partial class AudioSystem : SharedAudioSystem private (EntityUid Entity, AudioComponent Component) CreateAndStartPlayingStream(AudioParams? audioParams, AudioStream stream) { var audioP = audioParams ?? AudioParams.Default; - var entity = EntityManager.CreateEntityUninitialized($"Audio {stream.Name}", MapCoordinates.Nullspace); + var entity = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace); var comp = SetupAudio(entity, null, audioP, stream.Length); LoadStream((entity, comp), stream); EntityManager.InitializeAndStartEntity(entity); From cb543240c6a68806de22c32db535961d52549490 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sun, 12 May 2024 22:20:21 -0700 Subject: [PATCH 37/59] Fix clients mispredicting a character's gender (#5119) * Fix clients mispredicting a character's gender * Allow nullable value in set --- .../Localization/GrammarComponent.cs | 58 ++++++++----------- .../Components/Localization/GrammarSystem.cs | 39 +++++++++++++ 2 files changed, 63 insertions(+), 34 deletions(-) create mode 100644 Robust.Shared/GameObjects/Components/Localization/GrammarSystem.cs diff --git a/Robust.Shared/GameObjects/Components/Localization/GrammarComponent.cs b/Robust.Shared/GameObjects/Components/Localization/GrammarComponent.cs index 58e80b2ea..e63ac6f4f 100644 --- a/Robust.Shared/GameObjects/Components/Localization/GrammarComponent.cs +++ b/Robust.Shared/GameObjects/Components/Localization/GrammarComponent.cs @@ -2,45 +2,35 @@ using System; using System.Collections.Generic; using Robust.Shared.Enums; using Robust.Shared.GameStates; +using Robust.Shared.IoC; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; -namespace Robust.Shared.GameObjects.Components.Localization +namespace Robust.Shared.GameObjects.Components.Localization; + +/// +/// Overrides grammar attributes specified in prototypes or localization files. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +// [Access(typeof(GrammarSystem))] TODO access +public sealed partial class GrammarComponent : Component { - /// - /// Overrides grammar attributes specified in prototypes or localization files. - /// - [RegisterComponent] - [NetworkedComponent()] - public sealed partial class GrammarComponent : Component + [DataField, AutoNetworkedField] + public Dictionary Attributes = new(); + + [ViewVariables] + public Gender? Gender { - [DataField("attributes")] - public Dictionary Attributes { get; private set; } = new(); + get => Attributes.TryGetValue("gender", out var g) ? Enum.Parse(g, true) : null; + [Obsolete("Use GrammarSystem.SetGender instead")] + set => IoCManager.Resolve().System().SetGender((Owner, this), value); + } - [ViewVariables] - public Gender? Gender - { - get => Attributes.TryGetValue("gender", out var g) ? Enum.Parse(g, true) : null; - set - { - if (value.HasValue) - Attributes["gender"] = value.Value.ToString(); - else - Attributes.Remove("gender"); - } - } - - [ViewVariables] - public bool? ProperNoun - { - get => Attributes.TryGetValue("proper", out var g) ? bool.Parse(g) : null; - set - { - if (value.HasValue) - Attributes["proper"] = value.Value.ToString(); - else - Attributes.Remove("proper"); - } - } + [ViewVariables] + public bool? ProperNoun + { + get => Attributes.TryGetValue("proper", out var g) ? bool.Parse(g) : null; + [Obsolete("Use GrammarSystem.SetProperNoun instead")] + set => IoCManager.Resolve().System().SetProperNoun((Owner, this), value); } } diff --git a/Robust.Shared/GameObjects/Components/Localization/GrammarSystem.cs b/Robust.Shared/GameObjects/Components/Localization/GrammarSystem.cs new file mode 100644 index 000000000..52c418370 --- /dev/null +++ b/Robust.Shared/GameObjects/Components/Localization/GrammarSystem.cs @@ -0,0 +1,39 @@ +using System.Diagnostics.CodeAnalysis; +using Robust.Shared.Enums; +using Robust.Shared.GameObjects.Components.Localization; + +namespace Robust.Shared.GameObjects; + +public sealed class GrammarSystem : EntitySystem +{ + public void Clear(Entity grammar) + { + grammar.Comp.Attributes.Clear(); + Dirty(grammar); + } + + public bool TryGet(Entity grammar, string key, [NotNullWhen(true)] out string? value) + { + return grammar.Comp.Attributes.TryGetValue(key, out value); + } + + public void Set(Entity grammar, string key, string? value) + { + if (value == null) + grammar.Comp.Attributes.Remove(key); + else + grammar.Comp.Attributes[key] = value; + + Dirty(grammar); + } + + public void SetGender(Entity grammar, Gender? gender) + { + Set(grammar, "gender", gender?.ToString()); + } + + public void SetProperNoun(Entity grammar, bool? proper) + { + Set(grammar, "proper", proper?.ToString()); + } +} From aae0a8bc519fd245706c1aad9377df61cb2e2db8 Mon Sep 17 00:00:00 2001 From: ShadowCommander Date: Mon, 13 May 2024 15:42:34 -0700 Subject: [PATCH 38/59] Add doc comments to CreateEntityUninitialized (#5131) --- Robust.Shared/GameObjects/EntityManager.cs | 1 + Robust.Shared/GameObjects/IEntityManager.cs | 31 +++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index 56e616ec6..82f0f806a 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -281,6 +281,7 @@ namespace Robust.Shared.GameObjects #region Entity Management + /// public EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null) { return CreateEntity(prototypeName, out _, overrides); diff --git a/Robust.Shared/GameObjects/IEntityManager.cs b/Robust.Shared/GameObjects/IEntityManager.cs index 3d1210277..e9da826f7 100644 --- a/Robust.Shared/GameObjects/IEntityManager.cs +++ b/Robust.Shared/GameObjects/IEntityManager.cs @@ -71,12 +71,43 @@ namespace Robust.Shared.GameObjects ///
public event Action? AfterEntityFlush; + /// + /// Creates an uninitialized entity. + /// + /// + /// Does nothing. Used to be the forced EntityUid of the new entity. + /// + /// + [Obsolete($"Use one of the other {nameof(CreateEntityUninitialized)} overloads. euid no longer does anything.")] EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null); + /// + /// Creates an uninitialized entity. + /// + /// + /// + /// EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null); + /// + /// Creates an uninitialized entity and sets its position to the EntityCoordinates provided. + /// + /// + /// Coordinates to set position and parent of the newly spawned entity to. + /// + /// EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null); + /// + /// Creates an uninitialized entity and puts it on the grid or map at the MapCoordinates provided. + /// + /// Name of the to spawn. + /// Coordinates to place the newly spawned entity. + /// Overrides to add or remove components that differ from the prototype. + /// Map rotation to set the newly spawned entity to. + /// A new uninitialized entity. + /// If there is a grid at the , the entity will be parented to the grid. + /// Otherwise, it will be parented to the map. EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!); void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null); From 6e61c35d3508eb2aeee504304a7427a35523cc97 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Mon, 13 May 2024 22:43:03 +0000 Subject: [PATCH 39/59] add missing Comp inline to EntityQuery (#5123) Co-authored-by: deltanedas <@deltanedas:kde.org> --- Robust.Shared/GameObjects/EntityManager.Components.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index 18443a000..ae7ef477c 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -1586,6 +1586,13 @@ namespace Robust.Shared.GameObjects return default; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + public TComp1 Comp(EntityUid uid) + { + return GetComponent(uid); + } + #region Internal /// From 41c40f1a94d6b947792308bb8301b045ab86196c Mon Sep 17 00:00:00 2001 From: Amy <3855802+amylizzle@users.noreply.github.com> Date: Mon, 13 May 2024 23:44:19 +0100 Subject: [PATCH 40/59] Fix checking wrong property in `TryGetVariableType()` (#5120) Co-authored-by: amylizzle --- Robust.Shared/Serialization/Manager/SerializationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.cs b/Robust.Shared/Serialization/Manager/SerializationManager.cs index d2f27ba96..90b874a4e 100644 --- a/Robust.Shared/Serialization/Manager/SerializationManager.cs +++ b/Robust.Shared/Serialization/Manager/SerializationManager.cs @@ -271,7 +271,7 @@ namespace Robust.Shared.Serialization.Manager variableType = null; return false; } - var foundFieldDef = definition.BaseFieldDefinitions.FirstOrDefault(fieldDef => fieldDef?.BackingField.Name==variableName, null); + var foundFieldDef = definition.BaseFieldDefinitions.FirstOrDefault(fieldDef => fieldDef?.Attribute is DataFieldAttribute attr && attr.Tag==variableName, null); if(foundFieldDef != null) { variableType = foundFieldDef.BackingField.FieldType; From a9ed53f47b270f6b1775c47ee6da5976be3292a9 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 14 May 2024 09:03:26 +1000 Subject: [PATCH 41/59] Run BUI range checks in parallel (#5118) These still take almost half-ms in server tick time on live as it has to do a raycast for everyone that has storage or their PDA open or whatever. Haven't benchmarked with a lot of clients but easiest way to tell is just check grafanaTM and I'm not sure how to check this locally. --- .../Systems/SharedUserInterfaceSystem.cs | 68 +++++++++++++++---- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index e842ed2b1..6c38c00df 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -10,6 +10,7 @@ using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Reflection; +using Robust.Shared.Threading; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -20,6 +21,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem [Dependency] private readonly IDynamicTypeFactory _factory = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly IParallelManager _parallel = default!; [Dependency] private readonly IReflectionManager _reflection = default!; [Dependency] private readonly ISharedPlayerManager _player = default!; [Dependency] private readonly SharedTransformSystem _transforms = default!; @@ -29,6 +31,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem private EntityQuery _uiQuery; private EntityQuery _userQuery; + private ActorRangeCheckJob _rangeJob; + public override void Initialize() { base.Initialize(); @@ -38,6 +42,12 @@ public abstract class SharedUserInterfaceSystem : EntitySystem _uiQuery = GetEntityQuery(); _userQuery = GetEntityQuery(); + _rangeJob = new() + { + System = this, + XformQuery = _xformQuery, + }; + SubscribeAllEvent((msg, args) => { if (args.SenderSession.AttachedEntity is not { } player) @@ -886,6 +896,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem public override void Update(float frameTime) { var query = AllEntityQuery(); + // Run these in parallel because it's expensive. + _rangeJob.ActorRanges.Clear(); // Handles closing the BUI if actors move out of range of them. while (query.MoveNext(out var uid, out _, out var uiComp)) @@ -899,24 +911,26 @@ public abstract class SharedUserInterfaceSystem : EntitySystem if (data.InteractionRange <= 0f || actors.Count == 0) continue; - // Okay so somehow UISystem is high up on the server profile - // If that's actually still a problem turn this into an IParallelRobustJob and slam all the UIs in parallel. - var xform = _xformQuery.GetComponent(uid); - var coordinates = xform.Coordinates; - var mapId = xform.MapID; - - for (var i = actors.Count - 1; i >= 0; i--) + foreach (var actor in actors) { - var actor = actors[i]; - - if (CheckRange(uid, key, data, actor, coordinates, mapId)) - continue; - - // Using the non-predicted one here seems fine? - CloseUi((uid, uiComp), key, actor); + _rangeJob.ActorRanges.Add((uid, key, data, actor, false)); } } } + + _parallel.ProcessNow(_rangeJob, _rangeJob.ActorRanges.Count); + + foreach (var data in _rangeJob.ActorRanges) + { + var uid = data.Ui; + var actor = data.Actor; + var key = data.Key; + + if (data.Result || Deleted(uid) || Deleted(actor) || !_uiQuery.TryComp(uid, out var uiComp)) + continue; + + CloseUi((uid, uiComp), key, actor); + } } /// @@ -953,6 +967,32 @@ public abstract class SharedUserInterfaceSystem : EntitySystem return uiCoordinates.InRange(EntityManager, _transforms, actorXform.Coordinates, data.InteractionRange); } + + /// + /// Used for running UI raycast checks in parallel. + /// + private record struct ActorRangeCheckJob() : IParallelRobustJob + { + public EntityQuery XformQuery; + public SharedUserInterfaceSystem System; + public readonly List<(EntityUid Ui, Enum Key, InterfaceData Data, EntityUid Actor, bool Result)> ActorRanges = new(); + + public void Execute(int index) + { + var data = ActorRanges[index]; + + if (!XformQuery.TryComp(data.Ui, out var uiXform)) + { + data.Result = false; + } + else + { + data.Result = System.CheckRange(data.Ui, data.Key, data.Data, data.Actor, uiXform.Coordinates, uiXform.MapID); + } + + ActorRanges[index] = data; + } + } } /// From 51f0c60bd3d4573cb8bb9a9880ae7763e6231e96 Mon Sep 17 00:00:00 2001 From: Vasilis Date: Tue, 14 May 2024 03:12:24 +0300 Subject: [PATCH 42/59] Do not log wrong and correct watchdog token into info logs (#5133) * Lets not do this? * Webedit 2 --- Robust.Server/ServerStatus/WatchdogApi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Server/ServerStatus/WatchdogApi.cs b/Robust.Server/ServerStatus/WatchdogApi.cs index 55afe1ea0..3f85979d2 100644 --- a/Robust.Server/ServerStatus/WatchdogApi.cs +++ b/Robust.Server/ServerStatus/WatchdogApi.cs @@ -68,7 +68,7 @@ namespace Robust.Server.ServerStatus if (auth != _watchdogToken) { // Holy shit nobody read these logs please. - _sawmill.Info(@"Failed auth: ""{0}"" vs ""{1}""", auth, _watchdogToken); + _sawmill.Verbose(@"Failed auth: ""{0}"" vs ""{1}""", auth, _watchdogToken); await context.RespondErrorAsync(HttpStatusCode.Unauthorized); return true; } From 63df90f86f88c70be5c63d2f31528d186e33431f Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 14 May 2024 23:01:41 +1200 Subject: [PATCH 43/59] Version: 222.2.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 98b305eba..56b290525 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 222.1.1 + 222.2.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 8d9b87189..4e4c5477a 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,22 @@ END TEMPLATE--> *None yet* +## 222.2.0 + +### New features + +* Added `EntityQuery.Comp()` (abbreviation of `GetComponent()`) + +### Bugfixes + +* Fix `SerializationManager.TryGetVariableType` checking the wrong property. +* Fixed GrammarSystem mispredicting a character's gender + +### Other + +* User interface system now performs range checks in parallel + + ## 222.1.1 ### Bugfixes From 799702b814124442ca1de956d895d0919232907d Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 15 May 2024 21:02:35 +0200 Subject: [PATCH 44/59] Work against .NET SDK update ManagePackageVersionsCentrally change .NET SDK 8.0.300 changed ManagePackageVersionsCentrally to be implicitly set if Directory.Packages.props exists. We do not want this, as we intentionally have some projects that have it disabled. We now explicitly unset the value in the Directory.Packages.props file to get the old behavior back. See https://github.com/dotnet/core/issues/9309 --- Directory.Packages.props | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Directory.Packages.props b/Directory.Packages.props index aead66c49..24284c3e6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,4 +1,14 @@ + + + + From fbc8086335e85e6e7a45d71aceea678abffa1030 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 17 May 2024 04:53:33 +1200 Subject: [PATCH 45/59] Don't iterate over component events when removing components (#5138) * Don't iterate over component events when removing components * Welp nevermind, forgot about tests * A * AAAAAA * AAAA --- .../GameObjects/EntityEventBus.Common.cs | 26 ++++-- .../GameObjects/EntityEventBus.Directed.cs | 87 ++++++++++--------- .../GameObjects/EntityEventBus.Ordering.cs | 4 +- 3 files changed, 66 insertions(+), 51 deletions(-) diff --git a/Robust.Shared/GameObjects/EntityEventBus.Common.cs b/Robust.Shared/GameObjects/EntityEventBus.Common.cs index c251e42fa..f03999c93 100644 --- a/Robust.Shared/GameObjects/EntityEventBus.Common.cs +++ b/Robust.Shared/GameObjects/EntityEventBus.Common.cs @@ -29,20 +29,28 @@ internal sealed partial class EntityEventBus : IEventBus // See EventTable declaration for layout details internal Dictionary _entEventTables = new(); - // CompType -> EventType -> Handler - internal FrozenDictionary?[] _entSubscriptions = default!; + /// + /// Array of component events and their handlers. The array is indexed by a component's + /// , while the dictionary is indexed by the event type. This does not include events + /// with the + /// + internal FrozenDictionary[] _eventSubs = default!; - // Variant of _entSubscriptions that omits any events with the ComponentEventAttribute - internal FrozenDictionary?[] _entSubscriptionsNoCompEv = default!; + /// + /// Variant of that also includes events with the + /// + internal FrozenDictionary[] _compEventSubs = default!; - // pre-freeze _entSubscriptions data - internal Dictionary?[] _entSubscriptionsUnfrozen = - Array.Empty?>(); + // pre-freeze event subscription data + internal Dictionary?[] _eventSubsUnfrozen = + Array.Empty>(); - // EventType -> { CompType1, ... CompType N } + /// + /// Inverse of , mapping event types to sets of components. + /// + private Dictionary> _eventSubsInv = new(); // Only required to sort ordered subscriptions, which only happens during initialization // so doesn't need to be a frozen dictionary. - private Dictionary> _entSubscriptionsInv = new(); // prevents shitcode, get your subscriptions figured out before you start spawning entities private bool _subscriptionLock; diff --git a/Robust.Shared/GameObjects/EntityEventBus.Directed.cs b/Robust.Shared/GameObjects/EntityEventBus.Directed.cs index d8ccd6243..3115d3405 100644 --- a/Robust.Shared/GameObjects/EntityEventBus.Directed.cs +++ b/Robust.Shared/GameObjects/EntityEventBus.Directed.cs @@ -327,7 +327,7 @@ namespace Robust.Shared.GameObjects foreach (var reg in regs) { - CompIdx.RefArray(ref _entSubscriptionsUnfrozen, reg.Idx) ??= new(); + CompIdx.RefArray(ref _eventSubsUnfrozen, reg.Idx) ??= new(); } } @@ -351,29 +351,33 @@ namespace Robust.Shared.GameObjects _subscriptionLock = true; _eventData = _eventDataUnfrozen.ToFrozenDictionary(); - _entSubscriptions = _entSubscriptionsUnfrozen - .Select(x => x?.ToFrozenDictionary()) + // Find last non-null entry. + var last = 0; + for (var i = 0; i < _eventSubsUnfrozen.Length; i++) + { + var entry = _eventSubsUnfrozen[i]; + if (entry != null) + last = i; + } + + // TODO PERFORMANCE + // make this only contain events that actually use comp-events + // Assuming it makes the frozen dictionaries more specialized and thus faster. + // AFAIK currently only MapInit is both a comp-event and a general event. + // It should probably be changed to just be a comp event. + _compEventSubs = _eventSubsUnfrozen + .Take(last+1) + .Select(dict => dict?.ToFrozenDictionary()!) .ToArray(); - _entSubscriptionsNoCompEv = _entSubscriptionsUnfrozen.Select(FreezeWithoutComponentEvent).ToArray(); + _eventSubs = _eventSubsUnfrozen + .Take(last+1) + .Select(dict => dict?.Where(x => !IsComponentEvent(x.Key)).ToFrozenDictionary()!) + .ToArray(); CalcOrdering(); } - /// - /// Freezes a dictionary while committing events with the . - /// This avoids unnecessarily adding one-off events to the list of subscriptions. - /// - private FrozenDictionary? FreezeWithoutComponentEvent( - Dictionary? input) - { - if (input == null) - return null; - - return input.Where(x => !IsComponentEvent(x.Key)) - .ToFrozenDictionary(); - } - private bool IsComponentEvent(Type t) { var isCompEv = _eventData[t].ComponentEvent; @@ -395,8 +399,8 @@ namespace Robust.Shared.GameObjects if (_subscriptionLock) throw new InvalidOperationException("Subscription locked."); - if (compType.Value >= _entSubscriptionsUnfrozen.Length - || _entSubscriptionsUnfrozen[compType.Value] is not { } compSubs) + if (compType.Value >= _eventSubsUnfrozen.Length + || _eventSubsUnfrozen[compType.Value] is not { } compSubs) { if (IgnoreUnregisteredComponents) return; @@ -411,10 +415,11 @@ namespace Robust.Shared.GameObjects } compSubs.Add(eventType, registration); - _entSubscriptionsInv.GetOrNew(eventType).Add(compType); RegisterCommon(eventType, registration.Ordering, out var data); data.ComponentEvent = eventType.HasCustomAttribute(); + if (!data.ComponentEvent) + _eventSubsInv.GetOrNew(eventType).Add(compType); } private void EntSubscribe( @@ -438,8 +443,8 @@ namespace Robust.Shared.GameObjects if (_subscriptionLock) throw new InvalidOperationException("Subscription locked."); - if (compType.Value >= _entSubscriptionsUnfrozen.Length - || _entSubscriptionsUnfrozen[compType.Value] is not { } compSubs) + if (compType.Value >= _eventSubsUnfrozen.Length + || _eventSubsUnfrozen[compType.Value] is not { } compSubs) { if (IgnoreUnregisteredComponents) return; @@ -449,7 +454,7 @@ namespace Robust.Shared.GameObjects var removed = compSubs.Remove(eventType); if (removed) - _entSubscriptionsInv[eventType].Remove(compType); + _eventSubsInv[eventType].Remove(compType); } private void EntAddEntity(EntityUid euid) @@ -469,7 +474,7 @@ namespace Robust.Shared.GameObjects DebugTools.Assert(_subscriptionLock); var eventTable = _entEventTables[euid]; - var compSubs = _entSubscriptionsNoCompEv[compType.Value]!; + var compSubs = _eventSubs[compType.Value]; foreach (var evType in compSubs.Keys) { @@ -528,13 +533,17 @@ namespace Robust.Shared.GameObjects private void EntRemoveComponent(EntityUid euid, CompIdx compType) { var eventTable = _entEventTables[euid]; - var compSubs = _entSubscriptions[compType.Value]!; + var compSubs = _eventSubs[compType.Value]; foreach (var evType in compSubs.Keys) { + DebugTools.Assert(!_eventData[evType].ComponentEvent); ref var dictIdx = ref CollectionsMarshal.GetValueRefOrNullRef(eventTable.EventIndices, evType); if (Unsafe.IsNullRef(ref dictIdx)) + { + DebugTools.Assert("This should not be possible. Were the events for this component never added?"); continue; + } ref var updateNext = ref dictIdx; @@ -610,9 +619,7 @@ namespace Robust.Shared.GameObjects ref Unit args) where TEvent : notnull { - var compSubs = _entSubscriptions[baseType.Value]!; - - if (compSubs.TryGetValue(typeof(TEvent), out var reg)) + if (_compEventSubs[baseType.Value].TryGetValue(typeof(TEvent), out var reg)) reg.Handler(euid, component, ref args); } @@ -634,7 +641,7 @@ namespace Robust.Shared.GameObjects return false; } - enumerator = new(eventType, startEntry, eventTable.ComponentLists, _entSubscriptions, euid, _entMan); + enumerator = new(eventType, startEntry, eventTable.ComponentLists, _eventSubs, euid, _entMan); return true; } @@ -644,10 +651,10 @@ namespace Robust.Shared.GameObjects _eventDataUnfrozen.Clear(); _entEventTables.Clear(); _inverseEventSubscriptions.Clear(); - _entSubscriptions = default!; - _entSubscriptionsNoCompEv = default!; + _compEventSubs = default!; + _eventSubs = default!; _eventData = FrozenDictionary.Empty; - foreach (var sub in _entSubscriptionsUnfrozen) + foreach (var sub in _eventSubsUnfrozen) { sub?.Clear(); } @@ -661,17 +668,17 @@ namespace Robust.Shared.GameObjects _entMan = null!; _comFac = null!; _entEventTables = null!; - _entSubscriptions = null!; - _entSubscriptionsNoCompEv = null!; - _entSubscriptionsUnfrozen = null!; - _entSubscriptionsInv = null!; + _compEventSubs = null!; + _eventSubs = null!; + _eventSubsUnfrozen = null!; + _eventSubsInv = null!; } private struct SubscriptionsEnumerator { private readonly Type _eventType; private readonly EntityUid _uid; - private readonly FrozenDictionary?[] _subscriptions; + private readonly FrozenDictionary[] _subscriptions; private readonly IEntityManager _entityManager; private readonly EventTableListEntry[] _list; private int _idx; @@ -680,7 +687,7 @@ namespace Robust.Shared.GameObjects Type eventType, int startEntry, EventTableListEntry[] list, - FrozenDictionary?[] subscriptions, + FrozenDictionary[] subscriptions, EntityUid uid, IEntityManager entityManager) { @@ -707,7 +714,7 @@ namespace Robust.Shared.GameObjects _idx = entry.Next; var compType = entry.Component; - var compSubs = _subscriptions[compType.Value]!; + var compSubs = _subscriptions[compType.Value]; if (!compSubs.TryGetValue(_eventType, out registration)) { diff --git a/Robust.Shared/GameObjects/EntityEventBus.Ordering.cs b/Robust.Shared/GameObjects/EntityEventBus.Ordering.cs index 6bb026526..0b87c25ee 100644 --- a/Robust.Shared/GameObjects/EntityEventBus.Ordering.cs +++ b/Robust.Shared/GameObjects/EntityEventBus.Ordering.cs @@ -59,10 +59,10 @@ namespace Robust.Shared.GameObjects // Collect all subscriptions, broadcast and ordered. IEnumerable regs = sub.BroadcastRegistrations; - if (_entSubscriptionsInv.TryGetValue(eventType, out var comps)) + if (_eventSubsInv.TryGetValue(eventType, out var comps)) { regs = regs.Concat(comps - .Select(c => _entSubscriptions[c.Value]) + .Select(c => _eventSubs[c.Value]) .Where(c => c != null) .Select(c => c![eventType])); } From b056caeed778bbdce92cbf8e2ec235f2d6ff8b35 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 17 May 2024 03:00:11 +1000 Subject: [PATCH 46/59] Fix cross-map BUIs (#5115) Even the ignore range bit is going to break with pvs but uhh not sure on that one unless we do overrides or something. --- .../GameObjects/Systems/SharedUserInterfaceSystem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 6c38c00df..7e62b893a 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -944,12 +944,12 @@ public abstract class SharedUserInterfaceSystem : EntitySystem EntityCoordinates uiCoordinates, MapId uiMap) { + if (!_xformQuery.TryGetComponent(actor, out var actorXform) || actorXform.MapID != uiMap) + return false; + if (_ignoreUIRangeQuery.HasComponent(actor)) return true; - if (!_xformQuery.TryGetComponent(actor, out var actorXform)) - return false; - // Handle pluggable BoundUserInterfaceCheckRangeEvent var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(uid, key, data, actor); RaiseLocalEvent(uid, ref checkRangeEvent, broadcast: true); From e30e96362306515279883ef7c0735ec9c8253a2e Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Thu, 16 May 2024 20:05:39 +0300 Subject: [PATCH 47/59] Hidden tiles (#5102) * hidden tiles * Update TileSpawningUIController.cs * Update TileSpawningUIController.cs * Update ITileDefinition.cs * Update TileSpawningUIController.cs * Move EditorHidden where clause out * Make EditorHidden a DIM So there's no breaking change * Release notes. --------- Co-authored-by: Pieter-Jan Briers --- RELEASE-NOTES.md | 2 +- .../Controllers/Implementations/TileSpawningUIController.cs | 2 +- Robust.Shared/Map/ITileDefinition.cs | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 4e4c5477a..640e54ed2 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* `ITileDefinition.EditorHidden` allows hiding a tile from the tile spawn panel. ### Bugfixes diff --git a/Robust.Client/UserInterface/Controllers/Implementations/TileSpawningUIController.cs b/Robust.Client/UserInterface/Controllers/Implementations/TileSpawningUIController.cs index 104f20df4..9b2a8b5f8 100644 --- a/Robust.Client/UserInterface/Controllers/Implementations/TileSpawningUIController.cs +++ b/Robust.Client/UserInterface/Controllers/Implementations/TileSpawningUIController.cs @@ -130,7 +130,7 @@ public sealed class TileSpawningUIController : UIController _window.TileList.Clear(); - IEnumerable tileDefs = _tiles; + IEnumerable tileDefs = _tiles.Where(def => !def.EditorHidden); if (!string.IsNullOrEmpty(searchStr)) { diff --git a/Robust.Shared/Map/ITileDefinition.cs b/Robust.Shared/Map/ITileDefinition.cs index 4801f0fd2..76b45ede6 100644 --- a/Robust.Shared/Map/ITileDefinition.cs +++ b/Robust.Shared/Map/ITileDefinition.cs @@ -55,5 +55,10 @@ namespace Robust.Shared.Map /// /// The new tile ID for this tile definition. void AssignTileId(ushort id); + + /// + /// Allows you to hide tiles from the tile spawn menu. + /// + bool EditorHidden => false; } } From 7f2da4d4f32ed1f3bbb2b80b58d739bbda888127 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 17 May 2024 05:06:11 +1200 Subject: [PATCH 48/59] Fix paused entities not updating on prototype reload (#5128) --- Robust.Shared/GameObjects/Systems/PrototypeReloadSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Shared/GameObjects/Systems/PrototypeReloadSystem.cs b/Robust.Shared/GameObjects/Systems/PrototypeReloadSystem.cs index a3c8a8800..2a240e03f 100644 --- a/Robust.Shared/GameObjects/Systems/PrototypeReloadSystem.cs +++ b/Robust.Shared/GameObjects/Systems/PrototypeReloadSystem.cs @@ -24,7 +24,7 @@ internal sealed class PrototypeReloadSystem : EntitySystem if (!eventArgs.ByType.TryGetValue(typeof(EntityPrototype), out var set)) return; - var query = EntityQueryEnumerator(); + var query = AllEntityQuery(); while (query.MoveNext(out var uid, out var metadata)) { var id = metadata.EntityPrototype?.ID; From 30907d84155df10d51bbc0067602157932cabf87 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Thu, 16 May 2024 11:00:15 -0700 Subject: [PATCH 49/59] Fix ordered subscriptions not working when targeting a parent system type (#5135) * Fix ordered subscriptions not working when targeting a parent system type * Fix missing usages of expand ordering * Extract method --- .../GameObjects/EntityEventBus.Broadcast.cs | 4 +-- .../GameObjects/EntityEventBus.Common.cs | 4 +++ .../GameObjects/EntityEventBus.Directed.cs | 12 +++++--- .../GameObjects/EntityEventBus.Ordering.cs | 28 +++++++++++++++++++ Robust.Shared/GameObjects/EntityManager.cs | 4 ++- .../GameObjects/EntitySystem.Subscriptions.cs | 22 +++++++-------- .../EntityEventBusTests.ComponentEvent.cs | 14 ++++++---- .../EntityEventBusTests.SystemEvent.cs | 4 ++- 8 files changed, 68 insertions(+), 24 deletions(-) diff --git a/Robust.Shared/GameObjects/EntityEventBus.Broadcast.cs b/Robust.Shared/GameObjects/EntityEventBus.Broadcast.cs index bfafd0098..60bff3afc 100644 --- a/Robust.Shared/GameObjects/EntityEventBus.Broadcast.cs +++ b/Robust.Shared/GameObjects/EntityEventBus.Broadcast.cs @@ -167,7 +167,7 @@ namespace Robust.Shared.GameObjects if (eventHandler == null) throw new ArgumentNullException(nameof(eventHandler)); - var order = new OrderingData(orderType, before ?? Array.Empty(), after ?? Array.Empty()); + var order = CreateOrderingData(orderType, before, after); SubscribeEventCommon(source, subscriber, (ref Unit ev) => eventHandler(Unsafe.As(ref ev)), eventHandler, order, false); @@ -187,7 +187,7 @@ namespace Robust.Shared.GameObjects EntityEventRefHandler eventHandler, Type orderType, Type[]? before = null, Type[]? after = null) where T : notnull { - var order = new OrderingData(orderType, before ?? Array.Empty(), after ?? Array.Empty()); + var order = CreateOrderingData(orderType, before, after); SubscribeEventCommon(source, subscriber, (ref Unit ev) => { diff --git a/Robust.Shared/GameObjects/EntityEventBus.Common.cs b/Robust.Shared/GameObjects/EntityEventBus.Common.cs index f03999c93..1d8c22859 100644 --- a/Robust.Shared/GameObjects/EntityEventBus.Common.cs +++ b/Robust.Shared/GameObjects/EntityEventBus.Common.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using JetBrains.Annotations; using Robust.Shared.Collections; +using Robust.Shared.Reflection; using Robust.Shared.Utility; namespace Robust.Shared.GameObjects; @@ -13,6 +14,7 @@ internal sealed partial class EntityEventBus : IEventBus { private IEntityManager _entMan; private IComponentFactory _comFac; + private IReflectionManager _reflection; // Data on individual events. Used to check ordering info and fire broadcast events. private FrozenDictionary _eventData = FrozenDictionary.Empty; @@ -57,6 +59,8 @@ internal sealed partial class EntityEventBus : IEventBus public bool IgnoreUnregisteredComponents; + private readonly List _childrenTypesTemp = []; + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ref Unit ExtractUnitRef(ref object obj, Type objType) { diff --git a/Robust.Shared/GameObjects/EntityEventBus.Directed.cs b/Robust.Shared/GameObjects/EntityEventBus.Directed.cs index 3115d3405..80291a6cc 100644 --- a/Robust.Shared/GameObjects/EntityEventBus.Directed.cs +++ b/Robust.Shared/GameObjects/EntityEventBus.Directed.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Robust.Shared.Collections; +using Robust.Shared.Reflection; using Robust.Shared.Utility; namespace Robust.Shared.GameObjects @@ -117,10 +118,12 @@ namespace Robust.Shared.GameObjects /// Constructs a new instance of . /// /// The entity manager to watch for entity/component events. - public EntityEventBus(IEntityManager entMan) + /// The reflection manager to use when finding derived types. + public EntityEventBus(IEntityManager entMan, IReflectionManager reflection) { _entMan = entMan; _comFac = entMan.ComponentFactory; + _reflection = reflection; // Dynamic handling of components is only for RobustUnitTest compatibility spaghetti. _comFac.ComponentsAdded += ComFacOnComponentsAdded; @@ -248,7 +251,7 @@ namespace Robust.Shared.GameObjects void EventHandler(EntityUid uid, IComponent comp, ref TEvent args) => handler(uid, (TComp)comp, args); - var orderData = new OrderingData(orderType, before ?? Array.Empty(), after ?? Array.Empty()); + var orderData = CreateOrderingData(orderType, before, after); EntSubscribe( CompIdx.Index(), @@ -281,7 +284,7 @@ namespace Robust.Shared.GameObjects void EventHandler(EntityUid uid, IComponent comp, ref TEvent args) => handler(uid, (TComp)comp, ref args); - var orderData = new OrderingData(orderType, before ?? Array.Empty(), after ?? Array.Empty()); + var orderData = CreateOrderingData(orderType, before, after); EntSubscribe( CompIdx.Index(), @@ -300,7 +303,7 @@ namespace Robust.Shared.GameObjects void EventHandler(EntityUid uid, IComponent comp, ref TEvent args) => handler(new Entity(uid, (TComp) comp), ref args); - var orderData = new OrderingData(orderType, before ?? Array.Empty(), after ?? Array.Empty()); + var orderData = CreateOrderingData(orderType, before, after); EntSubscribe( CompIdx.Index(), @@ -667,6 +670,7 @@ namespace Robust.Shared.GameObjects // punishment for use-after-free _entMan = null!; _comFac = null!; + _reflection = null!; _entEventTables = null!; _compEventSubs = null!; _eventSubs = null!; diff --git a/Robust.Shared/GameObjects/EntityEventBus.Ordering.cs b/Robust.Shared/GameObjects/EntityEventBus.Ordering.cs index 0b87c25ee..3ae3a702e 100644 --- a/Robust.Shared/GameObjects/EntityEventBus.Ordering.cs +++ b/Robust.Shared/GameObjects/EntityEventBus.Ordering.cs @@ -200,5 +200,33 @@ namespace Robust.Shared.GameObjects } } } + + private OrderingData CreateOrderingData(Type orderType, Type[]? before, Type[]? after) + { + AddChildrenTypes(ref before); + AddChildrenTypes(ref after); + return new OrderingData(orderType, before ?? [], after ?? []); + } + + private void AddChildrenTypes(ref Type[]? original) + { + if (original == null || original.Length == 0) + return; + + _childrenTypesTemp.Clear(); + foreach (var beforeType in original) + { + foreach (var child in _reflection.GetAllChildren(beforeType)) + { + _childrenTypesTemp.Add(child); + } + } + + if (_childrenTypesTemp.Count > 0) + { + Array.Resize(ref original, original.Length + _childrenTypesTemp.Count); + _childrenTypesTemp.CopyTo(original, original.Length - _childrenTypesTemp.Count); + } + } } } diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index 82f0f806a..161b5312d 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -15,6 +15,7 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Player; using Robust.Shared.Profiling; using Robust.Shared.Prototypes; +using Robust.Shared.Reflection; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Timing; @@ -40,6 +41,7 @@ namespace Robust.Shared.GameObjects [IoC.Dependency] private readonly ISerializationManager _serManager = default!; [IoC.Dependency] private readonly ProfManager _prof = default!; [IoC.Dependency] private readonly INetManager _netMan = default!; + [IoC.Dependency] private readonly IReflectionManager _reflection = default!; // I feel like PJB might shed me for putting a system dependency here, but its required for setting entity // positions on spawn.... @@ -125,7 +127,7 @@ namespace Robust.Shared.GameObjects if (Initialized) throw new InvalidOperationException("Initialize() called multiple times"); - _eventBus = new EntityEventBus(this); + _eventBus = new EntityEventBus(this, _reflection); InitializeComponents(); _metaReg = _componentFactory.GetRegistration(typeof(MetaDataComponent)); diff --git a/Robust.Shared/GameObjects/EntitySystem.Subscriptions.cs b/Robust.Shared/GameObjects/EntitySystem.Subscriptions.cs index 897db313e..2a865a83b 100644 --- a/Robust.Shared/GameObjects/EntitySystem.Subscriptions.cs +++ b/Robust.Shared/GameObjects/EntitySystem.Subscriptions.cs @@ -109,6 +109,17 @@ namespace Robust.Shared.GameObjects _subscriptions.Add(new SubBroadcast>(src)); } + protected void SubscribeLocalEvent( + EntityEventRefHandler handler, + Type[]? before = null, Type[]? after = null) + where TComp : IComponent + where TEvent : notnull + { + EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after); + + _subscriptions.Add(new SubLocal()); + } + /// // [Obsolete("Subscribe to the event by ref instead (ComponentEventRefHandler)")] protected void SubscribeLocalEvent( @@ -133,17 +144,6 @@ namespace Robust.Shared.GameObjects _subscriptions.Add(new SubLocal()); } - protected void SubscribeLocalEvent( - EntityEventRefHandler handler, - Type[]? before = null, Type[]? after = null) - where TComp : IComponent - where TEvent : notnull - { - EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after); - - _subscriptions.Add(new SubLocal()); - } - private void ShutdownSubscriptions() { foreach (var sub in _subscriptions) diff --git a/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.ComponentEvent.cs b/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.ComponentEvent.cs index ff42c0e10..fbe7a3120 100644 --- a/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.ComponentEvent.cs +++ b/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.ComponentEvent.cs @@ -1,10 +1,10 @@ -using System; using System.Collections.Generic; using Moq; using NUnit.Framework; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Log; +using Robust.Shared.Reflection; using Robust.UnitTesting.Shared.Reflection; namespace Robust.UnitTesting.Shared.GameObjects @@ -21,6 +21,7 @@ namespace Robust.UnitTesting.Shared.GameObjects var compInstance = new MetaDataComponent(); var entManMock = new Mock(); + var reflectMock = new Mock(); compFactory.RegisterClass(); entManMock.Setup(m => m.ComponentFactory).Returns(compFactory); @@ -35,7 +36,7 @@ namespace Robust.UnitTesting.Shared.GameObjects entManMock.Setup(m => m.GetComponentInternal(entUid, CompIdx.Index())) .Returns(compInstance); - var bus = new EntityEventBus(entManMock.Object); + var bus = new EntityEventBus(entManMock.Object, reflectMock.Object); bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare(); // Subscribe @@ -80,6 +81,7 @@ namespace Robust.UnitTesting.Shared.GameObjects CompIdx.Index()); var compFacMock = new Mock(); + var reflectMock = new Mock(); compFacMock.Setup(m => m.GetRegistration(CompIdx.Index())).Returns(compRegistration); compFacMock.Setup(m => m.GetAllRegistrations()).Returns(new[] { compRegistration }); @@ -92,7 +94,7 @@ namespace Robust.UnitTesting.Shared.GameObjects entManMock.Setup(m => m.GetComponent(entUid, typeof(MetaDataComponent))) .Returns(compInstance); - var bus = new EntityEventBus(entManMock.Object); + var bus = new EntityEventBus(entManMock.Object, reflectMock.Object); bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare(); // Subscribe @@ -137,6 +139,7 @@ namespace Robust.UnitTesting.Shared.GameObjects CompIdx.Index()); var compFacMock = new Mock(); + var reflectMock = new Mock(); compFacMock.Setup(m => m.GetRegistration(CompIdx.Index())).Returns(compRegistration); compFacMock.Setup(m => m.GetAllRegistrations()).Returns(new[] { compRegistration }); @@ -149,7 +152,7 @@ namespace Robust.UnitTesting.Shared.GameObjects entManMock.Setup(m => m.GetComponent(entUid, typeof(MetaDataComponent))) .Returns(compInstance); - var bus = new EntityEventBus(entManMock.Object); + var bus = new EntityEventBus(entManMock.Object, reflectMock.Object); bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare(); // Subscribe @@ -184,6 +187,7 @@ namespace Robust.UnitTesting.Shared.GameObjects var entManMock = new Mock(); var compFacMock = new Mock(); + var reflectMock = new Mock(); List allRefTypes = new(); void Setup(out T instance) where T : IComponent, new() @@ -208,7 +212,7 @@ namespace Robust.UnitTesting.Shared.GameObjects compFacMock.Setup(m => m.GetAllRegistrations()).Returns(allRefTypes.ToArray()); entManMock.Setup(m => m.ComponentFactory).Returns(compFacMock.Object); - var bus = new EntityEventBus(entManMock.Object); + var bus = new EntityEventBus(entManMock.Object, reflectMock.Object); bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare(); // Subscribe diff --git a/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.SystemEvent.cs b/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.SystemEvent.cs index 44bb208ec..6dd43e6fe 100644 --- a/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.SystemEvent.cs +++ b/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.SystemEvent.cs @@ -2,6 +2,7 @@ using System; using Moq; using NUnit.Framework; using Robust.Shared.GameObjects; +using Robust.Shared.Reflection; namespace Robust.UnitTesting.Shared.GameObjects { @@ -12,8 +13,9 @@ namespace Robust.UnitTesting.Shared.GameObjects { var compFacMock = new Mock(); var entManMock = new Mock(); + var reflectMock = new Mock(); entManMock.SetupGet(e => e.ComponentFactory).Returns(compFacMock.Object); - var bus = new EntityEventBus(entManMock.Object); + var bus = new EntityEventBus(entManMock.Object, reflectMock.Object); return bus; } From 118961390896319beeb359f19a0bdbaaafb04d30 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 17 May 2024 04:09:25 +1000 Subject: [PATCH 50/59] Add physics delta states (#5116) * Add physics delta states Significantly cuts down on data being sent + should make client state handling faster. * Update Robust.Shared/Physics/Components/PhysicsComponentState.cs --- .../Components/PhysicsComponent.Physics.cs | 6 ++ .../Components/PhysicsComponentState.cs | 31 +++++++- .../Systems/SharedPhysicsSystem.Components.cs | 79 +++++++++++++++---- 3 files changed, 98 insertions(+), 18 deletions(-) diff --git a/Robust.Shared/Physics/Components/PhysicsComponent.Physics.cs b/Robust.Shared/Physics/Components/PhysicsComponent.Physics.cs index 4e620399b..2e887f435 100644 --- a/Robust.Shared/Physics/Components/PhysicsComponent.Physics.cs +++ b/Robust.Shared/Physics/Components/PhysicsComponent.Physics.cs @@ -30,6 +30,7 @@ using Robust.Shared.Maths; using Robust.Shared.Physics.Dynamics.Contacts; using Robust.Shared.Physics.Systems; using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Timing; using Robust.Shared.ViewVariables; namespace Robust.Shared.Physics.Components; @@ -37,6 +38,11 @@ namespace Robust.Shared.Physics.Components; [RegisterComponent, NetworkedComponent] public sealed partial class PhysicsComponent : Component { + /// + /// Last time the physics component requires a full dirty + /// + public GameTick FullUpdate = GameTick.Zero; + /// /// Has this body been added to an island previously in this tick. /// diff --git a/Robust.Shared/Physics/Components/PhysicsComponentState.cs b/Robust.Shared/Physics/Components/PhysicsComponentState.cs index 29080b4a0..e9f2dad61 100644 --- a/Robust.Shared/Physics/Components/PhysicsComponentState.cs +++ b/Robust.Shared/Physics/Components/PhysicsComponentState.cs @@ -1,13 +1,12 @@ using System; using System.Numerics; using Robust.Shared.GameObjects; -using Robust.Shared.Maths; using Robust.Shared.Serialization; namespace Robust.Shared.Physics.Components; [Serializable, NetSerializable] -public readonly record struct PhysicsComponentState( +public sealed class PhysicsComponentState( bool CanCollide, bool SleepingAllowed, bool FixedRotation, @@ -25,11 +24,35 @@ public readonly record struct PhysicsComponentState( public readonly bool FixedRotation = FixedRotation; public readonly BodyStatus Status = Status; - public readonly Vector2 LinearVelocity = LinearVelocity; - public readonly float AngularVelocity = AngularVelocity; + public Vector2 LinearVelocity = LinearVelocity; + public float AngularVelocity = AngularVelocity; public readonly BodyType BodyType = BodyType; public readonly float Friction = Friction; public readonly float LinearDamping = LinearDamping; public readonly float AngularDamping = AngularDamping; } + +[Serializable, NetSerializable] +public sealed class PhysicsDeltaState : IComponentState, IComponentDeltaState +{ + public Vector2 LinearVelocity; + public float AngularVelocity; + + public bool FullState => false; + + public void ApplyToFullState(IComponentState fullState) + { + var state = (PhysicsComponentState)fullState; + + state.LinearVelocity = LinearVelocity; + state.AngularVelocity = AngularVelocity; + } + + public IComponentState CreateNewFullState(IComponentState fullState) + { + var state = (PhysicsComponentState) fullState; + return new PhysicsComponentState(state.CanCollide, state.SleepingAllowed, state.FixedRotation, state.Status, + LinearVelocity, AngularVelocity, state.BodyType, state.Friction, state.LinearDamping, state.AngularDamping); + } +} diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs index 73e1b891f..d652d18c9 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs @@ -69,21 +69,38 @@ public partial class SharedPhysicsSystem private void OnPhysicsGetState(EntityUid uid, PhysicsComponent component, ref ComponentGetState args) { - args.State = new PhysicsComponentState( - component.CanCollide, - component.SleepingAllowed, - component.FixedRotation, - component.BodyStatus, - component.LinearVelocity, - component.AngularVelocity, - component.BodyType, - component._friction, - component.LinearDamping, - component.AngularDamping); + if (args.FromTick <= component.FullUpdate) + { + args.State = new PhysicsComponentState( + component.CanCollide, + component.SleepingAllowed, + component.FixedRotation, + component.BodyStatus, + component.LinearVelocity, + component.AngularVelocity, + component.BodyType, + component._friction, + component.LinearDamping, + component.AngularDamping); + return; + } + + args.State = new PhysicsDeltaState() + { + LinearVelocity = component.LinearVelocity, + AngularVelocity = component.AngularVelocity, + }; } private void OnPhysicsHandleState(EntityUid uid, PhysicsComponent component, ref ComponentHandleState args) { + if (args.Current is PhysicsDeltaState delta) + { + component.LinearVelocity = delta.LinearVelocity; + component.AngularVelocity = delta.AngularVelocity; + return; + } + if (args.Current is not PhysicsComponentState newState) return; @@ -94,7 +111,7 @@ public partial class SharedPhysicsSystem // So transform doesn't apply MapId in the HandleComponentState because ??? so MapId can still be 0. // Fucking kill me, please. You have no idea deep the rabbit hole of shitcode goes to make this work. - TryComp(uid, out var manager); + _fixturesQuery.TryComp(uid, out var manager); SetLinearVelocity(uid, newState.LinearVelocity, body: component, manager: manager); SetAngularVelocity(uid, newState.AngularVelocity, body: component, manager: manager); @@ -278,6 +295,7 @@ public partial class SharedPhysicsSystem if (((int) body.BodyType & (int) (BodyType.Kinematic | BodyType.Static)) != 0) { body._localCenter = Vector2.Zero; + FullDirty((uid, body)); Dirty(uid, body); return; } @@ -313,6 +331,7 @@ public partial class SharedPhysicsSystem // Update center of mass velocity. body.LinearVelocity += Vector2Helpers.Cross(body.AngularVelocity, localCenter - oldCenter); + FullDirty((uid, body)); Dirty(uid, body); } @@ -339,7 +358,9 @@ public partial class SharedPhysicsSystem body.AngularVelocity = value; if (dirty) + { Dirty(uid, body); + } return true; } @@ -367,7 +388,9 @@ public partial class SharedPhysicsSystem body.LinearVelocity = velocity; if (dirty) + { Dirty(uid, body); + } return true; } @@ -380,7 +403,10 @@ public partial class SharedPhysicsSystem body.AngularDamping = value; if (dirty) + { + FullDirty((uid, body)); Dirty(uid, body); + } } [Obsolete("Use overload that takes EntityUid")] @@ -397,7 +423,10 @@ public partial class SharedPhysicsSystem body.LinearDamping = value; if (dirty) + { + FullDirty((uid, body)); Dirty(uid, body); + } } [Obsolete("Use overload that takes EntityUid")] @@ -442,7 +471,7 @@ public partial class SharedPhysicsSystem // Update wake system last, if sleeping but still colliding. if (!value && body.CanCollide) - _wakeSystem.UpdateCanCollide(ent, checkTerminating: false, dirty: false); + _wakeSystem.UpdateCanCollide(ent, checkTerminating: false); if (updateSleepTime) SetSleepTime(body, 0); @@ -455,7 +484,6 @@ public partial class SharedPhysicsSystem } UpdateMapAwakeState(uid, body); - Dirty(ent); } public void TrySetBodyType(EntityUid uid, BodyType value, FixturesComponent? manager = null, PhysicsComponent? body = null, TransformComponent? xform = null) @@ -512,7 +540,10 @@ public partial class SharedPhysicsSystem body.BodyStatus = status; if (dirty) + { + FullDirty((uid, body)); Dirty(uid, body); + } } [Obsolete("Use overload that takes EntityUid")] @@ -571,7 +602,10 @@ public partial class SharedPhysicsSystem } if (dirty) + { + FullDirty((uid, body)); Dirty(uid, body); + } return value; } @@ -586,7 +620,10 @@ public partial class SharedPhysicsSystem ResetMassData(uid, manager: manager, body: body); if (dirty) + { + FullDirty((uid, body)); Dirty(uid, body); + } } public void SetFriction(EntityUid uid, PhysicsComponent body, float value, bool dirty = true) @@ -597,7 +634,10 @@ public partial class SharedPhysicsSystem body._friction = value; if (dirty) + { + FullDirty((uid, body)); Dirty(uid, body); + } } [Obsolete("Use overload that takes EntityUid")] @@ -621,7 +661,10 @@ public partial class SharedPhysicsSystem body.InvI = 1.0f / body._inertia; if (dirty) + { + FullDirty((uid, body)); Dirty(uid, body); + } } } @@ -651,7 +694,10 @@ public partial class SharedPhysicsSystem body.SleepingAllowed = value; if (dirty) + { + FullDirty((uid, body)); Dirty(uid, body); + } } public void SetSleepTime(PhysicsComponent body, float value) @@ -774,4 +820,9 @@ public partial class SharedPhysicsSystem { // See client-side system } + + private void FullDirty(Entity entity) + { + entity.Comp.FullUpdate = _gameTiming.CurTick; + } } From ed359481b43043a1674b742d79e9379a3ae849ad Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 16 May 2024 20:14:18 +0200 Subject: [PATCH 51/59] We can't expect god to do the release notes. --- RELEASE-NOTES.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 640e54ed2..efc0378d0 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -40,14 +40,17 @@ END TEMPLATE--> ### New features * `ITileDefinition.EditorHidden` allows hiding a tile from the tile spawn panel. +* Ordered event subscriptions now take child types into account, so ordering based on a shared type will work. ### Bugfixes -*None yet* +* Cross-map BUI range checks now work. +* Paused entities update on prototype reload. ### Other -*None yet* +* Fixed build compatibility with .NET 8.0.300 SDK, due to changes in how Central Package Management behaves. +* Physics component has delta states to reduce network usage. ### Internal From 0b95a4edeb72704e4d9fe670c612211094a79ccf Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 16 May 2024 20:14:37 +0200 Subject: [PATCH 52/59] Version: 222.3.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 56b290525..f9d62e428 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 222.2.0 + 222.3.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index efc0378d0..16b798c74 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,6 +39,25 @@ END TEMPLATE--> ### New features +*None yet* + +### Bugfixes + +*None yet* + +### Other + +*None yet* + +### Internal + +*None yet* + + +## 222.3.0 + +### New features + * `ITileDefinition.EditorHidden` allows hiding a tile from the tile spawn panel. * Ordered event subscriptions now take child types into account, so ordering based on a shared type will work. @@ -52,10 +71,6 @@ END TEMPLATE--> * Fixed build compatibility with .NET 8.0.300 SDK, due to changes in how Central Package Management behaves. * Physics component has delta states to reduce network usage. -### Internal - -*None yet* - ## 222.2.0 From b48ee22800db6436ec20913b4ddfea98e85c8ad0 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 16 May 2024 22:25:13 +0200 Subject: [PATCH 53/59] Add more System.Numerics types to sandbox. --- RELEASE-NOTES.md | 2 +- Robust.Shared/ContentPack/Sandbox.yml | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 16b798c74..8833a6342 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* Added the following types from `System.Numerics` to the sandbox: `Complex`, `Matrix3x2`, `Matrix4x4`, `Plane`, `Quaternion`, `Vector3`, `Vector4`. ### Bugfixes diff --git a/Robust.Shared/ContentPack/Sandbox.yml b/Robust.Shared/ContentPack/Sandbox.yml index d0828f9aa..06aeb8285 100644 --- a/Robust.Shared/ContentPack/Sandbox.yml +++ b/Robust.Shared/ContentPack/Sandbox.yml @@ -442,7 +442,14 @@ Types: AddressFamily: { } System.Numerics: BitOperations: { All: True } + Complex: { All: True } + Matrix3x2: { All: True } + Matrix4x4: { All: True } + Plane: { All: True } + Quaternion: { All: True } Vector2: { All: True } + Vector3: { All: True } + Vector4: { All: True } System.Reflection: Assembly: Methods: From 6b1347584265c0506b7d1cd20d0a638e4ddc7c42 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 17 May 2024 10:21:05 +1000 Subject: [PATCH 54/59] Revert "Add physics delta states (#5116)" (#5144) This reverts commit 118961390896319beeb359f19a0bdbaaafb04d30. --- .../Components/PhysicsComponent.Physics.cs | 6 -- .../Components/PhysicsComponentState.cs | 31 +------- .../Systems/SharedPhysicsSystem.Components.cs | 79 ++++--------------- 3 files changed, 18 insertions(+), 98 deletions(-) diff --git a/Robust.Shared/Physics/Components/PhysicsComponent.Physics.cs b/Robust.Shared/Physics/Components/PhysicsComponent.Physics.cs index 2e887f435..4e620399b 100644 --- a/Robust.Shared/Physics/Components/PhysicsComponent.Physics.cs +++ b/Robust.Shared/Physics/Components/PhysicsComponent.Physics.cs @@ -30,7 +30,6 @@ using Robust.Shared.Maths; using Robust.Shared.Physics.Dynamics.Contacts; using Robust.Shared.Physics.Systems; using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Timing; using Robust.Shared.ViewVariables; namespace Robust.Shared.Physics.Components; @@ -38,11 +37,6 @@ namespace Robust.Shared.Physics.Components; [RegisterComponent, NetworkedComponent] public sealed partial class PhysicsComponent : Component { - /// - /// Last time the physics component requires a full dirty - /// - public GameTick FullUpdate = GameTick.Zero; - /// /// Has this body been added to an island previously in this tick. /// diff --git a/Robust.Shared/Physics/Components/PhysicsComponentState.cs b/Robust.Shared/Physics/Components/PhysicsComponentState.cs index e9f2dad61..29080b4a0 100644 --- a/Robust.Shared/Physics/Components/PhysicsComponentState.cs +++ b/Robust.Shared/Physics/Components/PhysicsComponentState.cs @@ -1,12 +1,13 @@ using System; using System.Numerics; using Robust.Shared.GameObjects; +using Robust.Shared.Maths; using Robust.Shared.Serialization; namespace Robust.Shared.Physics.Components; [Serializable, NetSerializable] -public sealed class PhysicsComponentState( +public readonly record struct PhysicsComponentState( bool CanCollide, bool SleepingAllowed, bool FixedRotation, @@ -24,35 +25,11 @@ public sealed class PhysicsComponentState( public readonly bool FixedRotation = FixedRotation; public readonly BodyStatus Status = Status; - public Vector2 LinearVelocity = LinearVelocity; - public float AngularVelocity = AngularVelocity; + public readonly Vector2 LinearVelocity = LinearVelocity; + public readonly float AngularVelocity = AngularVelocity; public readonly BodyType BodyType = BodyType; public readonly float Friction = Friction; public readonly float LinearDamping = LinearDamping; public readonly float AngularDamping = AngularDamping; } - -[Serializable, NetSerializable] -public sealed class PhysicsDeltaState : IComponentState, IComponentDeltaState -{ - public Vector2 LinearVelocity; - public float AngularVelocity; - - public bool FullState => false; - - public void ApplyToFullState(IComponentState fullState) - { - var state = (PhysicsComponentState)fullState; - - state.LinearVelocity = LinearVelocity; - state.AngularVelocity = AngularVelocity; - } - - public IComponentState CreateNewFullState(IComponentState fullState) - { - var state = (PhysicsComponentState) fullState; - return new PhysicsComponentState(state.CanCollide, state.SleepingAllowed, state.FixedRotation, state.Status, - LinearVelocity, AngularVelocity, state.BodyType, state.Friction, state.LinearDamping, state.AngularDamping); - } -} diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs index d652d18c9..73e1b891f 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs @@ -69,38 +69,21 @@ public partial class SharedPhysicsSystem private void OnPhysicsGetState(EntityUid uid, PhysicsComponent component, ref ComponentGetState args) { - if (args.FromTick <= component.FullUpdate) - { - args.State = new PhysicsComponentState( - component.CanCollide, - component.SleepingAllowed, - component.FixedRotation, - component.BodyStatus, - component.LinearVelocity, - component.AngularVelocity, - component.BodyType, - component._friction, - component.LinearDamping, - component.AngularDamping); - return; - } - - args.State = new PhysicsDeltaState() - { - LinearVelocity = component.LinearVelocity, - AngularVelocity = component.AngularVelocity, - }; + args.State = new PhysicsComponentState( + component.CanCollide, + component.SleepingAllowed, + component.FixedRotation, + component.BodyStatus, + component.LinearVelocity, + component.AngularVelocity, + component.BodyType, + component._friction, + component.LinearDamping, + component.AngularDamping); } private void OnPhysicsHandleState(EntityUid uid, PhysicsComponent component, ref ComponentHandleState args) { - if (args.Current is PhysicsDeltaState delta) - { - component.LinearVelocity = delta.LinearVelocity; - component.AngularVelocity = delta.AngularVelocity; - return; - } - if (args.Current is not PhysicsComponentState newState) return; @@ -111,7 +94,7 @@ public partial class SharedPhysicsSystem // So transform doesn't apply MapId in the HandleComponentState because ??? so MapId can still be 0. // Fucking kill me, please. You have no idea deep the rabbit hole of shitcode goes to make this work. - _fixturesQuery.TryComp(uid, out var manager); + TryComp(uid, out var manager); SetLinearVelocity(uid, newState.LinearVelocity, body: component, manager: manager); SetAngularVelocity(uid, newState.AngularVelocity, body: component, manager: manager); @@ -295,7 +278,6 @@ public partial class SharedPhysicsSystem if (((int) body.BodyType & (int) (BodyType.Kinematic | BodyType.Static)) != 0) { body._localCenter = Vector2.Zero; - FullDirty((uid, body)); Dirty(uid, body); return; } @@ -331,7 +313,6 @@ public partial class SharedPhysicsSystem // Update center of mass velocity. body.LinearVelocity += Vector2Helpers.Cross(body.AngularVelocity, localCenter - oldCenter); - FullDirty((uid, body)); Dirty(uid, body); } @@ -358,9 +339,7 @@ public partial class SharedPhysicsSystem body.AngularVelocity = value; if (dirty) - { Dirty(uid, body); - } return true; } @@ -388,9 +367,7 @@ public partial class SharedPhysicsSystem body.LinearVelocity = velocity; if (dirty) - { Dirty(uid, body); - } return true; } @@ -403,10 +380,7 @@ public partial class SharedPhysicsSystem body.AngularDamping = value; if (dirty) - { - FullDirty((uid, body)); Dirty(uid, body); - } } [Obsolete("Use overload that takes EntityUid")] @@ -423,10 +397,7 @@ public partial class SharedPhysicsSystem body.LinearDamping = value; if (dirty) - { - FullDirty((uid, body)); Dirty(uid, body); - } } [Obsolete("Use overload that takes EntityUid")] @@ -471,7 +442,7 @@ public partial class SharedPhysicsSystem // Update wake system last, if sleeping but still colliding. if (!value && body.CanCollide) - _wakeSystem.UpdateCanCollide(ent, checkTerminating: false); + _wakeSystem.UpdateCanCollide(ent, checkTerminating: false, dirty: false); if (updateSleepTime) SetSleepTime(body, 0); @@ -484,6 +455,7 @@ public partial class SharedPhysicsSystem } UpdateMapAwakeState(uid, body); + Dirty(ent); } public void TrySetBodyType(EntityUid uid, BodyType value, FixturesComponent? manager = null, PhysicsComponent? body = null, TransformComponent? xform = null) @@ -540,10 +512,7 @@ public partial class SharedPhysicsSystem body.BodyStatus = status; if (dirty) - { - FullDirty((uid, body)); Dirty(uid, body); - } } [Obsolete("Use overload that takes EntityUid")] @@ -602,10 +571,7 @@ public partial class SharedPhysicsSystem } if (dirty) - { - FullDirty((uid, body)); Dirty(uid, body); - } return value; } @@ -620,10 +586,7 @@ public partial class SharedPhysicsSystem ResetMassData(uid, manager: manager, body: body); if (dirty) - { - FullDirty((uid, body)); Dirty(uid, body); - } } public void SetFriction(EntityUid uid, PhysicsComponent body, float value, bool dirty = true) @@ -634,10 +597,7 @@ public partial class SharedPhysicsSystem body._friction = value; if (dirty) - { - FullDirty((uid, body)); Dirty(uid, body); - } } [Obsolete("Use overload that takes EntityUid")] @@ -661,10 +621,7 @@ public partial class SharedPhysicsSystem body.InvI = 1.0f / body._inertia; if (dirty) - { - FullDirty((uid, body)); Dirty(uid, body); - } } } @@ -694,10 +651,7 @@ public partial class SharedPhysicsSystem body.SleepingAllowed = value; if (dirty) - { - FullDirty((uid, body)); Dirty(uid, body); - } } public void SetSleepTime(PhysicsComponent body, float value) @@ -820,9 +774,4 @@ public partial class SharedPhysicsSystem { // See client-side system } - - private void FullDirty(Entity entity) - { - entity.Comp.FullUpdate = _gameTiming.CurTick; - } } From ec794ce4e4693069d3b3ebf7a88ead5ff2f860e0 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 17 May 2024 02:51:44 +0200 Subject: [PATCH 55/59] Version: 222.4.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index f9d62e428..9bf8e2f1d 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 222.3.0 + 222.4.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 8833a6342..195b2f23d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -* Added the following types from `System.Numerics` to the sandbox: `Complex`, `Matrix3x2`, `Matrix4x4`, `Plane`, `Quaternion`, `Vector3`, `Vector4`. +*None yet* ### Bugfixes @@ -54,6 +54,13 @@ END TEMPLATE--> *None yet* +## 222.4.0 + +### New features + +* Added the following types from `System.Numerics` to the sandbox: `Complex`, `Matrix3x2`, `Matrix4x4`, `Plane`, `Quaternion`, `Vector3`, `Vector4`. + + ## 222.3.0 ### New features From 12808d073eb1ab1cf4b361e31699734a29b0d707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B6=D0=B5=D0=BA=D1=81=D0=BE=D0=BD=20=D0=9C=D0=B8?= =?UTF-8?q?=D1=81=D1=81=D0=B8=D1=81=D1=81=D0=B8=D0=BF=D0=BF=D0=B8?= Date: Fri, 17 May 2024 00:15:17 -0500 Subject: [PATCH 56/59] Make CVar RenderFOVColor settable by server only (#5142) * | CVar.SERVER * Update CVars.cs --- Robust.Shared/CVars.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index a53f6a38e..2515e6592 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -899,7 +899,7 @@ namespace Robust.Shared CVarDef.Create("render.sprite_direction_bias", -0.05, CVar.ARCHIVE | CVar.CLIENTONLY); public static readonly CVarDef RenderFOVColor = - CVarDef.Create("render.fov_color", Color.Black.ToHex(), CVar.ARCHIVE | CVar.CLIENTONLY); + CVarDef.Create("render.fov_color", Color.Black.ToHex(), CVar.REPLICATED | CVar.SERVER); /* * CONTROLS From b1329d30bf079bb21dc9f02a56b1f5a56080eb2d Mon Sep 17 00:00:00 2001 From: Vasilis Date: Fri, 17 May 2024 08:15:30 +0300 Subject: [PATCH 57/59] Dont print watchdog token eletric boogaloo (#5143) --- Robust.Server/ServerStatus/WatchdogApi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Server/ServerStatus/WatchdogApi.cs b/Robust.Server/ServerStatus/WatchdogApi.cs index 3f85979d2..f10de9103 100644 --- a/Robust.Server/ServerStatus/WatchdogApi.cs +++ b/Robust.Server/ServerStatus/WatchdogApi.cs @@ -105,7 +105,7 @@ namespace Robust.Server.ServerStatus if (auth != _watchdogToken) { - _sawmill.Warning( + _sawmill.Verbose( "received POST /shutdown with invalid authentication token. Ignoring {0}, {1}", auth, _watchdogToken); await context.RespondErrorAsync(HttpStatusCode.Unauthorized); From da7abc65800ba2e85c0c72d57a515673165a7c16 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Fri, 17 May 2024 01:44:03 -0400 Subject: [PATCH 58/59] Add analyzer and fixer for redundant DataField tag arguments (#5134) * Add analyzer and fixer for redundant DataField tag arguments * Share Tag autogeneration logic --- Robust.Analyzers/DataDefinitionAnalyzer.cs | 46 +++++++++++- Robust.Analyzers/DataDefinitionFixer.cs | 72 +++++++++++++++++-- Robust.Analyzers/Robust.Analyzers.csproj | 5 ++ Robust.Roslyn.Shared/Diagnostics.cs | 1 + .../Manager/Definition/DataDefinition.cs | 3 +- .../Definition/DataDefinitionUtility.cs | 12 ++++ 6 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 Robust.Shared/Serialization/Manager/Definition/DataDefinitionUtility.cs diff --git a/Robust.Analyzers/DataDefinitionAnalyzer.cs b/Robust.Analyzers/DataDefinitionAnalyzer.cs index 14910c10a..f658a2aaa 100644 --- a/Robust.Analyzers/DataDefinitionAnalyzer.cs +++ b/Robust.Analyzers/DataDefinitionAnalyzer.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Robust.Roslyn.Shared; +using Robust.Shared.Serialization.Manager.Definition; namespace Robust.Analyzers; @@ -56,8 +57,18 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer "Make sure to add a setter." ); + private static readonly DiagnosticDescriptor DataFieldRedundantTagRule = new( + Diagnostics.IdDataFieldRedundantTag, + "Data field has redundant tag specified", + "Data field {0} in data definition {1} has an explicitly set tag that matches autogenerated tag", + "Usage", + DiagnosticSeverity.Info, + true, + "Make sure to remove the tag string from the data field attribute." + ); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( - DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule + DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule, + DataFieldRedundantTagRule ); public override void Initialize(AnalysisContext context) @@ -125,6 +136,11 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer { context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name)); } + + if (HasRedundantTag(fieldSymbol)) + { + context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name)); + } } } @@ -149,6 +165,11 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer { context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, context.Node.GetLocation(), propertySymbol.Name, type.Name)); } + + if (HasRedundantTag(propertySymbol)) + { + context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), propertySymbol.Name, type.Name)); + } } private static bool IsReadOnlyDataField(ITypeSymbol type, ISymbol field) @@ -248,6 +269,29 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer return false; } + private static bool HasRedundantTag(ISymbol symbol) + { + if (!IsDataField(symbol, out var _, out var attribute)) + return false; + + // No args, no problem + if (attribute.ConstructorArguments.Length == 0) + return false; + + // If a tag is explicitly specified, it will be the first argument... + var tagArgument = attribute.ConstructorArguments[0]; + // ...but the first arg could also something else, since tag is optional + // so we make sure that it's a string + if (tagArgument.Value is not string explicitName) + return false; + + // Get the name that sourcegen would provide + var automaticName = DataDefinitionUtility.AutoGenerateTag(symbol.Name); + + // If the explicit name matches the sourcegen name, we have a redundancy + return explicitName == automaticName; + } + private static bool IsImplicitDataDefinition(ITypeSymbol type) { if (HasAttribute(type, ImplicitDataDefinitionNamespace)) diff --git a/Robust.Analyzers/DataDefinitionFixer.cs b/Robust.Analyzers/DataDefinitionFixer.cs index 80ba22a96..3ec8795dd 100644 --- a/Robust.Analyzers/DataDefinitionFixer.cs +++ b/Robust.Analyzers/DataDefinitionFixer.cs @@ -1,8 +1,5 @@ #nullable enable using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; @@ -16,8 +13,11 @@ namespace Robust.Analyzers; [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class DefinitionFixer : CodeFixProvider { + private const string DataFieldAttributeName = "DataField"; + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create( - IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable + IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable, + IdDataFieldRedundantTag ); public override Task RegisterCodeFixesAsync(CodeFixContext context) @@ -34,6 +34,8 @@ public sealed class DefinitionFixer : CodeFixProvider return RegisterDataFieldFix(context, diagnostic); case IdDataFieldPropertyWritable: return RegisterDataFieldPropertyFix(context, diagnostic); + case IdDataFieldRedundantTag: + return RegisterRedundantTagFix(context, diagnostic); } } @@ -72,6 +74,68 @@ public sealed class DefinitionFixer : CodeFixProvider return document.WithSyntaxRoot(root); } + private static async Task RegisterRedundantTagFix(CodeFixContext context, Diagnostic diagnostic) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken); + var span = diagnostic.Location.SourceSpan; + var token = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType().First(); + + if (token == null) + return; + + // Find the DataField attribute + AttributeSyntax? dataFieldAttribute = null; + foreach (var attributeList in token.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + if (attribute.Name.ToString() == DataFieldAttributeName) + { + dataFieldAttribute = attribute; + break; + } + } + if (dataFieldAttribute != null) + break; + } + + if (dataFieldAttribute == null) + return; + + context.RegisterCodeFix(CodeAction.Create( + "Remove explicitly set tag", + c => RemoveRedundantTag(context.Document, dataFieldAttribute, c), + "Remove explicitly set tag" + ), diagnostic); + } + + private static async Task RemoveRedundantTag(Document document, AttributeSyntax syntax, CancellationToken cancellation) + { + var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation); + + if (syntax.ArgumentList == null) + return document; + + AttributeSyntax? newSyntax; + if (syntax.ArgumentList.Arguments.Count == 1) + { + // If this is the only argument, delete the ArgumentList so we don't leave empty parentheses + newSyntax = syntax.RemoveNode(syntax.ArgumentList, SyntaxRemoveOptions.KeepNoTrivia); + } + else + { + // Remove the first argument, which is the tag + var newArgs = syntax.ArgumentList.Arguments.RemoveAt(0); + var newArgList = syntax.ArgumentList.WithArguments(newArgs); + // Construct a new attribute with the tag removed + newSyntax = syntax.WithArgumentList(newArgList); + } + + root = root!.ReplaceNode(syntax, newSyntax!); + + return document.WithSyntaxRoot(root); + } + private static async Task RegisterDataFieldFix(CodeFixContext context, Diagnostic diagnostic) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken); diff --git a/Robust.Analyzers/Robust.Analyzers.csproj b/Robust.Analyzers/Robust.Analyzers.csproj index 8bbb58f0f..e2990ffbd 100644 --- a/Robust.Analyzers/Robust.Analyzers.csproj +++ b/Robust.Analyzers/Robust.Analyzers.csproj @@ -16,6 +16,11 @@ + + + + + diff --git a/Robust.Roslyn.Shared/Diagnostics.cs b/Robust.Roslyn.Shared/Diagnostics.cs index f1bd11192..03b20162c 100644 --- a/Robust.Roslyn.Shared/Diagnostics.cs +++ b/Robust.Roslyn.Shared/Diagnostics.cs @@ -30,6 +30,7 @@ public static class Diagnostics public const string IdComponentPauseWrongTypeAttribute = "RA0024"; public const string IdDependencyFieldAssigned = "RA0025"; public const string IdUncachedRegex = "RA0026"; + public const string IdDataFieldRedundantTag = "RA0027"; public static SuppressionDescriptor MeansImplicitAssignment => new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned."); diff --git a/Robust.Shared/Serialization/Manager/Definition/DataDefinition.cs b/Robust.Shared/Serialization/Manager/Definition/DataDefinition.cs index 2f68148e8..412091cbe 100644 --- a/Robust.Shared/Serialization/Manager/Definition/DataDefinition.cs +++ b/Robust.Shared/Serialization/Manager/Definition/DataDefinition.cs @@ -65,8 +65,7 @@ namespace Robust.Shared.Serialization.Manager.Definition continue; } - var name = field.FieldInfo.Name.AsSpan(); - attribute.Tag = $"{char.ToLowerInvariant(name[0])}{name[1..]}"; + attribute.Tag = DataDefinitionUtility.AutoGenerateTag(field.FieldInfo.Name); } var dataFields = fieldDefs diff --git a/Robust.Shared/Serialization/Manager/Definition/DataDefinitionUtility.cs b/Robust.Shared/Serialization/Manager/Definition/DataDefinitionUtility.cs new file mode 100644 index 000000000..1a6adb89c --- /dev/null +++ b/Robust.Shared/Serialization/Manager/Definition/DataDefinitionUtility.cs @@ -0,0 +1,12 @@ +using System; + +namespace Robust.Shared.Serialization.Manager.Definition; + +public class DataDefinitionUtility +{ + public static string AutoGenerateTag(string name) + { + var span = name.AsSpan(); + return $"{char.ToLowerInvariant(span[0])}{span.Slice(1).ToString()}"; + } +} From 85f74c3ba352612ea53dad4064aaf6251863ce3f Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 19 May 2024 12:47:04 +1200 Subject: [PATCH 59/59] Fix GetWorldViewbounds() and GetWorldViewport() (#5060) * Fix GetWorldViewbounds() and GetWorldViewport() * Remove some uses of `CurrentMap` and `CurrentEye` --- RELEASE-NOTES.md | 2 +- Robust.Client/Debugging/DebugPhysicsSystem.cs | 4 ++-- .../EntitySystems/DebugLightTreeSystem.cs | 2 +- .../GridChunkBoundsDebugSystem.cs | 2 +- Robust.Client/GameStates/NetInterpOverlay.cs | 2 +- .../Graphics/ClientEye/EyeManager.cs | 21 +++++++------------ .../Graphics/ClientEye/IEyeManager.cs | 18 +++++++++------- Robust.Client/Graphics/Clyde/Clyde.HLR.cs | 9 +++++--- .../Placement/Modes/AlignSnapgridCenter.cs | 8 +++---- Robust.Client/Placement/PlacementManager.cs | 12 +++++------ Robust.Client/Placement/PlacementMode.cs | 5 +++-- Robust.Client/Placement/PlacementOverlay.cs | 2 +- 12 files changed, 44 insertions(+), 43 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 195b2f23d..c8583bcf8 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* `IEyeManager.GetWorldViewbounds()` and `IEyeManager.GetWorldViewbounds()` should now return the correct bounds if the main viewport does not take up the whole screen. ### Other diff --git a/Robust.Client/Debugging/DebugPhysicsSystem.cs b/Robust.Client/Debugging/DebugPhysicsSystem.cs index bd668fc51..14f6684c4 100644 --- a/Robust.Client/Debugging/DebugPhysicsSystem.cs +++ b/Robust.Client/Debugging/DebugPhysicsSystem.cs @@ -225,7 +225,7 @@ namespace Robust.Client.Debugging { var viewBounds = args.WorldBounds; var viewAABB = args.WorldAABB; - var mapId = _eyeManager.CurrentMap; + var mapId = args.MapId; if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Shapes) != 0) { @@ -373,7 +373,7 @@ namespace Robust.Client.Debugging private void DrawScreen(DrawingHandleScreen screenHandle, OverlayDrawArgs args) { - var mapId = _eyeManager.CurrentMap; + var mapId = args.MapId; var mousePos = _inputManager.MouseScreenPosition; if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0) diff --git a/Robust.Client/GameObjects/EntitySystems/DebugLightTreeSystem.cs b/Robust.Client/GameObjects/EntitySystems/DebugLightTreeSystem.cs index beece4cb9..042ac0934 100644 --- a/Robust.Client/GameObjects/EntitySystems/DebugLightTreeSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/DebugLightTreeSystem.cs @@ -63,7 +63,7 @@ namespace Robust.Client.GameObjects protected internal override void Draw(in OverlayDrawArgs args) { - var map = _eyeManager.CurrentMap; + var map = args.MapId; if (map == MapId.Nullspace) return; foreach (var (_, treeComp) in _trees.GetIntersectingTrees(map, args.WorldBounds)) diff --git a/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs b/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs index b7e1e8b2d..36c780800 100644 --- a/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs @@ -70,7 +70,7 @@ namespace Robust.Client.GameObjects protected internal override void Draw(in OverlayDrawArgs args) { - var currentMap = _eyeManager.CurrentMap; + var currentMap = args.MapId; var viewport = args.WorldBounds; var worldHandle = args.WorldHandle; diff --git a/Robust.Client/GameStates/NetInterpOverlay.cs b/Robust.Client/GameStates/NetInterpOverlay.cs index c1c60ac8e..1dd3d7cef 100644 --- a/Robust.Client/GameStates/NetInterpOverlay.cs +++ b/Robust.Client/GameStates/NetInterpOverlay.cs @@ -49,7 +49,7 @@ namespace Robust.Client.GameStates while (query.MoveNext(out var uid, out var transform)) { // if not on the same map, continue - if (transform.MapID != _eyeManager.CurrentMap || _container.IsEntityInContainer(uid)) + if (transform.MapID != args.MapId || _container.IsEntityInContainer(uid)) continue; if (transform.GridUid == uid) diff --git a/Robust.Client/Graphics/ClientEye/EyeManager.cs b/Robust.Client/Graphics/ClientEye/EyeManager.cs index 0c8f3e82f..c9ab2574d 100644 --- a/Robust.Client/Graphics/ClientEye/EyeManager.cs +++ b/Robust.Client/Graphics/ClientEye/EyeManager.cs @@ -64,29 +64,22 @@ namespace Robust.Client.Graphics /// public Box2 GetWorldViewport() { - var vpSize = _displayManager.ScreenSize; - - var topLeft = ScreenToMap(Vector2.Zero); - var topRight = ScreenToMap(new Vector2(vpSize.X, 0)); - var bottomRight = ScreenToMap(vpSize); - var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y)); - - var left = MathHelper.Min(topLeft.X, topRight.X, bottomRight.X, bottomLeft.X); - var bottom = MathHelper.Min(topLeft.Y, topRight.Y, bottomRight.Y, bottomLeft.Y); - var right = MathHelper.Max(topLeft.X, topRight.X, bottomRight.X, bottomLeft.X); - var top = MathHelper.Max(topLeft.Y, topRight.Y, bottomRight.Y, bottomLeft.Y); - - return new Box2(left, bottom, right, top); + return GetWorldViewbounds().CalcBoundingBox(); } /// public Box2Rotated GetWorldViewbounds() { - var vpSize = _displayManager.ScreenSize; + // This is an inefficient and roundabout way of geting the viewport. + // But its a method that shouldn't get used much. + + var vp = MainViewport as Control; + var vpSize = vp?.PixelSize ?? _displayManager.ScreenSize; var topRight = ScreenToMap(new Vector2(vpSize.X, 0)).Position; var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y)).Position; + // This assumes the main viewports eye and the main eye are the same. var rotation = new Angle(CurrentEye.Rotation); var center = (bottomLeft + topRight) / 2; diff --git a/Robust.Client/Graphics/ClientEye/IEyeManager.cs b/Robust.Client/Graphics/ClientEye/IEyeManager.cs index 534757333..60b8cee30 100644 --- a/Robust.Client/Graphics/ClientEye/IEyeManager.cs +++ b/Robust.Client/Graphics/ClientEye/IEyeManager.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using System; +using System.Numerics; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.Graphics; using Robust.Shared.Map; @@ -13,26 +14,29 @@ namespace Robust.Client.Graphics public interface IEyeManager { /// - /// The current eye that is being used to render the game. + /// The primary eye, which is usually the eye associated with the main viewport. /// /// + /// Generally, you should avoid using this whenever possible. E.g., when rendering overlays should use the + /// eye & viewbounds that gets passed to the draw method. /// Setting this property to null will use the default eye. /// IEye CurrentEye { get; set; } IViewportControl MainViewport { get; set; } - /// - /// The ID of the map on which the current eye is "placed". - /// + [Obsolete] MapId CurrentMap { get; } /// - /// A world-space box that is at LEAST the area covered by the viewport. + /// A world-space box that is at LEAST the area covered by the main viewport. /// May be larger due to say rotation. /// Box2 GetWorldViewport(); + /// + /// A world-space box of the area visible in the main viewport. + /// Box2Rotated GetWorldViewbounds(); /// @@ -43,7 +47,7 @@ namespace Robust.Client.Graphics void GetScreenProjectionMatrix(out Matrix3 projMatrix); /// - /// Projects a point from world space to UI screen space using the current camera. + /// Projects a point from world space to UI screen space using the main viewport. /// /// Point in world to transform. /// Corresponding point in UI screen space. diff --git a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs index a4cc29756..57ed4b600 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs @@ -480,7 +480,7 @@ namespace Robust.Client.Graphics.Clyde var worldBounds = CalcWorldBounds(viewport); var worldAABB = worldBounds.CalcBoundingBox(); - if (_eyeManager.CurrentMap != MapId.Nullspace) + if (eye.Position.MapId != MapId.Nullspace) { using (DebugGroup("Lights")) using (_prof.Group("Lights")) @@ -544,9 +544,12 @@ namespace Robust.Client.Graphics.Clyde UIBox2.FromDimensions(Vector2.Zero, viewport.Size), new Color(1, 1, 1, 0.5f)); } - using (_prof.Group("Overlays WS")) + if (eye.Position.MapId != MapId.Nullspace) { - RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB, worldBounds); + using (_prof.Group("Overlays WS")) + { + RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB, worldBounds); + } } _currentViewport = oldVp; diff --git a/Robust.Client/Placement/Modes/AlignSnapgridCenter.cs b/Robust.Client/Placement/Modes/AlignSnapgridCenter.cs index 7c3266b53..f801192d3 100644 --- a/Robust.Client/Placement/Modes/AlignSnapgridCenter.cs +++ b/Robust.Client/Placement/Modes/AlignSnapgridCenter.cs @@ -19,7 +19,7 @@ namespace Robust.Client.Placement.Modes public SnapgridCenter(PlacementManager pMan) : base(pMan) { } - public override void Render(DrawingHandleWorld handle) + public override void Render(in OverlayDrawArgs args) { if (Grid != null) { @@ -34,18 +34,18 @@ namespace Robust.Client.Placement.Modes { var from = ScreenToWorld(new Vector2(a, 0)); var to = ScreenToWorld(new Vector2(a, viewportSize.Y)); - handle.DrawLine(from, to, new Color(0, 0, 1f)); + args.WorldHandle.DrawLine(from, to, new Color(0, 0, 1f)); } for (var a = gridstart.Y; a < viewportSize.Y; a += SnapSize * EyeManager.PixelsPerMeter) { var from = ScreenToWorld(new Vector2(0, a)); var to = ScreenToWorld(new Vector2(viewportSize.X, a)); - handle.DrawLine(from, to, new Color(0, 0, 1f)); + args.WorldHandle.DrawLine(from, to, new Color(0, 0, 1f)); } } // Draw grid BELOW the ghost thing. - base.Render(handle); + base.Render(args); } public override void AlignPlacementMode(ScreenCoordinates mouseScreen) diff --git a/Robust.Client/Placement/PlacementManager.cs b/Robust.Client/Placement/PlacementManager.cs index d28c90a1a..7a7d5257f 100644 --- a/Robust.Client/Placement/PlacementManager.cs +++ b/Robust.Client/Placement/PlacementManager.cs @@ -628,20 +628,20 @@ namespace Robust.Client.Placement return true; } - private void Render(DrawingHandleWorld handle) + private void Render(in OverlayDrawArgs args) { if (CurrentMode == null || !IsActive) { if (EraserRect.HasValue) { - handle.UseShader(_drawingShader); - handle.DrawRect(EraserRect.Value, new Color(255, 0, 0, 50)); - handle.UseShader(null); + args.WorldHandle.UseShader(_drawingShader); + args.WorldHandle.DrawRect(EraserRect.Value, new Color(255, 0, 0, 50)); + args.WorldHandle.UseShader(null); } return; } - CurrentMode.Render(handle); + CurrentMode.Render(args); if (CurrentPermission is not {Range: > 0} || !CurrentMode.RangeRequired || @@ -650,7 +650,7 @@ namespace Robust.Client.Placement var worldPos = EntityManager.GetComponent(controlled).WorldPosition; - handle.DrawCircle(worldPos, CurrentPermission.Range, new Color(1, 1, 1, 0.25f)); + args.WorldHandle.DrawCircle(worldPos, CurrentPermission.Range, new Color(1, 1, 1, 0.25f)); } private void HandleStartPlacement(MsgPlacement msg) diff --git a/Robust.Client/Placement/PlacementMode.cs b/Robust.Client/Placement/PlacementMode.cs index 5f8c9fd86..d29971d0c 100644 --- a/Robust.Client/Placement/PlacementMode.cs +++ b/Robust.Client/Placement/PlacementMode.cs @@ -88,7 +88,7 @@ namespace Robust.Client.Placement /// public abstract bool IsValidPosition(EntityCoordinates position); - public virtual void Render(DrawingHandleWorld handle) + public virtual void Render(in OverlayDrawArgs args) { var uid = pManager.CurrentPlacementOverlayEntity; if (!pManager.EntityManager.TryGetComponent(uid, out SpriteComponent? sprite) || !sprite.Visible) @@ -125,7 +125,8 @@ namespace Robust.Client.Placement var worldRot = pManager.EntityManager.GetComponent(coordinate.EntityId).WorldRotation + dirAng; sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor; - spriteSys.Render(uid.Value, sprite, handle, pManager.EyeManager.CurrentEye.Rotation, worldRot, worldPos); + var rot = args.Viewport.Eye?.Rotation ?? default; + spriteSys.Render(uid.Value, sprite, args.WorldHandle, rot, worldRot, worldPos); } } diff --git a/Robust.Client/Placement/PlacementOverlay.cs b/Robust.Client/Placement/PlacementOverlay.cs index 058c7a207..79463c356 100644 --- a/Robust.Client/Placement/PlacementOverlay.cs +++ b/Robust.Client/Placement/PlacementOverlay.cs @@ -19,7 +19,7 @@ namespace Robust.Client.Placement protected internal override void Draw(in OverlayDrawArgs args) { - _manager.Render(args.WorldHandle); + _manager.Render(args); } } }