From 60a29933d87ea68d811b779ff1a88e56c412d91b Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 7 Aug 2024 22:07:57 +1200 Subject: [PATCH 001/140] Try fix broadphase bug (#5342) * Try fix broadphase * I love initialization pasta --- RELEASE-NOTES.md | 2 +- .../GameObjects/Systems/EntityLookupSystem.cs | 82 +++++++++++++++++-- .../SharedTransformSystem.Component.cs | 2 +- 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index d561827b1d7..95d939974c5 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* Fixed a bug where the client might not add entities to the broadphase/lookup components. ### Other diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs index 155ae7dd046..985d957b1f7 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs @@ -122,6 +122,7 @@ public override void Initialize() SubscribeLocalEvent(OnBroadphaseTerminating); SubscribeLocalEvent(OnBroadphaseAdd); + SubscribeLocalEvent(OnBroadphaseInit); SubscribeLocalEvent(OnGridAdd); SubscribeLocalEvent(OnMapChange); @@ -206,12 +207,79 @@ private void OnGridAdd(GridAddEvent ev) EnsureComp(ev.EntityUid); } - private void OnBroadphaseAdd(EntityUid uid, BroadphaseComponent component, ComponentAdd args) + private void OnBroadphaseAdd(Entity broadphase, ref ComponentAdd args) { - component.StaticSundriesTree = new DynamicTree( - (in EntityUid value) => GetTreeAABB(value, uid)); - component.SundriesTree = new DynamicTree( - (in EntityUid value) => GetTreeAABB(value, uid)); + broadphase.Comp.StaticSundriesTree = new DynamicTree( + (in EntityUid value) => GetTreeAABB(value, broadphase.Owner)); + broadphase.Comp.SundriesTree = new DynamicTree( + (in EntityUid value) => GetTreeAABB(value, broadphase.Owner)); + } + + private void OnBroadphaseInit(Entity broadphase, ref ComponentInit args) + { + var xform = Transform(broadphase.Owner); + _transform.InitializeMapUid(broadphase.Owner, xform); + + if (!_mapQuery.TryGetComponent(xform.MapUid, out var physMap)) + { + throw new InvalidOperationException( + $"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadphase.Owner)}"); + } + + var ent = new Entity(broadphase, xform, broadphase); + var map = new Entity(xform.MapUid.Value, physMap); + var enumerator = xform.ChildEnumerator; + while (enumerator.MoveNext(out var child)) + { + if (!_broadQuery.HasComp(child)) + InitializeChild(child, ent, map); + } + } + + private void InitializeChild( + EntityUid child, + Entity broadphase, + Entity map) + { + if (LifeStage(child) <= EntityLifeStage.PreInit) + return; + + var xform = Transform(child); + + if (xform.Broadphase != null) + { + if (!xform.Broadphase.Value.IsValid()) + return; // Entity is intentionally not on a broadphase (deferred updating?). + + _mapQuery.TryGetComponent(xform.Broadphase.Value.PhysicsMap, out var oldPhysMap); + if (!_broadQuery.TryGetComponent(xform.Broadphase.Value.Uid, out var oldBroadphase)) + { + DebugTools.Assert("Encountered deleted broadphase."); + if (_fixturesQuery.TryGetComponent(child, out var fixtures)) + { + foreach (var fixture in fixtures.Fixtures.Values) + { + fixture.ProxyCount = 0; + fixture.Proxies = Array.Empty(); + } + } + + xform.Broadphase = null; + } + else if (oldBroadphase != broadphase.Comp2) + { + RemoveFromEntityTree(xform.Broadphase.Value.Uid, oldBroadphase, ref oldPhysMap, child, xform); + } + } + + DebugTools.Assert(xform.Broadphase is not {} x || x.Uid == broadphase.Owner && x.PhysicsMap == map.Owner); + AddOrUpdateEntityTree( + broadphase.Owner, + broadphase.Comp2, + broadphase.Comp1, + map.Comp, + child, + xform); } private Box2 GetTreeAABB(EntityUid entity, EntityUid tree) @@ -474,7 +542,7 @@ private void OnMove(ref MoveEvent args) DebugTools.Assert(!_mapManager.IsMap(args.Sender)); if (args.ParentChanged) - UpdateParent(args.Sender, args.Component, args.OldPosition.EntityId); + UpdateParent(args.Sender, args.Component); else UpdateEntityTree(args.Sender, args.Component); } @@ -552,7 +620,7 @@ private void RecursiveOnGridChangedMap( } } - private void UpdateParent(EntityUid uid, TransformComponent xform, EntityUid oldParent) + private void UpdateParent(EntityUid uid, TransformComponent xform) { BroadphaseComponent? oldBroadphase = null; PhysicsMapComponent? oldPhysMap = null; diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index 7b17eb24ab1..130d160e4c9 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -192,7 +192,7 @@ public bool IsParentOf(TransformComponent parent, EntityUid child) #region Component Lifetime - private (EntityUid?, MapId) InitializeMapUid(EntityUid uid, TransformComponent xform) + internal (EntityUid?, MapId) InitializeMapUid(EntityUid uid, TransformComponent xform) { if (xform._mapIdInitialized) return (xform.MapUid, xform.MapID); From 49c831b48d1449e90a65acdb0c276d2deea4ce2c Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Thu, 8 Aug 2024 12:15:46 +1000 Subject: [PATCH 002/140] Version: 229.1.2 --- 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 ae700295df5..703850f293f 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 229.1.1 + 229.1.2 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 95d939974c5..96642994e4b 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -* Fixed a bug where the client might not add entities to the broadphase/lookup components. +*None yet* ### Other @@ -54,6 +54,13 @@ END TEMPLATE--> *None yet* +## 229.1.2 + +### Bugfixes + +* Fixed a bug where the client might not add entities to the broadphase/lookup components. + + ## 229.1.1 ### Bugfixes From 87725f27c3f7fb089d44811a5493513beb8e63ad Mon Sep 17 00:00:00 2001 From: Repo <47093363+Titian3@users.noreply.github.com> Date: Fri, 9 Aug 2024 04:56:32 +1200 Subject: [PATCH 003/140] Add a copy to clipboard button on alert popups. (#5336) * Add a copy button to clipboard on Alert Popups. * ButtonFlag and better formatting. * Localization and style cleanup --- Resources/Locale/en-US/userinterface.ftl | 2 + .../UserInterface/IUserInterfaceManager.cs | 2 +- .../UserInterfaceManager.Layout.cs | 40 +++++++++++++++++-- .../UserInterface/UserInterfaceManager.cs | 1 + 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 Resources/Locale/en-US/userinterface.ftl diff --git a/Resources/Locale/en-US/userinterface.ftl b/Resources/Locale/en-US/userinterface.ftl new file mode 100644 index 00000000000..950d8a50626 --- /dev/null +++ b/Resources/Locale/en-US/userinterface.ftl @@ -0,0 +1,2 @@ +popup-copy-button = Copy +popup-title = Alert! diff --git a/Robust.Client/UserInterface/IUserInterfaceManager.cs b/Robust.Client/UserInterface/IUserInterfaceManager.cs index 6d83be28dc8..e1081d4e0e4 100644 --- a/Robust.Client/UserInterface/IUserInterfaceManager.cs +++ b/Robust.Client/UserInterface/IUserInterfaceManager.cs @@ -64,7 +64,7 @@ public partial interface IUserInterfaceManager IDebugMonitors DebugMonitors { get; } - void Popup(string contents, string title = "Alert!"); + void Popup(string contents, string? title = null, bool clipboardButton = true); Control? MouseGetControl(ScreenCoordinates coordinates); diff --git a/Robust.Client/UserInterface/UserInterfaceManager.Layout.cs b/Robust.Client/UserInterface/UserInterfaceManager.Layout.cs index 1fb4acb6d55..5697b2f3b1a 100644 --- a/Robust.Client/UserInterface/UserInterfaceManager.Layout.cs +++ b/Robust.Client/UserInterface/UserInterfaceManager.Layout.cs @@ -4,6 +4,7 @@ using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Maths; using Robust.Shared.Profiling; @@ -53,14 +54,47 @@ private void RunArrange(Control control) } } - public void Popup(string contents, string title = "Alert!") + public void Popup(string contents, string? title = null, bool clipboardButton = true) { var popup = new DefaultWindow { - Title = title + Title = string.IsNullOrEmpty(title) ? Loc.GetString("popup-title") : title, }; - popup.Contents.AddChild(new Label {Text = contents}); + var label = new Label { Text = contents }; + + var vBox = new BoxContainer + { + Orientation = BoxContainer.LayoutOrientation.Vertical, + }; + + vBox.AddChild(label); + + if (clipboardButton) + { + var copyButton = new Button + { + Text = Loc.GetString("popup-copy-button"), + HorizontalExpand = true, + }; + + copyButton.OnPressed += _ => + { + _clipboard.SetText(contents); + }; + + var hBox = new BoxContainer + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + HorizontalAlignment = Control.HAlignment.Right, + }; + + hBox.AddChild(copyButton); + vBox.AddChild(hBox); + } + + popup.Contents.AddChild(vBox); + popup.OpenCentered(); } diff --git a/Robust.Client/UserInterface/UserInterfaceManager.cs b/Robust.Client/UserInterface/UserInterfaceManager.cs index bb988f0aca2..fd4bc38e640 100644 --- a/Robust.Client/UserInterface/UserInterfaceManager.cs +++ b/Robust.Client/UserInterface/UserInterfaceManager.cs @@ -54,6 +54,7 @@ internal sealed partial class UserInterfaceManager : IUserInterfaceManagerIntern [Dependency] private readonly IEntitySystemManager _systemManager = default!; [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly IRuntimeLog _runtime = default!; + [Dependency] private readonly IClipboardManager _clipboard = null!; private IAudioSource? _clickSource; private IAudioSource? _hoverSource; From b503390837e9bc916748e1c714c56f395c092bff Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 9 Aug 2024 05:10:23 +1200 Subject: [PATCH 004/140] Add InterpolatedStringHandlerArgumentAttribute to sandbox whitelist (#5339) --- RELEASE-NOTES.md | 2 +- Robust.Shared/ContentPack/Sandbox.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 96642994e4b..1b89737a634 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* Added `InterpolatedStringHandlerArgumentAttribute` to the sandbox whitelist. ### Bugfixes diff --git a/Robust.Shared/ContentPack/Sandbox.yml b/Robust.Shared/ContentPack/Sandbox.yml index 3d04c1fb8ee..6d05df2c5f2 100644 --- a/Robust.Shared/ContentPack/Sandbox.yml +++ b/Robust.Shared/ContentPack/Sandbox.yml @@ -507,6 +507,7 @@ Types: IAsyncStateMachine: { All: True } InternalsVisibleToAttribute: { All: True } InterpolatedStringHandlerAttribute: { All: True } + InterpolatedStringHandlerArgumentAttribute: { All: True } IsByRefLikeAttribute: { All: True } IsExternalInit: { All: True } IsReadOnlyAttribute: { All: True } From 99e49104400537204cde027e4144b184541a3896 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Fri, 9 Aug 2024 21:12:44 -0400 Subject: [PATCH 005/140] Fix TimedDespawnComponent causing a crash if spawning another entity with TimedDespawnComponent (#5345) --- .../Spawners/SharedTimedDespawnSystem.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Robust.Shared/Spawners/SharedTimedDespawnSystem.cs b/Robust.Shared/Spawners/SharedTimedDespawnSystem.cs index fee8ae92287..5e8febd6d28 100644 --- a/Robust.Shared/Spawners/SharedTimedDespawnSystem.cs +++ b/Robust.Shared/Spawners/SharedTimedDespawnSystem.cs @@ -1,4 +1,4 @@ -using Robust.Shared.Audio; +using System.Collections.Generic; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Timing; @@ -9,6 +9,8 @@ public abstract class SharedTimedDespawnSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; + private readonly HashSet _queuedDespawnEntities = new(); + public override void Initialize() { base.Initialize(); @@ -24,6 +26,8 @@ public override void Update(float frameTime) if (!_timing.IsFirstTimePredicted) return; + _queuedDespawnEntities.Clear(); + var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp)) @@ -35,11 +39,16 @@ public override void Update(float frameTime) if (comp.Lifetime <= 0) { - var ev = new TimedDespawnEvent(); - RaiseLocalEvent(uid, ref ev); - QueueDel(uid); + _queuedDespawnEntities.Add(uid); } } + + foreach (var queued in _queuedDespawnEntities) + { + var ev = new TimedDespawnEvent(); + RaiseLocalEvent(queued, ref ev); + QueueDel(queued); + } } protected abstract bool CanDelete(EntityUid uid); From 672819d525c5393d1ee148a259465da825d06146 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Fri, 9 Aug 2024 18:15:02 -0700 Subject: [PATCH 006/140] Add missing return calls for positions and angles in SolveIsland (#5327) --- Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs index 15bb8f02f3e..b084b694589 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.ObjectPool; using Robust.Shared.GameObjects; -using Robust.Shared.Maths; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Dynamics.Contacts; @@ -960,6 +959,8 @@ private void SolveIsland( } // Cleanup + ArrayPool.Shared.Return(positions); + ArrayPool.Shared.Return(angles); ArrayPool.Shared.Return(velocityConstraints); ArrayPool.Shared.Return(positionConstraints); } @@ -985,7 +986,7 @@ private void FinalisePositions(int start, int end, int offset, List Date: Sun, 11 Aug 2024 16:21:54 +0200 Subject: [PATCH 007/140] Security updates (#5353) * Fix security bug in WritableDirProvider.OpenOsWindow() Reported by @NarryG and @nyeogmi * Sandbox updates * Update ImageSharp again --- Directory.Packages.props | 2 +- .../Commands/DumpMetadataMembersCommand.cs | 13 +- .../AssemblyTypeChecker.Parsing.cs | 2 +- Robust.Shared/ContentPack/Sandbox.yml | 140 +++++++++++++++++- .../ContentPack/WritableDirProvider.cs | 35 ++++- 5 files changed, 182 insertions(+), 10 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a709466be97..d5d38d21899 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -56,7 +56,7 @@ - + diff --git a/Robust.Client/Console/Commands/DumpMetadataMembersCommand.cs b/Robust.Client/Console/Commands/DumpMetadataMembersCommand.cs index 724e2d71d4c..c73e1ab2165 100644 --- a/Robust.Client/Console/Commands/DumpMetadataMembersCommand.cs +++ b/Robust.Client/Console/Commands/DumpMetadataMembersCommand.cs @@ -11,7 +11,7 @@ internal sealed class DumpMetadataMembersCommand : LocalizedCommands public override void Execute(IConsoleShell shell, string argStr, string[] args) { - var type = Type.GetType(args[0]); + var type = GetType(args[0]); if (type == null) { @@ -25,6 +25,17 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) shell.WriteLine(sig); } } + + private Type? GetType(string name) + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (assembly.GetType(name) is { } type) + return type; + } + + return null; + } } #endif } diff --git a/Robust.Shared/ContentPack/AssemblyTypeChecker.Parsing.cs b/Robust.Shared/ContentPack/AssemblyTypeChecker.Parsing.cs index 5a3bb49f12f..a92a76849a7 100644 --- a/Robust.Shared/ContentPack/AssemblyTypeChecker.Parsing.cs +++ b/Robust.Shared/ContentPack/AssemblyTypeChecker.Parsing.cs @@ -32,7 +32,7 @@ internal sealed partial class AssemblyTypeChecker String("short").ThenReturn(PrimitiveTypeCode.Int16); private static readonly Parser UInt16TypeParser = - String("ushort").ThenReturn(PrimitiveTypeCode.UInt32); + String("ushort").ThenReturn(PrimitiveTypeCode.UInt16); private static readonly Parser Int32TypeParser = String("int").ThenReturn(PrimitiveTypeCode.Int32); diff --git a/Robust.Shared/ContentPack/Sandbox.yml b/Robust.Shared/ContentPack/Sandbox.yml index 6d05df2c5f2..d68199310ab 100644 --- a/Robust.Shared/ContentPack/Sandbox.yml +++ b/Robust.Shared/ContentPack/Sandbox.yml @@ -84,12 +84,146 @@ Types: - "bool get_HasContents()" Lidgren.Network: NetBuffer: - All: True + Methods: + - "byte[] get_Data()" + - "void set_Data(byte[])" + - "int get_LengthBytes()" + - "void set_LengthBytes(int)" + - "int get_LengthBits()" + - "void set_LengthBits(int)" + - "long get_Position()" + - "void set_Position(long)" + - "int get_PositionInBytes()" + - "byte[] PeekDataBuffer()" + - "bool PeekBoolean()" + - "byte PeekByte()" + - "sbyte PeekSByte()" + - "byte PeekByte(int)" + - "System.Span`1 PeekBytes(System.Span`1)" + - "byte[] PeekBytes(int)" + - "void PeekBytes(byte[], int, int)" + - "short PeekInt16()" + - "ushort PeekUInt16()" + - "int PeekInt32()" + - "int PeekInt32(int)" + - "uint PeekUInt32()" + - "uint PeekUInt32(int)" + - "ulong PeekUInt64()" + - "long PeekInt64()" + - "ulong PeekUInt64(int)" + - "long PeekInt64(int)" + - "float PeekFloat()" + - "System.Half PeekHalf()" + - "float PeekSingle()" + - "double PeekDouble()" + - "string PeekString()" + - "int PeekStringSize()" + - "bool ReadBoolean()" + - "byte ReadByte()" + - "bool ReadByte(ref byte)" + - "sbyte ReadSByte()" + - "byte ReadByte(int)" + - "System.Span`1 ReadBytes(System.Span`1)" + - "byte[] ReadBytes(int)" + - "bool ReadBytes(int, ref byte[])" + - "bool TryReadBytes(System.Span`1)" + - "void ReadBytes(byte[], int, int)" + - "void ReadBits(System.Span`1, int)" + - "void ReadBits(byte[], int, int)" + - "short ReadInt16()" + - "ushort ReadUInt16()" + - "int ReadInt32()" + - "bool ReadInt32(ref int)" + - "int ReadInt32(int)" + - "uint ReadUInt32()" + - "bool ReadUInt32(ref uint)" + - "uint ReadUInt32(int)" + - "ulong ReadUInt64()" + - "long ReadInt64()" + - "ulong ReadUInt64(int)" + - "long ReadInt64(int)" + - "float ReadFloat()" + - "System.Half ReadHalf()" + - "float ReadSingle()" + - "bool ReadSingle(ref float)" + - "double ReadDouble()" + - "uint ReadVariableUInt32()" + - "bool ReadVariableUInt32(ref uint)" + - "int ReadVariableInt32()" + - "long ReadVariableInt64()" + - "ulong ReadVariableUInt64()" + - "float ReadSignedSingle(int)" + - "float ReadUnitSingle(int)" + - "float ReadRangedSingle(float, float, int)" + - "int ReadRangedInteger(int, int)" + - "long ReadRangedInteger(long, long)" + - "string ReadString()" + - "bool ReadString(ref string)" + - "double ReadTime(Lidgren.Network.NetConnection, bool)" + - "System.Net.IPEndPoint ReadIPEndPoint()" + - "void SkipPadBits()" + - "void ReadPadBits()" + - "void SkipPadBits(int)" + - "void EnsureBufferSize(int)" + - "void Write(bool)" + - "void Write(byte)" + - "void WriteAt(int, byte)" + - "void Write(sbyte)" + - "void Write(byte, int)" + - "void Write(byte[])" + - "void Write(System.ReadOnlySpan`1)" + - "void Write(byte[], int, int)" + - "void Write(ushort)" + - "void WriteAt(int, ushort)" + - "void Write(ushort, int)" + - "void Write(short)" + - "void WriteAt(int, short)" + - "void Write(int)" + - "void WriteAt(int, int)" + - "void Write(uint)" + - "void WriteAt(int, uint)" + - "void Write(uint, int)" + - "void Write(int, int)" + - "void Write(ulong)" + - "void WriteAt(int, ulong)" + - "void Write(ulong, int)" + - "void Write(long)" + - "void Write(long, int)" + - "void Write(System.Half)" + - "void Write(float)" + - "void Write(double)" + - "int WriteVariableUInt32(uint)" + - "int WriteVariableInt32(int)" + - "int WriteVariableInt64(long)" + - "int WriteVariableUInt64(ulong)" + - "void WriteSignedSingle(float, int)" + - "void WriteUnitSingle(float, int)" + - "void WriteRangedSingle(float, float, float, int)" + - "int WriteRangedInteger(int, int, int)" + - "int WriteRangedInteger(long, long, long)" + - "void Write(string)" + - "void Write(System.Net.IPEndPoint)" + - "void WriteTime(bool)" + - "void WriteTime(double, bool)" + - "void WritePadBits()" + - "void WritePadBits(int)" + - "void Write(Lidgren.Network.NetBuffer)" + - "void Zero(int)" + - "void .ctor()" NetDeliveryMethod: { } NetIncomingMessage: - All: True + Methods: + - "Lidgren.Network.NetIncomingMessageType get_MessageType()" + - "Lidgren.Network.NetDeliveryMethod get_DeliveryMethod()" + - "int get_SequenceChannel()" + - "System.Net.IPEndPoint get_SenderEndPoint()" + - "Lidgren.Network.NetConnection get_SenderConnection()" + - "double get_ReceiveTime()" + - "double ReadTime(bool)" + - "string ToString()" NetOutgoingMessage: - All: True + Methods: + - "string ToString()" Nett: CommentLocation: { } # Enum Toml: diff --git a/Robust.Shared/ContentPack/WritableDirProvider.cs b/Robust.Shared/ContentPack/WritableDirProvider.cs index af3d2a40aae..34161f9baa2 100644 --- a/Robust.Shared/ContentPack/WritableDirProvider.cs +++ b/Robust.Shared/ContentPack/WritableDirProvider.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Threading; using Robust.Shared.Utility; namespace Robust.Shared.ContentPack @@ -135,11 +136,37 @@ public void OpenOsWindow(ResPath path) path = path.Directory; var fullPath = GetFullPath(path); - Process.Start(new ProcessStartInfo + if (OperatingSystem.IsWindows()) { - UseShellExecute = true, - FileName = fullPath, - }); + Process.Start(new ProcessStartInfo + { + FileName = "explorer.exe", + Arguments = ".", + WorkingDirectory = fullPath, + }); + } + else if (OperatingSystem.IsMacOS()) + { + Process.Start(new ProcessStartInfo + { + FileName = "open", + Arguments = ".", + WorkingDirectory = fullPath, + }); + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD()) + { + Process.Start(new ProcessStartInfo + { + FileName = "xdg-open", + Arguments = ".", + WorkingDirectory = fullPath, + }); + } + else + { + throw new NotSupportedException("Opening OS windows not supported on this OS"); + } } #endregion From 5b5894e2d568005be2a4018f639f139768738a3f Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 11 Aug 2024 16:26:25 +0200 Subject: [PATCH 008/140] Release notes --- RELEASE-NOTES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 1b89737a634..9b962439a13 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -40,10 +40,13 @@ END TEMPLATE--> ### New features * Added `InterpolatedStringHandlerArgumentAttribute` to the sandbox whitelist. +* `IUserInterfaceManager.Popup()` popups now have a copy to clipboard button. ### Bugfixes -*None yet* +* Security fixes +* Fix exception in `TimedDespawnComponent` spawning another `TimedDespawnComponent`. +* Fixed pool memory leak in physics `SolveIsland`. ### Other From 85abcff5ea40e7b8143f6cd9a31066732610ae91 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 11 Aug 2024 16:26:32 +0200 Subject: [PATCH 009/140] Version: 223.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 703850f293f..ff64e132130 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 229.1.2 + 223.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 9b962439a13..6b834d75a5d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,14 +39,11 @@ END TEMPLATE--> ### New features -* Added `InterpolatedStringHandlerArgumentAttribute` to the sandbox whitelist. -* `IUserInterfaceManager.Popup()` popups now have a copy to clipboard button. +*None yet* ### Bugfixes -* Security fixes -* Fix exception in `TimedDespawnComponent` spawning another `TimedDespawnComponent`. -* Fixed pool memory leak in physics `SolveIsland`. +*None yet* ### Other @@ -57,6 +54,20 @@ END TEMPLATE--> *None yet* +## 223.0.0 + +### New features + +* Added `InterpolatedStringHandlerArgumentAttribute` to the sandbox whitelist. +* `IUserInterfaceManager.Popup()` popups now have a copy to clipboard button. + +### Bugfixes + +* Security fixes +* Fix exception in `TimedDespawnComponent` spawning another `TimedDespawnComponent`. +* Fixed pool memory leak in physics `SolveIsland`. + + ## 229.1.2 ### Bugfixes From 6599f9565ed161f0ac5895d5cb32e3381f99a770 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 11 Aug 2024 16:26:47 +0200 Subject: [PATCH 010/140] Version: 230.0.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 ff64e132130..c89a7ed877c 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 223.0.0 + 230.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 6b834d75a5d..0375d48e00b 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,9 @@ END TEMPLATE--> *None yet* +## 230.0.0 + + ## 223.0.0 ### New features From 2c3cc070a650df734115b74792a84e67a9a17798 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 11 Aug 2024 16:33:00 +0200 Subject: [PATCH 011/140] Fix oopsie from me using version.py on an existing version --- RELEASE-NOTES.md | 3 --- Tools/version.py | 8 ++++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 0375d48e00b..db5d0a0274d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -56,9 +56,6 @@ END TEMPLATE--> ## 230.0.0 - -## 223.0.0 - ### New features * Added `InterpolatedStringHandlerArgumentAttribute` to the sandbox whitelist. diff --git a/Tools/version.py b/Tools/version.py index 9d44a9c080f..a8e2d0c8f35 100755 --- a/Tools/version.py +++ b/Tools/version.py @@ -45,6 +45,10 @@ def write_version(version: str, file_only: bool): # Verify verify_version(version) + if tag_exists(version): + print(f"Version tag already exists: v{version}") + sys.exit(1) + update_release_notes(version) # Update @@ -111,4 +115,8 @@ def undo_version(version: str): subprocess.run(["git", "reset", "--keep", "HEAD^"], check=True) print("Done (deleted commit saved as " + savename + ")") +def tag_exists(version: str): + result = subprocess.run(["git", "tag", "-l", "v" + version], stdout=subprocess.PIPE, encoding="utf-8") + return bool(result.stdout.strip()) + main() From 0284eb04307014ac2f781ab91bb1caf59b7c92dd Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 11 Aug 2024 19:48:48 +0200 Subject: [PATCH 012/140] Use absolute path for explorer.exe frick me --- Robust.Shared/ContentPack/WritableDirProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Shared/ContentPack/WritableDirProvider.cs b/Robust.Shared/ContentPack/WritableDirProvider.cs index 34161f9baa2..7f053afa492 100644 --- a/Robust.Shared/ContentPack/WritableDirProvider.cs +++ b/Robust.Shared/ContentPack/WritableDirProvider.cs @@ -140,7 +140,7 @@ public void OpenOsWindow(ResPath path) { Process.Start(new ProcessStartInfo { - FileName = "explorer.exe", + FileName = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\explorer.exe", Arguments = ".", WorkingDirectory = fullPath, }); From 217870793771c008327fe0a7d3be965b2f4880c3 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 11 Aug 2024 19:49:23 +0200 Subject: [PATCH 013/140] Version: 230.0.1 --- 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 c89a7ed877c..c15c956dcc6 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 230.0.0 + 230.0.1 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index db5d0a0274d..a5c109905a3 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,9 @@ END TEMPLATE--> *None yet* +## 230.0.1 + + ## 230.0.0 ### New features From 9781405f5e176cf7829af751b45c32d9bf106528 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Tue, 13 Aug 2024 05:40:23 -0400 Subject: [PATCH 014/140] Better location reporting for DataField analyzers (#5344) * Better location reporting for DataField analyzers * Update test * Use const string in both methods --- .../DataDefinitionAnalyzerTest.cs | 4 +-- Robust.Analyzers/DataDefinitionAnalyzer.cs | 32 ++++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Robust.Analyzers.Tests/DataDefinitionAnalyzerTest.cs b/Robust.Analyzers.Tests/DataDefinitionAnalyzerTest.cs index 5882bd57d2d..ec16b0f2523 100644 --- a/Robust.Analyzers.Tests/DataDefinitionAnalyzerTest.cs +++ b/Robust.Analyzers.Tests/DataDefinitionAnalyzerTest.cs @@ -84,8 +84,8 @@ public sealed partial class Foo """; await Verifier(code, - // /0/Test0.cs(35,5): info RA0028: Data field Bad in data definition Foo has ViewVariables attribute with ReadWrite access, which is redundant - VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldNoVVReadWriteRule).WithSpan(35, 5, 36, 20).WithArguments("Bad", "Foo") + // /0/Test0.cs(35,17): info RA0028: Data field Bad in data definition Foo has ViewVariables attribute with ReadWrite access, which is redundant + VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldNoVVReadWriteRule).WithSpan(35, 17, 35, 50).WithArguments("Bad", "Foo") ); } } diff --git a/Robust.Analyzers/DataDefinitionAnalyzer.cs b/Robust.Analyzers/DataDefinitionAnalyzer.cs index 66303a88448..d005c42775d 100644 --- a/Robust.Analyzers/DataDefinitionAnalyzer.cs +++ b/Robust.Analyzers/DataDefinitionAnalyzer.cs @@ -18,6 +18,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer private const string ImplicitDataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.ImplicitDataDefinitionForInheritorsAttribute"; private const string DataFieldBaseNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataFieldBaseAttribute"; private const string ViewVariablesNamespace = "Robust.Shared.ViewVariables.ViewVariablesAttribute"; + private const string DataFieldAttributeName = "DataField"; + private const string ViewVariablesAttributeName = "ViewVariables"; private static readonly DiagnosticDescriptor DataDefinitionPartialRule = new( Diagnostics.IdDataDefinitionPartial, @@ -152,12 +154,14 @@ private void AnalyzeDataField(SyntaxNodeAnalysisContext context) if (HasRedundantTag(fieldSymbol)) { - context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name)); + TryGetAttributeLocation(field, DataFieldAttributeName, out var location); + context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, location, fieldSymbol.Name, type.Name)); } if (HasVVReadWrite(fieldSymbol)) { - context.ReportDiagnostic(Diagnostic.Create(DataFieldNoVVReadWriteRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name)); + TryGetAttributeLocation(field, ViewVariablesAttributeName, out var location); + context.ReportDiagnostic(Diagnostic.Create(DataFieldNoVVReadWriteRule, location, fieldSymbol.Name, type.Name)); } } } @@ -186,12 +190,14 @@ private void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context) if (HasRedundantTag(propertySymbol)) { - context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), propertySymbol.Name, type.Name)); + TryGetAttributeLocation(property, DataFieldAttributeName, out var location); + context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, location, propertySymbol.Name, type.Name)); } if (HasVVReadWrite(propertySymbol)) { - context.ReportDiagnostic(Diagnostic.Create(DataFieldNoVVReadWriteRule, context.Node.GetLocation(), propertySymbol.Name, type.Name)); + TryGetAttributeLocation(property, ViewVariablesAttributeName, out var location); + context.ReportDiagnostic(Diagnostic.Create(DataFieldNoVVReadWriteRule, location, propertySymbol.Name, type.Name)); } } @@ -261,6 +267,24 @@ private static bool Inherits(ITypeSymbol type, string parent) return false; } + private static bool TryGetAttributeLocation(MemberDeclarationSyntax syntax, string attributeName, out Location location) + { + foreach (var attributeList in syntax.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + if (attribute.Name.ToString() != attributeName) + continue; + + location = attribute.GetLocation(); + return true; + } + } + // Default to the declaration syntax's location + location = syntax.GetLocation(); + return false; + } + private static bool IsReadOnlyMember(ITypeSymbol type, ISymbol member) { if (member is IFieldSymbol field) From 59ed76c66fcb0b0e3f3987ff9e7feb57bda5a8cb Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 13 Aug 2024 11:45:40 +0200 Subject: [PATCH 015/140] Remove obsolete usages of *Variant prototype manager functions Replaced with *Kind --- Robust.Server/ViewVariables/ViewVariablesTrait.cs | 4 ++-- Robust.UnitTesting/Shared/Serialization/DataRecordTest.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Robust.Server/ViewVariables/ViewVariablesTrait.cs b/Robust.Server/ViewVariables/ViewVariablesTrait.cs index 2c65f4bdf3e..859b2c027fe 100644 --- a/Robust.Server/ViewVariables/ViewVariablesTrait.cs +++ b/Robust.Server/ViewVariables/ViewVariablesTrait.cs @@ -107,7 +107,7 @@ public virtual bool TryModifyProperty(object[] property, object value) // We don't blindly send any prototypes, we ONLY send prototypes for valid, registered variants. if (typeof(IPrototype).IsAssignableFrom(valType) - && IoCManager.Resolve().TryGetVariantFrom(valType, out var variant)) + && IoCManager.Resolve().TryGetKindFrom(valType, out var variant)) { return new ViewVariablesBlobMembers.PrototypeReferenceToken() { @@ -161,7 +161,7 @@ public virtual bool TryModifyProperty(object[] property, object value) { var protoMan = IoCManager.Resolve(); - if (protoMan.TryGetVariantFrom(type, out var variant)) + if (protoMan.TryGetKindFrom(type, out var variant)) return new ViewVariablesBlobMembers.PrototypeReferenceToken() { ID = null, Variant = variant, Stringified = PrettyPrint.PrintUserFacing(null), diff --git a/Robust.UnitTesting/Shared/Serialization/DataRecordTest.cs b/Robust.UnitTesting/Shared/Serialization/DataRecordTest.cs index 2f87994cf92..ce2180fc07c 100644 --- a/Robust.UnitTesting/Shared/Serialization/DataRecordTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/DataRecordTest.cs @@ -152,7 +152,7 @@ public void RegisterPrototypeTest() var prototypes = IoCManager.Resolve(); prototypes.Initialize(); - Assert.That(prototypes.HasVariant("emptyTestPrototypeRecord"), Is.True); + Assert.That(prototypes.HasKind("emptyTestPrototypeRecord"), Is.True); } [Test] From 4b12ff8574dc28841d8b8c700d8d118e6a257823 Mon Sep 17 00:00:00 2001 From: faint <46868845+ficcialfaint@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:52:09 +0300 Subject: [PATCH 016/140] fix loadprototype (#5359) --- Robust.Server/BaseServer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Robust.Server/BaseServer.cs b/Robust.Server/BaseServer.cs index 8bc2cf55f47..547d4e7c52c 100644 --- a/Robust.Server/BaseServer.cs +++ b/Robust.Server/BaseServer.cs @@ -104,6 +104,7 @@ internal sealed class BaseServer : IBaseServerInternal, IPostInjectInit [Dependency] private readonly IComponentFactory _componentFactory = default!; [Dependency] private readonly IReplayRecordingManagerInternal _replay = default!; [Dependency] private readonly IGamePrototypeLoadManager _protoLoadMan = default!; + [Dependency] private readonly UploadedContentManager _uploadedContMan = default!; [Dependency] private readonly NetworkResourceManager _netResMan = default!; [Dependency] private readonly IReflectionManager _refMan = default!; @@ -393,6 +394,7 @@ public bool Start(ServerOptions options, Func? logHandlerFactory = _scriptHost.Initialize(); _protoLoadMan.Initialize(); _netResMan.Initialize(); + _uploadedContMan.Initialize(); // String serializer has to be locked before PostInit as content can depend on it (e.g., replays that start // automatically recording on startup). From efa3e010a6afe52bf74e0b2d39dfeaff483da66a Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 15 Aug 2024 06:59:20 +1000 Subject: [PATCH 017/140] Add Flip method to SplitContainer (#5333) --- .../UserInterface/Controls/SplitContainer.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Robust.Client/UserInterface/Controls/SplitContainer.cs b/Robust.Client/UserInterface/Controls/SplitContainer.cs index b768facaaeb..25e92484f6d 100644 --- a/Robust.Client/UserInterface/Controls/SplitContainer.cs +++ b/Robust.Client/UserInterface/Controls/SplitContainer.cs @@ -191,6 +191,19 @@ public SplitContainer() _splitDragArea.OnMouseMove += OnMove; } + /// + /// Swaps the position of the first and zeroeth children; for a 2-control viewport it effectively flips them. + /// + public void Flip() + { + if (ChildCount < 3) + return; + + DebugTools.Assert(ChildCount <= 3); + GetChild(1).SetPositionFirst(); + InvalidateArrange(); + } + private void OnMove(GUIMouseMoveEventArgs args) { if (ResizeMode == SplitResizeMode.NotResizable) From 53516d6389df8ed6d1bfb5d47a4f1357e8439fb5 Mon Sep 17 00:00:00 2001 From: Stalen <33173619+stalengd@users.noreply.github.com> Date: Mon, 19 Aug 2024 01:55:04 +0300 Subject: [PATCH 018/140] Fixed client crash on devwindow inspect control's property which throws exception (#5370) * Fixed client crash on devwindow inspect control property which throws exception * Display inner exception only in case of TargetInvocationException --- Robust.Client/Console/Commands/Debug.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Robust.Client/Console/Commands/Debug.cs b/Robust.Client/Console/Commands/Debug.cs index af9c472ef91..b9d5a21d775 100644 --- a/Robust.Client/Console/Commands/Debug.cs +++ b/Robust.Client/Console/Commands/Debug.cs @@ -579,7 +579,20 @@ internal static string PropertyValuesString(Control control, string key) private static string GetMemberValue(MemberInfo? member, Control control, string separator, string wrap = "{0}") { - var value = member?.GetValue(control); + object? value = null; + try + { + value = member?.GetValue(control); + } + catch (TargetInvocationException exception) + { + var exceptionToPrint = exception.InnerException ?? exception; + value = $"{exceptionToPrint.GetType()}: {exceptionToPrint.Message}"; + } + catch (Exception exception) + { + value = $"{exception.GetType()}: {exception.Message}"; + } var o = value switch { ICollection controls => string.Join(separator, From cdb94748c8cebad7633f056d01bdf6520888a93c Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Tue, 20 Aug 2024 02:35:14 -0700 Subject: [PATCH 019/140] Make resetting contacts on the client only set is touching if it is true (#5372) --- Robust.Shared/Physics/Dynamics/Contacts/Contact.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs b/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs index 52ef03d0ef3..6bb2f8e8779 100644 --- a/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs +++ b/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs @@ -31,8 +31,6 @@ using System.Collections.Generic; using System.Numerics; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Maths; using Robust.Shared.Physics.Collision; using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Components; @@ -258,7 +256,9 @@ internal void UpdateIsTouching(Transform bodyATransform, Transform bodyBTransfor { var manifold = Manifold; Evaluate(ref manifold, bodyATransform, bodyBTransform); - IsTouching = manifold.PointCount > 0; + + if (IsTouching) + IsTouching = manifold.PointCount > 0; } } From 5f2881e3e4664e4804a3e09c9ba311ae1125828d Mon Sep 17 00:00:00 2001 From: c4llv07e Date: Tue, 20 Aug 2024 23:38:28 +0300 Subject: [PATCH 020/140] Add completion support for the change state command (#5368) --- Robust.Client/UserInterface/UiCommands.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Robust.Client/UserInterface/UiCommands.cs b/Robust.Client/UserInterface/UiCommands.cs index e12081b0f2a..db9fbc732db 100644 --- a/Robust.Client/UserInterface/UiCommands.cs +++ b/Robust.Client/UserInterface/UiCommands.cs @@ -1,3 +1,4 @@ +using System.Linq; using Robust.Client.State; using Robust.Shared.Console; using Robust.Shared.IoC; @@ -7,11 +8,12 @@ namespace Robust.Client.UserInterface { sealed class ChangeSceneCommpand : LocalizedCommands { + [Dependency] private readonly IReflectionManager _reflection = default!; + public override string Command => "scene"; public override void Execute(IConsoleShell shell, string argStr, string[] args) { - var reflection = IoCManager.Resolve(); - var types = reflection.GetAllChildren(typeof(State.State)); + var types = _reflection.GetAllChildren(typeof(State.State)); foreach (var tryType in types) { @@ -26,5 +28,13 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) shell.WriteError($"No scene child class type ends with {args[0]}"); } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length != 1) + return CompletionResult.Empty; + var types = _reflection.GetAllChildren(typeof(State.State)); + return CompletionResult.FromHintOptions(types.Select(x => x.Name), "[State name]"); + } } } From 046f7a2e555327ad2999d77b0ce17a1522cbfeea Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 21 Aug 2024 01:16:32 +0200 Subject: [PATCH 021/140] Add "Loc" property to LocalizedCommands Intended to match the localization manager in EntitySystem, much more convenient than the existing one. --- RELEASE-NOTES.md | 2 +- Robust.Shared/Console/LocalizedCommands.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index a5c109905a3..ccf6afc79de 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* `LocalizedCommands` now has a `Loc` property that is the same as its `LocalizationManager` field, for brevity. ### Bugfixes diff --git a/Robust.Shared/Console/LocalizedCommands.cs b/Robust.Shared/Console/LocalizedCommands.cs index be9119e4ffc..3cc9d5379f8 100644 --- a/Robust.Shared/Console/LocalizedCommands.cs +++ b/Robust.Shared/Console/LocalizedCommands.cs @@ -10,6 +10,8 @@ public abstract class LocalizedCommands : IConsoleCommand { [Dependency] protected readonly ILocalizationManager LocalizationManager = default!; + protected ILocalizationManager Loc => LocalizationManager; + /// public abstract string Command { get; } From 2f85b94ea26d524d72b34d319e60202069009b08 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 21 Aug 2024 01:23:25 +0200 Subject: [PATCH 022/140] Use new LocalizedCommands.Loc property in engine Also made BaseReplayCommand.Loc an explicit hide of the base property. --- Robust.Client/Console/ClientConsoleHost.cs | 4 ++-- Robust.Client/Replays/Commands/ReplayCommand.cs | 5 ++++- Robust.Client/Replays/Commands/ReplayLoadCommand.cs | 2 +- Robust.Shared/Audio/AudioDebugCommands.cs | 4 ++-- Robust.Shared/Console/Commands/VfsCommands.cs | 4 ++-- Robust.Shared/Console/LocalizedCommands.cs | 6 +++--- Robust.Shared/Replays/ReplayRecordingCommands.cs | 12 ++++++------ 7 files changed, 20 insertions(+), 17 deletions(-) diff --git a/Robust.Client/Console/ClientConsoleHost.cs b/Robust.Client/Console/ClientConsoleHost.cs index 5cac6c3a73f..01cdef3b02d 100644 --- a/Robust.Client/Console/ClientConsoleHost.cs +++ b/Robust.Client/Console/ClientConsoleHost.cs @@ -332,8 +332,8 @@ public async ValueTask GetCompletionAsync( private sealed class RemoteExecCommand : LocalizedCommands { public override string Command => ">"; - public override string Description => LocalizationManager.GetString("cmd-remoteexec-desc"); - public override string Help => LocalizationManager.GetString("cmd-remoteexec-help"); + public override string Description => Loc.GetString("cmd-remoteexec-desc"); + public override string Help => Loc.GetString("cmd-remoteexec-help"); public override void Execute(IConsoleShell shell, string argStr, string[] args) { diff --git a/Robust.Client/Replays/Commands/ReplayCommand.cs b/Robust.Client/Replays/Commands/ReplayCommand.cs index de1b83a0c73..f80e89e7c84 100644 --- a/Robust.Client/Replays/Commands/ReplayCommand.cs +++ b/Robust.Client/Replays/Commands/ReplayCommand.cs @@ -11,7 +11,10 @@ namespace Robust.Client.Replays.Commands; public abstract class BaseReplayCommand : LocalizedCommands { [Dependency] protected readonly IReplayPlaybackManager PlaybackManager = default!; - protected ILocalizationManager Loc => LocalizationManager; + + // This Loc property is identical to the one on the base type, but this one was here first. + // So we have to keep it for ABI compat. + protected new ILocalizationManager Loc => LocalizationManager; public override string Description => Loc.GetString($"cmd-{Command.Replace('_','-')}-desc"); diff --git a/Robust.Client/Replays/Commands/ReplayLoadCommand.cs b/Robust.Client/Replays/Commands/ReplayLoadCommand.cs index 6d5cace7ea5..c4d080410af 100644 --- a/Robust.Client/Replays/Commands/ReplayLoadCommand.cs +++ b/Robust.Client/Replays/Commands/ReplayLoadCommand.cs @@ -27,7 +27,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) { if (PlaybackManager.Replay != null) { - shell.WriteError(LocalizationManager.GetString("cmd-replay-error-already-loaded")); + shell.WriteError(Loc.GetString("cmd-replay-error-already-loaded")); return; } diff --git a/Robust.Shared/Audio/AudioDebugCommands.cs b/Robust.Shared/Audio/AudioDebugCommands.cs index d3e9992759b..cec7bcacc74 100644 --- a/Robust.Shared/Audio/AudioDebugCommands.cs +++ b/Robust.Shared/Audio/AudioDebugCommands.cs @@ -15,7 +15,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 1) { - shell.WriteError(LocalizationManager.GetString("cmd-invalid-arg-number-error")); + shell.WriteError(Loc.GetString("cmd-invalid-arg-number-error")); return; } @@ -28,7 +28,7 @@ public override CompletionResult GetCompletion(IConsoleShell shell, string[] arg { if (args.Length == 1) { - return CompletionResult.FromHint(LocalizationManager.GetString("cmd-audio_length-arg-file-name")); + return CompletionResult.FromHint(Loc.GetString("cmd-audio_length-arg-file-name")); } return CompletionResult.Empty; diff --git a/Robust.Shared/Console/Commands/VfsCommands.cs b/Robust.Shared/Console/Commands/VfsCommands.cs index 1f13edb9d4d..37f9d1b09ca 100644 --- a/Robust.Shared/Console/Commands/VfsCommands.cs +++ b/Robust.Shared/Console/Commands/VfsCommands.cs @@ -14,7 +14,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length > 1) { - shell.WriteError(LocalizationManager.GetString("cmd-vfs_ls-err-args")); + shell.WriteError(Loc.GetString("cmd-vfs_ls-err-args")); return; } @@ -30,7 +30,7 @@ public override CompletionResult GetCompletion(IConsoleShell shell, string[] arg if (args.Length == 1) { var opts = CompletionHelper.ContentDirPath(args[0], _resourceManager); - return CompletionResult.FromHintOptions(opts, LocalizationManager.GetString("cmd-vfs_ls-hint-path")); + return CompletionResult.FromHintOptions(opts, Loc.GetString("cmd-vfs_ls-hint-path")); } return CompletionResult.Empty; diff --git a/Robust.Shared/Console/LocalizedCommands.cs b/Robust.Shared/Console/LocalizedCommands.cs index 3cc9d5379f8..785c66daacc 100644 --- a/Robust.Shared/Console/LocalizedCommands.cs +++ b/Robust.Shared/Console/LocalizedCommands.cs @@ -10,16 +10,16 @@ public abstract class LocalizedCommands : IConsoleCommand { [Dependency] protected readonly ILocalizationManager LocalizationManager = default!; - protected ILocalizationManager Loc => LocalizationManager; + protected ILocalizationManager Loc => LocalizationManager; /// public abstract string Command { get; } /// - public virtual string Description => LocalizationManager.TryGetString($"cmd-{Command}-desc", out var val) ? val : ""; + public virtual string Description => Loc.TryGetString($"cmd-{Command}-desc", out var val) ? val : ""; /// - public virtual string Help => LocalizationManager.TryGetString($"cmd-{Command}-help", out var val) ? val : ""; + public virtual string Help => Loc.TryGetString($"cmd-{Command}-help", out var val) ? val : ""; /// public virtual bool RequireServerOrSingleplayer => false; diff --git a/Robust.Shared/Replays/ReplayRecordingCommands.cs b/Robust.Shared/Replays/ReplayRecordingCommands.cs index 516fbb92bbf..e89bd777b56 100644 --- a/Robust.Shared/Replays/ReplayRecordingCommands.cs +++ b/Robust.Shared/Replays/ReplayRecordingCommands.cs @@ -12,8 +12,8 @@ internal sealed class ReplayStartCommand : LocalizedCommands [Dependency] private readonly IResourceManager _resMan = default!; public override string Command => "replay_recording_start"; - public override string Description => LocalizationManager.GetString($"cmd-replay-recording-start-desc"); - public override string Help => LocalizationManager.GetString($"cmd-replay-recording-start-help"); + public override string Description => Loc.GetString($"cmd-replay-recording-start-desc"); + public override string Help => Loc.GetString($"cmd-replay-recording-start-help"); public override void Execute(IConsoleShell shell, string argStr, string[] args) { @@ -72,8 +72,8 @@ internal sealed class ReplayStopCommand : LocalizedCommands [Dependency] private readonly IReplayRecordingManager _replay = default!; public override string Command => "replay_recording_stop"; - public override string Description => LocalizationManager.GetString($"cmd-replay-recording-stop-desc"); - public override string Help => LocalizationManager.GetString($"cmd-replay-recording-stop-help"); + public override string Description => Loc.GetString($"cmd-replay-recording-stop-desc"); + public override string Help => Loc.GetString($"cmd-replay-recording-stop-help"); public override void Execute(IConsoleShell shell, string argStr, string[] args) { @@ -92,8 +92,8 @@ internal sealed class ReplayStatsCommand : LocalizedCommands [Dependency] private readonly IReplayRecordingManager _replay = default!; public override string Command => "replay_recording_stats"; - public override string Description => LocalizationManager.GetString($"cmd-replay-recording-stats-desc"); - public override string Help => LocalizationManager.GetString($"cmd-replay-recording-stats-help"); + public override string Description => Loc.GetString($"cmd-replay-recording-stats-desc"); + public override string Help => Loc.GetString($"cmd-replay-recording-stats-help"); public override void Execute(IConsoleShell shell, string argStr, string[] args) { From d584e51de64c09481851c0389b8a7636ca75049c Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 21 Aug 2024 02:03:16 +0200 Subject: [PATCH 023/140] Revert LocalizedCommands Loc change. This is what I get for ragecoding at 1 AM. --- RELEASE-NOTES.md | 2 +- Robust.Client/Console/ClientConsoleHost.cs | 4 ++-- Robust.Client/Replays/Commands/ReplayCommand.cs | 5 +---- Robust.Client/Replays/Commands/ReplayLoadCommand.cs | 2 +- Robust.Shared/Audio/AudioDebugCommands.cs | 4 ++-- Robust.Shared/Console/Commands/VfsCommands.cs | 4 ++-- Robust.Shared/Console/LocalizedCommands.cs | 6 ++---- Robust.Shared/Replays/ReplayRecordingCommands.cs | 12 ++++++------ 8 files changed, 17 insertions(+), 22 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index ccf6afc79de..a5c109905a3 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -* `LocalizedCommands` now has a `Loc` property that is the same as its `LocalizationManager` field, for brevity. +*None yet* ### Bugfixes diff --git a/Robust.Client/Console/ClientConsoleHost.cs b/Robust.Client/Console/ClientConsoleHost.cs index 01cdef3b02d..5cac6c3a73f 100644 --- a/Robust.Client/Console/ClientConsoleHost.cs +++ b/Robust.Client/Console/ClientConsoleHost.cs @@ -332,8 +332,8 @@ public async ValueTask GetCompletionAsync( private sealed class RemoteExecCommand : LocalizedCommands { public override string Command => ">"; - public override string Description => Loc.GetString("cmd-remoteexec-desc"); - public override string Help => Loc.GetString("cmd-remoteexec-help"); + public override string Description => LocalizationManager.GetString("cmd-remoteexec-desc"); + public override string Help => LocalizationManager.GetString("cmd-remoteexec-help"); public override void Execute(IConsoleShell shell, string argStr, string[] args) { diff --git a/Robust.Client/Replays/Commands/ReplayCommand.cs b/Robust.Client/Replays/Commands/ReplayCommand.cs index f80e89e7c84..de1b83a0c73 100644 --- a/Robust.Client/Replays/Commands/ReplayCommand.cs +++ b/Robust.Client/Replays/Commands/ReplayCommand.cs @@ -11,10 +11,7 @@ namespace Robust.Client.Replays.Commands; public abstract class BaseReplayCommand : LocalizedCommands { [Dependency] protected readonly IReplayPlaybackManager PlaybackManager = default!; - - // This Loc property is identical to the one on the base type, but this one was here first. - // So we have to keep it for ABI compat. - protected new ILocalizationManager Loc => LocalizationManager; + protected ILocalizationManager Loc => LocalizationManager; public override string Description => Loc.GetString($"cmd-{Command.Replace('_','-')}-desc"); diff --git a/Robust.Client/Replays/Commands/ReplayLoadCommand.cs b/Robust.Client/Replays/Commands/ReplayLoadCommand.cs index c4d080410af..6d5cace7ea5 100644 --- a/Robust.Client/Replays/Commands/ReplayLoadCommand.cs +++ b/Robust.Client/Replays/Commands/ReplayLoadCommand.cs @@ -27,7 +27,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) { if (PlaybackManager.Replay != null) { - shell.WriteError(Loc.GetString("cmd-replay-error-already-loaded")); + shell.WriteError(LocalizationManager.GetString("cmd-replay-error-already-loaded")); return; } diff --git a/Robust.Shared/Audio/AudioDebugCommands.cs b/Robust.Shared/Audio/AudioDebugCommands.cs index cec7bcacc74..d3e9992759b 100644 --- a/Robust.Shared/Audio/AudioDebugCommands.cs +++ b/Robust.Shared/Audio/AudioDebugCommands.cs @@ -15,7 +15,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 1) { - shell.WriteError(Loc.GetString("cmd-invalid-arg-number-error")); + shell.WriteError(LocalizationManager.GetString("cmd-invalid-arg-number-error")); return; } @@ -28,7 +28,7 @@ public override CompletionResult GetCompletion(IConsoleShell shell, string[] arg { if (args.Length == 1) { - return CompletionResult.FromHint(Loc.GetString("cmd-audio_length-arg-file-name")); + return CompletionResult.FromHint(LocalizationManager.GetString("cmd-audio_length-arg-file-name")); } return CompletionResult.Empty; diff --git a/Robust.Shared/Console/Commands/VfsCommands.cs b/Robust.Shared/Console/Commands/VfsCommands.cs index 37f9d1b09ca..1f13edb9d4d 100644 --- a/Robust.Shared/Console/Commands/VfsCommands.cs +++ b/Robust.Shared/Console/Commands/VfsCommands.cs @@ -14,7 +14,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length > 1) { - shell.WriteError(Loc.GetString("cmd-vfs_ls-err-args")); + shell.WriteError(LocalizationManager.GetString("cmd-vfs_ls-err-args")); return; } @@ -30,7 +30,7 @@ public override CompletionResult GetCompletion(IConsoleShell shell, string[] arg if (args.Length == 1) { var opts = CompletionHelper.ContentDirPath(args[0], _resourceManager); - return CompletionResult.FromHintOptions(opts, Loc.GetString("cmd-vfs_ls-hint-path")); + return CompletionResult.FromHintOptions(opts, LocalizationManager.GetString("cmd-vfs_ls-hint-path")); } return CompletionResult.Empty; diff --git a/Robust.Shared/Console/LocalizedCommands.cs b/Robust.Shared/Console/LocalizedCommands.cs index 785c66daacc..be9119e4ffc 100644 --- a/Robust.Shared/Console/LocalizedCommands.cs +++ b/Robust.Shared/Console/LocalizedCommands.cs @@ -10,16 +10,14 @@ public abstract class LocalizedCommands : IConsoleCommand { [Dependency] protected readonly ILocalizationManager LocalizationManager = default!; - protected ILocalizationManager Loc => LocalizationManager; - /// public abstract string Command { get; } /// - public virtual string Description => Loc.TryGetString($"cmd-{Command}-desc", out var val) ? val : ""; + public virtual string Description => LocalizationManager.TryGetString($"cmd-{Command}-desc", out var val) ? val : ""; /// - public virtual string Help => Loc.TryGetString($"cmd-{Command}-help", out var val) ? val : ""; + public virtual string Help => LocalizationManager.TryGetString($"cmd-{Command}-help", out var val) ? val : ""; /// public virtual bool RequireServerOrSingleplayer => false; diff --git a/Robust.Shared/Replays/ReplayRecordingCommands.cs b/Robust.Shared/Replays/ReplayRecordingCommands.cs index e89bd777b56..516fbb92bbf 100644 --- a/Robust.Shared/Replays/ReplayRecordingCommands.cs +++ b/Robust.Shared/Replays/ReplayRecordingCommands.cs @@ -12,8 +12,8 @@ internal sealed class ReplayStartCommand : LocalizedCommands [Dependency] private readonly IResourceManager _resMan = default!; public override string Command => "replay_recording_start"; - public override string Description => Loc.GetString($"cmd-replay-recording-start-desc"); - public override string Help => Loc.GetString($"cmd-replay-recording-start-help"); + public override string Description => LocalizationManager.GetString($"cmd-replay-recording-start-desc"); + public override string Help => LocalizationManager.GetString($"cmd-replay-recording-start-help"); public override void Execute(IConsoleShell shell, string argStr, string[] args) { @@ -72,8 +72,8 @@ internal sealed class ReplayStopCommand : LocalizedCommands [Dependency] private readonly IReplayRecordingManager _replay = default!; public override string Command => "replay_recording_stop"; - public override string Description => Loc.GetString($"cmd-replay-recording-stop-desc"); - public override string Help => Loc.GetString($"cmd-replay-recording-stop-help"); + public override string Description => LocalizationManager.GetString($"cmd-replay-recording-stop-desc"); + public override string Help => LocalizationManager.GetString($"cmd-replay-recording-stop-help"); public override void Execute(IConsoleShell shell, string argStr, string[] args) { @@ -92,8 +92,8 @@ internal sealed class ReplayStatsCommand : LocalizedCommands [Dependency] private readonly IReplayRecordingManager _replay = default!; public override string Command => "replay_recording_stats"; - public override string Description => Loc.GetString($"cmd-replay-recording-stats-desc"); - public override string Help => Loc.GetString($"cmd-replay-recording-stats-help"); + public override string Description => LocalizationManager.GetString($"cmd-replay-recording-stats-desc"); + public override string Help => LocalizationManager.GetString($"cmd-replay-recording-stats-help"); public override void Execute(IConsoleShell shell, string argStr, string[] args) { From d47d488ce70d3f9a20eab388dd40c7a119474ee2 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:39:23 +1000 Subject: [PATCH 024/140] Broadphase init fixes (#5367) 1 for replays 1 for loadmap. The replay one is kinda sussy but physicamap is supposed to get dumped at some point so. --- Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs index 985d957b1f7..d60a3ce0e9d 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs @@ -220,6 +220,11 @@ private void OnBroadphaseInit(Entity broadphase, ref Compon var xform = Transform(broadphase.Owner); _transform.InitializeMapUid(broadphase.Owner, xform); + // If in broadphase then skip this for now because no physicsmap to init physics entities properly + // This mainly happens in replays or otherwise spawning grids in nullspace. PhysicsMap is getting dumped in box2c anyway + if (xform.MapUid == null) + return; + if (!_mapQuery.TryGetComponent(xform.MapUid, out var physMap)) { throw new InvalidOperationException( @@ -272,7 +277,7 @@ private void InitializeChild( } } - DebugTools.Assert(xform.Broadphase is not {} x || x.Uid == broadphase.Owner && x.PhysicsMap == map.Owner); + DebugTools.Assert(xform.Broadphase is not {} x || x.Uid == broadphase.Owner && (!x.CanCollide || x.PhysicsMap == map.Owner)); AddOrUpdateEntityTree( broadphase.Owner, broadphase.Comp2, From 580dd5f1a6f3177711349a93314737edfc38704b Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:40:50 -0700 Subject: [PATCH 025/140] Fix RichTextLabel.Text property not setting _message if null (#5361) --- Robust.Client/UserInterface/Controls/RichTextLabel.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Robust.Client/UserInterface/Controls/RichTextLabel.cs b/Robust.Client/UserInterface/Controls/RichTextLabel.cs index 57fbb618152..69402fcb3a5 100644 --- a/Robust.Client/UserInterface/Controls/RichTextLabel.cs +++ b/Robust.Client/UserInterface/Controls/RichTextLabel.cs @@ -44,9 +44,12 @@ public string? Text set { if (value == null) + { _message?.Clear(); - else - _message?.AddMarkupPermissive(value); + return; + } + + SetMessage(FormattedMessage.FromMarkupPermissive(value)); } } From 0553600c9a8d88b03a49ba1673ecfbda3fcae07a Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:43:19 -0700 Subject: [PATCH 026/140] Add cvar to limit entities passed into nearby command (#5355) * Add cvar to limit entities passed into nearby command * Fix double enumeration --- Robust.Shared/CVars.cs | 7 +++++++ .../Commands/Entities/NearbyCommand.cs | 21 +++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index 5dccc6e8a3f..c781d1eabee 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -1788,5 +1788,12 @@ protected CVars() /// public static readonly CVarDef ToolshedNearbyLimit = CVarDef.Create("toolshed.nearby_limit", 200, CVar.SERVER | CVar.REPLICATED); + + /// + /// The max amount of entities that can be passed to the nearby toolshed command. + /// Any higher value will cause an exception. + /// + public static readonly CVarDef ToolshedNearbyEntitiesLimit = + CVarDef.Create("toolshed.nearby_entities_limit", 5, CVar.SERVER | CVar.REPLICATED); } } diff --git a/Robust.Shared/Toolshed/Commands/Entities/NearbyCommand.cs b/Robust.Shared/Toolshed/Commands/Entities/NearbyCommand.cs index 009f5e1c437..4c8aa4e3260 100644 --- a/Robust.Shared/Toolshed/Commands/Entities/NearbyCommand.cs +++ b/Robust.Shared/Toolshed/Commands/Entities/NearbyCommand.cs @@ -21,11 +21,24 @@ public IEnumerable Nearby( [CommandArgument] float range ) { - var limit = _cfg.GetCVar(CVars.ToolshedNearbyLimit); - if (range > limit) - throw new ArgumentException($"Tried to query too many entities with nearby ({range})! Limit: {limit}. Change the {CVars.ToolshedNearbyLimit.Name} cvar to increase this at your own risk."); + var rangeLimit = _cfg.GetCVar(CVars.ToolshedNearbyLimit); + if (range > rangeLimit) + throw new ArgumentException($"Tried to query too big of a range with nearby ({range})! Limit: {rangeLimit}. Change the {CVars.ToolshedNearbyLimit.Name} cvar to increase this at your own risk."); _lookup ??= GetSys(); - return input.SelectMany(x => _lookup.GetEntitiesInRange(x, range)).Distinct(); + + var i = 0; + var entitiesLimit = _cfg.GetCVar(CVars.ToolshedNearbyEntitiesLimit); + return input.SelectMany(x => + { + if (i++ > entitiesLimit) + { + throw new ArgumentException( + $"Too many entities were passed to nearby ({i})! Limit: {entitiesLimit}. Change the {CVars.ToolshedNearbyEntitiesLimit.Name} cvar to increase this at your own risk."); + } + + return _lookup.GetEntitiesInRange(x, range); + }) + .Distinct(); } } From d46885b96dcc81df76935bab032c66566a0482d2 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 21 Aug 2024 02:43:46 +0200 Subject: [PATCH 027/140] Fix LocalizedEntityCommands breaking content unit tests. (#5375) They are now not loaded inside content unit tests. Fixes #5374 --- RELEASE-NOTES.md | 2 +- Robust.Shared/Console/EntityConsoleHost.cs | 11 +++++++++++ Robust.UnitTesting/RobustUnitTest.cs | 5 +++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index a5c109905a3..052a0d15590 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* `LocalizedEntityCommands` are now not initialized inside `RobustUnitTest`, fixing guaranteed test failures. ### Other diff --git a/Robust.Shared/Console/EntityConsoleHost.cs b/Robust.Shared/Console/EntityConsoleHost.cs index ede5fd364a2..bea23aa2293 100644 --- a/Robust.Shared/Console/EntityConsoleHost.cs +++ b/Robust.Shared/Console/EntityConsoleHost.cs @@ -21,10 +21,21 @@ internal sealed class EntityConsoleHost private readonly HashSet _entityCommands = []; + /// + /// If disabled, don't automatically discover commands via reflection. + /// + /// + /// This gets disabled in certain unit tests. + /// + public bool DiscoverCommands { get; set; } = true; + public void Startup() { DebugTools.Assert(_entityCommands.Count == 0); + if (!DiscoverCommands) + return; + var deps = ((EntitySystemManager)_entitySystemManager).SystemDependencyCollection; _consoleHost.BeginRegistrationRegion(); diff --git a/Robust.UnitTesting/RobustUnitTest.cs b/Robust.UnitTesting/RobustUnitTest.cs index 6e75f666ce5..15df76fe6c3 100644 --- a/Robust.UnitTesting/RobustUnitTest.cs +++ b/Robust.UnitTesting/RobustUnitTest.cs @@ -10,6 +10,7 @@ using Robust.Server.Physics; using Robust.Shared.ComponentTrees; using Robust.Shared.Configuration; +using Robust.Shared.Console; using Robust.Shared.Containers; using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; @@ -165,6 +166,10 @@ public void BaseSetup() var entMan = deps.Resolve(); var mapMan = deps.Resolve(); + // Avoid discovering EntityCommands since they may depend on systems + // that aren't available in a unit test context. + deps.Resolve().DiscoverCommands = false; + // Required components for the engine to work // Why are we still here? Just to suffer? Why can't we just use [RegisterComponent] magic? // TODO End Suffering. From 5cb1901870b5912c4a73cc0c36710893fce16afe Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:52:41 +1000 Subject: [PATCH 028/140] Fix local tile enlargement (#5349) * Fix local tile enlargement Not used. * release notes --------- Co-authored-by: Pieter-Jan Briers --- RELEASE-NOTES.md | 1 + .../GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 052a0d15590..c09ddab0f6f 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,6 +43,7 @@ END TEMPLATE--> ### Bugfixes +* Fix tile enlargement not being applied for some EntityLookup queries. * `LocalizedEntityCommands` are now not initialized inside `RobustUnitTest`, fixing guaranteed test failures. ### Other diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs index fb2015de306..dd3b095a139 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs @@ -760,7 +760,7 @@ public void GetLocalEntitiesIntersecting( } var localAABB = GetLocalBounds(localTile, tileSize); - localAABB = localAABB.Enlarged(TileEnlargementRadius); + localAABB = localAABB.Enlarged(enlargement); GetLocalEntitiesIntersecting(gridUid, localAABB, intersecting, flags); } From 0d53c5e3296559b399e6c8e319d4520ab2790a95 Mon Sep 17 00:00:00 2001 From: qwerltaz <69696513+qwerltaz@users.noreply.github.com> Date: Wed, 21 Aug 2024 22:36:03 +0200 Subject: [PATCH 029/140] add bool[] support to shaders (#5373) * Add bool array support to shaders * better setUniform * less unsafe * stackalloc, less pointer --- .../Graphics/Clyde/Clyde.Rendering.cs | 3 ++ Robust.Client/Graphics/Clyde/Clyde.Shaders.cs | 8 +++++ Robust.Client/Graphics/Clyde/ClydeHeadless.cs | 4 +++ .../Clyde/GLObjects/Clyde.ShaderProgram.cs | 31 +++++++++++++++++++ .../Graphics/Shaders/ParsedShader.cs | 3 +- .../Graphics/Shaders/ShaderInstance.cs | 8 +++++ 6 files changed, 56 insertions(+), 1 deletion(-) diff --git a/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs b/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs index 778f3c9f9fa..af19a2c1a2e 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs @@ -496,6 +496,9 @@ private Color ConvertClearFromSrgb(Color color) case bool b: program.SetUniform(name, b ? 1 : 0); break; + case bool[] bArr: + program.SetUniform(name, bArr); + break; case Matrix3x2 matrix3: program.SetUniform(name, matrix3); break; diff --git a/Robust.Client/Graphics/Clyde/Clyde.Shaders.cs b/Robust.Client/Graphics/Clyde/Clyde.Shaders.cs index d9c09f52e87..148e5d11244 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Shaders.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Shaders.cs @@ -506,6 +506,14 @@ private protected override void SetParameterImpl(string name, bool value) data.Parameters[name] = value; } + private protected override void SetParameterImpl(string name, bool[] value) + { + var data = Parent._shaderInstances[Handle]; + data.ParametersDirty = true; + data.Parameters[name] = value; + } + + private protected override void SetParameterImpl(string name, in Matrix3x2 value) { var data = Parent._shaderInstances[Handle]; diff --git a/Robust.Client/Graphics/Clyde/ClydeHeadless.cs b/Robust.Client/Graphics/Clyde/ClydeHeadless.cs index 9c5c0b0a694..5754702641c 100644 --- a/Robust.Client/Graphics/Clyde/ClydeHeadless.cs +++ b/Robust.Client/Graphics/Clyde/ClydeHeadless.cs @@ -361,6 +361,10 @@ private protected override void SetParameterImpl(string name, bool value) { } + private protected override void SetParameterImpl(string name, bool[] value) + { + } + private protected override void SetParameterImpl(string name, in Matrix3x2 value) { } diff --git a/Robust.Client/Graphics/Clyde/GLObjects/Clyde.ShaderProgram.cs b/Robust.Client/Graphics/Clyde/GLObjects/Clyde.ShaderProgram.cs index c80d4a36add..ad75ded8fdd 100644 --- a/Robust.Client/Graphics/Clyde/GLObjects/Clyde.ShaderProgram.cs +++ b/Robust.Client/Graphics/Clyde/GLObjects/Clyde.ShaderProgram.cs @@ -418,6 +418,37 @@ private void SetUniformDirect(int slot, Vector2[] vectors) } } + public void SetUniform(string uniformName, bool[] bools) + { + var uniformId = GetUniform(uniformName); + SetUniformDirect(uniformId, bools); + } + + public void SetUniform(int uniformName, bool[] bools) + { + var uniformId = GetUniform(uniformName); + SetUniformDirect(uniformId, bools); + } + + private void SetUniformDirect(int slot, bool[] bools) + { + Span intBools = stackalloc int[bools.Length]; + + for (var i = 0; i < bools.Length; i++) + { + intBools[i] = bools[i] ? 1 : 0; + } + + unsafe + { + fixed (int* intBoolsPtr = intBools) + { + GL.Uniform1(slot, bools.Length, intBoolsPtr); + _clyde.CheckGlError(); + } + } + } + public void SetUniformTexture(string uniformName, TextureUnit textureUnit) { var uniformId = GetUniform(uniformName); diff --git a/Robust.Client/Graphics/Shaders/ParsedShader.cs b/Robust.Client/Graphics/Shaders/ParsedShader.cs index 76d3a64d226..6b275737c27 100644 --- a/Robust.Client/Graphics/Shaders/ParsedShader.cs +++ b/Robust.Client/Graphics/Shaders/ParsedShader.cs @@ -224,7 +224,8 @@ public static bool TypeSupportsArrays(this ShaderDataType type) // TODO: add support for int, and vec3/4 arrays return (type == ShaderDataType.Float) || - (type == ShaderDataType.Vec2); + (type == ShaderDataType.Vec2) || + (type == ShaderDataType.Bool); } [SuppressMessage("ReSharper", "StringLiteralTypo")] diff --git a/Robust.Client/Graphics/Shaders/ShaderInstance.cs b/Robust.Client/Graphics/Shaders/ShaderInstance.cs index 9c93986eb87..d6e612b8d5d 100644 --- a/Robust.Client/Graphics/Shaders/ShaderInstance.cs +++ b/Robust.Client/Graphics/Shaders/ShaderInstance.cs @@ -148,6 +148,13 @@ public void SetParameter(string name, bool value) SetParameterImpl(name, value); } + public void SetParameter(string name, bool[] value) + { + EnsureAlive(); + EnsureMutable(); + SetParameterImpl(name, value); + } + public void SetParameter(string name, in Matrix3x2 value) { EnsureAlive(); @@ -219,6 +226,7 @@ private void EnsureAlive() private protected abstract void SetParameterImpl(string name, int value); private protected abstract void SetParameterImpl(string name, Vector2i value); private protected abstract void SetParameterImpl(string name, bool value); + private protected abstract void SetParameterImpl(string name, bool[] value); private protected abstract void SetParameterImpl(string name, in Matrix3x2 value); private protected abstract void SetParameterImpl(string name, in Matrix4 value); private protected abstract void SetParameterImpl(string name, Texture value); From f03c0061298ccda874a41a32ae71d579e9b7e7b0 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 22 Aug 2024 01:49:43 +0200 Subject: [PATCH 030/140] Version: 230.1.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 26 ++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index c15c956dcc6..b6476951274 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 230.0.1 + 230.1.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index c09ddab0f6f..9a8498582a5 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,8 +43,7 @@ END TEMPLATE--> ### Bugfixes -* Fix tile enlargement not being applied for some EntityLookup queries. -* `LocalizedEntityCommands` are now not initialized inside `RobustUnitTest`, fixing guaranteed test failures. +*None yet* ### Other @@ -55,6 +54,29 @@ END TEMPLATE--> *None yet* +## 230.1.0 + +### New features + +* You can now pass `bool[]` parameters to shaders. +* Added `toolshed.nearby_entities_limit` CVar. +* Fix `RichTextLabel.Text` to clear and reset the message properly in all cases. +* `scene` command has tab completion now. +* `devwindow` UI inspector property catches exceptions for read properties. +* `SplitContainer.Flip()` + +### Bugfixes + +* Fix tile enlargement not being applied for some EntityLookup queries. +* `LocalizedEntityCommands` are now not initialized inside `RobustUnitTest`, fixing guaranteed test failures. +* Fixed issues with broadphase init breaking replays frequently. +* Fix uploaded prototypes and resources for clients connecting to a server. + +### Other + +* Improved error reporting for DataField analyzer. + + ## 230.0.1 From c14689f233e33eeb52a7cf3ee57c82d6e925d727 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:33:08 +1000 Subject: [PATCH 031/140] Add more directions for Vector2i (#5386) Convenient. --- Robust.Shared.Maths/Vector2i.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Robust.Shared.Maths/Vector2i.cs b/Robust.Shared.Maths/Vector2i.cs index b8ea4a96f34..ad11d2bfe42 100644 --- a/Robust.Shared.Maths/Vector2i.cs +++ b/Robust.Shared.Maths/Vector2i.cs @@ -27,6 +27,11 @@ public struct Vector2i : public static readonly Vector2i Left = (-1, 0); public static readonly Vector2i Right = (1, 0); + public static readonly Vector2i DownLeft = (-1, -1); + public static readonly Vector2i DownRight = (1, -1); + public static readonly Vector2i UpRight = (1, 1); + public static readonly Vector2i UpLeft = (-1, 1); + /// /// The X component of the Vector2i. /// From 1eb63cb616c04d2ded8a0b28666c6a5c7e851144 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:33:36 +1000 Subject: [PATCH 032/140] Stop inheriting IThreadPoolWorkitem (#5377) More annoying with internal changes. --- Robust.Shared/Threading/IRobustJob.cs | 3 ++- Robust.Shared/Threading/ParallelManager.cs | 11 +++++++++-- Robust.UnitTesting/TestingParallelManager.cs | 5 +++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Robust.Shared/Threading/IRobustJob.cs b/Robust.Shared/Threading/IRobustJob.cs index c84f8cb2c71..83565ac67b9 100644 --- a/Robust.Shared/Threading/IRobustJob.cs +++ b/Robust.Shared/Threading/IRobustJob.cs @@ -5,6 +5,7 @@ namespace Robust.Shared.Threading; /// /// Implement for code that needs to be runnable on a threadpool. /// -public interface IRobustJob : IThreadPoolWorkItem +public interface IRobustJob { + void Execute(); } diff --git a/Robust.Shared/Threading/ParallelManager.cs b/Robust.Shared/Threading/ParallelManager.cs index f461b22c305..8d87853e584 100644 --- a/Robust.Shared/Threading/ParallelManager.cs +++ b/Robust.Shared/Threading/ParallelManager.cs @@ -24,6 +24,8 @@ public interface IParallelManager /// WaitHandle Process(IRobustJob job); + public void ProcessNow(IRobustJob job); + /// /// Takes in a parallel job and runs it the specified amount. /// @@ -123,6 +125,11 @@ public WaitHandle Process(IRobustJob job) return subJob.Event.WaitHandle; } + public void ProcessNow(IRobustJob job) + { + job.Execute(); + } + /// public void ProcessNow(IParallelRobustJob job, int amount) { @@ -195,7 +202,7 @@ private ParallelTracker InternalProcess(IParallelRobustJob job, int amount) /// /// Runs an and handles cleanup. /// - private sealed class InternalJob : IRobustJob + private sealed class InternalJob : IRobustJob, IThreadPoolWorkItem { private ISawmill _sawmill = default!; private IRobustJob _robust = default!; @@ -231,7 +238,7 @@ public void Execute() /// /// Runs an and handles cleanup. /// - private sealed class InternalParallelJob : IRobustJob + private sealed class InternalParallelJob : IRobustJob, IThreadPoolWorkItem { private IParallelRobustJob _robust = default!; private int _start; diff --git a/Robust.UnitTesting/TestingParallelManager.cs b/Robust.UnitTesting/TestingParallelManager.cs index 0c2907f8ec9..2434514f25a 100644 --- a/Robust.UnitTesting/TestingParallelManager.cs +++ b/Robust.UnitTesting/TestingParallelManager.cs @@ -26,6 +26,11 @@ WaitHandle IParallelManager.Process(IRobustJob job) return ev.WaitHandle; } + public void ProcessNow(IRobustJob job) + { + job.Execute(); + } + /// public void ProcessNow(IParallelRobustJob jobs, int amount) { From 36a5b672e5f4d0c569a4a17f7f833d72dad2d16d Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Fri, 23 Aug 2024 14:36:00 +1000 Subject: [PATCH 033/140] Version: 230.2.0 --- 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 b6476951274..6413ee7d3b9 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 230.1.0 + 230.2.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 9a8498582a5..9f6a179530d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,18 @@ END TEMPLATE--> *None yet* +## 230.2.0 + +### New features + +* Add ProcessNow for IRobustJob as a convenience method where you may not want to run a job in the background sometimes. +* Add Vector2i helpers to all 8 neighbouring directions. + +### Other + +* Remove IThreadPoolWorkItem interface from IRobustJob. + + ## 230.1.0 ### New features From 140767c2627087cc0bec7b173fe3e2573289f558 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:29:57 +1000 Subject: [PATCH 034/140] Move viewsubscriber to shared + handle eye targets (#5362) * Move viewsubscriber to shared + handle eye targets Need this stuff for AI. * Fix dumb * review --- .../EntitySystems/ViewSubscriberSystem.cs | 5 + .../Components/Eye/ViewSubscriberComponent.cs | 12 -- .../EntitySystems/ViewSubscriberSystem.cs | 128 +++++++++--------- .../Components/Eye/ViewSubscriberComponent.cs | 11 ++ .../GameObjects/Systems/SharedEyeSystem.cs | 48 +++++++ .../Systems/SharedViewSubscriberSystem.cs | 18 +++ 6 files changed, 145 insertions(+), 77 deletions(-) create mode 100644 Robust.Client/GameObjects/EntitySystems/ViewSubscriberSystem.cs delete mode 100644 Robust.Server/GameObjects/Components/Eye/ViewSubscriberComponent.cs create mode 100644 Robust.Shared/GameObjects/Components/Eye/ViewSubscriberComponent.cs create mode 100644 Robust.Shared/GameObjects/Systems/SharedViewSubscriberSystem.cs diff --git a/Robust.Client/GameObjects/EntitySystems/ViewSubscriberSystem.cs b/Robust.Client/GameObjects/EntitySystems/ViewSubscriberSystem.cs new file mode 100644 index 00000000000..1e0d0d97d17 --- /dev/null +++ b/Robust.Client/GameObjects/EntitySystems/ViewSubscriberSystem.cs @@ -0,0 +1,5 @@ +using Robust.Shared.GameObjects; + +namespace Robust.Client.GameObjects; + +public sealed class ViewSubscriberSystem : SharedViewSubscriberSystem; diff --git a/Robust.Server/GameObjects/Components/Eye/ViewSubscriberComponent.cs b/Robust.Server/GameObjects/Components/Eye/ViewSubscriberComponent.cs deleted file mode 100644 index eb078fc7058..00000000000 --- a/Robust.Server/GameObjects/Components/Eye/ViewSubscriberComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using Robust.Shared.GameObjects; -using Robust.Shared.Player; - -namespace Robust.Server.GameObjects -{ - [RegisterComponent] - internal sealed partial class ViewSubscriberComponent : Component - { - internal readonly HashSet SubscribedSessions = new(); - } -} diff --git a/Robust.Server/GameObjects/EntitySystems/ViewSubscriberSystem.cs b/Robust.Server/GameObjects/EntitySystems/ViewSubscriberSystem.cs index 9784d02f30d..6dec7e75a00 100644 --- a/Robust.Server/GameObjects/EntitySystems/ViewSubscriberSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/ViewSubscriberSystem.cs @@ -1,89 +1,87 @@ using Robust.Shared.GameObjects; using Robust.Shared.Player; -namespace Robust.Server.GameObjects +namespace Robust.Server.GameObjects; + +/// +/// Entity System that handles subscribing and unsubscribing to PVS views. +/// +public sealed class ViewSubscriberSystem : SharedViewSubscriberSystem { + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnViewSubscriberShutdown); + } + /// - /// Entity System that handles subscribing and unsubscribing to PVS views. + /// Subscribes the session to get PVS updates from the point of view of the specified entity. /// - public sealed class ViewSubscriberSystem : EntitySystem + public override void AddViewSubscriber(EntityUid uid, ICommonSession session) { - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnViewSubscriberShutdown); - } - - /// - /// Subscribes the session to get PVS updates from the point of view of the specified entity. - /// - public void AddViewSubscriber(EntityUid uid, ICommonSession session) - { - // If the entity doesn't have the component, it will be added. - var viewSubscriber = EntityManager.EnsureComponent(uid); + // If the entity doesn't have the component, it will be added. + var viewSubscriber = EntityManager.EnsureComponent(uid); - if (viewSubscriber.SubscribedSessions.Contains(session)) - return; // Already subscribed, do nothing else. + if (viewSubscriber.SubscribedSessions.Contains(session)) + return; // Already subscribed, do nothing else. - viewSubscriber.SubscribedSessions.Add(session); - session.ViewSubscriptions.Add(uid); + viewSubscriber.SubscribedSessions.Add(session); + session.ViewSubscriptions.Add(uid); - RaiseLocalEvent(uid, new ViewSubscriberAddedEvent(uid, session), true); - } - - /// - /// Unsubscribes the session from getting PVS updates from the point of view of the specified entity. - /// - public void RemoveViewSubscriber(EntityUid uid, ICommonSession session) - { - if(!EntityManager.TryGetComponent(uid, out ViewSubscriberComponent? viewSubscriber)) - return; // Entity didn't have any subscriptions, do nothing. - - if (!viewSubscriber.SubscribedSessions.Remove(session)) - return; // Session wasn't subscribed, do nothing. - - session.ViewSubscriptions.Remove(uid); - RaiseLocalEvent(uid, new ViewSubscriberRemovedEvent(uid, session), true); - } - - private void OnViewSubscriberShutdown(EntityUid uid, ViewSubscriberComponent component, ComponentShutdown _) - { - foreach (var session in component.SubscribedSessions) - { - session.ViewSubscriptions.Remove(uid); - } - } + RaiseLocalEvent(uid, new ViewSubscriberAddedEvent(uid, session), true); } /// - /// Raised when a session subscribes to an entity's PVS view. + /// Unsubscribes the session from getting PVS updates from the point of view of the specified entity. /// - public sealed class ViewSubscriberAddedEvent : EntityEventArgs + public override void RemoveViewSubscriber(EntityUid uid, ICommonSession session) { - public EntityUid View { get; } - public ICommonSession Subscriber { get; } + if(!EntityManager.TryGetComponent(uid, out Shared.GameObjects.ViewSubscriberComponent? viewSubscriber)) + return; // Entity didn't have any subscriptions, do nothing. + + if (!viewSubscriber.SubscribedSessions.Remove(session)) + return; // Session wasn't subscribed, do nothing. - public ViewSubscriberAddedEvent(EntityUid view, ICommonSession subscriber) + session.ViewSubscriptions.Remove(uid); + RaiseLocalEvent(uid, new ViewSubscriberRemovedEvent(uid, session), true); + } + + private void OnViewSubscriberShutdown(EntityUid uid, ViewSubscriberComponent component, ComponentShutdown _) + { + foreach (var session in component.SubscribedSessions) { - View = view; - Subscriber = subscriber; + session.ViewSubscriptions.Remove(uid); } } +} - /// - /// Raised when a session is unsubscribed from an entity's PVS view. - /// Not raised when sessions are unsubscribed due to the component being removed. - /// - public sealed class ViewSubscriberRemovedEvent : EntityEventArgs +/// +/// Raised when a session subscribes to an entity's PVS view. +/// +public sealed class ViewSubscriberAddedEvent : EntityEventArgs +{ + public EntityUid View { get; } + public ICommonSession Subscriber { get; } + + public ViewSubscriberAddedEvent(EntityUid view, ICommonSession subscriber) { - public EntityUid View { get; } - public ICommonSession Subscriber { get; } + View = view; + Subscriber = subscriber; + } +} - public ViewSubscriberRemovedEvent(EntityUid view, ICommonSession subscriber) - { - View = view; - Subscriber = subscriber; - } +/// +/// Raised when a session is unsubscribed from an entity's PVS view. +/// Not raised when sessions are unsubscribed due to the component being removed. +/// +public sealed class ViewSubscriberRemovedEvent : EntityEventArgs +{ + public EntityUid View { get; } + public ICommonSession Subscriber { get; } + + public ViewSubscriberRemovedEvent(EntityUid view, ICommonSession subscriber) + { + View = view; + Subscriber = subscriber; } } diff --git a/Robust.Shared/GameObjects/Components/Eye/ViewSubscriberComponent.cs b/Robust.Shared/GameObjects/Components/Eye/ViewSubscriberComponent.cs new file mode 100644 index 00000000000..2c732853d94 --- /dev/null +++ b/Robust.Shared/GameObjects/Components/Eye/ViewSubscriberComponent.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Robust.Shared.Player; + +namespace Robust.Shared.GameObjects; + +// Not networked because doesn't do anything on client. +[RegisterComponent] +internal sealed partial class ViewSubscriberComponent : Component +{ + internal readonly HashSet SubscribedSessions = new(); +} diff --git a/Robust.Shared/GameObjects/Systems/SharedEyeSystem.cs b/Robust.Shared/GameObjects/Systems/SharedEyeSystem.cs index 039201ca8dc..2091e271485 100644 --- a/Robust.Shared/GameObjects/Systems/SharedEyeSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedEyeSystem.cs @@ -2,11 +2,41 @@ using System.Numerics; using Robust.Shared.IoC; using Robust.Shared.Maths; +using Robust.Shared.Player; namespace Robust.Shared.GameObjects; public abstract class SharedEyeSystem : EntitySystem { + [Dependency] private readonly SharedViewSubscriberSystem _views = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnEyePlayerAttached); + SubscribeLocalEvent(OnEyePlayerDetached); + } + + private void OnEyePlayerAttached(Entity ent, ref PlayerAttachedEvent args) + { + var value = ent.Comp.Target; + + if (value != null && TryComp(ent.Owner, out ActorComponent? actorComp)) + { + _views.AddViewSubscriber(value.Value, actorComp.PlayerSession); + } + } + + private void OnEyePlayerDetached(Entity ent, ref PlayerDetachedEvent args) + { + var value = ent.Comp.Target; + + if (value != null && TryComp(ent.Owner, out ActorComponent? actorComp)) + { + _views.RemoveViewSubscriber(value.Value, actorComp.PlayerSession); + } + } + /// /// Refreshes all values for IEye with the component. /// @@ -74,6 +104,10 @@ public void SetRotation(EntityUid uid, Angle rotation, EyeComponent? eyeComponen eyeComponent.Eye.Rotation = rotation; } + /// + /// Sets the eye component as tracking another entity. + /// Will also add the target to view subscribers so they can leave range and still work with PVS. + /// public void SetTarget(EntityUid uid, EntityUid? value, EyeComponent? eyeComponent = null) { if (!Resolve(uid, ref eyeComponent)) @@ -82,6 +116,20 @@ public void SetTarget(EntityUid uid, EntityUid? value, EyeComponent? eyeComponen if (eyeComponent.Target.Equals(value)) return; + // Automatically handle view subs. + if (TryComp(uid, out ActorComponent? actorComp)) + { + if (value != null) + { + _views.AddViewSubscriber(value.Value, actorComp.PlayerSession); + } + else + { + // Should never be null here + _views.RemoveViewSubscriber(eyeComponent.Target!.Value, actorComp.PlayerSession); + } + } + eyeComponent.Target = value; Dirty(uid, eyeComponent); } diff --git a/Robust.Shared/GameObjects/Systems/SharedViewSubscriberSystem.cs b/Robust.Shared/GameObjects/Systems/SharedViewSubscriberSystem.cs new file mode 100644 index 00000000000..77828075a59 --- /dev/null +++ b/Robust.Shared/GameObjects/Systems/SharedViewSubscriberSystem.cs @@ -0,0 +1,18 @@ +using Robust.Shared.Player; + +namespace Robust.Shared.GameObjects; + +public abstract class SharedViewSubscriberSystem : EntitySystem +{ + // NOOP on client + + /// + /// Subscribes the session to get PVS updates from the point of view of the specified entity. + /// + public virtual void AddViewSubscriber(EntityUid uid, ICommonSession session) {} + + /// + /// Unsubscribes the session from getting PVS updates from the point of view of the specified entity. + /// + public virtual void RemoveViewSubscriber(EntityUid uid, ICommonSession session) {} +} From 0bc3c517070ba81ae88e4249d520f4d850c0ebc1 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 25 Aug 2024 11:54:05 +1000 Subject: [PATCH 035/140] Contact QOL stuff (#5385) * Contact QOL stuff Only just made the enumerator version need to test with AI branch had an IEnumerable before. * Fix --- .../Physics/Dynamics/Contacts/Contact.cs | 23 +++++ .../Physics/Systems/FixtureSystem.cs | 1 + .../Physics/Systems/SharedBroadphaseSystem.cs | 2 +- .../Systems/SharedPhysicsSystem.Contacts.cs | 93 +++++++++++++++++++ 4 files changed, 118 insertions(+), 1 deletion(-) diff --git a/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs b/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs index 6bb2f8e8779..e588fc93952 100644 --- a/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs +++ b/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs @@ -349,6 +349,29 @@ public override int GetHashCode() // TODO: Need to suss this out return HashCode.Combine(EntityA, EntityB); } + + /// + /// Gets the other ent for this contact. + /// + public EntityUid OtherEnt(EntityUid uid) + { + if (uid == EntityA) + return EntityB; + else if (uid == EntityB) + return EntityA; + + throw new InvalidOperationException(); + } + + public (string Id, Fixture) OtherFixture(EntityUid uid) + { + if (uid == EntityA) + return (FixtureBId, FixtureB!); + else if (uid == EntityB) + return (FixtureAId, FixtureA!); + + throw new InvalidOperationException(); + } } [Flags] diff --git a/Robust.Shared/Physics/Systems/FixtureSystem.cs b/Robust.Shared/Physics/Systems/FixtureSystem.cs index c07c6831f08..ac22c96cc6a 100644 --- a/Robust.Shared/Physics/Systems/FixtureSystem.cs +++ b/Robust.Shared/Physics/Systems/FixtureSystem.cs @@ -116,6 +116,7 @@ internal void CreateFixture( // Don't need to ResetMassData as FixtureUpdate already does it. Dirty(uid, manager); } + // TODO: Set newcontacts to true. } diff --git a/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs b/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs index ba1a80e029c..c57b0fe1a30 100644 --- a/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs @@ -416,7 +416,7 @@ public void RegenerateContacts(EntityUid uid, PhysicsComponent body, FixturesCom } } - private void TouchProxies(EntityUid mapId, Matrix3x2 broadphaseMatrix, Fixture fixture) + internal void TouchProxies(EntityUid mapId, Matrix3x2 broadphaseMatrix, Fixture fixture) { foreach (var proxy in fixture.Proxies) { diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Contacts.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Contacts.cs index e98839a50c6..f7cee57fa47 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Contacts.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Contacts.cs @@ -30,7 +30,10 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Numerics; +using JetBrains.Annotations; using Microsoft.Extensions.ObjectPool; using Robust.Shared.GameObjects; using Robust.Shared.Maths; @@ -741,6 +744,96 @@ protected bool ShouldCollide( return true; } + + /// + /// Will destroy all contacts and queue for rebuild. + /// Useful if you have one that may no longer be relevant and don't want to destroy it directly. + /// + public void RegenerateContacts(Entity entity) + { + if (!PhysicsQuery.Resolve(entity.Owner, ref entity.Comp)) + return; + + _broadphase.RegenerateContacts(entity.Owner, entity.Comp); + } + + /// + /// Returns the number of touching contacts this entity has. + /// + /// Fixture we should ignore if applicable + [Pure] + public int GetTouchingContacts(Entity entity, string? ignoredFixtureId = null) + { + if (!_fixturesQuery.Resolve(entity.Owner, ref entity.Comp)) + return 0; + + var count = 0; + + foreach (var (id, fixture) in entity.Comp.Fixtures) + { + if (ignoredFixtureId == id) + continue; + + foreach (var contact in fixture.Contacts.Values) + { + if (!contact.IsTouching) + continue; + + count++; + } + } + + return count; + } + + /// + /// Returns all of this entity's contacts. + /// + [Pure] + public ContactEnumerator GetContacts(Entity entity) + { + _fixturesQuery.Resolve(entity.Owner, ref entity.Comp); + return new ContactEnumerator(entity.Comp); + } +} + +public record struct ContactEnumerator +{ + public static readonly ContactEnumerator Empty = new(null); + + private Dictionary.ValueCollection.Enumerator _fixtureEnumerator; + private Dictionary.ValueCollection.Enumerator _contactEnumerator; + + public ContactEnumerator(FixturesComponent? fixtures) + { + if (fixtures == null || fixtures.Fixtures.Count == 0) + { + this = Empty; + return; + } + + _fixtureEnumerator = fixtures.Fixtures.Values.GetEnumerator(); + _fixtureEnumerator.MoveNext(); + _contactEnumerator = _fixtureEnumerator.Current.Contacts.Values.GetEnumerator(); + } + + public bool MoveNext([NotNullWhen(true)] out Contact? contact) + { + if (!_contactEnumerator.MoveNext()) + { + if (!_fixtureEnumerator.MoveNext()) + { + contact = null; + return false; + } + + _contactEnumerator = _fixtureEnumerator.Current.Contacts.Values.GetEnumerator(); + return MoveNext(out contact); + } + + contact = _contactEnumerator.Current; + return true; + } } internal enum ContactStatus : byte From 679c31199d1b8086f51c9e661c124fd1cd96b44a Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:02:31 +1000 Subject: [PATCH 036/140] Add comments to sermanager attributes (#5384) I always forget which is which. --- .../Attributes/AlwaysPushInheritanceAttribute.cs | 3 +++ .../Manager/Attributes/IncludeDataFieldAttribute.cs | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/Robust.Shared/Serialization/Manager/Attributes/AlwaysPushInheritanceAttribute.cs b/Robust.Shared/Serialization/Manager/Attributes/AlwaysPushInheritanceAttribute.cs index b75abf69bff..04cd13e1f0b 100644 --- a/Robust.Shared/Serialization/Manager/Attributes/AlwaysPushInheritanceAttribute.cs +++ b/Robust.Shared/Serialization/Manager/Attributes/AlwaysPushInheritanceAttribute.cs @@ -3,6 +3,9 @@ namespace Robust.Shared.Serialization.Manager.Attributes { // TODO Serialization: find a way to constrain this to DataFields only & make exclusive w/ NeverPush + /// + /// Adds the parent DataDefinition field to this field. + /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class AlwaysPushInheritanceAttribute : Attribute { diff --git a/Robust.Shared/Serialization/Manager/Attributes/IncludeDataFieldAttribute.cs b/Robust.Shared/Serialization/Manager/Attributes/IncludeDataFieldAttribute.cs index eb63b764138..de0846619db 100644 --- a/Robust.Shared/Serialization/Manager/Attributes/IncludeDataFieldAttribute.cs +++ b/Robust.Shared/Serialization/Manager/Attributes/IncludeDataFieldAttribute.cs @@ -3,6 +3,17 @@ namespace Robust.Shared.Serialization.Manager.Attributes; +/// +/// Inlines the datafield instead of putting it into its own node. +/// +/// +/// mapping: +/// data1: 0 +/// data2: 0 +/// Becomes +/// data1: 0 +/// data2: 0 +/// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] [MeansImplicitAssignment] [MeansImplicitUse(ImplicitUseKindFlags.Assign)] From 3014d9880e2b4133147cbc61931029cd897e92f3 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:03:09 +1000 Subject: [PATCH 037/140] Fix prototype flag add not actually working (#5376) tryindex was inverted and causing issues with tools. --- Robust.Shared/Utility/PrototypeFlags.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Shared/Utility/PrototypeFlags.cs b/Robust.Shared/Utility/PrototypeFlags.cs index 8b56d2ec2e3..c46c6bf8954 100644 --- a/Robust.Shared/Utility/PrototypeFlags.cs +++ b/Robust.Shared/Utility/PrototypeFlags.cs @@ -45,7 +45,7 @@ public PrototypeFlags(IEnumerable flags) /// Whether the flag was added or not. public bool Add(string flag, IPrototypeManager prototypeManager) { - return !prototypeManager.TryIndex(flag, out _) && _flags.Add(flag); + return prototypeManager.HasIndex(flag) && _flags.Add(flag); } internal void UnionWith(PrototypeFlags flags) From ff056552febd852a64a6ab9cc4f0eb77316ccea7 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:08:19 +1000 Subject: [PATCH 038/140] Don't spam BUI closing in state handling (#5382) We just do what content does and defer it until update. Saves performance + we don't have some BUIs that do special logic on open re-running it constantly (e.g. open on mouse position). --- .../Systems/SharedUserInterfaceSystem.cs | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 5254a08a8e5..96cdc326259 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -36,6 +36,11 @@ public abstract class SharedUserInterfaceSystem : EntitySystem private ActorRangeCheckJob _rangeJob; + /// + /// Defer closing BUIs during state handling so client doesn't spam a BUI constantly during prediction. + /// + private HashSet _queuedCloses = new(); + public override void Initialize() { base.Initialize(); @@ -217,9 +222,9 @@ private void CloseUiInternal(Entity ent, Enum key, Enti } // If we're client we want this handled immediately. - if (ent.Comp.ClientOpenInterfaces.Remove(key, out var cBui)) + if (ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui)) { - cBui.Dispose(); + _queuedCloses.Add(cBui); } if (ent.Comp.Actors.Count == 0) @@ -376,8 +381,7 @@ private void OnUserInterfaceHandleState(Entity ent, ref continue; } - bui.Dispose(); - ent.Comp.ClientOpenInterfaces.Remove(key); + _queuedCloses.Add(bui); } // update any states we have open @@ -428,8 +432,10 @@ private void EnsureClientBui(Entity entity, Enum key, In DebugTools.Assert(_netManager.IsClient); - if (entity.Comp.ClientOpenInterfaces.ContainsKey(key)) + // Existing BUI just keep it. + if (entity.Comp.ClientOpenInterfaces.TryGetValue(key, out var existing)) { + _queuedCloses.Remove(existing); return; } @@ -920,6 +926,21 @@ public float GetUiRange(Entity entity, Enum key) /// public override void Update(float frameTime) { + if (_timing.IsFirstTimePredicted) + { + foreach (var bui in _queuedCloses) + { + if (TryComp(bui.Owner, out UserInterfaceComponent? uiComp)) + { + uiComp.ClientOpenInterfaces.Remove(bui.UiKey); + } + + bui.Dispose(); + } + + _queuedCloses.Clear(); + } + var query = AllEntityQuery(); // Run these in parallel because it's expensive. _rangeJob.ActorRanges.Clear(); From 43fd6bc764d2d00e9bc905d19b255b04f5488f24 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:25:19 +1000 Subject: [PATCH 039/140] Add FixturesChangeComponent (#5383) * Add FixturesChangeComponent Adds / removes fixtures. Useful when used in conjunction with EntProtoId to dynamically add / remove fixtures. * Move to system * Fix allcomps test --- .../Components/FixturesChangeComponent.cs | 17 ++++++ .../Physics/Systems/FixturesChangeSystem.cs | 55 +++++++++++++++++++ .../SharedPhysicsSystem.FixturesChange.cs | 40 ++++++++++++++ .../Physics/Systems/SharedPhysicsSystem.cs | 1 - 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 Robust.Shared/Physics/Components/FixturesChangeComponent.cs create mode 100644 Robust.Shared/Physics/Systems/FixturesChangeSystem.cs create mode 100644 Robust.Shared/Physics/Systems/SharedPhysicsSystem.FixturesChange.cs diff --git a/Robust.Shared/Physics/Components/FixturesChangeComponent.cs b/Robust.Shared/Physics/Components/FixturesChangeComponent.cs new file mode 100644 index 00000000000..2de30f2ff24 --- /dev/null +++ b/Robust.Shared/Physics/Components/FixturesChangeComponent.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Robust.Shared.Physics.Components; + +/// +/// Adds / removes fixtures on startup / shutdown without modifying the other fixtures on the entity. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class FixturesChangeComponent : Component +{ + [DataField, AutoNetworkedField] + public Dictionary Fixtures = new(); +} diff --git a/Robust.Shared/Physics/Systems/FixturesChangeSystem.cs b/Robust.Shared/Physics/Systems/FixturesChangeSystem.cs new file mode 100644 index 00000000000..bfdc432854b --- /dev/null +++ b/Robust.Shared/Physics/Systems/FixturesChangeSystem.cs @@ -0,0 +1,55 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Physics.Components; + +namespace Robust.Shared.Physics.Systems; + +public sealed class FixturesChangeSystem : EntitySystem +{ + [Dependency] private readonly FixtureSystem _fixtures = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + + private EntityQuery _fixturesQuery; + private EntityQuery _physicsQuery; + + public override void Initialize() + { + base.Initialize(); + _fixturesQuery = GetEntityQuery(); + _physicsQuery = GetEntityQuery(); + SubscribeLocalEvent(OnChangeStartup); + SubscribeLocalEvent(OnChangeShutdown); + } + + private void OnChangeStartup(Entity ent, ref ComponentStartup args) + { + if (!_physicsQuery.TryComp(ent, out var physics) || !_fixturesQuery.TryComp(ent, out var fixtures)) + return; + + foreach (var (id, fixture) in ent.Comp.Fixtures) + { + _fixtures.TryCreateFixture(ent.Owner, + fixture.Shape, + id, + fixture.Density, + fixture.Hard, + fixture.CollisionLayer, + fixture.CollisionMask, + fixture.Friction, + fixture.Restitution, + manager: fixtures, + body: physics); + } + + // TODO: Fixture creation should be handling this. + _physics.WakeBody(ent.Owner, manager: fixtures, body: physics); + } + + private void OnChangeShutdown(Entity ent, ref ComponentShutdown args) + { + foreach (var id in ent.Comp.Fixtures.Keys) + { + _fixtures.DestroyFixture(ent.Owner, id); + } + } +} diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.FixturesChange.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.FixturesChange.cs new file mode 100644 index 00000000000..4985b27ddf8 --- /dev/null +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.FixturesChange.cs @@ -0,0 +1,40 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Physics.Components; + +namespace Robust.Shared.Physics.Systems; + +public abstract partial class SharedPhysicsSystem +{ + private void InitializeFixturesChange() + { + SubscribeLocalEvent(OnChangeStartup); + SubscribeLocalEvent(OnChangeShutdown); + } + + private void OnChangeStartup(Entity ent, ref ComponentStartup args) + { + foreach (var (id, fixture) in ent.Comp.Fixtures) + { + _fixtures.TryCreateFixture(ent.Owner, + fixture.Shape, + id, + fixture.Density, + fixture.Hard, + fixture.CollisionLayer, + fixture.CollisionMask, + fixture.Friction, + fixture.Restitution); + } + + // TODO: Fixture creation should be handling this. + WakeBody(ent.Owner); + } + + private void OnChangeShutdown(Entity ent, ref ComponentShutdown args) + { + foreach (var id in ent.Comp.Fixtures.Keys) + { + _fixtures.DestroyFixture(ent.Owner, id); + } + } +} diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs index e9152031eac..7ee351b792b 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs @@ -27,7 +27,6 @@ public abstract partial class SharedPhysicsSystem : EntitySystem * Raycasts for non-box shapes. * TOI Solver (continuous collision detection) * Poly cutting - * Chain shape */ public static readonly Histogram TickUsageControllerBeforeSolveHistogram = Metrics.CreateHistogram("robust_entity_physics_controller_before_solve", From a4c54d360229c525301e7b404e541f4eb7e8b050 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:27:58 +1000 Subject: [PATCH 040/140] Make pointlight setting use an attempt event (#5378) Makes it easy for content to add functionality if multiple things try to set it without having to funnel every piece of code through a content system. --- .../Components/Light/SharedPointLightComponent.cs | 9 +++++++++ .../GameObjects/Systems/SharedPointLightSystem.cs | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/Robust.Shared/GameObjects/Components/Light/SharedPointLightComponent.cs b/Robust.Shared/GameObjects/Components/Light/SharedPointLightComponent.cs index 53b503c706f..248e7d60ebc 100644 --- a/Robust.Shared/GameObjects/Components/Light/SharedPointLightComponent.cs +++ b/Robust.Shared/GameObjects/Components/Light/SharedPointLightComponent.cs @@ -95,6 +95,15 @@ public float AnimatedRadius public string? MaskPath; } + /// + /// Raised directed on an entity when attempting to enable / disable it. + /// + [ByRefEvent] + public record struct AttemptPointLightToggleEvent(bool Enabled) + { + public bool Cancelled; + } + public sealed class PointLightToggleEvent : EntityEventArgs { public bool Enabled; diff --git a/Robust.Shared/GameObjects/Systems/SharedPointLightSystem.cs b/Robust.Shared/GameObjects/Systems/SharedPointLightSystem.cs index 71c32b54cb4..26a21751fdc 100644 --- a/Robust.Shared/GameObjects/Systems/SharedPointLightSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedPointLightSystem.cs @@ -42,6 +42,12 @@ public virtual void SetEnabled(EntityUid uid, bool enabled, SharedPointLightComp if (!ResolveLight(uid, ref comp) || enabled == comp.Enabled) return; + var attempt = new AttemptPointLightToggleEvent(enabled); + RaiseLocalEvent(uid, ref attempt); + + if (attempt.Cancelled) + return; + comp.Enabled = enabled; RaiseLocalEvent(uid, new PointLightToggleEvent(comp.Enabled)); if (!Resolve(uid, ref meta)) From e04caf7eb426af620e89406eadc8c059e881d766 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:32:28 +1000 Subject: [PATCH 041/140] Add OpenScreenAt for windows (#5387) If I want to open it at a particular position. Takes in clyde ref so not every single screen needs to keep the ref. --- .../UserInterface/CustomControls/BaseWindow.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Robust.Client/UserInterface/CustomControls/BaseWindow.cs b/Robust.Client/UserInterface/CustomControls/BaseWindow.cs index 64a89e7e5a4..2e416f871ec 100644 --- a/Robust.Client/UserInterface/CustomControls/BaseWindow.cs +++ b/Robust.Client/UserInterface/CustomControls/BaseWindow.cs @@ -1,5 +1,6 @@ using System; using System.Numerics; +using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; using Robust.Shared.IoC; @@ -235,6 +236,15 @@ public void Open() public void OpenToRight() => OpenCenteredAt(new Vector2(1, 0.5f)); public void OpenCenteredRight() => OpenCenteredAt(new Vector2(0.75f, 0.5f)); + /// + /// Opens a window and centers it relative to the screen position. + /// + public void OpenScreenAt(Vector2 relativePosition, IClyde clyde) + { + var adjusted = relativePosition / clyde.ScreenSize; + OpenCenteredAt(adjusted); + } + /// /// Opens a window, attempting to place the center of the window at some relative point on the screen. /// From 4d265b221093bb40e9a66d853295efb406b95611 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 25 Aug 2024 21:24:37 +1000 Subject: [PATCH 042/140] Add methods to get entity sprite position (#5381) * Add methods to get entity sprite position No easy way to get this in world-terms and I want a control to track it. * invalid --- .../EntitySystems/SpriteSystem.Helpers.cs | 43 +++++++++++++++++++ .../GameObjects/EntitySystems/SpriteSystem.cs | 2 + Robust.Shared/Map/ScreenCoordinates.cs | 2 + 3 files changed, 47 insertions(+) create mode 100644 Robust.Client/GameObjects/EntitySystems/SpriteSystem.Helpers.cs diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Helpers.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Helpers.cs new file mode 100644 index 00000000000..2cbf9fcbb59 --- /dev/null +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.Helpers.cs @@ -0,0 +1,43 @@ +using System.Numerics; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; + +namespace Robust.Client.GameObjects; + +public sealed partial class SpriteSystem +{ + /// + /// Gets an entity's sprite position in world terms. + /// + public Vector2 GetSpriteWorldPosition(Entity entity) + { + if (!Resolve(entity, ref entity.Comp2)) + return Vector2.Zero; + + var (worldPos, worldRot) = _xforms.GetWorldPositionRotation(entity.Owner); + + if (!Resolve(entity, ref entity.Comp1, false)) + { + return worldPos; + } + + if (entity.Comp1.NoRotation) + { + return worldPos + entity.Comp1.Offset; + } + + return worldPos + worldRot.RotateVec(entity.Comp1.Rotation.RotateVec(entity.Comp1.Offset)); + } + + /// + /// Gets an entity's sprite position in screen coordinates. + /// + public ScreenCoordinates GetSpriteScreenCoordinates(Entity entity) + { + if (!Resolve(entity, ref entity.Comp2)) + return ScreenCoordinates.Invalid; + + var spriteCoords = GetSpriteWorldPosition(entity); + return _eye.MapToScreen(new MapCoordinates(spriteCoords, entity.Comp2.MapID)); + } +} diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs index e1a83f2cf95..e92f0df258c 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs @@ -30,10 +30,12 @@ namespace Robust.Client.GameObjects public sealed partial class SpriteSystem : EntitySystem { [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IEyeManager _eye = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly ILogManager _logManager = default!; + [Dependency] private readonly SharedTransformSystem _xforms = default!; private readonly Queue _inertUpdateQueue = new(); diff --git a/Robust.Shared/Map/ScreenCoordinates.cs b/Robust.Shared/Map/ScreenCoordinates.cs index 13839ee6962..609805154fa 100644 --- a/Robust.Shared/Map/ScreenCoordinates.cs +++ b/Robust.Shared/Map/ScreenCoordinates.cs @@ -14,6 +14,8 @@ namespace Robust.Shared.Map [Serializable, NetSerializable] public readonly struct ScreenCoordinates : IEquatable, ISpanFormattable { + public static readonly ScreenCoordinates Invalid = new(Vector2.Zero, WindowId.Invalid); + /// /// Position on the rendering screen. /// From 8d5ebd830a41902071ec7602dff6f7342386045c Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 25 Aug 2024 21:37:14 +1000 Subject: [PATCH 043/140] Add CompRegistry methods to EntManager / CompFac (#5379) * Add CompRegistry methods to EntManager / CompFac CompRegistries are nice to use and this just makes it a bit easier to extend functionality. * fix bad pull --- .../GameObjects/ServerComponentFactory.cs | 3 +- Robust.Shared/GameObjects/ComponentFactory.cs | 10 ++ .../GameObjects/EntityManager.Components.cs | 159 ++++++++++++++++++ .../GameObjects/IComponentFactory.cs | 2 + .../GameObjects/IEntityManager.Components.cs | 10 ++ .../EntityEventBusTests.ComponentEvent.cs | 3 +- 6 files changed, 185 insertions(+), 2 deletions(-) diff --git a/Robust.Server/GameObjects/ServerComponentFactory.cs b/Robust.Server/GameObjects/ServerComponentFactory.cs index 3d22330f7fa..9e718fbb079 100644 --- a/Robust.Server/GameObjects/ServerComponentFactory.cs +++ b/Robust.Server/GameObjects/ServerComponentFactory.cs @@ -2,12 +2,13 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Reflection; +using Robust.Shared.Serialization.Manager; namespace Robust.Server.GameObjects; internal sealed class ServerComponentFactory : ComponentFactory { - public ServerComponentFactory(IDynamicTypeFactoryInternal typeFactory, IReflectionManager reflectionManager, ILogManager logManager) : base(typeFactory, reflectionManager, logManager) + public ServerComponentFactory(IDynamicTypeFactoryInternal typeFactory, IReflectionManager reflectionManager, ISerializationManager serManager, ILogManager logManager) : base(typeFactory, reflectionManager, serManager, logManager) { RegisterIgnore("Input"); RegisterIgnore("AnimationPlayer"); diff --git a/Robust.Shared/GameObjects/ComponentFactory.cs b/Robust.Shared/GameObjects/ComponentFactory.cs index d0161db08e5..b95c7a1668d 100644 --- a/Robust.Shared/GameObjects/ComponentFactory.cs +++ b/Robust.Shared/GameObjects/ComponentFactory.cs @@ -8,7 +8,9 @@ using Robust.Shared.GameStates; using Robust.Shared.IoC; using Robust.Shared.Log; +using Robust.Shared.Prototypes; using Robust.Shared.Reflection; +using Robust.Shared.Serialization.Manager; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -18,6 +20,7 @@ namespace Robust.Shared.GameObjects internal class ComponentFactory( IDynamicTypeFactoryInternal _typeFactory, IReflectionManager _reflectionManager, + ISerializationManager _serManager, ILogManager logManager) : IComponentFactory { private readonly ISawmill _sawmill = logManager.GetSawmill("ent.componentFactory"); @@ -181,6 +184,13 @@ public void IgnoreMissingComponents(string postfix = "") _ignoreMissingComponentPostfix = postfix ?? throw new ArgumentNullException(nameof(postfix)); } + public IComponent GetComponent(EntityPrototype.ComponentRegistryEntry entry) + { + var copy = GetComponent(entry.Component.GetType()); + _serManager.CopyTo(entry.Component, ref copy, notNullableOverride: true); + return copy; + } + public void RegisterIgnore(params string[] names) { foreach (var name in names) diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index 35d911c0c43..29e370523f0 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -297,6 +297,16 @@ public static implicit operator T(CompInitializeHandle handle) return new CompInitializeHandle(this, uid, newComponent, reg.Idx); } + public void AddComponent( + EntityUid uid, + EntityPrototype.ComponentRegistryEntry entry, + bool overwrite = false, + MetaDataComponent? metadata = null) + { + var copy = _componentFactory.GetComponent(entry); + AddComponent(uid, copy, overwrite, metadata); + } + /// public void AddComponent(EntityUid uid, T component, bool overwrite = false, MetaDataComponent? metadata = null) where T : IComponent { @@ -1125,6 +1135,34 @@ public NetComponentEnumerable GetNetComponents(EntityUid uid, MetaDataComponent? return comps; } + /// + public ComponentQueryEnumerator ComponentQueryEnumerator(ComponentRegistry registry) + { + if (registry.Count == 0) + { + return new ComponentQueryEnumerator(new Dictionary()); + } + + var comp1 = registry.First().Value; + var trait1 = _entTraitArray[_componentFactory.GetArrayIndex(comp1.Component.GetType())]; + + return new ComponentQueryEnumerator(trait1); + } + + /// + public CompRegistryEntityEnumerator CompRegistryQueryEnumerator(ComponentRegistry registry) + { + if (registry.Count == 0) + { + return new CompRegistryEntityEnumerator(this, new Dictionary(), registry); + } + + var comp1 = registry.First().Value; + var trait1 = _entTraitArray[_componentFactory.GetArrayIndex(comp1.Component.GetType())]; + + return new CompRegistryEntityEnumerator(this, trait1, registry); + } + public AllEntityQueryEnumerator AllEntityQueryEnumerator() where TComp1 : IComponent { @@ -1713,6 +1751,127 @@ internal bool ResolveInternal(EntityUid uid, [NotNullWhen(true)] ref TComp1? com #endregion } + #region ComponentRegistry Query + + /// + /// Returns entities that match the ComponentRegistry. + /// + public struct CompRegistryEntityEnumerator : IDisposable + { + private IEntityManager _entManager; + + private Dictionary.Enumerator _traitDict; + private ComponentRegistry _registry; + + public CompRegistryEntityEnumerator( + IEntityManager entManager, + Dictionary traitDict, ComponentRegistry registry) + { + _entManager = entManager; + _traitDict = traitDict.GetEnumerator(); + _registry = registry; + } + + public bool MoveNext(out EntityUid uid) + { + while (true) + { + if (!_traitDict.MoveNext()) + { + uid = default; + return false; + } + + var current = _traitDict.Current; + + if (current.Value.Deleted) + { + continue; + } + + var idx = -1; + var found = true; + + foreach (var comp in _registry) + { + idx++; + + // First one is us + if (idx == 0) + continue; + + if (!_entManager.TryGetComponent(current.Key, comp.Value.Component.GetType(), out var nextComp) || + nextComp.Deleted) + { + found = false; + break; + } + } + + if (!found) + continue; + + uid = current.Key; + return true; + } + } + + public void Dispose() + { + _traitDict.Dispose(); + } + } + + /// + /// Non-generic version of + /// + public struct ComponentQueryEnumerator : IDisposable + { + private Dictionary.Enumerator _traitDict; + + public ComponentQueryEnumerator( + Dictionary traitDict) + { + _traitDict = traitDict.GetEnumerator(); + } + + public bool MoveNext(out EntityUid uid, [NotNullWhen(true)] out IComponent? comp1) + { + while (true) + { + if (!_traitDict.MoveNext()) + { + uid = default; + comp1 = default; + return false; + } + + var current = _traitDict.Current; + + if (current.Value.Deleted) + { + continue; + } + + uid = current.Key; + comp1 = current.Value; + return true; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext([NotNullWhen(true)] out IComponent? comp1) + { + return MoveNext(out _, out comp1); + } + + public void Dispose() + { + _traitDict.Dispose(); + } + } + #endregion + #region Query /// diff --git a/Robust.Shared/GameObjects/IComponentFactory.cs b/Robust.Shared/GameObjects/IComponentFactory.cs index 8f22cb4c374..e060c94d955 100644 --- a/Robust.Shared/GameObjects/IComponentFactory.cs +++ b/Robust.Shared/GameObjects/IComponentFactory.cs @@ -112,6 +112,8 @@ public interface IComponentFactory /// If provided, will only ignore components ending with the postfix. void IgnoreMissingComponents(string postfix = ""); + IComponent GetComponent(EntityPrototype.ComponentRegistryEntry entry); + /// /// Gets a new component instantiated of the specified type. /// diff --git a/Robust.Shared/GameObjects/IEntityManager.Components.cs b/Robust.Shared/GameObjects/IEntityManager.Components.cs index 8e23583c23d..c371a3a055d 100644 --- a/Robust.Shared/GameObjects/IEntityManager.Components.cs +++ b/Robust.Shared/GameObjects/IEntityManager.Components.cs @@ -427,6 +427,16 @@ public partial interface IEntityManager /// List<(EntityUid Uid, T Component)> AllComponentsList() where T : IComponent; + /// + /// + /// + public ComponentQueryEnumerator ComponentQueryEnumerator(ComponentRegistry registry); + + /// + /// + /// + public CompRegistryEntityEnumerator CompRegistryQueryEnumerator(ComponentRegistry registry); + AllEntityQueryEnumerator AllEntityQueryEnumerator() where TComp1 : IComponent; diff --git a/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.ComponentEvent.cs b/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.ComponentEvent.cs index acd2f0a3d81..8a190e0b17f 100644 --- a/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.ComponentEvent.cs +++ b/Robust.UnitTesting/Shared/GameObjects/EntityEventBusTests.ComponentEvent.cs @@ -5,6 +5,7 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Reflection; +using Robust.Shared.Serialization.Manager; using Robust.UnitTesting.Shared.Reflection; namespace Robust.UnitTesting.Shared.GameObjects @@ -14,7 +15,7 @@ public sealed partial class EntityEventBusTests [Test] public void SubscribeCompEvent() { - var compFactory = new ComponentFactory(new DynamicTypeFactory(), new ReflectionManagerTest(), new LogManager()); + var compFactory = new ComponentFactory(new DynamicTypeFactory(), new ReflectionManagerTest(), new SerializationManager(), new LogManager()); // Arrange var entUid = new EntityUid(7); From f2ee9a43f94288be4662861e6e591da10234c0c7 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 25 Aug 2024 22:48:51 +1000 Subject: [PATCH 044/140] Version: 231.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 6413ee7d3b9..2cc06f405c5 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 230.2.0 + 231.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 9f6a179530d..e34a9b2e595 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,27 @@ END TEMPLATE--> *None yet* +## 231.0.0 + +### Breaking changes + +* ViewSubscriber has been moved to shared; it doesn't actually do anything on the client but makes shared code easier. + +### New features + +* ContactEnumreator exists to iterate the contacts of a particular entity. +* Add FixturesChangeComponent as a generic way to add and remove fixtures easily. +* PointLightComponent enabling / disabling now has an attempt event if you wish to block it on content side. +* There's an OpenScreenAt overload for screen-relative coordinates. +* SpriteSystem has methods to get an entity's position in sprite terms. +* EntityManager and ComponentFactory now have additional methods that interact with ComponentRegistry and ComponentRegistryEntry. + +### Bugfixes + +* Fix PrototypeFlags Add not actually working. +* Fix BUIs going BRRT opening and closing repeatedly upon prediction. The closing now gets deferred to the update loop if it's still closed at the end of prediction. + + ## 230.2.0 ### New features From 04344ffe192a8c69cd084d8cc0f15b81e7f7bb67 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:31:05 +1000 Subject: [PATCH 045/140] Make PVS exception log better (#5397) --- Robust.Server/GameStates/PvsSystem.ToSendSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Server/GameStates/PvsSystem.ToSendSet.cs b/Robust.Server/GameStates/PvsSystem.ToSendSet.cs index a9f05606ef5..ffe505994ba 100644 --- a/Robust.Server/GameStates/PvsSystem.ToSendSet.cs +++ b/Robust.Server/GameStates/PvsSystem.ToSendSet.cs @@ -78,7 +78,7 @@ private void AddEntity(PvsSession session, ref PvsChunk.ChunkEntity ent, ref Pvs if (meta.LifeStage >= EntityLifeStage.Terminating) { - Log.Error($"Attempted to send deleted entity: {ToPrettyString(ent.Uid)}"); + Log.Error($"Attempted to send deleted entity: {ToPrettyString(ent.Uid)}, lifestage is {meta.LifeStage}.\n{Environment.StackTrace}"); EntityManager.QueueDeleteEntity(ent.Uid); return; } From 57f133b742866ba677817f1c937cfa35c1af6ced Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 26 Aug 2024 14:41:40 +1000 Subject: [PATCH 046/140] Version: 231.0.1 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 2cc06f405c5..db4d1369cb2 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 231.0.0 + 231.0.1 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index e34a9b2e595..cca0eb955d9 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,13 @@ END TEMPLATE--> *None yet* +## 231.0.1 + +### Other + +* Add better logging to failed PVS sends. + + ## 231.0.0 ### Breaking changes From e3819f824557dc23cf74eaa7acd0cba646561a9d Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:48:15 +1000 Subject: [PATCH 047/140] Network interfacedata (#5399) If UIs are dynamically changed this fixes it. --- .../UserInterface/UserInterfaceComponent.cs | 12 ++++++++++-- .../Systems/SharedUserInterfaceSystem.cs | 13 ++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs b/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs index dda39acefc7..de5a3f3bb1a 100644 --- a/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs +++ b/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs @@ -34,16 +34,19 @@ public sealed partial class UserInterfaceComponent : Component [Serializable, NetSerializable] internal sealed class UserInterfaceComponentState( Dictionary> actors, - Dictionary states) + Dictionary states, + Dictionary data) : IComponentState { public Dictionary> Actors = actors; public Dictionary States = states; + + public Dictionary Data = data; } } - [DataDefinition] + [DataDefinition, Serializable, NetSerializable] public sealed partial class InterfaceData { [DataField("type", required: true)] @@ -65,6 +68,11 @@ public sealed partial class InterfaceData /// [DataField] public bool RequireInputValidation = true; + + public InterfaceData(string type) + { + ClientType = type; + } } /// diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 96cdc326259..50bfafcc698 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -300,7 +300,7 @@ private void OnUserInterfaceGetState(Entity ent, ref Com // I.e., don't resend the whole BUI state just because a new user opened it. var actors = new Dictionary>(); - args.State = new UserInterfaceComponent.UserInterfaceComponentState(actors, ent.Comp.States); + args.State = new UserInterfaceComponent.UserInterfaceComponentState(actors, ent.Comp.States, ent.Comp.Interfaces); // Ensure that only the player that currently has the UI open gets to know what they have it open. if (args.ReplayState) @@ -326,6 +326,17 @@ private void OnUserInterfaceHandleState(Entity ent, ref if (args.Current is not UserInterfaceComponent.UserInterfaceComponentState state) return; + ent.Comp.Interfaces.Clear(); + + foreach (var data in state.Data) + { + ent.Comp.Interfaces[data.Key] = new(data.Value.ClientType) + { + InteractionRange = data.Value.InteractionRange, + RequireInputValidation = data.Value.RequireInputValidation, + }; + } + foreach (var key in ent.Comp.Actors.Keys) { if (!state.Actors.ContainsKey(key)) From d7aa5daf6a6adc9869bec8258377fc51754e2e90 Mon Sep 17 00:00:00 2001 From: Stalen <33173619+stalengd@users.noreply.github.com> Date: Tue, 27 Aug 2024 02:35:42 +0300 Subject: [PATCH 048/140] Add decimal type to sandbox whitelist (#5396) --- Robust.Shared/ContentPack/Sandbox.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Robust.Shared/ContentPack/Sandbox.yml b/Robust.Shared/ContentPack/Sandbox.yml index d68199310ab..07d1ba6a4fd 100644 --- a/Robust.Shared/ContentPack/Sandbox.yml +++ b/Robust.Shared/ContentPack/Sandbox.yml @@ -1054,6 +1054,7 @@ Types: DateTime: { All: True } DateTimeKind: { } # Enum DateTimeOffset: { All: True } + Decimal: { All: True } Delegate: Methods: - "System.Delegate Combine(System.Delegate, System.Delegate)" From 6396ec472d0688c66ee2dbae75c9b9cd261a1b6d Mon Sep 17 00:00:00 2001 From: Nyeogmi <59642025+nyeogmi@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:16:57 -0700 Subject: [PATCH 049/140] XAML hot reloading (#5350) * Move RobustXaml to a shared package In a near-future change, I'll make it possible to optionally link to this from Robust.Client, which will allow JIT compiling XAML. Also upgrade it to a version of .NET that supports nullability annotations. * Re-namespace packages * Add a JIT compiler, plus hooks that call into it In Debug, after this change, all XAML will be hot reloaded once every time an assembly is reloaded. The new code is compiled with SRE and is _not_ sandboxed -- this is not suitable to run against prod. In Release, the hot reload path is totally skipped, using the same trick as SmugLeaf used in an earlier attempt to implement this functionality. * Hot reload: watcher This is a bit of a horror, but there's not in-engine support for identifying the source tree or the XAML files in it. * Put everything dangerous behind conditional comp * Code cleanup, docs * Fix a bad comment * Deal a little better with crashes in the watcher * Make reload failures Info, since they're expected They were previously causing the integration tests to flag, even though "a few types fail hot reloading because they're internal" is expected behavior. * Fix an unnecessary null check I removed the ability for CompileCore to return null. * injectors: null! strings, default primitives * Tidy documentation (thanks, PJB!) * Reinstate netstandard2.0, abolish Pidgin * Internal-ize all of Robust.Xaml * Add a cautionary note to Sandbox.yml * Shuffle around where conditional compilation occurs * Privatize fields in XamlImplementationStorage * Internalize XamlJitDelegate * Inline some remarks. No cond. comp in Robust.Xaml * Use file-scoped namespaces They aren't allowed at Language Level 8.0. (which I arbitrarily picked for Robust.Xaml because it's the oldest one that would work) * Bump language level for R.Xaml, file namespaces * Force hot reloading off for integration tests * Fix bizarre comment/behavior in XamlImplementationStorage * Consistently use interfaces, even in generated code * Update Robust.Client/ClientIoC.cs --------- Co-authored-by: Pieter-Jan Briers --- .../CompileRobustXamlTask.cs | 30 +- Robust.Client.Injectors/MathParsing.cs | 37 -- Robust.Client.Injectors/Program.cs | 10 +- .../Robust.Client.Injectors.csproj | 19 +- Robust.Client.Injectors/XamlCompiler.cs | 389 ------------------ Robust.Client/ClientIoC.cs | 11 + .../GameController/GameController.cs | 5 + Robust.Client/Robust.Client.csproj | 4 + .../XAML/Proxy/IXamlHotReloadManager.cs | 20 + .../XAML/Proxy/IXamlProxyHelper.cs | 11 + .../XAML/Proxy/IXamlProxyManager.cs | 76 ++++ .../XAML/Proxy/XamlHotReloadManager.cs | 195 +++++++++ .../XAML/Proxy/XamlHotReloadManagerStub.cs | 15 + .../XAML/Proxy/XamlImplementationStorage.cs | 221 ++++++++++ .../XAML/Proxy/XamlJitDelegate.cs | 17 + .../XAML/Proxy/XamlMetadataAttribute.cs | 29 ++ .../XAML/Proxy/XamlProxyHelper.cs | 14 + .../XAML/Proxy/XamlProxyManager.cs | 127 ++++++ .../XAML/Proxy/XamlProxyManagerStub.cs | 50 +++ Robust.Shared/ContentPack/Sandbox.yml | 2 + Robust.UnitTesting/RobustIntegrationTest.cs | 3 + Robust.Xaml/AssemblyInfo.cs | 4 + Robust.Xaml/CecilExtensions.cs | 32 ++ .../Extensions.cs | 4 +- Robust.Xaml/LowLevelCustomizations.cs | 249 +++++++++++ Robust.Xaml/MathParsing.cs | 113 +++++ .../RXamlColorAstNode.cs | 5 +- .../RXamlVecLikeConstAstNode.cs | 8 +- .../RXamlWellKnownTypes.cs | 4 +- Robust.Xaml/Robust.Xaml.csproj | 17 + .../RobustXamlILCompiler.cs | 23 +- .../XamlAotCompiler.Helpers.cs | 28 +- Robust.Xaml/XamlAotCompiler.cs | 209 ++++++++++ Robust.Xaml/XamlCustomizations.cs | 218 ++++++++++ Robust.Xaml/XamlJitCompiler.cs | 180 ++++++++ 35 files changed, 1892 insertions(+), 487 deletions(-) delete mode 100644 Robust.Client.Injectors/MathParsing.cs delete mode 100644 Robust.Client.Injectors/XamlCompiler.cs create mode 100644 Robust.Client/UserInterface/XAML/Proxy/IXamlHotReloadManager.cs create mode 100644 Robust.Client/UserInterface/XAML/Proxy/IXamlProxyHelper.cs create mode 100644 Robust.Client/UserInterface/XAML/Proxy/IXamlProxyManager.cs create mode 100644 Robust.Client/UserInterface/XAML/Proxy/XamlHotReloadManager.cs create mode 100644 Robust.Client/UserInterface/XAML/Proxy/XamlHotReloadManagerStub.cs create mode 100644 Robust.Client/UserInterface/XAML/Proxy/XamlImplementationStorage.cs create mode 100644 Robust.Client/UserInterface/XAML/Proxy/XamlJitDelegate.cs create mode 100644 Robust.Client/UserInterface/XAML/Proxy/XamlMetadataAttribute.cs create mode 100644 Robust.Client/UserInterface/XAML/Proxy/XamlProxyHelper.cs create mode 100644 Robust.Client/UserInterface/XAML/Proxy/XamlProxyManager.cs create mode 100644 Robust.Client/UserInterface/XAML/Proxy/XamlProxyManagerStub.cs create mode 100644 Robust.Xaml/AssemblyInfo.cs create mode 100644 Robust.Xaml/CecilExtensions.cs rename {Robust.Client.Injectors => Robust.Xaml}/Extensions.cs (88%) create mode 100644 Robust.Xaml/LowLevelCustomizations.cs create mode 100644 Robust.Xaml/MathParsing.cs rename {Robust.Client.Injectors => Robust.Xaml}/RXamlColorAstNode.cs (91%) rename {Robust.Client.Injectors => Robust.Xaml}/RXamlVecLikeConstAstNode.cs (91%) rename {Robust.Client.Injectors => Robust.Xaml}/RXamlWellKnownTypes.cs (97%) create mode 100644 Robust.Xaml/Robust.Xaml.csproj rename {Robust.Client.Injectors => Robust.Xaml}/RobustXamlILCompiler.cs (93%) rename Robust.Client.Injectors/XamlCompiler.Helpers.cs => Robust.Xaml/XamlAotCompiler.Helpers.cs (68%) create mode 100644 Robust.Xaml/XamlAotCompiler.cs create mode 100644 Robust.Xaml/XamlCustomizations.cs create mode 100644 Robust.Xaml/XamlJitCompiler.cs diff --git a/Robust.Client.Injectors/CompileRobustXamlTask.cs b/Robust.Client.Injectors/CompileRobustXamlTask.cs index e7fae94f037..7225f4650d6 100644 --- a/Robust.Client.Injectors/CompileRobustXamlTask.cs +++ b/Robust.Client.Injectors/CompileRobustXamlTask.cs @@ -1,8 +1,8 @@ using System; -using System.Diagnostics; using System.IO; using System.Linq; using Microsoft.Build.Framework; +using Robust.Xaml; namespace Robust.Build.Tasks { @@ -37,10 +37,12 @@ public bool Execute() var msg = $"CompileRobustXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{OutputPath}"; BuildEngine.LogMessage(msg, MessageImportance.High); - var res = XamlCompiler.Compile(BuildEngine, input, + var res = XamlAotCompiler.Compile( + BuildEngine, input, File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(), - ProjectDirectory, OutputPath, - (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null); + OutputPath, + (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null + ); if (!res.success) return false; if (!res.writtentofile) @@ -65,22 +67,24 @@ public bool Execute() return true; } + // PYREX NOTE: This project was comically null-unsafe before I touched it. I'm just marking what it did accurately [Required] - public string ReferencesFilePath { get; set; } + public string ReferencesFilePath { get; set; } = null!; [Required] - public string ProjectDirectory { get; set; } + + public string ProjectDirectory { get; set; } = null!; [Required] - public string AssemblyFile { get; set; } + public string AssemblyFile { get; set; } = null!; [Required] - public string OriginalCopyPath { get; set; } + public string? OriginalCopyPath { get; set; } = null; - public string OutputPath { get; set; } - public string UpdateBuildIndicator { get; set; } + public string? OutputPath { get; set; } + public string UpdateBuildIndicator { get; set; } = null!; - public string AssemblyOriginatorKeyFile { get; set; } + public string AssemblyOriginatorKeyFile { get; set; } = null!; public bool SignAssembly { get; set; } public bool DelaySign { get; set; } @@ -95,7 +99,7 @@ string GetPdbPath(string p) return rv; } - public IBuildEngine BuildEngine { get; set; } - public ITaskHost HostObject { get; set; } + public IBuildEngine BuildEngine { get; set; } = null!; + public ITaskHost HostObject { get; set; } = null!; } } diff --git a/Robust.Client.Injectors/MathParsing.cs b/Robust.Client.Injectors/MathParsing.cs deleted file mode 100644 index a2e06d3ad3f..00000000000 --- a/Robust.Client.Injectors/MathParsing.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Linq; -using Pidgin; -using static Pidgin.Parser; - - -namespace Robust.Build.Tasks -{ - public static class MathParsing - { - public static Parser Single { get; } = Real.Select(c => (float) c); - - public static Parser Single1 { get; } - = Single.Between(SkipWhitespaces); - - public static Parser Single2 { get; } - = Single.Before(SkipWhitespaces).Repeat(2).Select(e => - { - var arr = e.ToArray(); - return (arr[0], arr[1]); - }); - - public static Parser Single4 { get; } - = Single.Before(SkipWhitespaces).Repeat(4).Select(e => - { - var arr = e.ToArray(); - return (arr[0], arr[1], arr[2], arr[3]); - }); - - public static Parser Thickness { get; } - = SkipWhitespaces.Then( - OneOf( - Try(Single4.Select(c => new[] {c.Item1, c.Item2, c.Item3, c.Item4})), - Try(Single2.Select(c => new[] {c.Item1, c.Item2})), - Try(Single1.Select(c => new[] {c})) - )); - } -} diff --git a/Robust.Client.Injectors/Program.cs b/Robust.Client.Injectors/Program.cs index 28654b96bbf..0bfb8392050 100644 --- a/Robust.Client.Injectors/Program.cs +++ b/Robust.Client.Injectors/Program.cs @@ -55,9 +55,11 @@ public void LogCustomEvent(CustomBuildEventArgs e) public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs) => throw new NotSupportedException(); - public bool ContinueOnError { get; } - public int LineNumberOfTaskNode { get; } - public int ColumnNumberOfTaskNode { get; } - public string ProjectFileOfTaskNode { get; } + // PYREX NOTE: This project was extremely null-unsafe before I touched it. I'm just marking what it did already + // Here's the broken interface of IBuildEngine that we started with + public bool ContinueOnError => default; + public int LineNumberOfTaskNode => default; + public int ColumnNumberOfTaskNode => default; + public string ProjectFileOfTaskNode => null!; } } diff --git a/Robust.Client.Injectors/Robust.Client.Injectors.csproj b/Robust.Client.Injectors/Robust.Client.Injectors.csproj index bb71eaa0577..b252642f18d 100644 --- a/Robust.Client.Injectors/Robust.Client.Injectors.csproj +++ b/Robust.Client.Injectors/Robust.Client.Injectors.csproj @@ -1,17 +1,30 @@ + + - netstandard2.0 - true + netstandard2.0 + true + 8.0 + enable - + diff --git a/Robust.Client.Injectors/XamlCompiler.cs b/Robust.Client.Injectors/XamlCompiler.cs deleted file mode 100644 index 0b4b8799783..00000000000 --- a/Robust.Client.Injectors/XamlCompiler.cs +++ /dev/null @@ -1,389 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Microsoft.Build.Framework; -using Mono.Cecil; -using Mono.Cecil.Cil; -using Mono.Cecil.Rocks; -using Pidgin; -using XamlX; -using XamlX.Ast; -using XamlX.Emit; -using XamlX.IL; -using XamlX.Parsers; -using XamlX.Transform; -using XamlX.TypeSystem; - -namespace Robust.Build.Tasks -{ - /// - /// Based on https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs - /// Adjusted for our UI-Framework - /// - public partial class XamlCompiler - { - public static (bool success, bool writtentofile) Compile(IBuildEngine engine, string input, string[] references, - string projectDirectory, string output, string strongNameKey) - { - var typeSystem = new CecilTypeSystem(references - .Where(r => !r.ToLowerInvariant().EndsWith("robust.build.tasks.dll")) - .Concat(new[] { input }), input); - - var asm = typeSystem.TargetAssemblyDefinition; - - if (asm.MainModule.GetType("CompiledRobustXaml", "XamlIlContext") != null) - { - // If this type exists, the assembly has already been processed by us. - // Do not run again, it would corrupt the file. - // This *shouldn't* be possible due to Inputs/Outputs dependencies in the build system, - // but better safe than sorry eh? - engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", "", 0, 0, 0, 0, "Ran twice on same assembly file; ignoring.", "", "")); - return (true, false); - } - - var compileRes = CompileCore(engine, typeSystem); - if (compileRes == null) - return (true, false); - if (compileRes == false) - return (false, false); - - var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols }; - if (!string.IsNullOrWhiteSpace(strongNameKey)) - writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey); - - asm.Write(output, writerParameters); - - return (true, true); - - } - - static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem) - { - var asm = typeSystem.TargetAssemblyDefinition; - var embrsc = new EmbeddedResources(asm); - - if (embrsc.Resources.Count(CheckXamlName) == 0) - // Nothing to do - return null; - - var xamlLanguage = new XamlLanguageTypeMappings(typeSystem) - { - XmlnsAttributes = - { - typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute"), - - }, - ContentAttributes = - { - typeSystem.GetType("Avalonia.Metadata.ContentAttribute") - }, - UsableDuringInitializationAttributes = - { - typeSystem.GetType("Robust.Client.UserInterface.XAML.UsableDuringInitializationAttribute") - }, - DeferredContentPropertyAttributes = - { - typeSystem.GetType("Robust.Client.UserInterface.XAML.DeferredContentAttribute") - }, - RootObjectProvider = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestRootObjectProvider"), - UriContextProvider = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestUriContext"), - ProvideValueTarget = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestProvideValueTarget"), - }; - var emitConfig = new XamlLanguageEmitMappings - { - ContextTypeBuilderCallback = (b,c) => EmitNameScopeField(xamlLanguage, typeSystem, b, c) - }; - - var transformerconfig = new TransformerConfiguration( - typeSystem, - typeSystem.TargetAssembly, - xamlLanguage, - XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage), CustomValueConverter); - - var contextDef = new TypeDefinition("CompiledRobustXaml", "XamlIlContext", - TypeAttributes.Class, asm.MainModule.TypeSystem.Object); - asm.MainModule.Types.Add(contextDef); - var contextClass = XamlILContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem, - xamlLanguage, emitConfig); - - var compiler = - new RobustXamlILCompiler(transformerconfig, emitConfig, true); - - bool CompileGroup(IResourceGroup group) - { - var typeDef = new TypeDefinition("CompiledRobustXaml", "!" + group.Name, TypeAttributes.Class, - asm.MainModule.TypeSystem.Object); - - //typeDef.CustomAttributes.Add(new CustomAttribute(ed)); - asm.MainModule.Types.Add(typeDef); - var builder = typeSystem.CreateTypeBuilder(typeDef); - - foreach (var res in group.Resources.Where(CheckXamlName)) - { - try - { - engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", MessageImportance.Low); - - var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd(); - var parsed = XDocumentXamlParser.Parse(xaml); - - var initialRoot = (XamlAstObjectNode) parsed.Root; - - var classDirective = initialRoot.Children.OfType() - .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class"); - string classname; - if (classDirective != null && classDirective.Values[0] is XamlAstTextNode tn) - { - classname = tn.Text; - } - else - { - classname = res.Name.Replace(".xaml",""); - } - - var classType = typeSystem.TargetAssembly.FindType(classname); - if (classType == null) - throw new Exception($"Unable to find type '{classname}'"); - - compiler.Transform(parsed); - - var populateName = $"Populate:{res.Name}"; - var buildName = $"Build:{res.Name}"; - - var classTypeDefinition = typeSystem.GetTypeReference(classType).Resolve(); - - var populateBuilder = typeSystem.CreateTypeBuilder(classTypeDefinition); - - compiler.Compile(parsed, contextClass, - compiler.DefinePopulateMethod(populateBuilder, parsed, populateName, - classTypeDefinition == null), - compiler.DefineBuildMethod(builder, parsed, buildName, true), - null, - (closureName, closureBaseType) => - populateBuilder.DefineSubType(closureBaseType, closureName, false), - res.Uri, res - ); - - //add compiled populate method - var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve().Methods - .First(m => m.Name == populateName); - - const string TrampolineName = "!XamlIlPopulateTrampoline"; - var trampoline = new MethodDefinition(TrampolineName, - MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void); - trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition)); - classTypeDefinition.Methods.Add(trampoline); - - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); - - var foundXamlLoader = false; - // Find RobustXamlLoader.Load(this) and replace it with !XamlIlPopulateTrampoline(this) - foreach (var method in classTypeDefinition.Methods - .Where(m => !m.Attributes.HasFlag(MethodAttributes.Static))) - { - var i = method.Body.Instructions; - for (var c = 1; c < i.Count; c++) - { - if (i[c].OpCode == OpCodes.Call) - { - var op = i[c].Operand as MethodReference; - - if (op != null - && op.Name == TrampolineName) - { - foundXamlLoader = true; - break; - } - - if (op != null - && op.Name == "Load" - && op.Parameters.Count == 1 - && op.Parameters[0].ParameterType.FullName == "System.Object" - && op.DeclaringType.FullName == "Robust.Client.UserInterface.XAML.RobustXamlLoader") - { - if (MatchThisCall(i, c - 1)) - { - i[c].Operand = trampoline; - foundXamlLoader = true; - } - } - } - } - } - - if (!foundXamlLoader) - { - var ctors = classTypeDefinition.GetConstructors() - .Where(c => !c.IsStatic).ToList(); - // We can inject xaml loader into default constructor - if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o=>o.OpCode != OpCodes.Nop) == 3) - { - var i = ctors[0].Body.Instructions; - var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret)); - i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampoline)); - i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0)); - } - else - { - throw new InvalidProgramException( - $"No call to RobustXamlLoader.Load(this) call found anywhere in the type {classType.FullName} and type seems to have custom constructors."); - } - } - } - catch (Exception e) - { - engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0, - $"{res.FilePath}: {e.Message}", "", "CompileRobustXaml")); - } - res.Remove(); - } - return true; - } - - if (embrsc.Resources.Count(CheckXamlName) != 0) - { - if (!CompileGroup(embrsc)) - return false; - } - - return true; - } - - private static bool CustomValueConverter( - AstTransformationContext context, - IXamlAstValueNode node, - IXamlType type, - out IXamlAstValueNode result) - { - if (!(node is XamlAstTextNode textNode)) - { - result = null; - return false; - } - - var text = textNode.Text; - var types = context.GetRobustTypes(); - - if (type.Equals(types.Vector2)) - { - var foo = MathParsing.Single2.Parse(text); - - if (!foo.Success) - throw new XamlLoadException($"Unable to parse \"{text}\" as a Vector2", node); - - var (x, y) = foo.Value; - - result = new RXamlSingleVecLikeConstAstNode( - node, - types.Vector2, types.Vector2ConstructorFull, - types.Single, new[] {x, y}); - return true; - } - - if (type.Equals(types.Thickness)) - { - var foo = MathParsing.Thickness.Parse(text); - - if (!foo.Success) - throw new XamlLoadException($"Unable to parse \"{text}\" as a Thickness", node); - - var val = foo.Value; - float[] full; - if (val.Length == 1) - { - var u = val[0]; - full = new[] {u, u, u, u}; - } - else if (val.Length == 2) - { - var h = val[0]; - var v = val[1]; - full = new[] {h, v, h, v}; - } - else // 4 - { - full = val; - } - - result = new RXamlSingleVecLikeConstAstNode( - node, - types.Thickness, types.ThicknessConstructorFull, - types.Single, full); - return true; - } - - if (type.Equals(types.Thickness)) - { - var foo = MathParsing.Thickness.Parse(text); - - if (!foo.Success) - throw new XamlLoadException($"Unable to parse \"{text}\" as a Thickness", node); - - var val = foo.Value; - float[] full; - if (val.Length == 1) - { - var u = val[0]; - full = new[] {u, u, u, u}; - } - else if (val.Length == 2) - { - var h = val[0]; - var v = val[1]; - full = new[] {h, v, h, v}; - } - else // 4 - { - full = val; - } - - result = new RXamlSingleVecLikeConstAstNode( - node, - types.Thickness, types.ThicknessConstructorFull, - types.Single, full); - return true; - } - - if (type.Equals(types.Color)) - { - // TODO: Interpret these colors at XAML compile time instead of at runtime. - result = new RXamlColorAstNode(node, types, text); - return true; - } - - result = null; - return false; - } - - public const string ContextNameScopeFieldName = "RobustNameScope"; - - private static void EmitNameScopeField(XamlLanguageTypeMappings xamlLanguage, CecilTypeSystem typeSystem, IXamlTypeBuilder typeBuilder, IXamlILEmitter constructor) - { - var nameScopeType = typeSystem.FindType("Robust.Client.UserInterface.XAML.NameScope"); - var field = typeBuilder.DefineField(nameScopeType, - ContextNameScopeFieldName, true, false); - constructor - .Ldarg_0() - .Newobj(nameScopeType.GetConstructor()) - .Stfld(field); - } - } - - interface IResource : IFileSource - { - string Uri { get; } - string Name { get; } - void Remove(); - - } - - interface IResourceGroup - { - string Name { get; } - IEnumerable Resources { get; } - } -} diff --git a/Robust.Client/ClientIoC.cs b/Robust.Client/ClientIoC.cs index 9ffd524f601..b444bd09ed2 100644 --- a/Robust.Client/ClientIoC.cs +++ b/Robust.Client/ClientIoC.cs @@ -26,6 +26,7 @@ using Robust.Client.UserInterface; using Robust.Client.UserInterface.RichText; using Robust.Client.UserInterface.Themes; +using Robust.Client.UserInterface.XAML.Proxy; using Robust.Client.Utility; using Robust.Client.ViewVariables; using Robust.Shared; @@ -146,6 +147,16 @@ public static void RegisterIoC(GameController.DisplayMode mode, IDependencyColle deps.Register(); deps.Register(); deps.Register(); + +#if TOOLS + deps.Register(); + deps.Register(); +#else + deps.Register(); + deps.Register(); +#endif + + deps.Register(); deps.Register(); } } diff --git a/Robust.Client/GameController/GameController.cs b/Robust.Client/GameController/GameController.cs index f855d902ba2..bd162b6538c 100644 --- a/Robust.Client/GameController/GameController.cs +++ b/Robust.Client/GameController/GameController.cs @@ -19,6 +19,7 @@ using Robust.Client.Upload; using Robust.Client.UserInterface; using Robust.Client.UserInterface.RichText; +using Robust.Client.UserInterface.XAML.Proxy; using Robust.Client.Utility; using Robust.Client.ViewVariables; using Robust.Client.WebViewHook; @@ -53,6 +54,8 @@ internal sealed partial class GameController : IGameControllerInternal [Dependency] private readonly IResourceCacheInternal _resourceCache = default!; [Dependency] private readonly IResourceManagerInternal _resManager = default!; [Dependency] private readonly IRobustSerializer _serializer = default!; + [Dependency] private readonly IXamlProxyManager _xamlProxyManager = default!; + [Dependency] private readonly IXamlHotReloadManager _xamlHotReloadManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IClientNetManager _networkManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; @@ -171,6 +174,8 @@ internal bool StartupContinue(DisplayMode displayMode) _reflectionManager.Initialize(); _prototypeManager.Initialize(); _prototypeManager.LoadDefaultPrototypes(); + _xamlProxyManager.Initialize(); + _xamlHotReloadManager.Initialize(); _userInterfaceManager.Initialize(); _eyeManager.Initialize(); _entityManager.Initialize(); diff --git a/Robust.Client/Robust.Client.csproj b/Robust.Client/Robust.Client.csproj index 5ea5e896458..9cc646e6748 100644 --- a/Robust.Client/Robust.Client.csproj +++ b/Robust.Client/Robust.Client.csproj @@ -44,6 +44,10 @@ + + + + diff --git a/Robust.Client/UserInterface/XAML/Proxy/IXamlHotReloadManager.cs b/Robust.Client/UserInterface/XAML/Proxy/IXamlHotReloadManager.cs new file mode 100644 index 00000000000..f2eae45efa8 --- /dev/null +++ b/Robust.Client/UserInterface/XAML/Proxy/IXamlHotReloadManager.cs @@ -0,0 +1,20 @@ +namespace Robust.Client.UserInterface.XAML.Proxy; + +/// +/// This service locates the SS14 source tree and watches for changes to its xaml files. +/// +/// +/// It then reloads them instantly. +/// +/// It depends on and is stubbed on non-TOOLS builds. +/// +interface IXamlHotReloadManager +{ + /// + /// Initialize the hot reload manager. + /// + /// + /// You can't do anything with this once it's started, including turn it off. + /// + void Initialize(); +} diff --git a/Robust.Client/UserInterface/XAML/Proxy/IXamlProxyHelper.cs b/Robust.Client/UserInterface/XAML/Proxy/IXamlProxyHelper.cs new file mode 100644 index 00000000000..b242eb52e33 --- /dev/null +++ b/Robust.Client/UserInterface/XAML/Proxy/IXamlProxyHelper.cs @@ -0,0 +1,11 @@ +using System; + +namespace Robust.Client.UserInterface.XAML.Proxy; + +/// +/// Reexport the Populate method of and nothing else. +/// +public interface IXamlProxyHelper +{ + bool Populate(Type t, object o); +} diff --git a/Robust.Client/UserInterface/XAML/Proxy/IXamlProxyManager.cs b/Robust.Client/UserInterface/XAML/Proxy/IXamlProxyManager.cs new file mode 100644 index 00000000000..0d29f6233b0 --- /dev/null +++ b/Robust.Client/UserInterface/XAML/Proxy/IXamlProxyManager.cs @@ -0,0 +1,76 @@ +using System; + +namespace Robust.Client.UserInterface.XAML.Proxy; + +/// +/// This service provides a proxy for Populate, which is the generated function that +/// initializes the UI objects of a Xaml widget. +/// +/// +/// The proxy can always return false: in that case, a Xaml widget will self-populate +/// as usual. This is the behavior on Release builds. +/// +/// However, it can also call into an externally-provided implementation of the Xaml +/// widget. +/// +/// No source of externally-provided implementations actually exists, by default -- +/// you will need to call SetImplementation with a blob of xaml source code to provide +/// one. is an example of a service that calls into +/// that functionality. +/// +internal interface IXamlProxyManager +{ + /// + /// Initialize creates the . + /// + /// + /// If the is not a stub, then it will spy on the + /// assembly list (from ) + /// and find entries on the loaded types. + /// + void Initialize(); + + /// + /// Return true if at least one in the current project expects its XAML + /// to come from a file with the given name. + /// + /// + /// This method supports code that is trying to figure out what name the build process + /// would have assigned to a resource file. A caller can try a few candidate names and use + /// its "yes" to continue. + /// + /// This method is very fast, so it's OK to hammer it! + /// + /// Also, on a non-tools build, this always returns false. + /// + /// the filename + /// true if expected + bool CanSetImplementation(string fileName); + + /// + /// Replace the implementation of with , + /// compiling it if needed. + /// + /// All types based on will be recompiled. + /// + /// + /// This may fail and the caller won't be notified. (There will usually be logs.) + /// + /// On a non-tools build, this fails silently. + /// + /// the name of the file + /// the new content of the file + void SetImplementation(string fileName, string fileContent); + + /// + /// If we have a JIT version of the XAML code for , then call + /// the new implementation on . + /// + /// + /// may be a subclass of . + /// + /// the static type of the object + /// the object + /// true if we called a hot reloaded implementation + bool Populate(Type t, object o); +} diff --git a/Robust.Client/UserInterface/XAML/Proxy/XamlHotReloadManager.cs b/Robust.Client/UserInterface/XAML/Proxy/XamlHotReloadManager.cs new file mode 100644 index 00000000000..39669abaa3a --- /dev/null +++ b/Robust.Client/UserInterface/XAML/Proxy/XamlHotReloadManager.cs @@ -0,0 +1,195 @@ +#if TOOLS +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Robust.Shared.Asynchronous; +using Robust.Shared.ContentPack; +using Robust.Shared.IoC; +using Robust.Shared.Log; + +namespace Robust.Client.UserInterface.XAML.Proxy; + +/// +/// The real implementation of . +/// +/// +/// Its behavior is described there. +/// +internal sealed class XamlHotReloadManager : IXamlHotReloadManager +{ + private const string MarkerFileName = "SpaceStation14.sln"; + + [Dependency] ILogManager _logManager = null!; + [Dependency] private readonly IResourceManager _resources = null!; + [Dependency] private readonly ITaskManager _taskManager = null!; + [Dependency] private readonly IXamlProxyManager _xamlProxyManager = null!; + + private ISawmill _sawmill = null!; + private FileSystemWatcher? _watcher; + + public void Initialize() + { + _sawmill = _logManager.GetSawmill("xamlhotreload"); + var codeLocation = InferCodeLocation(); + + if (codeLocation == null) + { + _sawmill.Warning($"could not find code -- where is {MarkerFileName}?"); + return; + } + + _sawmill.Info($"code location: {codeLocation}"); + + // must not be gc'ed or else it will stop reporting + // therefore: keep a reference + _watcher = CreateWatcher(codeLocation); + } + + /// + /// Create a file system watcher that identifies XAML changes in a given + /// location. + /// + /// the location (a real path on the OS file system) + /// the new watcher + /// if violates its type-related postconditions + private FileSystemWatcher CreateWatcher(string location) + { + var watcher = new FileSystemWatcher(location) + { + IncludeSubdirectories = true, + NotifyFilter = NotifyFilters.LastWrite, + }; + + watcher.Changed += (_, args) => + { + switch (args.ChangeType) + { + case WatcherChangeTypes.Renamed: + case WatcherChangeTypes.Deleted: + return; + case WatcherChangeTypes.Created: + case WatcherChangeTypes.Changed: + case WatcherChangeTypes.All: + break; + default: + throw new ArgumentOutOfRangeException(nameof(args)); + } + + _taskManager.RunOnMainThread(() => + { + var resourceFileName = + ResourceFileName(location, args.FullPath, _xamlProxyManager.CanSetImplementation); + if (resourceFileName == null) + { + return; + } + + string newText; + try + { + newText = File.ReadAllText(args.FullPath); + } + catch (IOException ie) + { + _sawmill.Warning($"error attempting a hot reload -- skipped: {ie}"); + return; + } + + _xamlProxyManager.SetImplementation(resourceFileName, newText); + }); + }; + watcher.EnableRaisingEvents = true; + return watcher; + } + + /// + /// Using the content roots of the project, infer the location of its code. + /// + /// + /// This kind of introspection is almost universally a bad idea, but we don't + /// feasibly have other options, so I've buried it in a private method. + /// + /// the inferred code location or null + private string? InferCodeLocation() + { + // ascend upwards from each content root until the solution file is found + foreach (var contentRoot in _resources.GetContentRoots()) + { + var systemPath = contentRoot.ToRelativeSystemPath(); + while (true) + { + var files = Array.Empty(); + try + { + files = Directory.GetFiles(systemPath); + } + catch (IOException) { } // this is allowed to fail, and if so we just keep going up + + if (files.Any(f => Path.GetFileName(f).Equals(MarkerFileName, StringComparison.InvariantCultureIgnoreCase))) + { + return systemPath; + } + + DirectoryInfo? newPath = null; + try + { + newPath = Directory.GetParent(systemPath); + } + catch (IOException) { } // ditto here. if we don't find it, we're in the wrong place + + if (newPath == null) + { + break; + } + + systemPath = newPath.FullName; + } + } + + return null; + } + + /// + /// Infer the name of the resource file associated with the XAML item at the given path. + /// + /// the code location + /// the real path of the file + /// a function returning true if something expects this file + /// the name of a desired resource that matches this file, or null + private string? ResourceFileName(string codeLocation, string realPath, Predicate isDesired) + { + // start with the name of the file and systematically add each super-directory until we reach + // the inferred code location. + // + // for /home/pyrex/ss14/Content.Client/Instruments/UI/InstrumentMenu.xaml, the following names + // will be tried: + // + // - InstrumentMenu.xaml + // - UI.InstrumentMenu.xaml + // - Instruments.UI.InstrumentMenu.xaml + // - Content.Client.Instruments.UI.InstrumentMenu.xaml + var resourceFileName = Path.GetFileName(realPath); + var super = Directory.GetParent(realPath); + + var canonicalCodeLocation = Path.GetFullPath(codeLocation); + + while (true) + { + // did someone want it: OK, jump out + if (isDesired(resourceFileName)) + { + return resourceFileName; + } + + if (super == null || Path.GetFullPath(super.FullName) == canonicalCodeLocation) + { + return null; + } + + resourceFileName = super.Name + "." + resourceFileName; + super = super.Parent; + } + } +} +#endif diff --git a/Robust.Client/UserInterface/XAML/Proxy/XamlHotReloadManagerStub.cs b/Robust.Client/UserInterface/XAML/Proxy/XamlHotReloadManagerStub.cs new file mode 100644 index 00000000000..4979e6e73be --- /dev/null +++ b/Robust.Client/UserInterface/XAML/Proxy/XamlHotReloadManagerStub.cs @@ -0,0 +1,15 @@ +namespace Robust.Client.UserInterface.XAML.Proxy; + +/// +/// A stub implementation of . Its +/// behavior is to do nothing. +/// +internal sealed class XamlHotReloadManagerStub : IXamlHotReloadManager +{ + /// + /// Do nothing. + /// + public void Initialize() + { + } +} diff --git a/Robust.Client/UserInterface/XAML/Proxy/XamlImplementationStorage.cs b/Robust.Client/UserInterface/XAML/Proxy/XamlImplementationStorage.cs new file mode 100644 index 00000000000..833895b6353 --- /dev/null +++ b/Robust.Client/UserInterface/XAML/Proxy/XamlImplementationStorage.cs @@ -0,0 +1,221 @@ +#if TOOLS +using System; +using System.Collections.Generic; +using System.Reflection; +using Robust.Shared.Log; +using Robust.Xaml; + +namespace Robust.Client.UserInterface.XAML.Proxy; + +/// +/// This is a utility class that tracks the relationship between resource file names, +/// Xamlx-compatible s, s that are interested in a +/// given file, and implementations of Populate. +/// +internal sealed class XamlImplementationStorage +{ + /// + /// For each filename, we store its last known . + /// + /// + /// When we compile the new implementation, we will use the same . + /// + private readonly Dictionary _fileUri = new(); + + /// + /// For each filename, we store its last known content. + /// + /// + /// This is known even for AOT-compiled code -- therefore, we can use this table + /// to convert an AOT-compiled Control to a JIT-compiled one. + /// + private readonly Dictionary _fileContent = new(); + + /// + /// For each filename, we store the type interested in this file. + /// + private readonly Dictionary _fileType = new(); + + /// + /// For each type, store the JIT-compiled implementation of Populate. + /// + /// + /// If no such implementation exists, then methods that would normally + /// find and call a JIT'ed implementation will do nothing and return + /// false instead. As an ultimate result, the AOT'ed implementation + /// will be used. + /// + private readonly Dictionary _populateImplementations = new(); + + private readonly ISawmill _sawmill; + private readonly XamlJitDelegate _jitDelegate; + + /// + /// Create the storage. + /// + /// + /// It would be weird to call this from any type outside of + /// . + /// + /// the (shared) logger + /// + /// a delegate that calls the + /// , possibly handling errors + /// + public XamlImplementationStorage(ISawmill sawmill, XamlJitDelegate jitDelegate) + { + _sawmill = sawmill; + _jitDelegate = jitDelegate; + } + + /// + /// Inspect for types that declare a . + /// + /// + /// We can only do hot reloading if we know this basic information. + /// + /// Note that even release-mode content artifacts contain this attribute. + /// + /// the assembly + /// an IEnumerable of types with xaml metadata + private IEnumerable<(Type, XamlMetadataAttribute)> TypesWithXamlMetadata(Assembly assembly) + { + foreach (var type in assembly.GetTypes()) + { + if (type.GetCustomAttribute() is not { } attr) + { + continue; + } + + yield return (type, attr); + } + + } + + /// + /// Add all Xaml-annotated types from to this storage. + /// + /// + /// We don't JIT these types, but we store enough info that we could JIT + /// them if we wanted to. + /// + /// an assembly + public void Add(Assembly assembly) + { + foreach (var (type, metadata) in TypesWithXamlMetadata(assembly)) + { + // this can fail, but if it does, that means something is _really_ wrong + // with the compiler, or someone tried to write their own Xaml metadata + Uri uri; + try + { + uri = new Uri(metadata.Uri); + } + catch (UriFormatException) + { + throw new InvalidProgramException( + $"XamlImplementationStorage encountered an malformed Uri in the metadata for {type.FullName}: " + + $"{metadata.Uri}. this is a bug in XamlAotCompiler" + ); + } + + var fileName = metadata.FileName; + var content = metadata.Content; + + _fileUri[fileName] = uri; + _fileContent[fileName] = content; + + if (!_fileType.TryAdd(fileName, type)) + { + throw new InvalidProgramException( + $"XamlImplementationStorage observed that two types were interested in the same Xaml filename: " + + $"{fileName}. ({type.FullName} and {_fileType[fileName].FullName}). this is a bug in XamlAotCompiler" + ); + } + } + } + + /// + /// Quietly JIT every type with XAML metadata. + /// + /// + /// This should have no visible effect except that the + /// may dump some info messages into the terminal about cases where the + /// hot reload failed. + /// + public void ForceReloadAll() + { + foreach (var (fileName, fileContent) in _fileContent) + { + SetImplementation(fileName, fileContent, true); + } + } + + /// + /// Return true if calling on would not be a no-op. + /// + /// + /// That is: if some type cares about the contents of . + /// + /// the filename + /// true if not a no-op + public bool CanSetImplementation(string fileName) + { + return _fileType.ContainsKey(fileName); + } + + /// + /// Replace the implementation of by JIT-ing + /// . + /// + /// + /// If nothing cares about the implementation of , then this will do nothing. + /// + /// the name of the file whose implementation should be replaced + /// the new implementation + /// if true, then don't bother to log + public void SetImplementation(string fileName, string fileContent, bool quiet) + { + if (!_fileType.TryGetValue(fileName, out var type)) + { + _sawmill.Warning($"SetImplementation called with {fileName}, but no types care about its contents"); + return; + } + + var uri = + _fileUri.GetValueOrDefault(fileName) ?? + throw new InvalidProgramException("file URI missing (this is a bug in ImplementationStorage)"); + + if (!quiet) + { + _sawmill.Debug($"replacing {fileName} for {type}"); + } + var impl = _jitDelegate(type, uri, fileName, fileContent); + if (impl != null) + { + _populateImplementations[type] = impl; + } + _fileContent[fileName] = fileContent; + } + + /// + /// Call the JITed implementation of Populate on a XAML-associated object . + /// + /// If no JITed implementation exists, return false. + /// + /// the static type of + /// an instance of (can be a subclass) + /// true if a JITed implementation existed + public bool Populate(Type t, object o) + { + if (!_populateImplementations.TryGetValue(t, out var implementation)) + { + // pop out if we never JITed anything + return false; + } + + implementation.Invoke(null, [null, o]); + return true; + } +} +#endif diff --git a/Robust.Client/UserInterface/XAML/Proxy/XamlJitDelegate.cs b/Robust.Client/UserInterface/XAML/Proxy/XamlJitDelegate.cs new file mode 100644 index 00000000000..40aa0c01618 --- /dev/null +++ b/Robust.Client/UserInterface/XAML/Proxy/XamlJitDelegate.cs @@ -0,0 +1,17 @@ +using System; +using System.Reflection; + +namespace Robust.Client.UserInterface.XAML.Proxy; + +/// +/// This callback has the approximate type of , +/// but it has no error-signaling faculty. +/// +/// +/// Implementors of this delegate should inform the users of errors in their own way. +/// +/// Hot reloading failures should not directly take down the process, so implementors +/// should not rethrow exceptions unless they have a strong reason to believe they +/// will be caught. +/// +internal delegate MethodInfo? XamlJitDelegate(Type type, Uri uri, string filename, string content); diff --git a/Robust.Client/UserInterface/XAML/Proxy/XamlMetadataAttribute.cs b/Robust.Client/UserInterface/XAML/Proxy/XamlMetadataAttribute.cs new file mode 100644 index 00000000000..c3912685542 --- /dev/null +++ b/Robust.Client/UserInterface/XAML/Proxy/XamlMetadataAttribute.cs @@ -0,0 +1,29 @@ +using System; + +namespace Robust.Client.UserInterface.XAML.Proxy; + +/// +/// Metadata to support JIT compilation of XAML resources for a type. +/// +/// +/// We can feed XamlX data from this type, along with new content, to get new XAML +/// resources. +/// +/// This type is inert and is generated for release artifacts too, not just debug +/// artifacts. Released content should support hot reloading if loaded in a debug +/// client, but this is untested. +/// +[AttributeUsage(validOn: AttributeTargets.Class, Inherited = false)] +public sealed class XamlMetadataAttribute: System.Attribute +{ + public readonly string Uri; + public readonly string FileName; + public readonly string Content; + + public XamlMetadataAttribute(string uri, string fileName, string content) + { + Uri = uri; + FileName = fileName; + Content = content; + } +} diff --git a/Robust.Client/UserInterface/XAML/Proxy/XamlProxyHelper.cs b/Robust.Client/UserInterface/XAML/Proxy/XamlProxyHelper.cs new file mode 100644 index 00000000000..f6ff948064c --- /dev/null +++ b/Robust.Client/UserInterface/XAML/Proxy/XamlProxyHelper.cs @@ -0,0 +1,14 @@ +using System; +using Robust.Shared.IoC; + +namespace Robust.Client.UserInterface.XAML.Proxy; + +internal sealed class XamlProxyHelper: IXamlProxyHelper +{ + [Dependency] private IXamlProxyManager _xamlProxyManager = default!; + + public bool Populate(Type t, object o) + { + return _xamlProxyManager.Populate(t, o); + } +} diff --git a/Robust.Client/UserInterface/XAML/Proxy/XamlProxyManager.cs b/Robust.Client/UserInterface/XAML/Proxy/XamlProxyManager.cs new file mode 100644 index 00000000000..9d4d4dccec6 --- /dev/null +++ b/Robust.Client/UserInterface/XAML/Proxy/XamlProxyManager.cs @@ -0,0 +1,127 @@ +#if TOOLS +using System; +using System.Collections.Generic; +using System.Reflection; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Reflection; +using Robust.Xaml; + +namespace Robust.Client.UserInterface.XAML.Proxy; + +/// +/// The real implementation of . +/// +public sealed class XamlProxyManager: IXamlProxyManager +{ + ISawmill _sawmill = null!; + [Dependency] IReflectionManager _reflectionManager = null!; + [Dependency] ILogManager _logManager = null!; + + XamlImplementationStorage _xamlImplementationStorage = null!; + + List _knownAssemblies = []; + XamlJitCompiler? _xamlJitCompiler; + + /// + /// Initialize this, subscribing to assembly changes. + /// + public void Initialize() + { + _sawmill = _logManager.GetSawmill("xamlhotreload"); + _xamlImplementationStorage = new XamlImplementationStorage(_sawmill, Compile); + + AddAssemblies(); + _reflectionManager.OnAssemblyAdded += (_, _) => { AddAssemblies(); }; + } + + /// + /// Return true if setting the implementation of + /// would not be a no-op. + /// + /// the file name + /// true or false + public bool CanSetImplementation(string fileName) + { + return _xamlImplementationStorage.CanSetImplementation(fileName); + } + + /// + /// Replace the implementation of , failing + /// silently if the new content does not compile. (but still logging) + /// + /// the file name + /// the new content + public void SetImplementation(string fileName, string fileContent) + { + _xamlImplementationStorage.SetImplementation(fileName, fileContent, false); + } + + /// + /// Add all the types from all known assemblies, then force-JIT everything + /// again. + /// + private void AddAssemblies() + { + foreach (var a in _reflectionManager.Assemblies) + { + if (!_knownAssemblies.Contains(a)) + { + _knownAssemblies.Add(a); + _xamlImplementationStorage.Add(a); + + _xamlJitCompiler = null; + } + } + + // Always use the JITed versions on debug builds + _xamlImplementationStorage.ForceReloadAll(); + } + + /// + /// Populate using the JIT compiler, if possible. + /// + /// the static type of + /// a instance or subclass + /// true if there was a JITed implementation + public bool Populate(Type t, object o) + { + return _xamlImplementationStorage.Populate(t, o); + } + + /// + /// Calls using a stored + /// instance. + /// + /// the that cares about this Xaml + /// the of this xaml (from the type's metadata) + /// the filename of this xaml (from the type's metadata) + /// the new content of the xaml file + /// the MethodInfo for the new JITed implementation + private MethodInfo? Compile(Type t, Uri uri, string fileName, string content) + { + // initialize XamlJitCompiler lazily because constructing it has + // very high CPU cost + XamlJitCompiler xjit; + lock(this) + { + xjit = _xamlJitCompiler ??= new XamlJitCompiler(); + } + + var result = xjit.Compile(t, uri, fileName, content); + + if (result is XamlJitCompilerResult.Error e) + { + _sawmill.Info($"hot reloading failed: {t.FullName}; {fileName}; {e.Raw.Message} {e.Hint ?? ""}"); + return null; + } + + if (result is XamlJitCompilerResult.Success s) + { + return s.MethodInfo; + } + + throw new InvalidOperationException($"totally unexpected result from compiler operation: {result}"); + } +} +#endif diff --git a/Robust.Client/UserInterface/XAML/Proxy/XamlProxyManagerStub.cs b/Robust.Client/UserInterface/XAML/Proxy/XamlProxyManagerStub.cs new file mode 100644 index 00000000000..b3c93d7b669 --- /dev/null +++ b/Robust.Client/UserInterface/XAML/Proxy/XamlProxyManagerStub.cs @@ -0,0 +1,50 @@ +using System; + +namespace Robust.Client.UserInterface.XAML.Proxy; + +/// +/// The stub implementation of . +/// +public sealed class XamlProxyManagerStub: IXamlProxyManager +{ + /// + /// Do nothing. + /// + public void Initialize() + { + } + + /// + /// Return false. Nothing is ever interested in a Xaml content update when + /// hot reloading is off. + /// + /// the filename + /// false + public bool CanSetImplementation(string fileName) + { + return false; + } + + /// + /// Do nothing. A hot reload will always silently fail if hot reloading is off. + /// + /// + /// + public void SetImplementation(string fileName, string fileContent) + { + } + + /// + /// Return false. + /// + /// + /// There will never be a JIT-ed implementation of Populate if hot reloading is off. + /// + /// the static type of + /// an instance of or a subclass + /// false + public bool Populate(Type t, object o) + { + return false; + } +} diff --git a/Robust.Shared/ContentPack/Sandbox.yml b/Robust.Shared/ContentPack/Sandbox.yml index 07d1ba6a4fd..37770f25bd5 100644 --- a/Robust.Shared/ContentPack/Sandbox.yml +++ b/Robust.Shared/ContentPack/Sandbox.yml @@ -10,6 +10,8 @@ AllowedVerifierErrors: - InterfaceMethodNotImplemented # EVERYTHING in these namespaces is allowed. +# Note that, due to a historical bug in the sandbox, any namespace _prefixed_ with one of these +# is also allowed. (For instance, RobustBats.X, or ContentFarm.Y) WhitelistedNamespaces: - Robust - Content diff --git a/Robust.UnitTesting/RobustIntegrationTest.cs b/Robust.UnitTesting/RobustIntegrationTest.cs index 16231c68733..c983acf8136 100644 --- a/Robust.UnitTesting/RobustIntegrationTest.cs +++ b/Robust.UnitTesting/RobustIntegrationTest.cs @@ -17,6 +17,7 @@ using Robust.Client.Player; using Robust.Client.Timing; using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML.Proxy; using Robust.Server; using Robust.Server.Console; using Robust.Server.GameStates; @@ -929,6 +930,8 @@ private GameController Init() deps.Register(true); deps.Register(true); deps.Register(true); + deps.Register(true); + deps.Register(true); Options?.InitIoC?.Invoke(); deps.BuildGraph(); diff --git a/Robust.Xaml/AssemblyInfo.cs b/Robust.Xaml/AssemblyInfo.cs new file mode 100644 index 00000000000..8087f50c396 --- /dev/null +++ b/Robust.Xaml/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Robust.Client")] +[assembly: InternalsVisibleTo("Robust.Client.Injectors")] diff --git a/Robust.Xaml/CecilExtensions.cs b/Robust.Xaml/CecilExtensions.cs new file mode 100644 index 00000000000..3cc69d67383 --- /dev/null +++ b/Robust.Xaml/CecilExtensions.cs @@ -0,0 +1,32 @@ +using System; +using Mono.Cecil; + +namespace Robust.Xaml; + +/// +/// Source: https://github.com/jbevain/cecil/blob/master/Test/Mono.Cecil.Tests/Extensions.cs +/// +internal static class CecilExtensions +{ + /// + /// Specialize some generic parameters of a reference to a generic method. The return value + /// is a more monomorphized version. + /// + /// the original MethodReference + /// the specialized arguments + /// the monomorphic MethodReference + /// if the number of passed arguments is wrong + public static MethodReference MakeGenericMethod(this MethodReference self, params TypeReference[] arguments) + { + if (self.GenericParameters.Count != arguments.Length) + throw new ArgumentException(); + + var instance = new GenericInstanceMethod(self); + foreach (var argument in arguments) + { + instance.GenericArguments.Add(argument); + } + + return instance; + } +} diff --git a/Robust.Client.Injectors/Extensions.cs b/Robust.Xaml/Extensions.cs similarity index 88% rename from Robust.Client.Injectors/Extensions.cs rename to Robust.Xaml/Extensions.cs index 31d80cca7ab..c384c6d15c1 100644 --- a/Robust.Client.Injectors/Extensions.cs +++ b/Robust.Xaml/Extensions.cs @@ -1,11 +1,11 @@ using Microsoft.Build.Framework; -namespace Robust.Build.Tasks +namespace Robust.Xaml { /// /// Taken from https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/Extensions.cs /// - public static class Extensions + internal static class Extensions { //shamefully copied from avalonia public static void LogMessage(this IBuildEngine engine, string message, MessageImportance imp) diff --git a/Robust.Xaml/LowLevelCustomizations.cs b/Robust.Xaml/LowLevelCustomizations.cs new file mode 100644 index 00000000000..abad74fd5b7 --- /dev/null +++ b/Robust.Xaml/LowLevelCustomizations.cs @@ -0,0 +1,249 @@ +using System; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using Mono.Collections.Generic; +using XamlX.TypeSystem; + +namespace Robust.Xaml; + +/// +/// Class that performs find/replace operations on IL in assemblies that contain +/// SS14 content. +/// +/// +/// This code used to live in Robust.Client.Injectors. +/// +/// Paul Ritter wrote a lot of code that does low-level Cecil based patching +/// of AoT-compiled XamlX code. +/// +/// That's "fine" (it's not actually fine) -- this class just moves that all +/// to one place, and removes the extremely verbose Cecil-based type lookups +/// to a separate shared location. +/// +internal sealed class LowLevelCustomizations +{ + public const string TrampolineName = "!XamlIlPopulateTrampoline"; + public const int ExpectedNMetadataArgs = 3; + + private readonly CecilTypeSystem _typeSystem; + private readonly AssemblyDefinition _asm; + + private readonly TypeDefinition _iocManager; + private readonly TypeDefinition _iXamlProxyHelper; + private readonly TypeDefinition _systemType; + private readonly TypeDefinition _stringType; + private readonly TypeDefinition _xamlMetadataAttributeType; + + private readonly MethodReference _resolveXamlProxyHelperMethod; + private readonly MethodReference _populateMethod; + private readonly MethodReference _getTypeFromHandleMethod; + private readonly MethodReference _xamlMetadataAttributeConstructor; + + /// + /// Create a object. + /// + /// the + /// if some needed types were undefined + public LowLevelCustomizations(CecilTypeSystem typeSystem) + { + // resolve every type that we look for or substitute in when doing surgery + // what a mess! + _typeSystem = typeSystem; + _asm = typeSystem.TargetAssemblyDefinition; + + TypeDefinition ResolveType(string name) => + typeSystem.GetTypeReference(_typeSystem.FindType(name)).Resolve() + ?? throw new NullReferenceException($"type must exist: {name}"); + + _iocManager = ResolveType("Robust.Shared.IoC.IoCManager"); + _iXamlProxyHelper = ResolveType( + "Robust.Client.UserInterface.XAML.Proxy.IXamlProxyHelper" + ); + _resolveXamlProxyHelperMethod = _asm.MainModule.ImportReference( + _iocManager.Methods + .First(m => m.Name == "Resolve") + .MakeGenericMethod(_iXamlProxyHelper) + ); + + _populateMethod = _asm.MainModule.ImportReference( + _iXamlProxyHelper.Methods + .First(m => m.Name == "Populate") + ); + + _systemType = ResolveType("System.Type"); + + _getTypeFromHandleMethod = _asm.MainModule.ImportReference( + _systemType.Resolve() + .Methods + .First(m => m.Name == "GetTypeFromHandle") + ); + + _stringType = ResolveType("System.String"); + + _xamlMetadataAttributeType = ResolveType( + "Robust.Client.UserInterface.XAML.Proxy.XamlMetadataAttribute" + ); + + _xamlMetadataAttributeConstructor = _asm.MainModule.ImportReference( + _xamlMetadataAttributeType + .GetConstructors() + .First( + c => c.Parameters.Count == ExpectedNMetadataArgs && + c.Parameters.All(p => p.ParameterType.FullName == "System.String") + ) + ); + } + + /// + /// Creates a "trampoline" -- this is a method on the given subject which has the following general logic: + /// + /// + /// void TrampolineName(Subject subject) { + /// if (IoCManager.Resolve{XamlProxyHelper}().Populate(typeof(Subject), subject)) { + /// return; + /// } + /// aotPopulateMethod(null, subject) + /// } + /// + /// + /// + /// the type to create a trampoline on + /// the populate method to call if XamlProxyHelper's Populate method returns false + /// the new trampoline method + private MethodDefinition CreateTrampoline(TypeDefinition subject, MethodDefinition aotPopulateMethod) + { + var trampoline = new MethodDefinition( + TrampolineName, + MethodAttributes.Static | MethodAttributes.Private, + _asm.MainModule.TypeSystem.Void + ); + trampoline.Parameters.Add(new ParameterDefinition(subject)); + subject.Methods.Add(trampoline); + + void Emit(Instruction i) => trampoline.Body.Instructions.Add(i); + + Emit(Instruction.Create(OpCodes.Call, _resolveXamlProxyHelperMethod)); + Emit(Instruction.Create(OpCodes.Ldtoken, subject)); + Emit(Instruction.Create(OpCodes.Call, _getTypeFromHandleMethod)); + Emit(Instruction.Create(OpCodes.Ldarg_0)); + Emit(Instruction.Create(OpCodes.Callvirt, _populateMethod)); + + var ret = Instruction.Create(OpCodes.Ret); + Emit(Instruction.Create(OpCodes.Brtrue_S, ret)); + Emit(Instruction.Create(OpCodes.Ldnull)); + Emit(Instruction.Create(OpCodes.Ldarg_0)); + Emit(Instruction.Create(OpCodes.Call, aotPopulateMethod)); + Emit(ret); + + return trampoline; + } + + /// + /// Creates a trampoline on , then replaces + /// calls to RobustXamlLoader.Load with calls to the generated trampoline. + /// Returns true if the patching succeeded. + /// + /// the subject + /// the populate method + /// true + public bool TrampolineCallsToXamlLoader(TypeDefinition subject, MethodDefinition aotPopulateMethod) + { + // PYREX NOTE: This logic is brittle and has a lot of cases + // I do not understand all of them, but I have faithfully ported them + // Paul Ritter wrote most of this + var trampoline = CreateTrampoline(subject, aotPopulateMethod); + + var foundXamlLoader = false; + // Find RobustXamlLoader.Load(this) and replace it with !XamlIlPopulateTrampoline(this) + foreach (var method in subject.Methods + .Where(m => !m.Attributes.HasFlag(MethodAttributes.Static))) + { + var i = method.Body.Instructions; + for (var c = 1; c < i.Count; c++) + { + if (i[c].OpCode == OpCodes.Call) + { + var op = i[c].Operand as MethodReference; + + if (op != null + && op.Name == TrampolineName) + { + foundXamlLoader = true; + break; + } + + if (op != null + && op.Name == "Load" + && op.Parameters.Count == 1 + && op.Parameters[0].ParameterType.FullName == "System.Object" + && op.DeclaringType.FullName == "Robust.Client.UserInterface.XAML.RobustXamlLoader") + { + if (MatchThisCall(i, c - 1)) + { + i[c].Operand = trampoline; + foundXamlLoader = true; + } + } + } + } + } + + if (!foundXamlLoader) + { + var ctors = subject.GetConstructors() + .Where(c => !c.IsStatic) + .ToList(); + // We can inject xaml loader into default constructor + if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o => o.OpCode != OpCodes.Nop) == 3) + { + var i = ctors[0].Body.Instructions; + var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret)); + i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampoline)); + i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0)); + foundXamlLoader = true; + } + } + + return foundXamlLoader; + } + + private static bool MatchThisCall(Collection instructions, int idx) + { + var i = instructions[idx]; + // A "normal" way of passing `this` to a static method: + + // ldarg.0 + // call void [uAvalonia.Markup.Xaml]Avalonia.Markup.Xaml.AvaloniaXamlLoader::Load(object) + + return i.OpCode == OpCodes.Ldarg_0 || (i.OpCode == OpCodes.Ldarg && i.Operand?.Equals(0) == true); + } + + /// + /// Add a XamlMetadataAttribute to a given type, containing all the compiler + /// parameters for its Populate method. + /// + /// the subject type + /// the URI we generated + /// the filename + /// the new content + public void AddXamlMetadata(TypeDefinition subject, Uri uri, string filename, string content) + { + var attribute = new CustomAttribute(_xamlMetadataAttributeConstructor); + var args = new string[ExpectedNMetadataArgs] // reference this so that changing the number is a compile error + { + uri.ToString(), filename, content + }; + + foreach (var arg in args) + { + attribute.ConstructorArguments.Add( + new CustomAttributeArgument(_stringType, arg) + ); + } + + subject.CustomAttributes.Add(attribute); + } + +} diff --git a/Robust.Xaml/MathParsing.cs b/Robust.Xaml/MathParsing.cs new file mode 100644 index 00000000000..c46c9977142 --- /dev/null +++ b/Robust.Xaml/MathParsing.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace Robust.Xaml; + +internal static class MathParsing +{ + private static float[]? ParseSingleArr(string input) + { + // Transliteration note: The original patterns in this file were Pidgin parsers + // All of them were variations on Real.Select(c => (float c)).Between(SkipWhiteSpaces).Repeat(n) + // They somehow handled commas too, but I don't know how + // + // SkipWhitespace splits based on char.IsWhitespace: + // https://github.com/benjamin-hodgson/Pidgin/blob/cc72abb/Pidgin/Parser.Whitespace.cs#L30 + var items = SplitStringByFunction(input, (c) => c == ',' || char.IsWhiteSpace(c)); + var outs = new float[items.Count]; + + for (var i = 0; i < outs.Length; i++) + { + // Parser.Real ultimately resorts to double.TryParse + // https://github.com/benjamin-hodgson/Pidgin/blob/cc72abb/Pidgin/Parser.Number.cs#L222 + var parsed = double.TryParse( + items[i], + NumberStyles.Float | NumberStyles.AllowExponent | NumberStyles.AllowLeadingSign, + CultureInfo.InvariantCulture, + out var d + ); + if (!parsed) + { + return null; + } + outs[i] = (float)d; + } + + return outs; + } + + private static List SplitStringByFunction(string s, Func isSeparator) + { + // we want to split by commas _or_ char.IsWhitespace + // C#'s Split() can do one but not both + var splitItems = new List(); + var itemInProgress = new StringBuilder(); + foreach (var c in s) + { + if (isSeparator(c)) + { + if (itemInProgress.Length > 0) + { + splitItems.Add(itemInProgress.ToString()); + itemInProgress.Clear(); + } + } + else + { + itemInProgress.Append(c); + } + } + + if (itemInProgress.Length > 0) + { + splitItems.Add(itemInProgress.ToString()); + } + + return splitItems; + } + + /// + /// Parse a vector of two floats separated by commas or spaces, such as + /// "1,2" or "1.5 2.5" + /// + /// the string representation of the vector + /// the parsed floats, or null if parsing failed + public static (float, float)? ParseVector2(string s) + { + var arr = ParseSingleArr(s); + if (arr == null) + { + return null; + } + if (arr.Length == 2) + { + return (arr[0], arr[1]); + } + + return null; + } + + /// + /// Parse a vector of one, two, or four floats separated by commas or + /// spaces, such as "1", "1e2,2e3" or ".1,.2,.3,.4" + /// + /// the string representation of the vector + /// the parsed floats, or null if parsing failed + public static float[]? ParseThickness(string s) + { + var arr = ParseSingleArr(s); + if (arr == null) + { + return null; + } + if (arr.Length == 1 || arr.Length == 2 || arr.Length == 4) + { + return arr; + } + + return null; + } +} diff --git a/Robust.Client.Injectors/RXamlColorAstNode.cs b/Robust.Xaml/RXamlColorAstNode.cs similarity index 91% rename from Robust.Client.Injectors/RXamlColorAstNode.cs rename to Robust.Xaml/RXamlColorAstNode.cs index 2df0f542ad5..ae03efd8822 100644 --- a/Robust.Client.Injectors/RXamlColorAstNode.cs +++ b/Robust.Xaml/RXamlColorAstNode.cs @@ -1,10 +1,9 @@ -using System.Reflection.Emit; -using XamlX.Ast; +using XamlX.Ast; using XamlX.Emit; using XamlX.IL; using XamlX.TypeSystem; -namespace Robust.Build.Tasks +namespace Robust.Xaml { internal class RXamlColorAstNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode diff --git a/Robust.Client.Injectors/RXamlVecLikeConstAstNode.cs b/Robust.Xaml/RXamlVecLikeConstAstNode.cs similarity index 91% rename from Robust.Client.Injectors/RXamlVecLikeConstAstNode.cs rename to Robust.Xaml/RXamlVecLikeConstAstNode.cs index 0bc3bea7c39..0d7d93141b0 100644 --- a/Robust.Client.Injectors/RXamlVecLikeConstAstNode.cs +++ b/Robust.Xaml/RXamlVecLikeConstAstNode.cs @@ -6,9 +6,9 @@ using XamlX.IL; using XamlX.TypeSystem; -namespace Robust.Build.Tasks +namespace Robust.Xaml { - public abstract class RXamlVecLikeConstAstNode + internal abstract class RXamlVecLikeConstAstNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode where T : unmanaged { @@ -47,7 +47,7 @@ public virtual XamlILNodeEmitResult Emit( } } - public sealed class RXamlSingleVecLikeConstAstNode : RXamlVecLikeConstAstNode + internal sealed class RXamlSingleVecLikeConstAstNode : RXamlVecLikeConstAstNode { public RXamlSingleVecLikeConstAstNode( IXamlLineInfo lineInfo, @@ -69,7 +69,7 @@ public override XamlILNodeEmitResult Emit(XamlEmitContext + internal sealed class RXamlInt32VecLikeConstAstNode : RXamlVecLikeConstAstNode { public RXamlInt32VecLikeConstAstNode( IXamlLineInfo lineInfo, diff --git a/Robust.Client.Injectors/RXamlWellKnownTypes.cs b/Robust.Xaml/RXamlWellKnownTypes.cs similarity index 97% rename from Robust.Client.Injectors/RXamlWellKnownTypes.cs rename to Robust.Xaml/RXamlWellKnownTypes.cs index a95f437edba..e84637cbc28 100644 --- a/Robust.Client.Injectors/RXamlWellKnownTypes.cs +++ b/Robust.Xaml/RXamlWellKnownTypes.cs @@ -2,9 +2,9 @@ using XamlX.Transform; using XamlX.TypeSystem; -namespace Robust.Build.Tasks +namespace Robust.Xaml { - class RXamlWellKnownTypes + internal class RXamlWellKnownTypes { public XamlTypeWellKnownTypes XamlIlTypes { get; } public IXamlType Single { get; } diff --git a/Robust.Xaml/Robust.Xaml.csproj b/Robust.Xaml/Robust.Xaml.csproj new file mode 100644 index 00000000000..ccc7d2cabf1 --- /dev/null +++ b/Robust.Xaml/Robust.Xaml.csproj @@ -0,0 +1,17 @@ + + + netstandard2.0 + 12.0 + enable + + + + + + + + + + + + diff --git a/Robust.Client.Injectors/RobustXamlILCompiler.cs b/Robust.Xaml/RobustXamlILCompiler.cs similarity index 93% rename from Robust.Client.Injectors/RobustXamlILCompiler.cs rename to Robust.Xaml/RobustXamlILCompiler.cs index 549897c9735..ec95be23392 100644 --- a/Robust.Client.Injectors/RobustXamlILCompiler.cs +++ b/Robust.Xaml/RobustXamlILCompiler.cs @@ -1,5 +1,4 @@ -using System.Diagnostics; -using System.Linq; +using System.Linq; using XamlX; using XamlX.Ast; using XamlX.Emit; @@ -7,7 +6,7 @@ using XamlX.Transform; using XamlX.TypeSystem; -namespace Robust.Build.Tasks +namespace Robust.Xaml { /// /// Emitters & Transformers based on: @@ -15,7 +14,7 @@ namespace Robust.Build.Tasks /// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs /// - https://github.com/AvaloniaUI/Avalonia/blob/afb8ae6f3c517dae912729511483995b16cb31af/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs /// - public class RobustXamlILCompiler : XamlILCompiler + internal class RobustXamlILCompiler : XamlILCompiler { public RobustXamlILCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings emitMappings, bool fillWithDefaults) : base(configuration, emitMappings, fillWithDefaults) { @@ -41,8 +40,9 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod && mg.Children.OfType().Any()) return node; - IXamlAstValueNode value = null; + IXamlAstValueNode? value = null; for (var c = 0; c < pa.Values.Count; c++) + { if (pa.Values[c].Type.GetClrType().Equals(context.Configuration.WellKnownTypes.String)) { value = pa.Values[c]; @@ -57,6 +57,7 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod break; } + } if (value != null) { @@ -84,9 +85,9 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod class RobustNameScopeRegistrationXamlIlNode : XamlAstNode, IXamlAstManipulationNode { public IXamlAstValueNode Name { get; set; } - public IXamlType TargetType { get; } + public IXamlType? TargetType { get; } - public RobustNameScopeRegistrationXamlIlNode(IXamlAstValueNode name, IXamlType targetType) : base(name) + public RobustNameScopeRegistrationXamlIlNode(IXamlAstValueNode name, IXamlType? targetType) : base(name) { TargetType = targetType; Name = name; @@ -104,7 +105,7 @@ public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContextWithLocals - f.Name == XamlCompiler.ContextNameScopeFieldName); + f.Name == XamlCustomizations.ContextNameScopeFieldName); var namescopeRegisterFunction = context.Configuration.TypeSystem .FindType("Robust.Client.UserInterface.XAML.NameScope").Methods .First(m => m.Name == "Register"); @@ -128,7 +129,7 @@ public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContextWithLocals - f.Name == XamlCompiler.ContextNameScopeFieldName); + f.Name == XamlCustomizations.ContextNameScopeFieldName); var controlNameScopeField = controlType.Fields.First(f => f.Name == "NameScope"); var nameScopeType = context.Configuration.TypeSystem .FindType("Robust.Client.UserInterface.XAML.NameScope"); diff --git a/Robust.Client.Injectors/XamlCompiler.Helpers.cs b/Robust.Xaml/XamlAotCompiler.Helpers.cs similarity index 68% rename from Robust.Client.Injectors/XamlCompiler.Helpers.cs rename to Robust.Xaml/XamlAotCompiler.Helpers.cs index 73a5f72ceed..0808f46cb9b 100644 --- a/Robust.Client.Injectors/XamlCompiler.Helpers.cs +++ b/Robust.Xaml/XamlAotCompiler.Helpers.cs @@ -1,33 +1,23 @@ using System.Collections.Generic; using System.Linq; using Mono.Cecil; -using Mono.Cecil.Cil; -using Mono.Collections.Generic; using XamlX.TypeSystem; -namespace Robust.Build.Tasks +namespace Robust.Xaml { /// - /// Helpers taken from: + /// Helpers taken from AvaloniaUI on GitHub. + /// + /// /// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs /// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs - /// - public partial class XamlCompiler + /// + internal partial class XamlAotCompiler { - static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml") - || r.Name.ToLowerInvariant().EndsWith(".paml") - || r.Name.ToLowerInvariant().EndsWith(".axaml"); - - private static bool MatchThisCall(Collection instructions, int idx) - { - var i = instructions[idx]; - // A "normal" way of passing `this` to a static method: - - // ldarg.0 - // call void [Avalonia.Markup.Xaml]Avalonia.Markup.Xaml.AvaloniaXamlLoader::Load(object) + private static readonly string[] NameSuffixes = {".xaml", ".paml", ".axaml"}; - return i.OpCode == OpCodes.Ldarg_0 || (i.OpCode == OpCodes.Ldarg && i.Operand?.Equals(0) == true); - } + static bool CheckXamlName(IResource r) => + NameSuffixes.Any(suffix => r.Name.ToLowerInvariant().EndsWith(suffix)); interface IResource : IFileSource { diff --git a/Robust.Xaml/XamlAotCompiler.cs b/Robust.Xaml/XamlAotCompiler.cs new file mode 100644 index 00000000000..0c41140b6e6 --- /dev/null +++ b/Robust.Xaml/XamlAotCompiler.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Build.Framework; +using Mono.Cecil; +using XamlX; +using XamlX.Ast; +using XamlX.IL; +using XamlX.Parsers; +using XamlX.TypeSystem; + +namespace Robust.Xaml; + +/// +/// Utility class: holds scope information for a Microsoft.Build.Framework +/// build in order to AOT-compile the XAML resources for an assembly. +/// +/// +/// Also embed enough information to support future JIT attempts on those same resources. +/// +/// Code primarily by Paul Ritter, touched by Pyrex in 2024. +/// +/// Based on https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +/// Adjusted for our UI Framework +/// +internal partial class XamlAotCompiler +{ + /// + /// Update the assembly whose name is , then + /// save an updated assembly to . + /// + /// the Microsoft build engine (used for logging) + /// the input assembly by name + /// all the assemblies that the input Xaml is allowed to reference + /// the place to put the output assembly + /// + /// a file to use in order to generate a "strong name" for the assembly + /// (https://learn.microsoft.com/en-us/dotnet/standard/assembly/strong-named) + /// + /// + /// true if this succeeds and + /// true if the result was written to + /// + public static (bool success, bool writtentofile) Compile(IBuildEngine engine, string input, string[] references, + string output, string? strongNameKey) + { + var typeSystem = new CecilTypeSystem(references + .Where(r => !r.ToLowerInvariant().EndsWith("robust.build.tasks.dll")) + .Concat(new[] { input }), input); + + var asm = typeSystem.TargetAssemblyDefinition; + + if (asm.MainModule.GetType("CompiledRobustXaml", "XamlIlContext") != null) + { + // If this type exists, the assembly has already been processed by us. + // Do not run again, it would corrupt the file. + // This *shouldn't* be possible due to Inputs/Outputs dependencies in the build system, + // but better safe than sorry eh? + engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", "", 0, 0, 0, 0, "Ran twice on same assembly file; ignoring.", "", "")); + return (true, false); + } + + var compileRes = CompileCore(engine, typeSystem); + if (!compileRes) + return (false, false); + + var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols }; + if (!string.IsNullOrWhiteSpace(strongNameKey)) + writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey); + + asm.Write(output, writerParameters); + + return (true, true); + + } + + /// + /// For each XAML resource, identify its affiliated class, invoke the + /// AOT compiler, update the class to call into the generated code, + /// and write down metadata for future JIT compiles. + /// + /// the Microsoft build engine (for logging) + /// the type system (which includes info about the target assembly) + /// true if compilation succeeded in every case + static bool CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem) + { + var asm = typeSystem.TargetAssemblyDefinition; + var embrsc = new EmbeddedResources(asm); + + var xaml = new XamlCustomizations(typeSystem, typeSystem.TargetAssembly); + var lowLevel = new LowLevelCustomizations(typeSystem); + + var contextDef = new TypeDefinition("CompiledRobustXaml", "XamlIlContext", + TypeAttributes.Class, asm.MainModule.TypeSystem.Object); + asm.MainModule.Types.Add(contextDef); + var contextClass = XamlILContextDefinition.GenerateContextClass( + typeSystem.CreateTypeBuilder(contextDef), typeSystem, + xaml.TypeMappings, xaml.EmitMappings + ); + + bool CompileGroup(IResourceGroup group) + { + var typeDef = new TypeDefinition("CompiledRobustXaml", "!" + group.Name, TypeAttributes.Class, + asm.MainModule.TypeSystem.Object); + + asm.MainModule.Types.Add(typeDef); + + foreach (var res in group.Resources.Where(CheckXamlName)) + { + try + { + engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", MessageImportance.Low); + + var xamlText = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd(); + var parsed = XDocumentXamlParser.Parse(xamlText); + + var initialRoot = (XamlAstObjectNode) parsed.Root; + + var classDirective = initialRoot.Children.OfType() + .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class"); + string classname; + if (classDirective != null && classDirective.Values[0] is XamlAstTextNode tn) + { + classname = tn.Text; + } + else + { + classname = res.Name.Replace(".xaml",""); + } + + var classType = typeSystem.TargetAssembly.FindType(classname); + if (classType == null) + throw new InvalidProgramException($"Unable to find type '{classname}'"); + + xaml.ILCompiler.Transform(parsed); + + var populateName = $"Populate:{res.Name}"; + + var classTypeDefinition = typeSystem.GetTypeReference(classType).Resolve()!; + var populateBuilder = typeSystem.CreateTypeBuilder(classTypeDefinition); + + xaml.ILCompiler.Compile(parsed, contextClass, + xaml.ILCompiler.DefinePopulateMethod(populateBuilder, parsed, populateName, true), + null, + null, + (closureName, closureBaseType) => + populateBuilder.DefineSubType(closureBaseType, closureName, false), + res.Uri, res + ); + + var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve().Methods + .First(m => m.Name == populateName); + + lowLevel.AddXamlMetadata(classTypeDefinition, new Uri(res.Uri), res.FilePath, xamlText); + var foundXamlLoader = lowLevel.TrampolineCallsToXamlLoader(classTypeDefinition, compiledPopulateMethod); + + if (!foundXamlLoader) + { + throw new InvalidProgramException( + $"No call to RobustXamlLoader.Load(this) call found anywhere in the type {classType.FullName} and type seems to have custom constructors."); + } + } + catch (Exception e) + { + engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0, + $"{res.FilePath}: {e.Message}", "", "CompileRobustXaml")); + } + res.Remove(); + } + return true; + } + + if (embrsc.Resources.Count(CheckXamlName) != 0) + { + if (!CompileGroup(embrsc)) + { + return false; + } + } + + return true; + } +} + +/// +/// This is from XamlX, augmented with the other +/// arguments that the XAML compiler wants. +/// +/// +/// We store these later in the build process inside a XamlMetadataAttribute, +/// in order to support JIT compilation. +/// +interface IResource : IFileSource +{ + string Uri { get; } + string Name { get; } + void Remove(); + +} + +/// +/// A named collection of s. +/// +interface IResourceGroup +{ + string Name { get; } + IEnumerable Resources { get; } +} diff --git a/Robust.Xaml/XamlCustomizations.cs b/Robust.Xaml/XamlCustomizations.cs new file mode 100644 index 00000000000..870eb6cb9f9 --- /dev/null +++ b/Robust.Xaml/XamlCustomizations.cs @@ -0,0 +1,218 @@ +using XamlX; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; +using XamlX.Transform; +using XamlX.TypeSystem; + +namespace Robust.Xaml; + +/// +/// Shared XAML config info that both the AOT and JIT compiler can use. +/// +/// +/// This is a bunch of code primarily written by PJB that originally appeared in XamlAotCompiler.cs. +/// +internal sealed class XamlCustomizations +{ + public const string ContextNameScopeFieldName = "RobustNameScope"; + public readonly IXamlTypeSystem TypeSystem; + public readonly XamlLanguageTypeMappings TypeMappings; + public readonly XamlLanguageEmitMappings EmitMappings; + public readonly TransformerConfiguration TransformerConfiguration; + public readonly RobustXamlILCompiler ILCompiler; + + /// + /// Create and hold a bunch of resources related to SS14's particular dialect of XAML. + /// + /// + /// the type system for XamlX to use + /// (both and work) + /// + /// the default assembly (for unqualified names to be looked up in) + public XamlCustomizations(IXamlTypeSystem typeSystem, IXamlAssembly defaultAssembly) + { + TypeSystem = typeSystem; + TypeMappings = new XamlLanguageTypeMappings(typeSystem) + { + XmlnsAttributes = + { + typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute"), + + }, + ContentAttributes = + { + typeSystem.GetType("Avalonia.Metadata.ContentAttribute") + }, + UsableDuringInitializationAttributes = + { + typeSystem.GetType("Robust.Client.UserInterface.XAML.UsableDuringInitializationAttribute") + }, + DeferredContentPropertyAttributes = + { + typeSystem.GetType("Robust.Client.UserInterface.XAML.DeferredContentAttribute") + }, + RootObjectProvider = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestRootObjectProvider"), + UriContextProvider = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestUriContext"), + ProvideValueTarget = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestProvideValueTarget"), + }; + + EmitMappings = new XamlLanguageEmitMappings + { + ContextTypeBuilderCallback = EmitNameScopeField + }; + TransformerConfiguration = new TransformerConfiguration( + typeSystem, + defaultAssembly, + TypeMappings, + XamlXmlnsMappings.Resolve(typeSystem, TypeMappings), + CustomValueConverter + ); + ILCompiler = new RobustXamlILCompiler(TransformerConfiguration, EmitMappings, true); + } + + /// + /// Create a field of type NameScope that contains a new NameScope, then + /// alter the type's constructor to initialize that field. + /// + /// the type to alter + /// the constructor to alter + private void EmitNameScopeField( + IXamlTypeBuilder typeBuilder, + IXamlILEmitter constructor + ) + { + var nameScopeType = TypeSystem.FindType("Robust.Client.UserInterface.XAML.NameScope"); + var field = typeBuilder.DefineField(nameScopeType, + ContextNameScopeFieldName, + true, + false); + constructor + .Ldarg_0() + .Newobj(nameScopeType.GetConstructor()) + .Stfld(field); + } + + + /// + /// Convert a to some other kind of node, + /// if the purpose of the node appears to be to represent one of various + /// builtin types. + /// + /// + /// (See, for instance, .) + /// + /// The arguments here come from an interface built into XamlX. + /// + /// context object that holds the TransformerConfiguration + /// the node to consider rewriting + /// the type of that node + /// results get written to here + /// + /// if the literal for a type is poorly spelled for that type + private static bool CustomValueConverter( + AstTransformationContext context, + IXamlAstValueNode node, + IXamlType type, + out IXamlAstValueNode? result) + { + if (!(node is XamlAstTextNode textNode)) + { + result = null; + return false; + } + + var text = textNode.Text; + var types = context.GetRobustTypes(); + + if (type.Equals(types.Vector2)) + { + var foo = MathParsing.ParseVector2(text); + + if (foo == null) + { + throw new XamlLoadException($"Unable to parse \"{text}\" as a Vector2", node); + } + + var (x, y) = foo.Value; + + result = new RXamlSingleVecLikeConstAstNode( + node, + types.Vector2, + types.Vector2ConstructorFull, + types.Single, + new[] { x, y }); + return true; + } + + if (type.Equals(types.Thickness)) + { + var foo = MathParsing.ParseThickness(text); + + if (foo == null) + { + throw new XamlLoadException($"Unable to parse \"{text}\" as a Thickness", node); + } + + var val = foo; + float[] full; + if (val.Length == 1) + { + var u = val[0]; + full = new[] { u, u, u, u }; + } + else if (val.Length == 2) + { + var h = val[0]; + var v = val[1]; + full = new[] { h, v, h, v }; + } + else // 4 + { + full = val; + } + + result = new RXamlSingleVecLikeConstAstNode( + node, + types.Thickness, + types.ThicknessConstructorFull, + types.Single, + full); + return true; + } + + if (type.Equals(types.Color)) + { + // TODO: Interpret these colors at XAML compile time instead of at runtime. + result = new RXamlColorAstNode(node, types, text); + return true; + } + + result = null; + return false; + } + + /// + /// Wrap the and + /// from a Xaml file or from a XamlMetadataAttribute. + /// + /// + /// This interface is the primary input format that XamlX expects. + /// + /// the resource file path + /// the contents + /// IFileSource + public IFileSource CreateFileSource(string filePath, byte[] contents) + { + return new InternalFileSource(filePath, contents); + } + + /// + /// A trivial implementation of . + /// + private class InternalFileSource(string filePath, byte[] contents) : IFileSource + { + public string FilePath { get; } = filePath; + public byte[] FileContents { get; } = contents; + } +} diff --git a/Robust.Xaml/XamlJitCompiler.cs b/Robust.Xaml/XamlJitCompiler.cs new file mode 100644 index 00000000000..ce3eea0c872 --- /dev/null +++ b/Robust.Xaml/XamlJitCompiler.cs @@ -0,0 +1,180 @@ +using System; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading; +using XamlX.IL; +using XamlX.Parsers; + +namespace Robust.Xaml; + +/// +/// A JIT compiler for Xaml. +/// +/// +/// Uses , which can find types +/// at runtime without looking for their assemblies on disk. +/// +internal sealed class XamlJitCompiler +{ + private readonly SreTypeSystem _typeSystem; + + private static int _assemblyId; + + /// + /// Construct a XamlJitCompiler. + /// + /// + /// No configuration is needed or possible. + /// + /// This is a pretty expensive function because it creates an + /// , which requires going through the loaded + /// assembly list. + /// + public XamlJitCompiler() + { + _typeSystem = new SreTypeSystem(); + } + + /// + /// Generate a name for a new dynamic assembly. + /// + /// the new name + private static string GenerateAssemblyName() + { + // make the name unique (even though C# possibly doesn't care) + return + $"{nameof(XamlJitCompiler)}_{Interlocked.Increment(ref _assemblyId)}"; + } + + /// + /// Compile the Populate method for , using the given + /// uri/path/contents. + /// + /// + /// These values (except for contents) are generated during the AOT compile + /// process. + /// + /// It is not enforced that they be the same after JIT -- the JITed code has + /// no knowledge of the state of the AOT'ed code -- but our code and + /// documentation do assume that. + /// + /// the type whose Populate method should be generated + /// the Uri associated with the Control + /// the resource file path for the control + /// the contents of the new XAML file + /// Success or Failure depending on whether an error was thrown + public XamlJitCompilerResult Compile( + Type t, + Uri uri, + string filePath, + string contents + ) + { + try + { + var result = CompileOrCrash(t, uri, filePath, contents); + return new XamlJitCompilerResult.Success(result); + } + catch (Exception e) + { + return new XamlJitCompilerResult.Error( + e, + e.Message.StartsWith("Unable to resolve type") + ? "Is the type internal? (hot reloading can't handle that right now!)" + : null + ); + } + } + + private MethodInfo CompileOrCrash( + Type t, + Uri uri, + string filePath, + string contents + ) + { + + var xaml = new XamlCustomizations(_typeSystem, _typeSystem.FindAssembly(t.Assembly.FullName)); + + // attempt to parse the code + var document = XDocumentXamlParser.Parse(contents); + + // generate a toy assembly to contain everything we make + var assemblyNameString = GenerateAssemblyName(); + var assemblyName = new AssemblyName(assemblyNameString); + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( + assemblyName, + AssemblyBuilderAccess.RunAndCollect + ); + var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyNameString); + + var contextClassRawBuilder = moduleBuilder.DefineType("ContextClass"); + var populateClassRawBuilder = moduleBuilder.DefineType("PopulateClass"); + + var contextClassBuilder = _typeSystem.CreateTypeBuilder(contextClassRawBuilder); + var populateClassBuilder = _typeSystem.CreateTypeBuilder(populateClassRawBuilder); + + var contextClass = XamlILContextDefinition.GenerateContextClass( + contextClassBuilder, + _typeSystem, + xaml.TypeMappings, + xaml.EmitMappings + ); + var populateName = "!Populate"; + + xaml.ILCompiler.Transform(document); + xaml.ILCompiler.Compile( + document, + contextClass, + xaml.ILCompiler.DefinePopulateMethod( + populateClassBuilder, + document, + populateName, + true + ), + null, + null, + (closureName, closureBaseType) => + contextClassBuilder.DefineSubType(closureBaseType, closureName, false), + uri.ToString(), + xaml.CreateFileSource(filePath, Encoding.UTF8.GetBytes(contents)) + ); + + contextClassBuilder.CreateType(); + + var populateClass = populateClassRawBuilder.CreateTypeInfo()!; + var implementation = populateClass.GetMethod(populateName); + + if (implementation == null) + { + throw new NullReferenceException("populate method should have existed"); + } + + return implementation; + } + +} + +/// +/// An enum containing either (with a ) +/// or . (with an , and an optional hint +/// for how to fix it) +/// +/// +/// It is not guaranteed that the ever appeared on the stack. +/// That is an implementation detail of . +/// +public abstract class XamlJitCompilerResult +{ + public class Success(MethodInfo methodInfo) : XamlJitCompilerResult + { + public MethodInfo MethodInfo { get; } = methodInfo; + } + + public class Error(Exception raw, string? hint) : XamlJitCompilerResult + { + public Exception Raw { get; } = raw; + public string? Hint { get; } = hint; + } +} From cd95929ebee53357f33609a5383eff91c130c3e8 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 27 Aug 2024 21:32:50 +1000 Subject: [PATCH 050/140] Heavily optimise entitylookup (#5400) * Heavily optimise entitylookup Previously I made it so MOST of entitylookup goes through the same 4 or 5 codepaths and uses shapes. The downside was some codepaths were made much slower in the process. This fixes most of the going up and down lookups that some codepaths did. It should also be faster than the pre-shapes version because GetLocalPhysicsTransform is being used for the non-approx queries and most entities are parented directly to their broadphase. * Tests and confidence * code * dang --- Robust.Shared.Maths/Matrix3Helpers.cs | 7 + .../Systems/EntityLookup.Queries.cs | 144 +++++---- .../EntityLookupSystem.ComponentQueries.cs | 67 ++-- .../EntityLookupSystem.LocalQueries.cs | 14 +- .../GameObjects/Systems/EntityLookupSystem.cs | 5 + .../Systems/SharedPhysicsSystem.Components.cs | 48 +++ .../Shared/EntityLookup_Test.cs | 295 +++++++++++++++++- 7 files changed, 478 insertions(+), 102 deletions(-) diff --git a/Robust.Shared.Maths/Matrix3Helpers.cs b/Robust.Shared.Maths/Matrix3Helpers.cs index b9720c10fa7..85a5a853a10 100644 --- a/Robust.Shared.Maths/Matrix3Helpers.cs +++ b/Robust.Shared.Maths/Matrix3Helpers.cs @@ -25,6 +25,13 @@ public static bool EqualsApprox(this Matrix3x2 a, Matrix3x2 b, double tolerance) return a.EqualsApprox(b, (float) tolerance); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Box2Rotated TransformBounds(this Matrix3x2 refFromBox, Box2Rotated box) + { + var matty = Matrix3x2.Multiply(refFromBox, box.Transform); + return new Box2Rotated(Vector2.Transform(box.BottomLeft, matty), Vector2.Transform(box.TopRight, matty)); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Box2 TransformBox(this Matrix3x2 refFromBox, Box2Rotated box) { diff --git a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs index 5c1d0478a02..fdbca9a2a26 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs @@ -97,12 +97,17 @@ private void AddEntitiesIntersecting(MapId mapId, _mapManager.FindGridsIntersecting(mapId, worldAABB, ref state, static (EntityUid uid, MapGridComponent _, ref EntityQueryState state) => { - state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, state.Transform, state.Flags); + var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid); + var localAabb = state.Shape.ComputeAABB(localTransform, 0); + state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, localTransform, state.Flags); return true; }, approx: true, includeMap: false); var mapUid = _map.GetMapOrInvalid(mapId); - AddEntitiesIntersecting(mapUid, intersecting, shape, shapeTransform, flags); + var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid); + var localAabb = state.Shape.ComputeAABB(localTransform, 0); + + AddEntitiesIntersecting(mapUid, intersecting, shape, localAabb, localTransform, flags); AddContained(intersecting, flags); } @@ -110,23 +115,18 @@ private void AddEntitiesIntersecting( EntityUid lookupUid, HashSet intersecting, IPhysShape shape, - Transform shapeTransform, + Box2 localAABB, + Transform localShapeTransform, LookupFlags flags, BroadphaseComponent? lookup = null) { if (!_broadQuery.Resolve(lookupUid, ref lookup)) return; - var (_, lookupRot, lookupInvMatrix) = _transform.GetWorldPositionRotationInvMatrix(lookupUid); - var lookupTransform = new Transform(Vector2.Transform(shapeTransform.Position, lookupInvMatrix), - shapeTransform.Quaternion2D.Angle - lookupRot); - - var localAABB = shape.ComputeAABB(lookupTransform, 0); - var state = new EntityQueryState( intersecting, shape, - shapeTransform, + localShapeTransform, _fixtures, this, _physics, @@ -167,7 +167,7 @@ static bool PhysicsQuery(ref EntityQueryState state, in FixtureProxy value) if (!approx) { - var intersectingTransform = state.Physics.GetPhysicsTransform(value.Entity); + var intersectingTransform = state.Physics.GetLocalPhysicsTransform(value.Entity); if (!state.Manifolds.TestOverlap(state.Shape, 0, value.Fixture.Shape, value.ChildIndex, state.Transform, intersectingTransform)) { return true; @@ -188,7 +188,7 @@ static bool SundriesQuery(ref EntityQueryState state, in EntityUid value) return true; } - var intersectingTransform = state.Physics.GetPhysicsTransform(value); + var intersectingTransform = state.Physics.GetLocalPhysicsTransform(value); if (state.FixturesQuery.TryGetComponent(value, out var fixtures)) { @@ -248,7 +248,10 @@ private bool AnyEntitiesIntersecting(MapId mapId, _mapManager.FindGridsIntersecting(mapId, worldAABB, ref state, static (EntityUid uid, MapGridComponent _, ref AnyEntityQueryState state) => { - if (state.Lookup.AnyEntitiesIntersecting(uid, state.Shape, state.Transform, state.Flags, ignored: state.Ignored)) + var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid); + var localAabb = state.Shape.ComputeAABB(localTransform, 0); + + if (state.Lookup.AnyEntitiesIntersecting(uid, state.Shape, localAabb, state.Transform, state.Flags, ignored: state.Ignored)) { state.Found = true; return false; @@ -260,7 +263,9 @@ private bool AnyEntitiesIntersecting(MapId mapId, if (!state.Found) { var mapUid = _map.GetMapOrInvalid(mapId); - state.Found = AnyEntitiesIntersecting(mapUid, shape, shapeTransform, flags, ignored); + var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid); + var localAabb = state.Shape.ComputeAABB(localTransform, 0); + state.Found = AnyEntitiesIntersecting(mapUid, shape, localAabb, shapeTransform, flags, ignored); } return state.Found; @@ -268,6 +273,7 @@ private bool AnyEntitiesIntersecting(MapId mapId, private bool AnyEntitiesIntersecting(EntityUid lookupUid, IPhysShape shape, + Box2 localAABB, Transform shapeTransform, LookupFlags flags, EntityUid? ignored = null, @@ -287,15 +293,6 @@ private bool AnyEntitiesIntersecting(EntityUid lookupUid, _fixturesQuery, flags); - // Shape gets passed in via local terms - // Transform is in world terms - // Need to convert both back to lookup-local for AABB. - var (_, lookupRot, lookupInvMatrix) = _transform.GetWorldPositionRotationInvMatrix(lookupUid); - var lookupTransform = new Transform(Vector2.Transform(shapeTransform.Position, lookupInvMatrix), - shapeTransform.Quaternion2D.Angle - lookupRot); - - var localAABB = shape.ComputeAABB(lookupTransform, 0); - if ((flags & LookupFlags.Dynamic) != 0x0) { lookup.DynamicTree.QueryAabb(ref state, PhysicsQuery, localAABB, true); @@ -341,7 +338,7 @@ static bool PhysicsQuery(ref AnyEntityQueryState state, in FixtureProxy value) if (!approx) { - var intersectingTransform = state.Physics.GetPhysicsTransform(value.Entity); + var intersectingTransform = state.Physics.GetLocalPhysicsTransform(value.Entity); if (!state.Manifolds.TestOverlap(state.Shape, 0, value.Fixture.Shape, value.ChildIndex, state.Transform, intersectingTransform)) { return true; @@ -365,7 +362,7 @@ static bool SundriesQuery(ref AnyEntityQueryState state, in EntityUid value) return false; } - var intersectingTransform = state.Physics.GetPhysicsTransform(value); + var intersectingTransform = state.Physics.GetLocalPhysicsTransform(value); if (state.FixturesQuery.TryGetComponent(value, out var fixtures)) { @@ -408,11 +405,13 @@ private bool AnyEntitiesIntersecting(EntityUid lookupUid, LookupFlags flags, EntityUid? ignored = null) { + var broadphaseInv = _transform.GetInvWorldMatrix(lookupUid); + + var localBounds = broadphaseInv.TransformBounds(worldBounds); var shape = new PolygonShape(); - shape.Set(worldBounds); - var transform = Physics.Transform.Empty; + shape.Set(localBounds); - return AnyEntitiesIntersecting(lookupUid, shape, transform, flags, ignored); + return AnyEntitiesIntersecting(lookupUid, shape, localBounds.CalcBoundingBox(), Physics.Transform.Empty, flags, ignored); } #endregion @@ -549,22 +548,37 @@ public HashSet GetEntitiesIntersecting(MapId mapId, Box2Rotated world // Get grid entities var shape = new PolygonShape(); shape.Set(worldBounds); + var transform = Physics.Transform.Empty; - var state = (this, intersecting, shape, flags); - - _mapManager.FindGridsIntersecting(mapUid, shape, Physics.Transform.Empty, ref state, static - (EntityUid uid, MapGridComponent _, - ref (EntityLookupSystem lookup, - HashSet intersecting, - PolygonShape shape, - LookupFlags flags) tuple) => - { - tuple.lookup.AddEntitiesIntersecting(uid, tuple.intersecting, tuple.shape, Physics.Transform.Empty, tuple.flags); - return true; - }, approx: true, includeMap: false); + var state = (this, _physics, intersecting, transform, shape, flags); + + _mapManager.FindGridsIntersecting(mapUid, shape, transform, ref state, + static ( + EntityUid uid, + MapGridComponent grid, + ref (EntityLookupSystem lookup, + SharedPhysicsSystem _physics, + HashSet intersecting, + Transform transform, + PolygonShape shape, LookupFlags flags) state) => + { + var localTransform = state._physics.GetRelativePhysicsTransform(state.transform, uid); + var localAabb = state.shape.ComputeAABB(localTransform, 0); + + state.lookup.AddEntitiesIntersecting(uid, + state.intersecting, + state.shape, + localAabb, + state.transform, + state.flags); + return true; + }); // Get map entities - AddEntitiesIntersecting(mapUid, intersecting, shape, Physics.Transform.Empty, flags); + var localTransform = _physics.GetRelativePhysicsTransform(transform, mapUid); + var localAabb = shape.ComputeAABB(localTransform, 0); + + AddEntitiesIntersecting(mapUid, intersecting, shape, localAabb, transform, flags); AddContained(intersecting, flags); return intersecting; @@ -591,19 +605,24 @@ public bool AnyEntitiesInRange(EntityUid uid, float range, LookupFlags flags = D var circle = new PhysShapeCircle(range, mapPos.Position); const bool found = false; - var state = (this, worldAABB, circle, flags, found, uid); + var transform = Physics.Transform.Empty; + var state = (this, _physics, transform, circle, flags, found, uid); _mapManager.FindGridsIntersecting(mapPos.MapId, worldAABB, ref state, static ( EntityUid gridUid, MapGridComponent _, ref ( EntityLookupSystem lookup, - Box2 worldAABB, + SharedPhysicsSystem physics, + Transform worldTransform, PhysShapeCircle circle, LookupFlags flags, bool found, EntityUid ignored) tuple) => { - if (tuple.lookup.AnyEntitiesIntersecting(gridUid, tuple.circle, Physics.Transform.Empty, tuple.flags, tuple.ignored)) + var localTransform = tuple.physics.GetRelativePhysicsTransform(tuple.worldTransform, gridUid); + var localAabb = tuple.circle.ComputeAABB(localTransform, 0); + + if (tuple.lookup.AnyEntitiesIntersecting(gridUid, tuple.circle, localAabb, localTransform, tuple.flags, tuple.ignored)) { tuple.found = true; return false; @@ -618,7 +637,10 @@ public bool AnyEntitiesInRange(EntityUid uid, float range, LookupFlags flags = D } var mapUid = _map.GetMapOrInvalid(mapPos.MapId); - return AnyEntitiesIntersecting(mapUid, circle, Physics.Transform.Empty, flags, uid); + var localTransform = _physics.GetRelativePhysicsTransform(transform, uid); + var localAabb = circle.ComputeAABB(localTransform, 0); + + return AnyEntitiesIntersecting(mapUid, circle, localAabb, localTransform, flags, uid); } public HashSet GetEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags) @@ -656,15 +678,16 @@ public void GetEntitiesIntersecting(EntityUid uid, HashSet intersecti var worldAABB = GetAABBNoContainer(uid, worldPos, worldRot); var existing = intersecting.Contains(uid); var transform = new Transform(worldPos, worldRot); - var state = (uid, transform, intersecting, _fixturesQuery, this, flags); + + var state = (uid, transform, intersecting, _fixturesQuery, this, _physics, flags); // Unfortuantely I can't think of a way to de-dupe this with the other ones as it's slightly different. _mapManager.FindGridsIntersecting(mapId, worldAABB, ref state, static (EntityUid gridUid, MapGridComponent grid, ref (EntityUid entity, Transform transform, HashSet intersecting, - EntityQuery fixturesQuery, EntityLookupSystem lookup, LookupFlags flags) tuple) => + EntityQuery fixturesQuery, EntityLookupSystem lookup, SharedPhysicsSystem physics, LookupFlags flags) state) => { - EntityIntersectingQuery(gridUid, tuple); + EntityIntersectingQuery(gridUid, state); return true; }, approx: true, includeMap: false); @@ -681,24 +704,29 @@ public void GetEntitiesIntersecting(EntityUid uid, HashSet intersecti return; static void EntityIntersectingQuery(EntityUid lookupUid, (EntityUid entity, Transform shapeTransform, HashSet intersecting, - EntityQuery fixturesQuery, EntityLookupSystem lookup, LookupFlags flags) tuple) + EntityQuery fixturesQuery, EntityLookupSystem lookup, SharedPhysicsSystem physics, LookupFlags flags) state) { - if (tuple.fixturesQuery.TryGetComponent(tuple.entity, out var fixturesComp)) + var localTransform = state.physics.GetRelativePhysicsTransform(state.shapeTransform, lookupUid); + + if (state.fixturesQuery.TryGetComponent(state.entity, out var fixturesComp)) { foreach (var fixture in fixturesComp.Fixtures.Values) { // If our own fixture isn't hard and sensors ignored then ignore it. - if (!fixture.Hard && (tuple.flags & LookupFlags.Sensors) == 0x0) + if (!fixture.Hard && (state.flags & LookupFlags.Sensors) == 0x0) continue; - tuple.lookup.AddEntitiesIntersecting(lookupUid, tuple.intersecting, fixture.Shape, tuple.shapeTransform, tuple.flags); + var localAabb = fixture.Shape.ComputeAABB(localTransform, 0); + state.lookup.AddEntitiesIntersecting(lookupUid, state.intersecting, fixture.Shape, localAabb, localTransform, state.flags); } } // Single point check else { var shape = new PhysShapeCircle(LookupEpsilon); - tuple.lookup.AddEntitiesIntersecting(lookupUid, tuple.intersecting, shape, tuple.shapeTransform, tuple.flags); + var localAabb = shape.ComputeAABB(localTransform, 0); + + state.lookup.AddEntitiesIntersecting(lookupUid, state.intersecting, shape, localAabb, localTransform, state.flags); } } } @@ -840,10 +868,11 @@ public void GetEntitiesIntersecting(EntityUid gridId, Box2 worldAABB, HashSet( var lookupPoly = new PolygonShape(); lookupPoly.SetAsBox(localAABB); - var (lookupPos, lookupRot) = _transform.GetWorldPositionRotation(lookupUid); - var transform = new Transform(lookupPos, lookupRot); - AddEntitiesIntersecting(lookupUid, intersecting, lookupPoly, transform, flags, query, lookup); + AddEntitiesIntersecting(lookupUid, intersecting, lookupPoly, localAABB, Physics.Transform.Empty, flags, query, lookup); } private void AddEntitiesIntersecting( EntityUid lookupUid, HashSet> intersecting, IPhysShape shape, - Transform shapeTransform, + Box2 localAABB, + Transform localTransform, LookupFlags flags, EntityQuery query, BroadphaseComponent? lookup = null) where T : IComponent @@ -141,19 +140,10 @@ private void AddEntitiesIntersecting( if (!_broadQuery.Resolve(lookupUid, ref lookup)) return; - // Shape gets passed in via local terms - // Transform is in world terms - // Need to convert both back to lookup-local for AABB. - var (_, lookupRot, lookupInvMatrix) = _transform.GetWorldPositionRotationInvMatrix(lookupUid); - var lookupTransform = new Transform(Vector2.Transform(shapeTransform.Position, lookupInvMatrix), - shapeTransform.Quaternion2D.Angle - lookupRot); - - var localAABB = shape.ComputeAABB(lookupTransform, 0); - var state = new QueryState( intersecting, shape, - shapeTransform, + localTransform, _fixtures, _physics, _manifoldManager, @@ -195,7 +185,7 @@ static bool PhysicsQuery(ref QueryState state, in FixtureProxy value) wher if (!state.Approximate) { - var intersectingTransform = state.Physics.GetPhysicsTransform(value.Entity); + var intersectingTransform = state.Physics.GetLocalPhysicsTransform(value.Entity); if (!state.Manifolds.TestOverlap(state.Shape, 0, value.Fixture.Shape, value.ChildIndex, state.Transform, intersectingTransform)) { return true; @@ -217,7 +207,7 @@ static bool SundriesQuery(ref QueryState state, in EntityUid value) where return true; } - var intersectingTransform = state.Physics.GetPhysicsTransform(value); + var intersectingTransform = state.Physics.GetLocalPhysicsTransform(value); if (state.FixturesQuery.TryGetComponent(value, out var fixtures)) { @@ -266,12 +256,13 @@ private bool AnyLocalComponentsIntersecting( var (lookupPos, lookupRot) = _transform.GetWorldPositionRotation(lookupUid); var transform = new Transform(lookupPos, lookupRot); - return AnyComponentsIntersecting(lookupUid, shape, transform, flags, query, ignored, lookup); + return AnyComponentsIntersecting(lookupUid, shape, localAABB, transform, flags, query, ignored, lookup); } private bool AnyComponentsIntersecting( EntityUid lookupUid, IPhysShape shape, + Box2 localAABB, Transform shapeTransform, LookupFlags flags, EntityQuery query, @@ -286,12 +277,6 @@ private bool AnyComponentsIntersecting( if (!_broadQuery.Resolve(lookupUid, ref lookup)) return false; - var (_, lookupRot, lookupInvMatrix) = _transform.GetWorldPositionRotationInvMatrix(lookupUid); - var lookupTransform = new Transform(Vector2.Transform(shapeTransform.Position, lookupInvMatrix), - shapeTransform.Quaternion2D.Angle - lookupRot); - - var localAABB = shape.ComputeAABB(lookupTransform, 0); - var state = new AnyQueryState(false, ignored, shape, @@ -554,17 +539,22 @@ public void GetEntitiesIntersecting(Type type, MapId mapId, IPhysShape shape, Tr var query = EntityManager.GetEntityQuery(type); // Get grid entities - var state = new GridQueryState(intersecting, shape, shapeTransform, this, flags, query); + var state = new GridQueryState(intersecting, shape, shapeTransform, this, _physics, flags, query); _mapManager.FindGridsIntersecting(mapId, worldAABB, ref state, static (EntityUid uid, MapGridComponent grid, ref GridQueryState state) => { - state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, state.Transform, state.Flags, state.Query); + var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid); + var localAabb = state.Shape.ComputeAABB(localTransform, 0); + state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, state.Transform, state.Flags, state.Query); return true; }, approx: true, includeMap: false); var mapUid = _map.GetMapOrInvalid(mapId); - AddEntitiesIntersecting(mapUid, intersecting, shape, shapeTransform, flags, query); + var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid); + var localAabb = state.Shape.ComputeAABB(localTransform, 0); + + AddEntitiesIntersecting(mapUid, intersecting, shape, localAabb, shapeTransform, flags, query); AddContained(intersecting, flags, query); } @@ -593,19 +583,23 @@ public void GetEntitiesIntersecting(MapId mapId, IPhysShape shape, Transform var query = GetEntityQuery(); // Get grid entities - var state = new GridQueryState(entities, shape, shapeTransform, this, flags, query); + var state = new GridQueryState(entities, shape, shapeTransform, this, _physics, flags, query); _mapManager.FindGridsIntersecting(mapId, worldAABB, ref state, static (EntityUid uid, MapGridComponent grid, ref GridQueryState state) => { - state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, state.Transform, state.Flags, state.Query); + var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid); + var localAabb = state.Shape.ComputeAABB(localTransform, 0); + state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, state.Transform, state.Flags, state.Query); return true; }, approx: true, includeMap: false); // Get map entities var mapUid = _map.GetMapOrInvalid(mapId); - AddEntitiesIntersecting(mapUid, entities, shape, shapeTransform, flags, query); + var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid); + var localAabb = state.Shape.ComputeAABB(localTransform, 0); + AddEntitiesIntersecting(mapUid, entities, shape, localAabb, shapeTransform, flags, query); AddContained(entities, flags, query); } } @@ -778,6 +772,20 @@ public void GetLocalEntitiesIntersecting( AddContained(intersecting, flags, query); } + /// + /// Gets the entities intersecting the specified broadphase entity using a local AABB. + /// + public void GetLocalEntitiesIntersecting( + Entity grid, + Box2 localAABB, + HashSet> intersecting, + EntityQuery query, + LookupFlags flags = DefaultFlags) where T : IComponent + { + AddLocalEntitiesIntersecting(grid, intersecting, localAABB, flags, query, grid.Comp); + AddContained(intersecting, flags, query); + } + #endregion /// @@ -819,6 +827,7 @@ private readonly record struct GridQueryState( IPhysShape Shape, Transform Transform, EntityLookupSystem Lookup, + SharedPhysicsSystem Physics, LookupFlags Flags, EntityQuery Query ) where T : IComponent; diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.LocalQueries.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.LocalQueries.cs index 772835b9e48..09499832045 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.LocalQueries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.LocalQueries.cs @@ -27,10 +27,7 @@ private void AddLocalEntitiesIntersecting( var lookupPoly = new PolygonShape(); lookupPoly.SetAsBox(localAABB); - var (lookupPos, lookupRot) = _transform.GetWorldPositionRotation(lookupUid); - var lookupTransform = new Transform(lookupPos, lookupRot); - - AddEntitiesIntersecting(lookupUid, intersecting, lookupPoly, lookupTransform, flags, lookup); + AddEntitiesIntersecting(lookupUid, intersecting, lookupPoly, localAABB, Physics.Transform.Empty, flags, lookup); } private void AddLocalEntitiesIntersecting( @@ -45,9 +42,9 @@ private void AddLocalEntitiesIntersecting( var shape = new PolygonShape(); shape.Set(localBounds); + var localAABB = localBounds.CalcBoundingBox(); - var transform = _physics.GetPhysicsTransform(lookupUid); - AddEntitiesIntersecting(lookupUid, intersecting, shape, transform, flags); + AddEntitiesIntersecting(lookupUid, intersecting, shape, localAABB, Physics.Transform.Empty, flags); } public bool AnyLocalEntitiesIntersecting(EntityUid lookupUid, @@ -61,8 +58,7 @@ public bool AnyLocalEntitiesIntersecting(EntityUid lookupUid, var shape = new PolygonShape(); shape.SetAsBox(localAABB); - var transform = _physics.GetPhysicsTransform(lookupUid); - return AnyEntitiesIntersecting(lookupUid, shape, transform, flags, ignored, lookup); + return AnyEntitiesIntersecting(lookupUid, shape, localAABB, Physics.Transform.Empty, flags, ignored, lookup); } public HashSet GetLocalEntitiesIntersecting(EntityUid gridId, Vector2i gridIndices, float enlargement = TileEnlargementRadius, LookupFlags flags = DefaultFlags, MapGridComponent? gridComp = null) @@ -88,7 +84,7 @@ public void GetLocalEntitiesIntersecting(EntityUid gridUid, Vector2i localTile, } var localAABB = GetLocalBounds(localTile, tileSize); - localAABB = localAABB.Enlarged(TileEnlargementRadius); + localAABB = localAABB.Enlarged(enlargement); GetLocalEntitiesIntersecting(gridUid, localAABB, intersecting, flags); } diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs index d60a3ce0e9d..2462af5dbb2 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs @@ -12,6 +12,7 @@ using Robust.Shared.Physics; using Robust.Shared.Physics.BroadPhase; using Robust.Shared.Physics.Collision; +using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Events; @@ -95,6 +96,10 @@ public sealed partial class EntityLookupSystem : EntitySystem private EntityQuery _mapQuery; private EntityQuery _xformQuery; + /// + /// 1 x 1 polygons can overlap neighboring tiles (even without considering the polygon skin around them. + /// When querying for specific tile fixtures we shrink the bounds by this amount to avoid this overlap. + /// public const float TileEnlargementRadius = -PhysicsConstants.PolygonRadius * 4f; /// diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs index cb2347bf046..730f5e18ebf 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs @@ -692,6 +692,54 @@ public bool WakeBody(EntityUid uid, bool force = false, FixturesComponent? manag #endregion + public Transform GetRelativePhysicsTransform(Transform worldTransform, Entity relative) + { + if (!_xformQuery.Resolve(relative.Owner, ref relative.Comp)) + return Physics.Transform.Empty; + + var (_, broadphaseRot, _, broadphaseInv) = _transform.GetWorldPositionRotationMatrixWithInv(relative.Comp); + + return new Transform(Vector2.Transform(worldTransform.Position, broadphaseInv), + worldTransform.Quaternion2D.Angle - broadphaseRot); + } + + /// + /// Gets the physics transform relative to another entity. + /// + public Transform GetRelativePhysicsTransform( + Entity entity, + Entity relative) + { + if (!_xformQuery.Resolve(entity.Owner, ref entity.Comp) || + !_xformQuery.Resolve(relative.Owner, ref relative.Comp)) + { + return Physics.Transform.Empty; + } + + var (worldPos, worldRot) = _transform.GetWorldPositionRotation(entity.Comp); + var (_, broadphaseRot, _, broadphaseInv) = _transform.GetWorldPositionRotationMatrixWithInv(relative.Comp); + + return new Transform(Vector2.Transform(worldPos, broadphaseInv), worldRot - broadphaseRot); + } + + /// + /// Gets broadphase relevant transform. + /// + public Transform GetLocalPhysicsTransform(EntityUid uid, TransformComponent? xform = null) + { + if (!_xformQuery.Resolve(uid, ref xform) || xform.Broadphase == null) + return Physics.Transform.Empty; + + var broadphase = xform.Broadphase.Value.Uid; + + if (xform.ParentUid == broadphase) + { + return new Transform(xform.LocalPosition, xform.LocalRotation); + } + + return GetRelativePhysicsTransform((uid, xform), broadphase); + } + public Transform GetPhysicsTransform(EntityUid uid, TransformComponent? xform = null) { if (!_xformQuery.Resolve(uid, ref xform)) diff --git a/Robust.UnitTesting/Shared/EntityLookup_Test.cs b/Robust.UnitTesting/Shared/EntityLookup_Test.cs index e4deb6d1203..b79a28c593e 100644 --- a/Robust.UnitTesting/Shared/EntityLookup_Test.cs +++ b/Robust.UnitTesting/Shared/EntityLookup_Test.cs @@ -1,9 +1,14 @@ +using System.Collections.Generic; using System.Linq; using System.Numerics; using NUnit.Framework; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Maths; +using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; using Robust.UnitTesting.Server; namespace Robust.UnitTesting.Shared @@ -11,8 +16,66 @@ namespace Robust.UnitTesting.Shared [TestFixture, TestOf(typeof(EntityLookupSystem))] public sealed class EntityLookupTest { - [Test] - public void AnyIntersecting() + private static readonly MapId MapId = new MapId(1); + + private static readonly TestCaseData[] InRangeCases = new[] + { + new TestCaseData(true, new MapCoordinates(Vector2.One, MapId), new MapCoordinates(Vector2.Zero, MapId), 0.5f, false), + new TestCaseData(true, new MapCoordinates(new Vector2(10f, 10f), MapId), new MapCoordinates(new Vector2(9.5f, 9.5f), MapId), 0.5f, true), + // Close but no cigar + new TestCaseData(true, new MapCoordinates(new Vector2(10f, 10f), MapId), new MapCoordinates(new Vector2(9f, 9f), MapId), 0.5f, false), + // Large area so useboundsquery + new TestCaseData(true, new MapCoordinates(new Vector2(0f, 0f), MapId), new MapCoordinates(new Vector2(0f, 1000f), MapId), 999f, false), + new TestCaseData(true, new MapCoordinates(new Vector2(0f, 0f), MapId), new MapCoordinates(new Vector2(0f, 999f), MapId), 999f, true), + + // NoFixturecases + new TestCaseData(false, new MapCoordinates(Vector2.One, MapId), new MapCoordinates(Vector2.Zero, MapId), 0.5f, false), + new TestCaseData(false, new MapCoordinates(new Vector2(10f, 10f), MapId), new MapCoordinates(new Vector2(9.5f, 9.5f), MapId), 0.5f, false), + // Close but no cigar + new TestCaseData(false, new MapCoordinates(new Vector2(10f, 10f), MapId), new MapCoordinates(new Vector2(9f, 9f), MapId), 0.5f, false), + }; + + // Remember this test data is relative. + private static readonly TestCaseData[] Box2Cases = new[] + { + new TestCaseData(true, new MapCoordinates(Vector2.One, MapId), Box2.UnitCentered, false), + new TestCaseData(true, new MapCoordinates(new Vector2(10f, 10f), MapId), Box2.UnitCentered, true), + }; + + private static readonly TestCaseData[] TileCases = new[] + { + new TestCaseData(true, new MapCoordinates(Vector2.One, MapId), Vector2i.Zero, false), + new TestCaseData(true, new MapCoordinates(new Vector2(10f, 10f), MapId), Vector2i.Zero, true), + // Need to make sure we don't pull out neighbor fixtures even if they barely touch our tile + new TestCaseData(true, new MapCoordinates(new Vector2(11f + 0.35f, 10f), MapId), Vector2i.Zero, false), + }; + + private EntityUid GetPhysicsEntity(IEntityManager entManager, MapCoordinates spawnPos) + { + var ent = entManager.SpawnEntity(null, spawnPos); + var physics = entManager.AddComponent(ent); + entManager.System().TryCreateFixture(ent, new PhysShapeCircle(0.35f, Vector2.Zero), "fix1"); + entManager.System().SetCanCollide(ent, true, body: physics); + return ent; + } + + private Entity SetupGrid(MapId mapId, SharedMapSystem mapSystem, IEntityManager entManager, IMapManager mapManager) + { + var grid = mapManager.CreateGridEntity(mapId); + entManager.System().SetLocalPosition(grid.Owner, new Vector2(10f, 10f)); + mapSystem.SetTile(grid, Vector2i.Zero, new Tile(1)); + return grid; + } + + #region Entity + + /* + * We double these tests just because these have slightly different codepaths at the moment. + * + */ + + [Test, TestCaseSource(nameof(Box2Cases))] + public void TestEntityAnyIntersecting(bool physics, MapCoordinates spawnPos, Box2 queryBounds, bool result) { var sim = RobustServerSimulation.NewSimulation(); var server = sim.InitializeInstance(); @@ -20,16 +83,234 @@ public void AnyIntersecting() var lookup = server.Resolve().GetEntitySystem(); var entManager = server.Resolve(); var mapManager = server.Resolve(); + var mapSystem = entManager.System(); - var mapId = server.CreateMap().MapId; + mapSystem.CreateMap(spawnPos.MapId); + var grid = SetupGrid(spawnPos.MapId, mapSystem, entManager, mapManager); - var theMapSpotBeingUsed = new Box2(Vector2.Zero, Vector2.One); + if (physics) + GetPhysicsEntity(entManager, spawnPos); + else + entManager.Spawn(null, spawnPos); - var dummy = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId)); - Assert.That(lookup.AnyEntitiesIntersecting(mapId, theMapSpotBeingUsed)); - mapManager.DeleteMap(mapId); + var outcome = lookup.AnyLocalEntitiesIntersecting(grid.Owner, queryBounds, LookupFlags.All); + + Assert.That(outcome, Is.EqualTo(result)); + mapManager.DeleteMap(spawnPos.MapId); + } + + [Test, TestCaseSource(nameof(Box2Cases))] + public void TestEntityAnyLocalIntersecting(bool physics, MapCoordinates spawnPos, Box2 queryBounds, bool result) + { + var sim = RobustServerSimulation.NewSimulation(); + var server = sim.InitializeInstance(); + + var lookup = server.Resolve().GetEntitySystem(); + var entManager = server.Resolve(); + var mapManager = server.Resolve(); + var mapSystem = entManager.System(); + + mapSystem.CreateMap(spawnPos.MapId); + var grid = SetupGrid(spawnPos.MapId, mapSystem, entManager, mapManager); + + if (physics) + GetPhysicsEntity(entManager, spawnPos); + else + entManager.Spawn(null, spawnPos); + + var outcome = lookup.AnyLocalEntitiesIntersecting(grid.Owner, queryBounds, LookupFlags.All); + + Assert.That(outcome, Is.EqualTo(result)); + mapManager.DeleteMap(spawnPos.MapId); + } + + /// + /// Tests Box2 local queries for a particular lookup ID. + /// + [Test, TestCaseSource(nameof(Box2Cases))] + public void TestEntityGridLocalIntersecting(bool physics, MapCoordinates spawnPos, Box2 queryBounds, bool result) + { + var sim = RobustServerSimulation.NewSimulation(); + var server = sim.InitializeInstance(); + + var lookup = server.Resolve().GetEntitySystem(); + var entManager = server.Resolve(); + var mapManager = server.Resolve(); + var mapSystem = entManager.System(); + + mapSystem.CreateMap(spawnPos.MapId); + var grid = SetupGrid(spawnPos.MapId, mapSystem, entManager, mapManager); + + if (physics) + GetPhysicsEntity(entManager, spawnPos); + else + entManager.Spawn(null, spawnPos); + + var entities = new HashSet>(); + lookup.GetLocalEntitiesIntersecting(grid.Owner, queryBounds, entities); + + Assert.That(entities.Count > 0, Is.EqualTo(result)); + mapManager.DeleteMap(spawnPos.MapId); } + /// + /// Tests Box2 local queries for a particular lookup ID. + /// + [Test, TestCaseSource(nameof(TileCases))] + public void TestEntityGridTileIntersecting(bool physics, MapCoordinates spawnPos, Vector2i queryTile, bool result) + { + var sim = RobustServerSimulation.NewSimulation(); + var server = sim.InitializeInstance(); + + var lookup = server.Resolve().GetEntitySystem(); + var entManager = server.Resolve(); + var mapManager = server.Resolve(); + var mapSystem = entManager.System(); + + mapSystem.CreateMap(spawnPos.MapId); + var grid = SetupGrid(spawnPos.MapId, mapSystem, entManager, mapManager); + + if (physics) + GetPhysicsEntity(entManager, spawnPos); + else + entManager.Spawn(null, spawnPos); + + var entities = new HashSet>(); + lookup.GetLocalEntitiesIntersecting(grid.Owner, queryTile, entities); + + Assert.That(entities.Count > 0, Is.EqualTo(result)); + mapManager.DeleteMap(spawnPos.MapId); + } + + #endregion + + #region EntityUid + + [Test, TestCaseSource(nameof(InRangeCases))] + public void TestMapInRange(bool physics, MapCoordinates spawnPos, MapCoordinates queryPos, float range, bool result) + { + var sim = RobustServerSimulation.NewSimulation(); + var server = sim.InitializeInstance(); + + var lookup = server.Resolve().GetEntitySystem(); + var entManager = server.Resolve(); + var mapManager = server.Resolve(); + + entManager.System().CreateMap(spawnPos.MapId); + + if (physics) + GetPhysicsEntity(entManager, spawnPos); + else + entManager.Spawn(null, spawnPos); + + Assert.That(lookup.GetEntitiesInRange(queryPos.MapId, queryPos.Position, range).Count > 0, Is.EqualTo(result)); + mapManager.DeleteMap(spawnPos.MapId); + } + + [Test, TestCaseSource(nameof(InRangeCases))] + public void TestGridInRange(bool physics, MapCoordinates spawnPos, MapCoordinates queryPos, float range, bool result) + { + var sim = RobustServerSimulation.NewSimulation(); + var server = sim.InitializeInstance(); + + var lookup = server.Resolve().GetEntitySystem(); + var entManager = server.Resolve(); + var mapManager = server.Resolve(); + var mapSystem = entManager.System(); + + mapSystem.CreateMap(spawnPos.MapId); + var grid = SetupGrid(spawnPos.MapId, mapSystem, entManager, mapManager); + + if (physics) + GetPhysicsEntity(entManager, spawnPos); + else + entManager.Spawn(null, spawnPos); + + _ = entManager.SpawnEntity(null, spawnPos); + Assert.That(lookup.GetEntitiesInRange(queryPos.MapId, queryPos.Position, range).Count > 0, Is.EqualTo(result)); + mapManager.DeleteMap(spawnPos.MapId); + } + + [Test, TestCaseSource(nameof(InRangeCases))] + public void TestMapNoFixtureInRange(bool physics, MapCoordinates spawnPos, MapCoordinates queryPos, float range, bool result) + { + var sim = RobustServerSimulation.NewSimulation(); + var server = sim.InitializeInstance(); + + var lookup = server.Resolve().GetEntitySystem(); + var entManager = server.Resolve(); + var mapManager = server.Resolve(); + + entManager.System().CreateMap(spawnPos.MapId); + + if (physics) + GetPhysicsEntity(entManager, spawnPos); + else + entManager.Spawn(null, spawnPos); + + Assert.That(lookup.GetEntitiesInRange(queryPos.MapId, queryPos.Position, range).Count > 0, Is.EqualTo(result)); + mapManager.DeleteMap(spawnPos.MapId); + } + + /// + /// Tests Box2 local queries for a particular lookup ID. + /// + [Test, TestCaseSource(nameof(Box2Cases))] + public void TestGridAnyIntersecting(bool physics, MapCoordinates spawnPos, Box2 queryBounds, bool result) + { + var sim = RobustServerSimulation.NewSimulation(); + var server = sim.InitializeInstance(); + + var lookup = server.Resolve().GetEntitySystem(); + var entManager = server.Resolve(); + var mapManager = server.Resolve(); + var mapSystem = entManager.System(); + + mapSystem.CreateMap(spawnPos.MapId); + var grid = SetupGrid(spawnPos.MapId, mapSystem, entManager, mapManager); + + if (physics) + GetPhysicsEntity(entManager, spawnPos); + else + entManager.Spawn(null, spawnPos); + + var outcome = lookup.AnyLocalEntitiesIntersecting(grid.Owner, queryBounds, LookupFlags.All); + + Assert.That(outcome, Is.EqualTo(result)); + mapManager.DeleteMap(spawnPos.MapId); + } + + /// + /// Tests Box2 local queries for a particular lookup ID. + /// + [Test, TestCaseSource(nameof(Box2Cases))] + public void TestGridLocalIntersecting(bool physics, MapCoordinates spawnPos, Box2 queryBounds, bool result) + { + var sim = RobustServerSimulation.NewSimulation(); + var server = sim.InitializeInstance(); + + var lookup = server.Resolve().GetEntitySystem(); + var entManager = server.Resolve(); + var mapManager = server.Resolve(); + var mapSystem = entManager.System(); + + mapSystem.CreateMap(spawnPos.MapId); + var grid = SetupGrid(spawnPos.MapId, mapSystem, entManager, mapManager); + + if (physics) + GetPhysicsEntity(entManager, spawnPos); + else + entManager.Spawn(null, spawnPos); + + var entities = new HashSet(); + lookup.GetLocalEntitiesIntersecting(grid.Owner, queryBounds, entities); + + Assert.That(entities.Count > 0, Is.EqualTo(result)); + mapManager.DeleteMap(spawnPos.MapId); + } + + #endregion + /// /// Is the entity correctly removed / added to EntityLookup when anchored /// From 1a2c9008feba891739df69a881fcbdb5e9df25b6 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 27 Aug 2024 22:21:48 +1000 Subject: [PATCH 051/140] Add Box matrix tests (#5402) Thought we had but apparently not. --- .../Shared/Maths/Box2Rotated_Test.cs | 41 +++++++++++++++++++ Robust.UnitTesting/Shared/Maths/Box2_Test.cs | 19 +++++++++ 2 files changed, 60 insertions(+) diff --git a/Robust.UnitTesting/Shared/Maths/Box2Rotated_Test.cs b/Robust.UnitTesting/Shared/Maths/Box2Rotated_Test.cs index 97b302d2a4a..8b826ad9adf 100644 --- a/Robust.UnitTesting/Shared/Maths/Box2Rotated_Test.cs +++ b/Robust.UnitTesting/Shared/Maths/Box2Rotated_Test.cs @@ -56,6 +56,47 @@ public void FullRotationTest([ValueSource(nameof(BoxRotations))] Box2[] boxes) (new Box2(-1, 1, 1, 2), new Vector2(0, 0), -Math.PI/2, new Box2(1, -1, 2, 1)), }; + private static TestCaseData[] MatrixCases = new[] + { + new TestCaseData(Matrix3x2.Identity, + Box2Rotated.UnitCentered, + Box2Rotated.UnitCentered), + new TestCaseData(Matrix3x2.CreateRotation(MathF.PI), + Box2Rotated.UnitCentered, + new Box2Rotated(new Vector2(0.5f, 0.5f), new Vector2(-0.5f, -0.5f))), + new TestCaseData(Matrix3x2.CreateTranslation(Vector2.One), + Box2Rotated.UnitCentered, + new Box2Rotated(new Vector2(0.5f, 0.5f), new Vector2(1.5f, 1.5f))), + }; + + [Test, TestCaseSource(nameof(MatrixCases))] + public void TestBox2RotatedMatrices(Matrix3x2 matrix, Box2Rotated bounds, Box2Rotated result) + { + Assert.That(matrix.TransformBounds(bounds), Is.EqualTo(result)); + } + + private static TestCaseData[] MatrixBox2Cases = new[] + { + new TestCaseData(Matrix3x2.Identity, + Box2Rotated.UnitCentered, + Box2Rotated.UnitCentered.CalcBoundingBox()), + new TestCaseData(Matrix3x2.CreateRotation(MathF.PI), + Box2Rotated.UnitCentered, + new Box2Rotated(new Vector2(0.5f, 0.5f), new Vector2(-0.5f, -0.5f)).CalcBoundingBox()), + new TestCaseData(Matrix3x2.CreateTranslation(Vector2.One), + Box2Rotated.UnitCentered, + new Box2Rotated(new Vector2(0.5f, 0.5f), new Vector2(1.5f, 1.5f)).CalcBoundingBox()), + }; + + /// + /// Tests that transforming a Box2Rotated into a Box2 works. + /// + [Test, TestCaseSource(nameof(MatrixBox2Cases))] + public void TestBox2Matrices(Matrix3x2 matrix, Box2Rotated bounds, Box2 result) + { + Assert.That(matrix.TransformBox(bounds), Is.EqualTo(result)); + } + [Test] public void TestCalcBoundingBox([ValueSource(nameof(CalcBoundingBoxData))] (Box2 baseBox, Vector2 origin, Angle rotation, Box2 expected) dat) diff --git a/Robust.UnitTesting/Shared/Maths/Box2_Test.cs b/Robust.UnitTesting/Shared/Maths/Box2_Test.cs index 7ad2a62de7c..505fbc358b1 100644 --- a/Robust.UnitTesting/Shared/Maths/Box2_Test.cs +++ b/Robust.UnitTesting/Shared/Maths/Box2_Test.cs @@ -65,6 +65,25 @@ public sealed class Box2_Test 10.0f }; + private static TestCaseData[] MatrixCases = new[] + { + new TestCaseData(Matrix3x2.Identity, + Box2.UnitCentered, + Box2.UnitCentered), + new TestCaseData(Matrix3x2.CreateRotation(MathF.PI), + Box2.UnitCentered, + new Box2(new Vector2(-0.5f, -0.5f), new Vector2(0.5f, 0.5f))), + new TestCaseData(Matrix3x2.CreateTranslation(Vector2.One), + Box2.UnitCentered, + new Box2(new Vector2(0.5f, 0.5f), new Vector2(1.5f, 1.5f))), + }; + + [Test, TestCaseSource(nameof(MatrixCases))] + public void TestBox2Matrices(Matrix3x2 matrix, Box2 bounds, Box2 result) + { + Assert.That(matrix.TransformBox(bounds), Is.EqualTo(result)); + } + /// /// Check whether the sources list has correct data. /// That is, no boxes where left > right or top > bottom. From 057a68b366f3255456bf1caece58b43c4737d71f Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 27 Aug 2024 22:58:42 +1000 Subject: [PATCH 052/140] Minor allocs reductions (#5330) * Minor allocs reductions Added a poly struct with the intention of replacing the existing one whenever I finish box2c port. * fix merges * Revert some stuff * Poly tests --- .../Systems/EntityLookup.Queries.cs | 15 +- .../EntityLookupSystem.ComponentQueries.cs | 16 +- .../EntityLookupSystem.LocalQueries.cs | 10 +- Robust.Shared/Map/IMapManager.cs | 4 +- Robust.Shared/Map/MapManager.Queries.cs | 117 ++++----- .../Physics/Collision/DistanceProxy.cs | 16 +- Robust.Shared/Physics/Shapes/Polygon.cs | 226 ++++++++++++++++++ .../Physics/Systems/FixtureSystem.Shapes.cs | 15 ++ .../Shared/EntityLookup_Test.cs | 4 +- .../Shared/Physics/Shapes/Polygon_Test.cs | 46 ++++ 10 files changed, 367 insertions(+), 102 deletions(-) create mode 100644 Robust.Shared/Physics/Shapes/Polygon.cs create mode 100644 Robust.UnitTesting/Shared/Physics/Shapes/Polygon_Test.cs diff --git a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs index fdbca9a2a26..e9051c87c82 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs @@ -11,6 +11,7 @@ using Robust.Shared.Physics.Collision; using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Physics.Shapes; using Robust.Shared.Physics.Systems; using Robust.Shared.Utility; @@ -408,8 +409,7 @@ private bool AnyEntitiesIntersecting(EntityUid lookupUid, var broadphaseInv = _transform.GetInvWorldMatrix(lookupUid); var localBounds = broadphaseInv.TransformBounds(worldBounds); - var shape = new PolygonShape(); - shape.Set(localBounds); + var shape = new Polygon(localBounds); return AnyEntitiesIntersecting(lookupUid, shape, localBounds.CalcBoundingBox(), Physics.Transform.Empty, flags, ignored); } @@ -546,8 +546,7 @@ public HashSet GetEntitiesIntersecting(MapId mapId, Box2Rotated world var mapUid = _map.GetMapOrInvalid(mapId); // Get grid entities - var shape = new PolygonShape(); - shape.Set(worldBounds); + var shape = new Polygon(worldBounds); var transform = Physics.Transform.Empty; var state = (this, _physics, intersecting, transform, shape, flags); @@ -560,7 +559,7 @@ public HashSet GetEntitiesIntersecting(MapId mapId, Box2Rotated world SharedPhysicsSystem _physics, HashSet intersecting, Transform transform, - PolygonShape shape, LookupFlags flags) state) => + Polygon shape, LookupFlags flags) state) => { var localTransform = state._physics.GetRelativePhysicsTransform(state.transform, uid); var localAabb = state.shape.ComputeAABB(localTransform, 0); @@ -869,8 +868,7 @@ public void GetEntitiesIntersecting(EntityUid gridId, Box2 worldAABB, HashSet( if (!_broadQuery.Resolve(lookupUid, ref lookup)) return; - var lookupPoly = new PolygonShape(); - lookupPoly.SetAsBox(localAABB); + var lookupPoly = new Polygon(localAABB); AddEntitiesIntersecting(lookupUid, intersecting, lookupPoly, localAABB, Physics.Transform.Empty, flags, query, lookup); } @@ -251,8 +251,7 @@ private bool AnyLocalComponentsIntersecting( if (!_broadQuery.Resolve(lookupUid, ref lookup)) return false; - var shape = new PolygonShape(); - shape.SetAsBox(localAABB); + var shape = new Polygon(localAABB); var (lookupPos, lookupRot) = _transform.GetWorldPositionRotation(lookupUid); var transform = new Transform(lookupPos, lookupRot); @@ -423,8 +422,7 @@ private bool UseBoundsQuery(float area) where T : IComponent public bool AnyComponentsIntersecting(Type type, MapId mapId, Box2 worldAABB, EntityUid? ignored = null, LookupFlags flags = DefaultFlags) { - var shape = new PolygonShape(); - shape.SetAsBox(worldAABB); + var shape = new Polygon(worldAABB); var transform = Physics.Transform.Empty; return AnyComponentsIntersecting(type, mapId, shape, transform, ignored, flags); @@ -492,8 +490,7 @@ public void GetEntitiesIntersecting(Type type, MapId mapId, Box2 worldAABB, Hash if (mapId == MapId.Nullspace) return; - var shape = new PolygonShape(); - shape.SetAsBox(worldAABB); + var shape = new Polygon(worldAABB); var transform = Physics.Transform.Empty; GetEntitiesIntersecting(type, mapId, shape, transform, intersecting, flags); @@ -503,8 +500,7 @@ public void GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, HashSet> grids, bool approx = Approximate, bool includeMap = IncludeMap); - public void FindGridsIntersecting(MapId mapId, PolygonShape shape, Transform transform, GridCallback callback, + public void FindGridsIntersecting(MapId mapId, IPhysShape shape, Transform transform, GridCallback callback, bool approx = Approximate, bool includeMap = IncludeMap); public void FindGridsIntersecting(MapId mapId, Box2 worldAABB, GridCallback callback, bool approx = Approximate, @@ -116,7 +116,7 @@ public void FindGridsIntersecting(MapId mapId, Box2Rotated worldBounds, ref List #region MapEnt - public void FindGridsIntersecting(EntityUid mapEnt, PolygonShape shape, Transform transform, GridCallback callback, + public void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, Transform transform, GridCallback callback, bool approx = Approximate, bool includeMap = IncludeMap); public void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, Transform transform, diff --git a/Robust.Shared/Map/MapManager.Queries.cs b/Robust.Shared/Map/MapManager.Queries.cs index e459fe8f3e7..5d90e96d182 100644 --- a/Robust.Shared/Map/MapManager.Queries.cs +++ b/Robust.Shared/Map/MapManager.Queries.cs @@ -8,6 +8,7 @@ using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Shapes; namespace Robust.Shared.Map; @@ -47,7 +48,7 @@ public void FindGridsIntersecting(MapId mapId, IPhysShape shape, Transform trans FindGridsIntersecting(mapEnt, shape, transform, ref grids, approx, includeMap); } - public void FindGridsIntersecting(MapId mapId, PolygonShape shape, Transform transform, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) + public void FindGridsIntersecting(MapId mapId, IPhysShape shape, Transform transform, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) { if (_mapEntities.TryGetValue(mapId, out var mapEnt)) FindGridsIntersecting(mapEnt, shape, transform, callback, includeMap, approx); @@ -97,56 +98,40 @@ public void FindGridsIntersecting(MapId mapId, Box2Rotated worldBounds, ref List #region MapEnt - public void FindGridsIntersecting(EntityUid mapEnt, PolygonShape shape, Transform transform, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) + public void FindGridsIntersecting( + EntityUid mapEnt, + IPhysShape shape, + Transform transform, + GridCallback callback, + bool approx = IMapManager.Approximate, + bool includeMap = IMapManager.IncludeMap) { - if (!_gridTreeQuery.TryGetComponent(mapEnt, out var gridTree)) - return; - - if (includeMap && _gridQuery.TryGetComponent(mapEnt, out var mapGrid)) - { - callback(mapEnt, mapGrid); - } - - var worldAABB = shape.ComputeAABB(transform, 0); - - var gridState = new GridQueryState( - callback, - worldAABB, - shape, - transform, - gridTree.Tree, - _mapSystem, - this, - _transformSystem, - approx); - - - gridTree.Tree.Query(ref gridState, static (ref GridQueryState state, DynamicTree.Proxy proxy) => - { - // Even for approximate we'll check if any chunks roughly overlap. - var data = state.Tree.GetUserData(proxy); - var gridInvMatrix = state.TransformSystem.GetInvWorldMatrix(data.Uid); - var localAABB = gridInvMatrix.TransformBox(state.WorldAABB); - - var overlappingChunks = state.MapSystem.GetLocalMapChunks(data.Uid, data.Grid, localAABB); + FindGridsIntersecting(mapEnt, shape, shape.ComputeAABB(transform, 0), transform, callback, approx, includeMap); + } - if (state.Approximate) - { - if (!overlappingChunks.MoveNext(out _)) - return true; - } - else if (!state.MapManager.IsIntersecting(overlappingChunks, state.Shape, state.Transform, data.Uid)) - { - return true; - } + private void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, Box2 worldAABB, Transform transform, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) + { + // This is here so we don't double up on code. + var state = callback; - state.Callback(data.Uid, data.Grid); + FindGridsIntersecting(mapEnt, shape, worldAABB, transform, ref state, + static (EntityUid uid, MapGridComponent grid, ref GridCallback state) => state.Invoke(uid, grid), + approx: approx, includeMap: includeMap); + } - return true; - }, worldAABB); + public void FindGridsIntersecting( + EntityUid mapEnt, + IPhysShape shape, + Transform transform, + ref TState state, + GridCallback callback, + bool approx = IMapManager.Approximate, + bool includeMap = IMapManager.IncludeMap) + { + FindGridsIntersecting(mapEnt, shape, shape.ComputeAABB(transform, 0), transform, ref state, callback, approx, includeMap); } - public void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, Transform transform, + private void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, Box2 worldAABB, Transform transform, ref TState state, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) { if (!_gridTreeQuery.TryGetComponent(mapEnt, out var gridTree)) @@ -157,8 +142,6 @@ public void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, Tr callback(mapEnt, mapGrid, ref state); } - var worldAABB = shape.ComputeAABB(transform, 0); - var gridState = new GridQueryState( callback, state, @@ -209,16 +192,22 @@ public void FindGridsIntersecting(EntityUid mapEnt, List shapes, Tra { foreach (var shape in shapes) { - FindGridsIntersecting(mapEnt, shape, transform, ref entities); + FindGridsIntersecting(mapEnt, shape, shape.ComputeAABB(transform, 0), transform, ref entities); } } public void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, Transform transform, ref List> grids, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) + { + FindGridsIntersecting(mapEnt, shape, shape.ComputeAABB(transform, 0), transform, ref grids, approx, includeMap); + } + + public void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, Box2 worldAABB, Transform transform, + ref List> grids, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) { var state = grids; - FindGridsIntersecting(mapEnt, shape, transform, ref state, + FindGridsIntersecting(mapEnt, shape, worldAABB, transform, ref state, static (EntityUid uid, MapGridComponent grid, ref List> list) => { list.Add((uid, grid)); @@ -228,51 +217,39 @@ public void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, Transform public void FindGridsIntersecting(EntityUid mapEnt, Box2 worldAABB, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) { - var shape = new PolygonShape(); - shape.SetAsBox(worldAABB); - FindGridsIntersecting(mapEnt, shape, Transform.Empty, callback, approx, includeMap); + FindGridsIntersecting(mapEnt, new Polygon(worldAABB), worldAABB, Transform.Empty, callback, approx, includeMap); } public void FindGridsIntersecting(EntityUid mapEnt, Box2 worldAABB, ref TState state, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) { - var shape = new PolygonShape(); - shape.SetAsBox(worldAABB); - FindGridsIntersecting(mapEnt, shape, Transform.Empty, ref state, callback, approx, includeMap); + FindGridsIntersecting(mapEnt, new Polygon(worldAABB), worldAABB, Transform.Empty, ref state, callback, approx, includeMap); } public void FindGridsIntersecting(EntityUid mapEnt, Box2 worldAABB, ref List> grids, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) { - var shape = new PolygonShape(); - shape.SetAsBox(worldAABB); - FindGridsIntersecting(mapEnt, shape, Transform.Empty, ref grids, approx, includeMap); + FindGridsIntersecting(mapEnt, new Polygon(worldAABB), worldAABB, Transform.Empty, ref grids, approx, includeMap); } public void FindGridsIntersecting(EntityUid mapEnt, Box2Rotated worldBounds, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) { - var shape = new PolygonShape(); - shape.Set(worldBounds); - - FindGridsIntersecting(mapEnt, shape, Transform.Empty, callback, approx, includeMap); + var shape = new Polygon(worldBounds); + FindGridsIntersecting(mapEnt, shape, worldBounds.CalcBoundingBox(), Transform.Empty, callback, approx, includeMap); } public void FindGridsIntersecting(EntityUid mapEnt, Box2Rotated worldBounds, ref TState state, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) { - var shape = new PolygonShape(); - shape.Set(worldBounds); - - FindGridsIntersecting(mapEnt, shape, Transform.Empty, ref state, callback, approx, includeMap); + var shape = new Polygon(worldBounds); + FindGridsIntersecting(mapEnt, shape, worldBounds.CalcBoundingBox(), Transform.Empty, ref state, callback, approx, includeMap); } public void FindGridsIntersecting(EntityUid mapEnt, Box2Rotated worldBounds, ref List> grids, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) { - var shape = new PolygonShape(); - shape.Set(worldBounds); - - FindGridsIntersecting(mapEnt, shape, Transform.Empty, ref grids, approx, includeMap); + var shape = new Polygon(worldBounds); + FindGridsIntersecting(mapEnt, shape, worldBounds.CalcBoundingBox(), Transform.Empty, ref grids, approx, includeMap); } #endregion diff --git a/Robust.Shared/Physics/Collision/DistanceProxy.cs b/Robust.Shared/Physics/Collision/DistanceProxy.cs index a531bc895c2..7312d732a66 100644 --- a/Robust.Shared/Physics/Collision/DistanceProxy.cs +++ b/Robust.Shared/Physics/Collision/DistanceProxy.cs @@ -28,6 +28,7 @@ using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Shapes; using Robust.Shared.Utility; namespace Robust.Shared.Physics.Collision; @@ -61,9 +62,18 @@ public void Set(IPhysShape shape, int index) break; case ShapeType.Polygon: - var polygon = (PolygonShape) shape; - Vertices = polygon.Vertices; - Radius = polygon.Radius; + if (shape is Polygon poly) + { + Vertices = poly.Vertices; + Radius = poly.Radius; + } + else + { + var polyShape = (PolygonShape) shape; + Vertices = polyShape.Vertices; + Radius = polyShape.Radius; + } + break; case ShapeType.Chain: diff --git a/Robust.Shared/Physics/Shapes/Polygon.cs b/Robust.Shared/Physics/Shapes/Polygon.cs new file mode 100644 index 00000000000..78fd9a55a4b --- /dev/null +++ b/Robust.Shared/Physics/Shapes/Polygon.cs @@ -0,0 +1,226 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using Robust.Shared.Maths; +using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Utility; + +namespace Robust.Shared.Physics.Shapes; + +// Internal so people don't use it when it will have breaking changes very soon. +internal record struct Polygon : IPhysShape +{ + public static Polygon Empty = new(Box2.Empty); + + [DataField] + public Vector2[] Vertices; + + public int VertexCount => Vertices.Length; + + public Vector2[] Normals; + + public Vector2 Centroid; + + public int ChildCount => 1; + public float Radius { get; set; } + public ShapeType ShapeType => ShapeType.Polygon; + + // Hopefully this one is short-lived for a few months + public Polygon(IPhysShape shape) : this((PolygonShape) shape) + { + + } + + public Polygon(PolygonShape polyShape) + { + Unsafe.SkipInit(out this); + Vertices = new Vector2[polyShape.VertexCount]; + Normals = new Vector2[polyShape.Normals.Length]; + Radius = polyShape.Radius; + Centroid = polyShape.Centroid; + + Array.Copy(polyShape.Vertices, Vertices, Vertices.Length); + Array.Copy(polyShape.Normals, Normals, Vertices.Length); + } + + public Polygon(Box2 aabb) + { + Unsafe.SkipInit(out this); + Vertices = new Vector2[4]; + Normals = new Vector2[4]; + Radius = 0f; + + Vertices[0] = aabb.BottomLeft; + Vertices[1] = aabb.BottomRight; + Vertices[2] = aabb.TopRight; + Vertices[3] = aabb.TopLeft; + + Normals[0] = new Vector2(0.0f, -1.0f); + Normals[1] = new Vector2(1.0f, 0.0f); + Normals[2] = new Vector2(0.0f, 1.0f); + Normals[3] = new Vector2(-1.0f, 0.0f); + + Centroid = aabb.Center; + } + + public Polygon(Box2Rotated bounds) + { + Unsafe.SkipInit(out this); + Radius = 0f; + Span verts = stackalloc Vector2[4]; + verts[0] = bounds.BottomLeft; + verts[1] = bounds.BottomRight; + verts[2] = bounds.TopRight; + verts[3] = bounds.TopLeft; + + var hull = new PhysicsHull(verts, 4); + Set(hull); + + Centroid = bounds.Center; + } + + public Polygon(Vector2[] vertices) + { + Unsafe.SkipInit(out this); + var hull = PhysicsHull.ComputeHull(vertices, vertices.Length); + + if (hull.Count < 3) + { + Vertices = Array.Empty(); + Normals = Array.Empty(); + return; + } + + Vertices = vertices; + Normals = new Vector2[vertices.Length]; + Set(hull); + Centroid = ComputeCentroid(Vertices); + } + + public static explicit operator Polygon(PolygonShape polyShape) + { + return new Polygon(polyShape); + } + + private void Set(PhysicsHull hull) + { + DebugTools.Assert(hull.Count >= 3); + var vertexCount = hull.Count; + Array.Resize(ref Vertices, vertexCount); + Array.Resize(ref Normals, vertexCount); + + for (var i = 0; i < vertexCount; i++) + { + Vertices[i] = hull.Points[i]; + } + + // Compute normals. Ensure the edges have non-zero length. + for (var i = 0; i < vertexCount; i++) + { + var next = i + 1 < vertexCount ? i + 1 : 0; + var edge = Vertices[next] - Vertices[i]; + DebugTools.Assert(edge.LengthSquared() > float.Epsilon * float.Epsilon); + + var temp = Vector2Helpers.Cross(edge, 1f); + Normals[i] = temp.Normalized(); + } + } + + private static Vector2 ComputeCentroid(Vector2[] vs) + { + var count = vs.Length; + DebugTools.Assert(count >= 3); + + var c = new Vector2(0.0f, 0.0f); + float area = 0.0f; + + // Get a reference point for forming triangles. + // Use the first vertex to reduce round-off errors. + var s = vs[0]; + + const float inv3 = 1.0f / 3.0f; + + for (var i = 0; i < count; ++i) + { + // Triangle vertices. + var p1 = vs[0] - s; + var p2 = vs[i] - s; + var p3 = i + 1 < count ? vs[i+1] - s : vs[0] - s; + + var e1 = p2 - p1; + var e2 = p3 - p1; + + float D = Vector2Helpers.Cross(e1, e2); + + float triangleArea = 0.5f * D; + area += triangleArea; + + // Area weighted centroid + c += (p1 + p2 + p3) * triangleArea * inv3; + } + + // Centroid + DebugTools.Assert(area > float.Epsilon); + c = c * (1.0f / area) + s; + return c; + } + + public Box2 ComputeAABB(Transform transform, int childIndex) + { + DebugTools.Assert(childIndex == 0); + var lower = Transform.Mul(transform, Vertices[0]); + var upper = lower; + + for (var i = 1; i < VertexCount; ++i) + { + var v = Transform.Mul(transform, Vertices[i]); + lower = Vector2.Min(lower, v); + upper = Vector2.Max(upper, v); + } + + var r = new Vector2(Radius, Radius); + return new Box2(lower - r, upper + r); + } + + public bool Equals(IPhysShape? other) + { + if (other is not PolygonShape poly) return false; + if (VertexCount != poly.VertexCount) return false; + for (var i = 0; i < VertexCount; i++) + { + var vert = Vertices[i]; + if (!vert.Equals(poly.Vertices[i])) return false; + } + + return true; + } + + public bool Equals(PolygonShape? other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + if (!Radius.Equals(other.Radius) || VertexCount != other.VertexCount) + return false; + + for (var i = 0; i < VertexCount; i++) + { + var vert = Vertices[i]; + var otherVert = other.Vertices[i]; + + if (!vert.Equals(otherVert)) + return false; + } + + return true; + } + + public override int GetHashCode() + { + return HashCode.Combine(VertexCount, Vertices.AsSpan(0, VertexCount).ToArray(), Radius); + } +} diff --git a/Robust.Shared/Physics/Systems/FixtureSystem.Shapes.cs b/Robust.Shared/Physics/Systems/FixtureSystem.Shapes.cs index f8658fb4b90..c13315396cc 100644 --- a/Robust.Shared/Physics/Systems/FixtureSystem.Shapes.cs +++ b/Robust.Shared/Physics/Systems/FixtureSystem.Shapes.cs @@ -3,6 +3,7 @@ using Robust.Shared.Maths; using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Physics.Shapes; using Robust.Shared.Utility; namespace Robust.Shared.Physics.Systems @@ -28,6 +29,7 @@ public bool TestPoint(IPhysShape shape, Transform xform, Vector2 worldPoint) var distance = worldPoint - center; return Vector2.Dot(distance, distance) <= circle.Radius * circle.Radius; case PolygonShape poly: + { var pLocal = Physics.Transform.MulT(xform.Quaternion2D, worldPoint - xform.Position); for (var i = 0; i < poly.VertexCount; i++) @@ -37,6 +39,19 @@ public bool TestPoint(IPhysShape shape, Transform xform, Vector2 worldPoint) } return true; + } + case Polygon poly: + { + var pLocal = Physics.Transform.MulT(xform.Quaternion2D, worldPoint - xform.Position); + + for (var i = 0; i < poly.VertexCount; i++) + { + var dot = Vector2.Dot(poly.Normals[i], pLocal - poly.Vertices[i]); + if (dot > 0f) return false; + } + + return true; + } default: throw new ArgumentOutOfRangeException($"No implemented TestPoint for {shape.GetType()}"); } diff --git a/Robust.UnitTesting/Shared/EntityLookup_Test.cs b/Robust.UnitTesting/Shared/EntityLookup_Test.cs index b79a28c593e..b7ce0778250 100644 --- a/Robust.UnitTesting/Shared/EntityLookup_Test.cs +++ b/Robust.UnitTesting/Shared/EntityLookup_Test.cs @@ -330,11 +330,11 @@ public void TestAnchoring() var theMapSpotBeingUsed = new Box2(Vector2.Zero, Vector2.One); grid.Comp.SetTile(new Vector2i(), new Tile(1)); - Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList().Count, Is.EqualTo(0)); + Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList(), Is.Empty); // Setup and check it actually worked var dummy = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId)); - Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList().Count, Is.EqualTo(1)); + Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList(), Has.Count.EqualTo(1)); var xform = entManager.GetComponent(dummy); diff --git a/Robust.UnitTesting/Shared/Physics/Shapes/Polygon_Test.cs b/Robust.UnitTesting/Shared/Physics/Shapes/Polygon_Test.cs new file mode 100644 index 00000000000..7009c93b3af --- /dev/null +++ b/Robust.UnitTesting/Shared/Physics/Shapes/Polygon_Test.cs @@ -0,0 +1,46 @@ +using System.Numerics; +using NUnit.Framework; +using Robust.Shared.Maths; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Shapes; + +namespace Robust.UnitTesting.Shared.Physics; + +[TestFixture] +public sealed class Polygon_Test +{ + [Test] + public void TestAABB() + { + var shape = new Polygon(Box2.UnitCentered.Translated(Vector2.One)); + + Assert.That(shape.ComputeAABB(Transform.Empty, 0), Is.EqualTo(Box2.UnitCentered.Translated(Vector2.One))); + } + + [Test] + public void TestBox2() + { + var shape = new Polygon(Box2.UnitCentered.Translated(Vector2.One)); + Assert.That(shape.Vertices, Is.EqualTo(new Vector2[] + { + new Vector2(0.5f, 0.5f), + new Vector2(1.5f, 0.5f), + new Vector2(1.5f, 1.5f), + new Vector2(0.5f, 1.5f), + })); + } + + [Test] + public void TestBox2Rotated() + { + var shape = new Polygon(new Box2Rotated(Box2.UnitCentered, Angle.FromDegrees(90))); + + Assert.That(shape.Vertices, Is.EqualTo(new Vector2[] + { + new Vector2(0.5f, -0.5f), + new Vector2(0.5f, 0.5f), + new Vector2(-0.5f, 0.5f), + new Vector2(-0.5f, -0.5f), + })); + } +} From fe33ad265209e027496c01708983006f4ea6e2c4 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 27 Aug 2024 23:24:23 +1000 Subject: [PATCH 053/140] Add physicshull tests (#5404) --- .../Shared/Physics/PhysicsHull_Test.cs | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 Robust.UnitTesting/Shared/Physics/PhysicsHull_Test.cs diff --git a/Robust.UnitTesting/Shared/Physics/PhysicsHull_Test.cs b/Robust.UnitTesting/Shared/Physics/PhysicsHull_Test.cs new file mode 100644 index 00000000000..a19e17828c7 --- /dev/null +++ b/Robust.UnitTesting/Shared/Physics/PhysicsHull_Test.cs @@ -0,0 +1,95 @@ +using System; +using System.Numerics; +using NUnit.Framework; +using Robust.Shared.Physics; + +namespace Robust.UnitTesting.Shared.Physics; + +internal sealed class PhysicsHull_Test +{ + private static readonly TestCaseData[] CollinearHulls = new TestCaseData[] + { + new TestCaseData(new Vector2[] + { + Vector2.Zero, + Vector2.One, + Vector2.UnitY, + }, 3), + // Same points + new TestCaseData(new Vector2[] + { + Vector2.Zero, + Vector2.One, + Vector2.One, + Vector2.UnitY, + }, 3), + new TestCaseData(new Vector2[] + { + Vector2.Zero, + Vector2.UnitX / 2f, + Vector2.UnitX, + Vector2.UnitY, + }, 3), + }; + + [Test, TestCaseSource(nameof(CollinearHulls))] + public void CollinearTest(Vector2[] vertices, int count) + { + var hull = PhysicsHull.ComputeHull(vertices.AsSpan(), vertices.Length); + Assert.That(hull.Count, Is.EqualTo(count)); + } + + private static readonly TestCaseData[] ValidateHulls = new TestCaseData[] + { + new TestCaseData(Array.Empty(), false), + new TestCaseData(new Vector2[] + { + Vector2.Zero, + Vector2.One, + Vector2.UnitY, + }, true), + new TestCaseData(new Vector2[] + { + Vector2.Zero, + Vector2.UnitX, + Vector2.One, + Vector2.UnitY, + }, true), + // Same point + new TestCaseData(new Vector2[] + { + Vector2.Zero, + Vector2.One, + Vector2.One, + Vector2.UnitY, + }, false), + // Collinear point + new TestCaseData(new Vector2[] + { + Vector2.Zero, + Vector2.One / 2f, + Vector2.One, + }, false), + // Too many verts + new TestCaseData(new Vector2[] + { + Vector2.Zero, + Vector2.UnitX, + Vector2.One * 1f, + Vector2.One * 2f, + Vector2.One * 3f, + Vector2.One * 4f, + Vector2.One * 5f, + Vector2.One * 6f, + Vector2.One * 7f, + Vector2.One * 8f, + }, false), + }; + + [Test, TestCaseSource(nameof(ValidateHulls))] + public void ValidationTest(Vector2[] vertices, bool result) + { + var hull = new PhysicsHull(vertices.AsSpan(), vertices.Length); + Assert.That(PhysicsHull.ValidateHull(hull), Is.EqualTo(result)); + } +} From b96419f0b21a4c97779d95f9c54e9a3ac6aaf90c Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 28 Aug 2024 00:24:24 +1000 Subject: [PATCH 054/140] Add mapmanager query tests (#5403) Sanity --- Robust.UnitTesting/Shared/Map/Query_Tests.cs | 79 ++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Robust.UnitTesting/Shared/Map/Query_Tests.cs diff --git a/Robust.UnitTesting/Shared/Map/Query_Tests.cs b/Robust.UnitTesting/Shared/Map/Query_Tests.cs new file mode 100644 index 00000000000..22d229c7234 --- /dev/null +++ b/Robust.UnitTesting/Shared/Map/Query_Tests.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using NUnit.Framework; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Maths; +using Robust.UnitTesting.Server; + +namespace Robust.UnitTesting.Shared.Map; + +[TestFixture] +public sealed class Query_Tests +{ + private static readonly TestCaseData[] Box2Data = new[] + { + new TestCaseData( + Vector2.Zero, + 0f, + Box2.UnitCentered.Translated(new Vector2(0f, 10f)), + true + ), + + new TestCaseData( + Vector2.Zero, + MathF.PI, + Box2.UnitCentered.Translated(new Vector2(0f, 10f)), + false + ), + + new TestCaseData( + Vector2.Zero, + MathF.PI, + Box2.UnitCentered.Translated(new Vector2(0f, -10f)), + true + ), + + new TestCaseData( + Vector2.Zero, + MathF.PI / 2f, + Box2.UnitCentered.Translated(new Vector2(-10f, 0f)), + true + ), + + new TestCaseData( + Vector2.Zero, + MathF.PI / 4f, + Box2.UnitCentered.Translated(new Vector2(-5f, 5f)), + true + ), + }; + + [Test, TestCaseSource(nameof(Box2Data))] + public void TestBox2GridIntersection(Vector2 position, float radians, Box2 worldAABB, bool result) + { + var sim = RobustServerSimulation.NewSimulation().InitializeInstance(); + + var entManager = sim.Resolve(); + var mapManager = sim.Resolve(); + var mapSystem = entManager.System(); + var xformSystem = entManager.System(); + + var map = mapSystem.CreateMap(); + var grid = mapManager.CreateGridEntity(map); + + for (var i = 0; i < 10; i++) + { + mapSystem.SetTile(grid, new Vector2i(0, i), new Tile(1)); + } + + xformSystem.SetWorldRotation(grid.Owner, radians); + + var grids = new List>(); + mapManager.FindGridsIntersecting(map, worldAABB, ref grids); + + Assert.That(grids.Count > 0, Is.EqualTo(result)); + } +} From 903041dfd1f20f56057be95f830103b8d0ef4e40 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 28 Aug 2024 01:36:54 +1000 Subject: [PATCH 055/140] Add storage BUI bandaid (#5401) --- .../UserInterface/BoundUserInterface.cs | 7 ++++ .../Systems/SharedUserInterfaceSystem.cs | 42 +++++++++++++------ 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs index c80debab8c5..c5edf5a34d8 100644 --- a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs +++ b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs @@ -28,6 +28,13 @@ public abstract class BoundUserInterface : IDisposable /// protected internal BoundUserInterfaceState? State { get; internal set; } + // Bandaid just for storage :) + /// + /// Defers state handling + /// + [Obsolete] + public virtual bool DeferredClose { get; } = true; + protected BoundUserInterface(EntityUid owner, Enum uiKey) { IoCManager.InjectDependencies(this); diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 50bfafcc698..5b9878705b4 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -39,7 +39,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem /// /// Defer closing BUIs during state handling so client doesn't spam a BUI constantly during prediction. /// - private HashSet _queuedCloses = new(); + private readonly List _queuedCloses = new(); public override void Initialize() { @@ -221,10 +221,15 @@ private void CloseUiInternal(Entity ent, Enum key, Enti } } - // If we're client we want this handled immediately. if (ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui)) { - _queuedCloses.Add(cBui); + if (cBui.DeferredClose) + _queuedCloses.Add(cBui); + else + { + ent.Comp.ClientOpenInterfaces.Remove(key); + cBui.Dispose(); + } } if (ent.Comp.Actors.Count == 0) @@ -382,9 +387,10 @@ private void OnUserInterfaceHandleState(Entity ent, ref } var attachedEnt = _player.LocalEntity; + var clientBuis = new ValueList(ent.Comp.ClientOpenInterfaces.Keys); // Check if the UI is open by us, otherwise dispose of it. - foreach (var (key, bui) in ent.Comp.ClientOpenInterfaces) + foreach (var key in clientBuis) { if (ent.Comp.Actors.TryGetValue(key, out var actors) && (attachedEnt == null || actors.Contains(attachedEnt.Value))) @@ -392,7 +398,15 @@ private void OnUserInterfaceHandleState(Entity ent, ref continue; } - _queuedCloses.Add(bui); + var bui = ent.Comp.ClientOpenInterfaces[key]; + + if (bui.DeferredClose) + _queuedCloses.Add(bui); + else + { + ent.Comp.ClientOpenInterfaces.Remove(key); + bui.Dispose(); + } } // update any states we have open @@ -434,6 +448,15 @@ private void EnsureClientBui(Entity entity, Enum key, In // If it's out BUI open it up and apply the state, otherwise do nothing. var player = Player.LocalEntity; + // Existing BUI just keep it. + if (entity.Comp.ClientOpenInterfaces.TryGetValue(key, out var existing)) + { + if (existing.DeferredClose) + _queuedCloses.Remove(existing); + + return; + } + if (player == null || !entity.Comp.Actors.TryGetValue(key, out var actors) || !actors.Contains(player.Value)) @@ -443,13 +466,6 @@ private void EnsureClientBui(Entity entity, Enum key, In DebugTools.Assert(_netManager.IsClient); - // Existing BUI just keep it. - if (entity.Comp.ClientOpenInterfaces.TryGetValue(key, out var existing)) - { - _queuedCloses.Remove(existing); - return; - } - // Try-catch to try prevent error loops / bricked clients that constantly throw exceptions while applying game // states. E.g., stripping UI used to throw NREs in some instances while fetching the identity of unknown // entities. @@ -941,7 +957,7 @@ public override void Update(float frameTime) { foreach (var bui in _queuedCloses) { - if (TryComp(bui.Owner, out UserInterfaceComponent? uiComp)) + if (UIQuery.TryComp(bui.Owner, out var uiComp)) { uiComp.ClientOpenInterfaces.Remove(bui.UiKey); } From 12b0bc4e0a5612beb80e433e87b49c92466ad358 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 27 Aug 2024 17:38:48 +0200 Subject: [PATCH 056/140] Add way for content to write arbitrary files into replay. (#5405) Added a new RecordingStopped2 event that receives a IReplayFileWriter object that can be used to write arbitrary files into the replay zip file. Fixes #5261 --- .../Replays/IReplayRecordingManager.cs | 56 +++++++++++++++++++ .../Replays/SharedReplayRecordingManager.cs | 29 ++++++++++ 2 files changed, 85 insertions(+) diff --git a/Robust.Shared/Replays/IReplayRecordingManager.cs b/Robust.Shared/Replays/IReplayRecordingManager.cs index 72f0b6b864a..0a6b0adb70a 100644 --- a/Robust.Shared/Replays/IReplayRecordingManager.cs +++ b/Robust.Shared/Replays/IReplayRecordingManager.cs @@ -2,6 +2,7 @@ using Robust.Shared.Serialization.Markdown.Mapping; using System; using System.Collections.Generic; +using System.IO.Compression; using System.Threading.Tasks; using Robust.Shared.ContentPack; using Robust.Shared.GameStates; @@ -71,8 +72,17 @@ public interface IReplayRecordingManager /// This gets invoked whenever a replay recording is stopping. Subscribers can use this to add extra yaml data to the /// recording's metadata file. /// + /// event Action RecordingStopped; + /// + /// This gets invoked whenever a replay recording is stopping. Subscribers can use this to add extra data to the replay. + /// + /// + /// This is effectively a more powerful version of . + /// + event Action RecordingStopped2; + /// /// This gets invoked after a replay recording has finished and provides information about where the replay data /// was saved. Note that this only means that all write tasks have started, however some of the file tasks may not @@ -131,6 +141,27 @@ bool TryStartRecording( bool IsWriting(); } +/// +/// Event object used by . +/// Allows modifying metadata and adding more data to replay files. +/// +public sealed class ReplayRecordingStopped +{ + /// + /// Mutable metadata that will be saved to the replay's metadata file. + /// + public required MappingDataNode Metadata { get; init; } + + /// + /// A writer that allows arbitrary file writing into the replay file. + /// + public required IReplayFileWriter Writer { get; init; } + + internal ReplayRecordingStopped() + { + } +} + /// /// Event data for . /// @@ -148,6 +179,31 @@ public record ReplayRecordingFinished(IWritableDirProvider Directory, ResPath Pa /// The total uncompressed size of the replay data blobs. public record struct ReplayRecordingStats(TimeSpan Time, uint Ticks, long Size, long UncompressedSize); +/// +/// Allows writing extra files directly into the replay file. +/// +/// +/// +public interface IReplayFileWriter +{ + /// + /// The base directory inside the replay directory you should generally be writing to. + /// This is equivalent to . + /// + ResPath BaseReplayPath { get; } + + /// + /// Writes arbitrary data into a file in the replay. + /// + /// The file path to write to. + /// The bytes to write to the file. + /// How much to compress the file. + void WriteBytes( + ResPath path, + ReadOnlyMemory bytes, + CompressionLevel compressionLevel = CompressionLevel.Optimal); +} + /// /// Engine-internal functions for . /// diff --git a/Robust.Shared/Replays/SharedReplayRecordingManager.cs b/Robust.Shared/Replays/SharedReplayRecordingManager.cs index 9291d987dbc..b5d842d53b4 100644 --- a/Robust.Shared/Replays/SharedReplayRecordingManager.cs +++ b/Robust.Shared/Replays/SharedReplayRecordingManager.cs @@ -49,6 +49,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM public event Action>? RecordingStarted; public event Action? RecordingStopped; + public event Action? RecordingStopped2; public event Action? RecordingFinished; private ISawmill _sawmill = default!; @@ -312,6 +313,7 @@ protected virtual void Reset() // File stream & compression context is always disposed from the worker task. _recState.WriteCommandChannel.Complete(); + _recState.Done = true; _recState = null; } @@ -373,6 +375,11 @@ private void WriteFinalMetadata(RecordingState recState) { var yamlMetadata = new MappingDataNode(); RecordingStopped?.Invoke(yamlMetadata); + RecordingStopped2?.Invoke(new ReplayRecordingStopped + { + Metadata = yamlMetadata, + Writer = new ReplayFileWriter(this, recState) + }); var time = Timing.CurTime - recState.StartTime; yamlMetadata[MetaFinalKeyEndTick] = new ValueDataNode(Timing.CurTick.Value.ToString()); yamlMetadata[MetaFinalKeyDuration] = new ValueDataNode(time.ToString()); @@ -384,6 +391,7 @@ private void WriteFinalMetadata(RecordingState recState) // this just overwrites the previous yml with additional data. var document = new YamlDocument(yamlMetadata.ToYaml()); WriteYaml(recState, ReplayZipFolder / FileMetaFinal, document); + UpdateWriteTasks(); Reset(); @@ -492,6 +500,8 @@ private sealed class RecordingState public long CompressedSize; public long UncompressedSize; + public bool Done; + public RecordingState( ZipArchive zip, MemoryStream buffer, @@ -518,4 +528,23 @@ public RecordingState( WriteCommandChannel = writeCommandChannel; } } + + private sealed class ReplayFileWriter(SharedReplayRecordingManager manager, RecordingState state) + : IReplayFileWriter + { + public ResPath BaseReplayPath => ReplayZipFolder; + + public void WriteBytes(ResPath path, ReadOnlyMemory bytes, CompressionLevel compressionLevel) + { + CheckDisposed(); + + manager.WriteBytes(state, path, bytes, compressionLevel); + } + + private void CheckDisposed() + { + if (state.Done) + throw new ObjectDisposedException(nameof(ReplayFileWriter)); + } + } } From ec3a74d26868f9640ac06170bdf1c2557d19a063 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 27 Aug 2024 17:47:25 +0200 Subject: [PATCH 057/140] Version: 231.1.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index db4d1369cb2..888dc103cdc 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 231.0.1 + 231.1.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index cca0eb955d9..d7c6ffe672d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,24 @@ END TEMPLATE--> *None yet* +## 231.1.0 + +### New features + +* Network `InterfaceData` on `UserInterfaceComponent`. +* Added `System.Decimal` to sandbox. +* Added XAML hot reloading. +* Added API for content to write custom files into replay through `IReplayFileWriter`. + +### Other + +* Optimized `EntityLookup` and other physics systems. + +### Internal + +* Added more tests related to physics. + + ## 231.0.1 ### Other From 23a23f7c224c10dfcab3c5057f2ca8005fa0f2e1 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:22:47 +1200 Subject: [PATCH 058/140] Misc toolshed fixes (#5340) * Prevent map/emplace command errors from locking up the server * Fix EmplaceCommand * Fix sort commands * Fix JoinCommand * changelog --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- RELEASE-NOTES.md | 5 +++-- .../Toolshed/Commands/Generic/EmplaceCommand.cs | 4 ++-- .../Commands/Generic/Ordering/SortCommand.cs | 4 +--- .../Commands/Generic/Ordering/SortDownCommand.cs | 4 +--- Robust.Shared/Toolshed/Commands/Math/ListCommands.cs | 4 ++-- Robust.Shared/Toolshed/Syntax/Expression.cs | 12 ++++++++++++ 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index d7c6ffe672d..e5c8433d66b 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,11 +43,12 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* Fixed a bug where the client might not add entities to the broadphase/lookup components. +* Fixed various toolshed commands not working, including `sort`, `sortdown` `join` (for strings), and `emplace` ### Other -*None yet* +* Toolshed command blocks now stop executing if previous errors were not handled / cleared. ### Internal diff --git a/Robust.Shared/Toolshed/Commands/Generic/EmplaceCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/EmplaceCommand.cs index c88df018524..e330846e6ba 100644 --- a/Robust.Shared/Toolshed/Commands/Generic/EmplaceCommand.cs +++ b/Robust.Shared/Toolshed/Commands/Generic/EmplaceCommand.cs @@ -16,7 +16,7 @@ public sealed class EmplaceCommand : ToolshedCommand public override Type[] TypeParameterParsers => new[] {typeof(Type)}; [CommandImplementation, TakesPipedTypeAsGeneric] - TOut Emplace( + TOut Emplace( [CommandInvocationContext] IInvocationContext ctx, [PipedArgument] TIn value, [CommandArgument] Block block @@ -27,7 +27,7 @@ [CommandArgument] Block block } [CommandImplementation, TakesPipedTypeAsGeneric] - IEnumerable Emplace( + IEnumerable Emplace( [CommandInvocationContext] IInvocationContext ctx, [PipedArgument] IEnumerable value, [CommandArgument] Block block diff --git a/Robust.Shared/Toolshed/Commands/Generic/Ordering/SortCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/Ordering/SortCommand.cs index 9f5295768eb..972b52e057b 100644 --- a/Robust.Shared/Toolshed/Commands/Generic/Ordering/SortCommand.cs +++ b/Robust.Shared/Toolshed/Commands/Generic/Ordering/SortCommand.cs @@ -5,11 +5,9 @@ namespace Robust.Shared.Toolshed.Commands.Generic.Ordering; -[ToolshedCommand, MapLikeCommand] +[ToolshedCommand] public sealed class SortCommand : ToolshedCommand { - public override Type[] TypeParameterParsers => new[] {typeof(Type)}; - [CommandImplementation, TakesPipedTypeAsGeneric] public IEnumerable Sort( [CommandInvocationContext] IInvocationContext ctx, diff --git a/Robust.Shared/Toolshed/Commands/Generic/Ordering/SortDownCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/Ordering/SortDownCommand.cs index baf6b59743e..e81fefb75aa 100644 --- a/Robust.Shared/Toolshed/Commands/Generic/Ordering/SortDownCommand.cs +++ b/Robust.Shared/Toolshed/Commands/Generic/Ordering/SortDownCommand.cs @@ -5,11 +5,9 @@ namespace Robust.Shared.Toolshed.Commands.Generic.Ordering; -[ToolshedCommand, MapLikeCommand] +[ToolshedCommand] public sealed class SortDownCommand : ToolshedCommand { - public override Type[] TypeParameterParsers => new[] {typeof(Type)}; - [CommandImplementation, TakesPipedTypeAsGeneric] public IEnumerable Sort( [CommandInvocationContext] IInvocationContext ctx, diff --git a/Robust.Shared/Toolshed/Commands/Math/ListCommands.cs b/Robust.Shared/Toolshed/Commands/Math/ListCommands.cs index 579896b619b..368fa1957d9 100644 --- a/Robust.Shared/Toolshed/Commands/Math/ListCommands.cs +++ b/Robust.Shared/Toolshed/Commands/Math/ListCommands.cs @@ -7,7 +7,7 @@ namespace Robust.Shared.Toolshed.Commands.Math; [ToolshedCommand] public sealed class JoinCommand : ToolshedCommand { - [CommandImplementation, TakesPipedTypeAsGeneric] + [CommandImplementation] public string Join( [CommandInvocationContext] IInvocationContext ctx, [PipedArgument] string x, @@ -18,7 +18,7 @@ [CommandArgument] ValueRef y if (yVal is null) return x; - return x + y; + return x + yVal; } [CommandImplementation, TakesPipedTypeAsGeneric] diff --git a/Robust.Shared/Toolshed/Syntax/Expression.cs b/Robust.Shared/Toolshed/Syntax/Expression.cs index d7c10149014..8472ddfd4b8 100644 --- a/Robust.Shared/Toolshed/Syntax/Expression.cs +++ b/Robust.Shared/Toolshed/Syntax/Expression.cs @@ -74,6 +74,18 @@ public static bool TryParse(bool doAutocomplete, public object? Invoke(object? input, IInvocationContext ctx, bool reportErrors = true) { + // TODO TOOLSHED + // improve error handling. Most expression invokers don't bother to check for errors. + // This especially applies to all map / emplace / sort commands. + // A simple error while enumerating entities could lock up the server. + + if (ctx.GetErrors().Any()) + { + // Attempt to prevent O(n^2) growth in errors due to people repeatedly evaluating expressions without + // checking for errors. + throw new Exception($"Improperly handled Toolshed errors"); + } + var ret = input; foreach (var (cmd, span) in Commands) { From b0cb41e94a71db0495810705696b035424f55093 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Wed, 28 Aug 2024 12:23:04 +1000 Subject: [PATCH 059/140] Version: 231.1.1 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 888dc103cdc..6fba408d005 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 231.1.0 + 231.1.1 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index e5c8433d66b..a6cd8bef96d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,18 +43,29 @@ END TEMPLATE--> ### Bugfixes -* Fixed a bug where the client might not add entities to the broadphase/lookup components. -* Fixed various toolshed commands not working, including `sort`, `sortdown` `join` (for strings), and `emplace` +*None yet* ### Other -* Toolshed command blocks now stop executing if previous errors were not handled / cleared. +*None yet* ### Internal *None yet* +## 231.1.1 + +### Bugfixes + +* Fixed a bug where the client might not add entities to the broadphase/lookup components. +* Fixed various toolshed commands not working, including `sort`, `sortdown` `join` (for strings), and `emplace` + +### Other + +* Toolshed command blocks now stop executing if previous errors were not handled / cleared. + + ## 231.1.0 ### New features From 6eb080a277c1b3d30ce3c6dddf79e285566e5c16 Mon Sep 17 00:00:00 2001 From: Winkarst <74284083+Winkarst-cpu@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:49:42 +0300 Subject: [PATCH 060/140] Add Robust.Xaml.csproj to the solution (#5408) --- RobustToolbox.sln | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/RobustToolbox.sln b/RobustToolbox.sln index 43580afda26..5eefb5aa730 100644 --- a/RobustToolbox.sln +++ b/RobustToolbox.sln @@ -53,6 +53,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cefglue", "cefglue", "{2D78 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CefGlue", "cefglue\CefGlue\CefGlue.csproj", "{6BC71226-BA9C-4CD6-9838-03AC076F9518}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Xaml", "Robust.Xaml\Robust.Xaml.csproj", "{EC7BA4C0-A02F-40E8-B4FC-9A96D91BD1EC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -229,6 +231,14 @@ Global {6BC71226-BA9C-4CD6-9838-03AC076F9518}.Release|Any CPU.Build.0 = Release|Any CPU {6BC71226-BA9C-4CD6-9838-03AC076F9518}.Release|x64.ActiveCfg = Release|Any CPU {6BC71226-BA9C-4CD6-9838-03AC076F9518}.Release|x64.Build.0 = Release|Any CPU + {EC7BA4C0-A02F-40E8-B4FC-9A96D91BD1EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC7BA4C0-A02F-40E8-B4FC-9A96D91BD1EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC7BA4C0-A02F-40E8-B4FC-9A96D91BD1EC}.Debug|x64.ActiveCfg = Debug|Any CPU + {EC7BA4C0-A02F-40E8-B4FC-9A96D91BD1EC}.Debug|x64.Build.0 = Debug|Any CPU + {EC7BA4C0-A02F-40E8-B4FC-9A96D91BD1EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC7BA4C0-A02F-40E8-B4FC-9A96D91BD1EC}.Release|Any CPU.Build.0 = Release|Any CPU + {EC7BA4C0-A02F-40E8-B4FC-9A96D91BD1EC}.Release|x64.ActiveCfg = Release|Any CPU + {EC7BA4C0-A02F-40E8-B4FC-9A96D91BD1EC}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From e5995d4edc8f8ca2c1f864d26e2b78394db680b4 Mon Sep 17 00:00:00 2001 From: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:43:58 +0200 Subject: [PATCH 061/140] Add ObjectSerializer, AppearanceComponent.AppearanceDataInit, and AppearanceSystem.AppendData (#5324) * V1 commit * V2 Commit, ObjectSerializer * Make sure write for objects have the !type: set * Added AppearanceDataInit * Change to AppearanceDataInit setting to AppearanceData the moment it itself gets set; ComponentInit is too late. Forgive me sloth. * RELEASE-NOTES.md * Fix release notes * Fix release-notes for realsies --- RELEASE-NOTES.md | 13 ++- .../Appearance/AppearanceComponent.cs | 15 +++ .../Systems/SharedAppearanceSystem.cs | 26 ++++++ .../Manager/SerializationManager.Writing.cs | 7 +- .../Generic/ObjectSerializer.cs | 92 +++++++++++++++++++ 5 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/ObjectSerializer.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index a6cd8bef96d..b1716e45260 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,23 +35,26 @@ END TEMPLATE--> ### Breaking changes -*None yet* +* Obsolete method `AppearanceComponent.TryGetData` is now access-restricted to `SharedAppearanceSystem`; use `SharedAppearanceSystem.TryGetData` instead. ### New features -*None yet* +* Added `SharedAppearanceSystem.AppendData`, which appends non-existing `AppearanceData` from one `AppearanceComponent` to another. +* Added `AppearanceComponent.AppearanceDataInit`, which can be used to set initial `AppearanceData` entries in .yaml. ### Bugfixes -*None yet* +* Fix multithreading bug in ParallelTracker that caused the game to crash randomly. ### Other -*None yet* +* Added obsoletion warning for `Control.Dispose()`. New code should not rely on it. +* Reduced the default tickrate to 30 ticks. +* Serialization will now add type tags (`!type:`) for necessary `NodeData` when writing (currently only for `object` nodes). ### Internal -*None yet* +* Added `ObjectSerializer`, which handles serialization of the generic `object` type. ## 231.1.1 diff --git a/Robust.Shared/GameObjects/Components/Appearance/AppearanceComponent.cs b/Robust.Shared/GameObjects/Components/Appearance/AppearanceComponent.cs index 8816d055227..daef024d494 100644 --- a/Robust.Shared/GameObjects/Components/Appearance/AppearanceComponent.cs +++ b/Robust.Shared/GameObjects/Components/Appearance/AppearanceComponent.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Robust.Shared.GameStates; +using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; namespace Robust.Shared.GameObjects; @@ -15,6 +16,7 @@ namespace Robust.Shared.GameObjects; /// Visualization works client side with derivatives of the VisualizerSystem class and corresponding components. /// [RegisterComponent, NetworkedComponent] +[Access(typeof(SharedAppearanceSystem))] public sealed partial class AppearanceComponent : Component { /// @@ -32,6 +34,19 @@ public sealed partial class AppearanceComponent : Component [ViewVariables] internal Dictionary AppearanceData = new(); + private Dictionary? _appearanceDataInit; + + /// + /// Sets starting values for AppearanceData. + /// + /// + /// Should only be filled in via prototype .yaml; subsequent data must be set via SharedAppearanceSystem.SetData(). + /// + [DataField(readOnly: true)] public Dictionary? AppearanceDataInit { + get { return _appearanceDataInit; } + set { AppearanceData = value ?? AppearanceData; _appearanceDataInit = value; } + } + [Obsolete("Use SharedAppearanceSystem instead")] public bool TryGetData(Enum key, [NotNullWhen(true)] out T data) { diff --git a/Robust.Shared/GameObjects/Systems/SharedAppearanceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedAppearanceSystem.cs index 38ea710beac..55905a26df6 100644 --- a/Robust.Shared/GameObjects/Systems/SharedAppearanceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedAppearanceSystem.cs @@ -115,6 +115,32 @@ public void CopyData(Entity src, Entity + /// Appends appearance data from src to dest. If a key/value pair already exists in dest, it gets replaced. + /// If src has no nothing is done. + /// If dest has no AppearanceComponent then it is created. + /// + public void AppendData(Entity src, Entity dest) + { + if (!Resolve(src, ref src.Comp, false)) + return; + + AppendData(src.Comp, dest); + } + + public void AppendData(AppearanceComponent srcComp, Entity dest) + { + dest.Comp ??= EnsureComp(dest); + + foreach (var (key, value) in srcComp.AppearanceData) + { + dest.Comp.AppearanceData[key] = value; + } + + Dirty(dest, dest.Comp); + QueueUpdate(dest, dest.Comp); + } } [Serializable, NetSerializable] diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.Writing.cs b/Robust.Shared/Serialization/Manager/SerializationManager.Writing.cs index 40f2e577d5f..3bc5109dc28 100644 --- a/Robust.Shared/Serialization/Manager/SerializationManager.Writing.cs +++ b/Robust.Shared/Serialization/Manager/SerializationManager.Writing.cs @@ -260,7 +260,12 @@ public DataNode WriteValue(T value, bool alwaysWrite = false, ISerializationC return ValueDataNode.Null(); } - return GetOrCreateWriteGenericDelegate(value, notNullableOverride)(value, alwaysWrite, context); + var node = GetOrCreateWriteGenericDelegate(value, notNullableOverride)(value, alwaysWrite, context); + + if (typeof(T) == typeof(object)) + node.Tag = "!type:" + value.GetType().Name; + + return node; } public DataNode WriteValue(ITypeWriter writer, T value, bool alwaysWrite = false, diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/ObjectSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/ObjectSerializer.cs new file mode 100644 index 00000000000..d9d93aca47d --- /dev/null +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/ObjectSerializer.cs @@ -0,0 +1,92 @@ +using System; +using Robust.Shared.Reflection; +using Robust.Shared.IoC; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Markdown; +using Robust.Shared.Serialization.Markdown.Validation; +using Robust.Shared.Serialization.Markdown.Value; +using Robust.Shared.Serialization.TypeSerializers.Interfaces; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; + +[TypeSerializer] +public sealed class ObjectSerializer : ITypeSerializer, ITypeCopier +{ + #region Validate + + public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node, + IDependencyCollection dependencies, ISerializationContext? context = null) + { + var reflection = dependencies.Resolve(); + + if (node.Tag != null) + { + string? typeString = node.Tag[6..]; + + if (!reflection.TryLooseGetType(typeString, out var type)) + { + return new ErrorNode(node, $"Unable to find type for {typeString}"); + } + + return serializationManager.ValidateNode(type, node, context); + } + return new ErrorNode(node, $"Unable to find type for {node}"); + } + + #endregion + + #region Read + public object Read(ISerializationManager serializationManager, ValueDataNode node, + IDependencyCollection dependencies, + SerializationHookContext hookCtx, ISerializationContext? context = null, + ISerializationManager.InstantiationDelegate? instanceProvider = null) + { + var reflection = dependencies.Resolve(); + var value = instanceProvider != null ? instanceProvider() : new object(); + + if (node.Tag != null) + { + string? typeString = node.Tag[6..]; + + if (!reflection.TryLooseGetType(typeString, out var type)) + throw new NullReferenceException($"Found null type for {typeString}"); + + value = serializationManager.Read(type, node, hookCtx, context); + + if (value == null) + throw new NullReferenceException($"Found null data for {node}, expected {type}"); + } + + return value; + } + #endregion + + #region Write + public DataNode Write(ISerializationManager serializationManager, object value, + IDependencyCollection dependencies, bool alwaysWrite = false, + ISerializationContext? context = null) + { + var node = serializationManager.WriteValue(value.GetType(), value); + + if (node == null) + throw new NullReferenceException($"Attempted to write node with type {value.GetType()}, node returned null"); + + return node; + } + #endregion + + #region CopyTo + + public void CopyTo( + ISerializationManager serializationManager, + object source, + ref object target, + IDependencyCollection dependencies, + SerializationHookContext hookCtx, + ISerializationContext? context = null) + { + target = source; + } + #endregion +} From b1e13f5b13f73e50c3630da3f8a65fa6031980bd Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 29 Aug 2024 12:47:32 +1000 Subject: [PATCH 062/140] Fix BUI interfaces not deep copying (#5410) * Fix BUI interfaces not deep copying Didn't think I'd need to on a getstate but client state moment. * less shitcodey --- .../UserInterface/UserInterfaceComponent.cs | 6 ++++-- .../Systems/SharedUserInterfaceSystem.cs | 16 ++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs b/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs index de5a3f3bb1a..5e5efed8f76 100644 --- a/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs +++ b/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs @@ -69,9 +69,11 @@ public sealed partial class InterfaceData [DataField] public bool RequireInputValidation = true; - public InterfaceData(string type) + public InterfaceData(InterfaceData data) { - ClientType = type; + ClientType = data.ClientType; + InteractionRange = data.InteractionRange; + RequireInputValidation = data.RequireInputValidation; } } diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 5b9878705b4..0305cbb37ea 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -305,7 +305,15 @@ private void OnUserInterfaceGetState(Entity ent, ref Com // I.e., don't resend the whole BUI state just because a new user opened it. var actors = new Dictionary>(); - args.State = new UserInterfaceComponent.UserInterfaceComponentState(actors, ent.Comp.States, ent.Comp.Interfaces); + + var dataCopy = new Dictionary(); + + foreach (var (weh, a) in ent.Comp.Interfaces) + { + dataCopy[weh] = new InterfaceData(a); + } + + args.State = new UserInterfaceComponent.UserInterfaceComponentState(actors, ent.Comp.States, dataCopy); // Ensure that only the player that currently has the UI open gets to know what they have it open. if (args.ReplayState) @@ -335,11 +343,7 @@ private void OnUserInterfaceHandleState(Entity ent, ref foreach (var data in state.Data) { - ent.Comp.Interfaces[data.Key] = new(data.Value.ClientType) - { - InteractionRange = data.Value.InteractionRange, - RequireInputValidation = data.Value.RequireInputValidation, - }; + ent.Comp.Interfaces[data.Key] = new(data.Value); } foreach (var key in ent.Comp.Actors.Keys) From f659b2b58c7d0784f3582b93ba368a529558d3fd Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Thu, 29 Aug 2024 12:55:51 +1000 Subject: [PATCH 063/140] Version: 232.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 28 +++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 6fba408d005..179ad095def 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 231.1.1 + 232.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index b1716e45260..5ca1ab3b94e 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,6 +35,29 @@ END TEMPLATE--> ### Breaking changes +*None yet* + +### New features + +*None yet* + +### Bugfixes + +*None yet* + +### Other + +*None yet* + +### Internal + +*None yet* + + +## 232.0.0 + +### Breaking changes + * Obsolete method `AppearanceComponent.TryGetData` is now access-restricted to `SharedAppearanceSystem`; use `SharedAppearanceSystem.TryGetData` instead. ### New features @@ -44,12 +67,11 @@ END TEMPLATE--> ### Bugfixes -* Fix multithreading bug in ParallelTracker that caused the game to crash randomly. +* Fix BUI interfaces not deep-copying in state handling. +* Add Robust.Xaml.csproj to the solution to fix some XAML issues. ### Other -* Added obsoletion warning for `Control.Dispose()`. New code should not rely on it. -* Reduced the default tickrate to 30 ticks. * Serialization will now add type tags (`!type:`) for necessary `NodeData` when writing (currently only for `object` nodes). ### Internal From 2002402af8744918c4adff179b1b1567de46f399 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 29 Aug 2024 12:52:52 +0200 Subject: [PATCH 064/140] Version script now supports dash versions --- Tools/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/version.py b/Tools/version.py index a8e2d0c8f35..b12f18278f5 100755 --- a/Tools/version.py +++ b/Tools/version.py @@ -28,7 +28,7 @@ def main(): def verify_version(version: str): - parts = version.split(".") + parts = version.split("-")[0].split(".") if len(parts) != 3: print("Version must be split into three parts with '.'") sys.exit(1) From be36001ab8c5a91811055d81525ffe728d0d84dc Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 31 Aug 2024 03:04:02 +0200 Subject: [PATCH 065/140] Add thread check assert to core entity mutation commands. (#5411) * Add thread check assert to core entity mutation commands. Creation of entities and components is now checked to happen from the main thread. This is already catching multiple buggy integration tests, these will be addressed in separate commits. * Fix broken tests directly mutating entities from wrong thread. --- .../GameObjects/EntityManager.Components.cs | 4 ++++ Robust.Shared/GameObjects/EntityManager.cs | 23 +++++++++++++++++++ .../Shared/GameState/ComponentStateTests.cs | 12 ++++++++-- .../GameState/NoSharedReferencesTest.cs | 5 +++- 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index 29e370523f0..d584af52500 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -342,6 +342,8 @@ private void AddComponentInternal(EntityUid uid, T component, bool overwrite, private void AddComponentInternal(EntityUid uid, T component, ComponentRegistration reg, bool overwrite, bool skipInit, MetaDataComponent metadata) where T : IComponent { + ThreadCheck(); + // We can't use typeof(T) here in case T is just Component DebugTools.Assert(component is MetaDataComponent || (metadata ?? MetaQuery.GetComponent(uid)).EntityLifeStage < EntityLifeStage.Terminating, @@ -602,6 +604,8 @@ private void RemoveComponentImmediate( bool terminating, MetaDataComponent? meta) { + ThreadCheck(); + if (component.Deleted) { _sawmill.Warning($"Deleting an already deleted component. Entity: {ToPrettyString(uid)}, Component: {_componentFactory.GetComponentName(component.GetType())}."); diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index c854bf9e869..97b14ddd9e2 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; @@ -117,6 +118,10 @@ public abstract partial class EntityManager : IEntityManager public bool Initialized { get; protected set; } +#if DEBUG + private int _mainThreadId; +#endif + /// /// Constructs a new instance of . /// @@ -138,6 +143,10 @@ public virtual void Initialize() _sawmill = LogManager.GetSawmill("entity"); _resolveSawmill = LogManager.GetSawmill("resolve"); +#if DEBUG + _mainThreadId = Environment.CurrentManagedThreadId; +#endif + Initialized = true; } @@ -511,6 +520,8 @@ public void DeleteEntity(EntityUid e, MetaDataComponent meta, TransformComponent if (!Started) return; + ThreadCheck(); + if (meta.EntityLifeStage >= EntityLifeStage.Deleted) return; @@ -752,6 +763,8 @@ private protected EntityUid AllocEntity( /// private EntityUid AllocEntity(out MetaDataComponent metadata) { + ThreadCheck(); + var uid = GenerateEntityUid(); #if DEBUG @@ -953,6 +966,16 @@ internal EntityUid GenerateEntityUid() /// Generates a unique network id and increments /// protected virtual NetEntity GenerateNetEntity() => new(NextNetworkId++); + + [Conditional("DEBUG")] + protected void ThreadCheck() + { +#if DEBUG + DebugTools.Assert( + Environment.CurrentManagedThreadId == _mainThreadId, + "Environment.CurrentManagedThreadId == _mainThreadId"); +#endif + } } public enum EntityMessageType : byte diff --git a/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs b/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs index 2d0d303b29b..0a3f51c352d 100644 --- a/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs +++ b/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs @@ -38,7 +38,11 @@ public async Task UnknownEntityTest() server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, true)); // Set up map. - var map = server.System().CreateMap(); + EntityUid map = default; + server.Post(() => + { + map = server.System().CreateMap(); + }); await RunTicks(); @@ -157,7 +161,11 @@ public async Task UnknownEntityDeleteTest() server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, true)); // Set up map. - var map = server.System().CreateMap(); + EntityUid map = default; + server.Post(() => + { + map = server.System().CreateMap(); + }); await RunTicks(); diff --git a/Robust.UnitTesting/Shared/GameState/NoSharedReferencesTest.cs b/Robust.UnitTesting/Shared/GameState/NoSharedReferencesTest.cs index bc036f1328e..b24218bd6f9 100644 --- a/Robust.UnitTesting/Shared/GameState/NoSharedReferencesTest.cs +++ b/Robust.UnitTesting/Shared/GameState/NoSharedReferencesTest.cs @@ -37,7 +37,10 @@ public async Task ReferencesAreNotShared() client.Post(() => netMan.ClientConnect(null!, 0, null!)); // Set up map. - var map = server.System().CreateMap(); + server.Post(() => + { + server.System().CreateMap(); + }); await RunTicks(); From 26c87b5858638d4f83215ceee979c326f6011927 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 31 Aug 2024 03:04:21 +0200 Subject: [PATCH 066/140] Make tests run parallelizable (#5412) Hope this won't cause issues. Massively improves test speed. --- Robust.UnitTesting/AssemblyInfo.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Robust.UnitTesting/AssemblyInfo.cs b/Robust.UnitTesting/AssemblyInfo.cs index edba04417fb..fc728beff83 100644 --- a/Robust.UnitTesting/AssemblyInfo.cs +++ b/Robust.UnitTesting/AssemblyInfo.cs @@ -1,4 +1,7 @@ using System.Runtime.CompilerServices; +using NUnit.Framework; // So it can use RobustServerSimulation. [assembly: InternalsVisibleTo("Robust.Benchmarks")] + +[assembly: Parallelizable(ParallelScope.Fixtures)] From 656835e7fa4bd40a364b20733e7642caf526b4e6 Mon Sep 17 00:00:00 2001 From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Date: Sat, 31 Aug 2024 01:04:44 +0000 Subject: [PATCH 067/140] Change EntityRenamedEvents arguments and make it broadcast (#5413) --- .../GameObjects/EntitySystemMessages/EntityRenamedEvent.cs | 3 ++- Robust.Shared/GameObjects/Systems/MetaDataSystem.cs | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Robust.Shared/GameObjects/EntitySystemMessages/EntityRenamedEvent.cs b/Robust.Shared/GameObjects/EntitySystemMessages/EntityRenamedEvent.cs index 798359b6b35..cbb29dafc80 100644 --- a/Robust.Shared/GameObjects/EntitySystemMessages/EntityRenamedEvent.cs +++ b/Robust.Shared/GameObjects/EntitySystemMessages/EntityRenamedEvent.cs @@ -2,6 +2,7 @@ namespace Robust.Shared.GameObjects; /// /// Raised directed on an entity when its name is changed. +/// Contains the EntityUid as systems may need to subscribe to it without targeting a specific component. /// [ByRefEvent] -public readonly record struct EntityRenamedEvent(string NewName); +public readonly record struct EntityRenamedEvent(EntityUid Uid, string OldName, string NewName); diff --git a/Robust.Shared/GameObjects/Systems/MetaDataSystem.cs b/Robust.Shared/GameObjects/Systems/MetaDataSystem.cs index a67ba3d2589..2ee1e89e91f 100644 --- a/Robust.Shared/GameObjects/Systems/MetaDataSystem.cs +++ b/Robust.Shared/GameObjects/Systems/MetaDataSystem.cs @@ -47,12 +47,14 @@ public void SetEntityName(EntityUid uid, string value, MetaDataComponent? metada if (!_metaQuery.Resolve(uid, ref metadata) || value.Equals(metadata.EntityName)) return; + var oldName = metadata.EntityName; + metadata._entityName = value; if (raiseEvents) { - var ev = new EntityRenamedEvent(value); - RaiseLocalEvent(uid, ref ev); + var ev = new EntityRenamedEvent(uid, oldName, value); + RaiseLocalEvent(uid, ref ev, true); } Dirty(uid, metadata, metadata); From 73ef69aa94d8e71ac8aa8a23d504f5c48e8723b5 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sat, 31 Aug 2024 13:05:56 +1200 Subject: [PATCH 068/140] Try and ensure that parents are always initialized before children (#5343) * Try and ensure that parents are always initialized before children * release notes --- RELEASE-NOTES.md | 2 +- .../GameStates/ClientGameStateManager.cs | 63 +++++++++++++------ .../GameObjects/ServerEntityManager.cs | 1 + .../GameObjects/EntityManager.Components.cs | 2 + Robust.Shared/GameObjects/EntityManager.cs | 20 ++++++ .../GameObjects/IEntityManager.Components.cs | 2 + 6 files changed, 71 insertions(+), 19 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 5ca1ab3b94e..78a0e645c56 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -51,7 +51,7 @@ END TEMPLATE--> ### Internal -*None yet* +* `ClientGameStateManager` now only initialises or starts entities after their parents have already been initialized. There are also some new debug asserts to try ensure that this rule isn't broken elsewhere. ## 232.0.0 diff --git a/Robust.Client/GameStates/ClientGameStateManager.cs b/Robust.Client/GameStates/ClientGameStateManager.cs index f1d01566878..30240d7abb5 100644 --- a/Robust.Client/GameStates/ClientGameStateManager.cs +++ b/Robust.Client/GameStates/ClientGameStateManager.cs @@ -960,7 +960,7 @@ public IEnumerable ApplyGameState(GameState curState, GameState? next // Initialize and start the newly created entities. if (_toCreate.Count > 0) - InitializeAndStart(_toCreate); + InitializeAndStart(_toCreate, metas, xforms); _prof.WriteValue("State Size", ProfData.Int32(curSpan.Length)); _prof.WriteValue("Entered PVS", ProfData.Int32(enteringPvs)); @@ -1188,7 +1188,10 @@ private void Detach(GameTick maxTick, } } - private void InitializeAndStart(Dictionary toCreate) + private void InitializeAndStart( + Dictionary toCreate, + EntityQuery metas, + EntityQuery xforms) { _toStart.Clear(); @@ -1197,22 +1200,8 @@ private void InitializeAndStart(Dictionary toCreate) EntityUid entity = default; foreach (var netEntity in toCreate.Keys) { - try - { - (entity, var meta) = _entityManager.GetEntityData(netEntity); - _entities.InitializeEntity(entity, meta); - _toStart.Add((entity, netEntity)); - } - catch (Exception e) - { - _sawmill.Error($"Server entity threw in Init: nent={netEntity}, ent={_entities.ToPrettyString(entity)}"); - _runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}"); - _toCreate.Remove(netEntity); - _brokenEnts.Add(entity); -#if !EXCEPTION_TOLERANCE - throw; -#endif - } + (entity, var meta) = _entityManager.GetEntityData(netEntity); + InitializeRecursive(entity, meta, metas, xforms); } } @@ -1244,6 +1233,44 @@ private void InitializeAndStart(Dictionary toCreate) _brokenEnts.Clear(); } + private void InitializeRecursive( + EntityUid entity, + MetaDataComponent meta, + EntityQuery metas, + EntityQuery xforms) + { + var xform = xforms.GetComponent(entity); + if (xform.ParentUid is {Valid: true} parent) + { + var parentMeta = metas.GetComponent(parent); + if (parentMeta.EntityLifeStage < EntityLifeStage.Initialized) + InitializeRecursive(parent, parentMeta, metas, xforms); + } + + if (meta.EntityLifeStage >= EntityLifeStage.Initialized) + { + // Was probably already initialized because one of its children appeared earlier in the list. + DebugTools.AssertEqual(_toStart.Count(x => x.Item1 == entity), 1); + return; + } + + try + { + _entities.InitializeEntity(entity, meta); + _toStart.Add((entity, meta.NetEntity)); + } + catch (Exception e) + { + _sawmill.Error($"Server entity threw in Init: nent={meta.NetEntity}, ent={_entities.ToPrettyString(entity)}"); + _runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}"); + _toCreate.Remove(meta.NetEntity); + _brokenEnts.Add(entity); +#if !EXCEPTION_TOLERANCE + throw; +#endif + } + } + private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState, EntityState? nextState, GameTick lastApplied, GameTick toTick, bool enteringPvs) { diff --git a/Robust.Server/GameObjects/ServerEntityManager.cs b/Robust.Server/GameObjects/ServerEntityManager.cs index 000cbc6a2e9..ead2ee7e148 100644 --- a/Robust.Server/GameObjects/ServerEntityManager.cs +++ b/Robust.Server/GameObjects/ServerEntityManager.cs @@ -81,6 +81,7 @@ void IServerEntityManagerInternal.FinishEntityInitialization(EntityUid entity, M InitializeEntity(entity, meta); } + [Obsolete("Use StartEntity")] void IServerEntityManagerInternal.FinishEntityStartup(EntityUid entity) { StartEntity(entity); diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index d584af52500..2ab8298cd4e 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -110,6 +110,7 @@ public int Count(Type component) return dict.Count; } + [Obsolete("Use InitializeEntity")] public void InitializeComponents(EntityUid uid, MetaDataComponent? metadata = null) { DebugTools.AssertOwner(uid, metadata); @@ -145,6 +146,7 @@ public void InitializeComponents(EntityUid uid, MetaDataComponent? metadata = nu SetLifeStage(metadata, EntityLifeStage.Initialized); } + [Obsolete("Use StartEntity")] public void StartComponents(EntityUid uid) { // Startup() can modify _components diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index 97b14ddd9e2..7ba519b7eae 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -877,15 +877,35 @@ public void InitializeAndStartEntity(Entity entity, bool doM public void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null) { + // Ideally, entities only ever get initialized once their parent has already been initialized. + // Note that this doesn't guarantee that an uninitialized entity will never have initialized children. + // In particular, for the client this might happen when applying a new game state that re-parents an + // existing entity to a newly created entity. The new entity only gets initialiuzed & started at the end, + // after the old/existing entity was already moved to the new parent. + DebugTools.Assert(TransformQuery.GetComponent(entity).ParentUid is not { Valid: true } parent + || MetaQuery.GetComponent(parent).EntityLifeStage >= EntityLifeStage.Initialized); + DebugTools.AssertOwner(entity, meta); meta ??= GetComponent(entity); +#pragma warning disable CS0618 // Type or member is obsolete InitializeComponents(entity, meta); +#pragma warning restore CS0618 // Type or member is obsolete EntityInitialized?.Invoke((entity, meta)); } public void StartEntity(EntityUid entity) { + // Ideally, entities only ever get initialized once their parent has already been initialized. + // Note that this doesn't guarantee that an uninitialized entity will never have initialized children. + // In particular, for the client this might happen when applying a new game state that re-parents an + // existing entity to a newly created entity. The new entity only gets initialiuzed & started at the end, + // after the old/existing entity was already moved to the new parent. + DebugTools.Assert(TransformQuery.GetComponent(entity).ParentUid is not { Valid: true } parent + || MetaQuery.GetComponent(parent).EntityLifeStage >= EntityLifeStage.Initialized); + +#pragma warning disable CS0618 // Type or member is obsolete StartComponents(entity); +#pragma warning restore CS0618 // Type or member is obsolete } public void RunMapInit(EntityUid entity, MetaDataComponent meta) diff --git a/Robust.Shared/GameObjects/IEntityManager.Components.cs b/Robust.Shared/GameObjects/IEntityManager.Components.cs index c371a3a055d..0a749eb4010 100644 --- a/Robust.Shared/GameObjects/IEntityManager.Components.cs +++ b/Robust.Shared/GameObjects/IEntityManager.Components.cs @@ -22,11 +22,13 @@ public partial interface IEntityManager /// /// Calls Initialize() on all registered components of the entity. /// + [Obsolete("Use InitializeEntity")] void InitializeComponents(EntityUid uid, MetaDataComponent? meta = null); /// /// Calls Startup() on all registered components of the entity. /// + [Obsolete("Use StartEntity")] void StartComponents(EntityUid uid); /// From ab6bd198171c6bb8cffc8ac80bf0278bd9ff32a8 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 31 Aug 2024 11:45:59 +1000 Subject: [PATCH 069/140] Fix mouse hover not updating for new controls (#5313) * Fix mouse hover not updating for new controls It only ever updated on movement previously. The issue is for example if a new window shows up where the mouse is (or any control) it doesn't handle it. Just checking it every frame AFAIK shouldn't be that expensive. Worst case we just have some flag to check it that gets set on . * review --- Robust.Client/UserInterface/UserInterfaceManager.Input.cs | 4 ++-- Robust.Client/UserInterface/UserInterfaceManager.cs | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Robust.Client/UserInterface/UserInterfaceManager.Input.cs b/Robust.Client/UserInterface/UserInterfaceManager.Input.cs index a82b4c2a133..2b647131fc0 100644 --- a/Robust.Client/UserInterface/UserInterfaceManager.Input.cs +++ b/Robust.Client/UserInterface/UserInterfaceManager.Input.cs @@ -148,7 +148,7 @@ public void MouseMove(MouseMoveEventArgs mouseMoveEventArgs) var newHovered = MouseGetControl(mouseMoveEventArgs.Position); SetHovered(newHovered); - var target = ControlFocused ?? newHovered; + var target = ControlFocused ?? CurrentlyHovered; if (target != null) { var pos = mouseMoveEventArgs.Position.Position; @@ -164,7 +164,7 @@ public void MouseMove(MouseMoveEventArgs mouseMoveEventArgs) public void UpdateHovered() { - var ctrl = MouseGetControl(_inputManager.MouseScreenPosition); + var ctrl = MouseGetControl(_inputManager.MouseScreenPosition); SetHovered(ctrl); } diff --git a/Robust.Client/UserInterface/UserInterfaceManager.cs b/Robust.Client/UserInterface/UserInterfaceManager.cs index fd4bc38e640..af7c1eb6afe 100644 --- a/Robust.Client/UserInterface/UserInterfaceManager.cs +++ b/Robust.Client/UserInterface/UserInterfaceManager.cs @@ -215,6 +215,9 @@ public void FrameUpdate(FrameEventArgs args) { using (_prof.Group("Update")) { + // Update hovered. Can't rely upon mouse movement due to New controls potentially coming up. + UpdateHovered(); + foreach (var root in _roots) { CheckRootUIScaleUpdate(root); From 3bb7df32541b6dc339213f4eb6606738b6d95794 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 31 Aug 2024 14:35:17 +1000 Subject: [PATCH 070/140] Relative lookup fix (#5415) * Relative lookup fix Some of the transforms weren't being transformed, added another test. * test * better test * Reduce any-entities-intersecting tests --- .../Systems/EntityLookup.Queries.cs | 152 ++---------------- .../EntityLookupSystem.ComponentQueries.cs | 12 +- .../Shared/EntityLookup_Test.cs | 32 ++++ 3 files changed, 49 insertions(+), 147 deletions(-) diff --git a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs index e9051c87c82..f332a94c54c 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs @@ -252,7 +252,7 @@ private bool AnyEntitiesIntersecting(MapId mapId, var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid); var localAabb = state.Shape.ComputeAABB(localTransform, 0); - if (state.Lookup.AnyEntitiesIntersecting(uid, state.Shape, localAabb, state.Transform, state.Flags, ignored: state.Ignored)) + if (state.Lookup.AnyEntitiesIntersecting(uid, state.Shape, localAabb, localTransform, state.Flags, ignored: state.Ignored)) { state.Found = true; return false; @@ -266,7 +266,7 @@ private bool AnyEntitiesIntersecting(MapId mapId, var mapUid = _map.GetMapOrInvalid(mapId); var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid); var localAabb = state.Shape.ComputeAABB(localTransform, 0); - state.Found = AnyEntitiesIntersecting(mapUid, shape, localAabb, shapeTransform, flags, ignored); + state.Found = AnyEntitiesIntersecting(mapUid, shape, localAabb, localTransform, flags, ignored); } return state.Found; @@ -454,26 +454,8 @@ public bool AnyEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags fla { if (mapId == MapId.Nullspace) return false; - // Don't need to check contained entities as they have the same bounds as the parent. - var found = false; - - var state = (this, worldAABB, flags, found); - - _mapManager.FindGridsIntersecting(mapId, worldAABB, ref state, - static (EntityUid uid, MapGridComponent _, ref (EntityLookupSystem lookup, Box2 worldAABB, LookupFlags flags, bool found) tuple) => - { - if (!tuple.lookup.AnyLocalEntitiesIntersecting(uid, tuple.worldAABB, tuple.flags)) - return true; - - tuple.found = true; - return false; - }, approx: true, includeMap: false); - - if (state.found) - return true; - - var mapUid = _map.GetMapOrInvalid(mapId); - return AnyLocalEntitiesIntersecting(mapUid, worldAABB, flags); + var shape = new Polygon(worldAABB); + return AnyEntitiesIntersecting(mapId, shape, Physics.Transform.Empty, flags); } public HashSet GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags) @@ -487,23 +469,8 @@ public void GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, HashSet intersecting, - Box2 worldAABB, SharedTransformSystem xformSystem, LookupFlags flags) tuple) => - { - var localAABB = tuple.xformSystem.GetInvWorldMatrix(gridUid).TransformBox(tuple.worldAABB); - tuple.lookup.AddLocalEntitiesIntersecting(gridUid, tuple.intersecting, localAABB, tuple.flags); - return true; - }, approx: true, includeMap: false); - - // Get map entities - var mapUid = _map.GetMapOrInvalid(mapId); - AddLocalEntitiesIntersecting(mapUid, intersecting, worldAABB, flags); - AddContained(intersecting, flags); + var shape = new Polygon(worldAABB); + AddEntitiesIntersecting(mapId, intersecting, shape, Physics.Transform.Empty, flags); } #endregion @@ -513,73 +480,15 @@ public void GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, HashSet - { - if (tuple.lookup.AnyEntitiesIntersecting(uid, tuple.worldBounds, tuple.flags)) - { - tuple.found = true; - return false; - } - return true; - }, approx: true, includeMap: false); - - if (state.found) - return true; - - var mapUid = _map.GetMapOrInvalid(mapId); - return AnyEntitiesIntersecting(mapUid, worldBounds, flags); + var shape = new Polygon(worldBounds); + return AnyEntitiesIntersecting(mapId, shape, Physics.Transform.Empty, flags); } public HashSet GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags) { var intersecting = new HashSet(); - - if (mapId == MapId.Nullspace) - return intersecting; - - var mapUid = _map.GetMapOrInvalid(mapId); - - // Get grid entities var shape = new Polygon(worldBounds); - var transform = Physics.Transform.Empty; - - var state = (this, _physics, intersecting, transform, shape, flags); - - _mapManager.FindGridsIntersecting(mapUid, shape, transform, ref state, - static ( - EntityUid uid, - MapGridComponent grid, - ref (EntityLookupSystem lookup, - SharedPhysicsSystem _physics, - HashSet intersecting, - Transform transform, - Polygon shape, LookupFlags flags) state) => - { - var localTransform = state._physics.GetRelativePhysicsTransform(state.transform, uid); - var localAabb = state.shape.ComputeAABB(localTransform, 0); - - state.lookup.AddEntitiesIntersecting(uid, - state.intersecting, - state.shape, - localAabb, - state.transform, - state.flags); - return true; - }); - - // Get map entities - var localTransform = _physics.GetRelativePhysicsTransform(transform, mapUid); - var localAabb = shape.ComputeAABB(localTransform, 0); - - AddEntitiesIntersecting(mapUid, intersecting, shape, localAabb, transform, flags); - AddContained(intersecting, flags); - + AddEntitiesIntersecting(mapId, intersecting, shape, Physics.Transform.Empty, flags); return intersecting; } @@ -599,47 +508,8 @@ public bool AnyEntitiesInRange(EntityUid uid, float range, LookupFlags flags = D if (mapPos.MapId == MapId.Nullspace) return false; - var rangeVec = new Vector2(range, range); - var worldAABB = new Box2(mapPos.Position - rangeVec, mapPos.Position + rangeVec); - var circle = new PhysShapeCircle(range, mapPos.Position); - - const bool found = false; - var transform = Physics.Transform.Empty; - var state = (this, _physics, transform, circle, flags, found, uid); - - _mapManager.FindGridsIntersecting(mapPos.MapId, worldAABB, ref state, static ( - EntityUid gridUid, - MapGridComponent _, ref ( - EntityLookupSystem lookup, - SharedPhysicsSystem physics, - Transform worldTransform, - PhysShapeCircle circle, - LookupFlags flags, - bool found, - EntityUid ignored) tuple) => - { - var localTransform = tuple.physics.GetRelativePhysicsTransform(tuple.worldTransform, gridUid); - var localAabb = tuple.circle.ComputeAABB(localTransform, 0); - - if (tuple.lookup.AnyEntitiesIntersecting(gridUid, tuple.circle, localAabb, localTransform, tuple.flags, tuple.ignored)) - { - tuple.found = true; - return false; - } - - return true; - }, approx: true, includeMap: false); - - if (state.found) - { - return true; - } - - var mapUid = _map.GetMapOrInvalid(mapPos.MapId); - var localTransform = _physics.GetRelativePhysicsTransform(transform, uid); - var localAabb = circle.ComputeAABB(localTransform, 0); - - return AnyEntitiesIntersecting(mapUid, circle, localAabb, localTransform, flags, uid); + var shape = new PhysShapeCircle(range, mapPos.Position); + return AnyEntitiesIntersecting(mapPos.MapId, shape, Physics.Transform.Empty, flags, uid); } public HashSet GetEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags) diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs index 52e6134ff14..6b001b024e3 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs @@ -542,7 +542,7 @@ public void GetEntitiesIntersecting(Type type, MapId mapId, IPhysShape shape, Tr { var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid); var localAabb = state.Shape.ComputeAABB(localTransform, 0); - state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, state.Transform, state.Flags, state.Query); + state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, localTransform, state.Flags, state.Query); return true; }, approx: true, includeMap: false); @@ -550,7 +550,7 @@ public void GetEntitiesIntersecting(Type type, MapId mapId, IPhysShape shape, Tr var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid); var localAabb = state.Shape.ComputeAABB(localTransform, 0); - AddEntitiesIntersecting(mapUid, intersecting, shape, localAabb, shapeTransform, flags, query); + AddEntitiesIntersecting(mapUid, intersecting, shape, localAabb, localTransform, flags, query); AddContained(intersecting, flags, query); } @@ -586,7 +586,7 @@ public void GetEntitiesIntersecting(MapId mapId, IPhysShape shape, Transform { var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid); var localAabb = state.Shape.ComputeAABB(localTransform, 0); - state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, state.Transform, state.Flags, state.Query); + state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, localTransform, state.Flags, state.Query); return true; }, approx: true, includeMap: false); @@ -595,7 +595,7 @@ public void GetEntitiesIntersecting(MapId mapId, IPhysShape shape, Transform var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid); var localAabb = state.Shape.ComputeAABB(localTransform, 0); - AddEntitiesIntersecting(mapUid, entities, shape, localAabb, shapeTransform, flags, query); + AddEntitiesIntersecting(mapUid, entities, shape, localAabb, localTransform, flags, query); AddContained(entities, flags, query); } } @@ -678,8 +678,8 @@ public HashSet GetComponentsInRange(MapId mapId, Vector2 worldPos, float r public void GetEntitiesInRange(MapId mapId, Vector2 worldPos, float range, HashSet> entities, LookupFlags flags = DefaultFlags) where T : IComponent { - var shape = new PhysShapeCircle(range); - var transform = new Transform(worldPos, 0f); + var shape = new PhysShapeCircle(range, worldPos); + var transform = Physics.Transform.Empty; GetEntitiesInRange(mapId, shape, transform, entities, flags); } diff --git a/Robust.UnitTesting/Shared/EntityLookup_Test.cs b/Robust.UnitTesting/Shared/EntityLookup_Test.cs index b7ce0778250..24bae21a661 100644 --- a/Robust.UnitTesting/Shared/EntityLookup_Test.cs +++ b/Robust.UnitTesting/Shared/EntityLookup_Test.cs @@ -18,6 +18,12 @@ public sealed class EntityLookupTest { private static readonly MapId MapId = new MapId(1); + private static readonly TestCaseData[] IntersectingCases = new[] + { + // Big offset + new TestCaseData(true, new MapCoordinates(new Vector2(10.5f, 10.5f), MapId), new MapCoordinates(new Vector2(10.5f, 10.5f), MapId), 0.25f, true), + }; + private static readonly TestCaseData[] InRangeCases = new[] { new TestCaseData(true, new MapCoordinates(Vector2.One, MapId), new MapCoordinates(Vector2.Zero, MapId), 0.5f, false), @@ -207,6 +213,32 @@ public void TestMapInRange(bool physics, MapCoordinates spawnPos, MapCoordinates mapManager.DeleteMap(spawnPos.MapId); } + [Test, TestCaseSource(nameof(IntersectingCases))] + public void TestGridIntersecting(bool physics, MapCoordinates spawnPos, MapCoordinates queryPos, float range, bool result) + { + var sim = RobustServerSimulation.NewSimulation(); + var server = sim.InitializeInstance(); + + var lookup = server.Resolve().GetEntitySystem(); + var entManager = server.Resolve(); + var mapManager = server.Resolve(); + var mapSystem = entManager.System(); + + mapSystem.CreateMap(spawnPos.MapId); + var grid = SetupGrid(spawnPos.MapId, mapSystem, entManager, mapManager); + + if (physics) + GetPhysicsEntity(entManager, spawnPos); + else + entManager.Spawn(null, spawnPos); + + _ = entManager.SpawnEntity(null, spawnPos); + var bounds = new Box2Rotated(Box2.CenteredAround(queryPos.Position, new Vector2(range, range))); + + Assert.That(lookup.GetEntitiesIntersecting(queryPos.MapId, bounds).Count > 0, Is.EqualTo(result)); + mapManager.DeleteMap(spawnPos.MapId); + } + [Test, TestCaseSource(nameof(InRangeCases))] public void TestGridInRange(bool physics, MapCoordinates spawnPos, MapCoordinates queryPos, float range, bool result) { From c558a0327bc8179e2618a4587e99c73cb3ae094c Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sat, 31 Aug 2024 14:37:23 +1000 Subject: [PATCH 071/140] Version: 233.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 179ad095def..9d7d85477d9 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 232.0.0 + 233.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 78a0e645c56..23c80e797f0 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -51,7 +51,26 @@ END TEMPLATE--> ### Internal +*None yet* + + +## 233.0.0 + +### Breaking changes + +* Made EntityRenamed a broadcast event & added additional args. +* Made test runs parallelizable. +* Added a debug assert that other threads aren't touching entities. + +### Bugfixes + +* Fix some entitylookup method transformations and add more tests. +* Fix mousehover not updating if new controls showed up under the mouse. + +### Internal + * `ClientGameStateManager` now only initialises or starts entities after their parents have already been initialized. There are also some new debug asserts to try ensure that this rule isn't broken elsewhere. +* Engine version script now supports dashes. ## 232.0.0 From 69ed2c3c33add0b54033ac9ef4dba2a8894c8636 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 31 Aug 2024 17:49:59 +1000 Subject: [PATCH 072/140] Fix IsHardCollidable (#5416) --- .../Systems/SharedPhysicsSystem.Fixtures.cs | 4 ++-- .../Shared/Physics/Collision_Test.cs | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Fixtures.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Fixtures.cs index f5199867d13..907a03a4775 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Fixtures.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Fixtures.cs @@ -81,7 +81,7 @@ public bool IsCurrentlyHardCollidable(Entity bodyA if (!_fixturesQuery.Resolve(bodyA, ref bodyA.Comp1, false) || !_fixturesQuery.Resolve(bodyB, ref bodyB.Comp1, false) || !PhysicsQuery.Resolve(bodyA, ref bodyA.Comp2, false) || - !PhysicsQuery.Resolve(bodyA, ref bodyB.Comp2, false)) + !PhysicsQuery.Resolve(bodyB, ref bodyB.Comp2, false)) { return false; } diff --git a/Robust.UnitTesting/Shared/Physics/Collision_Test.cs b/Robust.UnitTesting/Shared/Physics/Collision_Test.cs index 7a5dc3c08ed..ebee07a62bc 100644 --- a/Robust.UnitTesting/Shared/Physics/Collision_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/Collision_Test.cs @@ -38,6 +38,30 @@ namespace Robust.UnitTesting.Shared.Physics; [TestFixture] public sealed class Collision_Test { + [Test] + public void TestHardCollidable() + { + var sim = RobustServerSimulation.NewSimulation().InitializeInstance(); + var entManager = sim.Resolve(); + + var fixtures = entManager.System(); + var physics = entManager.System(); + + var map = sim.CreateMap(); + + var bodyAUid = entManager.SpawnAttachedTo(null, new EntityCoordinates(map.Uid, Vector2.Zero)); + var bodyBUid = entManager.SpawnAttachedTo(null, new EntityCoordinates(map.Uid, Vector2.Zero)); + var bodyA = entManager.AddComponent(bodyAUid); + var bodyB = entManager.AddComponent(bodyBUid); + + Assert.That(!physics.IsHardCollidable(bodyAUid, bodyBUid)); + + fixtures.CreateFixture(bodyAUid, "fix1", new Fixture(new PhysShapeCircle(0.5f), 1, 1, true)); + fixtures.CreateFixture(bodyBUid, "fix1", new Fixture(new PhysShapeCircle(0.5f), 1, 1, true)); + + Assert.That(physics.IsHardCollidable(bodyAUid, bodyBUid)); + } + [Test] public void TestCollision() { From da56851846d3fb33f7a77233fc3d505affd81ac1 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sat, 31 Aug 2024 18:22:39 +1000 Subject: [PATCH 073/140] Version: 233.0.1 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 9d7d85477d9..d0033c40cfc 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 233.0.0 + 233.0.1 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 23c80e797f0..aebe22d7bb5 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,13 @@ END TEMPLATE--> *None yet* +## 233.0.1 + +### Bugfixes + +* Fix IsHardCollidable component to EntityUid references. + + ## 233.0.0 ### Breaking changes From cfae6e1f95eb1d289d6763db23b902b02b12106d Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 31 Aug 2024 18:37:43 +1000 Subject: [PATCH 074/140] Don't rely on client for grid fixture rebuilds (#5348) * Don't rely on client for grid fixture rebuilds Server is already networking fixture data and this has a chance to go bad. Easier to just stop this entirely and remove the fixture references to just network the relevant ones for each chunk. Performance impact should pretty much be non-existent and it should be less buggy. * a * weh notes * fix aabb update * Fix AABB gen * weh * More networking --- RELEASE-NOTES.md | 2 +- .../GridChunkBoundsDebugSystem.cs | 5 +- .../Systems/SharedGridFixtureSystem.cs | 11 +- .../Systems/SharedMapSystem.Grid.cs | 98 +++++++------ .../GameObjects/Systems/SharedMapSystem.cs | 3 + Robust.Shared/GameStates/GameStateMapData.cs | 33 +++-- .../Map/Components/GridTreeComponent.cs | 2 +- .../Map/Components/MapGridComponent.cs | 19 ++- Robust.Shared/Map/MapChunk.cs | 7 +- Robust.Shared/Map/MapManager.Queries.cs | 14 +- .../Physics/Systems/SharedBroadphaseSystem.cs | 39 +++-- .../Shared/Map/GridFixtures_Tests.cs | 133 +++++++++++------- 12 files changed, 228 insertions(+), 138 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index aebe22d7bb5..6c0927ed773 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* Fix exceptions in client game state handling for grids. ### Other diff --git a/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs b/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs index 0472ec6f283..15d469ef76f 100644 --- a/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs @@ -82,6 +82,7 @@ protected internal override void Draw(in OverlayDrawArgs args) var viewport = args.WorldBounds; var worldHandle = args.WorldHandle; + var fixturesQuery = _entityManager.GetEntityQuery(); _grids.Clear(); _mapManager.FindGridsIntersecting(currentMap, viewport, ref _grids); foreach (var grid in _grids) @@ -89,13 +90,15 @@ protected internal override void Draw(in OverlayDrawArgs args) var worldMatrix = _transformSystem.GetWorldMatrix(grid); worldHandle.SetTransform(worldMatrix); var transform = new Transform(Vector2.Zero, Angle.Zero); + var fixtures = fixturesQuery.Comp(grid.Owner); var chunkEnumerator = _mapSystem.GetMapChunks(grid.Owner, grid.Comp, viewport); while (chunkEnumerator.MoveNext(out var chunk)) { - foreach (var fixture in chunk.Fixtures.Values) + foreach (var id in chunk.Fixtures) { + var fixture = fixtures.Fixtures[id]; var poly = (PolygonShape) fixture.Shape; var verts = new Vector2[poly.VertexCount]; diff --git a/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs b/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs index a60f78c84c1..0de5870a86c 100644 --- a/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs @@ -94,9 +94,9 @@ internal void RegenerateCollision( { UpdateFixture(uid, chunk, rectangles, body, manager, xform); - foreach (var (id, fixture) in chunk.Fixtures) + foreach (var id in chunk.Fixtures) { - fixtures[id] = fixture; + fixtures[id] = manager.Fixtures[id]; } } @@ -157,8 +157,9 @@ private bool UpdateFixture(EntityUid uid, MapChunk chunk, List rectangles // Check if we even need to issue an eventbus event var updated = false; - foreach (var (oldId, oldFixture) in chunk.Fixtures) + foreach (var oldId in chunk.Fixtures) { + var oldFixture = manager.Fixtures[oldId]; var existing = false; // Handle deleted / updated fixtures @@ -196,16 +197,16 @@ private bool UpdateFixture(EntityUid uid, MapChunk chunk, List rectangles // Anything remaining is a new fixture (or at least, may have not serialized onto the chunk yet). foreach (var (id, fixture) in newFixtures.Span) { + chunk.Fixtures.Add(id); var existingFixture = _fixtures.GetFixtureOrNull(uid, id, manager: manager); // Check if it's the same (otherwise remove anyway). if (existingFixture?.Shape is PolygonShape poly && poly.EqualsApprox((PolygonShape) fixture.Shape)) { - chunk.Fixtures.Add(id, existingFixture); + continue; } - chunk.Fixtures.Add(id, fixture); _fixtures.CreateFixture(uid, id, fixture, false, manager, body, xform); } diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs index 929b904ace3..5b0294eff2c 100644 --- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs +++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs @@ -12,6 +12,7 @@ using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Dynamics; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -245,6 +246,7 @@ protected virtual void UpdatePvsChunks(Entity modifiedChunks; + switch (args.Current) { case MapGridComponentDeltaState delta: @@ -257,9 +259,9 @@ private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref Co if (delta.ChunkData == null) return; - foreach (var chunkData in delta.ChunkData) + foreach (var (index, chunkData) in delta.ChunkData) { - ApplyChunkData(uid, component, chunkData, modifiedChunks); + ApplyChunkData(uid, component, index, chunkData, modifiedChunks); } component.LastTileModifiedTick = delta.LastTileModifiedTick; @@ -277,12 +279,12 @@ private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref Co foreach (var index in component.Chunks.Keys) { if (!state.FullGridData.ContainsKey(index)) - ApplyChunkData(uid, component, ChunkDatum.CreateDeleted(index), modifiedChunks); + ApplyChunkData(uid, component, index, ChunkDatum.Empty, modifiedChunks); } - foreach (var (index, tiles) in state.FullGridData) + foreach (var (index, data) in state.FullGridData) { - ApplyChunkData(uid, component, ChunkDatum.CreateModified(index, tiles), modifiedChunks); + ApplyChunkData(uid, component, index, new(data), modifiedChunks); } break; @@ -291,12 +293,8 @@ private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref Co return; } - var count = component.Chunks.Count; - RegenerateCollision(uid, component, modifiedChunks); - - // Regeneration can remove chunks in general, but it shouldn't do that here as the state handling - // should already have removed all the chunks. - DebugTools.AssertEqual(component.Chunks.Count, count); + RegenerateAabb(component); + OnGridBoundsChange(uid, component); #if DEBUG foreach (var chunk in component.Chunks.Values) @@ -307,7 +305,11 @@ private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref Co #endif } - private void ApplyChunkData(EntityUid uid, MapGridComponent component, ChunkDatum data, + private void ApplyChunkData( + EntityUid uid, + MapGridComponent component, + Vector2i index, + ChunkDatum data, HashSet modifiedChunks) { bool shapeChanged = false; @@ -315,7 +317,7 @@ private void ApplyChunkData(EntityUid uid, MapGridComponent component, ChunkDatu if (data.IsDeleted()) { - if (!component.Chunks.TryGetValue(data.Index, out var deletedChunk)) + if (!component.Chunks.TryGetValue(index, out var deletedChunk)) return; // Deleted chunks still need to raise tile-changed events. @@ -329,18 +331,18 @@ private void ApplyChunkData(EntityUid uid, MapGridComponent component, ChunkDatu var gridIndices = deletedChunk.ChunkTileToGridTile((x, y)); var newTileRef = new TileRef(uid, gridIndices, Tile.Empty); - _mapInternal.RaiseOnTileChanged(newTileRef, oldTile, data.Index); + _mapInternal.RaiseOnTileChanged(newTileRef, oldTile, index); } } - component.Chunks.Remove(data.Index); + component.Chunks.Remove(index); // TODO is this required? modifiedChunks.Add(deletedChunk); return; } - var chunk = GetOrAddChunk(uid, component, data.Index); + var chunk = GetOrAddChunk(uid, component, index); chunk.SuppressCollisionRegeneration = true; DebugTools.Assert(data.TileData.Any(x => !x.IsEmpty)); DebugTools.Assert(data.TileData.Length == component.ChunkSize * component.ChunkSize); @@ -355,13 +357,24 @@ private void ApplyChunkData(EntityUid uid, MapGridComponent component, ChunkDatu shapeChanged |= tileShapeChanged; var gridIndices = chunk.ChunkTileToGridTile((x, y)); var newTileRef = new TileRef(uid, gridIndices, tile); - _mapInternal.RaiseOnTileChanged(newTileRef, oldTile, data.Index); + _mapInternal.RaiseOnTileChanged(newTileRef, oldTile, index); } } + if (data.Fixtures != null && !chunk.Fixtures.SetEquals(data.Fixtures)) + { + chunk.Fixtures.Clear(); + + if (data.Fixtures != null) + chunk.Fixtures.UnionWith(data.Fixtures); + } + + chunk.CachedBounds = data.CachedBounds!.Value; chunk.SuppressCollisionRegeneration = false; if (shapeChanged) + { modifiedChunks.Add(chunk); + } } private void OnGridGetState(EntityUid uid, MapGridComponent component, ref ComponentGetState args) @@ -372,7 +385,7 @@ private void OnGridGetState(EntityUid uid, MapGridComponent component, ref Compo return; } - List? chunkData; + Dictionary? chunkData; var fromTick = args.FromTick; if (component.LastTileModifiedTick < fromTick) @@ -381,7 +394,7 @@ private void OnGridGetState(EntityUid uid, MapGridComponent component, ref Compo } else { - chunkData = new List(); + chunkData = new Dictionary(); foreach (var (tick, indices) in component.ChunkDeletionHistory) { @@ -391,7 +404,7 @@ private void OnGridGetState(EntityUid uid, MapGridComponent component, ref Compo // Chunk may have been re-added sometime after it was deleted, but before deletion history was culled. if (!component.Chunks.TryGetValue(indices, out var chunk)) { - chunkData.Add(ChunkDatum.CreateDeleted(indices)); + chunkData.Add(indices, ChunkDatum.Empty); continue; } @@ -416,7 +429,7 @@ private void OnGridGetState(EntityUid uid, MapGridComponent component, ref Compo tileBuffer[x * component.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y); } } - chunkData.Add(ChunkDatum.CreateModified(index, tileBuffer)); + chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, chunk.Fixtures, chunk.CachedBounds)); } } @@ -427,12 +440,12 @@ private void OnGridGetState(EntityUid uid, MapGridComponent component, ref Compo return; HashSet keys = new(); - foreach (var chunk in chunkData) + foreach (var (index, chunk) in chunkData) { - if (chunk.TileData == null) + if (chunk.IsDeleted()) continue; - DebugTools.Assert(keys.Add(chunk.Index), "Duplicate chunk"); + DebugTools.Assert(keys.Add(index), "Duplicate chunk"); DebugTools.Assert(chunk.TileData.Any(x => !x.IsEmpty), "Empty non-deleted chunk"); } #endif @@ -440,7 +453,7 @@ private void OnGridGetState(EntityUid uid, MapGridComponent component, ref Compo private void GetFullState(EntityUid uid, MapGridComponent component, ref ComponentGetState args) { - var chunkData = new Dictionary(); + var chunkData = new Dictionary(); foreach (var (index, chunk) in GetMapChunks(uid, component)) { @@ -453,7 +466,7 @@ private void GetFullState(EntityUid uid, MapGridComponent component, ref Compone tileBuffer[x * component.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y); } } - chunkData.Add(index, tileBuffer); + chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, chunk.Fixtures, chunk.CachedBounds)); } args.State = new MapGridComponentState(component.ChunkSize, chunkData, component.LastTileModifiedTick); @@ -461,7 +474,7 @@ private void GetFullState(EntityUid uid, MapGridComponent component, ref Compone #if DEBUG foreach (var chunk in chunkData.Values) { - DebugTools.Assert(chunk.Any(x => !x.IsEmpty)); + DebugTools.Assert(chunk.TileData!.Any(x => !x.IsEmpty)); } #endif } @@ -495,7 +508,7 @@ private void OnGridInit(EntityUid uid, MapGridComponent component, ComponentInit if (TryComp(xform.MapUid, out var gridTree)) { - var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, component)); + var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, _fixturesQuery.Comp(uid), component)); DebugTools.Assert(component.MapProxy == DynamicTree.Proxy.Free); component.MapProxy = proxy; } @@ -549,7 +562,7 @@ private void AddGrid(EntityUid uid, MapGridComponent grid) if (TryComp(xform.MapUid, out var gridTree)) { - var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, grid)); + var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, _fixturesQuery.Comp(uid), grid)); DebugTools.Assert(grid.MapProxy == DynamicTree.Proxy.Free); grid.MapProxy = proxy; } @@ -629,9 +642,9 @@ internal void RegenerateCollision(EntityUid uid, MapGridComponent grid, IReadOnl PhysicsComponent? body = null; TransformComponent? xform = null; - foreach (var (id, fixture) in mapChunk.Fixtures) + foreach (var id in mapChunk.Fixtures) { - _fixtures.DestroyFixture(uid, id, fixture, false, manager: manager, body: body, xform: xform); + _fixtures.DestroyFixture(uid, id, false, manager: manager, body: body, xform: xform); } RemoveChunk(uid, grid, mapChunk.Indices); @@ -639,6 +652,20 @@ internal void RegenerateCollision(EntityUid uid, MapGridComponent grid, IReadOnl } } + RegenerateAabb(grid); + + // May have been deleted from the bulk update above! + if (Deleted(uid)) + return; + + _physics.WakeBody(uid); + OnGridBoundsChange(uid, grid); + var ev = new RegenerateGridBoundsEvent(uid, chunkRectangles, removedChunks); + RaiseLocalEvent(ref ev); + } + + private void RegenerateAabb(MapGridComponent grid) + { grid.LocalAABB = new Box2(); foreach (var chunk in grid.Chunks.Values) @@ -659,15 +686,6 @@ internal void RegenerateCollision(EntityUid uid, MapGridComponent grid, IReadOnl grid.LocalAABB = grid.LocalAABB.Union(gridBounds); } } - - // May have been deleted from the bulk update above! - if (Deleted(uid)) - return; - - _physics.WakeBody(uid); - OnGridBoundsChange(uid, grid); - var ev = new RegenerateGridBoundsEvent(uid, chunkRectangles, removedChunks); - RaiseLocalEvent(ref ev); } /// diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs index b99177cc18f..f87a98e33cb 100644 --- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs @@ -5,6 +5,7 @@ using Robust.Shared.Map.Components; using Robust.Shared.Maths; using Robust.Shared.Network; +using Robust.Shared.Physics; using Robust.Shared.Physics.Systems; using Robust.Shared.Timing; @@ -23,6 +24,7 @@ public abstract partial class SharedMapSystem : EntitySystem [Dependency] private readonly IComponentFactory _factory = default!; [Dependency] private readonly MetaDataSystem _meta = default!; + private EntityQuery _fixturesQuery; private EntityQuery _mapQuery; private EntityQuery _gridQuery; private EntityQuery _metaQuery; @@ -34,6 +36,7 @@ public override void Initialize() { base.Initialize(); + _fixturesQuery = GetEntityQuery(); _mapQuery = GetEntityQuery(); _gridQuery = GetEntityQuery(); _metaQuery = GetEntityQuery(); diff --git a/Robust.Shared/GameStates/GameStateMapData.cs b/Robust.Shared/GameStates/GameStateMapData.cs index 3fb09a608d7..2daf838c77a 100644 --- a/Robust.Shared/GameStates/GameStateMapData.cs +++ b/Robust.Shared/GameStates/GameStateMapData.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -9,33 +10,49 @@ namespace Robust.Shared.GameStates [Serializable, NetSerializable] public readonly struct ChunkDatum { - public readonly Vector2i Index; + public static readonly ChunkDatum Empty = new ChunkDatum(); + + public readonly HashSet? Fixtures; // Definitely wasteful to send EVERY tile. // Optimize away future coder. // Also it's stored row-major. public readonly Tile[]? TileData; + public readonly Box2i? CachedBounds; + [MemberNotNullWhen(false, nameof(TileData))] public bool IsDeleted() { return TileData == null; } - private ChunkDatum(Vector2i index, Tile[] tileData) + internal ChunkDatum(ChunkDatum data) { - Index = index; - TileData = tileData; + if (data.TileData != null) + { + TileData = new Tile[data.TileData.Length]; + data.TileData.CopyTo(TileData, 0); + } + + if (data.Fixtures != null) + { + Fixtures = new HashSet(data.Fixtures); + } + + CachedBounds = data.CachedBounds; } - public static ChunkDatum CreateModified(Vector2i index, Tile[] tileData) + private ChunkDatum(Tile[] tileData, HashSet fixtures, Box2i cachedBounds) { - return new ChunkDatum(index, tileData); + TileData = tileData; + Fixtures = fixtures; + CachedBounds = cachedBounds; } - public static ChunkDatum CreateDeleted(Vector2i index) + public static ChunkDatum CreateModified(Tile[] tileData, HashSet fixtures, Box2i cachedBounds) { - return new ChunkDatum(index, null!); + return new ChunkDatum(tileData, fixtures, cachedBounds); } } } diff --git a/Robust.Shared/Map/Components/GridTreeComponent.cs b/Robust.Shared/Map/Components/GridTreeComponent.cs index 8e888938812..8a38b1b2b50 100644 --- a/Robust.Shared/Map/Components/GridTreeComponent.cs +++ b/Robust.Shared/Map/Components/GridTreeComponent.cs @@ -9,5 +9,5 @@ namespace Robust.Shared.Map.Components; public sealed partial class GridTreeComponent : Component { [ViewVariables] - public readonly B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> Tree = new(); + public readonly B2DynamicTree<(EntityUid Uid, FixturesComponent Fixtures, MapGridComponent Grid)> Tree = new(); } diff --git a/Robust.Shared/Map/Components/MapGridComponent.cs b/Robust.Shared/Map/Components/MapGridComponent.cs index 2ba82aaeeb2..882d6a821d1 100644 --- a/Robust.Shared/Map/Components/MapGridComponent.cs +++ b/Robust.Shared/Map/Components/MapGridComponent.cs @@ -335,7 +335,7 @@ public bool TryGetTileRef(EntityCoordinates coords, out TileRef tile) /// Serialized state of a . /// [Serializable, NetSerializable] - internal sealed class MapGridComponentState(ushort chunkSize, Dictionary fullGridData, GameTick lastTileModifiedTick) : ComponentState + internal sealed class MapGridComponentState(ushort chunkSize, Dictionary fullGridData, GameTick lastTileModifiedTick) : ComponentState { /// /// The size of the chunks in the map grid. @@ -345,7 +345,7 @@ internal sealed class MapGridComponentState(ushort chunkSize, Dictionary /// Networked chunk data containing the full grid state. /// - public Dictionary FullGridData = fullGridData; + public Dictionary FullGridData = fullGridData; /// /// Last game tick that the tile on the grid was modified. @@ -357,7 +357,7 @@ internal sealed class MapGridComponentState(ushort chunkSize, Dictionary. /// [Serializable, NetSerializable] - internal sealed class MapGridComponentDeltaState(ushort chunkSize, List? chunkData, GameTick lastTileModifiedTick) + internal sealed class MapGridComponentDeltaState(ushort chunkSize, Dictionary? chunkData, GameTick lastTileModifiedTick) : ComponentState, IComponentDeltaState { /// @@ -368,7 +368,7 @@ internal sealed class MapGridComponentDeltaState(ushort chunkSize, List /// Networked chunk data. /// - public readonly List? ChunkData = chunkData; + public readonly Dictionary? ChunkData = chunkData; /// /// Last game tick that the tile on the grid was modified. @@ -382,12 +382,12 @@ public void ApplyToFullState(MapGridComponentState state) if (ChunkData == null) return; - foreach (var data in ChunkData) + foreach (var (index, data) in ChunkData) { if (data.IsDeleted()) - state.FullGridData!.Remove(data.Index); + state.FullGridData.Remove(index); else - state.FullGridData![data.Index] = data.TileData; + state.FullGridData[index] = new(data); } state.LastTileModifiedTick = LastTileModifiedTick; @@ -395,12 +395,11 @@ public void ApplyToFullState(MapGridComponentState state) public MapGridComponentState CreateNewFullState(MapGridComponentState state) { - var fullGridData = new Dictionary(state.FullGridData.Count); + var fullGridData = new Dictionary(state.FullGridData.Count); foreach (var (key, value) in state.FullGridData) { - var arr = fullGridData[key] = new Tile[value.Length]; - Array.Copy(value, arr, value.Length); + fullGridData[key] = new(value); } var newState = new MapGridComponentState(ChunkSize, fullGridData, LastTileModifiedTick); diff --git a/Robust.Shared/Map/MapChunk.cs b/Robust.Shared/Map/MapChunk.cs index 98cc4d4d3be..8c685b62098 100644 --- a/Robust.Shared/Map/MapChunk.cs +++ b/Robust.Shared/Map/MapChunk.cs @@ -39,12 +39,11 @@ internal sealed class MapChunk /// /// Chunk-local AABB of this chunk. /// + [ViewVariables] public Box2i CachedBounds { get; set; } - /// - /// Physics fixtures that make up this grid chunk. - /// - public Dictionary Fixtures { get; } = new(); + [ViewVariables] + internal HashSet Fixtures = new(); /// /// The last game simulation tick that a tile on this chunk was modified. diff --git a/Robust.Shared/Map/MapManager.Queries.cs b/Robust.Shared/Map/MapManager.Queries.cs index 5d90e96d182..e61e572747c 100644 --- a/Robust.Shared/Map/MapManager.Queries.cs +++ b/Robust.Shared/Map/MapManager.Queries.cs @@ -18,14 +18,16 @@ private bool IsIntersecting( ChunkEnumerator enumerator, IPhysShape shape, Transform shapeTransform, - EntityUid gridUid) + Entity grid) { - var gridTransform = _physics.GetPhysicsTransform(gridUid); + var gridTransform = _physics.GetPhysicsTransform(grid); while (enumerator.MoveNext(out var chunk)) { - foreach (var fixture in chunk.Fixtures.Values) + foreach (var id in chunk.Fixtures) { + var fixture = grid.Comp.Fixtures[id]; + for (var j = 0; j < fixture.Shape.ChildCount; j++) { if (_manifolds.TestOverlap(shape, 0, fixture.Shape, j, shapeTransform, gridTransform)) @@ -169,7 +171,7 @@ private void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, B if (!overlappingChunks.MoveNext(out _)) return true; } - else if (!state.MapManager.IsIntersecting(overlappingChunks, state.Shape, state.Transform, data.Uid)) + else if (!state.MapManager.IsIntersecting(overlappingChunks, state.Shape, state.Transform, (data.Uid, data.Fixtures))) { return true; } @@ -345,7 +347,7 @@ private readonly record struct GridQueryState( Box2 WorldAABB, IPhysShape Shape, Transform Transform, - B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> Tree, + B2DynamicTree<(EntityUid Uid, FixturesComponent Fixtures, MapGridComponent Grid)> Tree, SharedMapSystem MapSystem, MapManager MapManager, SharedTransformSystem TransformSystem, @@ -357,7 +359,7 @@ private record struct GridQueryState( Box2 WorldAABB, IPhysShape Shape, Transform Transform, - B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> Tree, + B2DynamicTree<(EntityUid Uid, FixturesComponent Fixtures, MapGridComponent Grid)> Tree, SharedMapSystem MapSystem, MapManager MapManager, SharedTransformSystem TransformSystem, diff --git a/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs b/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs index c57b0fe1a30..18aee0396f3 100644 --- a/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs @@ -249,24 +249,36 @@ private void HandleGridCollisions( // TODO: Need to handle grids colliding with non-grid entities with the same layer // (nothing in SS14 does this yet). + var fixture = _fixturesQuery.Comp(gridUid); + var physics = _physicsQuery.Comp(gridUid); var transform = _physicsSystem.GetPhysicsTransform(gridUid); - var state = (gridUid, grid, transform, worldMatrix, invWorldMatrix, _map, _physicsSystem, _transform, _physicsQuery, _xformQuery); + var state = ( + new Entity(gridUid, fixture, grid, physics), + transform, + worldMatrix, + invWorldMatrix, + _map, + _physicsSystem, + _transform, + _fixturesQuery, + _physicsQuery, + _xformQuery); _mapManager.FindGridsIntersecting(mapId, aabb, ref state, static (EntityUid uid, MapGridComponent component, - ref (EntityUid gridUid, - MapGridComponent grid, + ref (Entity grid, Transform transform, Matrix3x2 worldMatrix, Matrix3x2 invWorldMatrix, SharedMapSystem _map, SharedPhysicsSystem _physicsSystem, SharedTransformSystem xformSystem, + EntityQuery fixturesQuery, EntityQuery physicsQuery, EntityQuery xformQuery) tuple) => { - if (tuple.gridUid == uid || + if (tuple.grid.Owner == uid || !tuple.xformQuery.TryGetComponent(uid, out var collidingXform)) { return true; @@ -277,38 +289,43 @@ private void HandleGridCollisions( var otherTransform = tuple._physicsSystem.GetPhysicsTransform(uid); // Get Grid2 AABB in grid1 ref - var aabb1 = tuple.grid.LocalAABB.Intersect(tuple.invWorldMatrix.TransformBox(otherGridBounds)); + var aabb1 = tuple.grid.Comp2.LocalAABB.Intersect(tuple.invWorldMatrix.TransformBox(otherGridBounds)); // TODO: AddPair has a nasty check in there that's O(n) but that's also a general physics problem. - var ourChunks = tuple._map.GetLocalMapChunks(tuple.gridUid, tuple.grid, aabb1); - var physicsA = tuple.physicsQuery.GetComponent(tuple.gridUid); + var ourChunks = tuple._map.GetLocalMapChunks(tuple.grid.Owner, tuple.grid, aabb1); + var physicsA = tuple.grid.Comp3; var physicsB = tuple.physicsQuery.GetComponent(uid); + var fixturesB = tuple.fixturesQuery.Comp(uid); // Only care about chunks on other grid overlapping us. while (ourChunks.MoveNext(out var ourChunk)) { var ourChunkWorld = tuple.worldMatrix.TransformBox( - ourChunk.CachedBounds.Translated(ourChunk.Indices * tuple.grid.ChunkSize)); + ourChunk.CachedBounds.Translated(ourChunk.Indices * tuple.grid.Comp2.ChunkSize)); var ourChunkOtherRef = otherGridInvMatrix.TransformBox(ourChunkWorld); var collidingChunks = tuple._map.GetLocalMapChunks(uid, component, ourChunkOtherRef); while (collidingChunks.MoveNext(out var collidingChunk)) { - foreach (var (ourId, fixture) in ourChunk.Fixtures) + foreach (var ourId in ourChunk.Fixtures) { + var fixture = tuple.grid.Comp1.Fixtures[ourId]; + for (var i = 0; i < fixture.Shape.ChildCount; i++) { var fixAABB = fixture.Shape.ComputeAABB(tuple.transform, i); - foreach (var (otherId, otherFixture) in collidingChunk.Fixtures) + foreach (var otherId in collidingChunk.Fixtures) { + var otherFixture = fixturesB.Fixtures[otherId]; + for (var j = 0; j < otherFixture.Shape.ChildCount; j++) { var otherAABB = otherFixture.Shape.ComputeAABB(otherTransform, j); if (!fixAABB.Intersects(otherAABB)) continue; - tuple._physicsSystem.AddPair(tuple.gridUid, uid, + tuple._physicsSystem.AddPair(tuple.grid.Owner, uid, ourId, otherId, fixture, i, otherFixture, j, diff --git a/Robust.UnitTesting/Shared/Map/GridFixtures_Tests.cs b/Robust.UnitTesting/Shared/Map/GridFixtures_Tests.cs index cb27151cbde..7702d319194 100644 --- a/Robust.UnitTesting/Shared/Map/GridFixtures_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/GridFixtures_Tests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Threading.Tasks; @@ -8,62 +9,92 @@ using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; +using Robust.UnitTesting.Server; -namespace Robust.UnitTesting.Shared.Map +namespace Robust.UnitTesting.Shared.Map; + +/// +/// Tests whether grid fixtures are being generated correctly. +/// +[Parallelizable(ParallelScope.All)] +[TestFixture] +public sealed class GridFixtures_Tests : RobustIntegrationTest { /// - /// Tests whether grid fixtures are being generated correctly. + /// Tests that grid fixtures match what's expected. /// - [Parallelizable(ParallelScope.All)] - [TestFixture] - public sealed class GridFixtures_Tests : RobustIntegrationTest + [Test] + public void TestGridFixtureDeletion() { - [Test] - public async Task TestGridFixtures() + var server = RobustServerSimulation.NewSimulation().InitializeInstance(); + var map = server.CreateMap(); + var entManager = server.Resolve(); + var grid = server.Resolve().CreateGridEntity(map.MapId); + var mapSystem = entManager.System(); + var fixtures = entManager.GetComponent(grid); + + mapSystem.SetTiles(grid, new List<(Vector2i GridIndices, Tile Tile)>() { - var server = StartServer(); - await server.WaitIdleAsync(); - - var entManager = server.ResolveDependency(); - var mapManager = server.ResolveDependency(); - var physSystem = server.ResolveDependency().GetEntitySystem(); - - await server.WaitAssertion(() => - { - entManager.System().CreateMap(out var mapId); - var grid = mapManager.CreateGridEntity(mapId); - - // Should be nothing if grid empty - Assert.That(entManager.TryGetComponent(grid, out PhysicsComponent? gridBody)); - Assert.That(entManager.TryGetComponent(grid, out FixturesComponent? manager)); - Assert.That(manager!.FixtureCount, Is.EqualTo(0)); - Assert.That(gridBody!.BodyType, Is.EqualTo(BodyType.Static)); - - // 1 fixture if we only ever update the 1 chunk - grid.Comp.SetTile(Vector2i.Zero, new Tile(1)); - - Assert.That(manager.FixtureCount, Is.EqualTo(1)); - // Also should only be a single tile. - var bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0); - // Poly probably has Box2D's radius added to it so won't be a unit square - Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 1.0f, 0.1f)); - - // Now do 2 tiles (same chunk) - grid.Comp.SetTile(new Vector2i(0, 1), new Tile(1)); - - Assert.That(manager.FixtureCount, Is.EqualTo(1)); - bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0); - - // Even if we add a new tile old fixture should stay the same if they don't connect. - Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 2.0f, 0.1f)); - - // If we add a new chunk should be 2 now - grid.Comp.SetTile(new Vector2i(0, -1), new Tile(1)); - Assert.That(manager.FixtureCount, Is.EqualTo(2)); - - physSystem.SetLinearVelocity(grid, Vector2.One, manager: manager, body: gridBody); - Assert.That(gridBody.LinearVelocity.Length, Is.EqualTo(0f)); - }); - } + (Vector2i.Zero, new Tile(1)), + (Vector2i.Right, new Tile(1)), + (Vector2i.Right * 2, new Tile(1)), + (Vector2i.Up, new Tile(1)), + }); + + Assert.That(fixtures.FixtureCount, Is.EqualTo(2)); + Assert.That(grid.Comp.LocalAABB.Equals(new Box2(0f, 0f, 3f, 2f))); + + mapSystem.SetTile(grid, Vector2i.Up, Tile.Empty); + + Assert.That(fixtures.FixtureCount, Is.EqualTo(1)); + Assert.That(grid.Comp.LocalAABB.Equals(new Box2(0f, 0f, 3f, 1f))); + } + + [Test] + public async Task TestGridFixtures() + { + var server = StartServer(); + await server.WaitIdleAsync(); + + var entManager = server.ResolveDependency(); + var mapManager = server.ResolveDependency(); + var physSystem = server.ResolveDependency().GetEntitySystem(); + + await server.WaitAssertion(() => + { + entManager.System().CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); + + // Should be nothing if grid empty + Assert.That(entManager.TryGetComponent(grid, out PhysicsComponent? gridBody)); + Assert.That(entManager.TryGetComponent(grid, out FixturesComponent? manager)); + Assert.That(manager!.FixtureCount, Is.EqualTo(0)); + Assert.That(gridBody!.BodyType, Is.EqualTo(BodyType.Static)); + + // 1 fixture if we only ever update the 1 chunk + grid.Comp.SetTile(Vector2i.Zero, new Tile(1)); + + Assert.That(manager.FixtureCount, Is.EqualTo(1)); + // Also should only be a single tile. + var bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0); + // Poly probably has Box2D's radius added to it so won't be a unit square + Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 1.0f, 0.1f)); + + // Now do 2 tiles (same chunk) + grid.Comp.SetTile(new Vector2i(0, 1), new Tile(1)); + + Assert.That(manager.FixtureCount, Is.EqualTo(1)); + bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0); + + // Even if we add a new tile old fixture should stay the same if they don't connect. + Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 2.0f, 0.1f)); + + // If we add a new chunk should be 2 now + grid.Comp.SetTile(new Vector2i(0, -1), new Tile(1)); + Assert.That(manager.FixtureCount, Is.EqualTo(2)); + + physSystem.SetLinearVelocity(grid, Vector2.One, manager: manager, body: gridBody); + Assert.That(gridBody.LinearVelocity.Length, Is.EqualTo(0f)); + }); } } From 6e25ead58878d5cb2c94facdd8ee2b3e62c4c356 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sat, 31 Aug 2024 18:39:01 +1000 Subject: [PATCH 075/140] Version: 233.0.2 --- 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 d0033c40cfc..61514327453 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 233.0.1 + 233.0.2 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 6c0927ed773..237ecca2e27 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -* Fix exceptions in client game state handling for grids. +*None yet* ### Other @@ -54,6 +54,13 @@ END TEMPLATE--> *None yet* +## 233.0.2 + +### Bugfixes + +* Fix exceptions in client game state handling for grids. Now they will rely upon the networked fixture data and not try to rebuild in the grid state handler. + + ## 233.0.1 ### Bugfixes From b1e1a0cd880e1c1a976eaed47edf66594d35c34b Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 1 Sep 2024 04:54:28 +0200 Subject: [PATCH 076/140] Quick warning fixes (#5417) --- Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs | 2 +- .../Serialization/Manager/Definition/DataDefinitionUtility.cs | 2 +- Robust.UnitTesting/GameControllerDummy.cs | 2 +- .../Serialization/SerializationTests/DataDefinitionTests.cs | 2 +- .../Serialization/SerializationTests/ManagerTests.Resources.cs | 2 +- Robust.UnitTesting/TestingParallelManager.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs b/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs index ec3b9f8c02e..656f5b80a25 100644 --- a/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs +++ b/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs @@ -23,7 +23,7 @@ public sealed partial class ReplayLoadManager // Scratch data used by UpdateEntityStates. // Avoids copying changes for every change to an entity between checkpoints, instead copies once per checkpoint on // first change. We can also use this to avoid building a dictionary of ComponentChange inside the inner loop. - private class UpdateScratchData + private sealed class UpdateScratchData { public Dictionary Changes; public EntityState lastChange; diff --git a/Robust.Shared/Serialization/Manager/Definition/DataDefinitionUtility.cs b/Robust.Shared/Serialization/Manager/Definition/DataDefinitionUtility.cs index 1a6adb89c58..72aec126e46 100644 --- a/Robust.Shared/Serialization/Manager/Definition/DataDefinitionUtility.cs +++ b/Robust.Shared/Serialization/Manager/Definition/DataDefinitionUtility.cs @@ -2,7 +2,7 @@ namespace Robust.Shared.Serialization.Manager.Definition; -public class DataDefinitionUtility +public static class DataDefinitionUtility { public static string AutoGenerateTag(string name) { diff --git a/Robust.UnitTesting/GameControllerDummy.cs b/Robust.UnitTesting/GameControllerDummy.cs index 1f9411614ee..a29ac8ae1a0 100644 --- a/Robust.UnitTesting/GameControllerDummy.cs +++ b/Robust.UnitTesting/GameControllerDummy.cs @@ -12,7 +12,7 @@ internal sealed class GameControllerDummy : IGameControllerInternal public GameControllerOptions Options { get; } = new(); public bool ContentStart { get; set; } - public event Action? TickUpdateOverride; + public event Action? TickUpdateOverride { add { } remove { } } public void Shutdown(string? reason = null) { diff --git a/Robust.UnitTesting/Shared/Serialization/SerializationTests/DataDefinitionTests.cs b/Robust.UnitTesting/Shared/Serialization/SerializationTests/DataDefinitionTests.cs index 6030dfd108e..f2840126d91 100644 --- a/Robust.UnitTesting/Shared/Serialization/SerializationTests/DataDefinitionTests.cs +++ b/Robust.UnitTesting/Shared/Serialization/SerializationTests/DataDefinitionTests.cs @@ -23,7 +23,7 @@ public sealed partial class DataDefinitionTests : SerializationTest //copy: null <> cts(cc(+nt), c)/regular(c) [DataDefinition] - public partial class DataDefTestDummy + public sealed partial class DataDefTestDummy { [DataField("a")] public int a = Int32.MaxValue; [DataField("b")] public DataDummyStruct b = new(){Value = "default"}; diff --git a/Robust.UnitTesting/Shared/Serialization/SerializationTests/ManagerTests.Resources.cs b/Robust.UnitTesting/Shared/Serialization/SerializationTests/ManagerTests.Resources.cs index 15a0c5cd71b..a997865ad2d 100644 --- a/Robust.UnitTesting/Shared/Serialization/SerializationTests/ManagerTests.Resources.cs +++ b/Robust.UnitTesting/Shared/Serialization/SerializationTests/ManagerTests.Resources.cs @@ -185,7 +185,7 @@ public SerializerClass CreateCopy(ISerializationManager serializationManager, Se #region Other TypeDefinitions [CopyByRef] - private class CopyByRefTestClass {} + private sealed class CopyByRefTestClass {} [CopyByRef] private struct CopyByRefTestStruct diff --git a/Robust.UnitTesting/TestingParallelManager.cs b/Robust.UnitTesting/TestingParallelManager.cs index 2434514f25a..0a88eff86df 100644 --- a/Robust.UnitTesting/TestingParallelManager.cs +++ b/Robust.UnitTesting/TestingParallelManager.cs @@ -9,7 +9,7 @@ namespace Robust.UnitTesting; /// j public sealed class TestingParallelManager : IParallelManagerInternal { - public event Action? ParallelCountChanged; + public event Action? ParallelCountChanged { add { } remove { } } public int ParallelProcessCount => 1; public void AddAndInvokeParallelCountChanged(Action changed) From 4e73d727533e284932a23163c512848b8c4327bb Mon Sep 17 00:00:00 2001 From: Mervill Date: Sun, 1 Sep 2024 14:23:02 -0700 Subject: [PATCH 077/140] Remove unused IoC dependencies (#5419) --- Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs | 1 - Robust.Client/GameObjects/EntitySystems/InputSystem.cs | 1 - Robust.Client/GameStates/NetInterpOverlay.cs | 1 - Robust.Client/Graphics/Clyde/Clyde.cs | 1 - .../Controllers/Implementations/EntitySpawningUIController.cs | 1 - Robust.Shared/Containers/ContainerManagerComponent.cs | 1 - Robust.Shared/Toolshed/ToolshedEnvironment.cs | 1 - 7 files changed, 7 deletions(-) diff --git a/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs b/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs index 46ec41efa53..d646d34541a 100644 --- a/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs @@ -17,7 +17,6 @@ namespace Robust.Client.GameObjects { public sealed class ContainerSystem : SharedContainerSystem { - [Dependency] private readonly INetManager _netMan = default!; [Dependency] private readonly IRobustSerializer _serializer = default!; [Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!; [Dependency] private readonly PointLightSystem _lightSys = default!; diff --git a/Robust.Client/GameObjects/EntitySystems/InputSystem.cs b/Robust.Client/GameObjects/EntitySystems/InputSystem.cs index 8bc5cca0d3e..2ec62ceda42 100644 --- a/Robust.Client/GameObjects/EntitySystems/InputSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/InputSystem.cs @@ -20,7 +20,6 @@ namespace Robust.Client.GameObjects /// public sealed class InputSystem : SharedInputSystem, IPostInjectInit { - [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IClientGameStateManager _stateManager = default!; diff --git a/Robust.Client/GameStates/NetInterpOverlay.cs b/Robust.Client/GameStates/NetInterpOverlay.cs index 1dd3d7cef38..b1cbc02b8ca 100644 --- a/Robust.Client/GameStates/NetInterpOverlay.cs +++ b/Robust.Client/GameStates/NetInterpOverlay.cs @@ -15,7 +15,6 @@ internal sealed class NetInterpOverlay : Overlay { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; private readonly EntityLookupSystem _lookup; diff --git a/Robust.Client/Graphics/Clyde/Clyde.cs b/Robust.Client/Graphics/Clyde/Clyde.cs index a1e2484c441..736116e5abc 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.cs @@ -32,7 +32,6 @@ namespace Robust.Client.Graphics.Clyde internal sealed partial class Clyde : IClydeInternal, IPostInjectInit, IEntityEventSubscriber { [Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!; - [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly ILightManager _lightManager = default!; [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; diff --git a/Robust.Client/UserInterface/Controllers/Implementations/EntitySpawningUIController.cs b/Robust.Client/UserInterface/Controllers/Implementations/EntitySpawningUIController.cs index 66b11f6ffa1..70eab2f376e 100644 --- a/Robust.Client/UserInterface/Controllers/Implementations/EntitySpawningUIController.cs +++ b/Robust.Client/UserInterface/Controllers/Implementations/EntitySpawningUIController.cs @@ -21,7 +21,6 @@ public sealed class EntitySpawningUIController : UIController { [Dependency] private readonly IPlacementManager _placement = default!; [Dependency] private readonly IPrototypeManager _prototypes = default!; - [Dependency] private readonly IResourceCache _resources = default!; private EntitySpawnWindow? _window; private readonly List _shownEntities = new(); diff --git a/Robust.Shared/Containers/ContainerManagerComponent.cs b/Robust.Shared/Containers/ContainerManagerComponent.cs index b96ac472b3d..e86e266a42b 100644 --- a/Robust.Shared/Containers/ContainerManagerComponent.cs +++ b/Robust.Shared/Containers/ContainerManagerComponent.cs @@ -19,7 +19,6 @@ namespace Robust.Shared.Containers [RegisterComponent, ComponentProtoName("ContainerContainer")] public sealed partial class ContainerManagerComponent : Component, ISerializationHooks { - [Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!; [Dependency] private readonly IEntityManager _entMan = default!; [DataField("containers")] diff --git a/Robust.Shared/Toolshed/ToolshedEnvironment.cs b/Robust.Shared/Toolshed/ToolshedEnvironment.cs index 35a8c0da1e4..b1f998f9001 100644 --- a/Robust.Shared/Toolshed/ToolshedEnvironment.cs +++ b/Robust.Shared/Toolshed/ToolshedEnvironment.cs @@ -14,7 +14,6 @@ public sealed class ToolshedEnvironment { [Dependency] private readonly IReflectionManager _reflection = default!; [Dependency] private readonly ILogManager _logManager = default!; - [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly ToolshedManager _toolshedManager = default!; private readonly Dictionary _commands = new(); private readonly Dictionary> _commandPipeValueMap = new(); From 38c227b692344ab86b58057e6cbabc8807ed7517 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 03:27:25 +0200 Subject: [PATCH 078/140] Fix MarkupNode equality Implement GetHashCode() Fix Equals doing reference equality on the attributes. Small code cleanup. Actually mark it as IEquatable because we already implement Equals() so there's no reason not to. --- RELEASE-NOTES.md | 7 +- Robust.Shared/Utility/MarkupNode.cs | 45 ++++++++++--- .../Shared/Utility/MarkupNodeTest.cs | 67 +++++++++++++++++++ 3 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 Robust.UnitTesting/Shared/Utility/MarkupNodeTest.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 237ecca2e27..57c2e0a4fde 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,11 +39,12 @@ END TEMPLATE--> ### New features -*None yet* +* `MarkupNode` is now `IEquatable`. It already supported equality checks, now it implements the interface. ### Bugfixes -*None yet* +* Fixed equality checks for `MarkupNode` not properly handling attributes. +* Fixed `MarkupNode` not having a `GetHashCode()` implementation. ### Other @@ -83,7 +84,7 @@ END TEMPLATE--> ### Internal -* `ClientGameStateManager` now only initialises or starts entities after their parents have already been initialized. There are also some new debug asserts to try ensure that this rule isn't broken elsewhere. +* `ClientGameStateManager` now only initialises or starts entities after their parents have already been initialized. There are also some new debug asserts to try ensure that this rule isn't broken elsewhere. * Engine version script now supports dashes. diff --git a/Robust.Shared/Utility/MarkupNode.cs b/Robust.Shared/Utility/MarkupNode.cs index fc6713bf486..9cf7b6706f1 100644 --- a/Robust.Shared/Utility/MarkupNode.cs +++ b/Robust.Shared/Utility/MarkupNode.cs @@ -7,7 +7,7 @@ namespace Robust.Shared.Utility; [Serializable, NetSerializable] -public sealed class MarkupNode : IComparable +public sealed class MarkupNode : IComparable, IEquatable { public readonly string? Name; public readonly MarkupParameter Value; @@ -43,7 +43,7 @@ public override string ToString() attributesString += $"{k}{v}"; } - return $"[{(Closing ? "/" : "")}{Name}{Value.ToString().ReplaceLineEndings("\\n") ?? ""}{attributesString}]"; + return $"[{(Closing ? "/" : "")}{Name}{Value.ToString().ReplaceLineEndings("\\n")}{attributesString}]"; } public override bool Equals(object? obj) @@ -51,14 +51,38 @@ public override bool Equals(object? obj) return obj is MarkupNode node && Equals(node); } - public bool Equals(MarkupNode node) + public bool Equals(MarkupNode? node) { - var equal = Name == node.Name; - equal &= Value.Equals(node.Value); - equal &= Attributes.Count == 0 && node.Attributes.Count == 0 || Attributes.Equals(node.Attributes); - equal &= Closing == node.Closing; + if (node is null) + return false; - return equal; + if (Name != node.Name) + return false; + + if (!Value.Equals(node.Value)) + return false; + + if (Closing != node.Closing) + return false; + + if (Attributes.Count != node.Attributes.Count) + return false; + + foreach (var (key, value) in Attributes) + { + if (!node.Attributes.TryGetValue(key, out var nodeValue)) + return false; + + if (!value.Equals(nodeValue)) + return false; + } + + return true; + } + + public override int GetHashCode() + { + return HashCode.Combine(Name, Value, Closing); } public int CompareTo(MarkupNode? other) @@ -69,7 +93,10 @@ public int CompareTo(MarkupNode? other) return 1; var nameComparison = string.Compare(Name, other.Name, StringComparison.Ordinal); - return nameComparison != 0 ? nameComparison : Closing.CompareTo(other.Closing); + if (nameComparison != 0) + return nameComparison; + + return Closing.CompareTo(other.Closing); } } diff --git a/Robust.UnitTesting/Shared/Utility/MarkupNodeTest.cs b/Robust.UnitTesting/Shared/Utility/MarkupNodeTest.cs new file mode 100644 index 00000000000..08fc1f9d601 --- /dev/null +++ b/Robust.UnitTesting/Shared/Utility/MarkupNodeTest.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using NUnit.Framework; +using Robust.Shared.Maths; +using Robust.Shared.Utility; + +namespace Robust.UnitTesting.Shared.Utility; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +[TestOf(typeof(MarkupNode))] +[SuppressMessage("ReSharper", "AccessToStaticMemberViaDerivedType")] +public sealed class MarkupNodeTest +{ + [Test] + [SuppressMessage("Assertion", "NUnit2009:The same value has been provided as both the actual and the expected argument")] + public void TestEquality() + { + Assert.Multiple(() => + { + // Test name match + Assert.That(new MarkupNode("A"), Is.EqualTo(new MarkupNode("A"))); + Assert.That(new MarkupNode("A").GetHashCode(), Is.EqualTo(new MarkupNode("A").GetHashCode())); + Assert.That(new MarkupNode("A"), Is.Not.EqualTo(new MarkupNode("B"))); + + // Test closing match + Assert.That(new MarkupNode("A", null, null, true), Is.EqualTo(new MarkupNode("A", null, null, true))); + Assert.That(new MarkupNode("A", null, null, true).GetHashCode(), Is.EqualTo(new MarkupNode("A", null, null, true).GetHashCode())); + Assert.That(new MarkupNode("A", null, null, true), Is.Not.EqualTo(new MarkupNode("A"))); + + // Test value match + var param = new MarkupParameter("A"); + Assert.That(new MarkupNode("A", param, null), Is.EqualTo(new MarkupNode("A", param, null))); + Assert.That(new MarkupNode("A", param, null).GetHashCode(), Is.EqualTo(new MarkupNode("A", param, null).GetHashCode())); + Assert.That(new MarkupNode("A", param, null), Is.Not.EqualTo(new MarkupNode("A"))); + Assert.That(new MarkupNode("A", param, null), Is.Not.EqualTo(new MarkupNode("A", new MarkupParameter("B"), null))); + + // Test attributes match + var attrs = new Dictionary + { + { "A", new MarkupParameter("A") }, + { "B", new MarkupParameter(5) }, + { "C", new MarkupParameter(Color.Red) }, + }; + var wrongAttrs = new Dictionary + { + { "A", new MarkupParameter("A") }, + { "B", new MarkupParameter(6) }, + { "C", new MarkupParameter(Color.Red) }, + }; + var wrongAttrsTooLong = new Dictionary + { + { "A", new MarkupParameter("A") }, + { "B", new MarkupParameter(5) }, + { "C", new MarkupParameter(Color.Red) }, + { "D", new MarkupParameter(Color.Red) }, + }; + var attrs2 = attrs.Reverse().ToDictionary(); + Assert.That(new MarkupNode("A", null, attrs), Is.EqualTo(new MarkupNode("A", null, attrs2))); + Assert.That(new MarkupNode("A", null, attrs).GetHashCode(), Is.EqualTo(new MarkupNode("A", null, attrs2).GetHashCode())); + Assert.That(new MarkupNode("A", null, attrs), Is.Not.EqualTo(new MarkupNode("A"))); + Assert.That(new MarkupNode("A", null, attrs), Is.Not.EqualTo(new MarkupNode("A", null, wrongAttrs))); + Assert.That(new MarkupNode("A", null, attrs), Is.Not.EqualTo(new MarkupNode("A", null, wrongAttrsTooLong))); + }); + } +} From 1208c25dcdfd734fd9bca7c21a7a10bad8e68dc7 Mon Sep 17 00:00:00 2001 From: Mervill Date: Sun, 1 Sep 2024 18:29:10 -0700 Subject: [PATCH 079/140] resolve instances of the CS8974 warning (#5418) --- .../SerializationTests/ManagerTests.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Robust.UnitTesting/Shared/Serialization/SerializationTests/ManagerTests.cs b/Robust.UnitTesting/Shared/Serialization/SerializationTests/ManagerTests.cs index ad4dce76d37..6337b0dee68 100644 --- a/Robust.UnitTesting/Shared/Serialization/SerializationTests/ManagerTests.cs +++ b/Robust.UnitTesting/Shared/Serialization/SerializationTests/ManagerTests.cs @@ -137,8 +137,8 @@ todo test the generic variants too new object[] { SerializerRanDataNode, - SerializerClass.SerializerReturn, - SerializerClass.SerializerReturnAlt, + (object)SerializerClass.SerializerReturn, + (object)SerializerClass.SerializerReturnAlt, false, new Func[] { @@ -149,8 +149,8 @@ todo test the generic variants too new object[] { SerializerRanCustomDataNode, - SerializerClass.SerializerCustomReturn, - SerializerClass.SerializerCustomReturnAlt, + (object)SerializerClass.SerializerCustomReturn, + (object)SerializerClass.SerializerCustomReturnAlt, true, new Func[] { @@ -229,8 +229,8 @@ todo test the generic variants too new object[] { SerializerRanDataNode, - SerializerStruct.SerializerReturn, - SerializerStruct.SerializerReturnAlt, + (object)SerializerStruct.SerializerReturn, + (object)SerializerStruct.SerializerReturnAlt, false, new Func[] { @@ -241,8 +241,8 @@ todo test the generic variants too new object[] { SerializerRanCustomDataNode, - SerializerStruct.SerializerCustomReturn, - SerializerStruct.SerializerCustomReturnAlt, + (object)SerializerStruct.SerializerCustomReturn, + (object)SerializerStruct.SerializerCustomReturnAlt, true, new Func[] { From bbcc7cfe1f3be983fdd63bd26501f0c56fe61ea2 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 03:44:16 +0200 Subject: [PATCH 080/140] Fix warnings in GameLoop_Test.cs --- Robust.UnitTesting/Shared/Timing/GameLoop_Test.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Robust.UnitTesting/Shared/Timing/GameLoop_Test.cs b/Robust.UnitTesting/Shared/Timing/GameLoop_Test.cs index db444941673..2bdc7ed2fb4 100644 --- a/Robust.UnitTesting/Shared/Timing/GameLoop_Test.cs +++ b/Robust.UnitTesting/Shared/Timing/GameLoop_Test.cs @@ -17,7 +17,6 @@ sealed class GameLoop_Test : RobustUnitTest /// With single step enabled, the game loop should run 1 tick and then pause again. /// [Test] - [Timeout(1000)] // comment this out if you want to debug public void SingleStepTest() { // TimeoutAttribute causes this to run on different thread on .NET Core, From 0e621a26be10b62642ee0135c0bcabab7804227d Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 03:44:34 +0200 Subject: [PATCH 081/140] Fix warnings in ControlTest.cs --- Robust.UnitTesting/Client/UserInterface/ControlTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.UnitTesting/Client/UserInterface/ControlTest.cs b/Robust.UnitTesting/Client/UserInterface/ControlTest.cs index c0a1b585f0f..e0d4344a13a 100644 --- a/Robust.UnitTesting/Client/UserInterface/ControlTest.cs +++ b/Robust.UnitTesting/Client/UserInterface/ControlTest.cs @@ -84,7 +84,7 @@ public void TestVisibleInTree() control1.Visible = true; Assert.That(control2.VisibleInTree, Is.False); - control1.Dispose(); + control1.Orphan(); } [Test] From e7ac5ad047a0cad4404c00b3f87a04aba27e421c Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 03:45:18 +0200 Subject: [PATCH 082/140] Fix warnings in UserInterfaceManagerTest.cs --- .../UserInterface/UserInterfaceManagerTest.cs | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/Robust.UnitTesting/Client/UserInterface/UserInterfaceManagerTest.cs b/Robust.UnitTesting/Client/UserInterface/UserInterfaceManagerTest.cs index 512118f1f1f..ce5e891d6cb 100644 --- a/Robust.UnitTesting/Client/UserInterface/UserInterfaceManagerTest.cs +++ b/Robust.UnitTesting/Client/UserInterface/UserInterfaceManagerTest.cs @@ -133,10 +133,7 @@ void Control2MouseDown(GUIBoundKeyEventArgs ev) Assert.That(control3Fired, NUnit.Framework.Is.True); }); - control1.Dispose(); - control2.Dispose(); - control3.Dispose(); - control4.Dispose(); + control1.Orphan(); } [Test] @@ -144,7 +141,6 @@ public void TestGrabKeyboardFocus() { Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null); var control1 = new Control {CanKeyboardFocus = true}; - var control2 = new Control {CanKeyboardFocus = true}; control1.GrabKeyboardFocus(); Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.EqualTo(control1)); @@ -152,9 +148,6 @@ public void TestGrabKeyboardFocus() control1.ReleaseKeyboardFocus(); Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null); - - control1.Dispose(); - control2.Dispose(); } [Test] @@ -169,9 +162,6 @@ public void TestGrabKeyboardFocusSteal() Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.EqualTo(control2)); control2.ReleaseKeyboardFocus(); Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null); - - control1.Dispose(); - control2.Dispose(); } [Test] @@ -186,9 +176,6 @@ public void TestGrabKeyboardFocusOtherRelease() Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.EqualTo(control1)); _userInterfaceManager.ReleaseKeyboardFocus(); Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null); - - control1.Dispose(); - control2.Dispose(); } [Test] @@ -226,7 +213,7 @@ public void TestGrabKeyboardFocusOnClick() _userInterfaceManager.ReleaseKeyboardFocus(); Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null); - control.Dispose(); + control.Orphan(); } /// @@ -253,7 +240,7 @@ public void TestNotGrabKeyboardFocusOnClick() Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null); - control.Dispose(); + control.Orphan(); } } } From cdd8df743a636bd297ba849860f7e748b1d11739 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 03:49:06 +0200 Subject: [PATCH 083/140] Fix warnings in Transform_Test.cs --- .../GameObjects/Components/Transform_Test.cs | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Robust.UnitTesting/Server/GameObjects/Components/Transform_Test.cs b/Robust.UnitTesting/Server/GameObjects/Components/Transform_Test.cs index ba1585e2ccb..a7accf49d1c 100644 --- a/Robust.UnitTesting/Server/GameObjects/Components/Transform_Test.cs +++ b/Robust.UnitTesting/Server/GameObjects/Components/Transform_Test.cs @@ -101,7 +101,7 @@ public void ParentMapSwitchTest() // move the parent, and the child should move with it XformSystem.SetLocalPosition(child, new Vector2(6, 6), childTrans); - XformSystem.SetWorldPosition(parentTrans, new Vector2(-8, -8)); + XformSystem.SetWorldPosition(parent, new Vector2(-8, -8)); Assert.That(XformSystem.GetWorldPosition(childTrans), NUnit.Framework.Is.EqualTo(new Vector2(-2, -2))); @@ -131,8 +131,8 @@ public void ParentAttachMoveTest() var child = EntityManager.SpawnEntity(null, InitialPos); var parentTrans = EntityManager.GetComponent(parent); var childTrans = EntityManager.GetComponent(child); - XformSystem.SetWorldPosition(parentTrans, new Vector2(5, 5)); - XformSystem.SetWorldPosition(childTrans, new Vector2(6, 6)); + XformSystem.SetWorldPosition(parent, new Vector2(5, 5)); + XformSystem.SetWorldPosition(child, new Vector2(6, 6)); // Act var oldWpos = XformSystem.GetWorldPosition(childTrans); @@ -156,9 +156,9 @@ public void ParentDoubleAttachMoveTest() var parentTrans = EntityManager.GetComponent(parent); var childOneTrans = EntityManager.GetComponent(childOne); var childTwoTrans = EntityManager.GetComponent(childTwo); - XformSystem.SetWorldPosition(parentTrans, new Vector2(1, 1)); - XformSystem.SetWorldPosition(childOneTrans, new Vector2(2, 2)); - XformSystem.SetWorldPosition(childTwoTrans, new Vector2(3, 3)); + XformSystem.SetWorldPosition(parent, new Vector2(1, 1)); + XformSystem.SetWorldPosition(childOne, new Vector2(2, 2)); + XformSystem.SetWorldPosition(childTwo, new Vector2(3, 3)); // Act var oldWpos = XformSystem.GetWorldPosition(childOneTrans); @@ -188,8 +188,8 @@ public void ParentRotateTest() var child = EntityManager.SpawnEntity(null, InitialPos); var parentTrans = EntityManager.GetComponent(parent); var childTrans = EntityManager.GetComponent(child); - XformSystem.SetWorldPosition(parentTrans, new Vector2(0, 0)); - XformSystem.SetWorldPosition(childTrans, new Vector2(2, 0)); + XformSystem.SetWorldPosition(parent, new Vector2(0, 0)); + XformSystem.SetWorldPosition(child, new Vector2(2, 0)); XformSystem.SetParent(child, childTrans, parent, parentXform: parentTrans); //Act @@ -215,8 +215,8 @@ public void ParentTransRotateTest() var child = EntityManager.SpawnEntity(null, InitialPos); var parentTrans = EntityManager.GetComponent(parent); var childTrans = EntityManager.GetComponent(child); - XformSystem.SetWorldPosition(parentTrans, new Vector2(1, 1)); - XformSystem.SetWorldPosition(childTrans, new Vector2(2, 1)); + XformSystem.SetWorldPosition(parent, new Vector2(1, 1)); + XformSystem.SetWorldPosition(child, new Vector2(2, 1)); XformSystem.SetParent(child, childTrans, parent, parentXform: parentTrans); //Act @@ -248,10 +248,10 @@ public void PositionCompositionTest() var node3Trans = EntityManager.GetComponent(node3); var node4Trans = EntityManager.GetComponent(node4); - XformSystem.SetWorldPosition(node1Trans, new Vector2(0, 0)); - XformSystem.SetWorldPosition(node2Trans, new Vector2(1, 1)); - XformSystem.SetWorldPosition(node3Trans, new Vector2(2, 2)); - XformSystem.SetWorldPosition(node4Trans, new Vector2(0, 2)); + XformSystem.SetWorldPosition(node1, new Vector2(0, 0)); + XformSystem.SetWorldPosition(node2, new Vector2(1, 1)); + XformSystem.SetWorldPosition(node3, new Vector2(2, 2)); + XformSystem.SetWorldPosition(node4, new Vector2(0, 2)); XformSystem.SetParent(node2, node2Trans, node1, parentXform: node1Trans); XformSystem.SetParent(node3, node3Trans, node2, parentXform: node2Trans); @@ -285,9 +285,9 @@ public void ParentLocalPositionRoundingErrorTest() var node2Trans = EntityManager.GetComponent(node2); var node3Trans = EntityManager.GetComponent(node3); - XformSystem.SetWorldPosition(node1Trans, new Vector2(0, 0)); - XformSystem.SetWorldPosition(node2Trans, new Vector2(1, 1)); - XformSystem.SetWorldPosition(node3Trans, new Vector2(2, 2)); + XformSystem.SetWorldPosition(node1, new Vector2(0, 0)); + XformSystem.SetWorldPosition(node2, new Vector2(1, 1)); + XformSystem.SetWorldPosition(node3, new Vector2(2, 2)); XformSystem.SetParent(node1, node1Trans, node2, parentXform: node2Trans); XformSystem.SetParent(node2, node2Trans, node3, parentXform: node3Trans); @@ -330,9 +330,9 @@ public void ParentRotationRoundingErrorTest() var node2Trans = EntityManager.GetComponent(node2); var node3Trans = EntityManager.GetComponent(node3); - XformSystem.SetWorldPosition(node1Trans, new Vector2(0, 0)); - XformSystem.SetWorldPosition(node2Trans, new Vector2(1, 1)); - XformSystem.SetWorldPosition(node3Trans, new Vector2(2, 2)); + XformSystem.SetWorldPosition(node1, new Vector2(0, 0)); + XformSystem.SetWorldPosition(node2, new Vector2(1, 1)); + XformSystem.SetWorldPosition(node3, new Vector2(2, 2)); XformSystem.SetParent(node2, node2Trans, node1, parentXform: node1Trans); XformSystem.SetParent(node3, node3Trans, node2, parentXform: node2Trans); @@ -379,10 +379,10 @@ public void TreeComposeWorldMatricesTest() var node3Trans = EntityManager.GetComponent(node3); var node4Trans = EntityManager.GetComponent(node4); - XformSystem.SetWorldPosition(node1Trans, new Vector2(0, 0)); - XformSystem.SetWorldPosition(node2Trans, new Vector2(1, 1)); - XformSystem.SetWorldPosition(node3Trans, new Vector2(2, 2)); - XformSystem.SetWorldPosition(node4Trans, new Vector2(0, 2)); + XformSystem.SetWorldPosition(node1, new Vector2(0, 0)); + XformSystem.SetWorldPosition(node2, new Vector2(1, 1)); + XformSystem.SetWorldPosition(node3, new Vector2(2, 2)); + XformSystem.SetWorldPosition(node4, new Vector2(0, 2)); XformSystem.SetParent(node2, node2Trans, node1, parentXform: node1Trans); XformSystem.SetParent(node3, node3Trans, node2, parentXform: node2Trans); @@ -390,7 +390,7 @@ public void TreeComposeWorldMatricesTest() //Act node1Trans.LocalRotation = new Angle(MathHelper.Pi / 6.37); - XformSystem.SetWorldPosition(node1Trans, new Vector2(1, 1)); + XformSystem.SetWorldPosition(node1, new Vector2(1, 1)); var worldMat = XformSystem.GetWorldMatrix(node4Trans); var invWorldMat = XformSystem.GetInvWorldMatrix(node4Trans); From 306deddbd2527c38089b786757e2d7bfa0f2cec7 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 03:54:52 +0200 Subject: [PATCH 084/140] Fix warnings in TransformComponent_Tests.cs --- .../GameObjects/TransformComponent_Tests.cs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Robust.UnitTesting/Shared/GameObjects/TransformComponent_Tests.cs b/Robust.UnitTesting/Shared/GameObjects/TransformComponent_Tests.cs index 336c9e02e78..7e151e089c0 100644 --- a/Robust.UnitTesting/Shared/GameObjects/TransformComponent_Tests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/TransformComponent_Tests.cs @@ -1,6 +1,7 @@ using System; using System.Numerics; using NUnit.Framework; +using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -21,6 +22,7 @@ public void TestGetWorldMatches() var entManager = server.Resolve(); entManager.System().CreateMap(out var mapId); + var xform = entManager.System(); var ent1 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId)); var ent2 = entManager.SpawnEntity(null, new MapCoordinates(new Vector2(100f, 0f), mapId)); @@ -28,19 +30,19 @@ public void TestGetWorldMatches() var xform1 = entManager.GetComponent(ent1); var xform2 = entManager.GetComponent(ent2); - xform2.AttachParent(xform1); + xform.SetParent(ent2, ent1); xform1.LocalRotation = MathF.PI; - var (worldPos, worldRot, worldMatrix) = xform2.GetWorldPositionRotationMatrix(); + var (worldPos, worldRot, worldMatrix) = xform.GetWorldPositionRotationMatrix(xform2); - Assert.That(worldPos, Is.EqualTo(xform2.WorldPosition)); - Assert.That(worldRot, Is.EqualTo(xform2.WorldRotation)); - Assert.That(worldMatrix, Is.EqualTo(xform2.WorldMatrix)); + Assert.That(worldPos, Is.EqualTo(xform.GetWorldPosition(xform2))); + Assert.That(worldRot, Is.EqualTo(xform.GetWorldRotation(xform2))); + Assert.That(worldMatrix, Is.EqualTo(xform.GetWorldMatrix(xform2))); - var (_, _, invWorldMatrix) = xform2.GetWorldPositionRotationInvMatrix(); + var (_, _, invWorldMatrix) = xform.GetWorldPositionRotationInvMatrix(xform2); - Assert.That(invWorldMatrix, Is.EqualTo(xform2.InvWorldMatrix)); + Assert.That(invWorldMatrix, Is.EqualTo(xform.GetInvWorldMatrix(xform2))); } /// @@ -53,20 +55,21 @@ public void AttachToGridOrMap() var entManager = server.Resolve(); var mapManager = server.Resolve(); + var mapSystem = entManager.System(); + var xformSystem = entManager.System(); - entManager.System().CreateMap(out var mapId); + mapSystem.CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); - grid.Comp.SetTile(new Vector2i(0, 0), new Tile(1)); - var gridXform = entManager.GetComponent(grid); - gridXform.LocalPosition = new Vector2(0f, 100f); + mapSystem.SetTile(grid, new Vector2i(0, 0), new Tile(1)); + xformSystem.SetLocalPosition(grid, new Vector2(0f, 100f)); var ent1 = entManager.SpawnEntity(null, new EntityCoordinates(grid, Vector2.One * grid.Comp.TileSize / 2)); var ent2 = entManager.SpawnEntity(null, new EntityCoordinates(ent1, Vector2.Zero)); var xform2 = entManager.GetComponent(ent2); - Assert.That(xform2.WorldPosition, Is.EqualTo(new Vector2(0.5f, 100.5f))); + Assert.That(xformSystem.GetWorldPosition(ent2), Is.EqualTo(new Vector2(0.5f, 100.5f))); - xform2.AttachToGridOrMap(); + xformSystem.AttachToGridOrMap(ent2); Assert.That(xform2.LocalPosition, Is.EqualTo(Vector2.One * grid.Comp.TileSize / 2)); } } From f17f07784923f5326ab7a3be3494f880b58c6427 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 03:55:14 +0200 Subject: [PATCH 085/140] Fix warnings in FormattedMessage_Test.cs --- .../Shared/Utility/FormattedMessage_Test.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Robust.UnitTesting/Shared/Utility/FormattedMessage_Test.cs b/Robust.UnitTesting/Shared/Utility/FormattedMessage_Test.cs index 0af1da290cc..cf22363d83c 100644 --- a/Robust.UnitTesting/Shared/Utility/FormattedMessage_Test.cs +++ b/Robust.UnitTesting/Shared/Utility/FormattedMessage_Test.cs @@ -13,7 +13,7 @@ public sealed class FormattedMessage_Test [Test] public static void TestParseMarkup() { - var msg = FormattedMessage.FromMarkup("foo[color=#aabbcc]bar[/color]baz"); + var msg = FormattedMessage.FromMarkupOrThrow("foo[color=#aabbcc]bar[/color]baz"); Assert.That(msg.Nodes, NUnit.Framework.Is.EquivalentTo(new MarkupNode[] { @@ -28,7 +28,7 @@ public static void TestParseMarkup() [Test] public static void TestParseMarkupColorName() { - var msg = FormattedMessage.FromMarkup("foo[color=orange]bar[/color]baz"); + var msg = FormattedMessage.FromMarkupOrThrow("foo[color=orange]bar[/color]baz"); Assert.That(msg.Nodes, NUnit.Framework.Is.EquivalentTo(new MarkupNode[] { @@ -57,7 +57,7 @@ public static void TestParsePermissiveMarkup(string text) [TestCase("[color=red]Foo[/color]bar", ExpectedResult = "Foobar")] public string TestRemoveMarkup(string test) { - return FormattedMessage.RemoveMarkup(test); + return FormattedMessage.RemoveMarkupOrThrow(test); } [Test] @@ -66,7 +66,7 @@ public string TestRemoveMarkup(string test) [TestCase("[color=lime]Foo[/color]bar")] public static void TestToMarkup(string text) { - var message = FormattedMessage.FromMarkup(text); + var message = FormattedMessage.FromMarkupOrThrow(text); Assert.That(message.ToMarkup(), NUnit.Framework.Is.EqualTo(text)); } @@ -77,7 +77,7 @@ public static void TestToMarkup(string text) [TestCase("honk honk [color=#00FF00FF]Foo[/color]bar")] public static void TestEnumerateRunes(string text) { - var message = FormattedMessage.FromMarkup(text); + var message = FormattedMessage.FromMarkupOrThrow(text); Assert.That( message.EnumerateRunes(), From c2657812f5205f0a4c66f82a48b620b43e7506da Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 03:56:42 +0200 Subject: [PATCH 086/140] Fix warnings in GridTraversalTest.cs --- Robust.UnitTesting/Shared/TransformTests/GridTraversalTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.UnitTesting/Shared/TransformTests/GridTraversalTest.cs b/Robust.UnitTesting/Shared/TransformTests/GridTraversalTest.cs index e9edf633c38..451c75b3e80 100644 --- a/Robust.UnitTesting/Shared/TransformTests/GridTraversalTest.cs +++ b/Robust.UnitTesting/Shared/TransformTests/GridTraversalTest.cs @@ -33,7 +33,7 @@ await server.WaitPost(() => grid = gridComp.Owner; mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1)); var gridCentre = new EntityCoordinates(grid, .5f, .5f); - gridMapPos = gridCentre.ToMap(sEntMan, xforms).Position; + gridMapPos = xforms.ToMapCoordinates(gridCentre).Position; }); for (int i = 0; i < 10; i++) From 2f17cbb1dc87223d16e4f7def5bd36b86dde8f7c Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 03:57:09 +0200 Subject: [PATCH 087/140] Fix warnings in ToolshedTypesTest.BugCheck.cs --- .../Shared/Toolshed/ToolshedTypesTest.BugCheck.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Robust.UnitTesting/Shared/Toolshed/ToolshedTypesTest.BugCheck.cs b/Robust.UnitTesting/Shared/Toolshed/ToolshedTypesTest.BugCheck.cs index 972556c9a80..7b3ee61c952 100644 --- a/Robust.UnitTesting/Shared/Toolshed/ToolshedTypesTest.BugCheck.cs +++ b/Robust.UnitTesting/Shared/Toolshed/ToolshedTypesTest.BugCheck.cs @@ -8,9 +8,11 @@ public sealed partial class ToolshedTypesTest { // Assert that T -> Nullable holds and it's inverse does not. [Test] - public async Task Bug_Nullables_08_21_2023() + public Task Bug_Nullables_08_21_2023() { Assert.That(Toolshed.IsTransformableTo(typeof(int), typeof(Nullable))); Assert.That(!Toolshed.IsTransformableTo(typeof(Nullable), typeof(int))); + + return Task.CompletedTask; } } From 3f02ef37304fcca5ef5c400f2d4db2aca6b390b5 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 03:57:54 +0200 Subject: [PATCH 088/140] Fix warnings in PvsSystemTests.cs --- Robust.UnitTesting/Server/GameStates/PvsSystemTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Robust.UnitTesting/Server/GameStates/PvsSystemTests.cs b/Robust.UnitTesting/Server/GameStates/PvsSystemTests.cs index f08e7c5630a..b8993f7a8f6 100644 --- a/Robust.UnitTesting/Server/GameStates/PvsSystemTests.cs +++ b/Robust.UnitTesting/Server/GameStates/PvsSystemTests.cs @@ -30,6 +30,7 @@ public async Task TestMultipleIndexChange() var confMan = server.ResolveDependency(); var sPlayerMan = server.ResolveDependency(); var xforms = sEntMan.System(); + var maps = sEntMan.System(); var cEntMan = client.ResolveDependency(); var netMan = client.ResolveDependency(); @@ -52,7 +53,7 @@ await server.WaitPost(() => { map = server.System().CreateMap(out var mapId); var gridComp = mapMan.CreateGridEntity(mapId); - gridComp.Comp.SetTile(Vector2i.Zero, new Tile(1)); + maps.SetTile(gridComp, Vector2i.Zero, new Tile(1)); grid = gridComp.Owner; }); From 9ab4286592bdcb255ab5bac5583f772e44312d79 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 03:59:25 +0200 Subject: [PATCH 089/140] Wait why is that even returning a task in the first place --- .../Shared/Toolshed/ToolshedTypesTest.BugCheck.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Robust.UnitTesting/Shared/Toolshed/ToolshedTypesTest.BugCheck.cs b/Robust.UnitTesting/Shared/Toolshed/ToolshedTypesTest.BugCheck.cs index 7b3ee61c952..740d3f099fd 100644 --- a/Robust.UnitTesting/Shared/Toolshed/ToolshedTypesTest.BugCheck.cs +++ b/Robust.UnitTesting/Shared/Toolshed/ToolshedTypesTest.BugCheck.cs @@ -8,11 +8,9 @@ public sealed partial class ToolshedTypesTest { // Assert that T -> Nullable holds and it's inverse does not. [Test] - public Task Bug_Nullables_08_21_2023() + public void Bug_Nullables_08_21_2023() { Assert.That(Toolshed.IsTransformableTo(typeof(int), typeof(Nullable))); Assert.That(!Toolshed.IsTransformableTo(typeof(Nullable), typeof(int))); - - return Task.CompletedTask; } } From eaf7a6ba0fe6c39349e96821e5bfedd61d65c6c6 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 03:59:55 +0200 Subject: [PATCH 090/140] Fix warnings in ToolshedTypesTest.cs --- Robust.UnitTesting/Shared/Toolshed/ToolshedTypesTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Robust.UnitTesting/Shared/Toolshed/ToolshedTypesTest.cs b/Robust.UnitTesting/Shared/Toolshed/ToolshedTypesTest.cs index f72f3ba5922..0da055b21ad 100644 --- a/Robust.UnitTesting/Shared/Toolshed/ToolshedTypesTest.cs +++ b/Robust.UnitTesting/Shared/Toolshed/ToolshedTypesTest.cs @@ -11,7 +11,7 @@ public sealed partial class ToolshedTypesTest : ToolshedTest { // Assert that T -> IEnumerable holds. [Test] - public async Task EnumerableAutoCast() + public void EnumerableAutoCast() { Assert.That(Toolshed.IsTransformableTo(typeof(int), typeof(IEnumerable))); var l = Expression @@ -24,7 +24,7 @@ public async Task EnumerableAutoCast() // Assert that T -> IEnumerable where T: T' holds. [Test] - public async Task EnumerableSubtypeAutocast() + public void EnumerableSubtypeAutocast() { Assert.That(Toolshed.IsTransformableTo(typeof(int), typeof(IEnumerable))); var l = Expression @@ -37,7 +37,7 @@ public async Task EnumerableSubtypeAutocast() // Assert that T -> object. [Test] - public async Task CastToObject() + public void CastToObject() { Assert.That(Toolshed.IsTransformableTo(typeof(int), typeof(object))); var l = Expression From 5c7b1e68236117ffc890e764806c0bc2775d26f5 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 04:01:24 +0200 Subject: [PATCH 091/140] Fix warnings in ToolshedTest.cs --- Robust.UnitTesting/Shared/Toolshed/ToolshedTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Robust.UnitTesting/Shared/Toolshed/ToolshedTest.cs b/Robust.UnitTesting/Shared/Toolshed/ToolshedTest.cs index e0c488c33fa..2e77af54433 100644 --- a/Robust.UnitTesting/Shared/Toolshed/ToolshedTest.cs +++ b/Robust.UnitTesting/Shared/Toolshed/ToolshedTest.cs @@ -32,10 +32,11 @@ public async Task TearDownInternal() Server.Dispose(); } - protected virtual async Task TearDown() + protected virtual Task TearDown() { Assert.That(_expectedErrors, Is.Empty); ClearErrors(); + return Task.CompletedTask; } [SetUp] From df2160b1514ca1bc822e5bf5a43cba96a28c9ab9 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 04:10:54 +0200 Subject: [PATCH 092/140] Fix warnings in Broadphase_Test.cs --- .../Shared/Physics/Broadphase_Test.cs | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs b/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs index 8aac3e90649..22be866b88e 100644 --- a/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; using NUnit.Framework; @@ -14,6 +15,7 @@ namespace Robust.UnitTesting.Shared.Physics; [TestFixture] +[SuppressMessage("ReSharper", "AccessToStaticMemberViaDerivedType")] public sealed class Broadphase_Test { /// @@ -25,11 +27,13 @@ public void ReparentSundries() var sim = RobustServerSimulation.NewSimulation().InitializeInstance(); var entManager = sim.Resolve(); var mapManager = sim.Resolve(); + var mapSys = entManager.System(); + var xformSys = entManager.System(); var (mapEnt, mapId) = sim.CreateMap(); var grid = mapManager.CreateGridEntity(mapId); - grid.Comp.SetTile(Vector2i.Zero, new Tile(1)); + mapSys.SetTile(grid, Vector2i.Zero, new Tile(1)); Assert.That(entManager.HasComponent(grid)); var broadphase = entManager.GetComponent(grid); @@ -40,7 +44,7 @@ public void ReparentSundries() var broadphaseData = xform.Broadphase; Assert.That(broadphaseData!.Value.Uid, Is.EqualTo(grid.Owner)); - xform.Coordinates = new EntityCoordinates(mapEnt, Vector2.One); + xformSys.SetCoordinates(ent, new EntityCoordinates(mapEnt, Vector2.One)); Assert.That(broadphase.SundriesTree, Does.Not.Contain(ent)); Assert.That(entManager.GetComponent(mapEnt).SundriesTree, Does.Contain(ent)); @@ -59,12 +63,14 @@ public void ReparentBroadphase() var mapManager = sim.Resolve(); var fixturesSystem = entManager.EntitySysManager.GetEntitySystem(); var physicsSystem = entManager.EntitySysManager.GetEntitySystem(); + var mapSys = entManager.System(); + var xformSys = entManager.System(); var (mapEnt, mapId) = sim.CreateMap(); var grid = mapManager.CreateGridEntity(mapId); var gridUid = grid.Owner; - grid.Comp.SetTile(Vector2i.Zero, new Tile(1)); + mapSys.SetTile(grid, Vector2i.Zero, new Tile(1)); Assert.That(entManager.HasComponent(gridUid)); var broadphase = entManager.GetComponent(gridUid); @@ -90,7 +96,7 @@ public void ReparentBroadphase() Assert.That(broadphase.StaticTree.GetProxy(fixture.Proxies[0].ProxyId)!.Equals(fixture.Proxies[0])); // Now check we go to the map's tree correctly. - xform.Coordinates = new EntityCoordinates(mapEnt, Vector2.One); + xformSys.SetCoordinates(ent, new EntityCoordinates(mapEnt, Vector2.One)); Assert.That(entManager.GetComponent(mapEnt).StaticTree.GetProxy(fixture.Proxies[0].ProxyId)!.Equals(fixture.Proxies[0])); Assert.That(xform.Broadphase!.Value.Uid.Equals(mapEnt)); } @@ -107,21 +113,22 @@ public void GridMapUpdate() var sim = RobustServerSimulation.NewSimulation().InitializeInstance(); var entManager = sim.Resolve(); var mapManager = sim.Resolve(); + var mapSys = entManager.System(); + var xformSys = entManager.System(); - var mapId1 = sim.CreateMap().MapId; - var mapId2 = sim.CreateMap().MapId; + var (map1, mapId1) = sim.CreateMap(); + var (map2, _) = sim.CreateMap(); var grid = mapManager.CreateGridEntity(mapId1); - var xform = entManager.GetComponent(grid); - grid.Comp.SetTile(Vector2i.Zero, new Tile(1)); - var mapBroadphase1 = entManager.GetComponent(mapManager.GetMapEntityId(mapId1)); - var mapBroadphase2 = entManager.GetComponent(mapManager.GetMapEntityId(mapId2)); + mapSys.SetTile(grid, Vector2i.Zero, new Tile(1)); + var mapBroadphase1 = entManager.GetComponent(map1); + var mapBroadphase2 = entManager.GetComponent(map2); entManager.TickUpdate(0.016f, false); #pragma warning disable NUnit2046 Assert.That(mapBroadphase1.DynamicTree.Count, Is.EqualTo(0)); #pragma warning restore NUnit2046 - xform.Coordinates = new EntityCoordinates(mapManager.GetMapEntityId(mapId2), Vector2.Zero); + xformSys.SetCoordinates(grid, new EntityCoordinates(map1, Vector2.One)); entManager.TickUpdate(0.016f, false); #pragma warning disable NUnit2046 Assert.That(mapBroadphase2.DynamicTree.Count, Is.EqualTo(0)); @@ -140,13 +147,14 @@ public void BroadphaseRecursiveUpdate() var system = entManager.EntitySysManager; var physicsSystem = system.GetEntitySystem(); var lookup = system.GetEntitySystem(); + var mapSys = entManager.System(); - var mapId = sim.CreateMap().MapId; + var (map, mapId) = sim.CreateMap(); var grid = mapManager.CreateGridEntity(mapId); - grid.Comp.SetTile(Vector2i.Zero, new Tile(1)); + mapSys.SetTile(grid, Vector2i.Zero, new Tile(1)); var gridBroadphase = entManager.GetComponent(grid); - var mapBroadphase = entManager.GetComponent(mapManager.GetMapEntityId(mapId)); + var mapBroadphase = entManager.GetComponent(map); Assert.That(entManager.EntityQuery(true).Count(), Is.EqualTo(2)); @@ -169,7 +177,7 @@ public void BroadphaseRecursiveUpdate() Assert.That(lookup.FindBroadphase(child1), Is.EqualTo(gridBroadphase)); // They should get deparented to the map and updated to the map's broadphase instead. - grid.Comp.SetTile(Vector2i.Zero, Tile.Empty); + mapSys.SetTile(grid, Vector2i.Zero, Tile.Empty); Assert.That(lookup.FindBroadphase(parent), Is.EqualTo(mapBroadphase)); Assert.That(lookup.FindBroadphase(child1), Is.EqualTo(mapBroadphase)); Assert.That(lookup.FindBroadphase(child2), Is.EqualTo(mapBroadphase)); @@ -191,6 +199,7 @@ public void EntMapChangeRecursiveUpdate() var xforms = system.GetEntitySystem(); var physSystem = system.GetEntitySystem(); var fixtures = system.GetEntitySystem(); + var mapSys = entManager.System(); // setup maps var (mapA, mapAId) = sim.CreateMap(); @@ -204,9 +213,9 @@ public void EntMapChangeRecursiveUpdate() var gridB = gridBComp.Owner; var gridC = gridCComp.Owner; xforms.SetLocalPosition(gridC, new Vector2(10, 10)); - gridAComp.Comp.SetTile(Vector2i.Zero, new Tile(1)); - gridBComp.Comp.SetTile(Vector2i.Zero, new Tile(1)); - gridCComp.Comp.SetTile(Vector2i.Zero, new Tile(1)); + mapSys.SetTile(gridAComp, Vector2i.Zero, new Tile(1)); + mapSys.SetTile(gridBComp, Vector2i.Zero, new Tile(1)); + mapSys.SetTile(gridCComp, Vector2i.Zero, new Tile(1)); // set up test entities var parent = entManager.SpawnEntity(null, new EntityCoordinates(mapA, new Vector2(200,200))); From 44f9262d1a24df4dd3d7798fbb12ed762cbea7a0 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 05:30:10 +0200 Subject: [PATCH 093/140] SimulationExtensions helpers for RobustServerSimulation No more need to manually resolve IEntityManager in every test. --- .../Server/RobustServerSimulation.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Robust.UnitTesting/Server/RobustServerSimulation.cs b/Robust.UnitTesting/Server/RobustServerSimulation.cs index d8c71cbff23..55232b2e510 100644 --- a/Robust.UnitTesting/Server/RobustServerSimulation.cs +++ b/Robust.UnitTesting/Server/RobustServerSimulation.cs @@ -77,6 +77,32 @@ public interface ISimulation EntityUid SpawnEntity(string? protoId, MapCoordinates coordinates); } + /// + /// Helper methods for working with . + /// + internal static class SimulationExtensions + { + public static T System(this ISimulation simulation) where T : IEntitySystem + { + return simulation.Resolve().GetEntitySystem(); + } + + public static bool HasComp(this ISimulation simulation, EntityUid entity) where T : IComponent + { + return simulation.Resolve().HasComponent(entity); + } + + public static T Comp(this ISimulation simulation, EntityUid entity) where T : IComponent + { + return simulation.Resolve().GetComponent(entity); + } + + public static TransformComponent Transform(this ISimulation simulation, EntityUid entity) + { + return simulation.Comp(entity); + } + } + public delegate void DiContainerDelegate(IDependencyCollection diContainer); public delegate void CompRegistrationDelegate(IComponentFactory factory); From c740026014ab17ea7610e627a429cf224a972ea0 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 05:36:11 +0200 Subject: [PATCH 094/140] Entity overloads for some MapSystem methods. GetTileRef, TileIndicesFor, and GetAnchoredEntities --- RELEASE-NOTES.md | 1 + .../Systems/SharedMapSystem.Grid.cs | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 57c2e0a4fde..454c9951151 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -40,6 +40,7 @@ END TEMPLATE--> ### New features * `MarkupNode` is now `IEquatable`. It already supported equality checks, now it implements the interface. +* Added `Entity` overloads to the following `SharedMapSystem` methods: `GetTileRef`, `GetAnchoredEntities`, `TileIndicesFor`. ### Bugfixes diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs index 5b0294eff2c..47a750f05f7 100644 --- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs +++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs @@ -707,16 +707,31 @@ private void ClearEmptyMapChunks(EntityUid uid, MapGridComponent grid, IReadOnly #region TileAccess + public TileRef GetTileRef(Entity grid, MapCoordinates coords) + { + return GetTileRef(grid.Owner, grid.Comp, coords); + } + public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, MapCoordinates coords) { return GetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords)); } + public TileRef GetTileRef(Entity grid, EntityCoordinates coords) + { + return GetTileRef(grid.Owner, grid.Comp, coords); + } + public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, EntityCoordinates coords) { return GetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords)); } + public TileRef GetTileRef(Entity grid, Vector2i tileCoordinates) + { + return GetTileRef(grid.Owner, grid.Comp, tileCoordinates); + } + public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, Vector2i tileCoordinates) { var chunkIndices = GridTileToChunkIndices(uid, grid, tileCoordinates); @@ -1089,16 +1104,31 @@ public int AnchoredEntityCount(EntityUid uid, MapGridComponent grid, Vector2i po return chunk.GetSnapGrid((ushort)x, (ushort)y)?.Count ?? 0; // ? } + public IEnumerable GetAnchoredEntities(Entity grid, MapCoordinates coords) + { + return GetAnchoredEntities(grid.Owner, grid.Comp, coords); + } + public IEnumerable GetAnchoredEntities(EntityUid uid, MapGridComponent grid, MapCoordinates coords) { return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords)); } + public IEnumerable GetAnchoredEntities(Entity grid, EntityCoordinates coords) + { + return GetAnchoredEntities(grid.Owner, grid.Comp, coords); + } + public IEnumerable GetAnchoredEntities(EntityUid uid, MapGridComponent grid, EntityCoordinates coords) { return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords)); } + public IEnumerable GetAnchoredEntities(Entity grid, Vector2i pos) + { + return GetAnchoredEntities(grid.Owner, grid.Comp, pos); + } + public IEnumerable GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Vector2i pos) { // Because some content stuff checks neighboring tiles (which may not actually exist) we won't just @@ -1191,6 +1221,11 @@ public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, EntityCoord return SnapGridLocalCellFor(uid, grid, LocalToGrid(uid, grid, coords)); } + public Vector2i TileIndicesFor(Entity grid, EntityCoordinates coords) + { + return TileIndicesFor(grid.Owner, grid.Comp, coords); + } + public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, MapCoordinates worldPos) { #if DEBUG @@ -1202,6 +1237,11 @@ public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, MapCoordina return SnapGridLocalCellFor(uid, grid, localPos); } + public Vector2i TileIndicesFor(Entity grid, MapCoordinates coords) + { + return TileIndicesFor(grid.Owner, grid.Comp, coords); + } + private Vector2i SnapGridLocalCellFor(EntityUid uid, MapGridComponent grid, Vector2 localPos) { var x = (int)Math.Floor(localPos.X / grid.TileSize); From fd3eb092cc1a3af10ae40e8abed17d5c12da9106 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 05:37:49 +0200 Subject: [PATCH 095/140] EntityUid-only overloads for some TransformSystem methods AnchorEntity and Unanchor --- RELEASE-NOTES.md | 1 + .../Systems/SharedTransformSystem.Component.cs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 454c9951151..7e477bed387 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -41,6 +41,7 @@ END TEMPLATE--> * `MarkupNode` is now `IEquatable`. It already supported equality checks, now it implements the interface. * Added `Entity` overloads to the following `SharedMapSystem` methods: `GetTileRef`, `GetAnchoredEntities`, `TileIndicesFor`. +* Added `EntityUid`-only overloads to the following `SharedTransformSystem` methods: `AnchorEntity`, `Unanchor`. ### Bugfixes diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index 130d160e4c9..aed80ff5cba 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -107,6 +107,11 @@ public bool AnchorEntity(EntityUid uid, TransformComponent xform, MapGridCompone return AnchorEntity(uid, xform, grid.Owner, grid, tileIndices); } + public bool AnchorEntity(EntityUid uid) + { + return AnchorEntity(uid, XformQuery.GetComponent(uid)); + } + public bool AnchorEntity(EntityUid uid, TransformComponent xform) { return AnchorEntity((uid, xform)); @@ -127,6 +132,11 @@ public bool AnchorEntity(Entity entity, Entity Date: Mon, 2 Sep 2024 05:38:40 +0200 Subject: [PATCH 096/140] Fix most warnings in AnchoredSystemTests.cs Remaining warnings are cases where AnchorEntity, SetWorldPosition and SetLocalPosition subtly different from their component counterparts, and this triggers a test failure. Some help would be appreciated here. --- .../Systems/AnchoredSystemTests.cs | 301 ++++++++---------- 1 file changed, 136 insertions(+), 165 deletions(-) diff --git a/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs b/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs index e260e50da78..ecb53ce6e57 100644 --- a/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs @@ -20,8 +20,6 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems [TestFixture, Parallelizable] public sealed partial class AnchoredSystemTests { - private sealed class Subscriber : IEntityEventSubscriber { } - private const string Prototypes = @" - type: entity name: anchoredEnt @@ -30,7 +28,7 @@ private sealed class Subscriber : IEntityEventSubscriber { } - type: Transform anchored: true"; - private static (ISimulation, EntityUid gridId, MapCoordinates) SimulationFactory() + private static (ISimulation, Entity grid, MapCoordinates, SharedTransformSystem xformSys, SharedMapSystem mapSys) SimulationFactory() { var sim = RobustServerSimulation .NewSimulation() @@ -49,7 +47,7 @@ private static (ISimulation, EntityUid gridId, MapCoordinates) SimulationFactory // Add grid 1, as the default grid to anchor things to. var grid = mapManager.CreateGridEntity(testMapId); - return (sim, grid, coords); + return (sim, grid, coords, sim.System(), sim.System()); } // An entity is anchored to the tile it is over on the target grid. @@ -68,25 +66,23 @@ private static (ISimulation, EntityUid gridId, MapCoordinates) SimulationFactory [Test] public void OnAnchored_WorldPosition_TileCenter() { - var (sim, gridId, coordinates) = SimulationFactory(); - var entMan = sim.Resolve(); + var (sim, grid, coordinates, xformSys, mapSys) = SimulationFactory(); // can only be anchored to a tile - var grid = entMan.GetComponent(gridId); - grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); + mapSys.SetTile(grid, mapSys.TileIndicesFor(grid, coordinates), new Tile(1)); - var ent1 = entMan.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after + var ent1 = sim.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after // Act - entMan.System().ResetCounters(); - entMan.GetComponent(ent1).Anchored = true; - Assert.That(entMan.GetComponent(ent1).WorldPosition, Is.EqualTo(new Vector2(7.5f, 7.5f))); // centered on tile - entMan.System().AssertMoved(false); + sim.System().ResetCounters(); + xformSys.AnchorEntity(ent1); + Assert.That(xformSys.GetWorldPosition(ent1), Is.EqualTo(new Vector2(7.5f, 7.5f))); // centered on tile + sim.System().AssertMoved(false); } [ComponentProtoName("AnchorOnInit")] [Reflect(false)] - private sealed partial class AnchorOnInitComponent : Component { }; + private sealed partial class AnchorOnInitComponent : Component; [Reflect(false)] private sealed class AnchorOnInitTestSystem : EntitySystem @@ -117,9 +113,9 @@ public override void Shutdown() _transform.OnGlobalMoveEvent -= OnMove; } - public bool FailOnMove = false; - public int MoveCounter = 0; - public int ParentCounter = 0; + public bool FailOnMove; + public int MoveCounter; + public int ParentCounter; private void OnMove(ref MoveEvent ev) { @@ -163,25 +159,27 @@ public void OnInitAnchored_AddedToLookup() .RegisterComponents(f => f.RegisterClass()) .InitializeInstance(); + var mapSys = sim.System(); + var entMan = sim.Resolve(); var mapMan = sim.Resolve(); var mapId = sim.CreateMap().MapId; - var grid = mapMan.CreateGrid(mapId); + var grid = mapMan.CreateGridEntity(mapId); var coordinates = new MapCoordinates(new Vector2(7, 7), mapId); - var pos = grid.TileIndicesFor(coordinates); - grid.SetTile(pos, new Tile(1)); + var pos = mapSys.TileIndicesFor(grid, coordinates); + mapSys.SetTile(grid, pos, new Tile(1)); var ent1 = entMan.SpawnEntity(null, coordinates); - Assert.That(entMan.GetComponent(ent1).Anchored, Is.False); - Assert.That(!grid.GetAnchoredEntities(pos).Any()); + Assert.That(sim.Transform(ent1).Anchored, Is.False); + Assert.That(!mapSys.GetAnchoredEntities(grid, pos).Any()); entMan.DeleteEntity(ent1); var ent2 = entMan.CreateEntityUninitialized(null, coordinates); entMan.AddComponent(ent2); entMan.InitializeAndStartEntity(ent2); - Assert.That(entMan.GetComponent(ent2).Anchored); - Assert.That(grid.GetAnchoredEntities(pos).Count(), Is.EqualTo(1)); - Assert.That(grid.GetAnchoredEntities(pos).Contains(ent2)); + Assert.That(sim.Transform(ent2).Anchored); + Assert.That(mapSys.GetAnchoredEntities(grid, pos).Count(), Is.EqualTo(1)); + Assert.That(mapSys.GetAnchoredEntities(grid, pos).Contains(ent2)); } /// @@ -190,22 +188,20 @@ public void OnInitAnchored_AddedToLookup() [Test] public void OnAnchored_Parent_SetToGrid() { - var (sim, gridId, coordinates) = SimulationFactory(); - var entMan = sim.Resolve(); + var (sim, grid, coordinates, xformSys, mapSys) = SimulationFactory(); // can only be anchored to a tile - var grid = entMan.GetComponent(gridId); - grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); + mapSys.SetTile(grid, mapSys.TileIndicesFor(grid, coordinates), new Tile(1)); - var traversal = entMan.System(); + var traversal = sim.System(); traversal.Enabled = false; - var ent1 = entMan.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after + var ent1 = sim.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after // Act - entMan.System().ResetCounters(); - entMan.GetComponent(ent1).Anchored = true; - Assert.That(entMan.GetComponent(ent1).ParentUid, Is.EqualTo(grid.Owner)); - entMan.System().AssertMoved(); + sim.System().ResetCounters(); + sim.Transform(ent1).Anchored = true; + Assert.That(sim.Transform(ent1).ParentUid, Is.EqualTo(grid.Owner)); + sim.System().AssertMoved(); traversal.Enabled = true; } @@ -215,19 +211,17 @@ public void OnAnchored_Parent_SetToGrid() [Test] public void OnAnchored_EmptyTile_Nop() { - var (sim, gridId, coords) = SimulationFactory(); - var entMan = sim.Resolve(); + var (sim, grid, coords, xformSys, mapSys) = SimulationFactory(); - var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, coords); - var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); - grid.SetTile(tileIndices, Tile.Empty); + var ent1 = sim.SpawnEntity(null, coords); + var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates); + mapSys.SetTile(grid, tileIndices, Tile.Empty); // Act - entMan.GetComponent(ent1).Anchored = true; + xformSys.AnchorEntity(ent1); - Assert.That(grid.GetAnchoredEntities(tileIndices).Count(), Is.EqualTo(0)); - Assert.That(grid.GetTileRef(tileIndices).Tile, Is.EqualTo(Tile.Empty)); + Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).Count(), Is.EqualTo(0)); + Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.EqualTo(Tile.Empty)); } /// @@ -237,22 +231,20 @@ public void OnAnchored_EmptyTile_Nop() [Test] public void OnAnchored_NonEmptyTile_Anchors() { - var (sim, gridId, coords) = SimulationFactory(); - var entMan = sim.Resolve(); + var (sim, grid, coords, xformSys, mapSys) = SimulationFactory(); - var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, coords); - var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); - grid.SetTile(tileIndices, new Tile(1)); + var ent1 = sim.SpawnEntity(null, coords); + var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates); + mapSys.SetTile(grid, tileIndices, new Tile(1)); // Act - entMan.GetComponent(ent1).Anchored = true; + sim.Transform(ent1).Anchored = true; - Assert.That(grid.GetAnchoredEntities(tileIndices).First(), Is.EqualTo(ent1)); - Assert.That(grid.GetTileRef(tileIndices).Tile, Is.Not.EqualTo(Tile.Empty)); - Assert.That(entMan.HasComponent(ent1), Is.False); + Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).First(), Is.EqualTo(ent1)); + Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.Not.EqualTo(Tile.Empty)); + Assert.That(sim.HasComp(ent1), Is.False); var tempQualifier = grid.Owner; - Assert.That(entMan.HasComponent(tempQualifier), Is.True); + Assert.That(sim.HasComp(tempQualifier), Is.True); } /// @@ -263,26 +255,24 @@ public void OnAnchored_NonEmptyTile_Anchors() [Test] public void Anchored_SetPosition_Nop() { - var (sim, gridId, coordinates) = SimulationFactory(); - var entMan = sim.Resolve(); + var (sim, grid, coordinates, xformSys, mapSys) = SimulationFactory(); // coordinates are already tile centered to prevent snapping and MoveEvent coordinates = coordinates.Offset(new Vector2(0.5f, 0.5f)); // can only be anchored to a tile - var grid = entMan.GetComponent(gridId); - grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); + mapSys.SetTile(grid, mapSys.TileIndicesFor(grid, coordinates), new Tile(1)); - var ent1 = entMan.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after - entMan.GetComponent(ent1).Anchored = true; // Anchoring will change parent if needed, raising MoveEvent, subscribe after - entMan.System().FailOnMove = true; + var ent1 = sim.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after + sim.Transform(ent1).Anchored = true; + sim.System().FailOnMove = true; // Act - entMan.GetComponent(ent1).WorldPosition = new Vector2(99, 99); - entMan.GetComponent(ent1).LocalPosition = new Vector2(99, 99); + sim.Transform(ent1).WorldPosition = new Vector2(99, 99); + sim.Transform(ent1).LocalPosition = new Vector2(99, 99); - Assert.That(entMan.GetComponent(ent1).MapPosition, Is.EqualTo(coordinates)); - entMan.System().FailOnMove = false; + Assert.That(xformSys.GetMapCoordinates(ent1), Is.EqualTo(coordinates)); + sim.System().FailOnMove = false; } /// @@ -291,23 +281,19 @@ public void Anchored_SetPosition_Nop() [Test] public void Anchored_ChangeParent_Unanchors() { - var (sim, gridId, coordinates) = SimulationFactory(); - var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); + var (sim, grid, coordinates, xformSys, mapSys) = SimulationFactory(); - var grid = entMan.GetComponent(gridId); - - var ent1 = entMan.SpawnEntity(null, coordinates); - var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); - grid.SetTile(tileIndices, new Tile(1)); - entMan.GetComponent(ent1).Anchored = true; + var ent1 = sim.SpawnEntity(null, coordinates); + var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates); + mapSys.SetTile(grid, tileIndices, new Tile(1)); + xformSys.AnchorEntity(ent1); // Act - entMan.EntitySysManager.GetEntitySystem().SetParent(ent1, mapMan.GetMapEntityId(coordinates.MapId)); + xformSys.SetParent(ent1, mapSys.GetMap(coordinates.MapId)); - Assert.That(entMan.GetComponent(ent1).Anchored, Is.False); - Assert.That(grid.GetAnchoredEntities(tileIndices).Count(), Is.EqualTo(0)); - Assert.That(grid.GetTileRef(tileIndices).Tile, Is.EqualTo(new Tile(1))); + Assert.That(sim.Transform(ent1).Anchored, Is.False); + Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).Count(), Is.EqualTo(0)); + Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.EqualTo(new Tile(1))); } /// @@ -318,20 +304,19 @@ public void Anchored_ChangeParent_Unanchors() [Test] public void Anchored_SetParentSame_Nop() { - var (sim, gridId, coords) = SimulationFactory(); + var (sim, grid, coords, xformSys, mapSys) = SimulationFactory(); var entMan = sim.Resolve(); - var grid = entMan.GetComponent(gridId); var ent1 = entMan.SpawnEntity(null, coords); - var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); - grid.SetTile(tileIndices, new Tile(1)); - entMan.GetComponent(ent1).Anchored = true; + var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates); + mapSys.SetTile(grid, tileIndices, new Tile(1)); + sim.Transform(ent1).Anchored = true; // Act - entMan.EntitySysManager.GetEntitySystem().SetParent(ent1, grid.Owner); + xformSys.SetParent(ent1, grid.Owner); - Assert.That(grid.GetAnchoredEntities(tileIndices).First(), Is.EqualTo(ent1)); - Assert.That(grid.GetTileRef(tileIndices).Tile, Is.Not.EqualTo(Tile.Empty)); + Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).First(), Is.EqualTo(ent1)); + Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.Not.EqualTo(Tile.Empty)); } /// @@ -340,22 +325,20 @@ public void Anchored_SetParentSame_Nop() [Test] public void Anchored_TileToSpace_Unanchors() { - var (sim, gridId, coords) = SimulationFactory(); - var entMan = sim.Resolve(); + var (sim, grid, coords, xformSys, mapSys) = SimulationFactory(); - var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, coords); - var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); - grid.SetTile(tileIndices, new Tile(1)); - grid.SetTile(new Vector2i(100, 100), new Tile(1)); // Prevents the grid from being deleted when the Act happens - entMan.GetComponent(ent1).Anchored = true; + var ent1 = sim.SpawnEntity(null, coords); + var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates); + mapSys.SetTile(grid, tileIndices, new Tile(1)); + mapSys.SetTile(grid, new Vector2i(100, 100), new Tile(1)); // Prevents the grid from being deleted when the Act happens + xformSys.AnchorEntity(ent1); // Act - grid.SetTile(tileIndices, Tile.Empty); + mapSys.SetTile(grid, tileIndices, Tile.Empty); - Assert.That(entMan.GetComponent(ent1).Anchored, Is.False); - Assert.That(grid.GetAnchoredEntities(tileIndices).Count(), Is.EqualTo(0)); - Assert.That(grid.GetTileRef(tileIndices).Tile, Is.EqualTo(Tile.Empty)); + Assert.That(sim.Transform(ent1).Anchored, Is.False); + Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).Count(), Is.EqualTo(0)); + Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.EqualTo(Tile.Empty)); } /// @@ -368,25 +351,24 @@ public void Anchored_TileToSpace_Unanchors() [Test] public void Anchored_AddToContainer_Unanchors() { - var (sim, gridId, coords) = SimulationFactory(); + var (sim, grid, coords, xformSys, mapSys) = SimulationFactory(); var entMan = sim.Resolve(); - var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, coords); - var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); - grid.SetTile(tileIndices, new Tile(1)); - entMan.GetComponent(ent1).Anchored = true; + var ent1 = sim.SpawnEntity(null, coords); + var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates); + mapSys.SetTile(grid, tileIndices, new Tile(1)); + xformSys.AnchorEntity(ent1); // Act // We purposefully use the grid as container so parent stays the same, reparent will unanchor var containerSys = entMan.System(); - var containerMan = entMan.AddComponent(gridId); - var container = containerSys.MakeContainer(gridId, "TestContainer", containerMan); + var containerMan = entMan.AddComponent(grid); + var container = containerSys.MakeContainer(grid, "TestContainer", containerMan); containerSys.Insert(ent1, container); - Assert.That(entMan.GetComponent(ent1).Anchored, Is.False); - Assert.That(grid.GetAnchoredEntities(tileIndices).Count(), Is.EqualTo(0)); - Assert.That(grid.GetTileRef(tileIndices).Tile, Is.EqualTo(new Tile(1))); + Assert.That(sim.Transform(ent1).Anchored, Is.False); + Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).Count(), Is.EqualTo(0)); + Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.EqualTo(new Tile(1))); Assert.That(container.ContainedEntities.Count, Is.EqualTo(1)); } @@ -396,14 +378,13 @@ public void Anchored_AddToContainer_Unanchors() [Test] public void Anchored_AddPhysComp_IsStaticBody() { - var (sim, gridId, coords) = SimulationFactory(); + var (sim, grid, coords, xformSys, mapSys) = SimulationFactory(); var entMan = sim.Resolve(); - var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, coords); - var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); - grid.SetTile(tileIndices, new Tile(1)); - entMan.GetComponent(ent1).Anchored = true; + var ent1 = sim.SpawnEntity(null, coords); + var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates); + mapSys.SetTile(grid, tileIndices, new Tile(1)); + xformSys.AnchorEntity(ent1); // Act // assumed default body is Dynamic @@ -418,20 +399,19 @@ public void Anchored_AddPhysComp_IsStaticBody() [Test] public void OnAnchored_HasPhysicsComp_IsStaticBody() { - var (sim, gridId, coordinates) = SimulationFactory(); + var (sim, grid, coordinates, xformSys, mapSys) = SimulationFactory(); var entMan = sim.Resolve(); - var physSystem = sim.Resolve().GetEntitySystem(); + var physSystem = sim.System(); // can only be anchored to a tile - var grid = entMan.GetComponent(gridId); - grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); + mapSys.SetTile(grid, mapSys.TileIndicesFor(grid, coordinates), new Tile(1)); var ent1 = entMan.SpawnEntity(null, coordinates); var physComp = entMan.AddComponent(ent1); physSystem.SetBodyType(ent1, BodyType.Dynamic, body: physComp); // Act - entMan.GetComponent(ent1).Anchored = true; + xformSys.AnchorEntity(ent1); Assert.That(physComp.BodyType, Is.EqualTo(BodyType.Static)); } @@ -442,18 +422,17 @@ public void OnAnchored_HasPhysicsComp_IsStaticBody() [Test] public void OnUnanchored_HasPhysicsComp_IsDynamicBody() { - var (sim, gridId, coords) = SimulationFactory(); + var (sim, grid, coords, xformSys, mapSys) = SimulationFactory(); var entMan = sim.Resolve(); - var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, coords); - var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); - grid.SetTile(tileIndices, new Tile(1)); + var ent1 = sim.SpawnEntity(null, coords); + var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates); + mapSys.SetTile(grid, tileIndices, new Tile(1)); var physComp = entMan.AddComponent(ent1); - entMan.GetComponent(ent1).Anchored = true; + sim.Transform(ent1).Anchored = true; // Act - entMan.GetComponent(ent1).Anchored = false; + xformSys.Unanchor(ent1); Assert.That(physComp.BodyType, Is.EqualTo(BodyType.Dynamic)); } @@ -464,18 +443,15 @@ public void OnUnanchored_HasPhysicsComp_IsDynamicBody() [Test] public void SpawnAnchored_EmptyTile_Unanchors() { - var (sim, gridId, coords) = SimulationFactory(); - var entMan = sim.Resolve(); - - var grid = entMan.GetComponent(gridId); + var (sim, grid, coords, _, mapSys) = SimulationFactory(); // Act - var ent1 = entMan.SpawnEntity("anchoredEnt", coords); + var ent1 = sim.SpawnEntity("anchoredEnt", coords); - var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); - Assert.That(grid.GetAnchoredEntities(tileIndices).Count(), Is.EqualTo(0)); - Assert.That(grid.GetTileRef(tileIndices).Tile, Is.EqualTo(Tile.Empty)); - Assert.That(entMan.GetComponent(ent1).Anchored, Is.False); + var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates); + Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).Count(), Is.EqualTo(0)); + Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.EqualTo(Tile.Empty)); + Assert.That(sim.Transform(ent1).Anchored, Is.False); } /// @@ -484,25 +460,24 @@ public void SpawnAnchored_EmptyTile_Unanchors() [Test] public void OnAnchored_InContainer_Nop() { - var (sim, gridId, coords) = SimulationFactory(); + var (sim, grid, coords, xformSys, mapSys) = SimulationFactory(); var entMan = sim.Resolve(); - var grid = entMan.GetComponent(gridId); - var ent1 = entMan.SpawnEntity(null, coords); - var tileIndices = grid.TileIndicesFor(entMan.GetComponent(ent1).Coordinates); - grid.SetTile(tileIndices, new Tile(1)); + var ent1 = sim.SpawnEntity(null, coords); + var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates); + mapSys.SetTile(grid, tileIndices, new Tile(1)); var containerSys = entMan.System(); - var containerMan = entMan.AddComponent(gridId); - var container = containerSys.MakeContainer(gridId, "TestContainer", containerMan); + var containerMan = entMan.AddComponent(grid); + var container = containerSys.MakeContainer(grid, "TestContainer", containerMan); containerSys.Insert(ent1, container); // Act - entMan.GetComponent(ent1).Anchored = true; + xformSys.AnchorEntity(ent1); - Assert.That(entMan.GetComponent(ent1).Anchored, Is.False); - Assert.That(grid.GetAnchoredEntities(tileIndices).Count(), Is.EqualTo(0)); - Assert.That(grid.GetTileRef(tileIndices).Tile, Is.EqualTo(new Tile(1))); + Assert.That(sim.Transform(ent1).Anchored, Is.False); + Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).Count(), Is.EqualTo(0)); + Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.EqualTo(new Tile(1))); Assert.That(container.ContainedEntities.Count, Is.EqualTo(1)); } @@ -512,23 +487,20 @@ public void OnAnchored_InContainer_Nop() [Test] public void Unanchored_Unanchor_Nop() { - var (sim, gridId, coordinates) = SimulationFactory(); - var entMan = sim.Resolve(); - var mapMan = sim.Resolve(); + var (sim, grid, coordinates, xformSys, mapSys) = SimulationFactory(); // can only be anchored to a tile - var grid = entMan.GetComponent(gridId); - grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); + mapSys.SetTile(grid, mapSys.TileIndicesFor(grid, coordinates), new Tile(1)); - var traversal = entMan.System(); + var traversal = sim.System(); traversal.Enabled = false; - var ent1 = entMan.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after + var ent1 = sim.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after // Act - entMan.System().FailOnMove = true; - entMan.GetComponent(ent1).Anchored = false; - Assert.That(entMan.GetComponent(ent1).ParentUid, Is.EqualTo(mapMan.GetMapEntityId(coordinates.MapId))); - entMan.System().FailOnMove = false; + sim.System().FailOnMove = true; + xformSys.Unanchor(ent1); + Assert.That(sim.Transform(ent1).ParentUid, Is.EqualTo(mapSys.GetMap(coordinates.MapId))); + sim.System().FailOnMove = false; traversal.Enabled = true; } @@ -538,17 +510,16 @@ public void Unanchored_Unanchor_Nop() [Test] public void Anchored_Unanchored_ParentUnchanged() { - var (sim, gridId, coordinates) = SimulationFactory(); + var (sim, grid, coordinates, xformSys, mapSys) = SimulationFactory(); var entMan = sim.Resolve(); // can only be anchored to a tile - var grid = entMan.GetComponent(gridId); - grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); - var ent1 = entMan.SpawnEntity("anchoredEnt", grid.MapToGrid(coordinates)); + mapSys.SetTile(grid, mapSys.TileIndicesFor(grid, coordinates), new Tile(1)); + var ent1 = entMan.SpawnEntity("anchoredEnt", mapSys.MapToGrid(grid, coordinates)); - entMan.GetComponent(ent1).Anchored = false; + xformSys.Unanchor(ent1); - Assert.That(entMan.GetComponent(ent1).ParentUid, Is.EqualTo(grid.Owner)); + Assert.That(sim.Transform(ent1).ParentUid, Is.EqualTo(grid.Owner)); } } } From c36919d76a7fe872c5647f347228613cc94bd22b Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 05:45:08 +0200 Subject: [PATCH 097/140] Fix warnings in ArithmeticTest.cs --- Robust.UnitTesting/Shared/Toolshed/ArithmeticTest.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Robust.UnitTesting/Shared/Toolshed/ArithmeticTest.cs b/Robust.UnitTesting/Shared/Toolshed/ArithmeticTest.cs index 1eba5222348..1ec829f4a64 100644 --- a/Robust.UnitTesting/Shared/Toolshed/ArithmeticTest.cs +++ b/Robust.UnitTesting/Shared/Toolshed/ArithmeticTest.cs @@ -3,7 +3,6 @@ using System.Numerics; using System.Threading.Tasks; using NUnit.Framework; -using Is = NUnit.Framework.Is; namespace Robust.UnitTesting.Shared.Toolshed; @@ -16,9 +15,9 @@ public async Task OrderOfOperations() await Server.WaitAssertion(() => { // Toolshed always parses left-to-right with no precedence. - Assert.That(1.0f / 3.0f, Is.EqualTo(InvokeCommand("f 1 / 3"))); - Assert.That((1.0f + 1.0f) / 3.0f, Is.EqualTo(InvokeCommand("f 1 + 1 / 3"))); - Assert.That(float.Pow(2.0f + 2.0f, 3.0f), Is.EqualTo(InvokeCommand("f 2 + 2 pow 3"))); + Assert.That(InvokeCommand("f 1 / 3"), Is.EqualTo(1.0f / 3.0f)); + Assert.That(InvokeCommand("f 1 + 1 / 3"), Is.EqualTo((1.0f + 1.0f) / 3.0f)); + Assert.That(InvokeCommand("f 2 + 2 pow 3"), Is.EqualTo(float.Pow(2.0f + 2.0f, 3.0f))); }); } [Test] From 3c83f8e62a2b90222ed4b6eaf623840b9a5c2384 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 07:27:23 +0200 Subject: [PATCH 098/140] Make Rider not complain about Is. in Robust.UnitTesting, globally. --- Robust.UnitTesting/.editorconfig | 4 ++++ Robust.UnitTesting/Client/Graphics/EyeTest.cs | 1 - .../Server/GameObjects/Components/Container_Test.cs | 2 -- .../Shared/GameObjects/ComponentFactory_Tests.cs | 2 -- Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs | 2 -- .../Shared/GameObjects/Systems/AnchoredSystemTests.cs | 1 - .../Shared/GameState/DeletionNetworkingTests.cs | 2 -- Robust.UnitTesting/Shared/Map/EntityCoordinates_Tests.cs | 1 - Robust.UnitTesting/Shared/Map/MapPauseTests.cs | 2 -- Robust.UnitTesting/Shared/Maths/NumericsHelpers_Test.cs | 1 - Robust.UnitTesting/Shared/Physics/BroadphaseNetworkingTest.cs | 2 -- Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs | 1 - Robust.UnitTesting/Shared/Physics/RecursiveUpdateTest.cs | 2 -- Robust.UnitTesting/Shared/Random/RandomExtensionsTests.cs | 2 -- .../Shared/Serialization/PropertyAndFieldDefinitionTest.cs | 1 - .../Shared/Serialization/SerializationPriorityTest.cs | 2 -- .../Serialization/TypeSerializers/AngleSerializerTest.cs | 2 -- .../Serialization/TypeSerializers/ArraySerializerTest.cs | 2 -- .../Serialization/TypeSerializers/Box2SerializerTest.cs | 2 -- .../Serialization/TypeSerializers/ColorSerializerTest.cs | 2 -- .../TypeSerializers/ComponentRegistrySerializerTest.cs | 1 - .../TypeSerializers/Custom/FlagSerializerTest.cs | 2 -- .../Custom/Prototype/PrototypeIdListSerializerTest.cs | 2 -- .../Serialization/TypeSerializers/DictionarySerializerTest.cs | 2 -- .../TypeSerializers/FormattedMessageSerializerTest.cs | 2 -- .../Serialization/TypeSerializers/HashSetSerializerTest.cs | 2 -- .../Serialization/TypeSerializers/IntegerSerializerTest.cs | 2 -- .../Serialization/TypeSerializers/ListSerializerTest.cs | 2 -- .../Serialization/TypeSerializers/RegexSerializerTest.cs | 2 -- .../Serialization/TypeSerializers/SortedSetSerializerTest.cs | 2 -- .../ImmutableListSerializationTest.cs | 2 -- .../TypePropertySerialization_Test.cs | 2 -- .../YamlObjectSerializerTests/TypeSerialization_Test.cs | 2 -- Robust.UnitTesting/Shared/Utility/MarkupNodeTest.cs | 1 - Robust.UnitTesting/Shared/Utility/ResPathTest.cs | 3 --- Robust.UnitTesting/Shared/Utility/TextRope_Test.cs | 1 - 36 files changed, 4 insertions(+), 62 deletions(-) create mode 100644 Robust.UnitTesting/.editorconfig diff --git a/Robust.UnitTesting/.editorconfig b/Robust.UnitTesting/.editorconfig new file mode 100644 index 00000000000..39f6478b7ed --- /dev/null +++ b/Robust.UnitTesting/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] +# Doing this via EditorConfig seems to be the only way to suppress in an entire project. +# This warning conflicts with NUnit's recommendation to subtype the "Is" type. +resharper_access_to_static_member_via_derived_type_highlighting=none diff --git a/Robust.UnitTesting/Client/Graphics/EyeTest.cs b/Robust.UnitTesting/Client/Graphics/EyeTest.cs index c1e70f31dab..a992c7286af 100644 --- a/Robust.UnitTesting/Client/Graphics/EyeTest.cs +++ b/Robust.UnitTesting/Client/Graphics/EyeTest.cs @@ -5,7 +5,6 @@ using Robust.Shared.Map; using Robust.Shared.Maths; -// ReSharper disable AccessToStaticMemberViaDerivedType namespace Robust.UnitTesting.Client.Graphics { [TestFixture, Parallelizable, TestOf(typeof(Eye))] diff --git a/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs b/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs index 80191f52cc3..de140421721 100644 --- a/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs +++ b/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs @@ -11,8 +11,6 @@ using Robust.Shared.Timing; using Robust.Shared.Utility; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Server.GameObjects.Components { [TestFixture, Parallelizable] diff --git a/Robust.UnitTesting/Shared/GameObjects/ComponentFactory_Tests.cs b/Robust.UnitTesting/Shared/GameObjects/ComponentFactory_Tests.cs index dee615eb820..eaa4292db6a 100644 --- a/Robust.UnitTesting/Shared/GameObjects/ComponentFactory_Tests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/ComponentFactory_Tests.cs @@ -3,8 +3,6 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.GameObjects { [TestFixture] diff --git a/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs b/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs index 51c353d16a9..2960529f50f 100644 --- a/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs @@ -12,8 +12,6 @@ using Robust.Shared.Network; using Robust.Shared.Timing; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.GameObjects { public sealed class ContainerTests : RobustIntegrationTest diff --git a/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs b/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs index ecb53ce6e57..236c9469357 100644 --- a/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs @@ -13,7 +13,6 @@ using Robust.Shared.Reflection; using Robust.UnitTesting.Server; -// ReSharper disable AccessToStaticMemberViaDerivedType namespace Robust.UnitTesting.Shared.GameObjects.Systems { diff --git a/Robust.UnitTesting/Shared/GameState/DeletionNetworkingTests.cs b/Robust.UnitTesting/Shared/GameState/DeletionNetworkingTests.cs index 4185e9524b7..85021a7b196 100644 --- a/Robust.UnitTesting/Shared/GameState/DeletionNetworkingTests.cs +++ b/Robust.UnitTesting/Shared/GameState/DeletionNetworkingTests.cs @@ -10,8 +10,6 @@ using Robust.Shared.Network; using Robust.Shared.Player; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.GameState; /// diff --git a/Robust.UnitTesting/Shared/Map/EntityCoordinates_Tests.cs b/Robust.UnitTesting/Shared/Map/EntityCoordinates_Tests.cs index 2ce5d6418e4..f7f3c7bf3d4 100644 --- a/Robust.UnitTesting/Shared/Map/EntityCoordinates_Tests.cs +++ b/Robust.UnitTesting/Shared/Map/EntityCoordinates_Tests.cs @@ -7,7 +7,6 @@ using Robust.Shared.Serialization.Manager; // ReSharper disable InconsistentNaming -// ReSharper disable AccessToStaticMemberViaDerivedType namespace Robust.UnitTesting.Shared.Map { [TestFixture, Parallelizable, TestOf(typeof(EntityCoordinates))] diff --git a/Robust.UnitTesting/Shared/Map/MapPauseTests.cs b/Robust.UnitTesting/Shared/Map/MapPauseTests.cs index 0f4fdbc1467..6d39b5f9f34 100644 --- a/Robust.UnitTesting/Shared/Map/MapPauseTests.cs +++ b/Robust.UnitTesting/Shared/Map/MapPauseTests.cs @@ -4,8 +4,6 @@ using Robust.Shared.Map; using Robust.UnitTesting.Server; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Map; [TestFixture] diff --git a/Robust.UnitTesting/Shared/Maths/NumericsHelpers_Test.cs b/Robust.UnitTesting/Shared/Maths/NumericsHelpers_Test.cs index 7729eb3d1fc..575338755d7 100644 --- a/Robust.UnitTesting/Shared/Maths/NumericsHelpers_Test.cs +++ b/Robust.UnitTesting/Shared/Maths/NumericsHelpers_Test.cs @@ -12,7 +12,6 @@ namespace Robust.UnitTesting.Shared.Maths [Parallelizable(ParallelScope.All | ParallelScope.Fixtures)] [TestFixture] [TestOf(typeof(NumericsHelpers))] - [SuppressMessage("ReSharper", "AccessToStaticMemberViaDerivedType")] public sealed class NumericsHelpers_Test { #region Utils diff --git a/Robust.UnitTesting/Shared/Physics/BroadphaseNetworkingTest.cs b/Robust.UnitTesting/Shared/Physics/BroadphaseNetworkingTest.cs index b8d83181008..6b8de6fe526 100644 --- a/Robust.UnitTesting/Shared/Physics/BroadphaseNetworkingTest.cs +++ b/Robust.UnitTesting/Shared/Physics/BroadphaseNetworkingTest.cs @@ -15,8 +15,6 @@ using Robust.Shared.Physics.Systems; using Robust.Shared.Player; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Physics; public sealed class BroadphaseNetworkingTest : RobustIntegrationTest diff --git a/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs b/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs index 22be866b88e..85b1be0f659 100644 --- a/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs @@ -15,7 +15,6 @@ namespace Robust.UnitTesting.Shared.Physics; [TestFixture] -[SuppressMessage("ReSharper", "AccessToStaticMemberViaDerivedType")] public sealed class Broadphase_Test { /// diff --git a/Robust.UnitTesting/Shared/Physics/RecursiveUpdateTest.cs b/Robust.UnitTesting/Shared/Physics/RecursiveUpdateTest.cs index 8dcccbe3880..01dc9af570f 100644 --- a/Robust.UnitTesting/Shared/Physics/RecursiveUpdateTest.cs +++ b/Robust.UnitTesting/Shared/Physics/RecursiveUpdateTest.cs @@ -8,8 +8,6 @@ using Robust.Shared.Physics; using Robust.UnitTesting.Server; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Physics; [TestFixture] diff --git a/Robust.UnitTesting/Shared/Random/RandomExtensionsTests.cs b/Robust.UnitTesting/Shared/Random/RandomExtensionsTests.cs index 15c66fa0825..a93bb274c47 100644 --- a/Robust.UnitTesting/Shared/Random/RandomExtensionsTests.cs +++ b/Robust.UnitTesting/Shared/Random/RandomExtensionsTests.cs @@ -6,8 +6,6 @@ using Robust.Shared.Collections; using Robust.Shared.Random; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Random; /// Instantiable tests for . diff --git a/Robust.UnitTesting/Shared/Serialization/PropertyAndFieldDefinitionTest.cs b/Robust.UnitTesting/Shared/Serialization/PropertyAndFieldDefinitionTest.cs index f57724371c0..601251c8a4c 100644 --- a/Robust.UnitTesting/Shared/Serialization/PropertyAndFieldDefinitionTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/PropertyAndFieldDefinitionTest.cs @@ -9,7 +9,6 @@ // ReSharper disable UnassignedGetOnlyAutoProperty // ReSharper disable UnusedAutoPropertyAccessor.Global -// ReSharper disable AccessToStaticMemberViaDerivedType namespace Robust.UnitTesting.Shared.Serialization { diff --git a/Robust.UnitTesting/Shared/Serialization/SerializationPriorityTest.cs b/Robust.UnitTesting/Shared/Serialization/SerializationPriorityTest.cs index c9551a096c8..80817aeb6e4 100644 --- a/Robust.UnitTesting/Shared/Serialization/SerializationPriorityTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/SerializationPriorityTest.cs @@ -11,8 +11,6 @@ using Robust.Shared.Serialization.Markdown.Sequence; using YamlDotNet.RepresentationModel; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization { public sealed class SerializationPriorityTest : RobustUnitTest diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/AngleSerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/AngleSerializerTest.cs index 15c43c7f5f3..ae1c6b61c5c 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/AngleSerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/AngleSerializerTest.cs @@ -5,8 +5,6 @@ using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Implementations; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers { [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ArraySerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ArraySerializerTest.cs index 33b0baf1e80..327c72783e3 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ArraySerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ArraySerializerTest.cs @@ -4,8 +4,6 @@ using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers { [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/Box2SerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/Box2SerializerTest.cs index e97480aaf88..9b44d8db01c 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/Box2SerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/Box2SerializerTest.cs @@ -4,8 +4,6 @@ using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Implementations; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers { [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ColorSerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ColorSerializerTest.cs index b0708729904..798757bc2b2 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ColorSerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ColorSerializerTest.cs @@ -4,8 +4,6 @@ using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Implementations; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers { [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ComponentRegistrySerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ComponentRegistrySerializerTest.cs index 5dd8beac598..91ba30337ee 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ComponentRegistrySerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ComponentRegistrySerializerTest.cs @@ -12,7 +12,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations; using YamlDotNet.RepresentationModel; using static Robust.Shared.Prototypes.EntityPrototype; -// ReSharper disable AccessToStaticMemberViaDerivedType namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers { diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/Custom/FlagSerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/Custom/FlagSerializerTest.cs index f8360bd542b..30ac3bbb32a 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/Custom/FlagSerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/Custom/FlagSerializerTest.cs @@ -8,8 +8,6 @@ using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers.Custom { [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/Custom/Prototype/PrototypeIdListSerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/Custom/Prototype/PrototypeIdListSerializerTest.cs index 5cd6e48fc73..027a874b687 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/Custom/Prototype/PrototypeIdListSerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/Custom/Prototype/PrototypeIdListSerializerTest.cs @@ -14,8 +14,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; using YamlDotNet.RepresentationModel; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers.Custom.Prototype { [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/DictionarySerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/DictionarySerializerTest.cs index 8e41b29e9e7..d7a33d70353 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/DictionarySerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/DictionarySerializerTest.cs @@ -5,8 +5,6 @@ using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers { [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/FormattedMessageSerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/FormattedMessageSerializerTest.cs index f8dd2b125e1..db27c61a459 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/FormattedMessageSerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/FormattedMessageSerializerTest.cs @@ -4,8 +4,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations; using Robust.Shared.Utility; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers; [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/HashSetSerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/HashSetSerializerTest.cs index 402c4c9cbc4..d53563c0a5b 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/HashSetSerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/HashSetSerializerTest.cs @@ -5,8 +5,6 @@ using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers { [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/IntegerSerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/IntegerSerializerTest.cs index 53563e69f3f..cf2b4fa05e2 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/IntegerSerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/IntegerSerializerTest.cs @@ -1,8 +1,6 @@ using NUnit.Framework; using Robust.Shared.Serialization.Markdown.Value; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers { [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ListSerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ListSerializerTest.cs index c2eb911ebc0..f792dc3a2e3 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ListSerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/ListSerializerTest.cs @@ -5,8 +5,6 @@ using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers { [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/RegexSerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/RegexSerializerTest.cs index 22e4c24b46e..5132e52df8c 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/RegexSerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/RegexSerializerTest.cs @@ -4,8 +4,6 @@ using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Implementations; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers { [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/SortedSetSerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/SortedSetSerializerTest.cs index 917d4e2ee57..968eae3bf10 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/SortedSetSerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/SortedSetSerializerTest.cs @@ -5,8 +5,6 @@ using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers; [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/YamlObjectSerializerTests/ImmutableListSerializationTest.cs b/Robust.UnitTesting/Shared/Serialization/YamlObjectSerializerTests/ImmutableListSerializationTest.cs index 33bb917f6f3..b908a8aef10 100644 --- a/Robust.UnitTesting/Shared/Serialization/YamlObjectSerializerTests/ImmutableListSerializationTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/YamlObjectSerializerTests/ImmutableListSerializationTest.cs @@ -10,8 +10,6 @@ using YamlDotNet.Core; using YamlDotNet.RepresentationModel; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.YamlObjectSerializerTests { [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/YamlObjectSerializerTests/TypePropertySerialization_Test.cs b/Robust.UnitTesting/Shared/Serialization/YamlObjectSerializerTests/TypePropertySerialization_Test.cs index 3fc1988fc76..5b7b95732e2 100644 --- a/Robust.UnitTesting/Shared/Serialization/YamlObjectSerializerTests/TypePropertySerialization_Test.cs +++ b/Robust.UnitTesting/Shared/Serialization/YamlObjectSerializerTests/TypePropertySerialization_Test.cs @@ -10,8 +10,6 @@ using Robust.Shared.Serialization.Markdown.Value; using YamlDotNet.RepresentationModel; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.YamlObjectSerializerTests { [TestFixture] diff --git a/Robust.UnitTesting/Shared/Serialization/YamlObjectSerializerTests/TypeSerialization_Test.cs b/Robust.UnitTesting/Shared/Serialization/YamlObjectSerializerTests/TypeSerialization_Test.cs index 4ddbd894f07..5c87bbbcdeb 100644 --- a/Robust.UnitTesting/Shared/Serialization/YamlObjectSerializerTests/TypeSerialization_Test.cs +++ b/Robust.UnitTesting/Shared/Serialization/YamlObjectSerializerTests/TypeSerialization_Test.cs @@ -7,8 +7,6 @@ using Robust.Shared.Serialization.Markdown.Mapping; using YamlDotNet.RepresentationModel; -// ReSharper disable AccessToStaticMemberViaDerivedType - namespace Robust.UnitTesting.Shared.Serialization.YamlObjectSerializerTests { [TestFixture] diff --git a/Robust.UnitTesting/Shared/Utility/MarkupNodeTest.cs b/Robust.UnitTesting/Shared/Utility/MarkupNodeTest.cs index 08fc1f9d601..b65ea186c75 100644 --- a/Robust.UnitTesting/Shared/Utility/MarkupNodeTest.cs +++ b/Robust.UnitTesting/Shared/Utility/MarkupNodeTest.cs @@ -10,7 +10,6 @@ namespace Robust.UnitTesting.Shared.Utility; [TestFixture] [Parallelizable(ParallelScope.All)] [TestOf(typeof(MarkupNode))] -[SuppressMessage("ReSharper", "AccessToStaticMemberViaDerivedType")] public sealed class MarkupNodeTest { [Test] diff --git a/Robust.UnitTesting/Shared/Utility/ResPathTest.cs b/Robust.UnitTesting/Shared/Utility/ResPathTest.cs index e007730b585..62e014ee25a 100644 --- a/Robust.UnitTesting/Shared/Utility/ResPathTest.cs +++ b/Robust.UnitTesting/Shared/Utility/ResPathTest.cs @@ -2,9 +2,6 @@ using NUnit.Framework; using Robust.Shared.Utility; -// ReSharper disable AccessToStaticMemberViaDerivedType - - namespace Robust.UnitTesting.Shared.Utility; [TestFixture] diff --git a/Robust.UnitTesting/Shared/Utility/TextRope_Test.cs b/Robust.UnitTesting/Shared/Utility/TextRope_Test.cs index aad8130e6c3..1902ddd5e68 100644 --- a/Robust.UnitTesting/Shared/Utility/TextRope_Test.cs +++ b/Robust.UnitTesting/Shared/Utility/TextRope_Test.cs @@ -8,7 +8,6 @@ namespace Robust.UnitTesting.Shared.Utility; [TestFixture] [TestOf(typeof(Rope))] [Parallelizable(ParallelScope.All)] -[SuppressMessage("ReSharper", "AccessToStaticMemberViaDerivedType")] public static class TextRope_Test { [Test] From bc8d2c154ceaf5661db7d3937571b08e95cc9ecc Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 07:32:47 +0200 Subject: [PATCH 099/140] Fix warnings in EntityManager_Components_Tests.cs --- .../GameObjects/EntityManager_Components_Tests.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Robust.UnitTesting/Shared/GameObjects/EntityManager_Components_Tests.cs b/Robust.UnitTesting/Shared/GameObjects/EntityManager_Components_Tests.cs index 2fa43f4c700..a535b1779a2 100644 --- a/Robust.UnitTesting/Shared/GameObjects/EntityManager_Components_Tests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/EntityManager_Components_Tests.cs @@ -81,10 +81,7 @@ public void AddComponentTest() var (sim, coords) = SimulationFactory(); var entMan = sim.Resolve(); var entity = entMan.SpawnEntity(null, coords); - var component = new DummyComponent() - { - Owner = entity - }; + var component = new DummyComponent(); // Act entMan.AddComponent(entity, component); @@ -101,10 +98,7 @@ public void AddComponentOverwriteTest() var (sim, coords) = SimulationFactory(); var entMan = sim.Resolve(); var entity = entMan.SpawnEntity(null, coords); - var component = new DummyComponent() - { - Owner = entity - }; + var component = new DummyComponent(); // Act entMan.AddComponent(entity, component, true); @@ -121,10 +115,10 @@ public void AddComponent_ExistingDeleted() var (sim, coords) = SimulationFactory(); var entMan = sim.Resolve(); var entity = entMan.SpawnEntity(null, coords); - var firstComp = new DummyComponent {Owner = entity}; + var firstComp = new DummyComponent(); entMan.AddComponent(entity, firstComp); entMan.RemoveComponent(entity); - var secondComp = new DummyComponent { Owner = entity }; + var secondComp = new DummyComponent(); // Act entMan.AddComponent(entity, secondComp); From 4490751001c5685a0b03698343d383b14b4b2655 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 07:33:26 +0200 Subject: [PATCH 100/140] Fix warnings in FormattedMessageSerializerTest.cs --- .../TypeSerializers/FormattedMessageSerializerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/FormattedMessageSerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/FormattedMessageSerializerTest.cs index db27c61a459..9ce2f797127 100644 --- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/FormattedMessageSerializerTest.cs +++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/FormattedMessageSerializerTest.cs @@ -15,7 +15,7 @@ public sealed class FormattedMessageSerializerTest : SerializationTest [TestCase("[color=red]message[/color]")] public void SerializationTest(string text) { - var message = FormattedMessage.FromMarkup(text); + var message = FormattedMessage.FromMarkupOrThrow(text); var node = Serialization.WriteValueAs(message); Assert.That(node.Value, Is.EqualTo(text)); } From 4c81e68bf174b631b0b9a82d0762ec4e5c4e6b78 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 07:36:34 +0200 Subject: [PATCH 101/140] Remove last FormattedMessage.FromMarkup calls --- Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs | 2 +- Robust.Shared/Toolshed/Syntax/Block.cs | 2 +- Robust.Shared/Toolshed/Syntax/Expression.cs | 2 +- Robust.Shared/Toolshed/Syntax/ParsedCommand.cs | 4 ++-- Robust.Shared/Toolshed/ToolshedManager.Parsing.cs | 6 +++--- Robust.Shared/Toolshed/TypeParsers/BoolTypeParser.cs | 2 +- Robust.Shared/Toolshed/TypeParsers/ComponentTypeParser.cs | 2 +- Robust.Shared/Toolshed/TypeParsers/EntityTypeParser.cs | 4 ++-- Robust.Shared/Toolshed/TypeParsers/EnumTypeParser.cs | 2 +- Robust.Shared/Toolshed/TypeParsers/Math/AngleTypeParser.cs | 2 +- Robust.Shared/Toolshed/TypeParsers/Math/ColorTypeParser.cs | 2 +- .../Toolshed/TypeParsers/Math/NumberBaseTypeParser.cs | 2 +- .../Toolshed/TypeParsers/Math/SpanLikeTypeParser.cs | 2 +- Robust.Shared/Toolshed/TypeParsers/PrototypeTypeParser.cs | 2 +- Robust.Shared/Toolshed/TypeParsers/QuantityTypeParser.cs | 2 +- Robust.Shared/Toolshed/TypeParsers/SessionTypeParser.cs | 2 +- 16 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs b/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs index 6ff6c1040a4..6b82b005644 100644 --- a/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs +++ b/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs @@ -81,7 +81,7 @@ public record struct NoSuchPlayerError(string Username) : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkup($"No player with the username/GUID {Username} could be found."); + return FormattedMessage.FromUnformatted($"No player with the username/GUID {Username} could be found."); } public string? Expression { get; set; } diff --git a/Robust.Shared/Toolshed/Syntax/Block.cs b/Robust.Shared/Toolshed/Syntax/Block.cs index eeb2c04d4c0..b6d3264a045 100644 --- a/Robust.Shared/Toolshed/Syntax/Block.cs +++ b/Robust.Shared/Toolshed/Syntax/Block.cs @@ -134,7 +134,7 @@ public record struct MissingClosingBrace() : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkup("Expected a closing brace."); + return FormattedMessage.FromUnformatted("Expected a closing brace."); } public string? Expression { get; set; } diff --git a/Robust.Shared/Toolshed/Syntax/Expression.cs b/Robust.Shared/Toolshed/Syntax/Expression.cs index 8472ddfd4b8..5e603a8c073 100644 --- a/Robust.Shared/Toolshed/Syntax/Expression.cs +++ b/Robust.Shared/Toolshed/Syntax/Expression.cs @@ -182,7 +182,7 @@ public record struct ExpressionOfWrongType(Type Expected, Type Got, bool Once) : { public FormattedMessage DescribeInner() { - var msg = FormattedMessage.FromMarkup( + var msg = FormattedMessage.FromUnformatted( $"Expected an expression of type {Expected.PrettyName()}, but got {Got.PrettyName()}"); if (Once) diff --git a/Robust.Shared/Toolshed/Syntax/ParsedCommand.cs b/Robust.Shared/Toolshed/Syntax/ParsedCommand.cs index c4b695132ee..c061fe62de0 100644 --- a/Robust.Shared/Toolshed/Syntax/ParsedCommand.cs +++ b/Robust.Shared/Toolshed/Syntax/ParsedCommand.cs @@ -240,7 +240,7 @@ public record struct UnknownCommandError(string Cmd) : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkup($"Got unknown command {Cmd}."); + return FormattedMessage.FromUnformatted($"Got unknown command {Cmd}."); } public string? Expression { get; set; } @@ -252,7 +252,7 @@ public record NoImplementationError(string Cmd, Type[] Types, string? SubCommand { public FormattedMessage DescribeInner() { - var msg = FormattedMessage.FromMarkup($"Could not find an implementation for {Cmd} given the input type {PipedType?.PrettyName() ?? "void"}."); + var msg = FormattedMessage.FromUnformatted($"Could not find an implementation for {Cmd} given the input type {PipedType?.PrettyName() ?? "void"}."); msg.PushNewline(); var typeArgs = ""; diff --git a/Robust.Shared/Toolshed/ToolshedManager.Parsing.cs b/Robust.Shared/Toolshed/ToolshedManager.Parsing.cs index 78192b55e4d..b346c99a8f9 100644 --- a/Robust.Shared/Toolshed/ToolshedManager.Parsing.cs +++ b/Robust.Shared/Toolshed/ToolshedManager.Parsing.cs @@ -181,17 +181,17 @@ public FormattedMessage DescribeInner() if (T.Constructable()) { - var msg = FormattedMessage.FromMarkup( + var msg = FormattedMessage.FromUnformatted( $"The type {T.PrettyName()} has no parser available and cannot be parsed."); msg.PushNewline(); msg.AddText("Please contact a programmer with this error, they'd probably like to see it."); msg.PushNewline(); - msg.AddMarkup("[bold][color=red]THIS IS A BUG.[/color][/bold]"); + msg.AddMarkupOrThrow("[bold][color=red]THIS IS A BUG.[/color][/bold]"); return msg; } else { - return FormattedMessage.FromMarkup($"The type {T.PrettyName()} cannot be parsed, as it cannot be constructed."); + return FormattedMessage.FromUnformatted($"The type {T.PrettyName()} cannot be parsed, as it cannot be constructed."); } } diff --git a/Robust.Shared/Toolshed/TypeParsers/BoolTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/BoolTypeParser.cs index f5aa55f7d01..5a929af08e9 100644 --- a/Robust.Shared/Toolshed/TypeParsers/BoolTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/BoolTypeParser.cs @@ -60,7 +60,7 @@ public record InvalidBool(string Value) : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkup( + return FormattedMessage.FromUnformatted( $"The value {Value} is not a valid boolean."); } diff --git a/Robust.Shared/Toolshed/TypeParsers/ComponentTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/ComponentTypeParser.cs index 8acde11156d..fd8cad9428c 100644 --- a/Robust.Shared/Toolshed/TypeParsers/ComponentTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/ComponentTypeParser.cs @@ -61,7 +61,7 @@ public record struct UnknownComponentError(string Component) : IConError { public FormattedMessage DescribeInner() { - var msg = FormattedMessage.FromMarkup( + var msg = FormattedMessage.FromUnformatted( $"Unknown component {Component}. For a list of all components, try types:components." ); if (Component.EndsWith("component", true, CultureInfo.InvariantCulture)) diff --git a/Robust.Shared/Toolshed/TypeParsers/EntityTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/EntityTypeParser.cs index d270e28ad00..e5b40e11073 100644 --- a/Robust.Shared/Toolshed/TypeParsers/EntityTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/EntityTypeParser.cs @@ -49,7 +49,7 @@ public record InvalidEntity(string Value) : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkup($"Couldn't parse {Value} as an Entity."); + return FormattedMessage.FromUnformatted($"Couldn't parse {Value} as an Entity."); } public string? Expression { get; set; } @@ -61,7 +61,7 @@ public record DeadEntity(EntityUid Entity) : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkup($"The entity {Entity} does not exist."); + return FormattedMessage.FromUnformatted($"The entity {Entity} does not exist."); } public string? Expression { get; set; } diff --git a/Robust.Shared/Toolshed/TypeParsers/EnumTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/EnumTypeParser.cs index 0b5e0029c67..4a416daaeec 100644 --- a/Robust.Shared/Toolshed/TypeParsers/EnumTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/EnumTypeParser.cs @@ -57,7 +57,7 @@ public record InvalidEnum(string Value) : IConError { public FormattedMessage DescribeInner() { - var msg = FormattedMessage.FromMarkup($"The value {Value} is not a valid {typeof(T).PrettyName()}."); + var msg = FormattedMessage.FromUnformatted($"The value {Value} is not a valid {typeof(T).PrettyName()}."); msg.AddText($"Valid values are: {string.Join(", ", Enum.GetNames())}"); return msg; } diff --git a/Robust.Shared/Toolshed/TypeParsers/Math/AngleTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/Math/AngleTypeParser.cs index 8885fff5bd8..f69839a5b1c 100644 --- a/Robust.Shared/Toolshed/TypeParsers/Math/AngleTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/Math/AngleTypeParser.cs @@ -70,7 +70,7 @@ public record InvalidAngle(string Value) : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkup( + return FormattedMessage.FromUnformatted( $"The value {Value} is not a valid angle."); } diff --git a/Robust.Shared/Toolshed/TypeParsers/Math/ColorTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/Math/ColorTypeParser.cs index 1e628d59dc0..7aac2c5e035 100644 --- a/Robust.Shared/Toolshed/TypeParsers/Math/ColorTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/Math/ColorTypeParser.cs @@ -60,7 +60,7 @@ public record InvalidColor(string Value) : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkup( + return FormattedMessage.FromUnformatted( $"The value {Value} is not a valid RGB color or name of color."); } diff --git a/Robust.Shared/Toolshed/TypeParsers/Math/NumberBaseTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/Math/NumberBaseTypeParser.cs index 57b0577caef..bac588db163 100644 --- a/Robust.Shared/Toolshed/TypeParsers/Math/NumberBaseTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/Math/NumberBaseTypeParser.cs @@ -56,7 +56,7 @@ public record InvalidNumber(string Value) : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkup( + return FormattedMessage.FromUnformatted( $"The value {Value} is not a valid {typeof(T).PrettyName()}."); } diff --git a/Robust.Shared/Toolshed/TypeParsers/Math/SpanLikeTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/Math/SpanLikeTypeParser.cs index 9f9340acc9e..34199ece3aa 100644 --- a/Robust.Shared/Toolshed/TypeParsers/Math/SpanLikeTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/Math/SpanLikeTypeParser.cs @@ -97,7 +97,7 @@ public record UnexpectedCloseBrace : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkup("Unexpected closing brace."); + return FormattedMessage.FromUnformatted("Unexpected closing brace."); } public string? Expression { get; set; } diff --git a/Robust.Shared/Toolshed/TypeParsers/PrototypeTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/PrototypeTypeParser.cs index c8dcb62cdf7..56f28aae56c 100644 --- a/Robust.Shared/Toolshed/TypeParsers/PrototypeTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/PrototypeTypeParser.cs @@ -69,7 +69,7 @@ public record NotAValidPrototype(string Proto, string Kind) : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkup($"{Proto} is not a valid {Kind} prototype"); + return FormattedMessage.FromUnformatted($"{Proto} is not a valid {Kind} prototype"); } public string? Expression { get; set; } diff --git a/Robust.Shared/Toolshed/TypeParsers/QuantityTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/QuantityTypeParser.cs index f07033c2d2d..ac38a8345c0 100644 --- a/Robust.Shared/Toolshed/TypeParsers/QuantityTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/QuantityTypeParser.cs @@ -64,7 +64,7 @@ public record InvalidQuantity(string Value) : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkup( + return FormattedMessage.FromUnformatted( $"The value {Value} is not a valid quantity. Please input some decimal number, optionally with a % to indicate that it's a percentage."); } diff --git a/Robust.Shared/Toolshed/TypeParsers/SessionTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/SessionTypeParser.cs index 69524e841dd..06f5d5a1954 100644 --- a/Robust.Shared/Toolshed/TypeParsers/SessionTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/SessionTypeParser.cs @@ -54,7 +54,7 @@ public record InvalidUsername(ILocalizationManager Loc, string Username) : IConE { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkup(Loc.GetString("cmd-parse-failure-session", ("username", Username))); + return FormattedMessage.FromUnformatted(Loc.GetString("cmd-parse-failure-session", ("username", Username))); } public string? Expression { get; set; } From b0d17e95276fba31027bb8d45345c5cdf9e16c3c Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 13:17:02 +0200 Subject: [PATCH 102/140] Fix dead code equals method in Polygon Fixes #5420 Sloth clarified it's dead code from copy pasting. --- Robust.Shared/Physics/Shapes/Polygon.cs | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/Robust.Shared/Physics/Shapes/Polygon.cs b/Robust.Shared/Physics/Shapes/Polygon.cs index 78fd9a55a4b..64f0f3bd77c 100644 --- a/Robust.Shared/Physics/Shapes/Polygon.cs +++ b/Robust.Shared/Physics/Shapes/Polygon.cs @@ -1,7 +1,6 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; using Robust.Shared.Maths; using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Serialization.Manager.Attributes; @@ -197,28 +196,6 @@ public bool Equals(IPhysShape? other) return true; } - public bool Equals(PolygonShape? other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - - if (!Radius.Equals(other.Radius) || VertexCount != other.VertexCount) - return false; - - for (var i = 0; i < VertexCount; i++) - { - var vert = Vertices[i]; - var otherVert = other.Vertices[i]; - - if (!vert.Equals(otherVert)) - return false; - } - - return true; - } - public override int GetHashCode() { return HashCode.Combine(VertexCount, Vertices.AsSpan(0, VertexCount).ToArray(), Radius); From f3dfa1f6664908ef2cef4d65fc8eadc31b7d26c1 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:02:00 +1000 Subject: [PATCH 103/140] Move testbed command to benchmarks (#5424) --- .../Physics/BoxStackBenchmark.cs | 96 +++++ .../Physics/CircleStackBenchmark.cs | 92 +++++ Robust.Benchmarks/Physics/PyramidBenchmark.cs | 91 +++++ Robust.Benchmarks/Physics/TumblerBenchmark.cs | 105 ++++++ .../Console/Commands/TestbedCommand.cs | 335 ------------------ 5 files changed, 384 insertions(+), 335 deletions(-) create mode 100644 Robust.Benchmarks/Physics/BoxStackBenchmark.cs create mode 100644 Robust.Benchmarks/Physics/CircleStackBenchmark.cs create mode 100644 Robust.Benchmarks/Physics/PyramidBenchmark.cs create mode 100644 Robust.Benchmarks/Physics/TumblerBenchmark.cs delete mode 100644 Robust.Server/Console/Commands/TestbedCommand.cs diff --git a/Robust.Benchmarks/Physics/BoxStackBenchmark.cs b/Robust.Benchmarks/Physics/BoxStackBenchmark.cs new file mode 100644 index 00000000000..406264f54b3 --- /dev/null +++ b/Robust.Benchmarks/Physics/BoxStackBenchmark.cs @@ -0,0 +1,96 @@ +using System; +using System.Numerics; +using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.Configuration; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Physics.Systems; +using Robust.UnitTesting.Server; + +namespace Robust.Benchmarks.Physics; + +[Virtual] +[MediumRunJob] +public class PhysicsBoxStackBenchmark +{ + private ISimulation _sim = default!; + + [GlobalSetup] + public void Setup() + { + _sim = RobustServerSimulation.NewSimulation().InitializeInstance(); + + var entManager = _sim.Resolve(); + entManager.System().CreateMap(out var mapId); + SetupTumbler(entManager, mapId); + + for (var i = 0; i < 30; i++) + { + entManager.TickUpdate(0.016f, false); + } + } + + [Benchmark] + public void BoxStack() + { + var entManager = _sim.Resolve(); + + for (var i = 0; i < 10000; i++) + { + entManager.TickUpdate(0.016f, false); + } + } + + private void SetupTumbler(IEntityManager entManager, MapId mapId) + { + var physics = entManager.System(); + var fixtures = entManager.System(); + + var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); + var ground = entManager.AddComponent(groundUid); + + var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0)); + fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground); + + var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10)); + fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground); + + var xs = new[] + { + 0.0f, -10.0f, -5.0f, 5.0f, 10.0f + }; + + var columnCount = 1; + var rowCount = 15; + PolygonShape shape; + + for (var j = 0; j < columnCount; j++) + { + for (var i = 0; i < rowCount; i++) + { + var x = 0.0f; + + var boxUid = entManager.SpawnEntity(null, + new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 1.1f * i), mapId)); + var box = entManager.AddComponent(boxUid); + + physics.SetBodyType(boxUid, BodyType.Dynamic, body: box); + + shape = new PolygonShape(); + shape.SetAsBox(0.5f, 0.5f); + physics.SetFixedRotation(boxUid, false, body: box); + fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true), body: box); + + physics.WakeBody(boxUid, body: box); + physics.SetSleepingAllowed(boxUid, box, false); + } + } + + physics.WakeBody(groundUid, body: ground); + } +} diff --git a/Robust.Benchmarks/Physics/CircleStackBenchmark.cs b/Robust.Benchmarks/Physics/CircleStackBenchmark.cs new file mode 100644 index 00000000000..eb79a6b9a53 --- /dev/null +++ b/Robust.Benchmarks/Physics/CircleStackBenchmark.cs @@ -0,0 +1,92 @@ +using System.Numerics; +using BenchmarkDotNet.Attributes; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Physics.Systems; +using Robust.UnitTesting.Server; + +namespace Robust.Benchmarks.Physics; + +[Virtual] +public class PhysicsCircleStackBenchmark +{ + private ISimulation _sim = default!; + + [GlobalSetup] + public void Setup() + { + _sim = RobustServerSimulation.NewSimulation().InitializeInstance(); + + var entManager = _sim.Resolve(); + entManager.System().CreateMap(out var mapId); + SetupTumbler(entManager, mapId); + + for (var i = 0; i < 30; i++) + { + entManager.TickUpdate(0.016f, false); + } + } + + [Benchmark] + public void CircleStack() + { + var entManager = _sim.Resolve(); + + for (var i = 0; i < 10000; i++) + { + entManager.TickUpdate(0.016f, false); + } + } + + private void SetupTumbler(IEntityManager entManager, MapId mapId) + { + var physics = entManager.System(); + var fixtures = entManager.System(); + + var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); + var ground = entManager.AddComponent(groundUid); + + var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0)); + fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground); + + var vertical = new EdgeShape(new Vector2(20, 0), new Vector2(20, 20)); + fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground); + + var xs = new[] + { + 0.0f, -10.0f, -5.0f, 5.0f, 10.0f + }; + + var columnCount = 1; + var rowCount = 15; + PhysShapeCircle shape; + + for (var j = 0; j < columnCount; j++) + { + for (var i = 0; i < rowCount; i++) + { + var x = 0.0f; + + var boxUid = entManager.SpawnEntity(null, + new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 2.1f * i), mapId)); + var box = entManager.AddComponent(boxUid); + + physics.SetBodyType(boxUid, BodyType.Dynamic, body: box); + shape = new PhysShapeCircle(0.5f); + physics.SetFixedRotation(boxUid, false, body: box); + // TODO: Need to detect shape and work out if we need to use fixedrotation + + fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f)); + physics.WakeBody(boxUid, body: box); + physics.SetSleepingAllowed(boxUid, box, false); + } + } + + physics.WakeBody(groundUid, body: ground); + } +} diff --git a/Robust.Benchmarks/Physics/PyramidBenchmark.cs b/Robust.Benchmarks/Physics/PyramidBenchmark.cs new file mode 100644 index 00000000000..505176dfa65 --- /dev/null +++ b/Robust.Benchmarks/Physics/PyramidBenchmark.cs @@ -0,0 +1,91 @@ +using System; +using System.Numerics; +using BenchmarkDotNet.Attributes; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Physics.Systems; +using Robust.UnitTesting.Server; + +namespace Robust.Benchmarks.Physics; + +[Virtual] +public class PhysicsPyramidBenchmark +{ + private ISimulation _sim = default!; + + [GlobalSetup] + public void Setup() + { + _sim = RobustServerSimulation.NewSimulation().InitializeInstance(); + + var entManager = _sim.Resolve(); + entManager.System().CreateMap(out var mapId); + SetupTumbler(entManager, mapId); + + for (var i = 0; i < 300; i++) + { + entManager.TickUpdate(0.016f, false); + } + } + + [Benchmark] + public void Pyramid() + { + var entManager = _sim.Resolve(); + + for (var i = 0; i < 5000; i++) + { + entManager.TickUpdate(0.016f, false); + } + } + + private void SetupTumbler(IEntityManager entManager, MapId mapId) + { + const byte count = 20; + + // Setup ground + var physics = entManager.System(); + var fixtures = entManager.System(); + var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); + var ground = entManager.AddComponent(groundUid); + + var horizontal = new EdgeShape(new Vector2(40, 0), new Vector2(-40, 0)); + fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground); + physics.WakeBody(groundUid, body: ground); + + // Setup boxes + float a = 0.5f; + PolygonShape shape = new(); + shape.SetAsBox(a, a); + + var x = new Vector2(-7.0f, 0.75f); + Vector2 y; + Vector2 deltaX = new Vector2(0.5625f, 1.25f); + Vector2 deltaY = new Vector2(1.125f, 0.0f); + + for (var i = 0; i < count; ++i) + { + y = x; + + for (var j = i; j < count; ++j) + { + var boxUid = entManager.SpawnEntity(null, new MapCoordinates(y, mapId)); + var box = entManager.AddComponent(boxUid); + physics.SetBodyType(boxUid, BodyType.Dynamic, body: box); + + fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f), body: box); + y += deltaY; + + physics.WakeBody(boxUid, body: box); + physics.SetSleepingAllowed(boxUid, box, false); + } + + x += deltaX; + } + } +} diff --git a/Robust.Benchmarks/Physics/TumblerBenchmark.cs b/Robust.Benchmarks/Physics/TumblerBenchmark.cs new file mode 100644 index 00000000000..f2e44185be6 --- /dev/null +++ b/Robust.Benchmarks/Physics/TumblerBenchmark.cs @@ -0,0 +1,105 @@ +using System; +using System.Numerics; +using BenchmarkDotNet.Attributes; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Physics.Systems; +using Robust.UnitTesting.Server; + +namespace Robust.Benchmarks.Physics; + +[Virtual] +public class PhysicsTumblerBenchmark +{ + private ISimulation _sim = default!; + + [GlobalSetup] + public void Setup() + { + _sim = RobustServerSimulation.NewSimulation().InitializeInstance(); + + var entManager = _sim.Resolve(); + var physics = entManager.System(); + var fixtures = entManager.System(); + entManager.System().CreateMap(out var mapId); + SetupTumbler(entManager, mapId); + + for (var i = 0; i < 800; i++) + { + entManager.TickUpdate(0.016f, false); + var boxUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId)); + var box = entManager.AddComponent(boxUid); + physics.SetBodyType(boxUid, BodyType.Dynamic, body: box); + physics.SetFixedRotation(boxUid, false, body: box); + var shape = new PolygonShape(); + shape.SetAsBox(0.125f, 0.125f); + fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 0.0625f), body: box); + physics.WakeBody(boxUid, body: box); + physics.SetSleepingAllowed(boxUid, box, false); + } + } + + [Benchmark] + public void Tumbler() + { + var entManager = _sim.Resolve(); + + for (var i = 0; i < 5000; i++) + { + entManager.TickUpdate(0.016f, false); + } + } + + private void SetupTumbler(IEntityManager entManager, MapId mapId) + { + var physics = entManager.System(); + var fixtures = entManager.System(); + var joints = entManager.System(); + + var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 0f, mapId)); + var ground = entManager.AddComponent(groundUid); + // Due to lookup changes fixtureless bodies are invalid, so + var cShape = new PhysShapeCircle(1f); + fixtures.CreateFixture(groundUid, "fix1", new Fixture(cShape, 0, 0, false)); + + var bodyUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId)); + var body = entManager.AddComponent(bodyUid); + + physics.SetBodyType(bodyUid, BodyType.Dynamic, body: body); + physics.SetSleepingAllowed(bodyUid, body, false); + physics.SetFixedRotation(bodyUid, false, body: body); + + + // TODO: Box2D just deref, bleh shape structs someday + var shape1 = new PolygonShape(); + shape1.SetAsBox(0.5f, 10.0f, new Vector2(10.0f, 0.0f), 0.0f); + fixtures.CreateFixture(bodyUid, "fix1", new Fixture(shape1, 2, 0, true, 20f)); + + var shape2 = new PolygonShape(); + shape2.SetAsBox(0.5f, 10.0f, new Vector2(-10.0f, 0.0f), 0f); + fixtures.CreateFixture(bodyUid, "fix2", new Fixture(shape2, 2, 0, true, 20f)); + + var shape3 = new PolygonShape(); + shape3.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, 10.0f), 0f); + fixtures.CreateFixture(bodyUid, "fix3", new Fixture(shape3, 2, 0, true, 20f)); + + var shape4 = new PolygonShape(); + shape4.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, -10.0f), 0f); + fixtures.CreateFixture(bodyUid, "fix4", new Fixture(shape4, 2, 0, true, 20f)); + + physics.WakeBody(groundUid, body: ground); + physics.WakeBody(bodyUid, body: body); + var revolute = joints.CreateRevoluteJoint(groundUid, bodyUid); + revolute.LocalAnchorA = new Vector2(0f, 10f); + revolute.LocalAnchorB = new Vector2(0f, 0f); + revolute.ReferenceAngle = 0f; + revolute.MotorSpeed = 0.05f * MathF.PI; + revolute.MaxMotorTorque = 100000000f; + revolute.EnableMotor = true; + } +} diff --git a/Robust.Server/Console/Commands/TestbedCommand.cs b/Robust.Server/Console/Commands/TestbedCommand.cs deleted file mode 100644 index 37a3f8c44c3..00000000000 --- a/Robust.Server/Console/Commands/TestbedCommand.cs +++ /dev/null @@ -1,335 +0,0 @@ -// MIT License - -// Copyright (c) 2019 Erin Catto - -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - - -using System; -using System.Numerics; -using Robust.Server.Player; -using Robust.Shared.Console; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Map; -using Robust.Shared.Maths; -using Robust.Shared.Physics; -using Robust.Shared.Physics.Collision.Shapes; -using Robust.Shared.Physics.Components; -using Robust.Shared.Physics.Controllers; -using Robust.Shared.Physics.Dynamics; -using Robust.Shared.Physics.Dynamics.Joints; -using Robust.Shared.Physics.Systems; -using Robust.Shared.Timing; - -namespace Robust.Server.Console.Commands -{ - /* - * I didn't use blueprints because this is way easier to iterate upon as I can shit out testbed upon testbed on new maps - * and never have to leave my debugger. - */ - - /// - /// Copies of Box2D's physics testbed for debugging. - /// - public sealed class TestbedCommand : LocalizedCommands - { - [Dependency] private readonly IEntityManager _ent = default!; - [Dependency] private readonly IMapManager _map = default!; - - public override string Command => "testbed"; - - public override void Execute(IConsoleShell shell, string argStr, string[] args) - { - if (args.Length != 2) - { - shell.WriteError("Require 2 args for testbed!"); - return; - } - - if (!int.TryParse(args[0], out var mapInt)) - { - shell.WriteError($"Unable to parse map {args[0]}"); - return; - } - - var mapId = new MapId(mapInt); - if (!_map.MapExists(mapId)) - { - shell.WriteError($"map {args[0]} does not exist"); - return; - } - - if (shell.Player == null) - { - shell.WriteError("No player found"); - return; - } - - Action testbed; - SetupPlayer(mapId, shell); - - switch (args[1]) - { - case "boxstack": - testbed = () => CreateBoxStack(mapId); - break; - case "circlestack": - testbed = () => CreateCircleStack(mapId); - break; - case "pyramid": - testbed = () => CreatePyramid(mapId); - break; - case "tumbler": - testbed = () => CreateTumbler(mapId); - break; - default: - shell.WriteError($"testbed {args[0]} not found!"); - return; - } - - Timer.Spawn(1000, () => - { - if (!_map.MapExists(mapId)) return; - testbed(); - }); - - shell.WriteLine($"Testbed on map {mapId}"); - } - - private void SetupPlayer(MapId mapId, IConsoleShell shell) - { - _map.SetMapPaused(mapId, false); - var mapUid = _map.GetMapEntityIdOrThrow(mapId); - _ent.System().SetGravity(mapUid, new Vector2(0, -9.8f)); - - shell.ExecuteCommand("aghost"); - shell.ExecuteCommand($"tp 0 0 {mapId}"); - shell.RemoteExecuteCommand($"physics shapes"); - - return; - } - - private void CreateBoxStack(MapId mapId) - { - var physics = _ent.System(); - var fixtures = _ent.System(); - - var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); - var ground = _ent.AddComponent(groundUid); - - var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0)); - fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground); - - var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10)); - fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground); - - var xs = new[] - { - 0.0f, -10.0f, -5.0f, 5.0f, 10.0f - }; - - var columnCount = 1; - var rowCount = 15; - PolygonShape shape; - - for (var j = 0; j < columnCount; j++) - { - for (var i = 0; i < rowCount; i++) - { - var x = 0.0f; - - var boxUid = _ent.SpawnEntity(null, - new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 1.1f * i), mapId)); - var box = _ent.AddComponent(boxUid); - - physics.SetBodyType(boxUid, BodyType.Dynamic, body: box); - - shape = new PolygonShape(); - shape.SetAsBox(0.5f, 0.5f); - physics.SetFixedRotation(boxUid, false, body: box); - fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true), body: box); - - physics.WakeBody(boxUid, body: box); - } - } - - physics.WakeBody(groundUid, body: ground); - } - - private void CreateCircleStack(MapId mapId) - { - var physics = _ent.System(); - var fixtures = _ent.System(); - - var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); - var ground = _ent.AddComponent(groundUid); - - var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0)); - fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground); - - var vertical = new EdgeShape(new Vector2(20, 0), new Vector2(20, 20)); - fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground); - - var xs = new[] - { - 0.0f, -10.0f, -5.0f, 5.0f, 10.0f - }; - - var columnCount = 1; - var rowCount = 15; - PhysShapeCircle shape; - - for (var j = 0; j < columnCount; j++) - { - for (var i = 0; i < rowCount; i++) - { - var x = 0.0f; - - var boxUid = _ent.SpawnEntity(null, - new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 2.1f * i), mapId)); - var box = _ent.AddComponent(boxUid); - - physics.SetBodyType(boxUid, BodyType.Dynamic, body: box); - shape = new PhysShapeCircle(0.5f); - physics.SetFixedRotation(boxUid, false, body: box); - // TODO: Need to detect shape and work out if we need to use fixedrotation - - fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f)); - physics.WakeBody(boxUid, body: box); - } - } - - physics.WakeBody(groundUid, body: ground); - } - - private void CreatePyramid(MapId mapId) - { - const byte count = 20; - - // Setup ground - var physics = _ent.System(); - var fixtures = _ent.System(); - var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0, 0, mapId)); - var ground = _ent.AddComponent(groundUid); - - var horizontal = new EdgeShape(new Vector2(40, 0), new Vector2(-40, 0)); - fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground); - physics.WakeBody(groundUid, body: ground); - - // Setup boxes - float a = 0.5f; - PolygonShape shape = new(); - shape.SetAsBox(a, a); - - var x = new Vector2(-7.0f, 0.75f); - Vector2 y; - Vector2 deltaX = new Vector2(0.5625f, 1.25f); - Vector2 deltaY = new Vector2(1.125f, 0.0f); - - for (var i = 0; i < count; ++i) - { - y = x; - - for (var j = i; j < count; ++j) - { - var boxUid = _ent.SpawnEntity(null, new MapCoordinates(y, mapId)); - var box = _ent.AddComponent(boxUid); - physics.SetBodyType(boxUid, BodyType.Dynamic, body: box); - - fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f), body: box); - y += deltaY; - - physics.WakeBody(boxUid, body: box); - } - - x += deltaX; - } - } - - private void CreateTumbler(MapId mapId) - { - var physics = _ent.System(); - var fixtures = _ent.System(); - var joints = _ent.System(); - - var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0f, 0f, mapId)); - var ground = _ent.AddComponent(groundUid); - // Due to lookup changes fixtureless bodies are invalid, so - var cShape = new PhysShapeCircle(1f); - fixtures.CreateFixture(groundUid, "fix1", new Fixture(cShape, 0, 0, false)); - - var bodyUid = _ent.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId)); - var body = _ent.AddComponent(bodyUid); - - physics.SetBodyType(bodyUid, BodyType.Dynamic, body: body); - physics.SetSleepingAllowed(bodyUid, body, false); - physics.SetFixedRotation(bodyUid, false, body: body); - - - // TODO: Box2D just deref, bleh shape structs someday - var shape1 = new PolygonShape(); - shape1.SetAsBox(0.5f, 10.0f, new Vector2(10.0f, 0.0f), 0.0f); - fixtures.CreateFixture(bodyUid, "fix1", new Fixture(shape1, 2, 0, true, 20f)); - - var shape2 = new PolygonShape(); - shape2.SetAsBox(0.5f, 10.0f, new Vector2(-10.0f, 0.0f), 0f); - fixtures.CreateFixture(bodyUid, "fix2", new Fixture(shape2, 2, 0, true, 20f)); - - var shape3 = new PolygonShape(); - shape3.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, 10.0f), 0f); - fixtures.CreateFixture(bodyUid, "fix3", new Fixture(shape3, 2, 0, true, 20f)); - - var shape4 = new PolygonShape(); - shape4.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, -10.0f), 0f); - fixtures.CreateFixture(bodyUid, "fix4", new Fixture(shape4, 2, 0, true, 20f)); - - physics.WakeBody(groundUid, body: ground); - physics.WakeBody(bodyUid, body: body); - var revolute = joints.CreateRevoluteJoint(groundUid, bodyUid); - revolute.LocalAnchorA = new Vector2(0f, 10f); - revolute.LocalAnchorB = new Vector2(0f, 0f); - revolute.ReferenceAngle = 0f; - revolute.MotorSpeed = 0.05f * MathF.PI; - revolute.MaxMotorTorque = 100000000f; - revolute.EnableMotor = true; - - // Box2D has this as 800 which is jesus christo. - // Wouldn't recommend higher than 100 in debug and higher than 300 on release unless - // you really want a profile. - var count = 300; - - for (var i = 0; i < count; i++) - { - Timer.Spawn(i * 20, () => - { - if (!_map.MapExists(mapId)) return; - var boxUid = _ent.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId)); - var box = _ent.AddComponent(boxUid); - physics.SetBodyType(boxUid, BodyType.Dynamic, body: box); - physics.SetFixedRotation(boxUid, false, body: box); - var shape = new PolygonShape(); - shape.SetAsBox(0.125f, 0.125f); - fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 0.0625f), body: box); - physics.WakeBody(boxUid, body: box); - }); - } - } - } -} From 2f73f6190d13666185f4e9d97a27cf9027056c67 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 2 Sep 2024 22:46:00 +0200 Subject: [PATCH 104/140] Fix warning in ScriptGlobalsShared.cs --- Robust.Shared.Scripting/ScriptGlobalsShared.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Robust.Shared.Scripting/ScriptGlobalsShared.cs b/Robust.Shared.Scripting/ScriptGlobalsShared.cs index c494f0d2ab2..5f877f1f189 100644 --- a/Robust.Shared.Scripting/ScriptGlobalsShared.cs +++ b/Robust.Shared.Scripting/ScriptGlobalsShared.cs @@ -206,8 +206,11 @@ public void Del(EntityUid uid) public void Dirty(EntityUid uid) => ent.DirtyEntity(uid); +#pragma warning disable CS0618 // Type or member is obsolete + // Remove this helper when component.Owner finally gets removed. public void Dirty(Component comp) => ent.Dirty(comp.Owner, comp); +#pragma warning restore CS0618 // Type or member is obsolete public string Name(EntityUid uid) => ent.GetComponent(uid).EntityName; From be9db264dd51fda3243bdd7c8fbe02fbe346a156 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 6 Sep 2024 12:48:50 +1200 Subject: [PATCH 105/140] Minor toolshed fixes / tweaks (#5315) * Don't use markup for type names * Cache TypeTypeParser completions * Cache all type parsers * Release notes * More IConError fixes * a --- RELEASE-NOTES.md | 1 + .../Errors/NotForServerConsoleError.cs | 2 +- .../Errors/SessionHasNoEntityError.cs | 2 +- .../Toolshed/Syntax/ParserContext.cs | 2 +- Robust.Shared/Toolshed/Syntax/ValueRef.cs | 3 +-- .../Toolshed/ToolshedManager.Parsing.cs | 12 +++++++--- .../Toolshed/TypeParsers/StringTypeParser.cs | 2 +- .../Toolshed/TypeParsers/TypeTypeParser.cs | 23 +++++++------------ 8 files changed, 23 insertions(+), 24 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 7e477bed387..49713b58c42 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -46,6 +46,7 @@ END TEMPLATE--> ### Bugfixes * Fixed equality checks for `MarkupNode` not properly handling attributes. +* Fixed toolshed commands failing to generate error messages when working with array types * Fixed `MarkupNode` not having a `GetHashCode()` implementation. ### Other diff --git a/Robust.Shared/Toolshed/Errors/NotForServerConsoleError.cs b/Robust.Shared/Toolshed/Errors/NotForServerConsoleError.cs index cdd8ddff140..77ef43fb06b 100644 --- a/Robust.Shared/Toolshed/Errors/NotForServerConsoleError.cs +++ b/Robust.Shared/Toolshed/Errors/NotForServerConsoleError.cs @@ -8,7 +8,7 @@ public sealed class NotForServerConsoleError : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkupOrThrow( + return FormattedMessage.FromUnformatted( "You must be logged in with a client to use this, the server console isn't workable."); } diff --git a/Robust.Shared/Toolshed/Errors/SessionHasNoEntityError.cs b/Robust.Shared/Toolshed/Errors/SessionHasNoEntityError.cs index 3fddf8c78ed..d835a5bc3cd 100644 --- a/Robust.Shared/Toolshed/Errors/SessionHasNoEntityError.cs +++ b/Robust.Shared/Toolshed/Errors/SessionHasNoEntityError.cs @@ -9,7 +9,7 @@ public record SessionHasNoEntityError(ICommonSession Session) : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkupOrThrow($"The user {Session.Name} has no attached entity."); + return FormattedMessage.FromUnformatted($"The user {Session.Name} has no attached entity."); } public string? Expression { get; set; } diff --git a/Robust.Shared/Toolshed/Syntax/ParserContext.cs b/Robust.Shared/Toolshed/Syntax/ParserContext.cs index 1b684898db2..bf2a4308e0d 100644 --- a/Robust.Shared/Toolshed/Syntax/ParserContext.cs +++ b/Robust.Shared/Toolshed/Syntax/ParserContext.cs @@ -352,7 +352,7 @@ public record OutOfInputError : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkupOrThrow("Ran out of input data when data was expected."); + return FormattedMessage.FromUnformatted("Ran out of input data when data was expected."); } public string? Expression { get; set; } diff --git a/Robust.Shared/Toolshed/Syntax/ValueRef.cs b/Robust.Shared/Toolshed/Syntax/ValueRef.cs index cb0d5042d81..2716cb60da1 100644 --- a/Robust.Shared/Toolshed/Syntax/ValueRef.cs +++ b/Robust.Shared/Toolshed/Syntax/ValueRef.cs @@ -103,8 +103,7 @@ public record BadVarTypeError(Type Got, Type Expected, string VarName) : IConErr { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkupOrThrow( - $"Got unexpected type {Got.PrettyName()} in {VarName}, expected {Expected.PrettyName()}"); + return FormattedMessage.FromUnformatted($"Got unexpected type {Got.PrettyName()} in {VarName}, expected {Expected.PrettyName()}"); } public string? Expression { get; set; } diff --git a/Robust.Shared/Toolshed/ToolshedManager.Parsing.cs b/Robust.Shared/Toolshed/ToolshedManager.Parsing.cs index b346c99a8f9..8171749b5c7 100644 --- a/Robust.Shared/Toolshed/ToolshedManager.Parsing.cs +++ b/Robust.Shared/Toolshed/ToolshedManager.Parsing.cs @@ -16,7 +16,7 @@ namespace Robust.Shared.Toolshed; public sealed partial class ToolshedManager { - private readonly Dictionary _consoleTypeParsers = new(); + private readonly Dictionary _consoleTypeParsers = new(); private readonly Dictionary _genericTypeParsers = new(); private readonly List<(Type, Type)> _constrainedParsers = new(); @@ -55,6 +55,14 @@ private void InitializeParser() if (_consoleTypeParsers.TryGetValue(t, out var parser)) return parser; + parser = FindParserForType(t); + _consoleTypeParsers.TryAdd(t, parser); + return parser; + + } + + private ITypeParser? FindParserForType(Type t) + { if (t.IsConstructedGenericType) { if (_genericTypeParsers.TryGetValue(t.GetGenericTypeDefinition(), out var genParser)) @@ -65,7 +73,6 @@ private void InitializeParser() var builtParser = (ITypeParser) _typeFactory.CreateInstanceUnchecked(concreteParser, true); builtParser.PostInject(); - _consoleTypeParsers.Add(builtParser.Parses, builtParser); return builtParser; } catch (SecurityException) @@ -86,7 +93,6 @@ private void InitializeParser() var builtParser = (ITypeParser) _typeFactory.CreateInstanceUnchecked(concreteParser, true); builtParser.PostInject(); - _consoleTypeParsers.Add(builtParser.Parses, builtParser); return builtParser; } catch (SecurityException) diff --git a/Robust.Shared/Toolshed/TypeParsers/StringTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/StringTypeParser.cs index f4d4c3b7a3e..7c901803e7c 100644 --- a/Robust.Shared/Toolshed/TypeParsers/StringTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/StringTypeParser.cs @@ -93,7 +93,7 @@ public record struct StringMustStartWithQuote : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkupOrThrow("A string must start with a quote."); + return FormattedMessage.FromUnformatted("A string must start with a quote."); } public string? Expression { get; set; } diff --git a/Robust.Shared/Toolshed/TypeParsers/TypeTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/TypeTypeParser.cs index e8803fe7c54..243301abf44 100644 --- a/Robust.Shared/Toolshed/TypeParsers/TypeTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/TypeTypeParser.cs @@ -10,7 +10,6 @@ using Robust.Shared.Console; using Robust.Shared.ContentPack; using Robust.Shared.IoC; -using Robust.Shared.Log; using Robust.Shared.Maths; using Robust.Shared.Toolshed.Errors; using Robust.Shared.Toolshed.Syntax; @@ -56,6 +55,7 @@ internal sealed class TypeTypeParser : TypeParser }; private readonly HashSet _ambiguousTypes = new(); + private CompletionResult? _optionsCache; public override void PostInject() { @@ -75,6 +75,8 @@ public override void PostInject() } } } + + _optionsCache = CompletionResult.FromHintOptions(Types.Select(x => new CompletionOption(x.Key)), "C# level type"); } public override bool TryParse(ParserContext parserContext, [NotNullWhen(true)] out object? result, out IConError? error) @@ -168,8 +170,7 @@ public override bool TryParse(ParserContext parserContext, [NotNullWhen(true)] o string? argName) { // TODO: Suggest generics. - var options = Types.Select(x => new CompletionOption(x.Key)); - return ValueTask.FromResult<(CompletionResult? result, IConError? error)>((CompletionResult.FromHintOptions(options, "C# level type"), null)); + return ValueTask.FromResult<(CompletionResult? result, IConError? error)>((_optionsCache, null)); } } @@ -177,9 +178,7 @@ public record struct ExpectedNextType() : IConError { public FormattedMessage DescribeInner() { - var msg = new FormattedMessage(); - msg.AddText($"Expected another type in the generic arguments."); - return msg; + return FormattedMessage.FromUnformatted("Expected another type in the generic arguments."); } public string? Expression { get; set; } @@ -191,9 +190,7 @@ public record struct ExpectedGeneric() : IConError { public FormattedMessage DescribeInner() { - var msg = new FormattedMessage(); - msg.AddText($"Expected a generic type, did you forget the angle brackets?"); - return msg; + return FormattedMessage.FromUnformatted("Expected a generic type, did you forget the angle brackets?"); } public string? Expression { get; set; } @@ -205,9 +202,7 @@ public record struct UnknownType(string T) : IConError { public FormattedMessage DescribeInner() { - var msg = new FormattedMessage(); - msg.AddText($"The type {T} is not known and cannot be used."); - return msg; + return FormattedMessage.FromUnformatted($"The type {T} is not known and cannot be used."); } public string? Expression { get; set; } @@ -220,9 +215,7 @@ internal record struct TypeIsSandboxViolation(Type T) : IConError { public FormattedMessage DescribeInner() { - var msg = new FormattedMessage(); - msg.AddText($"The type {T.PrettyName()} is not permitted under sandbox rules."); - return msg; + return FormattedMessage.FromUnformatted($"The type {T.PrettyName()} is not permitted under sandbox rules."); } public string? Expression { get; set; } From 405ed378c09a29d6c7ba7a72264556b422b4505f Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 6 Sep 2024 22:51:34 +1200 Subject: [PATCH 106/140] Re-attempt `FlushEntities()` on failure (#5423) --- RELEASE-NOTES.md | 2 +- .../GameStates/PvsSystem.DataStorage.cs | 3 ++- Robust.Shared/GameObjects/EntityManager.cs | 24 +++++++++++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 49713b58c42..1834bc39166 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -51,7 +51,7 @@ END TEMPLATE--> ### Other -*None yet* +* If `EntityManager.FlushEntities()` fails to delete all entities, it will now attempt to do so a second time before throwing an exception. ### Internal diff --git a/Robust.Server/GameStates/PvsSystem.DataStorage.cs b/Robust.Server/GameStates/PvsSystem.DataStorage.cs index f34f747d5b9..b9927938b68 100644 --- a/Robust.Server/GameStates/PvsSystem.DataStorage.cs +++ b/Robust.Server/GameStates/PvsSystem.DataStorage.cs @@ -300,7 +300,8 @@ private void OnEntityDeleted(Entity entity) /// private void AfterEntityFlush() { - DebugTools.Assert(EntityManager.EntityCount == 0); + if (EntityManager.EntityCount > 0) + throw new Exception("Cannot reset PVS data without first deleting all entities."); ClearPvsData(); ShrinkDataMemory(); diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index 7ba519b7eae..b3abee1daac 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -693,6 +693,25 @@ public bool Deleted([NotNullWhen(false)] EntityUid? uid) public virtual void FlushEntities() { BeforeEntityFlush?.Invoke(); + FlushEntitiesInternal(); + + if (Entities.Count != 0) + _sawmill.Error("Failed to flush all entities"); + +#if EXCEPTION_TOLERANCE + // Attempt to flush entities a second time, just in case something somehow caused an entity to be spawned + // while flushing entities + FlushEntitiesInternal(); +#endif + + if (Entities.Count != 0) + throw new Exception("Failed to flush all entities"); + + AfterEntityFlush?.Invoke(); + } + + private void FlushEntitiesInternal() + { QueuedDeletions.Clear(); QueuedDeletionsSet.Clear(); @@ -738,11 +757,6 @@ public virtual void FlushEntities() #endif } } - - if (Entities.Count != 0) - _sawmill.Error("Entities were spawned while flushing entities."); - - AfterEntityFlush?.Invoke(); } /// From 5eb5ddd96e6b7af3087df46ee08ae76e321e1cc0 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 8 Sep 2024 17:22:48 +1000 Subject: [PATCH 107/140] Add some entitylookup methods (#5431) --- .../GameObjects/Systems/EntityLookup.Queries.cs | 13 +++++++++++++ .../EntityLookupSystem.ComponentQueries.cs | 16 ++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs index f332a94c54c..5dde1eaed7c 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs @@ -703,6 +703,19 @@ public HashSet GetEntitiesInRange(MapId mapId, Vector2 worldPos, floa return entities; } + public void GetEntitiesIntersecting( + MapId mapId, + IPhysShape shape, + Transform transform, + HashSet entities, + LookupFlags flags = LookupFlags.All) + { + if (mapId == MapId.Nullspace) + return; + + AddEntitiesIntersecting(mapId, entities, shape, transform, flags: flags); + } + public void GetEntitiesInRange(MapId mapId, Vector2 worldPos, float range, HashSet entities, LookupFlags flags = DefaultFlags) { DebugTools.Assert(range > 0, "Range must be a positive float"); diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs index 6b001b024e3..35e7887dc1e 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs @@ -784,6 +784,22 @@ public void GetLocalEntitiesIntersecting( #endregion + /// + /// Gets entities with the specified component with the specified grid. + /// + public void GetGridEntities(EntityUid gridUid, HashSet> entities) where TComp1 : IComponent + { + var query = AllEntityQuery(); + + while (query.MoveNext(out var uid, out var comp, out var xform)) + { + if (xform.GridUid != gridUid) + continue; + + entities.Add((uid, comp)); + } + } + /// /// Gets entities with the specified component with the specified parent. /// From dbc4e80e6186dd71f7b3f0cbde72606c2e986a75 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 8 Sep 2024 17:55:56 +1000 Subject: [PATCH 108/140] Version: 233.1.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 61514327453..9c5b9a74d07 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 233.0.2 + 233.1.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 1834bc39166..47e734429f9 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,6 +39,26 @@ END TEMPLATE--> ### New features +*None yet* + +### Bugfixes + +*None yet* + +### Other + +*None yet* + +### Internal + +*None yet* + + +## 233.1.0 + +### New features + +* Add GetGridEntities and another GetEntitiesIntersecting overload to EntityLookupSystem. * `MarkupNode` is now `IEquatable`. It already supported equality checks, now it implements the interface. * Added `Entity` overloads to the following `SharedMapSystem` methods: `GetTileRef`, `GetAnchoredEntities`, `TileIndicesFor`. * Added `EntityUid`-only overloads to the following `SharedTransformSystem` methods: `AnchorEntity`, `Unanchor`. @@ -53,10 +73,6 @@ END TEMPLATE--> * If `EntityManager.FlushEntities()` fails to delete all entities, it will now attempt to do so a second time before throwing an exception. -### Internal - -*None yet* - ## 233.0.2 From 814e5bcf17db12cabaa7a0311d4dbc10cc569fa8 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 9 Sep 2024 08:23:12 +0200 Subject: [PATCH 109/140] Mark large replays as requiring Server GC. This should significantly improve loading performance of large replays. System can be controlled by replay.server_gc_size_threshold, which defaults to 50 MiB. This is the engine-side of https://github.com/space-wizards/SS14.Launcher/issues/177 --- Robust.Shared/CVars.cs | 10 ++++++++++ .../Replays/SharedReplayRecordingManager.cs | 13 ++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index c781d1eabee..6404bd1bfb4 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -1636,6 +1636,16 @@ protected CVars() public static readonly CVarDef ReplayMaxUncompressedSize = CVarDef.Create("replay.max_uncompressed_size", 1024L * 1024, CVar.ARCHIVE); + /// + /// Size of the replay (in kilobytes) at which point the replay is considered "large", + /// and replay clients should enable server GC (if possible) to improve performance. + /// + /// + /// Set to -1 to never make replays use server GC. + /// + public static readonly CVarDef ReplayServerGCSizeThreshold = + CVarDef.Create("replay.server_gc_size_threshold", 50L * 1024); + /// /// Uncompressed size of individual files created by the replay (in kilobytes), where each file contains data /// for one or more ticks. Actual files may be slightly larger, this is just a threshold for the file to get diff --git a/Robust.Shared/Replays/SharedReplayRecordingManager.cs b/Robust.Shared/Replays/SharedReplayRecordingManager.cs index b5d842d53b4..656e9ff83b5 100644 --- a/Robust.Shared/Replays/SharedReplayRecordingManager.cs +++ b/Robust.Shared/Replays/SharedReplayRecordingManager.cs @@ -58,6 +58,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM // Config variables. private long _maxCompressedSize; private long _maxUncompressedSize; + private long _serverGCSizeThreshold; private int _tickBatchSize; private bool _enabled; @@ -71,6 +72,7 @@ public virtual void Initialize() NetConf.OnValueChanged(CVars.ReplayMaxCompressedSize, (v) => _maxCompressedSize = SaturatingMultiplyKb(v), true); NetConf.OnValueChanged(CVars.ReplayMaxUncompressedSize, (v) => _maxUncompressedSize = SaturatingMultiplyKb(v), true); + NetConf.OnValueChanged(CVars.ReplayServerGCSizeThreshold, (v) => _serverGCSizeThreshold = SaturatingMultiplyKb(v), true); NetConf.OnValueChanged(CVars.ReplayTickBatchSize, (v) => _tickBatchSize = Math.Min(v, MaxTickBatchSize) * 1024, true); NetConf.OnValueChanged(CVars.NetPvsCompressLevel, OnCompressionChanged); } @@ -238,7 +240,6 @@ public virtual bool TryStartRecording( try { - WriteContentBundleInfo(_recState); WriteInitialMetadata(name, _recState); } catch @@ -391,6 +392,7 @@ private void WriteFinalMetadata(RecordingState recState) // this just overwrites the previous yml with additional data. var document = new YamlDocument(yamlMetadata.ToYaml()); WriteYaml(recState, ReplayZipFolder / FileMetaFinal, document); + WriteContentBundleInfo(recState); UpdateWriteTasks(); Reset(); @@ -412,6 +414,7 @@ private void WriteContentBundleInfo(RecordingState recState) var document = new JsonObject { + ["server_gc"] = ShouldEnableServerGC(recState), ["engine_version"] = info.EngineVersion, ["base_build"] = new JsonObject { @@ -429,6 +432,14 @@ private void WriteContentBundleInfo(RecordingState recState) WriteBytes(recState, new ResPath("rt_content_bundle.json"), bytes); } + private bool ShouldEnableServerGC(RecordingState recState) + { + if (_serverGCSizeThreshold < 0) + return false; + + return recState.CompressedSize >= _serverGCSizeThreshold; + } + /// /// Get information describing the server build. /// This will be embedded in replay content bundles to allow the launcher to directly load them. From f682fb9cc76e74876121ace2b988a7f3ba307b84 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 9 Sep 2024 11:22:07 +0200 Subject: [PATCH 110/140] Obsolete some useless type proxies on IResourceCache These aren't even used, but they're pretty objectively bad ideas so let's obsolete them so we can get rid of them later. --- Robust.Client/ResourceManagement/IResourceCache.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Robust.Client/ResourceManagement/IResourceCache.cs b/Robust.Client/ResourceManagement/IResourceCache.cs index 113d7e74d82..dd13909bbec 100644 --- a/Robust.Client/ResourceManagement/IResourceCache.cs +++ b/Robust.Client/ResourceManagement/IResourceCache.cs @@ -48,7 +48,10 @@ T GetFallback() event Action OnRawTextureLoaded; event Action OnRsiLoaded; + [Obsolete("Fetch these through IoC directly instead")] IClyde Clyde { get; } + + [Obsolete("Fetch these through IoC directly instead")] IFontManager FontManager { get; } } From 48d70a09c61daf35fbfcf5e92ec6337975d1c952 Mon Sep 17 00:00:00 2001 From: Kara Date: Wed, 11 Sep 2024 02:38:26 -0700 Subject: [PATCH 111/140] Remove most fully-obsoleted code (#5433) --- Robust.Client/Audio/Midi/IMidiRenderer.cs | 6 -- Robust.Client/Audio/Midi/MidiRenderer.cs | 8 --- Robust.Client/Credits/CreditsManager.cs | 9 --- .../EntitySystems/AnimationPlayerSystem.cs | 9 --- .../GameStates/IClientGameStateManager.cs | 3 - .../CustomControls/SS14Window.cs | 10 --- Robust.Server/GameStates/PvsOverrideSystem.cs | 7 -- .../ServerStatus/IStatusHandlerContext.cs | 32 --------- Robust.Server/ServerStatus/IStatusHost.cs | 13 ---- .../ServerStatus/StatusHost.Acz.Sources.cs | 16 ----- Robust.Server/ServerStatus/StatusHost.cs | 62 ----------------- Robust.Shared/Audio/SoundSpecifier.cs | 20 ------ .../Containers/ContainerManagerComponent.cs | 27 -------- .../Containers/SharedContainerSystem.cs | 10 --- .../Appearance/AppearanceComponent.cs | 13 ---- .../Components/Timers/TimerExtensions.cs | 52 -------------- .../Transform/TransformComponent.cs | 38 ----------- .../GameObjects/EntityManager.Components.cs | 18 ----- .../GameObjects/EntitySystem.Proxy.cs | 10 --- .../GameObjects/IEntityManager.Components.cs | 13 ---- .../EntityLookupSystem.ComponentQueries.cs | 14 ---- .../SharedTransformSystem.Component.cs | 7 -- Robust.Shared/Log/Logger.cs | 56 --------------- .../Map/Components/MapGridComponent.cs | 68 ------------------- Robust.Shared/Map/EntityCoordinates.cs | 31 --------- Robust.Shared/Map/IMapManager.cs | 9 --- .../Map/MapManager.GridCollection.cs | 23 ------- .../Systems/SharedPhysicsSystem.Components.cs | 36 ---------- .../Systems/SharedPhysicsSystem.Queries.cs | 7 -- Robust.Shared/Player/Filter.cs | 19 ------ Robust.Shared/Player/ICommonSession.cs | 5 +- Robust.Shared/Player/ISharedPlayerManager.cs | 3 - Robust.Shared/Prototypes/IPrototypeManager.cs | 63 ----------------- Robust.Shared/Prototypes/PrototypeManager.cs | 32 --------- Robust.UnitTesting/RobustIntegrationTest.cs | 2 - 35 files changed, 1 insertion(+), 750 deletions(-) delete mode 100644 Robust.Client/UserInterface/CustomControls/SS14Window.cs diff --git a/Robust.Client/Audio/Midi/IMidiRenderer.cs b/Robust.Client/Audio/Midi/IMidiRenderer.cs index 216fe2c09d2..483694faa7c 100644 --- a/Robust.Client/Audio/Midi/IMidiRenderer.cs +++ b/Robust.Client/Audio/Midi/IMidiRenderer.cs @@ -33,12 +33,6 @@ public interface IMidiRenderer : IDisposable /// bool LoopMidi { get; set; } - /// - /// This increases all note on velocities to 127. - /// - [Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")] - bool VolumeBoost { get; set; } - /// /// The midi program (instrument) the renderer is using. /// diff --git a/Robust.Client/Audio/Midi/MidiRenderer.cs b/Robust.Client/Audio/Midi/MidiRenderer.cs index 98425503a0e..bed5537c38d 100644 --- a/Robust.Client/Audio/Midi/MidiRenderer.cs +++ b/Robust.Client/Audio/Midi/MidiRenderer.cs @@ -205,14 +205,6 @@ public bool LoopMidi } } - [ViewVariables(VVAccess.ReadWrite)] - [Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")] - public bool VolumeBoost - { - get => VelocityOverride == 127; - set => VelocityOverride = value ? 127 : null; - } - [ViewVariables(VVAccess.ReadWrite)] public EntityUid? TrackingEntity { get; set; } = null; diff --git a/Robust.Client/Credits/CreditsManager.cs b/Robust.Client/Credits/CreditsManager.cs index c247041294e..6acc0df488c 100644 --- a/Robust.Client/Credits/CreditsManager.cs +++ b/Robust.Client/Credits/CreditsManager.cs @@ -14,15 +14,6 @@ namespace Robust.Client.Credits /// public static class CreditsManager { - /// - /// Gets a list of open source software used in the engine and their license. - /// - [Obsolete("Use overload that takes in an explicit resource manager instead.")] - public static IEnumerable GetLicenses() - { - return GetLicenses(IoCManager.Resolve()); - } - /// /// Gets a list of open source software used in the engine and their license. /// diff --git a/Robust.Client/GameObjects/EntitySystems/AnimationPlayerSystem.cs b/Robust.Client/GameObjects/EntitySystems/AnimationPlayerSystem.cs index ed760390419..8d3323d6f4a 100644 --- a/Robust.Client/GameObjects/EntitySystems/AnimationPlayerSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/AnimationPlayerSystem.cs @@ -99,15 +99,6 @@ public void Play(EntityUid uid, AnimationPlayerComponent? component, Animation a Play(new Entity(uid, component), animation, key); } - /// - /// Start playing an animation. - /// - [Obsolete("Use Play(EntityUid ent, Animation animation, string key) instead")] - public void Play(AnimationPlayerComponent component, Animation animation, string key) - { - Play(new Entity(component.Owner, component), animation, key); - } - public void Play(Entity ent, Animation animation, string key) { AddComponent(ent); diff --git a/Robust.Client/GameStates/IClientGameStateManager.cs b/Robust.Client/GameStates/IClientGameStateManager.cs index 45fa9fd6f54..fc50155b693 100644 --- a/Robust.Client/GameStates/IClientGameStateManager.cs +++ b/Robust.Client/GameStates/IClientGameStateManager.cs @@ -34,9 +34,6 @@ public interface IClientGameStateManager /// int GetApplicableStateCount(); - [Obsolete("use GetApplicableStateCount()")] - int CurrentBufferSize => GetApplicableStateCount(); - /// /// Total number of game states currently in the state buffer. /// diff --git a/Robust.Client/UserInterface/CustomControls/SS14Window.cs b/Robust.Client/UserInterface/CustomControls/SS14Window.cs deleted file mode 100644 index f5483cfbf18..00000000000 --- a/Robust.Client/UserInterface/CustomControls/SS14Window.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Robust.Client.UserInterface.CustomControls; - -[Obsolete("Use DefaultWindow instead")] -[Virtual] -public class SS14Window : DefaultWindow -{ - -} diff --git a/Robust.Server/GameStates/PvsOverrideSystem.cs b/Robust.Server/GameStates/PvsOverrideSystem.cs index 5f3a468232b..bef471af085 100644 --- a/Robust.Server/GameStates/PvsOverrideSystem.cs +++ b/Robust.Server/GameStates/PvsOverrideSystem.cs @@ -250,13 +250,6 @@ public void AddSessionOverride(NetEntity entity, ICommonSession session, bool re AddSessionOverride(uid.Value, session); } - [Obsolete("Use variant that takes in an EntityUid")] - public void AddSessionOverrides(NetEntity entity, Filter filter, bool removeExistingOverride = true) - { - if (TryGetEntity(entity, out var uid)) - AddSessionOverrides(uid.Value, filter); - } - [Obsolete("Don't use this, clear specific overrides")] public void ClearOverride(NetEntity entity) { diff --git a/Robust.Server/ServerStatus/IStatusHandlerContext.cs b/Robust.Server/ServerStatus/IStatusHandlerContext.cs index 2c212ac64bf..740fb14701a 100644 --- a/Robust.Server/ServerStatus/IStatusHandlerContext.cs +++ b/Robust.Server/ServerStatus/IStatusHandlerContext.cs @@ -24,34 +24,8 @@ public interface IStatusHandlerContext IDictionary ResponseHeaders { get; } bool KeepAlive { get; set; } - [Obsolete("Use async versions instead")] - T? RequestBodyJson(); Task RequestBodyJsonAsync(); - [Obsolete("Use async versions instead")] - void Respond( - string text, - HttpStatusCode code = HttpStatusCode.OK, - string contentType = "text/plain"); - - [Obsolete("Use async versions instead")] - void Respond( - string text, - int code = 200, - string contentType = "text/plain"); - - [Obsolete("Use async versions instead")] - void Respond( - byte[] data, - HttpStatusCode code = HttpStatusCode.OK, - string contentType = "text/plain"); - - [Obsolete("Use async versions instead")] - void Respond( - byte[] data, - int code = 200, - string contentType = "text/plain"); - Task RespondNoContentAsync(); Task RespondAsync( @@ -74,14 +48,8 @@ Task RespondAsync( int code = 200, string contentType = "text/plain"); - [Obsolete("Use async versions instead")] - void RespondError(HttpStatusCode code); - Task RespondErrorAsync(HttpStatusCode code); - [Obsolete("Use async versions instead")] - void RespondJson(object jsonData, HttpStatusCode code = HttpStatusCode.OK); - Task RespondJsonAsync(object jsonData, HttpStatusCode code = HttpStatusCode.OK); Task RespondStreamAsync(HttpStatusCode code = HttpStatusCode.OK); diff --git a/Robust.Server/ServerStatus/IStatusHost.cs b/Robust.Server/ServerStatus/IStatusHost.cs index 8add077c034..d76ce80b0ec 100644 --- a/Robust.Server/ServerStatus/IStatusHost.cs +++ b/Robust.Server/ServerStatus/IStatusHost.cs @@ -27,19 +27,6 @@ public interface IStatusHost /// event Action OnInfoRequest; - /// - /// Set information used by automatic-client-zipping to determine the layout of your dev setup, - /// and which assembly files to send. - /// - /// - /// The name of your client project in the bin/ folder on the top of your project. - /// - /// - /// The list of client assemblies to send from the aforementioned folder. - /// - [Obsolete("This API is deprecated as it cannot share information with standalone packaging. Use SetMagicAczProvider instead")] - void SetAczInfo(string clientBinFolder, string[] clientAssemblyNames); - void SetMagicAczProvider(IMagicAczProvider provider); /// diff --git a/Robust.Server/ServerStatus/StatusHost.Acz.Sources.cs b/Robust.Server/ServerStatus/StatusHost.Acz.Sources.cs index b806e58416d..c181159c98b 100644 --- a/Robust.Server/ServerStatus/StatusHost.Acz.Sources.cs +++ b/Robust.Server/ServerStatus/StatusHost.Acz.Sources.cs @@ -171,22 +171,6 @@ private async Task SourceAczViaMagic(AssetPass pass, IPackageLogger logger // -- Information Input -- - public void SetAczInfo(string clientBinFolder, string[] clientAssemblyNames) - { - _aczLock.Wait(); - try - { - if (_aczPrepared != null) - throw new InvalidOperationException("ACZ already prepared"); - - _aczInfo = (clientBinFolder, clientAssemblyNames); - } - finally - { - _aczLock.Release(); - } - } - public void SetMagicAczProvider(IMagicAczProvider provider) { _magicAczProvider = provider; diff --git a/Robust.Server/ServerStatus/StatusHost.cs b/Robust.Server/ServerStatus/StatusHost.cs index 062c1dd8c07..1f74a6cf9b8 100644 --- a/Robust.Server/ServerStatus/StatusHost.cs +++ b/Robust.Server/ServerStatus/StatusHost.cs @@ -269,57 +269,11 @@ public ContextImpl(HttpListenerContext context) _responseHeaders = new Dictionary(); } - public T? RequestBodyJson() - { - return JsonSerializer.Deserialize(RequestBody); - } - public async Task RequestBodyJsonAsync() { return await JsonSerializer.DeserializeAsync(RequestBody); } - public void Respond(string text, HttpStatusCode code = HttpStatusCode.OK, string contentType = MediaTypeNames.Text.Plain) - { - Respond(text, (int)code, contentType); - } - - public void Respond(string text, int code = 200, string contentType = MediaTypeNames.Text.Plain) - { - _context.Response.StatusCode = code; - _context.Response.ContentType = contentType; - - if (RequestMethod == HttpMethod.Head) - { - return; - } - - using var writer = new StreamWriter(_context.Response.OutputStream, EncodingHelpers.UTF8); - - writer.Write(text); - } - - public void Respond(byte[] data, HttpStatusCode code = HttpStatusCode.OK, string contentType = MediaTypeNames.Text.Plain) - { - Respond(data, (int)code, contentType); - } - - public void Respond(byte[] data, int code = 200, string contentType = MediaTypeNames.Text.Plain) - { - _context.Response.StatusCode = code; - _context.Response.ContentType = contentType; - _context.Response.ContentLength64 = data.Length; - - if (RequestMethod == HttpMethod.Head) - { - _context.Response.Close(); - return; - } - - _context.Response.OutputStream.Write(data); - _context.Response.Close(); - } - public Task RespondNoContentAsync() { RespondShared(); @@ -373,27 +327,11 @@ public async Task RespondAsync(byte[] data, int code = 200, string contentType = _context.Response.Close(); } - public void RespondError(HttpStatusCode code) - { - Respond(code.ToString(), code); - } - public Task RespondErrorAsync(HttpStatusCode code) { return RespondAsync(code.ToString(), code); } - public void RespondJson(object jsonData, HttpStatusCode code = HttpStatusCode.OK) - { - RespondShared(); - - _context.Response.ContentType = "application/json"; - - JsonSerializer.Serialize(_context.Response.OutputStream, jsonData); - - _context.Response.Close(); - } - public async Task RespondJsonAsync(object jsonData, HttpStatusCode code = HttpStatusCode.OK) { RespondShared(); diff --git a/Robust.Shared/Audio/SoundSpecifier.cs b/Robust.Shared/Audio/SoundSpecifier.cs index 37137305486..f88254edc23 100644 --- a/Robust.Shared/Audio/SoundSpecifier.cs +++ b/Robust.Shared/Audio/SoundSpecifier.cs @@ -17,9 +17,6 @@ public abstract partial class SoundSpecifier { [DataField("params")] public AudioParams Params { get; set; } = AudioParams.Default; - - [Obsolete("Use SharedAudioSystem.GetSound(), or just pass sound specifier directly into SharedAudioSystem.")] - public abstract string GetSound(IRobustRandom? rand = null, IPrototypeManager? proto = null); } [Serializable, NetSerializable] @@ -45,12 +42,6 @@ public SoundPathSpecifier(ResPath path, AudioParams? @params = null) if (@params.HasValue) Params = @params.Value; } - - [Obsolete("Use SharedAudioSystem.GetSound(), or just pass sound specifier directly into SharedAudioSystem.")] - public override string GetSound(IRobustRandom? rand = null, IPrototypeManager? proto = null) - { - return Path.ToString(); - } } [Serializable, NetSerializable] @@ -70,15 +61,4 @@ public SoundCollectionSpecifier(string collection, AudioParams? @params = null) if (@params.HasValue) Params = @params.Value; } - - [Obsolete("Use SharedAudioSystem.GetSound(), or just pass sound specifier directly into SharedAudioSystem.")] - public override string GetSound(IRobustRandom? rand = null, IPrototypeManager? proto = null) - { - if (Collection == null) - return string.Empty; - - IoCManager.Resolve(ref rand, ref proto); - var soundCollection = proto.Index(Collection); - return rand.Pick(soundCollection.PickFiles).ToString(); - } } diff --git a/Robust.Shared/Containers/ContainerManagerComponent.cs b/Robust.Shared/Containers/ContainerManagerComponent.cs index e86e266a42b..9ce385dd1a6 100644 --- a/Robust.Shared/Containers/ContainerManagerComponent.cs +++ b/Robust.Shared/Containers/ContainerManagerComponent.cs @@ -33,19 +33,6 @@ void ISerializationHooks.AfterDeserialization() } } - [Obsolete] - public T MakeContainer(EntityUid uid, string id) - where T : BaseContainer - => _entMan.System().MakeContainer(uid, id, this); - - [Obsolete] - public BaseContainer GetContainer(string id) - => _entMan.System().GetContainer(Owner, id, this); - - [Obsolete] - public bool HasContainer(string id) - => _entMan.System().HasContainer(Owner, id, this); - [Obsolete] public bool TryGetContainer(string id, [NotNullWhen(true)] out BaseContainer? container) => _entMan.System().TryGetContainer(Owner, id, out container, this); @@ -54,20 +41,6 @@ public bool TryGetContainer(string id, [NotNullWhen(true)] out BaseContainer? co public bool TryGetContainer(EntityUid entity, [NotNullWhen(true)] out BaseContainer? container) => _entMan.System().TryGetContainingContainer(Owner, entity, out container, this); - [Obsolete] - public bool ContainsEntity(EntityUid entity) - => _entMan.System().ContainsEntity(Owner, entity, this); - - [Obsolete] - public bool Remove(EntityUid toremove, - TransformComponent? xform = null, - MetaDataComponent? meta = null, - bool reparent = true, - bool force = false, - EntityCoordinates? destination = null, - Angle? localRotation = null) - => _entMan.System().RemoveEntity(Owner, toremove, this, xform, meta, reparent, force, destination, localRotation); - [Obsolete] public AllContainersEnumerable GetAllContainers() => _entMan.System().GetAllContainers(Owner, this); diff --git a/Robust.Shared/Containers/SharedContainerSystem.cs b/Robust.Shared/Containers/SharedContainerSystem.cs index 88f9e0be181..dafba34fba9 100644 --- a/Robust.Shared/Containers/SharedContainerSystem.cs +++ b/Robust.Shared/Containers/SharedContainerSystem.cs @@ -193,16 +193,6 @@ public bool TryGetContainer( return true; } - [Obsolete("Use variant without skipExistCheck argument")] - public bool TryGetContainingContainer( - EntityUid uid, - EntityUid containedUid, - [NotNullWhen(true)] out BaseContainer? container, - bool skipExistCheck) - { - return TryGetContainingContainer(uid, containedUid, out container); - } - public bool TryGetContainingContainer( EntityUid uid, EntityUid containedUid, diff --git a/Robust.Shared/GameObjects/Components/Appearance/AppearanceComponent.cs b/Robust.Shared/GameObjects/Components/Appearance/AppearanceComponent.cs index daef024d494..e805df1838f 100644 --- a/Robust.Shared/GameObjects/Components/Appearance/AppearanceComponent.cs +++ b/Robust.Shared/GameObjects/Components/Appearance/AppearanceComponent.cs @@ -46,17 +46,4 @@ public sealed partial class AppearanceComponent : Component get { return _appearanceDataInit; } set { AppearanceData = value ?? AppearanceData; _appearanceDataInit = value; } } - - [Obsolete("Use SharedAppearanceSystem instead")] - public bool TryGetData(Enum key, [NotNullWhen(true)] out T data) - { - if (AppearanceData.TryGetValue(key, out var dat) && dat is T) - { - data = (T)dat; - return true; - } - - data = default!; - return false; - } } diff --git a/Robust.Shared/GameObjects/Components/Timers/TimerExtensions.cs b/Robust.Shared/GameObjects/Components/Timers/TimerExtensions.cs index 43812b5caf4..a4402e8279f 100644 --- a/Robust.Shared/GameObjects/Components/Timers/TimerExtensions.cs +++ b/Robust.Shared/GameObjects/Components/Timers/TimerExtensions.cs @@ -15,43 +15,6 @@ private static TimerComponent EnsureTimerComponent(this EntityUid entity) return entMan.EnsureComponent(entity); } - public static void AddTimer(this EntityUid entity, Timer timer, CancellationToken cancellationToken = default) - { - entity - .EnsureTimerComponent() - .AddTimer(timer, cancellationToken); - } - - /// - /// Creates a task that will complete after a given delay. - /// The task is resumed on the main game logic thread. - /// - /// The entity to add the timer to. - /// The length of time, in milliseconds, to delay for. - /// - /// The task that can be awaited. - public static Task DelayTask(this EntityUid entity, int milliseconds, CancellationToken cancellationToken = default) - { - return entity - .EnsureTimerComponent() - .Delay(milliseconds, cancellationToken); - } - - /// - /// Creates a task that will complete after a given delay. - /// The task is resumed on the main game logic thread. - /// - /// The entity to add the timer to. - /// The length of time to delay for. - /// - /// The task that can be awaited. - public static Task DelayTask(this EntityUid entity, TimeSpan duration, CancellationToken cancellationToken = default) - { - return entity - .EnsureTimerComponent() - .Delay((int) duration.TotalMilliseconds, cancellationToken); - } - /// /// Schedule an action to be fired after a certain delay. /// The action will be resumed on the main game logic thread. @@ -84,21 +47,6 @@ public static void SpawnTimer(this EntityUid entity, TimeSpan duration, Action o .Spawn((int) duration.TotalMilliseconds, onFired, cancellationToken); } - /// - /// Schedule an action that repeatedly fires after a delay specified in milliseconds. - /// - /// The entity to add the timer to. - /// The length of time, in milliseconds, to delay before firing the repeated action. - /// The action to fire. - /// The CancellationToken for stopping the Timer. - [Obsolete("Use a system update loop instead")] - public static void SpawnRepeatingTimer(this EntityUid entity, int milliseconds, Action onFired, CancellationToken cancellationToken) - { - entity - .EnsureTimerComponent() - .SpawnRepeating(milliseconds, onFired, cancellationToken); - } - /// /// Schedule an action that repeatedly fires after a delay. /// diff --git a/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs b/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs index a086c5d307e..174b68e3643 100644 --- a/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs +++ b/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs @@ -386,9 +386,6 @@ public bool Anchored } } - [Obsolete("Use ChildEnumerator")] - public IEnumerable ChildEntities => _children; - public TransformChildrenEnumerator ChildEnumerator => new(_children.GetEnumerator()); [ViewVariables] public int ChildCount => _children.Count; @@ -405,16 +402,6 @@ public void AttachToGridOrMap() _entMan.EntitySysManager.GetEntitySystem().AttachToGridOrMap(Owner, this); } - /// - /// Sets another entity as the parent entity, maintaining world position. - /// - /// - [Obsolete("Use TransformSystem.SetParent() instead")] - public void AttachParent(TransformComponent newParent) - { - _entMan.EntitySysManager.GetEntitySystem().SetParent(Owner, this, newParent.Owner, newParent); - } - internal void UpdateChildMapIdsRecursive( MapId newMapId, EntityUid? newUid, @@ -458,14 +445,6 @@ public void AttachParent(EntityUid parent) return (worldPos, worldRot); } - /// - [Obsolete("Use the system method instead")] - public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(EntityQuery xforms) - { - var (worldPos, worldRot, _) = GetWorldPositionRotationMatrix(xforms); - return (worldPos, worldRot); - } - /// /// Get the WorldPosition, WorldRotation, and WorldMatrix of this entity faster than each individually. /// @@ -502,16 +481,6 @@ public void AttachParent(EntityUid parent) return GetWorldPositionRotationMatrix(xforms); } - /// - /// Get the WorldPosition, WorldRotation, and InvWorldMatrix of this entity faster than each individually. - /// - [Obsolete("Use the system method instead")] - public (Vector2 WorldPosition, Angle WorldRotation, Matrix3x2 InvWorldMatrix) GetWorldPositionRotationInvMatrix() - { - var xformQuery = _entMan.GetEntityQuery(); - return GetWorldPositionRotationInvMatrix(xformQuery); - } - /// /// Get the WorldPosition, WorldRotation, and InvWorldMatrix of this entity faster than each individually. /// @@ -654,13 +623,6 @@ public readonly struct AnchorStateChangedEvent( /// If true, the entity is being detached to null-space /// public readonly bool Detaching = detaching; - - [Obsolete("Use constructor that takes in EntityUid")] - public AnchorStateChangedEvent(TransformComponent transform, bool detaching = false) - : this(transform.Owner, transform, detaching) - { - - } } /// diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index 2ab8298cd4e..12bb3f926e0 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -281,24 +281,6 @@ public static implicit operator T(CompInitializeHandle handle) } } - /// - [Obsolete] - public CompInitializeHandle AddComponentUninitialized(EntityUid uid) where T : IComponent, new() - { - var reg = _componentFactory.GetRegistration(); - var newComponent = (T)_componentFactory.GetComponent(reg); -#pragma warning disable CS0618 // Type or member is obsolete - newComponent.Owner = uid; -#pragma warning restore CS0618 // Type or member is obsolete - - if (!uid.IsValid() || !EntityExists(uid)) - throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid)); - - AddComponentInternal(uid, newComponent, false, true, null); - - return new CompInitializeHandle(this, uid, newComponent, reg.Idx); - } - public void AddComponent( EntityUid uid, EntityPrototype.ComponentRegistryEntry entry, diff --git a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs index fa9b6bc8730..d1ad1e07b4c 100644 --- a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs +++ b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs @@ -141,16 +141,6 @@ protected void DirtyEntity(EntityUid uid, MetaDataComponent? meta = null) EntityManager.DirtyEntity(uid, meta); } - /// - /// Marks a component as dirty. This also implicitly dirties the entity this component belongs to. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Obsolete("Use Dirty(EntityUid, Component, MetaDataComponent?")] - protected void Dirty(IComponent component, MetaDataComponent? meta = null) - { - EntityManager.Dirty(component.Owner, component, meta); - } - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void Dirty(EntityUid uid, IComponent component, MetaDataComponent? meta = null) diff --git a/Robust.Shared/GameObjects/IEntityManager.Components.cs b/Robust.Shared/GameObjects/IEntityManager.Components.cs index 0a749eb4010..e0e7a83b15e 100644 --- a/Robust.Shared/GameObjects/IEntityManager.Components.cs +++ b/Robust.Shared/GameObjects/IEntityManager.Components.cs @@ -74,19 +74,6 @@ public partial interface IEntityManager /// IComponent AddComponent(EntityUid uid, ushort netId, MetaDataComponent? meta = null); - /// - /// Adds an uninitialized Component type to an entity. - /// - /// - /// This function returns a disposable initialize handle that you can use in a statement, to set up a component - /// before initialization is ran on it. - /// - /// Concrete component type to add. - /// Entity being modified. - /// Component initialization handle. When you are done setting up the component, make sure to dispose this. - [Obsolete] - EntityManager.CompInitializeHandle AddComponentUninitialized(EntityUid uid) where T : IComponent, new(); - /// /// Adds a Component to an entity. If the entity is already Initialized, the component will /// automatically be Initialized and Started. diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs index 35e7887dc1e..02280cecfde 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs @@ -634,12 +634,6 @@ public void GetEntitiesInRange(Type type, MapCoordinates coordinates, float rang GetEntitiesInRange(type, coordinates.MapId, coordinates.Position, range, entities, flags); } - [Obsolete] - public HashSet GetComponentsInRange(MapCoordinates coordinates, float range) where T : IComponent - { - return GetComponentsInRange(coordinates.MapId, coordinates.Position, range); - } - public void GetEntitiesInRange(MapCoordinates coordinates, float range, HashSet> entities, LookupFlags flags = DefaultFlags) where T : IComponent { GetEntitiesInRange(coordinates.MapId, coordinates.Position, range, entities, flags); @@ -668,14 +662,6 @@ public void GetEntitiesInRange(Type type, MapId mapId, Vector2 worldPos, float r GetEntitiesIntersecting(type, mapId, circle, transform, entities, flags); } - [Obsolete] - public HashSet GetComponentsInRange(MapId mapId, Vector2 worldPos, float range) where T : IComponent - { - var entities = new HashSet>(); - GetEntitiesInRange(mapId, worldPos, range, entities); - return [..entities.Select(e => e.Comp)]; - } - public void GetEntitiesInRange(MapId mapId, Vector2 worldPos, float range, HashSet> entities, LookupFlags flags = DefaultFlags) where T : IComponent { var shape = new PhysShapeCircle(range, worldPos); diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index aed80ff5cba..92888a839c1 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -1107,13 +1107,6 @@ public void SetWorldRotation(TransformComponent component, Angle angle, EntityQu #region Set Position+Rotation - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Obsolete("Use override with EntityUid")] - public void SetWorldPositionRotation(TransformComponent component, Vector2 worldPos, Angle worldRot) - { - SetWorldPositionRotation(component.Owner, worldPos, worldRot, component); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetWorldPositionRotation(EntityUid uid, Vector2 worldPos, Angle worldRot, TransformComponent? component = null) { diff --git a/Robust.Shared/Log/Logger.cs b/Robust.Shared/Log/Logger.cs index 0beeacebdb5..f0ee62ddcbb 100644 --- a/Robust.Shared/Log/Logger.cs +++ b/Robust.Shared/Log/Logger.cs @@ -100,13 +100,6 @@ public static void Log(LogLevel logLevel, string message) [Obsolete("Use ISawmill.Log")] public static void DebugS(string sawmill, string message) => LogS(LogLevel.Debug, sawmill, message); - /// - /// Log a message as debug, taking in a format string and format list using the regular syntax. - /// - /// - [Obsolete("Use ISawmill.Debug")] - public static void Debug(string message, params object?[] args) => Log(LogLevel.Debug, message, args); - /// /// Log a message as debug. /// @@ -128,13 +121,6 @@ public static void Log(LogLevel logLevel, string message) [Obsolete("Use ISawmill.Info")] public static void InfoS(string sawmill, string message) => LogS(LogLevel.Info, sawmill, message); - /// - /// Log a message as info, taking in a format string and format list using the regular syntax. - /// - /// - [Obsolete("Use ISawmill.Info")] - public static void Info(string message, params object?[] args) => Log(LogLevel.Info, message, args); - /// /// Log a message as info. /// @@ -156,13 +142,6 @@ public static void Log(LogLevel logLevel, string message) [Obsolete("Use ISawmill.Warning")] public static void WarningS(string sawmill, string message) => LogS(LogLevel.Warning, sawmill, message); - /// - /// Log a message as warning, taking in a format string and format list using the regular syntax. - /// - /// - [Obsolete("Use ISawmill.Warning")] - public static void Warning(string message, params object?[] args) => Log(LogLevel.Warning, message, args); - /// /// Log a message as warning. /// @@ -177,13 +156,6 @@ public static void Log(LogLevel logLevel, string message) [Obsolete("Use ISawmill.Error")] public static void ErrorS(string sawmill, string message, params object?[] args) => LogS(LogLevel.Error, sawmill, message, args); - /// - /// Log a message as error, taking in a format string and format list using the regular syntax. - /// - /// - [Obsolete("Use ISawmill.Error")] - public static void ErrorS(string sawmill, Exception exception, string message, params object?[] args) => LogS(LogLevel.Error, sawmill, exception, message, args); - /// /// Log a message as error. /// @@ -204,33 +176,5 @@ public static void Log(LogLevel logLevel, string message) /// [Obsolete("Use ISawmill.Error")] public static void Error(string message) => Log(LogLevel.Error, message); - - /// - /// Log a message as fatal, taking in a format string and format list using the regular syntax. - /// - /// - [Obsolete("Use ISawmill.Fatal")] - public static void FatalS(string sawmill, string message, params object?[] args) => LogS(LogLevel.Fatal, sawmill, message, args); - - /// - /// Log a message as fatal. - /// - /// - [Obsolete("Use ISawmill.Fatal")] - public static void FatalS(string sawmill, string message) => LogS(LogLevel.Fatal, sawmill, message); - - /// - /// Log a message as fatal, taking in a format string and format list using the regular syntax. - /// - /// - [Obsolete("Use ISawmill.Fatal")] - public static void Fatal(string message, params object?[] args) => Log(LogLevel.Fatal, message, args); - - /// - /// Log a message as fatal. - /// - /// - [Obsolete("Use ISawmill.Fatal")] - public static void Fatal(string message) => Log(LogLevel.Fatal, message); } } diff --git a/Robust.Shared/Map/Components/MapGridComponent.cs b/Robust.Shared/Map/Components/MapGridComponent.cs index 882d6a821d1..e4a646faf39 100644 --- a/Robust.Shared/Map/Components/MapGridComponent.cs +++ b/Robust.Shared/Map/Components/MapGridComponent.cs @@ -75,12 +75,6 @@ public sealed partial class MapGridComponent : Component #region TileAccess - [Obsolete("Use the MapSystem method")] - public TileRef GetTileRef(MapCoordinates coords) - { - return MapSystem.GetTileRef(Owner, this, coords); - } - [Obsolete("Use the MapSystem method")] public TileRef GetTileRef(EntityCoordinates coords) { @@ -123,20 +117,6 @@ public void SetTiles(List<(Vector2i GridIndices, Tile Tile)> tiles) MapSystem.SetTiles(Owner, this, tiles); } - [Obsolete("Use the MapSystem method")] - public IEnumerable GetLocalTilesIntersecting(Box2Rotated localArea, bool ignoreEmpty = true, - Predicate? predicate = null) - { - return MapSystem.GetLocalTilesIntersecting(Owner, this, localArea, ignoreEmpty, predicate); - } - - [Obsolete("Use the MapSystem method")] - public IEnumerable GetTilesIntersecting(Box2Rotated worldArea, bool ignoreEmpty = true, - Predicate? predicate = null) - { - return MapSystem.GetTilesIntersecting(Owner, this, worldArea, ignoreEmpty, predicate); - } - [Obsolete("Use the MapSystem method")] public IEnumerable GetTilesIntersecting(Box2 worldArea, bool ignoreEmpty = true, Predicate? predicate = null) @@ -202,18 +182,6 @@ public AnchoredEntitiesEnumerator GetAnchoredEntitiesEnumerator(Vector2i pos) return MapSystem.GetAnchoredEntitiesEnumerator(Owner, this, pos); } - [Obsolete("Use the MapSystem method")] - public IEnumerable GetLocalAnchoredEntities(Box2 localAABB) - { - return MapSystem.GetLocalAnchoredEntities(Owner, this, localAABB); - } - - [Obsolete("Use the MapSystem method")] - public IEnumerable GetAnchoredEntities(Box2 worldAABB) - { - return MapSystem.GetAnchoredEntities(Owner, this, worldAABB); - } - [Obsolete("Use the MapSystem method")] public Vector2i TileIndicesFor(EntityCoordinates coords) { @@ -226,24 +194,6 @@ public Vector2i TileIndicesFor(MapCoordinates worldPos) return MapSystem.TileIndicesFor(Owner, this, worldPos); } - [Obsolete("Use the MapSystem method")] - public IEnumerable GetInDir(EntityCoordinates position, Direction dir) - { - return MapSystem.GetInDir(Owner, this, position, dir); - } - - [Obsolete("Use the MapSystem method")] - public IEnumerable GetLocal(EntityCoordinates coords) - { - return MapSystem.GetLocal(Owner, this, coords); - } - - [Obsolete("Use the MapSystem method")] - public IEnumerable GetCardinalNeighborCells(EntityCoordinates coords) - { - return MapSystem.GetCardinalNeighborCells(Owner, this, coords); - } - [Obsolete("Use the MapSystem method")] public IEnumerable GetCellsInSquareArea(EntityCoordinates coords, int n) { @@ -288,18 +238,6 @@ public Vector2i CoordinatesToTile(EntityCoordinates coords) return MapSystem.CoordinatesToTile(Owner, this, coords); } - [Obsolete("Use the MapSystem method")] - public bool CollidesWithGrid(Vector2i indices) - { - return MapSystem.CollidesWithGrid(Owner, this, indices); - } - - [Obsolete("Use the MapSystem method")] - public Vector2i GridTileToChunkIndices(Vector2i gridTile) - { - return MapSystem.GridTileToChunkIndices(Owner, this, gridTile); - } - [Obsolete("Use the MapSystem method")] public EntityCoordinates GridTileToLocal(Vector2i gridTile) { @@ -312,12 +250,6 @@ public Vector2 GridTileToWorldPos(Vector2i gridTile) return MapSystem.GridTileToWorldPos(Owner, this, gridTile); } - [Obsolete("Use the MapSystem method")] - public MapCoordinates GridTileToWorld(Vector2i gridTile) - { - return MapSystem.GridTileToWorld(Owner, this, gridTile); - } - [Obsolete("Use the MapSystem method")] public bool TryGetTileRef(Vector2i indices, out TileRef tile) { diff --git a/Robust.Shared/Map/EntityCoordinates.cs b/Robust.Shared/Map/EntityCoordinates.cs index de887eb6acc..011eb4bc8b5 100644 --- a/Robust.Shared/Map/EntityCoordinates.cs +++ b/Robust.Shared/Map/EntityCoordinates.cs @@ -76,61 +76,30 @@ public bool IsValid(IEntityManager entityManager) return true; } - [Obsolete("Use SharedTransformSystem.ToMapCoordinates()")] - public MapCoordinates ToMap(IEntityManager entityManager) - { - return ToMap(entityManager, entityManager.System()); - } - [Obsolete("Use SharedTransformSystem.ToMapCoordinates()")] public MapCoordinates ToMap(IEntityManager entityManager, SharedTransformSystem transformSystem) { return transformSystem.ToMapCoordinates(this); } - [Obsolete("Use SharedTransformSystem.ToMapCoordinates()")] - public Vector2 ToMapPos(IEntityManager entityManager) - { - return ToMap(entityManager, entityManager.System()).Position; - } - [Obsolete("Use SharedTransformSystem.ToMapCoordinates()")] public Vector2 ToMapPos(IEntityManager entityManager, SharedTransformSystem transformSystem) { return ToMap(entityManager, transformSystem).Position; } - [Obsolete("Use SharedTransformSystem.ToCoordinates()")] - public static EntityCoordinates FromMap(EntityUid entity, MapCoordinates coordinates, IEntityManager? entMan = null) - { - IoCManager.Resolve(ref entMan); - return FromMap(entity, coordinates, entMan.System(), entMan); - } - [Obsolete("Use SharedTransformSystem.ToCoordinates()")] public static EntityCoordinates FromMap(EntityUid entity, MapCoordinates coordinates, SharedTransformSystem transformSystem, IEntityManager? entMan = null) { return transformSystem.ToCoordinates(entity, coordinates); } - [Obsolete("Use SharedTransformSystem.ToCoordinates()")] - public static EntityCoordinates FromMap(IEntityManager entityManager, EntityUid entityUid, MapCoordinates coordinates) - { - return FromMap(entityUid, coordinates, entityManager.System(), entityManager); - } - [Obsolete("Use SharedTransformSystem.ToCoordinates()")] public static EntityCoordinates FromMap(IMapManager mapManager, MapCoordinates coordinates) { return IoCManager.Resolve().System().ToCoordinates(coordinates); } - [Obsolete("Use overload with TransformSystem")] - public Vector2i ToVector2i(IEntityManager entityManager, IMapManager mapManager) - { - return ToVector2i(entityManager, mapManager, entityManager.System()); - } - /// /// Converts this set of coordinates to Vector2i. /// diff --git a/Robust.Shared/Map/IMapManager.cs b/Robust.Shared/Map/IMapManager.cs index 918a160103d..db37657a152 100644 --- a/Robust.Shared/Map/IMapManager.cs +++ b/Robust.Shared/Map/IMapManager.cs @@ -71,15 +71,6 @@ public interface IMapManager Entity CreateGridEntity(MapId currentMapId, GridCreateOptions? options = null); Entity CreateGridEntity(EntityUid map, GridCreateOptions? options = null); - [Obsolete("Use GetComponent(uid)")] - MapGridComponent GetGrid(EntityUid gridId); - - [Obsolete("Use TryGetComponent(uid, out MapGridComponent? grid)")] - bool TryGetGrid([NotNullWhen(true)] EntityUid? euid, [NotNullWhen(true)] out MapGridComponent? grid); - - [Obsolete("Use HasComponent(uid)")] - bool GridExists([NotNullWhen(true)] EntityUid? euid); - IEnumerable GetAllMapGrids(MapId mapId); IEnumerable> GetAllGrids(MapId mapId); diff --git a/Robust.Shared/Map/MapManager.GridCollection.cs b/Robust.Shared/Map/MapManager.GridCollection.cs index 67d6393168b..50aba226ea2 100644 --- a/Robust.Shared/Map/MapManager.GridCollection.cs +++ b/Robust.Shared/Map/MapManager.GridCollection.cs @@ -39,35 +39,12 @@ public Entity CreateGridEntity(EntityUid map, GridCreateOption return CreateGrid(map, options.Value.ChunkSize, default); } - [Obsolete("Use GetComponent(uid)")] - public MapGridComponent GetGrid(EntityUid gridId) - => EntityManager.GetComponent(gridId); - [Obsolete("Use HasComponent(uid)")] public bool IsGrid(EntityUid uid) { return EntityManager.HasComponent(uid); } - [Obsolete("Use TryGetComponent(uid, out MapGridComponent? grid)")] - public bool TryGetGrid([NotNullWhen(true)] EntityUid? euid, [MaybeNullWhen(false)] out MapGridComponent grid) - { - if (EntityManager.TryGetComponent(euid, out MapGridComponent? comp)) - { - grid = comp; - return true; - } - - grid = default; - return false; - } - - [Obsolete("Use HasComponent(uid)")] - public bool GridExists([NotNullWhen(true)] EntityUid? euid) - { - return EntityManager.HasComponent(euid); - } - public IEnumerable GetAllMapGrids(MapId mapId) { var query = EntityManager.AllEntityQueryEnumerator(); diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs index 730f5e18ebf..3d542c7c809 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs @@ -242,12 +242,6 @@ public void ResetDynamics(EntityUid uid, PhysicsComponent body, bool dirty = tru Dirty(uid, body); } - [Obsolete("Use overload that takes EntityUid")] - public void ResetDynamics(PhysicsComponent body, bool dirty = true) - { - ResetDynamics(body.Owner, body, dirty); - } - public void ResetMassData(EntityUid uid, FixturesComponent? manager = null, PhysicsComponent? body = null) { if (!PhysicsQuery.Resolve(uid, ref body)) @@ -392,12 +386,6 @@ public void SetAngularDamping(EntityUid uid, PhysicsComponent body, float value, Dirty(uid, body); } - [Obsolete("Use overload that takes EntityUid")] - public void SetAngularDamping(PhysicsComponent body, float value, bool dirty = true) - { - SetAngularDamping(body.Owner, body, value, dirty); - } - public void SetLinearDamping(EntityUid uid, PhysicsComponent body, float value, bool dirty = true) { if (MathHelper.CloseTo(body.LinearDamping, value)) @@ -409,12 +397,6 @@ public void SetLinearDamping(EntityUid uid, PhysicsComponent body, float value, Dirty(uid, body); } - [Obsolete("Use overload that takes EntityUid")] - public void SetLinearDamping(PhysicsComponent body, float value, bool dirty = true) - { - SetLinearDamping(body.Owner, body, value, dirty); - } - [Obsolete("Use SetAwake with EntityUid")] public void SetAwake(EntityUid uid, PhysicsComponent body, bool value, bool updateSleepTime = true) { @@ -524,12 +506,6 @@ public void SetBodyStatus(EntityUid uid, PhysicsComponent body, BodyStatus statu Dirty(uid, body); } - [Obsolete("Use overload that takes EntityUid")] - public void SetBodyStatus(PhysicsComponent body, BodyStatus status, bool dirty = true) - { - SetBodyStatus(body.Owner, body, status, dirty); - } - /// /// Sets the property; this handles whether the body is enabled. /// @@ -609,12 +585,6 @@ public void SetFriction(EntityUid uid, PhysicsComponent body, float value, bool Dirty(uid, body); } - [Obsolete("Use overload that takes EntityUid")] - public void SetFriction(PhysicsComponent body, float value, bool dirty = true) - { - SetFriction(body.Owner, body, value, dirty); - } - public void SetInertia(EntityUid uid, PhysicsComponent body, float value, bool dirty = true) { DebugTools.Assert(!float.IsNaN(value)); @@ -634,12 +604,6 @@ public void SetInertia(EntityUid uid, PhysicsComponent body, float value, bool d } } - [Obsolete("Use overload that takes EntityUid")] - public void SetInertia(PhysicsComponent body, float value, bool dirty = true) - { - SetInertia(body.Owner, body, value, dirty); - } - public void SetLocalCenter(EntityUid uid, PhysicsComponent body, Vector2 value) { if (body.BodyType != BodyType.Dynamic) return; diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs index b76401dcafa..69893eaf9d4 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs @@ -178,13 +178,6 @@ public IEnumerable> GetCollidingEntities(MapId mapId, i return bodies; } - - [Obsolete("Use override that takes in a entity Uid")] - public HashSet GetContactingEntities(PhysicsComponent body, bool approximate = false) - { - return GetContactingEntities(body.Owner, body); - } - public HashSet GetContactingEntities(EntityUid uid, PhysicsComponent? body = null, bool approximate = false) { // HashSet to ensure that we only return each entity once, instead of once per colliding fixture. diff --git a/Robust.Shared/Player/Filter.cs b/Robust.Shared/Player/Filter.cs index c96bb0daace..3876c5e3b99 100644 --- a/Robust.Shared/Player/Filter.cs +++ b/Robust.Shared/Player/Filter.cs @@ -49,16 +49,6 @@ public Filter AddPlayersByPvs(EntityUid origin, float rangeMultiplier = 2f, IEnt return AddPlayersByPvs(transformSystem.GetMapCoordinates(transform), rangeMultiplier, entityManager, playerMan, cfgMan); } - /// - /// Adds all players inside an entity's PVS. - /// The current PVS range will be multiplied by . - /// - [Obsolete("Use overload that takes in managers")] - public Filter AddPlayersByPvs(TransformComponent origin, float rangeMultiplier = 2f) - { - return AddPlayersByPvs(origin.MapPosition, rangeMultiplier); - } - /// /// Adds all players inside an entity's PVS. /// The current PVS range will be multiplied by . @@ -372,15 +362,6 @@ public static Filter Pvs(EntityUid origin, float rangeMultiplier = 2f, IEntityMa return Empty().AddPlayersByPvs(origin, rangeMultiplier, entityManager, playerManager, cfgManager); } - /// - /// A filter with every player whose PVS overlaps this point. - /// - [Obsolete("Use overload that takes in managers")] - public static Filter Pvs(TransformComponent origin, float rangeMultiplier = 2f) - { - return Empty().AddPlayersByPvs(origin, rangeMultiplier); - } - /// /// A filter with every player whose PVS overlaps this point. /// diff --git a/Robust.Shared/Player/ICommonSession.cs b/Robust.Shared/Player/ICommonSession.cs index b5ef46aa906..7e5c9dfc62e 100644 --- a/Robust.Shared/Player/ICommonSession.cs +++ b/Robust.Shared/Player/ICommonSession.cs @@ -44,7 +44,7 @@ public interface ICommonSession /// On the Server every player has a network channel, /// on the Client only the LocalPlayer has a network channel, and that channel points to the server. /// - INetChannel Channel { get; [Obsolete] set; } + INetChannel Channel { get; set; } LoginType AuthType { get; } @@ -65,9 +65,6 @@ public interface ICommonSession /// SessionData Data { get; } - [Obsolete("Just use the Channel field instead.")] - INetChannel ConnectedClient => Channel; - /// /// If true, this indicates that this is a client-side session, and should be ignored when applying a server's /// game state. diff --git a/Robust.Shared/Player/ISharedPlayerManager.cs b/Robust.Shared/Player/ISharedPlayerManager.cs index 159bb581fd2..7631067ea18 100644 --- a/Robust.Shared/Player/ISharedPlayerManager.cs +++ b/Robust.Shared/Player/ISharedPlayerManager.cs @@ -167,7 +167,4 @@ bool SetAttachedEntity([NotNullWhen(true)] ICommonSession? session, EntityUid? e /// Set the session's status to . /// void JoinGame(ICommonSession session); - - [Obsolete("Use GetSessionById()")] - ICommonSession GetSessionByUserId(NetUserId user) => GetSessionById(user); } diff --git a/Robust.Shared/Prototypes/IPrototypeManager.cs b/Robust.Shared/Prototypes/IPrototypeManager.cs index deea42796e7..22211c2495a 100644 --- a/Robust.Shared/Prototypes/IPrototypeManager.cs +++ b/Robust.Shared/Prototypes/IPrototypeManager.cs @@ -161,61 +161,6 @@ bool TryGetInstances([NotNullWhen(true)] out FrozenDictionary? ins bool HasMapping(string id); bool TryGetMapping(Type kind, string id, [NotNullWhen(true)] out MappingDataNode? mappings); - /// - /// Returns whether a prototype variant exists. - /// - /// Identifier for the prototype variant. - /// Whether the prototype variant exists. - [Obsolete("Variant is outdated naming, use *kind* functions instead")] - bool HasVariant(string variant); - - /// - /// Returns the Type for a prototype variant. - /// - /// Identifier for the prototype variant. - /// The specified prototype Type. - /// - /// Thrown when the specified prototype variant isn't registered or doesn't exist. - /// - [Obsolete("Variant is outdated naming, use *kind* functions instead")] - Type GetVariantType(string variant); - - /// - /// Attempts to get the Type for a prototype variant. - /// - /// Identifier for the prototype variant. - /// The specified prototype Type, or null. - /// Whether the prototype type was found and isn't null. - [Obsolete("Variant is outdated naming, use *kind* functions instead")] - bool TryGetVariantType(string variant, [NotNullWhen(true)] out Type? prototype); - - /// - /// Attempts to get a prototype's variant. - /// - /// - /// - /// - [Obsolete("Variant is outdated naming, use *kind* functions instead")] - bool TryGetVariantFrom(Type type, [NotNullWhen(true)] out string? variant); - - /// - /// Attempts to get a prototype's variant. - /// - /// The prototype in question. - /// Identifier for the prototype variant, or null. - /// Whether the prototype variant was successfully retrieved. - [Obsolete("Variant is outdated naming, use *kind* functions instead")] - bool TryGetVariantFrom(IPrototype prototype, [NotNullWhen(true)] out string? variant); - - /// - /// Attempts to get a prototype's variant. - /// - /// Identifier for the prototype variant, or null. - /// The prototype in question. - /// Whether the prototype variant was successfully retrieved. - [Obsolete("Variant is outdated naming, use *kind* functions instead")] - bool TryGetVariantFrom([NotNullWhen(true)] out string? variant) where T : class, IPrototype; - /// /// Returns whether a prototype kind exists. /// @@ -355,14 +300,6 @@ void ReloadPrototypes(Dictionary> modified, /// void RegisterIgnore(string name); - /// - /// Loads a single prototype class type into the manager. - /// - /// A prototype class type that implements IPrototype. This type also - /// requires a with a non-empty class string. - [Obsolete("Prototype type is outdated naming, use *king* functions instead")] - void RegisterType(Type protoClass); - /// /// Loads several prototype kinds into the manager. Note that this will re-build a frozen dictionary and should be avoided if possible. /// diff --git a/Robust.Shared/Prototypes/PrototypeManager.cs b/Robust.Shared/Prototypes/PrototypeManager.cs index 4116395d32a..d063ee20d66 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.cs @@ -806,30 +806,6 @@ public bool TryGetMapping(Type kind, string id, [NotNullWhen(true)] out MappingD return _kinds[kind].Results.TryGetValue(id, out mappings); } - [Obsolete("Variant is outdated naming, use *kind* functions instead")] - public bool HasVariant(string variant) => HasKind(variant); - - [Obsolete("Variant is outdated naming, use *kind* functions instead")] - public Type GetVariantType(string variant) => GetKindType(variant); - - [Obsolete("Variant is outdated naming, use *kind* functions instead")] - public bool TryGetVariantType(string variant, [NotNullWhen(true)] out Type? prototype) - { - return TryGetKindType(variant, out prototype); - } - - [Obsolete("Variant is outdated naming, use *kind* functions instead")] - public bool TryGetVariantFrom(Type type, [NotNullWhen(true)] out string? variant) - { - return TryGetKindFrom(type, out variant); - } - - [Obsolete("Variant is outdated naming, use *kind* functions instead")] - public bool TryGetVariantFrom([NotNullWhen(true)] out string? variant) where T : class, IPrototype - { - return TryGetKindFrom(out variant); - } - public bool HasKind(string kind) { return _kindNames.ContainsKey(kind); @@ -918,20 +894,12 @@ public bool TryGetKindFrom([NotNullWhen(true)] out string? kind) where T : cl return TryGetKindFrom(typeof(T), out kind); } - [Obsolete("Variant is outdated naming, use *kind* functions instead")] - public bool TryGetVariantFrom(IPrototype prototype, [NotNullWhen(true)] out string? variant) - { - return TryGetKindFrom(prototype, out variant); - } - /// public void RegisterIgnore(string name) { _ignoredPrototypeTypes.Add(name); } - void IPrototypeManager.RegisterType(Type type) => RegisterKind(type); - static string CalculatePrototypeName(Type type) { const string prototype = "Prototype"; diff --git a/Robust.UnitTesting/RobustIntegrationTest.cs b/Robust.UnitTesting/RobustIntegrationTest.cs index c983acf8136..9a6f7db880e 100644 --- a/Robust.UnitTesting/RobustIntegrationTest.cs +++ b/Robust.UnitTesting/RobustIntegrationTest.cs @@ -830,8 +830,6 @@ public async Task RemoveAllDummySessions() public sealed class ClientIntegrationInstance : IntegrationInstance { - [Obsolete("Use Session instead")] - public LocalPlayer? Player => ((IPlayerManager) PlayerMan).LocalPlayer; public ICommonSession? Session => ((IPlayerManager) PlayerMan).LocalSession; public NetUserId? User => Session?.UserId; public EntityUid? AttachedEntity => Session?.AttachedEntity; From 4faef1bfd3b70a80ae5d72898cfc81032426a8de Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:53:36 +1000 Subject: [PATCH 112/140] Add another lookup override (#5436) --- .../Systems/EntityLookupSystem.LocalQueries.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.LocalQueries.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.LocalQueries.cs index d0ec3fae883..93bb4874016 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.LocalQueries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.LocalQueries.cs @@ -68,6 +68,16 @@ public HashSet GetLocalEntitiesIntersecting(EntityUid gridId, Vector2 return intersecting; } + /// + /// Gets entities intersecting to the relative broadphase entity. Does NOT turn the transform into local terms. + /// + public void GetLocalEntitiesIntersecting(EntityUid gridUid, IPhysShape shape, Transform localTransform, HashSet intersecting, LookupFlags flags = DefaultFlags, BroadphaseComponent? lookup = null) + { + var localAABB = shape.ComputeAABB(localTransform, 0); + AddEntitiesIntersecting(gridUid, intersecting, shape, localAABB, localTransform, flags: flags, lookup: lookup); + AddContained(intersecting, flags); + } + /// /// Gets the entities intersecting the specified broadphase entity using a local AABB. /// From dbe6f6588067eed6800c3e211a1a95c3e8a46f62 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Thu, 12 Sep 2024 13:56:27 +1000 Subject: [PATCH 113/140] Version: 234.0.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 9c5b9a74d07..ef0b48e216f 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 233.1.0 + 234.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 47e734429f9..1d08fe6dd02 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -54,6 +54,22 @@ END TEMPLATE--> *None yet* +## 234.0.0 + +### Breaking changes + +* Remove a lot of obsoleted code that has been obsoleted for a while. + +### New features + +* Add another GetLocalEntitiesIntersecting override. + +### Other + +* Mark large replays as requiring Server GC. +* Obsolete some IResourceCache proxies. + + ## 233.1.0 ### New features From 45b7500d9365d7a541da2133bbb4fb5070698426 Mon Sep 17 00:00:00 2001 From: Fildrance Date: Thu, 12 Sep 2024 10:41:40 +0300 Subject: [PATCH 114/140] feat: added audio system predicted method for only one receiver (#5435) * feat: added audio system predicted method for only one receiver * renamed to PlayLocal * tweak --------- Co-authored-by: pa.pecherskij Co-authored-by: metalgearsloth --- RELEASE-NOTES.md | 4 ++-- Robust.Client/Audio/AudioSystem.cs | 11 +++++++++++ Robust.Server/Audio/AudioSystem.cs | 11 +++++++++++ Robust.Shared/Audio/Systems/SharedAudioSystem.cs | 9 +++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 1d08fe6dd02..309e31ad7f0 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -*None yet* +* SharedAudioSystem now has PlayLocal which only runs audio locally on the client. ### Bugfixes @@ -74,7 +74,7 @@ END TEMPLATE--> ### New features -* Add GetGridEntities and another GetEntitiesIntersecting overload to EntityLookupSystem. +* Add GetGridEntities and another GetEntitiesIntersecting overload to EntityLookupSystem. * `MarkupNode` is now `IEquatable`. It already supported equality checks, now it implements the interface. * Added `Entity` overloads to the following `SharedMapSystem` methods: `GetTileRef`, `GetAnchoredEntities`, `TileIndicesFor`. * Added `EntityUid`-only overloads to the following `SharedTransformSystem` methods: `AnchorEntity`, `Unanchor`. diff --git a/Robust.Client/Audio/AudioSystem.cs b/Robust.Client/Audio/AudioSystem.cs index 2eabb9fbb24..d7630edaacd 100644 --- a/Robust.Client/Audio/AudioSystem.cs +++ b/Robust.Client/Audio/AudioSystem.cs @@ -453,6 +453,17 @@ public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(Soun return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio.... } + /// + public override (EntityUid Entity, AudioComponent Component)? PlayLocal( + SoundSpecifier? sound, + EntityUid source, + EntityUid? soundInitiator, + AudioParams? audioParams = null + ) + { + return PlayPredicted(sound, source, soundInitiator, audioParams); + } + public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null) { if (Timing.IsFirstTimePredicted && sound != null) diff --git a/Robust.Server/Audio/AudioSystem.cs b/Robust.Server/Audio/AudioSystem.cs index 0bb6b1b30c5..916d9ce0e32 100644 --- a/Robust.Server/Audio/AudioSystem.cs +++ b/Robust.Server/Audio/AudioSystem.cs @@ -175,6 +175,17 @@ public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? fi return (entity, entity.Comp); } + /// + public override (EntityUid Entity, AudioComponent Component)? PlayLocal( + SoundSpecifier? sound, + EntityUid source, + EntityUid? soundInitiator, + AudioParams? audioParams = null + ) + { + return null; + } + /// public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null) { diff --git a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs index 7bed97f5ef3..9fff8c8fb94 100644 --- a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs +++ b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs @@ -574,6 +574,15 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null); + /// + /// Plays a predicted sound following an entity for only one entity. The client-side system plays this sound as normal, server will do nothing. + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The UID of the entity "emitting" the audio. + /// The UID of the user that initiated this sound. This is usually some player's controlled entity. + [return: NotNullIfNotNull("sound")] + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayLocal(SoundSpecifier? sound, EntityUid source, EntityUid? soundInitiator, AudioParams? audioParams = null); + /// /// Plays a predicted sound following an entity. The server will send the sound to every player in PVS range, /// unless that player is attached to the "user" entity that initiated the sound. The client-side system plays From f7287b181d51b46717a430c875644b51b9f181df Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:48:37 +1000 Subject: [PATCH 115/140] Fix audioparams for playglobal (#5437) --- Robust.Shared/Audio/Systems/SharedAudioSystem.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs index 9fff8c8fb94..e58c0ed36c1 100644 --- a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs +++ b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs @@ -428,7 +428,7 @@ public TimeSpan GetAudioLength(string filename) [return: NotNullIfNotNull("sound")] public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) { - return sound == null ? null : PlayGlobal(GetSound(sound), playerFilter, recordReplay, sound.Params); + return sound == null ? null : PlayGlobal(GetSound(sound), playerFilter, recordReplay, audioParams ?? sound.Params); } /// @@ -445,9 +445,9 @@ public TimeSpan GetAudioLength(string filename) /// The sound specifier that points the audio file(s) that should be played. /// The player that will hear the sound. [return: NotNullIfNotNull("sound")] - public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, ICommonSession recipient) + public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, ICommonSession recipient, AudioParams? audioParams = null) { - return sound == null ? null : PlayGlobal(GetSound(sound), recipient, sound.Params); + return sound == null ? null : PlayGlobal(GetSound(sound), recipient, audioParams ?? sound.Params); } public abstract void LoadStream(Entity entity, T stream); @@ -468,7 +468,7 @@ public TimeSpan GetAudioLength(string filename) [return: NotNullIfNotNull("sound")] public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, EntityUid recipient, AudioParams? audioParams = null) { - return sound == null ? null : PlayGlobal(GetSound(sound), recipient, sound.Params); + return sound == null ? null : PlayGlobal(GetSound(sound), recipient, audioParams ?? sound.Params); } /// From 0f60ad9018f54f9b49da1810bbffa01e2c5975f7 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Thu, 12 Sep 2024 17:56:50 +1000 Subject: [PATCH 116/140] Version: 234.1.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index ef0b48e216f..2b318b4e856 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 234.0.0 + 234.1.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 309e31ad7f0..fcd09827bc9 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,7 @@ END TEMPLATE--> ### New features -* SharedAudioSystem now has PlayLocal which only runs audio locally on the client. +*None yet* ### Bugfixes @@ -54,6 +54,17 @@ END TEMPLATE--> *None yet* +## 234.1.0 + +### New features + +* SharedAudioSystem now has PlayLocal which only runs audio locally on the client. + +### Bugfixes + +* Fix AudioParams not being passed through on PlayGlobal methods. + + ## 234.0.0 ### Breaking changes From 4949b34c88ef414df914d4ce59096e9cd370104e Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sat, 14 Sep 2024 00:34:30 +1200 Subject: [PATCH 117/140] Fix "to" and "take" toolshed commands (#5438) --- RELEASE-NOTES.md | 2 +- Resources/Locale/en-US/toolshed-commands.ftl | 4 ++-- .../Toolshed/Commands/Generic/ListGeneration/ToCommand.cs | 2 +- Robust.Shared/Toolshed/Commands/Generic/TakeCommand.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index fcd09827bc9..871c680c048 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* Fixed the "to" and "take" toolshed commands not working as intended. ### Other diff --git a/Resources/Locale/en-US/toolshed-commands.ftl b/Resources/Locale/en-US/toolshed-commands.ftl index 53603d88f70..0ec32fd9b48 100644 --- a/Resources/Locale/en-US/toolshed-commands.ftl +++ b/Resources/Locale/en-US/toolshed-commands.ftl @@ -219,9 +219,9 @@ command-description-MulVecCommand = command-description-DivVecCommand = Divides every element in the input by a scalar (single value). command-description-rng-to = - Returns a number from its input to its argument (i.e. n..m inclusive) + Returns a number between the input (inclusive) and the argument (exclusive). command-description-rng-from = - Returns a number to its input from its argument (i.e. m..n inclusive) + Returns a number between the argument (inclusive) and the input (exclusive)) command-description-rng-prob = Returns a boolean based on the input probability/chance (from 0 to 1) command-description-sum = diff --git a/Robust.Shared/Toolshed/Commands/Generic/ListGeneration/ToCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/ListGeneration/ToCommand.cs index d7e722e5d72..ab03f8ba5e9 100644 --- a/Robust.Shared/Toolshed/Commands/Generic/ListGeneration/ToCommand.cs +++ b/Robust.Shared/Toolshed/Commands/Generic/ListGeneration/ToCommand.cs @@ -15,5 +15,5 @@ public IEnumerable To( [CommandArgument] ValueRef end ) where T : INumber - => Enumerable.Range(int.CreateTruncating(start), int.CreateTruncating(end.Evaluate(ctx)!)).Select(T.CreateTruncating); + => Enumerable.Range(int.CreateTruncating(start), int.CreateTruncating(end.Evaluate(ctx)! - start)).Select(T.CreateTruncating); } diff --git a/Robust.Shared/Toolshed/Commands/Generic/TakeCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/TakeCommand.cs index 72e1d22bba8..f5716b49de9 100644 --- a/Robust.Shared/Toolshed/Commands/Generic/TakeCommand.cs +++ b/Robust.Shared/Toolshed/Commands/Generic/TakeCommand.cs @@ -7,7 +7,7 @@ namespace Robust.Shared.Toolshed.Commands.Generic; [ToolshedCommand] public sealed class TakeCommand : ToolshedCommand { - [CommandImplementation] + [CommandImplementation, TakesPipedTypeAsGeneric] public IEnumerable Take( [CommandInvocationContext] IInvocationContext ctx, [PipedArgument] IEnumerable input, From f5c1d870f904ca1a5d67ae6db20c17e181a26df9 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:06:04 +1200 Subject: [PATCH 118/140] Improve FlushEntities() error logs (#5427) * Improve FlushEntities() error logs * log count before flush --- Robust.Shared/GameObjects/EntityManager.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index b3abee1daac..bec9e9afbf0 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -692,11 +692,23 @@ public bool Deleted([NotNullWhen(false)] EntityUid? uid) /// public virtual void FlushEntities() { + _sawmill.Info($"Flushing entities. Entity count: {Entities.Count}"); BeforeEntityFlush?.Invoke(); FlushEntitiesInternal(); if (Entities.Count != 0) - _sawmill.Error("Failed to flush all entities"); + { + _sawmill.Error($"Failed to flush all entities. Entity count: {Entities.Count}"); + // Dump entity info, but avoid dumping ~50k errors if for whatever reason we failed to delete almost all entities. + // Using 512 as the limit, in case the problem entities are related to player counts on high-pop servers. + if (Entities.Count < 512) + { + foreach (var uid in Entities) + { + _sawmill.Error($"Entity exists after flush: {ToPrettyString(uid)}"); + } + } + } #if EXCEPTION_TOLERANCE // Attempt to flush entities a second time, just in case something somehow caused an entity to be spawned @@ -705,7 +717,7 @@ public virtual void FlushEntities() #endif if (Entities.Count != 0) - throw new Exception("Failed to flush all entities"); + throw new Exception($"Failed to flush all entities. Entity count: {Entities.Count}"); AfterEntityFlush?.Invoke(); } From f81e30a0312026db8479875fa212c9eab6bafcf9 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:12:20 +1200 Subject: [PATCH 119/140] Try fix invalid PVS index bug (#5422) * Try fix invalid PVS index bug * bounds check * More Asserts * fix assert? * remove deletion * a * A! --- RELEASE-NOTES.md | 6 ++++++ Robust.Server/GameStates/PvsData.cs | 10 ++++++++++ Robust.Server/GameStates/PvsSystem.DataStorage.cs | 9 +++------ Robust.Server/GameStates/PvsSystem.Dirty.cs | 2 +- Robust.Server/GameStates/PvsSystem.Entity.cs | 2 +- Robust.Server/GameStates/PvsSystem.Overrides.cs | 3 +++ Robust.Server/GameStates/PvsSystem.ToSendSet.cs | 4 ++-- .../GameObjects/Components/MetaDataComponent.cs | 5 ++++- .../Commands/Generic/ListGeneration/ToCommand.cs | 2 +- Robust.Shared/Utility/ResizableMemoryRegion.cs | 8 ++++---- 10 files changed, 35 insertions(+), 16 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 871c680c048..a4b414631dc 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,13 @@ END TEMPLATE--> ### Bugfixes +<<<<<<< HEAD +* Fixed equality checks for `MarkupNode` not properly handling attributes. +* Fixed `MarkupNode` not having a `GetHashCode()` implementation. +* Fixed a PVS error that could occur when trying to delete the first entity that gets created in a round. +======= * Fixed the "to" and "take" toolshed commands not working as intended. +>>>>>>> f5c1d870f904ca1a5d67ae6db20c17e181a26df9 ### Other diff --git a/Robust.Server/GameStates/PvsData.cs b/Robust.Server/GameStates/PvsData.cs index f6778464a93..c4df89b5c44 100644 --- a/Robust.Server/GameStates/PvsData.cs +++ b/Robust.Server/GameStates/PvsData.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -190,6 +191,15 @@ internal struct PvsMetadata private byte Pad0; public uint Marker; #endif + + [Conditional("DEBUG")] + public void Validate(MetaDataComponent comp) + { + DebugTools.AssertEqual(NetEntity, comp.NetEntity); + DebugTools.AssertEqual(VisMask, comp.VisibilityMask); + DebugTools.AssertEqual(LifeStage, comp.EntityLifeStage); + DebugTools.Assert(LastModifiedTick == comp.EntityLastModifiedTick || LastModifiedTick.Value == 0); + } } [StructLayout(LayoutKind.Sequential, Size = 16)] diff --git a/Robust.Server/GameStates/PvsSystem.DataStorage.cs b/Robust.Server/GameStates/PvsSystem.DataStorage.cs index b9927938b68..814d82945a1 100644 --- a/Robust.Server/GameStates/PvsSystem.DataStorage.cs +++ b/Robust.Server/GameStates/PvsSystem.DataStorage.cs @@ -245,8 +245,6 @@ private static void FreeSessionDataMemory(PvsSession session) private void OnEntityAdded(Entity entity) { - DebugTools.Assert(entity.Comp.PvsData.Index == default); - AssignEntityPointer(entity.Comp); } @@ -255,6 +253,7 @@ private void OnEntityAdded(Entity entity) /// private void AssignEntityPointer(MetaDataComponent meta) { + DebugTools.Assert(meta.PvsData == PvsIndex.Invalid); if (_dataFreeListHead == PvsIndex.Invalid) { ExpandEntityCapacity(); @@ -267,8 +266,6 @@ private void AssignEntityPointer(MetaDataComponent meta) ref var freeLink = ref Unsafe.As(ref metadata); _dataFreeListHead = freeLink.NextFree; - // TODO: re-introduce this assert. - // DebugTools.AssertEqual(((PvsMetadata*) ptr)->NetEntity, NetEntity.Invalid); DebugTools.AssertNotEqual(meta.NetEntity, NetEntity.Invalid); meta.PvsData = index; @@ -287,9 +284,9 @@ private void AssignEntityPointer(MetaDataComponent meta) private void OnEntityDeleted(Entity entity) { var ptr = entity.Comp.PvsData; - entity.Comp.PvsData = default; + entity.Comp.PvsData = PvsIndex.Invalid; - if (ptr == default) + if (ptr == PvsIndex.Invalid) return; _incomingReturns.Add(ptr); diff --git a/Robust.Server/GameStates/PvsSystem.Dirty.cs b/Robust.Server/GameStates/PvsSystem.Dirty.cs index 98bab6daf07..c7a180ccb79 100644 --- a/Robust.Server/GameStates/PvsSystem.Dirty.cs +++ b/Robust.Server/GameStates/PvsSystem.Dirty.cs @@ -49,7 +49,7 @@ private void OnEntityAdd(Entity e) private void OnEntityDirty(Entity uid) { - if (uid.Comp.PvsData != default) + if (uid.Comp.PvsData != PvsIndex.Invalid) { ref var meta = ref _metadataMemory.GetRef(uid.Comp.PvsData.Index); meta.LastModifiedTick = uid.Comp.EntityLastModifiedTick; diff --git a/Robust.Server/GameStates/PvsSystem.Entity.cs b/Robust.Server/GameStates/PvsSystem.Entity.cs index 19774a4c80a..70748a196cf 100644 --- a/Robust.Server/GameStates/PvsSystem.Entity.cs +++ b/Robust.Server/GameStates/PvsSystem.Entity.cs @@ -107,7 +107,7 @@ private void AssertNullspace(EntityUid uid) internal void SyncMetadata(MetaDataComponent meta) { - if (meta.PvsData == default) + if (meta.PvsData == PvsIndex.Invalid) return; ref var ptr = ref _metadataMemory.GetRef(meta.PvsData.Index); diff --git a/Robust.Server/GameStates/PvsSystem.Overrides.cs b/Robust.Server/GameStates/PvsSystem.Overrides.cs index 0b395579b60..badd3964145 100644 --- a/Robust.Server/GameStates/PvsSystem.Overrides.cs +++ b/Robust.Server/GameStates/PvsSystem.Overrides.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using Robust.Shared.GameObjects; using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Robust.Server.GameStates; @@ -25,6 +26,7 @@ private void AddAllOverrides(PvsSession session) foreach (ref var ent in CollectionsMarshal.AsSpan(_cachedGlobalOverride)) { ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index); + meta.Validate(ent.Meta); if ((mask & meta.VisMask) == meta.VisMask) AddEntity(session, ref ent, ref meta, fromTick); } @@ -51,6 +53,7 @@ private void AddForcedEntities(PvsSession session) foreach (ref var ent in CollectionsMarshal.AsSpan(_cachedForceOverride)) { ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index); + meta.Validate(ent.Meta); if ((mask & meta.VisMask) == meta.VisMask) AddEntity(session, ref ent, ref meta, fromTick); } diff --git a/Robust.Server/GameStates/PvsSystem.ToSendSet.cs b/Robust.Server/GameStates/PvsSystem.ToSendSet.cs index ffe505994ba..5dfa5b949bd 100644 --- a/Robust.Server/GameStates/PvsSystem.ToSendSet.cs +++ b/Robust.Server/GameStates/PvsSystem.ToSendSet.cs @@ -57,6 +57,7 @@ private void AddPvsChunk(PvsChunk chunk, float distance, PvsSession session) foreach (ref var ent in span) { ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index); + meta.Validate(ent.Meta); if ((mask & meta.VisMask) == meta.VisMask) AddEntity(session, ref ent, ref meta, fromTick); } @@ -78,8 +79,7 @@ private void AddEntity(PvsSession session, ref PvsChunk.ChunkEntity ent, ref Pvs if (meta.LifeStage >= EntityLifeStage.Terminating) { - Log.Error($"Attempted to send deleted entity: {ToPrettyString(ent.Uid)}, lifestage is {meta.LifeStage}.\n{Environment.StackTrace}"); - EntityManager.QueueDeleteEntity(ent.Uid); + Log.Error($"Attempted to send deleted entity: {ToPrettyString(ent.Uid)}, Meta lifestage: {ent.Meta.EntityLifeStage}, PVS lifestage: {meta.LifeStage}.\n{Environment.StackTrace}"); return; } diff --git a/Robust.Shared/GameObjects/Components/MetaDataComponent.cs b/Robust.Shared/GameObjects/Components/MetaDataComponent.cs index fe58fe2a40b..a4b00b0d0c3 100644 --- a/Robust.Shared/GameObjects/Components/MetaDataComponent.cs +++ b/Robust.Shared/GameObjects/Components/MetaDataComponent.cs @@ -204,7 +204,7 @@ private protected override void ClearTicks() /// /// Offset into internal PVS data. /// - internal PvsIndex PvsData; + internal PvsIndex PvsData = PvsIndex.Invalid; } [Flags] @@ -254,5 +254,8 @@ internal readonly record struct PvsIndex(int Index) /// An invalid index. This is also used as a marker value in the free list. /// public static readonly PvsIndex Invalid = new PvsIndex(-1); + // TODO PVS + // Consider making 0 an invalid value. + // it prevents default structs from accidentally being used. } } diff --git a/Robust.Shared/Toolshed/Commands/Generic/ListGeneration/ToCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/ListGeneration/ToCommand.cs index ab03f8ba5e9..c5057899ac5 100644 --- a/Robust.Shared/Toolshed/Commands/Generic/ListGeneration/ToCommand.cs +++ b/Robust.Shared/Toolshed/Commands/Generic/ListGeneration/ToCommand.cs @@ -15,5 +15,5 @@ public IEnumerable To( [CommandArgument] ValueRef end ) where T : INumber - => Enumerable.Range(int.CreateTruncating(start), int.CreateTruncating(end.Evaluate(ctx)! - start)).Select(T.CreateTruncating); + => Enumerable.Range(int.CreateTruncating(start), 1 + int.CreateTruncating(end.Evaluate(ctx)! - start)).Select(T.CreateTruncating); } diff --git a/Robust.Shared/Utility/ResizableMemoryRegion.cs b/Robust.Shared/Utility/ResizableMemoryRegion.cs index acdcae53b73..7bd6ac4ab52 100644 --- a/Robust.Shared/Utility/ResizableMemoryRegion.cs +++ b/Robust.Shared/Utility/ResizableMemoryRegion.cs @@ -254,8 +254,8 @@ public Span GetSpan() where TCast : unmanaged public ref T GetRef(int index) { // If the memory region is disposed, CurrentSize is 0 and this check always fails. - if (index >= CurrentSize) - ThrowIndexOutOfRangeException(); + if (index >= CurrentSize || index < 0) + ThrowIndexOutOfRangeException(CurrentSize, index); return ref *(BaseAddress + index); } @@ -302,9 +302,9 @@ private static void ThrowNotInitialized() } [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowIndexOutOfRangeException() + private static void ThrowIndexOutOfRangeException(int size, int index) { - throw new IndexOutOfRangeException(); + throw new IndexOutOfRangeException($"Index was outside the bounds of the memory region. Size: {size}, Index: {index}"); } private void ReleaseUnmanagedResources() From 786acae47aeedd5e550b7651e958404312495182 Mon Sep 17 00:00:00 2001 From: Lgibb18 <65973111+Lgibb18@users.noreply.github.com> Date: Mon, 16 Sep 2024 09:13:59 +0500 Subject: [PATCH 120/140] Fix tags with controls in RichText and OutputPanel (#5428) * Controls in RichText fixes * useless * Get FormattedMessage from RichTextLabel * dont go through nodes * Comments and minor changes --------- Co-authored-by: ElectroJr --- RELEASE-NOTES.md | 4 +--- .../UserInterface/Controls/OutputPanel.cs | 15 ++++++++++++--- .../UserInterface/Controls/RichTextLabel.cs | 2 +- Robust.Client/UserInterface/RichTextEntry.cs | 18 ++++++++++++++---- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index a4b414631dc..d57d9856d52 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,13 +43,11 @@ END TEMPLATE--> ### Bugfixes -<<<<<<< HEAD * Fixed equality checks for `MarkupNode` not properly handling attributes. * Fixed `MarkupNode` not having a `GetHashCode()` implementation. * Fixed a PVS error that could occur when trying to delete the first entity that gets created in a round. -======= * Fixed the "to" and "take" toolshed commands not working as intended. ->>>>>>> f5c1d870f904ca1a5d67ae6db20c17e181a26df9 +* Rich text controls within an `OutputPanel` control will now become invisible when they are out of view. ### Other diff --git a/Robust.Client/UserInterface/Controls/OutputPanel.cs b/Robust.Client/UserInterface/Controls/OutputPanel.cs index c23f4ceaa9e..71497838547 100644 --- a/Robust.Client/UserInterface/Controls/OutputPanel.cs +++ b/Robust.Client/UserInterface/Controls/OutputPanel.cs @@ -127,6 +127,7 @@ protected internal override void Draw(DrawingHandleScreen handle) var style = _getStyleBox(); var font = _getFont(); + var lineSeparation = font.GetLineSeparation(UIScale); style?.Draw(handle, PixelSizeBox, UIScale); var contentBox = _getContentBox(); @@ -141,18 +142,26 @@ protected internal override void Draw(DrawingHandleScreen handle) { if (entryOffset + entry.Height < 0) { - entryOffset += entry.Height + font.GetLineSeparation(UIScale); + // Controls within the entry are the children of this control, which means they are drawn separately + // after this Draw call, so we have to mark them as invisible to prevent them from being drawn. + // + // An alternative option is to ensure that the control position updating logic in entry.Draw is always + // run, and then setting RectClipContent = true to use scissor box testing to handle the controls + // visibility + entry.HideControls(); + entryOffset += entry.Height + lineSeparation; continue; } if (entryOffset > contentBox.Height) { - break; + entry.HideControls(); + continue; } entry.Draw(_tagManager, handle, font, contentBox, entryOffset, context, UIScale); - entryOffset += entry.Height + font.GetLineSeparation(UIScale); + entryOffset += entry.Height + lineSeparation; } } diff --git a/Robust.Client/UserInterface/Controls/RichTextLabel.cs b/Robust.Client/UserInterface/Controls/RichTextLabel.cs index 69402fcb3a5..7e7c03c9d4d 100644 --- a/Robust.Client/UserInterface/Controls/RichTextLabel.cs +++ b/Robust.Client/UserInterface/Controls/RichTextLabel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Numerics; using JetBrains.Annotations; using Robust.Client.Graphics; diff --git a/Robust.Client/UserInterface/RichTextEntry.cs b/Robust.Client/UserInterface/RichTextEntry.cs index 432280bb06a..c6dd38da7cf 100644 --- a/Robust.Client/UserInterface/RichTextEntry.cs +++ b/Robust.Client/UserInterface/RichTextEntry.cs @@ -118,9 +118,6 @@ public void Update(MarkupTagManager tagManager, Font defaultFont, float maxSizeX if (_tagControls == null || !_tagControls.TryGetValue(nodeIndex, out var control)) continue; - if (ProcessRune(ref this, new Rune(' '), out breakLine)) - continue; - control.Measure(new Vector2(Width, Height)); var desiredSize = control.DesiredPixelSize; @@ -167,6 +164,16 @@ void CheckLineBreak(ref RichTextEntry src, int? line) } } + internal readonly void HideControls() + { + if (_tagControls == null) + return; + foreach (var control in _tagControls.Values) + { + control.Visible = false; + } + } + public readonly void Draw( MarkupTagManager tagManager, DrawingHandleScreen handle, @@ -216,8 +223,11 @@ public readonly void Draw( if (_tagControls == null || !_tagControls.TryGetValue(nodeIndex, out var control)) continue; - var invertedScale = 1f / uiScale; + // Controls may have been previously hidden via HideControls due to being "out-of frame". + // If this ever gets replaced with RectClipContents / scissor box testing, this can be removed. + control.Visible = true; + var invertedScale = 1f / uiScale; control.Position = new Vector2(baseLine.X * invertedScale, (baseLine.Y - defaultFont.GetAscent(uiScale)) * invertedScale); control.Measure(new Vector2(Width, Height)); var advanceX = control.DesiredPixelSize.X; From 4f95c07ab3eb05d68e2a7892990adaa310107dd8 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 16 Sep 2024 21:34:42 +0200 Subject: [PATCH 121/140] Add missing Roslyn components to solution --- RobustToolbox.sln | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/RobustToolbox.sln b/RobustToolbox.sln index 5eefb5aa730..2be6b3df1ce 100644 --- a/RobustToolbox.sln +++ b/RobustToolbox.sln @@ -55,6 +55,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CefGlue", "cefglue\CefGlue\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Xaml", "Robust.Xaml\Robust.Xaml.csproj", "{EC7BA4C0-A02F-40E8-B4FC-9A96D91BD1EC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Serialization.Generator", "Robust.Serialization.Generator\Robust.Serialization.Generator.csproj", "{26001B63-7C22-43F7-9690-D6F5D6B9711C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Roslyn", "Roslyn", "{50CEB810-1E9A-4C14-9EDE-DABCC15ECE73}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Shared.CompNetworkGenerator", "Robust.Shared.CompNetworkGenerator\Robust.Shared.CompNetworkGenerator.csproj", "{6AD8DA1A-C807-47B7-A251-97BE0AB381DB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -239,6 +245,22 @@ Global {EC7BA4C0-A02F-40E8-B4FC-9A96D91BD1EC}.Release|Any CPU.Build.0 = Release|Any CPU {EC7BA4C0-A02F-40E8-B4FC-9A96D91BD1EC}.Release|x64.ActiveCfg = Release|Any CPU {EC7BA4C0-A02F-40E8-B4FC-9A96D91BD1EC}.Release|x64.Build.0 = Release|Any CPU + {26001B63-7C22-43F7-9690-D6F5D6B9711C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26001B63-7C22-43F7-9690-D6F5D6B9711C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26001B63-7C22-43F7-9690-D6F5D6B9711C}.Debug|x64.ActiveCfg = Debug|Any CPU + {26001B63-7C22-43F7-9690-D6F5D6B9711C}.Debug|x64.Build.0 = Debug|Any CPU + {26001B63-7C22-43F7-9690-D6F5D6B9711C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26001B63-7C22-43F7-9690-D6F5D6B9711C}.Release|Any CPU.Build.0 = Release|Any CPU + {26001B63-7C22-43F7-9690-D6F5D6B9711C}.Release|x64.ActiveCfg = Release|Any CPU + {26001B63-7C22-43F7-9690-D6F5D6B9711C}.Release|x64.Build.0 = Release|Any CPU + {6AD8DA1A-C807-47B7-A251-97BE0AB381DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6AD8DA1A-C807-47B7-A251-97BE0AB381DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6AD8DA1A-C807-47B7-A251-97BE0AB381DB}.Debug|x64.ActiveCfg = Debug|Any CPU + {6AD8DA1A-C807-47B7-A251-97BE0AB381DB}.Debug|x64.Build.0 = Debug|Any CPU + {6AD8DA1A-C807-47B7-A251-97BE0AB381DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6AD8DA1A-C807-47B7-A251-97BE0AB381DB}.Release|Any CPU.Build.0 = Release|Any CPU + {6AD8DA1A-C807-47B7-A251-97BE0AB381DB}.Release|x64.ActiveCfg = Release|Any CPU + {6AD8DA1A-C807-47B7-A251-97BE0AB381DB}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -250,6 +272,11 @@ Global {1CDC9C4F-668E-47A3-8A44-216E95644BEB} = {1B1FC7C4-0212-4B3E-90D4-C7B58759E4B0} {B05EFB71-AEC7-4C6E-984A-A1BCC58F9AD1} = {1B1FC7C4-0212-4B3E-90D4-C7B58759E4B0} {6BC71226-BA9C-4CD6-9838-03AC076F9518} = {2D787B3C-65EB-43AA-BC6F-289A34ADFDDD} + {3173712A-9E75-4685-B657-9AF9B7D54EFB} = {50CEB810-1E9A-4C14-9EDE-DABCC15ECE73} + {A773F7D4-8EF5-4144-A0DF-3B393B4C1B3A} = {50CEB810-1E9A-4C14-9EDE-DABCC15ECE73} + {26001B63-7C22-43F7-9690-D6F5D6B9711C} = {50CEB810-1E9A-4C14-9EDE-DABCC15ECE73} + {EFB7A05D-71D0-47D1-B7B4-35D4FF661F13} = {50CEB810-1E9A-4C14-9EDE-DABCC15ECE73} + {6AD8DA1A-C807-47B7-A251-97BE0AB381DB} = {50CEB810-1E9A-4C14-9EDE-DABCC15ECE73} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {57757344-0FF4-4842-8A68-141CAA18A35D} From 5218bf70b076efabf0e9cfb45fefce04d65ae029 Mon Sep 17 00:00:00 2001 From: ike709 Date: Mon, 16 Sep 2024 15:31:10 -0500 Subject: [PATCH 122/140] Bump cefglue (#5441) * Bump cefglue * Another bump * Third time's the charm --------- Co-authored-by: ike709 Co-authored-by: Pieter-Jan Briers --- cefglue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cefglue b/cefglue index e265d67a21d..e89d3a86d4c 160000 --- a/cefglue +++ b/cefglue @@ -1 +1 @@ -Subproject commit e265d67a21d0e526178f72326bd45f36d85ad6ca +Subproject commit e89d3a86d4cd111fdd5e433c8890edefed98b7fa From 2fda62a27411575b49f0aed12b07c637dce077af Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Mon, 16 Sep 2024 19:51:54 -0700 Subject: [PATCH 123/140] Fix physics.maxlinvelocity not being a replicated cvar (#5445) --- 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 6404bd1bfb4..ebd4e29b2e2 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -1288,7 +1288,7 @@ protected CVars() /// Default is 35 m/s. Around half a tile per tick at 60 ticks per second. /// public static readonly CVarDef MaxLinVelocity = - CVarDef.Create("physics.maxlinvelocity", 35f); + CVarDef.Create("physics.maxlinvelocity", 35f, CVar.SERVER | CVar.REPLICATED); /// /// Maximum angular velocity in full rotations per second. From 19a87fb67afd2d34064916227e19ba85e98ba820 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:43:49 +1200 Subject: [PATCH 124/140] Remove incorrect NotNullIfNotNull attributes in SharedAudioSystem (#5449) --- RELEASE-NOTES.md | 2 +- .../Audio/Systems/SharedAudioSystem.cs | 25 ------------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index d57d9856d52..7868d1caa86 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,7 +35,7 @@ END TEMPLATE--> ### Breaking changes -*None yet* +* Several different `AudioSystem` methods were incorrectly given a `[return: NotNullIfNotNull]` attribute. Content code that uses these methods needs to be updated to perform null checks. ### New features diff --git a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs index e58c0ed36c1..4d79ff0e3d0 100644 --- a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs +++ b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs @@ -417,7 +417,6 @@ public TimeSpan GetAudioLength(string filename) /// /// The resource path to the OGG Vorbis file to play. /// The set of players that will hear the sound. - [return: NotNullIfNotNull("filename")] public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null); /// @@ -425,7 +424,6 @@ public TimeSpan GetAudioLength(string filename) /// /// The sound specifier that points the audio file(s) that should be played. /// The set of players that will hear the sound. - [return: NotNullIfNotNull("sound")] public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) { return sound == null ? null : PlayGlobal(GetSound(sound), playerFilter, recordReplay, audioParams ?? sound.Params); @@ -436,7 +434,6 @@ public TimeSpan GetAudioLength(string filename) /// /// The resource path to the OGG Vorbis file to play. /// The player that will hear the sound. - [return: NotNullIfNotNull("filename")] public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null); /// @@ -444,7 +441,6 @@ public TimeSpan GetAudioLength(string filename) /// /// The sound specifier that points the audio file(s) that should be played. /// The player that will hear the sound. - [return: NotNullIfNotNull("sound")] public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, ICommonSession recipient, AudioParams? audioParams = null) { return sound == null ? null : PlayGlobal(GetSound(sound), recipient, audioParams ?? sound.Params); @@ -457,7 +453,6 @@ public TimeSpan GetAudioLength(string filename) /// /// The resource path to the OGG Vorbis file to play. /// The player that will hear the sound. - [return: NotNullIfNotNull("filename")] public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null); /// @@ -465,7 +460,6 @@ public TimeSpan GetAudioLength(string filename) /// /// The sound specifier that points the audio file(s) that should be played. /// The player that will hear the sound. - [return: NotNullIfNotNull("sound")] public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, EntityUid recipient, AudioParams? audioParams = null) { return sound == null ? null : PlayGlobal(GetSound(sound), recipient, audioParams ?? sound.Params); @@ -477,7 +471,6 @@ public TimeSpan GetAudioLength(string filename) /// The resource path to the OGG Vorbis file to play. /// The set of players that will hear the sound. /// The UID of the entity "emitting" the audio. - [return: NotNullIfNotNull("filename")] public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null); /// @@ -486,7 +479,6 @@ public TimeSpan GetAudioLength(string filename) /// The resource path to the OGG Vorbis file to play. /// The player that will hear the sound. /// The UID of the entity "emitting" the audio. - [return: NotNullIfNotNull("filename")] public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null); /// @@ -495,7 +487,6 @@ public TimeSpan GetAudioLength(string filename) /// The resource path to the OGG Vorbis file to play. /// The player that will hear the sound. /// The UID of the entity "emitting" the audio. - [return: NotNullIfNotNull("filename")] public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null); /// @@ -504,7 +495,6 @@ public TimeSpan GetAudioLength(string filename) /// The sound specifier that points the audio file(s) that should be played. /// The set of players that will hear the sound. /// The UID of the entity "emitting" the audio. - [return: NotNullIfNotNull("sound")] public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null) { return sound == null ? null : PlayEntity(GetSound(sound), playerFilter, uid, recordReplay, audioParams ?? sound.Params); @@ -516,7 +506,6 @@ public TimeSpan GetAudioLength(string filename) /// The sound specifier that points the audio file(s) that should be played. /// The player that will hear the sound. /// The UID of the entity "emitting" the audio. - [return: NotNullIfNotNull("sound")] public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) { return sound == null ? null : PlayEntity(GetSound(sound), recipient, uid, audioParams ?? sound.Params); @@ -528,7 +517,6 @@ public TimeSpan GetAudioLength(string filename) /// The sound specifier that points the audio file(s) that should be played. /// The player that will hear the sound. /// The UID of the entity "emitting" the audio. - [return: NotNullIfNotNull("sound")] public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) { return sound == null ? null : PlayEntity(GetSound(sound), recipient, uid, audioParams ?? sound.Params); @@ -539,7 +527,6 @@ public TimeSpan GetAudioLength(string filename) /// /// The sound specifier that points the audio file(s) that should be played. /// The UID of the entity "emitting" the audio. - [return: NotNullIfNotNull("sound")] public (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(SoundSpecifier? sound, EntityUid uid, AudioParams? audioParams = null) { return sound == null ? null : PlayPvs(GetSound(sound), uid, audioParams ?? sound.Params); @@ -550,7 +537,6 @@ public TimeSpan GetAudioLength(string filename) /// /// The sound specifier that points the audio file(s) that should be played. /// The EntityCoordinates to attach the audio source to. - [return: NotNullIfNotNull("sound")] public (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(SoundSpecifier? sound, EntityCoordinates coordinates, AudioParams? audioParams = null) { return sound == null ? null : PlayPvs(GetSound(sound), coordinates, audioParams ?? sound.Params); @@ -561,7 +547,6 @@ public TimeSpan GetAudioLength(string filename) /// /// The sound specifier that points the audio file(s) that should be played. /// The EntityCoordinates to attach the audio source to. - [return: NotNullIfNotNull("filename")] public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates, AudioParams? audioParams = null); @@ -570,7 +555,6 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// /// The resource path to the OGG Vorbis file to play. /// The UID of the entity "emitting" the audio. - [return: NotNullIfNotNull("filename")] public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null); @@ -580,7 +564,6 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// The sound specifier that points the audio file(s) that should be played. /// The UID of the entity "emitting" the audio. /// The UID of the user that initiated this sound. This is usually some player's controlled entity. - [return: NotNullIfNotNull("sound")] public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayLocal(SoundSpecifier? sound, EntityUid source, EntityUid? soundInitiator, AudioParams? audioParams = null); /// @@ -591,7 +574,6 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// The sound specifier that points the audio file(s) that should be played. /// The UID of the entity "emitting" the audio. /// The UID of the user that initiated this sound. This is usually some player's controlled entity. - [return: NotNullIfNotNull("sound")] public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null); /// @@ -602,7 +584,6 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// The sound specifier that points the audio file(s) that should be played. /// The entitycoordinates "emitting" the audio /// The UID of the user that initiated this sound. This is usually some player's controlled entity. - [return: NotNullIfNotNull("sound")] public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null); /// @@ -611,7 +592,6 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// The resource path to the OGG Vorbis file to play. /// The set of players that will hear the sound. /// The coordinates at which to play the audio. - [return: NotNullIfNotNull("filename")] public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null); /// @@ -620,7 +600,6 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// The resource path to the OGG Vorbis file to play. /// The player that will hear the sound. /// The coordinates at which to play the audio. - [return: NotNullIfNotNull("filename")] public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null); /// @@ -629,7 +608,6 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// The resource path to the OGG Vorbis file to play. /// The player that will hear the sound. /// The coordinates at which to play the audio. - [return: NotNullIfNotNull("filename")] public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null); /// @@ -638,7 +616,6 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// The sound specifier that points the audio file(s) that should be played. /// The set of players that will hear the sound. /// The coordinates at which to play the audio. - [return: NotNullIfNotNull("sound")] public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) { return sound == null ? null : PlayStatic(GetSound(sound), playerFilter, coordinates, recordReplay, audioParams); @@ -650,7 +627,6 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// The sound specifier that points the audio file(s) that should be played. /// The player that will hear the sound. /// The coordinates at which to play the audio. - [return: NotNullIfNotNull("sound")] public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) { return sound == null ? null : PlayStatic(GetSound(sound), recipient, coordinates, audioParams ?? sound.Params); @@ -662,7 +638,6 @@ public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs /// The sound specifier that points the audio file(s) that should be played. /// The player that will hear the sound. /// The coordinates at which to play the audio. - [return: NotNullIfNotNull("sound")] public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) { return sound == null ? null : PlayStatic(GetSound(sound), recipient, coordinates, audioParams ?? sound.Params); From afffb3344608df2fd6bdcce2a6da6ae396ff77d2 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:44:16 +1200 Subject: [PATCH 125/140] Stop empty audio system filters from playing sounds for all players (#5444) * Fix audio system empty filter bug * The nullable attributes are lying --- RELEASE-NOTES.md | 1 + Robust.Server/Audio/AudioSystem.cs | 22 ++++++++-------------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 7868d1caa86..e680b23661d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,6 +43,7 @@ END TEMPLATE--> ### Bugfixes +* Fixed filtered AudioSystem methods playing a sound for all players when given an empty filter. * Fixed equality checks for `MarkupNode` not properly handling attributes. * Fixed `MarkupNode` not having a `GetHashCode()` implementation. * Fixed a PVS error that could occur when trying to delete the first entity that gets created in a round. diff --git a/Robust.Server/Audio/AudioSystem.cs b/Robust.Server/Audio/AudioSystem.cs index 916d9ce0e32..981c01e770e 100644 --- a/Robust.Server/Audio/AudioSystem.cs +++ b/Robust.Server/Audio/AudioSystem.cs @@ -66,32 +66,26 @@ public override void SetMapAudio(Entity? audio) private void AddAudioFilter(EntityUid uid, AudioComponent component, Filter filter) { - var count = filter.Count; + DebugTools.Assert(component.IncludedEntities == null); + component.IncludedEntities = new(); - if (count == 0) + if (filter.Count == 0) return; _pvs.AddSessionOverrides(uid, filter); - - var ents = new HashSet(count); - foreach (var session in filter.Recipients) { - var ent = session.AttachedEntity; - - if (ent == null) - continue; - - ents.Add(ent.Value); + if (session.AttachedEntity is {} ent) + component.IncludedEntities.Add(ent); } - - DebugTools.Assert(component.IncludedEntities == null); - component.IncludedEntities = ents; } /// public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) { + if (string.IsNullOrEmpty(filename)) + return null; + var entity = SetupAudio(filename, audioParams); AddAudioFilter(entity, entity.Comp, playerFilter); entity.Comp.Global = true; From 9be0f032e80c6b4ebe0d1180a5835f6e7d95dbb1 Mon Sep 17 00:00:00 2001 From: eoineoineoin Date: Wed, 18 Sep 2024 02:45:01 +0100 Subject: [PATCH 126/140] Fix DistanceJoints drawn by physics debug system (#5439) Co-authored-by: Eoin Mcloughlin --- Robust.Client/Debugging/DebugPhysicsSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robust.Client/Debugging/DebugPhysicsSystem.cs b/Robust.Client/Debugging/DebugPhysicsSystem.cs index 757dd5a955a..c916c264648 100644 --- a/Robust.Client/Debugging/DebugPhysicsSystem.cs +++ b/Robust.Client/Debugging/DebugPhysicsSystem.cs @@ -544,7 +544,7 @@ private void DrawJoint(DrawingHandleWorld worldHandle, Joint joint) switch (joint) { case DistanceJoint: - worldHandle.DrawLine(xf1, xf2, JointColor); + worldHandle.DrawLine(p1, p2, JointColor); break; case PrismaticJoint prisma: var pA = Transform.Mul(xfa, joint.LocalAnchorA); From 0fa21ee2d235b9fba383f23aef978ccbad20d689 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Tue, 17 Sep 2024 18:48:13 -0700 Subject: [PATCH 127/140] Completely obsolete noSpawn (#5364) --- Robust.Shared/Prototypes/EntityPrototype.cs | 7 ------- Robust.Shared/Prototypes/PrototypeManager.Categories.cs | 4 ---- 2 files changed, 11 deletions(-) diff --git a/Robust.Shared/Prototypes/EntityPrototype.cs b/Robust.Shared/Prototypes/EntityPrototype.cs index 0e19eaeb438..e97d14b1071 100644 --- a/Robust.Shared/Prototypes/EntityPrototype.cs +++ b/Robust.Shared/Prototypes/EntityPrototype.cs @@ -96,16 +96,9 @@ public sealed partial class EntityPrototype : IPrototype, IInheritingPrototype, [DataField("localizationId")] public string? CustomLocalizationID { get; private set; } - /// /// If true, this object should not show up in the entity spawn panel. /// - [ViewVariables] - [NeverPushInheritance] - [DataField("noSpawn")] - [Obsolete("Use HideSpawnMenu")] - public bool NoSpawn { get; private set; } - [Access(typeof(PrototypeManager))] public bool HideSpawnMenu { get; internal set; } diff --git a/Robust.Shared/Prototypes/PrototypeManager.Categories.cs b/Robust.Shared/Prototypes/PrototypeManager.Categories.cs index 33389f95721..feba747cf2b 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.Categories.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.Categories.cs @@ -149,10 +149,6 @@ private IReadOnlySet UpdateCategories(EntProtoId id, categories.GetOrNew(category).Add(protoInstance); } -#pragma warning disable CS0618 // Type or member is obsolete - protoInstance.HideSpawnMenu |= protoInstance.NoSpawn; -#pragma warning restore CS0618 // Type or member is obsolete - return set; } } From 8d03feb84f0f6fff2531f6c6acd13adef25ca91e Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 18 Sep 2024 12:08:36 +1000 Subject: [PATCH 128/140] Transform precision thing (#5451) Just noticed it but probably doesn't affect anything really, we'll go from 64bit to 32bit after the math operations and not before. --- Robust.Shared/Physics/Transform.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Robust.Shared/Physics/Transform.cs b/Robust.Shared/Physics/Transform.cs index 8297079753b..149c11ce022 100644 --- a/Robust.Shared/Physics/Transform.cs +++ b/Robust.Shared/Physics/Transform.cs @@ -23,6 +23,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using JetBrains.Annotations; using Robust.Shared.Maths; using Robust.Shared.Utility; @@ -166,10 +167,10 @@ public Quaternion2D(float angle) public Quaternion2D(Angle angle) { - var radians = (float) angle.Theta; + var radians = angle.Theta; - C = MathF.Cos(radians); - S = MathF.Sin(radians); + C = (float) Math.Cos(radians); + S = (float) Math.Sin(radians); } public Quaternion2D Set(float angle) From c86cb0b7951cc4a662c7292138c5f45d868c5f58 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Wed, 18 Sep 2024 12:13:35 +1000 Subject: [PATCH 129/140] Version: 235.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 30 +++++++++++++++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 2b318b4e856..5936870c994 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 234.1.0 + 235.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index e680b23661d..9006162384d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,7 +35,7 @@ END TEMPLATE--> ### Breaking changes -* Several different `AudioSystem` methods were incorrectly given a `[return: NotNullIfNotNull]` attribute. Content code that uses these methods needs to be updated to perform null checks. +*None yet* ### New features @@ -43,6 +43,28 @@ END TEMPLATE--> ### Bugfixes +*None yet* + +### Other + +*None yet* + +### Internal + +*None yet* + + +## 235.0.0 + +### Breaking changes + +* Several different `AudioSystem` methods were incorrectly given a `[return: NotNullIfNotNull]` attribute. Content code that uses these methods needs to be updated to perform null checks. +* noSpawn is no longer obsolete and is now removed in lieu of the EntityCategory HideSpawnMenu. + +### Bugfixes + +* physics.maxlinvelocity is now a replicated cvar. +* Fix DistanceJoint debug drawing in physics not using the local anchors. * Fixed filtered AudioSystem methods playing a sound for all players when given an empty filter. * Fixed equality checks for `MarkupNode` not properly handling attributes. * Fixed `MarkupNode` not having a `GetHashCode()` implementation. @@ -52,11 +74,7 @@ END TEMPLATE--> ### Other -*None yet* - -### Internal - -*None yet* +* Improve precision for Quaternion2D constructor from angles. ## 234.1.0 From ad929c99558c2e5d59bf1bec21c70d60e0474526 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sat, 21 Sep 2024 02:43:12 +1200 Subject: [PATCH 130/140] Fix ICommonSession.Ping (#5453) --- RELEASE-NOTES.md | 2 +- Robust.Client/Player/PlayerManager.cs | 3 --- Robust.Server/Player/PlayerManager.cs | 3 +-- Robust.Shared/GameStates/SessionState.cs | 5 ++++- Robust.Shared/Network/Messages/MsgPlayerList.cs | 2 -- Robust.Shared/Player/CommonSession.cs | 7 ++++++- Robust.Shared/Player/ICommonSession.cs | 5 ++++- Robust.Shared/Player/SharedPlayerManager.State.cs | 3 +++ 8 files changed, 19 insertions(+), 11 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 9006162384d..2dcfd1f5b8a 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,7 +43,7 @@ END TEMPLATE--> ### Bugfixes -*None yet* +* Fixed `ICommonSession.Ping` always returning zero instead of the ping. Note that this will still return zero for client-side code when trying to get the ping of other players. ### Other diff --git a/Robust.Client/Player/PlayerManager.cs b/Robust.Client/Player/PlayerManager.cs index babeca1f5d4..376a12bef86 100644 --- a/Robust.Client/Player/PlayerManager.cs +++ b/Robust.Client/Player/PlayerManager.cs @@ -261,7 +261,6 @@ private bool UpdatePlayerList(IEnumerable remotePlayers, bool full // This is a new userid, so we create a new session. DebugTools.Assert(state.UserId != LocalPlayer?.UserId); var newSession = (ICommonSessionInternal)CreateAndAddSession(state.UserId, state.Name); - newSession.SetPing(state.Ping); SetStatus(newSession, state.Status); SetAttachedEntity(newSession, controlled, out _, true); dirty = true; @@ -271,7 +270,6 @@ private bool UpdatePlayerList(IEnumerable remotePlayers, bool full // Check if the data is actually different if (session.Name == state.Name && session.Status == state.Status - && session.Ping == state.Ping && session.AttachedEntity == controlled) { continue; @@ -280,7 +278,6 @@ private bool UpdatePlayerList(IEnumerable remotePlayers, bool full dirty = true; var local = (ICommonSessionInternal)session; local.SetName(state.Name); - local.SetPing(state.Ping); SetStatus(local, state.Status); SetAttachedEntity(local, controlled, out _, true); } diff --git a/Robust.Server/Player/PlayerManager.cs b/Robust.Server/Player/PlayerManager.cs index 33288620630..c9f11c70edf 100644 --- a/Robust.Server/Player/PlayerManager.cs +++ b/Robust.Server/Player/PlayerManager.cs @@ -137,8 +137,7 @@ private void HandlePlayerListReq(MsgPlayerListReq message) { UserId = client.UserId, Name = client.Name, - Status = client.Status, - Ping = client.Channel!.Ping + Status = client.Status }; list.Add(info); } diff --git a/Robust.Shared/GameStates/SessionState.cs b/Robust.Shared/GameStates/SessionState.cs index 78a193800ef..3fda4152cda 100644 --- a/Robust.Shared/GameStates/SessionState.cs +++ b/Robust.Shared/GameStates/SessionState.cs @@ -21,6 +21,10 @@ public sealed class SessionState [ViewVariables] public SessionStatus Status { get; set; } + // TODO PlayerManager + // Network ping information, though probably do it outside of SessionState to avoid re-sending the name and such + // for all players every few seconds. + [Obsolete("Ping data is not currently networked")] [ViewVariables] public short Ping { get; set; } @@ -34,7 +38,6 @@ public SessionState Clone() UserId = UserId, Name = Name, Status = Status, - Ping = Ping, ControlledEntity = ControlledEntity }; } diff --git a/Robust.Shared/Network/Messages/MsgPlayerList.cs b/Robust.Shared/Network/Messages/MsgPlayerList.cs index 113769ed963..ecfed856dcc 100644 --- a/Robust.Shared/Network/Messages/MsgPlayerList.cs +++ b/Robust.Shared/Network/Messages/MsgPlayerList.cs @@ -25,7 +25,6 @@ public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer UserId = new NetUserId(buffer.ReadGuid()), Name = buffer.ReadString(), Status = (SessionStatus)buffer.ReadByte(), - Ping = buffer.ReadInt16() }; Plyrs.Add(plyNfo); } @@ -40,7 +39,6 @@ public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer buffer.Write(ply.UserId.UserId); buffer.Write(ply.Name); buffer.Write((byte) ply.Status); - buffer.Write(ply.Ping); } } } diff --git a/Robust.Shared/Player/CommonSession.cs b/Robust.Shared/Player/CommonSession.cs index bf520b4ddd0..78166dac330 100644 --- a/Robust.Shared/Player/CommonSession.cs +++ b/Robust.Shared/Player/CommonSession.cs @@ -20,7 +20,12 @@ internal sealed class CommonSession : ICommonSessionInternal public string Name { get; set; } = ""; [ViewVariables] - public short Ping { get; set; } + public short Ping + { + get => Channel?.Ping ?? _ping; + set => _ping = value; + } + private short _ping; [ViewVariables] public DateTime ConnectedTime { get; set; } diff --git a/Robust.Shared/Player/ICommonSession.cs b/Robust.Shared/Player/ICommonSession.cs index 7e5c9dfc62e..94177574e91 100644 --- a/Robust.Shared/Player/ICommonSession.cs +++ b/Robust.Shared/Player/ICommonSession.cs @@ -33,9 +33,12 @@ public interface ICommonSession string Name { get; } /// - /// Current connection latency of this session from the server to their client. + /// Current connection latency of this session. If is not null this simply returns + /// . This is not currently usable by client-side code that wants to try access ping + /// information of other players. /// short Ping { get; } + // TODO PlayerManager ping networking. /// /// The current network channel for this session. diff --git a/Robust.Shared/Player/SharedPlayerManager.State.cs b/Robust.Shared/Player/SharedPlayerManager.State.cs index 19956162541..bbdd8831e3a 100644 --- a/Robust.Shared/Player/SharedPlayerManager.State.cs +++ b/Robust.Shared/Player/SharedPlayerManager.State.cs @@ -18,6 +18,9 @@ public void GetPlayerStates(GameTick fromTick, List states) if (LastStateUpdate < fromTick) return; + // TODO PlayerManager delta states + // Track last update tick/time per session, and only send sessions that actually changed. + states.EnsureCapacity(InternalSessions.Count); foreach (var player in InternalSessions.Values) { From 46291af1be5acd657b9f1fe2ab01842f3ff037f1 Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Sat, 21 Sep 2024 04:57:08 -0700 Subject: [PATCH 131/140] Add ProtoId parser to Toolshed (#5220) * Add ProtoId parser to Toolshed * Change obsolete FromMarkup to FromMarkupOrThrow --- .../Toolshed/TypeParsers/ProtoIdTypeParser.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Robust.Shared/Toolshed/TypeParsers/ProtoIdTypeParser.cs diff --git a/Robust.Shared/Toolshed/TypeParsers/ProtoIdTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/ProtoIdTypeParser.cs new file mode 100644 index 00000000000..99ca63f2949 --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/ProtoIdTypeParser.cs @@ -0,0 +1,59 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Toolshed.Syntax; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.TypeParsers; + +internal sealed class ProtoIdTypeParser : TypeParser> + where T : class, IPrototype +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + + public override bool TryParse(ParserContext parserContext, [NotNullWhen(true)] out object? result, out IConError? error) + { + var proto = parserContext.GetWord(ParserContext.IsToken); + + if (proto is null || !_prototype.HasMapping(proto)) + { + _prototype.TryGetKindFrom(out var kind); + DebugTools.AssertNotNull(kind); + + error = new NotAValidProtoId(proto ?? "[null]", kind!); + result = null; + return false; + } + + result = new ProtoId(proto); + error = null; + return true; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ParserContext parserContext, string? argName) + { + var options = CompletionHelper.PrototypeIDs(); + + _prototype.TryGetKindFrom(out var kind); + DebugTools.AssertNotNull(kind); + + return ValueTask.FromResult<(CompletionResult? result, IConError? error)>((CompletionResult.FromHintOptions(options, $"<{kind} protoId>"), null)); + } +} + +public record NotAValidProtoId(string Proto, string Kind) : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkupOrThrow($"{Proto} is not a valid {Kind} prototype"); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} From e714dcc83c5e967c9db19132561a4e025e2d6572 Mon Sep 17 00:00:00 2001 From: eoineoineoin Date: Sun, 22 Sep 2024 13:41:27 +0100 Subject: [PATCH 132/140] Fix TabContainer click detection when UIScale was not == 1.0 (#5456) * Fix tabcontainer click detection when UIScale was not == 1.0 * Remove whitespace --------- Co-authored-by: Eoin Mcloughlin --- .../UserInterface/Controls/TabContainer.cs | 45 +++++-------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/Robust.Client/UserInterface/Controls/TabContainer.cs b/Robust.Client/UserInterface/Controls/TabContainer.cs index 623ac18ac1a..5ffd9724ce1 100644 --- a/Robust.Client/UserInterface/Controls/TabContainer.cs +++ b/Robust.Client/UserInterface/Controls/TabContainer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Numerics; using Robust.Client.Graphics; using Robust.Shared.Input; @@ -21,6 +22,8 @@ public class TabContainer : Container private int _currentTab; private bool _tabsVisible = true; + // The right-most coordinate of each tab header + private List _tabRight = new(); public int CurrentTab { @@ -157,11 +160,14 @@ protected internal override void Draw(DrawingHandleScreen handle) var headerOffset = 0f; + _tabRight.Clear(); + // Then, draw the tabs. for (var i = 0; i < ChildCount; i++) { if (!GetTabVisible(i)) { + _tabRight.Add(headerOffset); continue; } @@ -214,6 +220,8 @@ protected internal override void Draw(DrawingHandleScreen handle) } headerOffset += boxAdvance; + // Remember the right-most point of this tab, for testing clicked areas + _tabRight.Add(headerOffset); } } @@ -283,46 +291,17 @@ protected internal override void KeyBindDown(GUIBoundKeyEventArgs args) args.Handle(); var relX = args.RelativePixelPosition.X; - - var font = _getFont(); - var boxActive = _getTabBoxActive(); - var boxInactive = _getTabBoxInactive(); - - var headerOffset = 0f; - + float tabLeft = 0; for (var i = 0; i < ChildCount; i++) { - if (!GetTabVisible(i)) + if (relX > tabLeft && relX <= _tabRight[i]) { - continue; - } - - var title = GetActualTabTitle(i); - - var titleLength = 0; - // Get string length. - foreach (var rune in title.EnumerateRunes()) - { - if (!font.TryGetCharMetrics(rune, UIScale, out var metrics)) - { - continue; - } - - titleLength += metrics.Advance; - } - - var active = _currentTab == i; - var box = active ? boxActive : boxInactive; - var boxAdvance = titleLength + (box?.MinimumSize.X ?? 0); - - if (headerOffset < relX && headerOffset + boxAdvance > relX) - { - // Got em. CurrentTab = i; return; } - headerOffset += boxAdvance; + // Next tab starts here + tabLeft = _tabRight[i]; } } From 41ec2dc1314383c5b822b52bf1c0eb68e30dc603 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:39:33 +1200 Subject: [PATCH 133/140] Try improve PVS exception tolerance a bit more (#5454) --- Robust.Server/GameStates/PvsChunk.cs | 10 ++++++---- Robust.Server/GameStates/PvsSystem.Chunks.cs | 1 + Robust.Server/GameStates/PvsSystem.cs | 3 ++- Robust.Shared/GameObjects/EntityManager.cs | 9 +++++++++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Robust.Server/GameStates/PvsChunk.cs b/Robust.Server/GameStates/PvsChunk.cs index 38ec670b45d..dc77564a1fb 100644 --- a/Robust.Server/GameStates/PvsChunk.cs +++ b/Robust.Server/GameStates/PvsChunk.cs @@ -141,9 +141,10 @@ public bool PopulateContents(EntityQuery meta, EntityQuery= EntityLifeStage.Terminating) { - DebugTools.Assert($"PVS chunk contains a deleted entity: {child}"); + DebugTools.Assert($"PVS chunk contains a delete or terminating entity: {child}"); MarkDirty(); return false; } @@ -188,9 +189,10 @@ public bool PopulateContents(EntityQuery meta, EntityQuery= EntityLifeStage.Terminating) { - DebugTools.Assert($"PVS chunk contains a deleted entity: {child}"); + DebugTools.Assert($"PVS chunk contains a delete or terminating entity: {child}"); MarkDirty(); return false; } diff --git a/Robust.Server/GameStates/PvsSystem.Chunks.cs b/Robust.Server/GameStates/PvsSystem.Chunks.cs index 40cafb0bb94..a7ac09043a4 100644 --- a/Robust.Server/GameStates/PvsSystem.Chunks.cs +++ b/Robust.Server/GameStates/PvsSystem.Chunks.cs @@ -232,6 +232,7 @@ internal void ProcessVisibleChunksSequential() [MethodImpl(MethodImplOptions.AggressiveInlining)] private void AddEntityToChunk(EntityUid uid, MetaDataComponent meta, PvsChunkLocation location) { + DebugTools.Assert(meta.EntityLifeStage < EntityLifeStage.Terminating); ref var chunk = ref CollectionsMarshal.GetValueRefOrAddDefault(_chunks, location, out var existing); if (!existing) { diff --git a/Robust.Server/GameStates/PvsSystem.cs b/Robust.Server/GameStates/PvsSystem.cs index 7a53079a01e..bb63968cd1b 100644 --- a/Robust.Server/GameStates/PvsSystem.cs +++ b/Robust.Server/GameStates/PvsSystem.cs @@ -129,7 +129,6 @@ public override void Initialize() SubscribeLocalEvent(OnMapChanged); SubscribeLocalEvent(OnGridRemoved); - SubscribeLocalEvent(OnEntityTerminating); SubscribeLocalEvent(OnTransformStartup); _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; @@ -137,6 +136,7 @@ public override void Initialize() EntityManager.EntityAdded += OnEntityAdded; EntityManager.EntityDeleted += OnEntityDeleted; EntityManager.AfterEntityFlush += AfterEntityFlush; + EntityManager.BeforeEntityTerminating += OnEntityTerminating; Subs.CVar(_configManager, CVars.NetPVS, SetPvs, true); Subs.CVar(_configManager, CVars.NetMaxUpdateRange, OnViewsizeChanged, true); @@ -162,6 +162,7 @@ public override void Shutdown() EntityManager.EntityAdded -= OnEntityAdded; EntityManager.EntityDeleted -= OnEntityDeleted; EntityManager.AfterEntityFlush -= AfterEntityFlush; + EntityManager.BeforeEntityTerminating -= OnEntityTerminating; _parallelMgr.ParallelCountChanged -= ResetParallelism; diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index bec9e9afbf0..6aa976d10b9 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -93,6 +93,14 @@ public abstract partial class EntityManager : IEntityManager public event Action>? EntityAdded; public event Action>? EntityInitialized; public event Action>? EntityDeleted; + + /// + /// Internal termination event handlers. This is mainly for exception tolerance, we want to ensure that PVS, + /// and other important engine systems can get updated before some content code throws an exception. + /// + internal event TerminatingEventHandler? BeforeEntityTerminating; + public delegate void TerminatingEventHandler(ref EntityTerminatingEvent ev); + public event Action? BeforeEntityFlush; public event Action? AfterEntityFlush; @@ -556,6 +564,7 @@ private void RecursiveFlagEntityTermination(EntityUid uid, try { var ev = new EntityTerminatingEvent((uid, metadata)); + BeforeEntityTerminating?.Invoke(ref ev); EventBus.RaiseLocalEvent(uid, ref ev, true); } catch (Exception e) From abb3f65fe42ca84e6717ea51600edad40d0802a2 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:40:16 +1200 Subject: [PATCH 134/140] Make EnsureEntityDictionary use TryAdd (#5461) --- RELEASE-NOTES.md | 1 + Robust.Shared/GameObjects/EntityManager.Network.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 2dcfd1f5b8a..a4a8cdca1e0 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,6 +43,7 @@ END TEMPLATE--> ### Bugfixes +* Auto-networked dictionaries now use `TryAdd()` to avoid duplicate key errors when a dictionary contains multiple unknown networked entities. * Fixed `ICommonSession.Ping` always returning zero instead of the ping. Note that this will still return zero for client-side code when trying to get the ping of other players. ### Other diff --git a/Robust.Shared/GameObjects/EntityManager.Network.cs b/Robust.Shared/GameObjects/EntityManager.Network.cs index df5cdcf5895..370d43269a0 100644 --- a/Robust.Shared/GameObjects/EntityManager.Network.cs +++ b/Robust.Shared/GameObjects/EntityManager.Network.cs @@ -391,7 +391,7 @@ public void EnsureEntityDictionary(Dictionary entities.EnsureCapacity(netEntities.Count); foreach (var pair in netEntities) { - entities.Add(EnsureEntity(pair.Key, callerEntity), pair.Value); + entities.TryAdd(EnsureEntity(pair.Key, callerEntity), pair.Value); } } @@ -402,7 +402,7 @@ public void EnsureEntityDictionaryNullableValue(Dictionary(pair.Key, callerEntity), pair.Value); + entities.TryAdd(EnsureEntity(pair.Key, callerEntity), pair.Value); } } @@ -413,7 +413,7 @@ public void EnsureEntityDictionary(Dictionary netE entities.EnsureCapacity(netEntities.Count); foreach (var pair in netEntities) { - entities.Add(pair.Key, EnsureEntity(pair.Value, callerEntity)); + entities.TryAdd(pair.Key, EnsureEntity(pair.Value, callerEntity)); } } @@ -424,7 +424,7 @@ public void EnsureEntityDictionary(Dictionary net entities.EnsureCapacity(netEntities.Count); foreach (var pair in netEntities) { - entities.Add(pair.Key, EnsureEntity(pair.Value, callerEntity)); + entities.TryAdd(pair.Key, EnsureEntity(pair.Value, callerEntity)); } } @@ -435,7 +435,7 @@ public void EnsureEntityDictionary(Dictionary netEn entities.EnsureCapacity(netEntities.Count); foreach (var pair in netEntities) { - entities.Add(EnsureEntity(pair.Key, callerEntity), EnsureEntity(pair.Value, callerEntity)); + entities.TryAdd(EnsureEntity(pair.Key, callerEntity), EnsureEntity(pair.Value, callerEntity)); } } @@ -446,7 +446,7 @@ public void EnsureEntityDictionary(Dictionary netE entities.EnsureCapacity(netEntities.Count); foreach (var pair in netEntities) { - entities.Add(EnsureEntity(pair.Key, callerEntity), EnsureEntity(pair.Value, callerEntity)); + entities.TryAdd(EnsureEntity(pair.Key, callerEntity), EnsureEntity(pair.Value, callerEntity)); } } From b84917e8e422a9cf77be64a96c4d31687e8d196b Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:40:42 +1200 Subject: [PATCH 135/140] Obsolete some static localization methods (#5460) --- Robust.Shared/Localization/Loc.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Robust.Shared/Localization/Loc.cs b/Robust.Shared/Localization/Loc.cs index 2e19b6ac6cc..e12decae8ab 100644 --- a/Robust.Shared/Localization/Loc.cs +++ b/Robust.Shared/Localization/Loc.cs @@ -1,8 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using JetBrains.Annotations; -using Robust.Shared.ContentPack; using Robust.Shared.IoC; namespace Robust.Shared.Localization @@ -36,6 +34,7 @@ public static string GetString(string messageId) return LocalizationManager.GetString(messageId); } + [Obsolete("Use ILocalizationManager")] public static bool TryGetString(string messageId, [NotNullWhen(true)] out string? message) { return LocalizationManager.TryGetString(messageId, out message); @@ -49,6 +48,7 @@ public static string GetString(string messageId, params (string,object)[] args) return LocalizationManager.GetString(messageId, args); } + [Obsolete("Use ILocalizationManager")] public static bool TryGetString( string messageId, [NotNullWhen(true)] out string? value, From dbe297b1fc9bfd83996f648b6bab75291d8988d6 Mon Sep 17 00:00:00 2001 From: Stalen <33173619+stalengd@users.noreply.github.com> Date: Tue, 24 Sep 2024 02:43:00 +0300 Subject: [PATCH 136/140] Activate XAML hot reload on file rename (for VS support) (#5429) --- .../UserInterface/XAML/Proxy/XamlHotReloadManager.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Robust.Client/UserInterface/XAML/Proxy/XamlHotReloadManager.cs b/Robust.Client/UserInterface/XAML/Proxy/XamlHotReloadManager.cs index 39669abaa3a..d40fd02c13b 100644 --- a/Robust.Client/UserInterface/XAML/Proxy/XamlHotReloadManager.cs +++ b/Robust.Client/UserInterface/XAML/Proxy/XamlHotReloadManager.cs @@ -58,16 +58,16 @@ private FileSystemWatcher CreateWatcher(string location) var watcher = new FileSystemWatcher(location) { IncludeSubdirectories = true, - NotifyFilter = NotifyFilters.LastWrite, + NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName, }; - watcher.Changed += (_, args) => + void OnWatcherEvent(object sender, FileSystemEventArgs args) { switch (args.ChangeType) { - case WatcherChangeTypes.Renamed: case WatcherChangeTypes.Deleted: return; + case WatcherChangeTypes.Renamed: case WatcherChangeTypes.Created: case WatcherChangeTypes.Changed: case WatcherChangeTypes.All: @@ -98,7 +98,10 @@ private FileSystemWatcher CreateWatcher(string location) _xamlProxyManager.SetImplementation(resourceFileName, newText); }); - }; + } + + watcher.Changed += OnWatcherEvent; + watcher.Renamed += OnWatcherEvent; watcher.EnableRaisingEvents = true; return watcher; } From fb9b0ae89be0cfe8745b3206b894a6b3d0859907 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 28 Sep 2024 14:13:05 +1000 Subject: [PATCH 137/140] Remove IsTouching set on physics prediction (#5468) Just because an entity sleeps doesn't mean it's not touching necessarily. This causes client to mispredict against server and continuously fire collision events if we try to move into an entity. Easiest way to reproduced is to walk into a locked airlock and watch it flicker constantly. --- Robust.Client/Physics/PhysicsSystem.Predict.cs | 1 - Robust.Shared/Physics/Dynamics/Contacts/Contact.cs | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Robust.Client/Physics/PhysicsSystem.Predict.cs b/Robust.Client/Physics/PhysicsSystem.Predict.cs index af16a3fd023..c21ea92d38d 100644 --- a/Robust.Client/Physics/PhysicsSystem.Predict.cs +++ b/Robust.Client/Physics/PhysicsSystem.Predict.cs @@ -156,7 +156,6 @@ internal void UpdateIsTouching(List toUpdate) if (activeA == false && activeB == false) { - contact.IsTouching = false; continue; } diff --git a/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs b/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs index e588fc93952..fb27d6eae5a 100644 --- a/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs +++ b/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs @@ -35,6 +35,7 @@ using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; +using Robust.Shared.ViewVariables; namespace Robust.Shared.Physics.Dynamics.Contacts { @@ -94,11 +95,13 @@ internal Contact(IManifoldManager manifoldManager) /// /// Determines whether the contact is touching. /// + [ViewVariables] public bool IsTouching { get; internal set; } /// Enable/disable this contact. This can be used inside the pre-solve /// contact listener. The contact is only disabled for the current /// time step (or sub-step in continuous collisions). + [ViewVariables] public bool Enabled { get; set; } /// From 74e7e61a98f12c2b729cce75337cb5f3bdfd13fa Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 28 Sep 2024 14:33:37 +1000 Subject: [PATCH 138/140] Revert "Make resetting contacts on the client only set is touching if it is true" (#5469) This reverts commit cdb94748c8cebad7633f056d01bdf6520888a93c. --- Robust.Shared/Physics/Dynamics/Contacts/Contact.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs b/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs index fb27d6eae5a..177cc73da01 100644 --- a/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs +++ b/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs @@ -31,6 +31,8 @@ using System.Collections.Generic; using System.Numerics; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Maths; using Robust.Shared.Physics.Collision; using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Components; @@ -259,9 +261,7 @@ internal void UpdateIsTouching(Transform bodyATransform, Transform bodyBTransfor { var manifold = Manifold; Evaluate(ref manifold, bodyATransform, bodyBTransform); - - if (IsTouching) - IsTouching = manifold.PointCount > 0; + IsTouching = manifold.PointCount > 0; } } From f0ed3537ee0c9302fae76ac31da7286a5eea2da4 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 28 Sep 2024 07:35:18 +0200 Subject: [PATCH 139/140] Duplicate dependency field analyzer (#5463) * Duplicate dependency field analyzer Detects cases of duplicate [Dependency] fields in a type. We apparently have 27 of these across RT + SS14. * Fix duplicate dependencies in Robust --- .../DuplicateDependencyAnalyzerTest.cs | 63 +++++++++ .../DuplicateDependencyAnalyzer.cs | 126 ++++++++++++++++++ Robust.Roslyn.Shared/Diagnostics.cs | 1 + .../Systems/SharedUserInterfaceSystem.cs | 3 +- .../Systems/SharedPhysicsSystem.Island.cs | 28 ++-- .../Physics/Systems/SharedPhysicsSystem.cs | 11 +- .../Shared/IoC/IoCManager_Test.cs | 2 + 7 files changed, 212 insertions(+), 22 deletions(-) create mode 100644 Robust.Analyzers.Tests/DuplicateDependencyAnalyzerTest.cs create mode 100644 Robust.Analyzers/DuplicateDependencyAnalyzer.cs diff --git a/Robust.Analyzers.Tests/DuplicateDependencyAnalyzerTest.cs b/Robust.Analyzers.Tests/DuplicateDependencyAnalyzerTest.cs new file mode 100644 index 00000000000..94d801cd4af --- /dev/null +++ b/Robust.Analyzers.Tests/DuplicateDependencyAnalyzerTest.cs @@ -0,0 +1,63 @@ +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] +[TestOf(typeof(DuplicateDependencyAnalyzer))] +public sealed class DuplicateDependencyAnalyzerTest +{ + private static Task Verifier(string code, params DiagnosticResult[] expected) + { + var test = new CSharpAnalyzerTest() + { + TestState = + { + Sources = { code } + }, + }; + + TestHelper.AddEmbeddedSources( + test.TestState, + "Robust.Shared.IoC.DependencyAttribute.cs" + ); + + // 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 Robust.Shared.IoC; + + public sealed class Foo + { + [Dependency] + private object? Field; + + [Dependency] + private object? Field2; + + [Dependency] + private string? DifferentField; + + private string? NonDependency1; + private string? NonDependency2; + } + """; + + await Verifier(code, + // /0/Test0.cs(9,21): warning RA0032: Another [Dependency] field of type 'object?' already exists in this type as field 'Field' + VerifyCS.Diagnostic().WithSpan(9, 21, 9, 27).WithArguments("object?", "Field")); + } +} diff --git a/Robust.Analyzers/DuplicateDependencyAnalyzer.cs b/Robust.Analyzers/DuplicateDependencyAnalyzer.cs new file mode 100644 index 00000000000..5fdfbe3e9a4 --- /dev/null +++ b/Robust.Analyzers/DuplicateDependencyAnalyzer.cs @@ -0,0 +1,126 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Robust.Roslyn.Shared; + +namespace Robust.Analyzers; + +#nullable enable + +/// +/// Analyzer that detects duplicate [Dependency] fields inside a single type. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class DuplicateDependencyAnalyzer : DiagnosticAnalyzer +{ + private const string DependencyAttributeType = "Robust.Shared.IoC.DependencyAttribute"; + + private static readonly DiagnosticDescriptor Rule = new( + Diagnostics.IdDuplicateDependency, + "Duplicate dependency field", + "Another [Dependency] field of type '{0}' already exists in this type with field '{1}'", + "Usage", + DiagnosticSeverity.Warning, + true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(compilationContext => + { + var dependencyAttributeType = compilationContext.Compilation.GetTypeByMetadataName(DependencyAttributeType); + if (dependencyAttributeType == null) + return; + + compilationContext.RegisterSymbolStartAction(symbolContext => + { + var typeSymbol = (INamedTypeSymbol)symbolContext.Symbol; + // Only deal with non-static classes, doesn't make sense to have dependencies in anything else. + if (typeSymbol.TypeKind != TypeKind.Class || typeSymbol.IsStatic) + return; + + var state = new AnalyzerState(dependencyAttributeType); + symbolContext.RegisterSyntaxNodeAction(state.AnalyzeField, SyntaxKind.FieldDeclaration); + symbolContext.RegisterSymbolEndAction(state.End); + }, + SymbolKind.NamedType); + }); + } + + private sealed class AnalyzerState(INamedTypeSymbol dependencyAttributeType) + { + private readonly Dictionary> _dependencyFields = new(SymbolEqualityComparer.Default); + + public void AnalyzeField(SyntaxNodeAnalysisContext context) + { + var field = (FieldDeclarationSyntax)context.Node; + if (field.AttributeLists.Count == 0) + return; + + if (context.ContainingSymbol is not IFieldSymbol fieldSymbol) + return; + + // Can't have [Dependency]s for non-reference types. + if (!fieldSymbol.Type.IsReferenceType) + return; + + if (!IsDependency(context.ContainingSymbol)) + return; + + lock (_dependencyFields) + { + if (!_dependencyFields.TryGetValue(fieldSymbol.Type, out var dependencyFields)) + { + dependencyFields = []; + _dependencyFields.Add(fieldSymbol.Type, dependencyFields); + } + + dependencyFields.Add(fieldSymbol); + } + } + + private bool IsDependency(ISymbol symbol) + { + foreach (var attributeData in symbol.GetAttributes()) + { + if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, dependencyAttributeType)) + return true; + } + + return false; + } + + public void End(SymbolAnalysisContext context) + { + lock (_dependencyFields) + { + foreach (var pair in _dependencyFields) + { + var fieldType = pair.Key; + var fields = pair.Value; + if (fields.Count <= 1) + continue; + + // Sort so we can have deterministic order to skip reporting for a single field. + // Whichever sorts first doesn't get reported. + fields.Sort(static (a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal)); + + // Start at index 1 to skip first field. + var firstField = fields[0]; + for (var i = 1; i < fields.Count; i++) + { + var field = fields[i]; + + context.ReportDiagnostic( + Diagnostic.Create(Rule, field.Locations[0], fieldType.ToDisplayString(), firstField.Name)); + } + } + } + } + } +} diff --git a/Robust.Roslyn.Shared/Diagnostics.cs b/Robust.Roslyn.Shared/Diagnostics.cs index ec0664fbb31..4b35d44d7de 100644 --- a/Robust.Roslyn.Shared/Diagnostics.cs +++ b/Robust.Roslyn.Shared/Diagnostics.cs @@ -35,6 +35,7 @@ public static class Diagnostics public const string IdDataFieldNoVVReadWrite = "RA0029"; public const string IdUseNonGenericVariant = "RA0030"; public const string IdPreferOtherType = "RA0031"; + public const string IdDuplicateDependency = "RA0032"; public static SuppressionDescriptor MeansImplicitAssignment => new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned."); diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 0305cbb37ea..f08e5520083 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -23,7 +23,6 @@ public abstract class SharedUserInterfaceSystem : EntitySystem [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly IParallelManager _parallel = default!; - [Dependency] private readonly ISharedPlayerManager _player = default!; [Dependency] protected readonly IPrototypeManager ProtoManager = default!; [Dependency] private readonly IReflectionManager _reflection = default!; [Dependency] protected readonly ISharedPlayerManager Player = default!; @@ -390,7 +389,7 @@ private void OnUserInterfaceHandleState(Entity ent, ref ent.Comp.States.Remove(key); } - var attachedEnt = _player.LocalEntity; + var attachedEnt = Player.LocalEntity; var clientBuis = new ValueList(ent.Comp.ClientOpenInterfaces.Keys); // Check if the UI is open by us, otherwise dispose of it. diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs index b084b694589..b3fbc3b344c 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Island.cs @@ -198,20 +198,20 @@ internal record struct IslandData( private void InitializeIsland() { - Subs.CVar(_configManager, CVars.NetTickrate, SetTickRate, true); - Subs.CVar(_configManager, CVars.WarmStarting, SetWarmStarting, true); - Subs.CVar(_configManager, CVars.MaxLinearCorrection, SetMaxLinearCorrection, true); - Subs.CVar(_configManager, CVars.MaxAngularCorrection, SetMaxAngularCorrection, true); - Subs.CVar(_configManager, CVars.VelocityIterations, SetVelocityIterations, true); - Subs.CVar(_configManager, CVars.PositionIterations, SetPositionIterations, true); - Subs.CVar(_configManager, CVars.MaxLinVelocity, SetMaxLinearVelocity, true); - Subs.CVar(_configManager, CVars.MaxAngVelocity, SetMaxAngularVelocity, true); - Subs.CVar(_configManager, CVars.SleepAllowed, SetSleepAllowed, true); - Subs.CVar(_configManager, CVars.AngularSleepTolerance, SetAngularToleranceSqr, true); - Subs.CVar(_configManager, CVars.LinearSleepTolerance, SetLinearToleranceSqr, true); - Subs.CVar(_configManager, CVars.TimeToSleep, SetTimeToSleep, true); - Subs.CVar(_configManager, CVars.VelocityThreshold, SetVelocityThreshold, true); - Subs.CVar(_configManager, CVars.Baumgarte, SetBaumgarte, true); + Subs.CVar(_cfg, CVars.NetTickrate, SetTickRate, true); + Subs.CVar(_cfg, CVars.WarmStarting, SetWarmStarting, true); + Subs.CVar(_cfg, CVars.MaxLinearCorrection, SetMaxLinearCorrection, true); + Subs.CVar(_cfg, CVars.MaxAngularCorrection, SetMaxAngularCorrection, true); + Subs.CVar(_cfg, CVars.VelocityIterations, SetVelocityIterations, true); + Subs.CVar(_cfg, CVars.PositionIterations, SetPositionIterations, true); + Subs.CVar(_cfg, CVars.MaxLinVelocity, SetMaxLinearVelocity, true); + Subs.CVar(_cfg, CVars.MaxAngVelocity, SetMaxAngularVelocity, true); + Subs.CVar(_cfg, CVars.SleepAllowed, SetSleepAllowed, true); + Subs.CVar(_cfg, CVars.AngularSleepTolerance, SetAngularToleranceSqr, true); + Subs.CVar(_cfg, CVars.LinearSleepTolerance, SetLinearToleranceSqr, true); + Subs.CVar(_cfg, CVars.TimeToSleep, SetTimeToSleep, true); + Subs.CVar(_cfg, CVars.VelocityThreshold, SetVelocityThreshold, true); + Subs.CVar(_cfg, CVars.Baumgarte, SetBaumgarte, true); } private void SetWarmStarting(bool value) => _warmStarting = value; diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs index 7ee351b792b..721af0a365c 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs @@ -43,7 +43,6 @@ public abstract partial class SharedPhysicsSystem : EntitySystem Buckets = Histogram.ExponentialBuckets(0.000_001, 1.5, 25) }); - [Dependency] private readonly IConfigurationManager _configManager = default!; [Dependency] private readonly IManifoldManager _manifoldManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IParallelManager _parallel = default!; @@ -97,9 +96,9 @@ public override void Initialize() InitializeIsland(); InitializeContacts(); - Subs.CVar(_configManager, CVars.AutoClearForces, OnAutoClearChange); - Subs.CVar(_configManager, CVars.NetTickrate, UpdateSubsteps, true); - Subs.CVar(_configManager, CVars.TargetMinimumTickrate, UpdateSubsteps, true); + Subs.CVar(_cfg, CVars.AutoClearForces, OnAutoClearChange); + Subs.CVar(_cfg, CVars.NetTickrate, UpdateSubsteps, true); + Subs.CVar(_cfg, CVars.TargetMinimumTickrate, UpdateSubsteps, true); } private void OnPhysicsShutdown(EntityUid uid, PhysicsComponent component, ComponentShutdown args) @@ -143,8 +142,8 @@ private void OnAutoClearChange(bool value) private void UpdateSubsteps(int obj) { - var targetMinTickrate = (float) _configManager.GetCVar(CVars.TargetMinimumTickrate); - var serverTickrate = (float) _configManager.GetCVar(CVars.NetTickrate); + var targetMinTickrate = (float) _cfg.GetCVar(CVars.TargetMinimumTickrate); + var serverTickrate = (float) _cfg.GetCVar(CVars.NetTickrate); _substeps = (int)Math.Ceiling(targetMinTickrate / serverTickrate); } diff --git a/Robust.UnitTesting/Shared/IoC/IoCManager_Test.cs b/Robust.UnitTesting/Shared/IoC/IoCManager_Test.cs index 3450b157211..1811962f9c1 100644 --- a/Robust.UnitTesting/Shared/IoC/IoCManager_Test.cs +++ b/Robust.UnitTesting/Shared/IoC/IoCManager_Test.cs @@ -258,6 +258,7 @@ public virtual void Test() public sealed class TestFieldInjection : TestFieldInjectionParent { +#pragma warning disable RA0032 // Duplicate [Dependency] field. I wrote this test 7 years idk if this makes sense. [Dependency] #pragma warning disable 649 private readonly TestFieldInjection myuniqueself = default!; @@ -265,6 +266,7 @@ public sealed class TestFieldInjection : TestFieldInjectionParent [Dependency] public TestFieldInjection mydifferentself = default!; #pragma warning restore 649 +#pragma warning restore RA0032 public override void Test() { From 1c3ea968e4ab66d57ffe014f6ee207e4d0d2c403 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sat, 28 Sep 2024 19:16:35 +1000 Subject: [PATCH 140/140] Version: 236.0.0 --- MSBuild/Robust.Engine.Version.props | 2 +- RELEASE-NOTES.md | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props index 5936870c994..28247977453 100644 --- a/MSBuild/Robust.Engine.Version.props +++ b/MSBuild/Robust.Engine.Version.props @@ -1,4 +1,4 @@ - 235.0.0 + 236.0.0 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index a4a8cdca1e0..98785c46ea1 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -43,8 +43,7 @@ END TEMPLATE--> ### Bugfixes -* Auto-networked dictionaries now use `TryAdd()` to avoid duplicate key errors when a dictionary contains multiple unknown networked entities. -* Fixed `ICommonSession.Ping` always returning zero instead of the ping. Note that this will still return zero for client-side code when trying to get the ping of other players. +*None yet* ### Other @@ -55,6 +54,30 @@ END TEMPLATE--> *None yet* +## 236.0.0 + +### Breaking changes + +* Revert IsTouching only being set to true if the contact were laready touching in clientside physics prediction. +* Don't touch IsTouching if both bodies are asleep for clientside physics contacts. This change and the one above should fix a lot of clientside contact issues, particularly around repeated incorrect clientside contact events. + +### New features + +* Added an analyzer to detect duplicate Dependency fields. + +### Bugfixes + +* Auto-networked dictionaries now use `TryAdd()` to avoid duplicate key errors when a dictionary contains multiple unknown networked entities. +* Fixed `ICommonSession.Ping` always returning zero instead of the ping. Note that this will still return zero for client-side code when trying to get the ping of other players. +* Hot reload XAML files on rename to fix them potentially not being reloaded with Visual Studio. +* Fix TabContainer click detection for non-1.0 UI scales. + +### Other + +* Obsolete some static localization methods. +* Tried to improve PVS tolerance to exceptions occurring. + + ## 235.0.0 ### Breaking changes