Compare commits

...

50 Commits

Author SHA1 Message Date
Visne
e93c0f76a9 Adds SetButtonDisabledRecursive() method to Control (#1901) 2021-07-27 20:08:24 +02:00
metalgearsloth
7bac32d18e Don't use DeferMoveEvent if the position is NaN (#1896)
The issue is that currently moving around means any non-anchored entities have their positions updated to / from NaN. As you can imagine this is a performance nightmare for anything subscribing to it, especially considering it leads to the broadphase getting desynced for physics.

Realistically we need one of the alternatives Acruid has laid out because flooding the eventbus with NaNs will kill performance.
2021-07-28 00:28:07 +10:00
Pieter-Jan Briers
b6b1d46892 Ignore "CON" in DebugConsoleLogHandler. 2021-07-27 09:38:08 +02:00
Vera Aguilera Puerto
6f0bc3822e InputComponent is now public. 2021-07-27 08:51:02 +02:00
Vera Aguilera Puerto
b7c8452285 Use EntitySystem dependencies in a few places that cached systems before. 2021-07-26 12:57:39 +02:00
Paul Ritter
8c1e075c91 EntitySystem DependencyCollection & EntitySystem dependencies (#1890)
* started work

* tests

* namespace

* working

* thonk

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
Co-authored-by: Vera Aguilera Puerto <gradientvera@outlook.com>
2021-07-26 12:10:03 +02:00
metalgearsloth
b340e40c99 Cache physics transforms internally (#1873)
* Cache physics transforms internally

3 main points to cache them for:
1. Contact updates
2. Physics Islands solving
3. GetWorldManifold (though this is primarily for ss14).

Whenever multi-threading is done it will need adjustments to make sure no race conditions on accessing the transforms dictionary

* Also cache position and angle for GetWorldAABB

* Fix boogs

* woops
2021-07-26 18:23:10 +10:00
metalgearsloth
c4b124f48d Cleanup physics contacts a bit (#1895)
Removed a lot of the listener comments as we use the eventbus instead in this house.
2021-07-25 23:40:45 +10:00
metalgearsloth
7efae8fbc1 Remove IStartCollide (#1875)
* Remove IStartCollide

Needs several content PRs to be merged as well.

* Feex
2021-07-25 23:06:34 +10:00
Pieter-Jan Briers
7feeeb2f6f OOps 2021-07-25 14:50:14 +02:00
mirrorcult
f90462cf82 Improve RSI error messages (#1894) 2021-07-25 12:06:36 +02:00
metalgearsloth
b19ae9e69e Add AABB VVs for debugging 2021-07-25 19:04:25 +10:00
metalgearsloth
2132d6cbae Comment out the contactmanager crash safeguard for now 2021-07-24 13:50:27 +10:00
Ygg01
d2d6f9d08e Update Linguini to latest to fix escaping text literals (#1880) 2021-07-23 17:43:14 +02:00
metalgearsloth
4b58fcbff2 Fix anchoring on moved grid (#1885) 2021-07-23 10:16:19 +02:00
metalgearsloth
f83f6a8cd6 Remove manifolds from collision events
Nothing uses em anymore.
2021-07-23 13:06:24 +10:00
metalgearsloth
dfd7711506 Stop EndCollision from throwing exceptions
Won't fix the underlying problem but will log it and handle it more gracefully at least until I can reproduce it.
2021-07-23 13:03:54 +10:00
Pieter-Jan Briers
78f9d92c07 Add exception tolerance to DispatchEntityNetworkMessage. 2021-07-22 23:27:39 +02:00
Paul
3a86c827ea made xamlil errors show up in msbuild 2021-07-22 20:01:15 +02:00
metalgearsloth
325f25c547 Fix occluders for moved grid (#1878)
* Refactor occluders

* Copy pasting

* Reduce bounds

* Clear system updates on shutdown
2021-07-22 13:07:45 +10:00
Pieter-Jan Briers
be57b5d20b Add AnyCommandExecuted callback to IConsoleHost.
Intended use case here is for content to listen to ConCmds for AFK detection.
2021-07-21 21:32:32 +02:00
Pieter-Jan Briers
7124d86f94 Allow localization to pass through TimeSpan values. 2021-07-21 18:58:43 +02:00
Pieter-Jan Briers
229380a71d Add TimeSpan read/write helpers to NetMessageExt 2021-07-21 16:25:15 +02:00
metalgearsloth
e9eb536df5 Don't get fixture pairs if they're deleted 2021-07-21 23:08:10 +10:00
metalgearsloth
22297ef6d8 Out of the way System.Numerics 2021-07-21 21:14:41 +10:00
metalgearsloth
7f2e433087 Broadphase refactor (#1848)
* Broadphase refactor

* Stuff

* Working

* TODO

* Changes

* Which fucking madman came up with this shit

* Known gud state

* Kinda shitcodey but it works so fuck it doin it live

* Shuttle jankiness

* Refactor entitylookups to be 30% more based

* Refactor gucci

* Done?

* Fix most tests

* nothing suss

* Vera single-handedly saving shuttles

* fex

* Fix renderingtreecomp for relativity

* Fix IEntityLookup

* Fixes

* Testing jank please revert some of it before merging

* Fix the remaining bugs

* Fix crash

* Fix grid physics initialization

* Shuttle collisions working

* Fixes

* Fixes

* Velocity on transfers

* Velocity on parent change

* Fixes

* world angular velocity too

* Slightly faster map velocities

* showbb revert

* Sketch grids kinda workin

* Fix PVS contact crash

* Cleanup gridfixture updates 0.1%

* Grid fixtures

* AAAAAAAAAAAAAAA

* a

* termp

* Fixes

* Test reversion reversion?

* Tests

* Fix Equals

* Slight box2i cleanup

* Better initializer

* Fix merge issues

* Shuttles go BRRT

* Remove MoveProxy from DynamicTreeBroadphase

* Optimise a shit tonne

* Approx

* fix showbb

* clean

* Slightly more optimised

* My almonds are activating

* Contact transform caching

* Avoid duplicates

* Typo

* Logger

* Jitter 1% better

* Okay shit maybe that's not it.

* Contact fixes

* Move check thingy up front

* Revert some jank caching

* Contact filtering

* Fix master merge

* Test fixes

* Re-fix MapLoaderTest

* Use proxies instead

* uhh wtf?

* Woops

* Fix collisions

* Fix grid fixture generation

* Fix deserializing

* Tile window fix

* Cleanup broadphase

* Bit more cleanup

Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
2021-07-21 21:12:58 +10:00
Pieter-Jan Briers
18c32a0258 Don't ServerSendToAll to channels without completed handshake. 2021-07-21 03:06:32 +02:00
Pieter-Jan Briers
72314a102d EventBus improvements
Can do ordered session event subscription.

Can subscribe to "All" in one call, to reduce shared boilerplate.
2021-07-21 00:56:42 +02:00
Pieter-Jan Briers
719ea26a31 IConfigurationManager.UnsubValueChanged. 2021-07-20 17:26:52 +02:00
metalgearsloth
5cb8fe1897 End IEndCollide (#1874) 2021-07-20 17:01:30 +02:00
metalgearsloth
f35a52fc24 Change KinematicControllerCollision to use WorldNormal instead (#1872)
Content cares about the WorldNormal and not the LocalNormal so this fixes pushing bugs.
2021-07-19 10:17:39 +02:00
metalgearsloth
6bdb0cef47 Fix IMapGrid WorldToLocal + LocalToWorld (#1871) 2021-07-19 10:17:15 +02:00
Pieter-Jan Briers
fe3c9fe28f Fix late nwVar warning on connect. 2021-07-19 10:16:24 +02:00
Pieter-Jan Briers
6085671f22 Fix memory leak with physics islands and contact solvers.
They were doing config OnValueChanged for every instance and, as such, never getting released.
2021-07-19 10:06:20 +02:00
Visne
a2398da324 Replace most VBox/HBoxContainers with BoxContainers (#1867) 2021-07-18 18:42:08 +02:00
Pieter-Jan Briers
b27304cc58 Add System.Index and System.Range to sandbox whitelist. 2021-07-17 23:51:57 +02:00
Swept
3bf851a6cf FPS Counter no longer shows a decimal 2021-07-17 16:46:09 +00:00
Pieter-Jan Briers
cef92efd0f NetManager now enforces that only specific messages can be sent before serializer handshake completes. 2021-07-17 14:37:40 +02:00
Pieter-Jan Briers
c5961a5ab1 Fix possible race condition in net var handling and improve logging. 2021-07-17 14:37:35 +02:00
Pieter-Jan Briers
8ddd92993d Don't do initial net var stuff on server. 2021-07-17 02:03:37 +02:00
Pieter-Jan Briers
da253a5f34 Log user ID correctly on connection approval 2021-07-17 02:02:59 +02:00
Pieter-Jan Briers
ca9400a1ff Don't log encryption secrets on auth handshake. 2021-07-17 01:24:39 +02:00
Pieter-Jan Briers
f232195ceb Fix audio log warning not using interpolated string correctly. 2021-07-17 01:17:23 +02:00
Pieter-Jan Briers
b54a803519 Add ToString to NetChannel.
Some things already try to log like this so let's just go with it.
2021-07-17 01:16:47 +02:00
Vera Aguilera Puerto
a0d3d2108f Use ResourcePath instead of Path.Join 2021-07-16 21:48:19 +02:00
Vera Aguilera Puerto
977e4a017b Fixes tile window hardcoding tile sprites. 2021-07-16 08:21:58 +02:00
Swept
2d8b159016 Updates the dumb shit stupid path for tile window 2021-07-16 05:38:19 +00:00
Visne
9caa0dde4b Remove unused IEntityManager parameter from EntityCoordinates.FromMap() (#1866) 2021-07-15 18:51:11 +02:00
Pieter-Jan Briers
7a5a8c5eb1 Remove unused Process.WaitForExitAsync helper.
.NET 5 has its own implementation so we use that now.
2021-07-15 10:07:52 +02:00
Swept
95ba58f0a4 Fixes SpriteComponent error reporting 2021-07-14 22:41:51 +00:00
128 changed files with 2826 additions and 2351 deletions

View File

@@ -29,26 +29,27 @@ namespace Robust.Build.Tasks
}
}
//formatted according to https://github.com/dotnet/msbuild/blob/main/src/Shared/CanonicalError.cs#L57
class ConsoleBuildEngine : IBuildEngine
{
public void LogErrorEvent(BuildErrorEventArgs e)
{
Console.WriteLine($"ERROR: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL ERROR {e.Code}: {e.Message}");
}
public void LogWarningEvent(BuildWarningEventArgs e)
{
Console.WriteLine($"WARNING: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL WARNING {e.Code}: {e.Message}");
}
public void LogMessageEvent(BuildMessageEventArgs e)
{
Console.WriteLine($"MESSAGE: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL MESSAGE {e.Code}: {e.Message}");
}
public void LogCustomEvent(CustomBuildEventArgs e)
{
Console.WriteLine($"CUSTOM: {e.Message}");
Console.WriteLine(e.Message);
}
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties,

View File

@@ -280,8 +280,8 @@ namespace Robust.Build.Tasks
}
catch (Exception e)
{
engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", res.Uri, 0, 0, 0, 0,
e.ToString(), "", "CompileRobustXaml"));
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
}
}
return true;

View File

@@ -65,7 +65,7 @@ namespace Robust.Client.Audio.Midi
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
private SharedBroadphaseSystem _broadPhaseSystem = default!;
[ViewVariables]
public bool IsAvailable
@@ -175,7 +175,7 @@ namespace Robust.Client.Audio.Midi
_midiThread = new Thread(ThreadUpdate);
_midiThread.Start();
_broadPhaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
_broadPhaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
FluidsynthInitialized = true;
}

View File

@@ -25,6 +25,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
@@ -76,6 +77,7 @@ namespace Robust.Client
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IMidiManager, MidiManager>();
IoCManager.Register<IAuthManager, AuthManager>();
IoCManager.Register<IPhysicsManager, PhysicsManager>();
switch (mode)
{
case GameController.DisplayMode.Headless:

View File

@@ -83,6 +83,8 @@ namespace Robust.Client.Console
OutputText(text, true, true);
}
public override event ConAnyCommandCallback? AnyCommandExecuted;
/// <inheritdoc />
public override void ExecuteCommand(ICommonSession? session, string command)
{
@@ -103,7 +105,11 @@ namespace Robust.Client.Console
{
var command1 = AvailableCommands[commandName];
args.RemoveAt(0);
command1.Execute(new ConsoleShell(this, null), command, args.ToArray());
var shell = new ConsoleShell(this, null);
var cmdArgs = args.ToArray();
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
command1.Execute(shell, command, cmdArgs);
}
else
WriteError(null, "Unknown command: " + commandName);

View File

@@ -27,6 +27,7 @@ using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.Console.Commands
{
@@ -536,7 +537,10 @@ namespace Robust.Client.Console.Commands
var scroll = new ScrollContainer();
tabContainer.AddChild(scroll);
//scroll.SetAnchorAndMarginPreset(Control.LayoutPreset.Wide);
var vBox = new VBoxContainer();
var vBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
scroll.AddChild(vBox);
var progressBar = new ProgressBar { MaxValue = 10, Value = 5 };
@@ -594,7 +598,10 @@ namespace Robust.Client.Console.Commands
}
var group = new ButtonGroup();
var vBoxRadioButtons = new VBoxContainer();
var vBoxRadioButtons = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
for (var i = 0; i < 10; i++)
{
vBoxRadioButtons.AddChild(new Button
@@ -610,8 +617,9 @@ namespace Robust.Client.Console.Commands
TabContainer.SetTabTitle(vBoxRadioButtons, "Radio buttons!!");
tabContainer.AddChild(new VBoxContainer
tabContainer.AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Name = "Slider",
Children =
{

View File

@@ -11,6 +11,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Scripting;
using Robust.Shared.Timing;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.Console
{
@@ -18,7 +19,7 @@ namespace Robust.Client.Console
{
private readonly IReflectionManager _reflectionManager;
private readonly VBoxContainer _watchesVBox;
private readonly BoxContainer _watchesVBox;
private readonly LineEdit _addWatchEdit;
private readonly Button _addWatchButton;
@@ -31,17 +32,20 @@ namespace Robust.Client.Console
Title = "Watch Window";
var mainVBox = new VBoxContainer
var mainVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
MinSize = (500, 300),
Children =
{
(_watchesVBox = new VBoxContainer
(_watchesVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
VerticalExpand = true
}),
new HBoxContainer
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(_addWatchEdit = new HistoryLineEdit
@@ -105,8 +109,9 @@ namespace Robust.Client.Console
Button delButton;
_runner = runner;
AddChild(new HBoxContainer
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(_outputLabel = new Label
@@ -166,8 +171,9 @@ namespace Robust.Client.Console
public CompilationErrorControl(string message)
{
Button delButton;
AddChild(new HBoxContainer
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
new Label

View File

@@ -162,12 +162,13 @@ namespace Robust.Client.Debugging
if (viewport.IsEmpty()) return;
var mapId = _eyeManager.CurrentMap;
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
var colorEdge = Color.Red.WithAlpha(0.33f);
var drawnJoints = new HashSet<Joint>();
foreach (var physBody in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(mapId, viewport))
foreach (var physBody in EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(mapId, viewport))
{
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
// all entities have a TransformComponent
var transform = physBody.Owner.Transform;

View File

@@ -28,6 +28,7 @@ using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Dynamics.Contacts;
@@ -40,6 +41,8 @@ namespace Robust.Client.Debugging
* Used for debugging shapes, controllers, joints, contacts
*/
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
private const int MaxContactPoints = 2048;
internal int PointCount;
@@ -79,8 +82,7 @@ namespace Robust.Client.Debugging
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
Span<Vector2> points = stackalloc Vector2[2];
Vector2 normal;
contact.GetWorldManifold(out normal, points);
contact.GetWorldManifold(_physicsManager, out var normal, points);
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
{

View File

@@ -25,6 +25,7 @@ namespace Robust.Client.GameObjects
RegisterClass<InputComponent>();
RegisterClass<SpriteComponent>();
RegisterClass<ClientOccluderComponent>();
RegisterClass<OccluderTreeComponent>();
RegisterClass<EyeComponent>();
RegisterClass<AppearanceComponent>();
RegisterClass<AppearanceTestComponent>();

View File

@@ -1,7 +1,5 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
@@ -10,7 +8,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Defines data fields used in the <see cref="InputSystem"/>.
/// </summary>
class InputComponent : Component
public class InputComponent : Component
{
/// <inheritdoc />
public override string Name => "Input";

View File

@@ -365,7 +365,7 @@ namespace Robust.Client.GameObjects
}
else
{
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath);
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'.", rsiPath);
}
}
}

View File

@@ -23,8 +23,7 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IClydeAudio _clyde = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
[Dependency] private readonly SharedBroadphaseSystem _broadPhaseSystem = default!;
private readonly List<PlayingStream> _playingClydeStreams = new();
@@ -38,7 +37,6 @@ namespace Robust.Client.GameObjects
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
}
private void StopAudioMessageHandler(StopAudioMessageClient ev)
@@ -301,7 +299,7 @@ namespace Robust.Client.GameObjects
if (!source.SetPosition(coordinates.ToMapPos(EntityManager)))
{
source.Dispose();
Logger.Warning("Can't play positional audio \"{stream.Name}\", can't set position.");
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
return null;
}

View File

@@ -15,15 +15,14 @@ namespace Robust.Client.GameObjects
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly RenderingTreeSystem _treeSystem = default!;
private RenderingTreeSystem _treeSystem = default!;
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
public override void Initialize()
{
base.Initialize();
_treeSystem = Get<RenderingTreeSystem>();
SubscribeLocalEvent<SpriteUpdateInertEvent>(QueueUpdateInert);
}

View File

@@ -5,6 +5,7 @@ using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -773,24 +774,13 @@ namespace Robust.Client.Graphics.Clyde
var ii = 0;
var imi = 0;
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, expandedBounds, true))
foreach (var comp in occluderSystem.GetOccluderTrees(map, expandedBounds))
{
if (!occluderSystem.TryGetOccluderTreeForGrid(map, gridId, out var occluderTree)) continue;
// TODO: I know this doesn't work with rotated grids but when I come back to these I'm adding tests
// because rotation bugs are common.
var treeBounds = expandedBounds.Translated(-comp.Owner.Transform.WorldPosition);
Box2 gridBounds;
if (gridId == GridId.Invalid)
{
gridBounds = expandedBounds;
}
else
{
// TODO: Ideally this would clamp to the outer border of what we can see
var grid = _mapManager.GetGrid(gridId);
gridBounds = expandedBounds.Translated(-grid.WorldPosition);
}
occluderTree.QueryAabb((in OccluderComponent sOccluder) =>
comp.Tree.QueryAabb((in OccluderComponent sOccluder) =>
{
var occluder = (ClientOccluderComponent)sOccluder;
var transform = occluder.Owner.Transform;
@@ -919,7 +909,7 @@ namespace Robust.Client.Graphics.Clyde
ami += 4;
return true;
}, gridBounds);
}, treeBounds);
}
_occlusionDataLength = ii;

View File

@@ -24,6 +24,7 @@ namespace Robust.Client.Graphics.Clyde
internal sealed partial class Clyde : IClydeInternal, IClydeAudio, IPostInjectInit
{
[Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly ILightManager _lightManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;

View File

@@ -20,6 +20,9 @@ namespace Robust.Client.Log
public void Log(string sawmillName, LogEvent message)
{
if (sawmillName == "CON")
return;
var formatted = new FormattedMessage(8);
var robustLevel = message.Level.ToRobust();
formatted.PushColor(Color.DarkGray);

View File

@@ -8,6 +8,7 @@ using Robust.Client.Utility;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -62,7 +63,7 @@ namespace Robust.Client.Map
var row = i / dimensionX;
Image<Rgba32> image;
using (var stream = _resourceCache.ContentFileRead(Path.Join(def.Path, $"{def.SpriteName}.png")))
using (var stream = _resourceCache.ContentFileRead(new ResourcePath(def.Path) / $"{def.SpriteName}.png"))
{
image = Image.Load<Rgba32>(stream);
}

View File

@@ -1,8 +1,8 @@
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Physics;
namespace Robust.Client.Physics
{
internal sealed class BroadPhaseSystem : SharedBroadPhaseSystem
internal sealed class BroadPhaseSystem : SharedBroadphaseSystem
{
public override void Initialize()
{

View File

@@ -504,8 +504,8 @@ namespace Robust.Client.Placement
coordinates = new EntityCoordinates();
return false;
}
coordinates = EntityCoordinates.FromMap(ent.EntityManager, MapManager,
eyeManager.ScreenToMap(_inputManager.MouseScreenPosition));
coordinates = EntityCoordinates.FromMap(MapManager,
eyeManager.ScreenToMap(_inputManager.MouseScreenPosition));
return true;
}
}

View File

@@ -7,6 +7,7 @@ using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Utility;
@@ -232,7 +233,7 @@ namespace Robust.Client.Placement
bounds.Width,
bounds.Height);
return EntitySystem.Get<SharedBroadPhaseSystem>().TryCollideRect(collisionBox, mapCoords.MapId);
return EntitySystem.Get<SharedBroadphaseSystem>().TryCollideRect(collisionBox, mapCoords.MapId);
}
protected Vector2 ScreenToWorld(Vector2 point)
@@ -250,7 +251,7 @@ namespace Robust.Client.Placement
var mapCoords = pManager.eyeManager.ScreenToMap(coords.Position);
if (!pManager.MapManager.TryFindGridAt(mapCoords, out var grid))
{
return EntityCoordinates.FromMap(pManager.EntityManager, pManager.MapManager, mapCoords);
return EntityCoordinates.FromMap(pManager.MapManager, mapCoords);
}
return EntityCoordinates.FromMap(pManager.EntityManager, grid.GridEntityId, mapCoords);

View File

@@ -1,5 +1,5 @@
using Robust.Shared.Enums;
using Robust.Client.Graphics;
using Robust.Client.Graphics;
namespace Robust.Client.Placement

View File

@@ -102,7 +102,9 @@ namespace Robust.Client.ResourceManagement
if (reg.Src.Width % frameSize.X != 0 || reg.Src.Height % frameSize.Y != 0)
{
throw new RSILoadException("State image size is not a multiple of the icon size.");
var regDims = $"{reg.Src.Width}x{reg.Src.Height}";
var iconDims = $"{frameSize.X}x{frameSize.Y}";
throw new RSILoadException($"State '{stateObject.StateId}' image size ({regDims}) is not a multiple of the icon size ({iconDims}).");
}
// Load all frames into a list so we can operate on it more sanely.
@@ -250,7 +252,7 @@ namespace Robust.Client.ResourceManagement
}
if (manifestJson == null)
throw new RSILoadException("Manifest JSON was null!");
throw new RSILoadException($"Manifest JSON failed to deserialize!");
var size = manifestJson.Size;
var states = new StateMetadata[manifestJson.States.Length];
@@ -270,7 +272,7 @@ namespace Robust.Client.ResourceManagement
1 => RSI.State.DirectionType.Dir1,
4 => RSI.State.DirectionType.Dir4,
8 => RSI.State.DirectionType.Dir8,
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
_ => throw new RSILoadException($"Invalid direction for state '{stateName}': {dirValue}. Expected 1, 4 or 8")
};
}
else
@@ -291,7 +293,7 @@ namespace Robust.Client.ResourceManagement
if (delays.Length != dirValue)
{
throw new RSILoadException(
"DirectionsdirectionFramesList count does not match amount of delays specified.");
$"Direction frames list count ({dirValue}) does not match amount of delays specified ({delays.Length}) for state '{stateName}'.");
}
for (var i = 0; i < delays.Length; i++)

View File

@@ -763,6 +763,29 @@ namespace Robust.Client.UserInterface
SetPositionInParent(Parent.ChildCount - 1);
}
/// <summary>
/// This searches recursively through all the children of "parent"
/// and sets the Disabled value of any buttons found to "val"
/// </summary>
/// <param name="parent">The control which childrens get searched</param>
/// <param name="val">The value to which disabled gets set</param>
public void SetButtonDisabledRecursive(Control parent, bool val)
{
foreach (var child in parent.Children)
{
if (child is Button but)
{
but.Disabled = val;
continue;
}
if (child.Children != null)
{
SetButtonDisabledRecursive(child, val);
}
}
}
/// <summary>
/// Called when this control receives keyboard focus.
/// </summary>

View File

@@ -18,8 +18,9 @@ namespace Robust.Client.UserInterface.Controls
{
ToggleMode = true;
var hBox = new HBoxContainer
var hBox = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
StyleClasses = { StyleClassCheckBox },
};
AddChild(hBox);

View File

@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.UserInterface.Controls
{
@@ -13,9 +14,9 @@ namespace Robust.Client.UserInterface.Controls
{
private readonly List<Menu> _menus = new();
private readonly List<MenuBarTopButton> _buttons = new();
private readonly HBoxContainer _hBox;
private readonly BoxContainer _hBox;
private readonly Popup _popup;
private readonly VBoxContainer _popupVBox;
private readonly BoxContainer _popupVBox;
private bool _popupOpen;
public IList<Menu> Menus { get; }
@@ -26,13 +27,21 @@ namespace Robust.Client.UserInterface.Controls
{
Children =
{
(_popupVBox = new VBoxContainer {MinSize = (300, 0)})
(_popupVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
MinSize = (300, 0)
})
}
};
_popup.OnPopupHide += PopupHidden;
UserInterfaceManager.ModalRoot.AddChild(_popup);
Menus = new MenuCollection(this);
AddChild(_hBox = new HBoxContainer {SeparationOverride = 8});
AddChild(_hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
SeparationOverride = 8
});
}
private void AddMenu(Menu menu)

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.UserInterface.Controls
{
@@ -20,7 +21,7 @@ namespace Robust.Client.UserInterface.Controls
// map from key to buttondata index
private Dictionary<TKey, int> _keyMap = new();
private readonly Popup _popup;
private readonly VBoxContainer _popupVBox;
private readonly BoxContainer _popupVBox;
private readonly Label _label;
public event Action<ItemPressedEventArgs>? OnItemSelected;
@@ -60,11 +61,17 @@ namespace Robust.Client.UserInterface.Controls
AddStyleClass(StyleClassButton);
OnPressed += OnPressedInternal;
var hBox = new HBoxContainer();
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal
};
AddChild(hBox);
_popup = new Popup();
_popupVBox = new VBoxContainer();
_popupVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
_popup.AddChild(_popupVBox);
_popup.OnPopupHide += OnPopupHide;

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.UserInterface.Controls
{
@@ -13,7 +14,7 @@ namespace Robust.Client.UserInterface.Controls
private readonly List<ButtonData> _buttonData = new();
private readonly Dictionary<int, int> _idMap = new();
private readonly Popup _popup;
private readonly VBoxContainer _popupVBox;
private readonly BoxContainer _popupVBox;
private readonly Label _label;
private readonly TextureRect _triangle;
@@ -49,11 +50,17 @@ namespace Robust.Client.UserInterface.Controls
Prefix = "";
OnPressed += OnPressedInternal;
var hBox = new HBoxContainer();
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal
};
AddChild(hBox);
_popup = new Popup();
_popupVBox = new VBoxContainer();
_popupVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
_popup.AddChild(_popupVBox);
_popup.OnPopupHide += OnPopupHide;

View File

@@ -2,6 +2,7 @@ using System;
using System.Linq;
using System.Collections.Generic;
using static Robust.Client.UserInterface.Controls.BaseButton;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.UserInterface.Controls
{
@@ -34,11 +35,17 @@ namespace Robust.Client.UserInterface.Controls
switch (layout)
{
case RadioOptionsLayout.Vertical:
_container = new VBoxContainer();
_container = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
break;
case RadioOptionsLayout.Horizontal:
default:
_container = new HBoxContainer();
_container = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal
};
break;
}

View File

@@ -1,6 +1,6 @@
<Control xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics">
<VBoxContainer>
<BoxContainer Orientation="Vertical">
<OutputPanel Name="Output" VerticalExpand="True">
<OutputPanel.StyleBoxOverride>
<gfx:StyleBoxFlat BackgroundColor="#25252add"
@@ -9,5 +9,5 @@
</OutputPanel.StyleBoxOverride>
</OutputPanel>
<HistoryLineEdit Name="CommandBar" PlaceHolder="{Loc 'console-line-edit-placeholder'}" />
</VBoxContainer>
</BoxContainer>
</Control>

View File

@@ -12,6 +12,7 @@ using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.UserInterface.CustomControls
{
@@ -21,7 +22,7 @@ namespace Robust.Client.UserInterface.CustomControls
private readonly IPrototypeManager prototypeManager;
private readonly IResourceCache resourceCache;
private VBoxContainer MainVBox;
private BoxContainer MainVBox;
private PrototypeListContainer PrototypeList;
private LineEdit SearchBar;
private OptionButton OverrideMenu;
@@ -71,13 +72,15 @@ namespace Robust.Client.UserInterface.CustomControls
SetSize = (250, 300);
MinSize = (250, 200);
Contents.AddChild(MainVBox = new VBoxContainer
Contents.AddChild(MainVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Name = "AAAAAA",
Children =
{
new HBoxContainer
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(SearchBar = new LineEdit
@@ -102,8 +105,9 @@ namespace Robust.Client.UserInterface.CustomControls
(PrototypeList = new PrototypeListContainer())
}
},
new HBoxContainer
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(EraseButton = new Button
@@ -472,8 +476,9 @@ namespace Robust.Client.UserInterface.CustomControls
ToggleMode = true,
});
AddChild(new HBoxContainer
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(EntityTextureRects = new LayeredTextureRect

View File

@@ -25,7 +25,7 @@ namespace Robust.Client.UserInterface.CustomControls
}
var fps = _gameTiming.FramesPerSecondAvg;
Text = $"FPS: {fps:N1}";
Text = $"FPS: {fps:N0}";
}
}
}

View File

@@ -1,13 +1,13 @@
<SS14Window xmlns="https://spacestation14.io" MinWidth="100" MinHeight="50">
<PanelContainer StyleClasses="windowPanel" />
<VBoxContainer SeparationOverride="0">
<BoxContainer Orientation="Vertical" SeparationOverride="0">
<PanelContainer Name="WindowHeader" StyleClasses="windowHeader">
<HBoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Margin="5 0 0 0" HorizontalExpand="true" Name="TitleLabel" StyleIdentifier="foo" ClipText="True"
Text="{Loc 'ss14window-placeholder-title'}" VAlign="Center" StyleClasses="windowTitle" />
<TextureButton Name="CloseButton" StyleClasses="windowCloseButton" VerticalAlignment="Center" />
</HBoxContainer>
</BoxContainer>
</PanelContainer>
<Control Name="ContentsContainer" Margin="10" RectClipContent="True" VerticalExpand="true" />
</VBoxContainer>
</BoxContainer>
</SS14Window>

View File

@@ -1,6 +1,7 @@
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.UserInterface.CustomControls
{
@@ -12,8 +13,9 @@ namespace Robust.Client.UserInterface.CustomControls
protected ScriptConsole()
{
Contents.AddChild(new VBoxContainer
Contents.AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
{
new PanelContainer
@@ -29,8 +31,9 @@ namespace Robust.Client.UserInterface.CustomControls
},
VerticalExpand = true,
},
new HBoxContainer
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(InputBar = new HistoryLineEdit

View File

@@ -2,12 +2,15 @@
using Robust.Shared.Enums;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.Placement;
using Robust.Client.ResourceManagement;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.CustomControls
{
@@ -32,9 +35,15 @@ namespace Robust.Client.UserInterface.CustomControls
_placementManager = placementManager;
_resourceCache = resourceCache;
var vBox = new VBoxContainer();
var vBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
Contents.AddChild(vBox);
var hBox = new HBoxContainer();
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal
};
vBox.AddChild(hBox);
SearchBar = new LineEdit {PlaceHolder = "Search", HorizontalExpand = true};
SearchBar.OnTextChanged += OnSearchBarTextChanged;
@@ -105,7 +114,7 @@ namespace Robust.Client.UserInterface.CustomControls
Texture? texture = null;
if (!string.IsNullOrEmpty(entry.SpriteName))
{
texture = _resourceCache.GetResource<TextureResource>($"/Textures/Constructible/Tiles/{entry.SpriteName}.png");
texture = _resourceCache.GetResource<TextureResource>(new ResourcePath(entry.Path) / $"{entry.SpriteName}.png");
}
TileList.AddItem(entry.DisplayName, texture);
}

View File

@@ -2,6 +2,7 @@ using System.Globalization;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.ViewVariables.Editors
{
@@ -9,8 +10,9 @@ namespace Robust.Client.ViewVariables.Editors
{
protected override Control MakeUI(object? value)
{
var hBox = new HBoxContainer
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
MinSize = new Vector2(200, 0)
};
var angle = (Angle) value!;

View File

@@ -5,6 +5,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.ViewVariables.Editors
{
@@ -13,8 +14,9 @@ namespace Robust.Client.ViewVariables.Editors
protected override Control MakeUI(object? value)
{
var coords = (EntityCoordinates) value!;
var hBoxContainer = new HBoxContainer
var hBoxContainer = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
MinSize = new Vector2(240, 0),
};

View File

@@ -2,6 +2,7 @@
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.ViewVariables.Editors
{
@@ -9,8 +10,9 @@ namespace Robust.Client.ViewVariables.Editors
{
protected override Control MakeUI(object? value)
{
var hBox = new HBoxContainer
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
MinSize = new Vector2(200, 0)
};

View File

@@ -5,6 +5,7 @@ using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.ViewVariables.Editors
{
@@ -20,7 +21,11 @@ namespace Robust.Client.ViewVariables.Editors
{
_localValue = value;
var hbox = new HBoxContainer() { HorizontalExpand = true };
var hbox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
HorizontalExpand = true
};
_lineEdit = new LineEdit()
{

View File

@@ -3,6 +3,7 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.ViewVariables.Editors
{
@@ -20,7 +21,10 @@ namespace Robust.Client.ViewVariables.Editors
protected override Control MakeUI(object? value)
{
var hBox = new HBoxContainer();
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal
};
dynamic d = value!;

View File

@@ -3,6 +3,7 @@ using System.Globalization;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.ViewVariables.Editors
{
@@ -17,8 +18,9 @@ namespace Robust.Client.ViewVariables.Editors
protected override Control MakeUI(object? value)
{
var hBoxContainer = new HBoxContainer
var hBoxContainer = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
MinSize = new Vector2(200, 0),
};

View File

@@ -2,6 +2,7 @@ using System.Globalization;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.ViewVariables.Editors
{
@@ -16,8 +17,9 @@ namespace Robust.Client.ViewVariables.Editors
protected override Control MakeUI(object? value)
{
var hBoxContainer = new HBoxContainer
var hBoxContainer = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
MinSize = new Vector2(240, 0),
};

View File

@@ -17,6 +17,7 @@ using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Control;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using static Robust.Client.UserInterface.Controls.LineEdit;
namespace Robust.Client.ViewVariables.Instances
@@ -48,10 +49,10 @@ namespace Robust.Client.ViewVariables.Instances
private ViewVariablesBlobMembers? _membersBlob;
private VBoxContainer _clientComponents = default!;
private BoxContainer _clientComponents = default!;
private VBoxContainer _serverVariables = default!;
private VBoxContainer _serverComponents = default!;
private BoxContainer _serverVariables = default!;
private BoxContainer _serverComponents = default!;
private Button _clientComponentsAddButton = default!;
private Button _serverComponentsAddButton = default!;
@@ -73,7 +74,10 @@ namespace Robust.Client.ViewVariables.Instances
var scrollContainer = new ScrollContainer();
//scrollContainer.SetAnchorPreset(Control.LayoutPreset.Wide, true);
window.Contents.AddChild(scrollContainer);
var vBoxContainer = new VBoxContainer();
var vBoxContainer = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
scrollContainer.AddChild(vBoxContainer);
// Handle top bar displaying type and ToString().
@@ -84,7 +88,11 @@ namespace Robust.Client.ViewVariables.Instances
{
//var smallFont = new VectorFont(_resourceCache.GetResource<FontResource>("/Fonts/CALIBRI.TTF"), 10);
// Custom ToString() implementation.
var headBox = new VBoxContainer {SeparationOverride = 0};
var headBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
SeparationOverride = 0
};
headBox.AddChild(new Label {Text = stringified, ClipText = true});
headBox.AddChild(new Label
{
@@ -102,7 +110,10 @@ namespace Robust.Client.ViewVariables.Instances
if (_entity.TryGetComponent(out ISpriteComponent? sprite))
{
var hBox = new HBoxContainer();
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal
};
top.HorizontalExpand = true;
hBox.AddChild(top);
hBox.AddChild(new SpriteView {Sprite = sprite});
@@ -118,7 +129,11 @@ namespace Robust.Client.ViewVariables.Instances
_tabs.OnTabChanged += _tabsOnTabChanged;
vBoxContainer.AddChild(_tabs);
var clientVBox = new VBoxContainer {SeparationOverride = 0};
var clientVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
SeparationOverride = 0
};
_tabs.AddChild(clientVBox);
_tabs.SetTabTitle(TabClientVars, Loc.GetString("view-variable-instance-entity-client-variables-tab-title"));
@@ -136,7 +151,11 @@ namespace Robust.Client.ViewVariables.Instances
}
}
_clientComponents = new VBoxContainer {SeparationOverride = 0};
_clientComponents = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
SeparationOverride = 0
};
_tabs.AddChild(_clientComponents);
_tabs.SetTabTitle(TabClientComponents, Loc.GetString("view-variable-instance-entity-client-components-tab-title"));
@@ -144,11 +163,19 @@ namespace Robust.Client.ViewVariables.Instances
if (!_entity.Uid.IsClientSide())
{
_serverVariables = new VBoxContainer {SeparationOverride = 0};
_serverVariables = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
SeparationOverride = 0
};
_tabs.AddChild(_serverVariables);
_tabs.SetTabTitle(TabServerVars, Loc.GetString("view-variable-instance-entity-server-variables-tab-title"));
_serverComponents = new VBoxContainer {SeparationOverride = 0};
_serverComponents = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
SeparationOverride = 0
};
_tabs.AddChild(_serverComponents);
_tabs.SetTabTitle(TabServerComponents, Loc.GetString("view-variable-instance-entity-server-components-tab-title"));

View File

@@ -8,6 +8,7 @@ using Robust.Shared.Input;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Client.ViewVariables.Instances
@@ -64,8 +65,9 @@ namespace Robust.Client.ViewVariables.Instances
var scrollContainer = new ScrollContainer();
//scrollContainer.SetAnchorPreset(Control.LayoutPreset.Wide, true);
window.Contents.AddChild(scrollContainer);
var vBoxContainer = new VBoxContainer
var vBoxContainer = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
HorizontalExpand = true,
VerticalExpand = true,
};
@@ -73,7 +75,10 @@ namespace Robust.Client.ViewVariables.Instances
// Handle top bar.
{
var headBox = new HBoxContainer();
var headBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal
};
var name = MakeTopBar(top, bottom);
name.HorizontalExpand = true;
headBox.AddChild(name);

View File

@@ -10,6 +10,7 @@ using Robust.Client.ViewVariables.Editors;
using Robust.Client.ViewVariables.Instances;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.ViewVariables.Traits
{
@@ -25,8 +26,8 @@ namespace Robust.Client.ViewVariables.Traits
private Button _leftButton = default!;
private Button _rightButton = default!;
private LineEdit _pageLabel = default!;
private HBoxContainer _controlsHBox = default!;
private VBoxContainer _elementsVBox = default!;
private BoxContainer _controlsHBox = default!;
private BoxContainer _elementsVBox = default!;
private int HighestKnownPage => Math.Max(0, ((_cache.Count + ElementsPerPage - 1) / ElementsPerPage) - 1);
@@ -47,9 +48,13 @@ namespace Robust.Client.ViewVariables.Traits
_enumerator = enumerable.GetEnumerator();
}
var outerVBox = new VBoxContainer();
_controlsHBox = new HBoxContainer
var outerVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
_controlsHBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
HorizontalAlignment = Control.HAlignment.Center
};
@@ -70,7 +75,10 @@ namespace Robust.Client.ViewVariables.Traits
outerVBox.AddChild(_controlsHBox);
_elementsVBox = new VBoxContainer();
_elementsVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
outerVBox.AddChild(_elementsVBox);
instance.AddTab("IEnumerable", outerVBox);

View File

@@ -5,6 +5,7 @@ using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.ViewVariables.Traits
{
@@ -13,12 +14,16 @@ namespace Robust.Client.ViewVariables.Traits
private readonly IViewVariablesManagerInternal _vvm;
private readonly IRobustSerializer _robustSerializer;
private VBoxContainer _memberList = default!;
private BoxContainer _memberList = default!;
public override void Initialize(ViewVariablesInstanceObject instance)
{
base.Initialize(instance);
_memberList = new VBoxContainer {SeparationOverride = 0};
_memberList = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
SeparationOverride = 0
};
instance.AddTab("Members", _memberList);
}

View File

@@ -1,8 +1,8 @@
<cc:SS14Window xmlns:cc="clr-namespace:Robust.Client.UserInterface.CustomControls"
xmlns:c="clr-namespace:Robust.Client.UserInterface.Controls">
<c:VBoxContainer VerticalExpand="True">
<c:BoxContainer Orientation="Vertical" VerticalExpand="True">
<c:LineEdit Name="SearchLineEdit" PlaceHolder="Search..." HorizontalExpand="True" />
<c:ItemList Name="EntryItemList" VerticalExpand="True" HorizontalExpand="True" SelectMode="Single" />
<c:Button Name="AddButton" Text="Select" TextAlign="Center" HorizontalExpand="True"/>
</c:VBoxContainer>
</c:BoxContainer>
</cc:SS14Window>

View File

@@ -9,6 +9,7 @@ using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.ViewVariables
{
@@ -126,7 +127,11 @@ namespace Robust.Client.ViewVariables
// 10);
// Custom ToString() implementation.
var headBox = new VBoxContainer {SeparationOverride = 0};
var headBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
SeparationOverride = 0
};
headBox.AddChild(new Label {Text = top, ClipText = true});
headBox.AddChild(new Label
{

View File

@@ -8,14 +8,15 @@ using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.ViewVariables
{
internal class ViewVariablesPropertyControl : PanelContainer
{
public VBoxContainer VBox { get; }
public HBoxContainer TopContainer { get; }
public HBoxContainer BottomContainer { get; }
public BoxContainer VBox { get; }
public BoxContainer TopContainer { get; }
public BoxContainer BottomContainer { get; }
public Label NameLabel { get; }
private readonly Label _bottomLabel;
@@ -34,14 +35,23 @@ namespace Robust.Client.ViewVariables
ToolTip = "Click to expand";
MinHeight = 25;
VBox = new VBoxContainer {SeparationOverride = 0};
VBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
SeparationOverride = 0
};
AddChild(VBox);
TopContainer = new HBoxContainer {VerticalExpand = true};
TopContainer = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
VerticalExpand = true
};
VBox.AddChild(TopContainer);
BottomContainer = new HBoxContainer
BottomContainer = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Visible = false
};
VBox.AddChild(BottomContainer);

View File

@@ -17,6 +17,8 @@ namespace Robust.Server.Console
[Dependency] private readonly IPlayerManager _players = default!;
[Dependency] private readonly ISystemConsoleManager _systemConsole = default!;
public override event ConAnyCommandCallback? AnyCommandExecuted;
/// <inheritdoc />
public override void ExecuteCommand(ICommonSession? session, string command)
{
@@ -87,20 +89,22 @@ namespace Robust.Server.Console
if (AvailableCommands.TryGetValue(cmdName, out var conCmd)) // command registered
{
args.RemoveAt(0);
var cmdArgs = args.ToArray();
if (shell.Player != null) // remote client
{
if (_groupController.CanCommand((IPlayerSession) shell.Player, cmdName)) // client has permission
{
args.RemoveAt(0);
conCmd.Execute(shell, command, args.ToArray());
AnyCommandExecuted?.Invoke(shell, cmdName, command, cmdArgs);
conCmd.Execute(shell, command, cmdArgs);
}
else
shell.WriteError($"Unknown command: '{cmdName}'");
}
else // system console
{
args.RemoveAt(0);
conCmd.Execute(shell, command, args.ToArray());
AnyCommandExecuted?.Invoke(shell, cmdName, command, cmdArgs);
conCmd.Execute(shell, command, cmdArgs);
}
}
else

View File

@@ -24,6 +24,7 @@ namespace Robust.Server.GameObjects
RegisterClass<CollisionWakeComponent>();
RegisterClass<ContainerManagerComponent>();
RegisterClass<OccluderComponent>();
RegisterClass<OccluderTreeComponent>();
RegisterClass<SpriteComponent>();
RegisterClass<AppearanceComponent>();
RegisterClass<SnapGridComponent>();

View File

@@ -224,20 +224,32 @@ namespace Robust.Server.GameObjects
}
}
switch (message.Type)
#if EXCEPTION_TOLERANCE
try
#endif
{
case EntityMessageType.ComponentMessage:
ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message, player));
return;
switch (message.Type)
{
case EntityMessageType.ComponentMessage:
ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message, player));
return;
case EntityMessageType.SystemMessage:
var msg = message.SystemMessage;
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(player), msg)!;
ReceivedSystemMessage?.Invoke(this, msg);
ReceivedSystemMessage?.Invoke(this, sessionMsg);
return;
case EntityMessageType.SystemMessage:
var msg = message.SystemMessage;
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
var sessionMsg =
Activator.CreateInstance(sessionType, new EntitySessionEventArgs(player), msg)!;
ReceivedSystemMessage?.Invoke(this, msg);
ReceivedSystemMessage?.Invoke(this, sessionMsg);
return;
}
}
#if EXCEPTION_TOLERANCE
catch (Exception e)
{
Logger.ErrorS("net.ent", $"Caught exception while dispatching {message.Type}: {e}");
}
#endif
}
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs args)

View File

@@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.Physics;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -365,6 +366,11 @@ namespace Robust.Server.Maps
// Run Initialize on all components.
FinishEntitiesInitialization();
// Regardless of what the frequency is we'll process fixtures sometime on map load for anything
// that needs it before MapInit.
var gridFixtures = EntitySystem.Get<GridFixtureSystem>();
gridFixtures.Process();
// Run Startup on all components.
FinishEntitiesStartup();
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Utility;
using YamlDotNet.Core;

View File

@@ -1,9 +1,10 @@
using Robust.Server.GameObjects;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
namespace Robust.Server.Physics
{
internal sealed class BroadPhaseSystem : SharedBroadPhaseSystem
internal sealed class BroadPhaseSystem : SharedBroadphaseSystem
{
public override void Initialize()
{

View File

@@ -19,8 +19,7 @@ namespace Robust.Server.Physics
internal sealed class GridFixtureSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
private SharedBroadPhaseSystem _broadphase = default!;
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
// Is delaying fixture updates a good idea? IDEK. We definitely can't do them on every tile changed
// because if someone changes 50 tiles that will kill perf. We could probably just run it every Update
@@ -36,7 +35,6 @@ namespace Robust.Server.Physics
base.Initialize();
UpdatesBefore.Add(typeof(PhysicsSystem));
SubscribeLocalEvent<RegenerateChunkCollisionEvent>(HandleCollisionRegenerate);
_broadphase = Get<SharedBroadPhaseSystem>();
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.OnValueChanged(CVars.GridFixtureUpdateRate, value => _cooldown = value, true);
@@ -97,6 +95,14 @@ namespace Robust.Server.Physics
var origin = chunk.Indices * chunk.ChunkSize;
bounds = bounds.Translated(origin);
// So we store a reference to the fixture on the chunk because it's easier to cross-reference it.
// This is because when we get multiple fixtures per chunk there's no easy way to tell which the old one
// corresponds with.
// We also ideally want to avoid re-creating the fixture every time a tile changes and pushing that data
// to the client hence we diff it.
// Additionally, we need to handle map deserialization where content may have stored its own data
// on the grid (e.g. mass) which we want to preserve.
var oldFixture = chunk.Fixture;
var newFixture = new Fixture(
@@ -112,16 +118,57 @@ namespace Robust.Server.Physics
},
MapGridHelpers.CollisionGroup,
MapGridHelpers.CollisionGroup,
true) {ID = $"grid-{grid.Index}_chunk-{chunk.Indices.X}-{chunk.Indices.Y}",
true) {ID = $"grid_chunk-{chunk.Indices.X}-{chunk.Indices.Y}",
Body = physicsComponent};
// TODO: Chunk will likely need multiple fixtures but future sloth problem lmao fucking dickhead
if (oldFixture?.Equals(newFixture) == true) return;
// Check if we have an existing fixture on MapGrid
var existingFixture = physicsComponent.GetFixture(newFixture.ID);
var same = true;
// Some fucky shit but we gotta handle map deserialization.
if (existingFixture is {Shape: PolygonShape poly})
{
var newPoly = (PolygonShape) newFixture.Shape;
if (newPoly.Vertices.Count == poly.Vertices.Count)
{
for (var i = 0; i < poly.Vertices.Count; i++)
{
if (!poly.Vertices[i].EqualsApprox(newPoly.Vertices[i]))
{
same = false;
break;
}
}
}
else
{
same = false;
}
}
else
{
same = false;
}
// TODO: Chunk will likely need multiple fixtures but future sloth problem lmao idiot
if (same)
{
// If we're deserializing map this can occur so just update it.
if (oldFixture == null && existingFixture != null)
{
chunk.Fixture = existingFixture;
existingFixture.CollisionMask = MapGridHelpers.CollisionGroup;
existingFixture.CollisionLayer = MapGridHelpers.CollisionGroup;
}
return;
}
if (oldFixture != null)
physicsComponent.RemoveFixture(oldFixture);
_broadphase.DestroyFixture(physicsComponent, oldFixture);
physicsComponent.AddFixture(newFixture);
_broadphase.CreateFixture(physicsComponent, newFixture);
chunk.Fixture = newFixture;
EntityManager.EventBus.RaiseLocalEvent(gridEnt.Uid,new GridFixtureChangeEvent {OldFixture = oldFixture, NewFixture = newFixture});

View File

@@ -19,6 +19,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
@@ -71,6 +72,7 @@ namespace Robust.Server
IoCManager.Register<IScriptHost, ScriptHost>();
IoCManager.Register<IMetricsManager, MetricsManager>();
IoCManager.Register<IAuthManager, AuthManager>();
IoCManager.Register<IPhysicsManager, PhysicsManager>();
}
}
}

View File

@@ -83,7 +83,7 @@ namespace Robust.Shared.Configuration
{
// overwrite the value with the saved one
cfgVar.Value = tomlValue;
cfgVar.ValueChanged?.Invoke(cfgVar.Value);
InvokeValueChanged(cfgVar, cfgVar.Value);
}
else
{
@@ -189,16 +189,13 @@ namespace Robust.Shared.Configuration
public void RegisterCVar<T>(string name, T defaultValue, CVar flags = CVar.NONE, Action<T>? onValueChanged = null)
where T : notnull
{
Action<object>? valueChangedDelegate = null;
if (onValueChanged != null)
{
valueChangedDelegate = v => onValueChanged((T) v);
}
RegisterCVar(name, typeof(T), defaultValue, flags);
RegisterCVar(name, typeof(T), defaultValue, flags, valueChangedDelegate);
if (onValueChanged != null)
OnValueChanged(name, onValueChanged);
}
private void RegisterCVar(string name, Type type, object defaultValue, CVar flags, Action<object>? onValueChanged)
private void RegisterCVar(string name, Type type, object defaultValue, CVar flags)
{
DebugTools.Assert(!type.IsEnum || type.GetEnumUnderlyingType() == typeof(int),
$"{name}: Enum cvars must have int as underlying type.");
@@ -219,7 +216,6 @@ namespace Robust.Shared.Configuration
cVar.DefaultValue = defaultValue;
cVar.Flags = flags;
cVar.Registered = true;
cVar.ValueChanged = onValueChanged;
if (cVar.OverrideValue != null)
{
@@ -233,7 +229,6 @@ namespace Robust.Shared.Configuration
{
Registered = true,
Value = defaultValue,
ValueChanged = onValueChanged
});
}
@@ -247,7 +242,11 @@ namespace Robust.Shared.Configuration
where T : notnull
{
var reg = _configVars[name];
reg.ValueChanged += o => onValueChanged((T) o);
var exDel = (Action<T>?) reg.ValueChanged;
exDel += onValueChanged;
reg.ValueChanged = exDel;
reg.ValueChangedInvoker ??= (del, v) => ((Action<T>) del)((T) v);
if (invokeImmediately)
{
@@ -255,6 +254,19 @@ namespace Robust.Shared.Configuration
}
}
public void UnsubValueChanged<T>(CVarDef<T> cVar, Action<T> onValueChanged) where T : notnull
{
UnsubValueChanged(cVar.Name, onValueChanged);
}
public void UnsubValueChanged<T>(string name, Action<T> onValueChanged) where T : notnull
{
var reg = _configVars[name];
var exDel = (Action<T>?) reg.ValueChanged;
exDel -= onValueChanged;
reg.ValueChanged = exDel;
}
public void LoadCVarsFromAssembly(Assembly assembly)
{
foreach (var defField in assembly
@@ -282,7 +294,7 @@ namespace Robust.Shared.Configuration
throw new InvalidOperationException($"CVarDef '{defField.Name}' on '{defField.DeclaringType?.FullName}' is null.");
}
RegisterCVar(def.Name, type, def.DefaultValue, def.Flags, null);
RegisterCVar(def.Name, type, def.DefaultValue, def.Flags);
}
}
@@ -316,7 +328,7 @@ namespace Robust.Shared.Configuration
cVar.OverrideValueParsed = null;
cVar.Value = value;
cVar.ValueChanged?.Invoke(value);
InvokeValueChanged(cVar, value);
}
}
else
@@ -362,7 +374,7 @@ namespace Robust.Shared.Configuration
{
cfgVar.OverrideValue = value;
cfgVar.OverrideValueParsed = ParseOverrideValue(value, cfgVar.DefaultValue?.GetType());
cfgVar.ValueChanged?.Invoke(cfgVar.OverrideValueParsed);
InvokeValueChanged(cfgVar, cfgVar.OverrideValueParsed);
}
else
{
@@ -422,6 +434,11 @@ namespace Robust.Shared.Configuration
}
}
private static void InvokeValueChanged(ConfigVar var, object value)
{
var.ValueChangedInvoker?.Invoke(var.ValueChanged!, value);
}
/// <summary>
/// Holds the data for a single configuration variable.
/// </summary>
@@ -476,7 +493,9 @@ namespace Robust.Shared.Configuration
/// <summary>
/// Invoked when the value of this CVar is changed.
/// </summary>
public Action<object>? ValueChanged { get; set; }
public Delegate? ValueChanged { get; set; }
public Action<Delegate, object>? ValueChangedInvoker { get; set; }
// We don't know what the type of the var is until it's registered.
// So we can't actually parse them until then.

View File

@@ -63,10 +63,48 @@ namespace Robust.Shared.Configuration
/// <param name="name">The name of the CVar</param>
Type GetCVarType(string name);
/// <summary>
/// Listen for an event for if the config value changes.
/// </summary>
/// <param name="cVar">The CVar to listen for.</param>
/// <param name="onValueChanged">The delegate to run when the value was changed.</param>
/// <param name="invokeImmediately">
/// Whether to run the callback immediately in this method. Can help reduce boilerplate
/// </param>
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
/// <seealso cref="UnsubValueChanged{T}(Robust.Shared.Configuration.CVarDef{T},System.Action{T})"/>
void OnValueChanged<T>(CVarDef<T> cVar, Action<T> onValueChanged, bool invokeImmediately = false)
where T : notnull;
/// <summary>
/// Listen for an event for if the config value changes.
/// </summary>
/// <param name="name">The name of the CVar to listen for.</param>
/// <param name="onValueChanged">The delegate to run when the value was changed.</param>
/// <param name="invokeImmediately">
/// Whether to run the callback immediately in this method. Can help reduce boilerplate
/// </param>
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
/// <seealso cref="UnsubValueChanged{T}(string,System.Action{T})"/>
void OnValueChanged<T>(string name, Action<T> onValueChanged, bool invokeImmediately = false)
where T : notnull;
/// <summary>
/// Unsubscribe an event previously registered with <see cref="OnValueChanged{T}(Robust.Shared.Configuration.CVarDef{T},System.Action{T},bool)"/>.
/// </summary>
/// <param name="cVar">The CVar to unsubscribe from.</param>
/// <param name="onValueChanged">The delegate to unsubscribe.</param>
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
void UnsubValueChanged<T>(CVarDef<T> cVar, Action<T> onValueChanged)
where T : notnull;
/// <summary>
/// Unsubscribe an event previously registered with <see cref="OnValueChanged{T}(string,System.Action{T},bool)"/>.
/// </summary>
/// <param name="name">The name of the CVar to unsubscribe from.</param>
/// <param name="onValueChanged">The delegate to unsubscribe.</param>
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
void UnsubValueChanged<T>(string name, Action<T> onValueChanged)
where T : notnull;
}
}

View File

@@ -97,7 +97,7 @@ namespace Robust.Shared.Configuration
private void HandleNetVarMessage(MsgConVars message)
{
if(!_receivedInitialNwVars)
if (_netManager.IsClient && !_receivedInitialNwVars)
{
_receivedInitialNwVars = true;
@@ -125,7 +125,7 @@ namespace Robust.Shared.Configuration
ApplyNetVarChange(msg.MsgChannel, msg.NetworkedVars);
if(msg.Tick < _timing.LastRealTick)
if(msg.Tick != default && msg.Tick < _timing.LastRealTick)
Logger.WarningS("cfg", $"{msg.MsgChannel}: Received late nwVar message ({msg.Tick} < {_timing.LastRealTick} ).");
_netVarsMessages.RemoveSwap(i);
@@ -148,45 +148,50 @@ namespace Robust.Shared.Configuration
private void ApplyNetVarChange(INetChannel msgChannel, List<(string name, object value)> networkedVars)
{
Logger.DebugS("cfg", "Handling replicated cvars...");
Logger.DebugS("cfg", $"{msgChannel} Handling replicated cvars...");
foreach (var (name, value) in networkedVars)
if (_netManager.IsClient)
{
if (_netManager.IsClient) // Server sent us a CVar update.
// Server sent us a CVar update.
foreach (var (name, value) in networkedVars)
{
// Actually set the CVar
base.SetCVar(name, value);
Logger.DebugS("cfg", $"name={name}, val={value}");
}
else // Client sent us a CVar update
return;
}
// Client sent us a CVar update
if (!_replicatedCVars.TryGetValue(msgChannel, out var clientCVars))
{
Logger.WarningS("cfg", $"{msgChannel} tried to replicate CVars but is not in _replicatedCVars.");
return;
}
foreach (var (name, value) in networkedVars)
{
if (!_configVars.TryGetValue(name, out var cVar))
{
if (!_configVars.TryGetValue(name, out var cVar))
{
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an unknown CVar '{name}.'");
continue;
}
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an unknown CVar '{name}.'");
continue;
}
if (!cVar.Registered)
{
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an unregistered CVar '{name}.'");
continue;
}
if (!cVar.Registered)
{
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an unregistered CVar '{name}.'");
continue;
}
if((cVar.Flags & CVar.REPLICATED) != 0)
{
var clientCVars = _replicatedCVars[msgChannel];
if (clientCVars.ContainsKey(name))
clientCVars[name] = value;
else
clientCVars.Add(name, value);
Logger.DebugS("cfg", $"name={name}, val={value}");
}
else
{
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an un-replicated CVar '{name}.'");
}
if((cVar.Flags & CVar.REPLICATED) != 0)
{
clientCVars[name] = value;
Logger.DebugS("cfg", $"name={name}, val={value}");
}
else
{
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an un-replicated CVar '{name}.'");
}
}
}
@@ -290,7 +295,7 @@ namespace Robust.Shared.Configuration
Logger.InfoS("cfg", "Sending client info...");
var msg = _netManager.CreateNetMessage<MsgConVars>();
msg.Tick = _timing.CurTick;
msg.Tick = default;
msg.NetworkedVars = GetReplicatedVars();
_netManager.ClientSendMessage(msg);
}

View File

@@ -30,6 +30,8 @@ namespace Robust.Shared.Console
/// <inheritdoc />
public IReadOnlyDictionary<string, IConsoleCommand> RegisteredCommands => AvailableCommands;
public abstract event ConAnyCommandCallback? AnyCommandExecuted;
protected ConsoleHost()
{
LocalShell = new ConsoleShell(this, null);

View File

@@ -12,6 +12,8 @@ namespace Robust.Shared.Console
/// <param name="args">An array of all the parsed arguments.</param>
public delegate void ConCommandCallback(IConsoleShell shell, string argStr, string[] args);
public delegate void ConAnyCommandCallback(IConsoleShell shell, string commandName, string argStr, string[] args);
/// <summary>
/// The console host exists as a singleton subsystem that provides all of the features of the console API.
/// It will register console commands, spawn console shells and execute command strings.
@@ -38,7 +40,10 @@ namespace Robust.Shared.Console
/// </summary>
IReadOnlyDictionary<string, IConsoleCommand> RegisteredCommands { get; }
/// <summary>
/// Invoked before any console command is executed.
/// </summary>
event ConAnyCommandCallback AnyCommandExecuted;
event EventHandler ClearText;
/// <summary>

View File

@@ -871,6 +871,7 @@ Types:
IEquatable`1: { }
IFormatProvider: { All: True }
IFormattable: { All: True }
Index: { All: True }
IndexOutOfRangeException: { All: True }
Int16: { All: True }
Int32: { All: True }
@@ -912,6 +913,7 @@ Types:
ParamArrayAttribute: { All: True }
Predicate`1: { All: True } # Delegate
Random: { All: True }
Range: { All: True }
ReadOnlyMemory`1:
Methods:
- "!0[] ToArray()"

View File

@@ -1,35 +1,10 @@
using System;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// Called once when a collision starts
/// </summary>
public interface IStartCollide
{
/// <summary>
/// We'll pass in both our body and the other body to save the behaviors having to get these components themselves.
/// </summary>
[Obsolete("Use StartCollideEvent instead")]
void CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold);
}
/// <summary>
/// Called once when a collision ends.
/// </summary>
public interface IEndCollide
{
/// <summary>
/// Run behaviour after all other collision behaviors have run.
/// </summary>
[Obsolete("Use EndCollideEvent instead")]
void CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold);
}
[Serializable, NetSerializable]
public enum BodyStatus: byte
{

View File

@@ -33,6 +33,7 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Dynamics.Contacts;
using Robust.Shared.Physics.Dynamics.Joints;
@@ -59,6 +60,8 @@ namespace Robust.Shared.GameObjects
/// </summary>
public bool Island { get; set; }
internal BroadphaseComponent? Broadphase { get; set; }
/// <summary>
/// Store the body's index within the island so we can lookup its data.
/// Key is Island's ID and value is our index.
@@ -90,6 +93,47 @@ namespace Robust.Shared.GameObjects
}
}
[ViewVariables]
public Box2 LocalAABB
{
get
{
var broadphaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
var broadphase = broadphaseSystem.GetBroadphase(this);
if (broadphase == null) return new Box2();
var worldPos = Owner.Transform.WorldPosition;
var aabb = new Box2(worldPos, worldPos);
foreach (var fixture in Fixtures)
{
foreach (var proxy in fixture.Proxies)
{
aabb = aabb.Union(proxy.AABB);
}
}
return aabb;
}
}
[ViewVariables]
public Box2 WorldAABB
{
get
{
var broadphaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
var broadphase = broadphaseSystem.GetBroadphase(this);
if (broadphase == null) return new Box2();
var localAABB = LocalAABB;
var center = broadphase.Owner.Transform.WorldMatrix.Transform(localAABB.Center);
return new Box2Rotated(localAABB.Translated(center), broadphase.Owner.Transform.WorldRotation, center).CalcBoundingBox();
}
}
/// <summary>
/// Linked-list of all of our contacts.
/// </summary>
@@ -136,7 +180,7 @@ namespace Robust.Shared.GameObjects
Force = Vector2.Zero;
Torque = 0.0f;
RegenerateContacts();
EntitySystem.Get<SharedBroadphaseSystem>().RegenerateContacts(this);
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PhysicsBodyTypeChangedEvent(_bodyType, oldType), false);
}
@@ -242,39 +286,10 @@ namespace Robust.Shared.GameObjects
Awake = true;
}
/// <summary>
/// Removes all of our contacts and flags them as requiring regeneration next physics tick.
/// </summary>
public void RegenerateContacts()
{
var contactEdge = ContactEdges;
while (contactEdge != null)
{
var contactEdge0 = contactEdge;
contactEdge = contactEdge.Next;
PhysicsMap.ContactManager.Destroy(contactEdge0.Contact!);
}
ContactEdges = null;
var broadphaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
foreach (var fixture in Fixtures)
{
var proxyCount = fixture.ProxyCount;
foreach (var (gridId, proxies) in fixture.Proxies)
{
var broadPhase = broadphaseSystem.GetBroadPhase(Owner.Transform.MapID, gridId);
if (broadPhase == null) continue;
for (var i = 0; i < proxyCount; i++)
{
broadPhase.TouchProxy(proxies[i].ProxyId);
}
}
}
}
void ISerializationHooks.AfterDeserialization()
{
FixtureCount = _fixtures.Count;
foreach (var fixture in _fixtures)
{
fixture.Body = this;
@@ -446,17 +461,19 @@ namespace Robust.Shared.GameObjects
}
}
var broadphaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
foreach (var fixture in toRemoveFixtures)
{
computeProperties = true;
RemoveFixture(fixture);
broadphaseSystem.DestroyFixture(this, fixture);
}
// TODO: We also still need event listeners for shapes (Probably need C# events)
foreach (var fixture in toAddFixtures)
{
computeProperties = true;
AddFixture(fixture);
broadphaseSystem.CreateFixture(this, fixture);
fixture.Shape.ApplyState();
}
@@ -568,9 +585,12 @@ namespace Robust.Shared.GameObjects
}
}
[ViewVariables]
public int FixtureCount { get; internal set; }
[DataField("fixtures")]
[NeverPushInheritance]
private List<Fixture> _fixtures = new();
internal List<Fixture> _fixtures = new();
/// <summary>
/// Enables or disabled collision processing of this component.
@@ -657,9 +677,6 @@ namespace Robust.Shared.GameObjects
}
}
[ViewVariables]
public bool HasProxies { get; set; }
// I made Mass read-only just because overwriting it doesn't touch inertia.
/// <summary>
/// Current mass of the entity in kilograms.
@@ -1027,17 +1044,9 @@ namespace Robust.Shared.GameObjects
/// </summary>
public MapId BroadphaseMapId { get; set; }
public IEnumerable<PhysicsComponent> GetCollidingBodies()
{
foreach (var entity in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(this, Vector2.Zero))
{
yield return entity;
}
}
public IEnumerable<PhysicsComponent> GetBodiesIntersecting()
{
foreach (var entity in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(Owner.Transform.MapID, GetWorldAABB()))
foreach (var entity in EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(Owner.Transform.MapID, GetWorldAABB()))
{
yield return entity;
}
@@ -1064,24 +1073,6 @@ namespace Robust.Shared.GameObjects
return Transform.Mul(GetTransform(), localPoint);
}
/// <summary>
/// Remove the proxies from all the broadphases.
/// </summary>
public void ClearProxies()
{
if (!HasProxies) return;
var broadPhaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
var mapId = BroadphaseMapId;
foreach (var fixture in Fixtures)
{
fixture.ClearProxies(mapId, broadPhaseSystem);
}
HasProxies = false;
}
public void FixtureChanged(Fixture fixture)
{
// TODO: Optimise this a LOT
@@ -1089,12 +1080,32 @@ namespace Robust.Shared.GameObjects
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new FixtureUpdateMessage(this, fixture));
}
private string GetFixtureName(Fixture fixture)
public string GetFixtureName(Fixture fixture)
{
if (!string.IsNullOrEmpty(fixture.ID)) return fixture.ID;
// For any fixtures that aren't named in the code we will assign one.
return $"fixture-{_fixtures.IndexOf(fixture)}";
var i = 0;
while (true)
{
var found = false;
++i;
var name = $"fixture_{i}";
foreach (var existing in _fixtures)
{
if (existing.ID.Equals(name))
{
found = true;
break;
}
}
if (!found)
{
return name;
}
}
}
private string GetJointName(Joint joint)
@@ -1158,73 +1169,6 @@ namespace Robust.Shared.GameObjects
Force += force;
}
/// <summary>
/// Get the proxies for each of our fixtures and add them to the broadphases.
/// </summary>
/// <param name="mapManager"></param>
/// <param name="broadPhaseSystem"></param>
public void CreateProxies(IMapManager? mapManager = null, SharedBroadPhaseSystem? broadPhaseSystem = null)
{
if (HasProxies) return;
BroadphaseMapId = Owner.Transform.MapID;
if (BroadphaseMapId == MapId.Nullspace)
{
HasProxies = true;
return;
}
broadPhaseSystem ??= EntitySystem.Get<SharedBroadPhaseSystem>();
mapManager ??= IoCManager.Resolve<IMapManager>();
var worldPosition = Owner.Transform.WorldPosition;
var mapId = Owner.Transform.MapID;
var worldAABB = GetWorldAABB();
var worldRotation = Owner.Transform.WorldRotation.Theta;
// TODO: For singularity and shuttles: Any fixtures that have a MapGrid layer / mask needs to be added to the default broadphase (so it can collide with grids).
foreach (var gridId in mapManager.FindGridIdsIntersecting(mapId, worldAABB, true))
{
var broadPhase = broadPhaseSystem.GetBroadPhase(mapId, gridId);
DebugTools.AssertNotNull(broadPhase);
if (broadPhase == null) continue; // TODO
Vector2 offset = worldPosition;
double gridRotation = worldRotation;
if (gridId != GridId.Invalid)
{
var grid = mapManager.GetGrid(gridId);
offset -= grid.WorldPosition;
// TODO: Should probably have a helper for this
gridRotation = worldRotation - Owner.EntityManager.GetEntity(grid.GridEntityId).Transform.WorldRotation;
}
foreach (var fixture in Fixtures)
{
fixture.ProxyCount = fixture.Shape.ChildCount;
var proxies = new FixtureProxy[fixture.ProxyCount];
// TODO: Will need to pass in childIndex to this as well
for (var i = 0; i < fixture.ProxyCount; i++)
{
var aabb = fixture.Shape.CalculateLocalBounds(gridRotation).Translated(offset);
var proxy = new FixtureProxy(aabb, fixture, i);
proxy.ProxyId = broadPhase.AddProxy(ref proxy);
proxies[i] = proxy;
DebugTools.Assert(proxies[i].ProxyId != DynamicTree.Proxy.Free);
}
fixture.SetProxies(gridId, proxies);
}
}
HasProxies = true;
}
// TOOD: Need SetTransformIgnoreContacts so we can teleport body and /ignore contacts/
public void DestroyContacts()
{
@@ -1241,7 +1185,7 @@ namespace Robust.Shared.GameObjects
IEnumerable<IPhysBody> IPhysBody.GetCollidingEntities(Vector2 offset, bool approx)
{
return EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(this, offset, approx);
return EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(this, offset, approx);
}
/// <inheritdoc />
@@ -1254,6 +1198,9 @@ namespace Robust.Shared.GameObjects
_awake = false;
}
// TODO: Ordering fuckery need a new PR to fix some of this stuff
PhysicsMap = EntitySystem.Get<SharedPhysicsSystem>().Maps[Owner.Transform.MapID];
Dirty();
// Yeah yeah TODO Combine these
// Implicitly assume that stuff doesn't cover if a non-collidable is initialized.
@@ -1290,45 +1237,6 @@ namespace Robust.Shared.GameObjects
}
}
public void AddFixture(Fixture fixture)
{
// TODO: SynchronizeFixtures could be more optimally done. Maybe just eventbus it
// Also we need to queue updates and also not teardown completely every time.
_fixtures.Add(fixture);
Dirty();
EntitySystem.Get<SharedBroadPhaseSystem>().AddFixture(this, fixture);
}
public void RemoveFixture(Fixture fixture)
{
if (!_fixtures.Contains(fixture))
{
Logger.ErrorS("physics", $"Tried to remove fixture that isn't attached to the body {Owner.Uid}");
return;
}
ContactEdge? edge = ContactEdges;
while (edge != null)
{
var contact = edge.Contact!;
edge = edge.Next;
var fixtureA = contact.FixtureA;
var fixtureB = contact.FixtureB;
if (fixture == fixtureA || fixture == fixtureB)
{
PhysicsMap.ContactManager.Destroy(contact);
}
}
_fixtures.Remove(fixture);
EntitySystem.Get<SharedBroadPhaseSystem>().RemoveFixture(this, fixture);
ResetMassData();
Dirty();
}
public void AddJoint(Joint joint)
{
var id = GetJointName(joint);
@@ -1356,7 +1264,7 @@ namespace Robust.Shared.GameObjects
// Need to do these immediately in case collision behaviors deleted the body
// TODO: Could be more optimal as currently broadphase will call this ANYWAY
DestroyContacts();
ClearProxies();
EntitySystem.Get<SharedBroadphaseSystem>().RemoveBody(this);
CanCollide = false;
}
@@ -1382,7 +1290,27 @@ namespace Robust.Shared.GameObjects
var fixMass = fixture.Mass;
_mass += fixMass;
localCenter += fixture.Centroid * fixMass;
var center = Vector2.Zero;
// TODO: God this is garbage
switch (fixture.Shape)
{
case PhysShapeAabb aabb:
center = aabb.Centroid;
break;
case PhysShapeRect rect:
center = rect.Centroid;
break;
case PolygonShape poly:
center = poly.Centroid;
break;
case PhysShapeCircle circle:
center = circle.Position;
break;
}
localCenter += center * fixMass;
_inertia += fixture.Inertia;
}

View File

@@ -1,8 +1,6 @@
using System;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -20,6 +18,8 @@ namespace Robust.Shared.GameObjects
[DataField("boundingBox")]
private Box2 _boundingBox = new(-0.5f, -0.5f, 0.5f, 0.5f);
internal OccluderTreeComponent? Tree = null;
[ViewVariables(VVAccess.ReadWrite)]
public Box2 BoundingBox
{
@@ -28,15 +28,10 @@ namespace Robust.Shared.GameObjects
{
_boundingBox = value;
Dirty();
BoundingBoxChanged();
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new OccluderUpdateEvent(this));
}
}
private void BoundingBoxChanged()
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new OccluderBoundingBoxChangedMessage(this));
}
[ViewVariables(VVAccess.ReadWrite)]
public virtual bool Enabled
{
@@ -47,30 +42,19 @@ namespace Robust.Shared.GameObjects
return;
_enabled = value;
if (_enabled)
{
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new OccluderAddEvent(this));
}
else
{
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new OccluderRemoveEvent(this));
}
Dirty();
}
}
protected override void Startup()
{
base.Startup();
EntitySystem.Get<OccluderSystem>().AddOrUpdateEntity(Owner, Owner.Transform.Coordinates);
}
protected override void Shutdown()
{
base.Shutdown();
var transform = Owner.Transform;
var map = transform.MapID;
if (map != MapId.Nullspace)
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
new OccluderTreeRemoveOccluderMessage(this, map, transform.GridID));
}
}
public override ComponentState GetComponentState(ICommonSession player)
{
return new OccluderComponentState(Enabled, BoundingBox);
@@ -102,14 +86,4 @@ namespace Robust.Shared.GameObjects
}
}
}
internal struct OccluderBoundingBoxChangedMessage
{
public OccluderComponent Occluder;
public OccluderBoundingBoxChangedMessage(OccluderComponent occluder)
{
Occluder = occluder;
}
}
}

View File

@@ -0,0 +1,14 @@
using Robust.Shared.Physics;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// Stores the relevant occluder children of this entity.
/// </summary>
public sealed class OccluderTreeComponent : Component
{
public override string Name => "OccluderTree";
internal DynamicTree<OccluderComponent> Tree { get; set; } = default!;
}
}

View File

@@ -78,7 +78,7 @@ namespace Robust.Shared.GameObjects
xform.Parent = Owner.Transform;
// anchor snapping
xform.LocalPosition = Grid.GridTileToLocal(Grid.TileIndicesFor(xform.LocalPosition)).Position;
xform.LocalPosition = Grid.GridTileToLocal(Grid.TileIndicesFor(xform.WorldPosition)).Position;
xform.SetAnchored(result);

View File

@@ -734,7 +734,7 @@ namespace Robust.Shared.GameObjects
var newParentId = newState.ParentID;
var rebuildMatrices = false;
if (Parent?.Owner?.Uid != newParentId)
if (Parent?.Owner.Uid != newParentId)
{
if (newParentId != _parent)
{

View File

@@ -11,46 +11,74 @@ namespace Robust.Shared.GameObjects
protected void SubscribeNetworkEvent<T>(
EntityEventHandler<T> handler,
Type[]? before=null, Type[]? after=null)
Type[]? before = null, Type[]? after = null)
where T : notnull
{
EntityManager.EventBus.SubscribeEvent(EventSource.Network, this, handler, GetType(), before, after);
_subscriptions ??= new();
_subscriptions.Add(new SubBroadcast<T>(EventSource.Network));
}
protected void SubscribeNetworkEvent<T>(
EntitySessionEventHandler<T> handler,
Type[]? before=null, Type[]? after=null)
where T : notnull
{
EntityManager.EventBus.SubscribeSessionEvent(EventSource.Network, this, handler);
_subscriptions ??= new();
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(EventSource.Network));
SubEvent(EventSource.Network, handler, before, after);
}
protected void SubscribeLocalEvent<T>(
EntityEventHandler<T> handler,
Type[]? before=null, Type[]? after=null)
Type[]? before = null, Type[]? after = null)
where T : notnull
{
EntityManager.EventBus.SubscribeEvent(EventSource.Local, this, handler, GetType(), before, after);
SubEvent(EventSource.Local, handler, before, after);
}
_subscriptions ??= new();
_subscriptions.Add(new SubBroadcast<T>(EventSource.Local));
protected void SubscribeAllEvent<T>(
EntityEventHandler<T> handler,
Type[]? before = null, Type[]? after = null)
where T : notnull
{
SubEvent(EventSource.All, handler, before, after);
}
protected void SubscribeNetworkEvent<T>(
EntitySessionEventHandler<T> handler,
Type[]? before = null, Type[]? after = null)
where T : notnull
{
SubSessionEvent(EventSource.Network, handler, before, after);
}
protected void SubscribeLocalEvent<T>(
EntitySessionEventHandler<T> handler,
Type[]? before=null, Type[]? after=null)
Type[]? before = null, Type[]? after = null)
where T : notnull
{
EntityManager.EventBus.SubscribeSessionEvent(EventSource.Local, this, handler);
SubSessionEvent(EventSource.Local, handler, before, after);
}
protected void SubscribeAllEvent<T>(
EntitySessionEventHandler<T> handler,
Type[]? before = null, Type[]? after = null)
where T : notnull
{
SubSessionEvent(EventSource.All, handler, before, after);
}
private void SubEvent<T>(
EventSource src,
EntityEventHandler<T> handler,
Type[]? before, Type[]? after)
where T : notnull
{
EntityManager.EventBus.SubscribeEvent(src, this, handler, GetType(), before, after);
_subscriptions ??= new();
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(EventSource.Local));
_subscriptions.Add(new SubBroadcast<T>(src));
}
private void SubSessionEvent<T>(
EventSource src,
EntitySessionEventHandler<T> handler,
Type[]? before, Type[]? after)
where T : notnull
{
EntityManager.EventBus.SubscribeSessionEvent(src, this, handler, GetType(), before, after);
_subscriptions ??= new();
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(src));
}
[Obsolete("Unsubscribing of entity system events is now automatic")]
@@ -69,7 +97,7 @@ namespace Robust.Shared.GameObjects
protected void SubscribeLocalEvent<TComp, TEvent>(
ComponentEventHandler<TComp, TEvent> handler,
Type[]? before=null, Type[]? after=null)
Type[]? before = null, Type[]? after = null)
where TComp : IComponent
where TEvent : EntityEventArgs
{

View File

@@ -26,6 +26,9 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
#endif
private DependencyCollection _systemDependencyCollection = default!;
private List<Type> _systemTypes = new();
private static readonly Histogram _tickUsageHistogram = Metrics.CreateHistogram("robust_entity_systems_update_usage",
"Amount of time spent processing each entity system", new HistogramConfiguration
{
@@ -38,24 +41,11 @@ namespace Robust.Shared.GameObjects
private readonly Stopwatch _stopwatch = new();
/// <summary>
/// Maps system types to instances.
/// </summary>
[ViewVariables]
private readonly Dictionary<Type, IEntitySystem> _systems = new();
/// <summary>
/// Maps system supertypes to instances.
/// </summary>
[ViewVariables]
private readonly Dictionary<Type, IEntitySystem> _supertypeSystems = new();
private bool _initialized;
[ViewVariables] private UpdateReg[] _updateOrder = Array.Empty<UpdateReg>();
[ViewVariables] private IEntitySystem[] _frameUpdateOrder = Array.Empty<IEntitySystem>();
[ViewVariables] public IReadOnlyCollection<IEntitySystem> AllSystems => _systems.Values;
public bool MetricsEnabled { get; set; }
/// <inheritdoc />
@@ -68,55 +58,36 @@ namespace Robust.Shared.GameObjects
public T GetEntitySystem<T>()
where T : IEntitySystem
{
var type = typeof(T);
// check using exact match first, then check using the supertype
if (!_systems.ContainsKey(type))
{
if (!_supertypeSystems.ContainsKey(type))
{
throw new InvalidEntitySystemException();
}
else
{
return (T) _supertypeSystems[type];
}
}
return (T)_systems[type];
return _systemDependencyCollection.Resolve<T>();
}
/// <inheritdoc />
public bool TryGetEntitySystem<T>([NotNullWhen(true)] out T? entitySystem)
where T : IEntitySystem
{
if (_systems.TryGetValue(typeof(T), out var system))
{
entitySystem = (T) system;
return true;
}
if (_supertypeSystems.TryGetValue(typeof(T), out var systemFromSupertype))
{
entitySystem = (T) systemFromSupertype;
return true;
}
entitySystem = default;
return false;
return _systemDependencyCollection.TryResolveType<T>(out entitySystem);
}
/// <inheritdoc />
public void Initialize()
{
HashSet<Type> excludedTypes = new();
var excludedTypes = new HashSet<Type>();
_systemDependencyCollection = new(IoCManager.Instance!);
var subTypes = new Dictionary<Type, Type>();
_systemTypes.Clear();
foreach (var type in _reflectionManager.GetAllChildren<IEntitySystem>().Concat(_extraLoadedTypes))
{
Logger.DebugS("go.sys", "Initializing entity system {0}", type);
// Force IoC inject of all systems
var instance = _typeFactory.CreateInstanceUnchecked<IEntitySystem>(type, oneOff: true);
_systems.Add(type, instance);
_systemDependencyCollection.Register(type);
_systemTypes.Add(type);
excludedTypes.Add(type);
if (subTypes.ContainsKey(type))
{
subTypes.Remove(type);
}
// also register systems under their supertypes, so they can be retrieved by their supertype.
// We don't do this if there are multiple subtype systems of that supertype though, otherwise
@@ -127,27 +98,36 @@ namespace Robust.Shared.GameObjects
// so don't register under the supertype because it would be unclear
// which instance to return if we retrieved it by the supertype
if (excludedTypes.Contains(baseType)) continue;
if (_supertypeSystems.ContainsKey(baseType))
if (subTypes.ContainsKey(baseType))
{
_supertypeSystems.Remove(baseType);
subTypes.Remove(baseType);
excludedTypes.Add(baseType);
}
else
{
_supertypeSystems.Add(baseType, instance);
subTypes.Add(baseType, type);
}
}
}
foreach (var system in _systems.Values)
foreach (var (baseType, type) in subTypes)
{
_systemDependencyCollection.Register(baseType, type, overwrite: true);
_systemTypes.Remove(baseType);
}
_systemDependencyCollection.BuildGraph();
foreach (var systemType in _systemTypes)
{
var system = (IEntitySystem)_systemDependencyCollection.ResolveType(systemType);
system.Initialize();
SystemLoaded?.Invoke(this, new SystemChangedArgs(system));
}
// Create update order for entity systems.
var (fUpdate, update) = CalculateUpdateOrder(_systems.Values, _supertypeSystems);
var (fUpdate, update) = CalculateUpdateOrder(_systemTypes, subTypes, _systemDependencyCollection);
_frameUpdateOrder = fUpdate.ToArray();
_updateOrder = update
@@ -163,23 +143,24 @@ namespace Robust.Shared.GameObjects
private static (IEnumerable<IEntitySystem> frameUpd, IEnumerable<IEntitySystem> upd)
CalculateUpdateOrder(
Dictionary<Type, IEntitySystem>.ValueCollection systems,
Dictionary<Type, IEntitySystem> supertypeSystems)
List<Type> systemTypes,
Dictionary<Type, Type> subTypes,
DependencyCollection dependencyCollection)
{
var allNodes = new List<TopologicalSort.GraphNode<IEntitySystem>>();
var typeToNode = new Dictionary<Type, TopologicalSort.GraphNode<IEntitySystem>>();
foreach (var system in systems)
foreach (var systemType in systemTypes)
{
var node = new TopologicalSort.GraphNode<IEntitySystem>(system);
var node = new TopologicalSort.GraphNode<IEntitySystem>((IEntitySystem) dependencyCollection.ResolveType(systemType));
typeToNode.Add(system.GetType(), node);
typeToNode.Add(systemType, node);
allNodes.Add(node);
}
foreach (var (type, system) in supertypeSystems)
foreach (var (type, system) in subTypes)
{
var node = typeToNode[system.GetType()];
var node = typeToNode[system];
typeToNode[type] = node;
}
@@ -220,18 +201,20 @@ namespace Robust.Shared.GameObjects
public void Shutdown()
{
// System.Values is modified by RemoveSystem
foreach (var system in _systems.Values)
foreach (var systemType in _systemTypes)
{
if(_systemDependencyCollection == null) continue;
var system = (IEntitySystem)_systemDependencyCollection.ResolveType(systemType);
SystemUnloaded?.Invoke(this, new SystemChangedArgs(system));
system.Shutdown();
_entityManager.EventBus.UnsubscribeEvents(system);
}
_systems.Clear();
_systemTypes.Clear();
_updateOrder = Array.Empty<UpdateReg>();
_frameUpdateOrder = Array.Empty<IEntitySystem>();
_supertypeSystems.Clear();
_initialized = false;
_systemDependencyCollection?.Clear();
}
/// <inheritdoc />
@@ -343,6 +326,4 @@ namespace Robust.Shared.GameObjects
System = system;
}
}
public class InvalidEntitySystemException : Exception { }
}

View File

@@ -1,14 +1,37 @@
using System;
namespace Robust.Shared.GameObjects
{
public static class EventBusExt
{
public static void SubscribeSessionEvent<T>(this IEventBus eventBus, EventSource source,
IEntityEventSubscriber subscriber, EntitySessionEventHandler<T> handler)
public static void SubscribeSessionEvent<T>(
this IEventBus eventBus,
EventSource source,
IEntityEventSubscriber subscriber,
EntitySessionEventHandler<T> handler)
{
var wrapper = new HandlerWrapper<T>(handler);
eventBus.SubscribeEvent<EntitySessionMessage<T>>(source, subscriber, wrapper.Invoke);
}
public static void SubscribeSessionEvent<T>(
this IEventBus eventBus,
EventSource source,
IEntityEventSubscriber subscriber,
EntitySessionEventHandler<T> handler,
Type orderType,
Type[]? before=null,
Type[]? after=null)
{
var wrapper = new HandlerWrapper<T>(handler);
eventBus.SubscribeEvent<EntitySessionMessage<T>>(
source,
subscriber,
wrapper.Invoke,
orderType,
before, after);
}
private sealed class HandlerWrapper<T>
{
public HandlerWrapper(EntitySessionEventHandler<T> handler)

View File

@@ -32,8 +32,6 @@ namespace Robust.Shared.GameObjects
/// </summary>
event EventHandler<SystemChangedArgs> SystemUnloaded;
IReadOnlyCollection<IEntitySystem> AllSystems { get; }
/// <summary>
/// Get an entity system of the specified type.
/// </summary>

View File

@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -10,54 +10,88 @@ namespace Robust.Shared.GameObjects
{
public abstract class OccluderSystem : EntitySystem
{
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
private readonly Dictionary<MapId, Dictionary<GridId, DynamicTree<OccluderComponent>>> _gridTrees =
new();
private const float TreeGrowthRate = 256;
private readonly List<(OccluderComponent Occluder, EntityCoordinates Coordinates)> _occluderAddQueue =
new();
private readonly List<(OccluderComponent Occluder, EntityCoordinates Coordinates)> _occluderRemoveQueue =
new();
internal bool TryGetOccluderTreeForGrid(MapId mapId, GridId gridId, [NotNullWhen(true)] out DynamicTree<OccluderComponent>? gridTree)
{
gridTree = null;
if (!_gridTrees.TryGetValue(mapId, out var grids))
return false;
if (!grids.TryGetValue(gridId, out gridTree))
return false;
return true;
}
private Queue<OccluderEvent> _updates = new(64);
public override void Initialize()
{
base.Initialize();
_mapManager.MapCreated += OnMapCreated;
_mapManager.MapDestroyed += OnMapDestroyed;
_mapManager.OnGridCreated += OnGridCreated;
_mapManager.OnGridRemoved += OnGridRemoved;
SubscribeLocalEvent<MoveEvent>(EntMoved);
SubscribeLocalEvent<EntParentChangedMessage>(EntParentChanged);
SubscribeLocalEvent<OccluderBoundingBoxChangedMessage>(OccluderBoundingBoxChanged);
SubscribeLocalEvent<OccluderTreeRemoveOccluderMessage>(RemoveOccluder);
SubscribeLocalEvent<GridInitializeEvent>(HandleGridInit);
SubscribeLocalEvent<OccluderTreeComponent, ComponentInit>(HandleOccluderTreeInit);
SubscribeLocalEvent<OccluderComponent, ComponentInit>(HandleOccluderInit);
SubscribeLocalEvent<OccluderComponent, ComponentShutdown>(HandleOccluderShutdown);
SubscribeLocalEvent<OccluderComponent, MoveEvent>(EntMoved);
SubscribeLocalEvent<OccluderComponent, EntParentChangedMessage>(EntParentChanged);
SubscribeLocalEvent<OccluderEvent>(ev => _updates.Enqueue(ev));
}
internal IEnumerable<OccluderTreeComponent> GetOccluderTrees(MapId mapId, Box2 worldAABB)
{
if (mapId == MapId.Nullspace) yield break;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<OccluderTreeComponent>();
}
yield return _mapManager.GetMapEntity(mapId).GetComponent<OccluderTreeComponent>();
}
private void HandleOccluderInit(EntityUid uid, OccluderComponent component, ComponentInit args)
{
if (!component.Enabled) return;
_updates.Enqueue(new OccluderAddEvent(component));
}
private void HandleOccluderShutdown(EntityUid uid, OccluderComponent component, ComponentShutdown args)
{
if (!component.Enabled) return;
_updates.Enqueue(new OccluderRemoveEvent(component));
}
private void HandleOccluderTreeInit(EntityUid uid, OccluderTreeComponent component, ComponentInit args)
{
var capacity = (int) Math.Min(256, Math.Ceiling(component.Owner.Transform.ChildCount / TreeGrowthRate) * TreeGrowthRate);
component.Tree = new DynamicTree<OccluderComponent>(ExtractAabbFunc, capacity: capacity);
}
private void HandleGridInit(GridInitializeEvent ev)
{
EntityManager.GetEntity(ev.EntityUid).EnsureComponent<OccluderTreeComponent>();
}
private OccluderTreeComponent? GetOccluderTree(OccluderComponent component)
{
var entity = component.Owner;
if (entity.Transform.MapID == MapId.Nullspace) return null;
var parent = entity.Transform.Parent?.Owner;
while (true)
{
if (parent == null) break;
if (parent.TryGetComponent(out OccluderTreeComponent? comp)) return comp;
parent = parent.Transform.Parent?.Owner;
}
return null;
}
public override void Shutdown()
{
base.Shutdown();
_mapManager.MapCreated -= OnMapCreated;
_mapManager.MapDestroyed -= OnMapDestroyed;
_mapManager.OnGridCreated -= OnGridCreated;
_mapManager.OnGridRemoved -= OnGridRemoved;
_updates.Clear();
}
public override void FrameUpdate(float frameTime)
@@ -72,143 +106,59 @@ namespace Robust.Shared.GameObjects
private void UpdateTrees()
{
// Only care about stuff parented to a grid I think?
foreach (var (occluder, coordinates) in _occluderRemoveQueue)
while (_updates.TryDequeue(out var occluderUpdate))
{
if (coordinates.TryGetParent(EntityManager, out var parent) &&
parent.HasComponent<MapGridComponent>())
{
var gridTree = _gridTrees[parent.Transform.MapID][parent.Transform.GridID];
OccluderTreeComponent? tree;
var component = occluderUpdate.Component;
gridTree.Remove(occluder);
switch (occluderUpdate)
{
case OccluderAddEvent:
if (component.Tree != null) break;
tree = GetOccluderTree(component);
if (tree == null) break;
component.Tree = tree;
tree.Tree.Add(component);
break;
case OccluderUpdateEvent:
var oldTree = component.Tree;
tree = GetOccluderTree(component);
if (oldTree != tree)
{
oldTree?.Tree.Remove(component);
tree?.Tree.Add(component);
component.Tree = tree;
break;
}
tree?.Tree.Update(component);
break;
case OccluderRemoveEvent:
tree = component.Tree;
tree?.Tree.Remove(component);
break;
default:
throw new ArgumentOutOfRangeException($"No implemented occluder update for {occluderUpdate.GetType()}");
}
}
_occluderRemoveQueue.Clear();
foreach (var (occluder, coordinates) in _occluderAddQueue)
{
if (occluder.Deleted) continue;
if (coordinates.TryGetParent(EntityManager, out var parent) &&
parent.HasComponent<MapGridComponent>() || occluder.Owner.Transform.GridID == GridId.Invalid)
{
parent ??= EntityManager.GetEntity(occluder.Owner.Transform.ParentUid);
var gridTree = _gridTrees[parent.Transform.MapID][parent.Transform.GridID];
gridTree.AddOrUpdate(occluder);
}
}
_occluderAddQueue.Clear();
}
// If the Transform is removed BEFORE the Occluder,
// then the MapIdChanged code will handle and remove it (because MapId gets set to nullspace).
// Otherwise these will still have their past MapId and that's all we need..
private void RemoveOccluder(OccluderTreeRemoveOccluderMessage ev)
private void EntMoved(EntityUid uid, OccluderComponent component, MoveEvent args)
{
_gridTrees[ev.MapId][ev.GridId].Remove(ev.Occluder);
_updates.Enqueue(new OccluderUpdateEvent(component));
}
private void OccluderBoundingBoxChanged(OccluderBoundingBoxChangedMessage ev)
private void EntParentChanged(EntityUid uid, OccluderComponent component, EntParentChangedMessage args)
{
QueueUpdateOccluder(ev.Occluder, ev.Occluder.Owner.Transform.Coordinates);
}
private void EntMoved(MoveEvent ev)
{
ev.OldPosition.TryGetParent(EntityManager, out var oldParent);
ev.NewPosition.TryGetParent(EntityManager, out var newParent);
if (oldParent?.Uid != newParent?.Uid)
RemoveEntity(ev.Sender, ev.OldPosition);
AddOrUpdateEntity(ev.Sender, ev.NewPosition);
}
private void EntParentChanged(EntParentChangedMessage message)
{
if (!message.Entity.TryGetComponent(out OccluderComponent? occluder))
return;
// Really only care if it's a map or grid
if (message.OldParent != null && message.OldParent.TryGetComponent(out MapGridComponent? oldGrid))
{
var map = message.OldParent.Transform.MapID;
if (_gridTrees[map].TryGetValue(oldGrid.GridIndex, out var tree))
{
tree.Remove(occluder);
}
}
var newParent = EntityManager.GetEntity(message.Entity.Transform.ParentUid);
newParent.TryGetComponent(out MapGridComponent? newGrid);
var newGridIndex = newGrid?.GridIndex ?? GridId.Invalid;
var newMap = newParent.Transform.MapID;
if (!_gridTrees.TryGetValue(newMap, out var newMapGrids))
{
newMapGrids = new Dictionary<GridId, DynamicTree<OccluderComponent>>();
_gridTrees[newMap] = newMapGrids;
}
if (!newMapGrids.TryGetValue(newGridIndex, out var newTree))
{
newTree = new DynamicTree<OccluderComponent>(ExtractAabbFunc);
newMapGrids[newGridIndex] = newTree;
}
newTree.AddOrUpdate(occluder);
}
private void RemoveEntity(IEntity entity, EntityCoordinates coordinates)
{
if (entity.TryGetComponent(out OccluderComponent? occluder))
{
QueueRemoveOccluder(occluder, coordinates);
}
}
internal void AddOrUpdateEntity(IEntity entity, EntityCoordinates coordinates)
{
if (entity.TryGetComponent(out OccluderComponent? occluder))
{
QueueUpdateOccluder(occluder, coordinates);
}
// Do we even need the children update? Coz they be slow af and allocate a lot.
// If you do end up adding children back in then for the love of GOD check if the entity has a mapgridcomponent
}
private void OnMapDestroyed(object? sender, MapEventArgs e)
{
_gridTrees.Remove(e.Map);
_updates.Enqueue(new OccluderUpdateEvent(component));
}
private void OnMapCreated(object? sender, MapEventArgs e)
{
if (e.Map == MapId.Nullspace)
return;
if (e.Map == MapId.Nullspace) return;
_gridTrees[e.Map] = new Dictionary<GridId, DynamicTree<OccluderComponent>>();
}
private void OnGridRemoved(MapId mapId, GridId gridId)
{
foreach (var (_, gridIds) in _gridTrees)
{
if (gridIds.Remove(gridId))
break;
}
}
private void OnGridCreated(MapId mapId, GridId gridId)
{
if (!_gridTrees.TryGetValue(mapId, out var gridTree))
return;
gridTree.Add(gridId, new DynamicTree<OccluderComponent>(ExtractAabbFunc));
_mapManager.GetMapEntity(e.Map).EnsureComponent<OccluderTreeComponent>();
}
private static Box2 ExtractAabbFunc(in OccluderComponent o)
@@ -216,27 +166,26 @@ namespace Robust.Shared.GameObjects
return o.BoundingBox.Translated(o.Owner.Transform.LocalPosition);
}
private void QueueUpdateOccluder(OccluderComponent occluder, EntityCoordinates coordinates)
{
_occluderAddQueue.Add((occluder, coordinates));
}
private void QueueRemoveOccluder(OccluderComponent occluder, EntityCoordinates coordinates)
{
_occluderRemoveQueue.Add((occluder, coordinates));
}
public IEnumerable<RayCastResults> IntersectRayWithPredicate(MapId originMapId, in Ray ray, float maxLength,
public IEnumerable<RayCastResults> IntersectRayWithPredicate(MapId mapId, in Ray ray, float maxLength,
Func<IEntity, bool>? predicate = null, bool returnOnFirstHit = true)
{
if (mapId == MapId.Nullspace) return Enumerable.Empty<RayCastResults>();
var list = new List<RayCastResults>();
var worldBox = new Box2();
foreach (var gridId in _mapManager.FindGridIdsIntersecting(originMapId, worldBox, true))
var endPoint = ray.Position + ray.Direction * maxLength;
var worldBox = new Box2(Vector2.ComponentMin(ray.Position, endPoint), Vector2.ComponentMax(ray.Position, endPoint));
foreach (var comp in GetOccluderTrees(mapId, worldBox))
{
var gridTree = _gridTrees[originMapId][gridId];
var transform = comp.Owner.Transform;
var treePos = transform.WorldPosition;
var treeRot = transform.WorldRotation;
gridTree.QueryRay(ref list,
var relativePos = new Angle(-treeRot.Theta).RotateVec(ray.Position - treePos);
var treeRay = new Ray(relativePos, new Angle(-treeRot).RotateVec(ray.Direction));
comp.Tree.QueryRay(ref list,
(ref List<RayCastResults> state, in OccluderComponent value, in Vector2 point, float distFromOrigin) =>
{
if (distFromOrigin > maxLength)
@@ -251,24 +200,35 @@ namespace Robust.Shared.GameObjects
var result = new RayCastResults(distFromOrigin, point, value.Owner);
state.Add(result);
return !returnOnFirstHit;
}, ray);
}, treeRay);
}
return list;
}
}
internal readonly struct OccluderTreeRemoveOccluderMessage
internal sealed class OccluderAddEvent : OccluderEvent
{
public readonly OccluderComponent Occluder;
public readonly MapId MapId;
public readonly GridId GridId;
public OccluderAddEvent(OccluderComponent component) : base(component) {}
}
public OccluderTreeRemoveOccluderMessage(OccluderComponent occluder, MapId mapId, GridId gridId)
internal sealed class OccluderUpdateEvent : OccluderEvent
{
public OccluderUpdateEvent(OccluderComponent component) : base(component) {}
}
internal sealed class OccluderRemoveEvent : OccluderEvent
{
public OccluderRemoveEvent(OccluderComponent component) : base(component) {}
}
internal abstract class OccluderEvent : EntityEventArgs
{
public OccluderComponent Component { get; }
public OccluderEvent(OccluderComponent component)
{
Occluder = occluder;
MapId = mapId;
GridId = gridId;
Component = component;
}
}
}

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Robust.Shared.Containers;
using Robust.Shared.IoC;
using Robust.Shared.Map;
@@ -11,10 +12,23 @@ namespace Robust.Shared.GameObjects
{
[Dependency] private readonly IMapManager _mapManager = default!;
private Queue<MoveEvent> _queuedEvents = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MoveEvent>(HandleMove);
SubscribeLocalEvent<MoveEvent>(ev => _queuedEvents.Enqueue(ev));
}
public override void Update(float frameTime)
{
base.Update(frameTime);
// Need to queue because otherwise calling HandleMove during FrameUpdate will lead to prediction isues.
while (_queuedEvents.TryDequeue(out var moveEvent))
{
HandleMove(moveEvent);
}
}
private void HandleMove(MoveEvent moveEvent)
@@ -31,9 +45,16 @@ namespace Robust.Shared.GameObjects
var transform = entity.Transform;
if (float.IsNaN(moveEvent.NewPosition.X) || float.IsNaN(moveEvent.NewPosition.Y))
{
return;
}
var mapPos = moveEvent.NewPosition.ToMapPos(EntityManager);
// Change parent if necessary
if (_mapManager.TryFindGridAt(transform.MapID, moveEvent.NewPosition.ToMapPos(EntityManager), out var grid) &&
grid.GridEntityId.IsValid() &&
if (_mapManager.TryFindGridAt(transform.MapID, mapPos, out var grid) &&
EntityManager.TryGetEntity(grid.GridEntityId, out var gridEnt) &&
grid.GridEntityId != entity.Uid)
{
// Some minor duplication here with AttachParent but only happens when going on/off grid so not a big deal ATM.

View File

@@ -76,6 +76,7 @@ namespace Robust.Shared.GameObjects
});
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
public IReadOnlyDictionary<MapId, PhysicsMap> Maps => _maps;
private Dictionary<MapId, PhysicsMap> _maps = new();
@@ -83,7 +84,7 @@ namespace Robust.Shared.GameObjects
internal IReadOnlyList<VirtualController> Controllers => _controllers;
private List<VirtualController> _controllers = new();
public Action<Fixture, Fixture, float, Manifold>? KinematicControllerCollision;
public Action<Fixture, Fixture, float, Vector2>? KinematicControllerCollision;
public bool MetricsEnabled;
private readonly Stopwatch _stopwatch = new();
@@ -146,7 +147,7 @@ namespace Robust.Shared.GameObjects
body.LinearVelocity += linearVelocityDiff;
body.AngularVelocity += angularVelocityDiff;
}
private void HandleGridInit(GridInitializeEvent ev)
{
if (!EntityManager.TryGetEntity(ev.EntityUid, out var gridEntity)) return;
@@ -355,6 +356,8 @@ namespace Robust.Shared.GameObjects
if (mapId == MapId.Nullspace) continue;
map.ProcessQueue();
}
_physicsManager.ClearTransforms();
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -68,7 +69,13 @@ namespace Robust.Shared.GameObjects
if (ev.Sender.Deleted)
continue;
RaiseLocalEvent(ev);
// Hopefully we can remove this when PVS gets updated to not use NaNs
if (!ev.NewPosition.IsValid(EntityManager))
{
continue;
}
RaiseLocalEvent(ev.Sender.Uid, ev);
}
}
}

View File

@@ -48,6 +48,22 @@ namespace Robust.Shared.IoC
_parentCollection = parentCollection;
}
/// <inheritdoc />
public bool TryResolveType<T>([NotNullWhen(true)] out T? instance)
{
if (TryResolveType(typeof(T), out object? rawInstance))
{
if (rawInstance is T typedInstance)
{
instance = typedInstance;
return true;
}
}
instance = default;
return false;
}
/// <inheritdoc />
public bool TryResolveType(Type objectType, [MaybeNullWhen(false)] out object instance)
{
@@ -95,6 +111,8 @@ namespace Robust.Shared.IoC
}, overwrite);
}
/// <inheritdoc />
public void Register<TInterface, TImplementation>(DependencyFactoryDelegate<TImplementation> factory, bool overwrite = false)
where TImplementation : class, TInterface
@@ -108,9 +126,12 @@ namespace Robust.Shared.IoC
}
/// <inheritdoc />
public void Register(Type implementation, DependencyFactoryDelegate<object>? factory = null, bool overwrite = false)
public void Register(Type implementation, DependencyFactoryDelegate<object>? factory = null,
bool overwrite = false) => Register(implementation, implementation, factory, overwrite);
public void Register(Type interfaceType, Type implementation, DependencyFactoryDelegate<object>? factory = null, bool overwrite = false)
{
CheckRegisterInterface(implementation, implementation, overwrite);
CheckRegisterInterface(interfaceType, implementation, overwrite);
object DefaultFactory()
{
@@ -144,9 +165,9 @@ namespace Robust.Shared.IoC
return Activator.CreateInstance(implementation, parameters)!;
}
_resolveTypes[implementation] = implementation;
_resolveTypes[interfaceType] = implementation;
_resolveFactories[implementation] = factory ?? DefaultFactory;
_pendingResolves.Enqueue(implementation);
_pendingResolves.Enqueue(interfaceType);
}
[AssertionMethod]
@@ -174,19 +195,25 @@ namespace Robust.Shared.IoC
/// <inheritdoc />
public void RegisterInstance<TInterface>(object implementation, bool overwrite = false)
{
RegisterInstance(typeof(TInterface), implementation, overwrite);
}
/// <inheritdoc />
public void RegisterInstance(Type type, object implementation, bool overwrite = false)
{
if (implementation == null)
throw new ArgumentNullException(nameof(implementation));
if (!(implementation is TInterface))
if (!implementation.GetType().IsAssignableTo(type))
throw new InvalidOperationException(
$"Implementation type {implementation.GetType()} is not assignable to interface type {typeof(TInterface)}");
$"Implementation type {implementation.GetType()} is not assignable to type {type}");
CheckRegisterInterface(typeof(TInterface), implementation.GetType(), overwrite);
CheckRegisterInterface(type, implementation.GetType(), overwrite);
// do the equivalent of BuildGraph with a single type.
_resolveTypes[typeof(TInterface)] = implementation.GetType();
_services[typeof(TInterface)] = implementation;
_resolveTypes[type] = implementation.GetType();
_services[type] = implementation;
InjectDependencies(implementation, true);

View File

@@ -75,6 +75,19 @@ namespace Robust.Shared.IoC
/// </param>
void Register(Type implementation, DependencyFactoryDelegate<object>? factory = null, bool overwrite = false);
/// <summary>
/// Registers a simple implementation without an interface.
/// </summary>
/// <param name="interfaceType">The type that will be resolvable.</param>
/// <param name="implementation">The type that will be resolvable.</param>
/// <param name="factory">A factory method to construct the instance of the implementation.</param>
/// <param name="overwrite">
/// If true, do not throw an <see cref="InvalidOperationException"/> if an interface is already registered,
/// replace the current implementation instead.
/// </param>
void Register(Type interfaceType, Type implementation, DependencyFactoryDelegate<object>? factory = null,
bool overwrite = false);
/// <summary>
/// Registers an interface to an existing instance of an implementation,
/// making it accessible to <see cref="IDependencyCollection.Resolve{T}"/>.
@@ -82,7 +95,6 @@ namespace Robust.Shared.IoC
/// <see cref="IDependencyCollection.BuildGraph"/> does not need to be called after registering an instance.
/// </summary>
/// <typeparam name="TInterface">The type that will be resolvable.</typeparam>
/// <typeparam name="TImplementation">The type that will be constructed as implementation.</typeparam>
/// <param name="implementation">The existing instance to use as the implementation.</param>
/// <param name="overwrite">
/// If true, do not throw an <see cref="InvalidOperationException"/> if an interface is already registered,
@@ -90,6 +102,20 @@ namespace Robust.Shared.IoC
/// </param>
void RegisterInstance<TInterface>(object implementation, bool overwrite = false);
/// <summary>
/// Registers an interface to an existing instance of an implementation,
/// making it accessible to <see cref="IDependencyCollection.Resolve{T}"/>.
/// Unlike <see cref="IDependencyCollection.Register{TInterface, TImplementation}"/>,
/// <see cref="IDependencyCollection.BuildGraph"/> does not need to be called after registering an instance.
/// </summary>
/// <param name="type">The type that will be resolvable.</param>
/// <param name="implementation">The existing instance to use as the implementation.</param>
/// <param name="overwrite">
/// If true, do not throw an <see cref="InvalidOperationException"/> if an interface is already registered,
/// replace the current implementation instead.
/// </param>
void RegisterInstance(Type type, object implementation, bool overwrite = false);
/// <summary>
/// Clear all services and types.
/// Use this between unit tests and on program shutdown.
@@ -119,6 +145,11 @@ namespace Robust.Shared.IoC
[Pure]
object ResolveType(Type type);
/// <summary>
/// Resolve a dependency manually.
/// </summary>
bool TryResolveType<T>([NotNullWhen(true)] out T? instance);
/// <summary>
/// Resolve a dependency manually.
/// </summary>

View File

@@ -142,6 +142,14 @@ namespace Robust.Shared.Localization
}
}
public sealed record LocValueTimeSpan(TimeSpan Value) : LocValue<TimeSpan>(Value)
{
public override string Format(LocContext ctx)
{
return Value.ToString("g", ctx.Culture);
}
}
public sealed record LocValueString(string Value) : LocValue<string>(Value)
{
public override string Format(LocContext ctx)
@@ -200,4 +208,4 @@ namespace Robust.Shared.Localization
return matchValue.Equals(Value.ToString(), StringComparison.InvariantCultureIgnoreCase);
}#1#
}*/
}
}

View File

@@ -257,6 +257,7 @@ namespace Robust.Shared.Localization
ILocValue wrap => new FluentLocWrapperType(wrap),
IEntity entity => new FluentLocWrapperType(new LocValueEntity(entity)),
DateTime dateTime => new FluentLocWrapperType(new LocValueDateTime(dateTime)),
TimeSpan timeSpan => new FluentLocWrapperType(new LocValueTimeSpan(timeSpan)),
bool or Enum => (FluentString)obj.ToString()!.ToLowerInvariant(),
string str => (FluentString)str,
byte num => (FluentNumber)num,
@@ -280,9 +281,8 @@ namespace Robust.Shared.Localization
LocValueNone => FluentNone.None,
LocValueNumber number => (FluentNumber)number.Value,
LocValueString str => (FluentString)str.Value,
LocValueDateTime dateTime => new FluentLocWrapperType(dateTime),
_ => new FluentLocWrapperType(locValue),
};
}
}
}
}

View File

@@ -365,10 +365,10 @@ namespace Robust.Shared.Map
/// <summary>
/// Creates a set of EntityCoordinates given some MapCoordinates.
/// </summary>
/// <param name="entityManager"></param>
/// <param name="mapManager"></param>
/// <param name="coordinates"></param>
/// <returns></returns>
[Obsolete("Use FromMap(IMapManager mapManager, MapCoordinates coordinates) instead.")]
public static EntityCoordinates FromMap(IEntityManager entityManager, IMapManager mapManager, MapCoordinates coordinates)
{
var mapId = coordinates.MapId;
@@ -377,6 +377,19 @@ namespace Robust.Shared.Map
return new EntityCoordinates(mapEntity.Uid, coordinates.Position);
}
/// <summary>
/// Creates a set of EntityCoordinates given some MapCoordinates.
/// </summary>
/// <param name="mapManager"></param>
/// <param name="coordinates"></param>
public static EntityCoordinates FromMap(IMapManager mapManager, MapCoordinates coordinates)
{
var mapId = coordinates.MapId;
var mapEntity = mapManager.GetMapEntity(mapId);
return new EntityCoordinates(mapEntity.Uid, coordinates.Position);
}
/// <summary>
/// Converts this set of coordinates to Vector2i.
/// </summary>

View File

@@ -47,6 +47,10 @@ namespace Robust.Shared.Map
/// </summary>
Vector2 WorldPosition { get; set; }
Matrix3 WorldMatrix { get; }
Matrix3 InvWorldMatrix { get; }
#region TileAccess
/// <summary>

View File

@@ -112,6 +112,34 @@ namespace Robust.Shared.Map
}
}
/// <inheritdoc />
[ViewVariables]
public Matrix3 WorldMatrix
{
get
{
//TODO: Make grids real parents of entities.
if(GridEntityId.IsValid())
return _mapManager.EntityManager.GetEntity(GridEntityId).Transform.WorldMatrix;
return Matrix3.Identity;
}
}
/// <inheritdoc />
[ViewVariables]
public Matrix3 InvWorldMatrix
{
get
{
//TODO: Make grids real parents of entities.
if(GridEntityId.IsValid())
return _mapManager.EntityManager.GetEntity(GridEntityId).Transform.InvWorldMatrix;
return Matrix3.Identity;
}
}
/// <summary>
/// Expands the AABB for this grid when a new tile is added. If the tile is already inside the existing AABB,
/// nothing happens. If it is outside, the AABB is expanded to fit the new tile.
@@ -489,7 +517,7 @@ namespace Robust.Shared.Map
/// <inheritdoc />
public Vector2 WorldToLocal(Vector2 posWorld)
{
return posWorld - WorldPosition;
return InvWorldMatrix.Transform(posWorld);
}
/// <inheritdoc />
@@ -509,7 +537,7 @@ namespace Robust.Shared.Map
/// <inheritdoc />
public Vector2 LocalToWorld(Vector2 posLocal)
{
return posLocal + WorldPosition;
return WorldMatrix.Transform(posLocal);
}
public Vector2i WorldToTile(Vector2 posWorld)

View File

@@ -523,7 +523,10 @@ namespace Robust.Shared.Map
/// <inheritdoc />
public bool TryFindGridAt(MapId mapId, Vector2 worldPos, [NotNullWhen(true)] out IMapGrid? grid)
{
// TODO: this won't actually "work" but we need the broadphase refactor to finish it.
// TODO: this won't actually "work" but tests are fucking me hard
// We probably need to move these methods over to SharedBroadphaseSystem as they need to go through
// physics to find grids intersecting a point anyway but the level of refactoring required for that
// will kill me.
foreach (var mapGrid in _grids.Values)
{
if (mapGrid.ParentMapId != mapId)
@@ -548,31 +551,30 @@ namespace Robust.Shared.Map
public IEnumerable<IMapGrid> FindGridsIntersecting(MapId mapId, Box2 worldArea)
{
// TODO: Unfortunately can't use BroadphaseSystem here as it will explode. Need to suss it out with DI
// TryFindGridAt works as is and helps with grid traversals.
return _grids.Values.Where(g => g.ParentMapId == mapId && g.WorldBounds.Intersects(worldArea));
}
public IEnumerable<GridId> FindGridIdsIntersecting(MapId mapId, Box2 worldArea, bool includeInvalid = false)
{
foreach (var (_, grid) in _grids)
var broadphase = EntitySystem.Get<SharedBroadphaseSystem>();
foreach (var broady in broadphase.GetBroadphases(mapId, worldArea))
{
if (grid.ParentMapId != mapId) continue;
if (!broady.Owner.TryGetComponent(out MapGridComponent? mapGridComponent)) continue;
var gridBounds = grid.WorldBounds;
// If the worldArea is wholly contained within a grid then no need to get invalid
if (gridBounds.Encloses(worldArea))
yield return mapGridComponent.GridIndex;
// TODO: Optimise this. Need to avoid returning invalid unless absolutely necessary but still this check
// be hella expensive. Also doesn't account for rotation so.
if (broady.Owner.GetComponent<PhysicsComponent>().GetWorldAABB().Encloses(worldArea))
{
yield return grid.Index;
yield break;
}
if (gridBounds.Intersects(worldArea))
{
yield return grid.Index;
}
}
if (includeInvalid)
yield return GridId.Invalid;
yield return GridId.Invalid;
}
public virtual void DeleteGrid(GridId gridID)

View File

@@ -56,6 +56,11 @@ namespace Robust.Shared.Network
NetUserData UserData { get; }
/// <summary>
/// Has the serializer handshake completed and <see cref="INetManager.Connected"/> been ran?
/// </summary>
bool IsHandshakeComplete { get; }
/// <summary>
/// Creates a new NetMessage to be filled up and sent.
/// </summary>

View File

@@ -20,7 +20,7 @@ namespace Robust.Shared.Network.Messages
[UsedImplicitly]
internal class MsgMapStrClientHandshake : NetMessage
{
public override MsgGroups MsgGroup => MsgGroups.Core;
public override MsgGroups MsgGroup => MsgGroups.String;
/// <value>
/// <c>true</c> if the client needs a new copy of the mapping,

View File

@@ -216,7 +216,7 @@ namespace Robust.Shared.Network
private static byte[] MakeAuthHash(byte[] sharedSecret, byte[] pkBytes)
{
Logger.DebugS("auth", "shared: {0}, pk: {1}", Convert.ToBase64String(sharedSecret), Convert.ToBase64String(pkBytes));
// Logger.DebugS("auth", "shared: {0}, pk: {1}", Convert.ToBase64String(sharedSecret), Convert.ToBase64String(pkBytes));
var incHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
incHash.AppendData(sharedSecret);

View File

@@ -46,6 +46,8 @@ namespace Robust.Shared.Network
[ViewVariables] public NetUserId UserId => UserData.UserId;
[ViewVariables] public NetUserData UserData { get; }
public bool IsHandshakeComplete { get; set; }
// Only used on server, contains the encryption to use for this channel.
public NetEncryption? Encryption { get; set; }
@@ -88,6 +90,11 @@ namespace Robust.Shared.Network
if (_connection.Status == NetConnectionStatus.Connected)
_connection.Disconnect(reason);
}
public override string ToString()
{
return $"{RemoteEndPoint}/{UserId}";
}
}
}
}

View File

@@ -234,7 +234,7 @@ namespace Robust.Shared.Network
Logger.InfoS("net",
"Approved {ConnectionEndpoint} with username {Username} user ID {userId} into the server",
connection.RemoteEndPoint, userData.UserName, userData.UserName);
connection.RemoteEndPoint, userData.UserName, userData.UserId);
// Handshake complete!
HandleInitialHandshakeComplete(peer, connection, userData, encryption, type);

View File

@@ -118,7 +118,7 @@ namespace Robust.Shared.Network
/// <summary>
/// Holds lookup table for NetMessage.Id -> NetMessage.Type
/// </summary>
private readonly Dictionary<string, Type> _messages = new();
private readonly Dictionary<string, (Type type, bool isHandshake)> _messages = new();
/// <summary>
/// The StringTable for transforming packet Ids to Packet name.
@@ -204,7 +204,9 @@ namespace Robust.Shared.Network
public IReadOnlyDictionary<Type, ProcessMessage> CallbackAudit => _callbacks;
/// <inheritdoc />
public INetChannel? ServerChannel
public INetChannel? ServerChannel => ServerChannelImpl;
private NetChannel? ServerChannelImpl
{
get
{
@@ -261,7 +263,7 @@ namespace Robust.Shared.Network
_serializer.ClientHandshakeComplete += () =>
{
Logger.InfoS("net", "Client completed serializer handshake.");
OnConnected(ServerChannel!);
OnConnected(ServerChannelImpl!);
};
_initialized = true;
@@ -792,6 +794,7 @@ namespace Robust.Shared.Network
var id = msg.ReadByte();
ref var entry = ref _netMsgFunctions[id];
if (entry.CreateFunction == null)
{
Logger.WarningS("net",
@@ -801,6 +804,15 @@ namespace Robust.Shared.Network
return true;
}
if (!channel.IsHandshakeComplete && !entry.IsHandshake)
{
Logger.WarningS("net",
$"{msg.SenderConnection.RemoteEndPoint}: Got non-handshake message {entry.Type.Name} before handshake completion.");
channel.Disconnect("Got unacceptable net message before handshake completion");
return true;
}
var type = entry.Type;
var instance = entry.CreateFunction(channel);
@@ -837,6 +849,7 @@ namespace Robust.Shared.Network
// Callback must be available or else construction delegate will not be registered.
var callback = _callbacks[type];
// Logger.DebugS("net", $"RECV: {instance.GetType().Name}");
try
{
callback?.Invoke(instance);
@@ -857,11 +870,13 @@ namespace Robust.Shared.Network
return;
}
if (!_messages.TryGetValue(name, out var packetType))
if (!_messages.TryGetValue(name, out var msgDat))
{
return;
}
var (packetType, isHandshake) = msgDat;
if (!_callbacks.ContainsKey(packetType))
{
return;
@@ -897,6 +912,7 @@ namespace Robust.Shared.Network
ref var entry = ref _netMsgFunctions[id];
entry.CreateFunction = @delegate;
entry.Type = packetType;
entry.IsHandshake = isHandshake;
}
#region NetMessages
@@ -909,7 +925,7 @@ namespace Robust.Shared.Network
var name = new T().MsgName;
var id = _strings.AddString(name);
_messages.Add(name, typeof(T));
_messages.Add(name, (typeof(T), (accept & NetMessageAccept.Handshake) != 0));
var thisSide = IsServer ? NetMessageAccept.Server : NetMessageAccept.Client;
@@ -986,6 +1002,9 @@ namespace Robust.Shared.Network
foreach (var channel in _channels.Values)
{
if (!channel.IsHandshakeComplete)
continue;
ServerSendMessage(message, channel);
}
}
@@ -1006,6 +1025,12 @@ namespace Robust.Shared.Network
var method = message.DeliveryMethod;
peer.SendMessage(packet, channel.Connection, method);
LogSend(message, method, packet);
}
private static void LogSend(NetMessage message, NetDeliveryMethod method, NetOutgoingMessage packet)
{
// Logger.DebugS("net", $"SEND: {message.GetType().Name} {method} {packet.LengthBytes}");
}
/// <inheritdoc />
@@ -1042,6 +1067,7 @@ namespace Robust.Shared.Network
}
peer.Peer.SendMessage(packet, peer.ConnectionsWithChannels[0], method);
LogSend(message, method, packet);
}
#endregion NetMessages
@@ -1068,8 +1094,10 @@ namespace Robust.Shared.Network
ConnectFailed?.Invoke(this, args);
}
private void OnConnected(INetChannel channel)
private void OnConnected(NetChannel channel)
{
channel.IsHandshakeComplete = true;
Connected?.Invoke(this, new NetChannelArgs(channel));
}
@@ -1151,6 +1179,7 @@ namespace Robust.Shared.Network
private struct NetMsgEntry
{
public Func<INetChannel, NetMessage>? CreateFunction;
public bool IsHandshake;
public Type Type;
}
}

View File

@@ -13,16 +13,26 @@ namespace Robust.Shared.Network
/// <summary>
/// Message can only be received on the server and it is an error to send it to a client.
/// </summary>
Server = 1,
Server = 1 << 0,
/// <summary>
/// Message can only be received on the client and it is an error to send it to a server.
/// </summary>
Client = 2,
Client = 1 << 1,
/// <summary>
/// Message can be received on both client and server.
/// </summary>
Both = Client | Server
Both = Client | Server,
/// <summary>
/// Message is used during connection handshake and may be sent before the initial handshake is completed.
/// </summary>
/// <remarks>
/// There is a window of time between the initial authentication handshake and serialization handshake,
/// where the connection *does* have an INetChannel.
/// During this handshake messages are still blocked however unless this flag is sent on the message type.
/// </remarks>
Handshake = 1 << 2,
}
}

View File

@@ -106,5 +106,15 @@ namespace Robust.Shared.Network
message.Position += length * 8;
return stream;
}
public static TimeSpan ReadTimeSpan(this NetIncomingMessage message)
{
return TimeSpan.FromTicks(message.ReadInt64());
}
public static void Write(this NetOutgoingMessage message, TimeSpan timeSpan)
{
message.Write(timeSpan.Ticks);
}
}
}

View File

@@ -61,7 +61,7 @@ namespace Robust.Shared.Network
_callback = callback;
_updateCallback = updateCallback;
_network.RegisterNetMessage<MsgStringTableEntries>(ReceiveEntries, NetMessageAccept.Client);
_network.RegisterNetMessage<MsgStringTableEntries>(ReceiveEntries, NetMessageAccept.Client | NetMessageAccept.Handshake);
Reset();
}

View File

@@ -369,17 +369,6 @@ namespace Robust.Shared.Physics
return _nodes[proxy].UserData;
}
/// <summary>
/// Get the fat AABB for a proxy.
/// </summary>
/// <param name="proxyId">The proxy id.</param>
/// <param name="fatAABB">The fat AABB.</param>
public void GetFatAABB(Proxy proxy, out Box2 fatAABB)
{
DebugTools.Assert(0 <= proxy && proxy < Capacity);
fatAABB = _nodes[proxy].Aabb;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool WasMoved(Proxy proxy)
{

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Dynamics;
@@ -8,20 +7,10 @@ namespace Robust.Shared.Physics.Broadphase
{
public class DynamicTreeBroadPhase : IBroadPhase
{
public MapId MapId { get; set; }
public GridId GridId { get; set; }
// TODO: DynamicTree seems slow at updates when we have large entity counts so when we have boxstation
// need to suss out whether chunking it might be useful.
private B2DynamicTree<FixtureProxy> _tree = new(capacity: 256);
private B2DynamicTree<FixtureProxy> _tree = default!;
private readonly DynamicTree<FixtureProxy>.ExtractAabbDelegate _extractAabb = ExtractAabbFunc;
private DynamicTree.Proxy[] _moveBuffer;
private int _moveCapacity;
private int _moveCount;
private (DynamicTree.Proxy ProxyA, DynamicTree.Proxy ProxyB)[] _pairBuffer;
private int _pairCapacity;
private int _pairCount;
@@ -29,84 +18,24 @@ namespace Robust.Shared.Physics.Broadphase
private B2DynamicTree<FixtureProxy>.QueryCallback _queryCallback;
private DynamicTree.Proxy _queryProxyId;
public DynamicTreeBroadPhase(MapId mapId, GridId gridId)
public DynamicTreeBroadPhase(int capacity)
{
MapId = mapId;
GridId = gridId;
_tree = new B2DynamicTree<FixtureProxy>(capacity: capacity);
_queryCallback = QueryCallback;
_proxyCount = 0;
_pairCapacity = 16;
_pairCount = 0;
_pairBuffer = new (DynamicTree.Proxy ProxyA, DynamicTree.Proxy ProxyB)[_pairCapacity];
_moveCapacity = 16;
_moveCount = 0;
_moveBuffer = new DynamicTree.Proxy[_moveCapacity];
}
public DynamicTreeBroadPhase() : this(256) {}
private static Box2 ExtractAabbFunc(in FixtureProxy proxy)
{
return proxy.AABB;
}
public void UpdatePairs(BroadPhaseDelegate callback)
{
// Reset pair buffer
_pairCount = 0;
// Perform tree queries for all moving proxies.
for (int j = 0; j < _moveCount; ++j)
{
_queryProxyId = _moveBuffer[j];
if (_queryProxyId == DynamicTree.Proxy.Free)
{
continue;
}
// We have to query the tree with the fat AABB so that
// we don't fail to create a pair that may touch later.
Box2 fatAABB;
_tree.GetFatAABB(_queryProxyId, out fatAABB);
// Query tree, create pairs and add them pair buffer.
_tree.Query(_queryCallback, in fatAABB);
}
// Reset move buffer
_moveCount = 0;
// Sort the pair buffer to expose duplicates.
Array.Sort(_pairBuffer, 0, _pairCount);
// Send the pairs back to the client.
int i = 0;
while (i < _pairCount)
{
var primaryPair = _pairBuffer[i];
FixtureProxy userDataA = _tree.GetUserData(primaryPair.ProxyA)!;
FixtureProxy userDataB = _tree.GetUserData(primaryPair.ProxyB)!;
callback(GridId, in userDataA, in userDataB);
++i;
// Skip any duplicate pairs.
while (i < _pairCount)
{
(DynamicTree.Proxy ProxyA, DynamicTree.Proxy ProxyB) pair = _pairBuffer[i];
if (pair.ProxyA != primaryPair.ProxyA || pair.ProxyB != primaryPair.ProxyB)
{
break;
}
++i;
}
}
// Try to keep the tree balanced.
//_tree.Rebalance(4);
}
/// <summary>
/// This is called from DynamicTree.Query when we are gathering pairs.
/// </summary>
@@ -136,82 +65,29 @@ namespace Robust.Shared.Physics.Broadphase
return true;
}
// TODO: Refactor to use fatAABB
/// <summary>
/// Already assumed to be within the same broadphase.
/// </summary>
/// <param name="proxyIdA"></param>
/// <param name="proxyIdB"></param>
/// <returns></returns>
public bool TestOverlap(DynamicTree.Proxy proxyIdA, DynamicTree.Proxy proxyIdB)
public Box2 GetFatAabb(DynamicTree.Proxy proxy)
{
var proxyA = _tree.GetUserData(proxyIdA);
var proxyB = _tree.GetUserData(proxyIdB);
if (proxyA == null || proxyB == null) return false;
return proxyB.AABB.Intersects(proxyA.AABB);
return _tree.GetFatAabb(proxy);
}
public DynamicTree.Proxy AddProxy(ref FixtureProxy proxy)
{
var proxyID = _tree.CreateProxy(proxy.AABB, proxy);
_proxyCount++;
BufferMove(proxyID);
return proxyID;
}
public void MoveProxy(DynamicTree.Proxy proxy, in Box2 aabb, Vector2 displacement)
{
var buffer = _tree.MoveProxy(proxy, in aabb, displacement);
if (buffer)
{
BufferMove(proxy);
}
}
public void TouchProxy(DynamicTree.Proxy proxy)
{
BufferMove(proxy);
}
private void BufferMove(DynamicTree.Proxy proxyId)
{
if (_moveCount == _moveCapacity)
{
DynamicTree.Proxy[] oldBuffer = _moveBuffer;
_moveCapacity *= 2;
_moveBuffer = new DynamicTree.Proxy[_moveCapacity];
Array.Copy(oldBuffer, _moveBuffer, _moveCount);
}
_moveBuffer[_moveCount] = proxyId;
_moveCount++;
}
private void UnBufferMove(int proxyId)
{
for (int i = 0; i < _moveCount; ++i)
{
if (_moveBuffer[i] == proxyId)
{
_moveBuffer[i] = DynamicTree.Proxy.Free;
}
}
_tree.MoveProxy(proxy, in aabb, displacement);
}
public void RemoveProxy(DynamicTree.Proxy proxy)
{
UnBufferMove(proxy);
_proxyCount--;
_tree.DestroyProxy(proxy);
}
public void QueryAABB(DynamicTree<FixtureProxy>.QueryCallbackDelegate callback, Box2 aabb, bool approx = false)
{
QueryAabb(ref callback, EasyQueryCallback, aabb, approx);
}
public FixtureProxy? GetProxy(DynamicTree.Proxy proxy)
{
return _tree.GetUserData(proxy);
@@ -232,14 +108,18 @@ namespace Robust.Shared.Physics.Broadphase
public IEnumerable<FixtureProxy> QueryAabb(Box2 aabb, bool approx = false)
{
var list = new List<FixtureProxy>();
return QueryAabb(list, aabb, approx);
}
QueryAabb(ref list, (ref List<FixtureProxy> lst, in FixtureProxy i) =>
public IEnumerable<FixtureProxy> QueryAabb(List<FixtureProxy> proxies, Box2 aabb, bool approx = false)
{
QueryAabb(ref proxies, (ref List<FixtureProxy> lst, in FixtureProxy i) =>
{
lst.Add(i);
return true;
}, aabb, approx);
return list;
return proxies;
}
public void QueryPoint(DynamicTree<FixtureProxy>.QueryCallbackDelegate callback, Vector2 point, bool approx = false)

View File

@@ -1,913 +0,0 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Utility;
namespace Robust.Shared.Physics.Broadphase
{
public abstract class SharedBroadPhaseSystem : EntitySystem
{
/*
* That's right both the system implements IBroadPhase and also each grid has its own as well.
* The reason for this is other stuff should just be able to check for broadphase with no regard
* for the concept of grids, whereas internally this needs to worry about it.
*/
// Anything in a container is removed from the graph and anything removed from a container is added to the graph.
/*
* So the Box2D derivatives just use a generic "SynchronizeFixtures" method but it's kinda obtuse so
* I just made other methods (AddFixture, RefreshFixtures, etc.) that are clearer on what they're doing.
*/
[Dependency] private readonly IMapManager _mapManager = default!;
private readonly Dictionary<MapId, Dictionary<GridId, IBroadPhase>> _graph = new();
private Dictionary<IPhysBody, List<IBroadPhase>> _lastBroadPhases = new();
/// <summary>
/// Given MoveEvent and RotateEvent do the same thing we won't double up on work.
/// </summary>
private HashSet<IEntity> _handledThisTick = new();
private Queue<MoveEvent> _queuedMoveEvents = new();
private Queue<RotateEvent> _queuedRotateEvent = new();
private Queue<EntMapIdChangedMessage> _queuedMapChanges = new();
private Queue<FixtureUpdateMessage> _queuedFixtureUpdates = new();
private Queue<PhysicsUpdateMessage> _queuedCollisionChanges = new();
private Queue<EntInsertedIntoContainerMessage> _queuedContainerInsert = new();
private Queue<EntRemovedFromContainerMessage> _queuedContainerRemove = new();
private IEnumerable<IBroadPhase> BroadPhases()
{
foreach (var (_, grids) in _graph)
{
foreach (var (_, broad) in grids)
{
yield return broad;
}
}
}
/// <summary>
/// Gets the corresponding broadphase for this grid.
/// </summary>
/// <param name="mapId"></param>
/// <param name="gridId"></param>
/// <returns>null if broadphase already destroyed or none exists</returns>
public IBroadPhase? GetBroadPhase(MapId mapId, GridId gridId)
{
// Might be null if the grid is being instantiated.
if (mapId == MapId.Nullspace || !_graph[mapId].TryGetValue(gridId, out var grid))
return null;
return grid;
}
public ICollection<IBroadPhase> GetBroadPhases(MapId mapId)
{
return _graph[mapId].Values;
}
public IEnumerable<IBroadPhase> GetBroadPhases(PhysicsComponent body)
{
if (!_lastBroadPhases.TryGetValue(body, out var broadPhases)) return Array.Empty<IBroadPhase>();
return broadPhases;
}
// Look for now I've hardcoded grids
public IEnumerable<(IBroadPhase Broadphase, GridId GridId)> GetBroadphases(PhysicsComponent body)
{
if (!_lastBroadPhases.TryGetValue(body, out var broadPhases)) yield break;
foreach (var (_, grids) in _graph)
{
foreach (var broad in broadPhases)
{
foreach (var (gridId, broadPhase) in grids)
{
if (broad == broadPhase)
{
yield return (broadPhase, gridId);
break;
}
}
}
}
}
public bool TestOverlap(FixtureProxy proxyA, FixtureProxy proxyB)
{
// TODO: This should only ever be called on the same grid I think so maybe just assert
var mapA = proxyA.Fixture.Body.Owner.Transform.MapID;
var mapB = proxyB.Fixture.Body.Owner.Transform.MapID;
if (mapA != mapB)
return false;
return proxyA.AABB.Intersects(proxyB.AABB);
}
/// <summary>
/// Get the percentage that 2 bodies overlap. Ignores whether collision is turned on for either body.
/// </summary>
/// <param name="bodyA"></param>
/// <param name="bodyB"></param>
/// <returns> 0 -> 1.0f based on WorldAABB overlap</returns>
public float IntersectionPercent(PhysicsComponent bodyA, PhysicsComponent bodyB)
{
// TODO: Use actual shapes and not just the AABB?
return bodyA.GetWorldAABB().IntersectPercentage(bodyB.GetWorldAABB());
}
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PhysicsUpdateMessage>(QueueCollisionChange);
SubscribeLocalEvent<MoveEvent>(QueuePhysicsMove);
SubscribeLocalEvent<RotateEvent>(QueuePhysicsRotate);
SubscribeLocalEvent<EntMapIdChangedMessage>(QueueMapChange);
SubscribeLocalEvent<EntInsertedIntoContainerMessage>(QueueContainerInsertMessage);
SubscribeLocalEvent<EntRemovedFromContainerMessage>(QueueContainerRemoveMessage);
SubscribeLocalEvent<FixtureUpdateMessage>(QueueFixtureUpdate);
_mapManager.OnGridCreated += HandleGridCreated;
_mapManager.OnGridRemoved += HandleGridRemoval;
_mapManager.MapCreated += HandleMapCreated;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
while (_queuedMoveEvents.Count > 0)
{
var moveEvent = _queuedMoveEvents.Dequeue();
// Doing this seems to fuck with tp so leave off for now I guess, it's mainly to avoid the rotate duplication
if (_handledThisTick.Contains(moveEvent.Sender)) continue;
_handledThisTick.Add(moveEvent.Sender);
if (moveEvent.Sender.Deleted || !moveEvent.Sender.TryGetComponent(out PhysicsComponent? physicsComponent)) continue;
SynchronizeFixtures(physicsComponent, moveEvent.NewPosition.ToMapPos(EntityManager) - moveEvent.OldPosition.ToMapPos(EntityManager), moveEvent.WorldAABB);
}
while (_queuedRotateEvent.Count > 0)
{
var rotateEvent = _queuedRotateEvent.Dequeue();
if (_handledThisTick.Contains(rotateEvent.Sender)) continue;
_handledThisTick.Add(rotateEvent.Sender);
if (rotateEvent.Sender.Deleted || !rotateEvent.Sender.TryGetComponent(out PhysicsComponent? physicsComponent))
return;
SynchronizeFixtures(physicsComponent, Vector2.Zero, rotateEvent.WorldAABB);
}
_handledThisTick.Clear();
// TODO: Just call ProcessEventQueue directly?
// Manually manage queued stuff ourself given EventBus.QueueEvent happens at the same time every time
while (_queuedMapChanges.Count > 0)
{
HandleMapChange(_queuedMapChanges.Dequeue());
}
while (_queuedContainerInsert.Count > 0)
{
HandleContainerInsert(_queuedContainerInsert.Dequeue());
}
while (_queuedContainerRemove.Count > 0)
{
HandleContainerRemove(_queuedContainerRemove.Dequeue());
}
while (_queuedCollisionChanges.Count > 0)
{
var message = _queuedCollisionChanges.Dequeue();
if (message.Component.CanCollide && !message.Component.Deleted)
{
AddBody(message.Component);
}
else
{
RemoveBody(message.Component);
}
}
while (_queuedFixtureUpdates.Count > 0)
{
var message = _queuedFixtureUpdates.Dequeue();
RefreshFixture(message.Body, message.Fixture);
}
}
public override void Shutdown()
{
base.Shutdown();
_mapManager.OnGridCreated -= HandleGridCreated;
_mapManager.OnGridRemoved -= HandleGridRemoval;
_mapManager.MapCreated -= HandleMapCreated;
}
private void QueuePhysicsMove(MoveEvent moveEvent)
{
_queuedMoveEvents.Enqueue(moveEvent);
}
private void QueuePhysicsRotate(RotateEvent rotateEvent)
{
if (!rotateEvent.Sender.TryGetComponent(out PhysicsComponent? physicsComponent))
return;
SynchronizeFixtures(physicsComponent, Vector2.Zero);
}
private void QueueCollisionChange(PhysicsUpdateMessage message)
{
_queuedCollisionChanges.Enqueue(message);
}
private void QueueMapChange(EntMapIdChangedMessage message)
{
_queuedMapChanges.Enqueue(message);
}
private void QueueContainerInsertMessage(EntInsertedIntoContainerMessage message)
{
_queuedContainerInsert.Enqueue(message);
}
private void QueueContainerRemoveMessage(EntRemovedFromContainerMessage message)
{
_queuedContainerRemove.Enqueue(message);
}
private void HandleContainerInsert(EntInsertedIntoContainerMessage message)
{
if (!message.Entity.Deleted && message.Entity.TryGetComponent(out IPhysBody? physicsComponent))
{
physicsComponent.CanCollide = false;
physicsComponent.Awake = false;
}
}
private void HandleContainerRemove(EntRemovedFromContainerMessage message)
{
if (!message.Entity.Deleted && message.Entity.TryGetComponent(out IPhysBody? physicsComponent))
{
physicsComponent.CanCollide = true;
physicsComponent.Awake = true;
}
}
private void QueueFixtureUpdate(FixtureUpdateMessage message)
{
_queuedFixtureUpdates.Enqueue(message);
}
public void AddBroadPhase(PhysicsComponent body, IBroadPhase broadPhase)
{
if (!_lastBroadPhases.TryGetValue(body, out var broadPhases))
{
return;
}
if (broadPhases.Contains(broadPhase)) return;
broadPhases.Add(broadPhase);
}
/// <summary>
/// Handles map changes for bodies completely
/// </summary>
/// <param name="message"></param>
private void HandleMapChange(EntMapIdChangedMessage message)
{
if (message.Entity.Deleted ||
!message.Entity.TryGetComponent(out PhysicsComponent? body) ||
!_lastBroadPhases.TryGetValue(body, out var broadPhases))
{
return;
}
if (Get<SharedPhysicsSystem>().Maps.TryGetValue(message.OldMapId, out var oldMap))
{
oldMap.RemoveBody(body);
}
body.ClearProxies();
if (Get<SharedPhysicsSystem>().Maps.TryGetValue(message.Entity.Transform.MapID, out var newMap))
{
newMap.AddBody(body);
body.CreateProxies();
SetBroadPhases(body);
}
}
private void SetBroadPhases(IPhysBody body)
{
if (!_lastBroadPhases.TryGetValue(body, out var broadPhases))
{
broadPhases = new List<IBroadPhase>();
_lastBroadPhases[body] = broadPhases;
}
broadPhases.Clear();
foreach (var fixture in body.Fixtures)
{
foreach (var (gridId, _) in fixture.Proxies)
{
var broadPhase = GetBroadPhase(body.Owner.Transform.MapID, gridId);
if (broadPhase == null) continue;
broadPhases.Add(broadPhase);
}
}
}
private void HandleGridCreated(MapId mapId, GridId gridId)
{
if (!_graph.TryGetValue(mapId, out var grids))
{
grids = new Dictionary<GridId, IBroadPhase>();
_graph[mapId] = grids;
}
grids[gridId] = new DynamicTreeBroadPhase(mapId, gridId);
}
private void HandleMapCreated(object? sender, MapEventArgs eventArgs)
{
_graph[eventArgs.Map] = new Dictionary<GridId, IBroadPhase>()
{
{
GridId.Invalid,
new DynamicTreeBroadPhase(eventArgs.Map, GridId.Invalid)
}
};
}
private void HandleGridRemoval(MapId mapId, GridId gridId)
{
foreach (var (_, grids) in _graph)
{
if (!grids.TryGetValue(gridId, out var broadPhase)) continue;
var toCleanup = new List<IPhysBody>();
// Need to cleanup every body that was touching this grid.
foreach (var (body, broadPhases) in _lastBroadPhases)
{
if (broadPhases.Contains(broadPhase))
{
toCleanup.Add(body);
}
}
foreach (var body in toCleanup)
{
RemoveBody(body);
}
grids.Remove(gridId);
foreach (var body in toCleanup)
{
AddBody(body);
}
}
}
public void AddBody(IPhysBody body)
{
if (_lastBroadPhases.ContainsKey(body))
{
return;
}
var mapId = body.Owner.Transform.MapID;
if (mapId == MapId.Nullspace)
{
_lastBroadPhases[body] = new List<IBroadPhase>();
return;
}
foreach (var fixture in body.Fixtures)
{
DebugTools.Assert(fixture.Proxies.Count == 0, "Can't add a body to broadphase when it already has proxies!");
}
var broadPhases = new List<IBroadPhase>();
_lastBroadPhases[body] = broadPhases;
body.CreateProxies(_mapManager, this);
SetBroadPhases(body);
}
public void RemoveBody(IPhysBody body)
{
if (!_lastBroadPhases.ContainsKey(body))
{
return;
}
body.ClearProxies();
body.DestroyContacts();
_lastBroadPhases.Remove(body);
}
/// <summary>
/// Recreates this fixture in the relevant broadphases.
/// </summary>
/// <param name="body"></param>
/// <param name="fixture"></param>
public void RefreshFixture(PhysicsComponent body, Fixture fixture)
{
if (!_lastBroadPhases.TryGetValue(body, out var broadPhases))
{
return;
}
var mapId = body.Owner.Transform.MapID;
if (mapId == MapId.Nullspace || body.Deleted)
{
return;
}
fixture.ClearProxies(mapId, this);
fixture.CreateProxies(_mapManager, this);
// Need to update what broadphases are relevant.
SetBroadPhases(body);
}
internal void AddFixture(PhysicsComponent body, Fixture fixture)
{
// If the entity's still being initialized it might have MoveEvent called (might change in future?)
if (!_lastBroadPhases.TryGetValue(body, out var broadPhases))
{
return;
}
var mapId = body.Owner.Transform.MapID;
DebugTools.Assert(fixture.Proxies.Count == 0);
if (mapId == MapId.Nullspace || body.Deleted)
{
body.ClearProxies();
return;
}
broadPhases.Clear();
fixture.CreateProxies(_mapManager, this);
foreach (var fix in body.Fixtures)
{
foreach (var (gridId, _) in fix.Proxies)
{
var broadPhase = GetBroadPhase(mapId, gridId);
if (broadPhase == null) continue;
broadPhases.Add(broadPhase);
}
}
}
internal void RemoveFixture(PhysicsComponent body, Fixture fixture)
{
// If the entity's still being initialized it might have MoveEvent called (might change in future?)
if (!_lastBroadPhases.TryGetValue(body, out var broadPhases))
{
return;
}
var mapId = body.Owner.Transform.MapID;
fixture.ClearProxies(mapId, this);
if (mapId == MapId.Nullspace || body.Deleted)
{
body.ClearProxies();
return;
}
// Need to re-build the broadphases.
broadPhases.Clear();
foreach (var fix in body.Fixtures)
{
foreach (var (gridId, _) in fix.Proxies)
{
var broadPhase = GetBroadPhase(mapId, gridId);
if (broadPhase == null) continue;
broadPhases.Add(broadPhase);
}
}
}
/// <summary>
/// Move all of the fixtures on this body.
/// </summary>
/// <param name="body"></param>
/// <param name="displacement"></param>
private void SynchronizeFixtures(PhysicsComponent body, Vector2 displacement, Box2? worldAABB = null)
{
// If the entity's still being initialized it might have MoveEvent called (might change in future?)
if (!_lastBroadPhases.TryGetValue(body, out var oldBroadPhases))
{
return;
}
var worldPosition = body.Owner.Transform.WorldPosition;
var worldRotation = body.Owner.Transform.WorldRotation;
var mapId = body.Owner.Transform.MapID;
worldAABB ??= body.GetWorldAABB(worldPosition, worldRotation);
// 99% of the time this is going to be 1, maybe 2, so HashSet probably slower?
var newBroadPhases = _mapManager
.FindGridIdsIntersecting(mapId, worldAABB.Value, true)
.Select(gridId => GetBroadPhase(mapId, gridId))
.ToList();
// Remove from old broadphases
for (var i = oldBroadPhases.Count - 1; i >= 0; i--)
{
var broadPhase = oldBroadPhases[i];
if (newBroadPhases.Contains(broadPhase)) continue;
var gridId = GetGridId(broadPhase);
foreach (var fixture in body.Fixtures)
{
fixture.ClearProxies(mapId, this, gridId);
}
oldBroadPhases.RemoveAt(i);
}
// Update retained broadphases
// TODO: These will need swept broadPhases
foreach (var broadPhase in oldBroadPhases)
{
if (!newBroadPhases.Contains(broadPhase)) continue;
var gridId = GetGridId(broadPhase);
foreach (var fixture in body.Fixtures)
{
if (!fixture.Proxies.TryGetValue(gridId, out var proxies)) continue;
foreach (var proxy in proxies)
{
var gridRotation = worldRotation;
var gridPos = worldPosition;
if (gridId != GridId.Invalid)
{
var grid = _mapManager.GetGrid(gridId);
gridPos -= grid.WorldPosition;
// TODO: Should probably have a helper for this
gridRotation -= body.Owner.EntityManager.GetEntity(grid.GridEntityId).Transform.WorldRotation;
}
var aabb = fixture.Shape.CalculateLocalBounds(gridRotation).Translated(gridPos);
proxy.AABB = aabb;
broadPhase.MoveProxy(proxy.ProxyId, in aabb, displacement);
}
}
}
// Add to new broadphases
foreach (var broadPhase in newBroadPhases)
{
if (broadPhase == null || oldBroadPhases.Contains(broadPhase)) continue;
var gridId = GetGridId(broadPhase);
oldBroadPhases.Add(broadPhase);
foreach (var fixture in body.Fixtures)
{
DebugTools.Assert(!fixture.Proxies.ContainsKey(gridId));
fixture.CreateProxies(broadPhase, _mapManager, this);
}
}
}
public GridId GetGridId(IBroadPhase broadPhase)
{
foreach (var (_, grids) in _graph)
{
foreach (var (gridId, broad) in grids)
{
if (broadPhase == broad)
{
return gridId;
}
}
}
throw new InvalidOperationException("Unable to find GridId for broadPhase");
}
// This is dirty but so is a lot of other shit so it'll get refactored at some stage tm
public List<PhysicsComponent> GetAwakeBodies(MapId mapId, GridId gridId)
{
var bodies = new List<PhysicsComponent>();
var map = Get<SharedPhysicsSystem>().Maps[mapId];
foreach (var body in map.AwakeBodies)
{
if (body.Owner.Transform.GridID == gridId)
bodies.Add(body);
}
return bodies;
}
#region Queries
/// <summary>
/// Checks to see if the specified collision rectangle collides with any of the physBodies under management.
/// Also fires the OnCollide event of the first managed physBody to intersect with the collider.
/// </summary>
/// <param name="collider">Collision rectangle to check</param>
/// <param name="mapId">Map to check on</param>
/// <param name="approximate"></param>
/// <returns>true if collides, false if not</returns>
public bool TryCollideRect(Box2 collider, MapId mapId, bool approximate = true)
{
var state = (collider, mapId, found: false);
foreach (var gridId in _mapManager.FindGridIdsIntersecting(mapId, collider, true))
{
var gridCollider = _mapManager.GetGrid(gridId).WorldToLocal(collider.Center);
var gridBox = collider.Translated(gridCollider);
_graph[mapId][gridId].QueryAabb(ref state, (ref (Box2 collider, MapId map, bool found) state, in FixtureProxy proxy) =>
{
if (proxy.Fixture.CollisionLayer == 0x0)
return true;
if (proxy.AABB.Intersects(gridBox))
{
state.found = true;
return false;
}
return true;
}, gridBox, approximate);
}
return state.found;
}
public IEnumerable<PhysicsComponent> GetCollidingEntities(PhysicsComponent body, Vector2 offset, bool approximate = true)
{
// TODO: In an ideal world we'd just iterate over the body's contacts (need to make more stuff immediate
// for physics for this to be viable so future-work).
// If the body has just had its collision enabled or disabled it may not be ready yet so we'll wait a tick.
if (!body.CanCollide || body.Owner.Transform.MapID == MapId.Nullspace)
{
return Array.Empty<PhysicsComponent>();
}
// Unfortunately due to the way grids are currently created we have to queue CanCollide event changes, hence we need to do this here.
if (!_lastBroadPhases.ContainsKey(body))
{
AddBody(body);
}
var entities = new List<PhysicsComponent>();
var state = (body, entities);
foreach (var broadPhase in _lastBroadPhases[body])
{
foreach (var fixture in body.Fixtures)
{
foreach (var proxy in fixture.Proxies[GetGridId(broadPhase)])
{
broadPhase.QueryAabb(ref state,
(ref (PhysicsComponent body, List<PhysicsComponent> entities) state,
in FixtureProxy other) =>
{
if (other.Fixture.Body.Deleted || other.Fixture.Body == body) return true;
if ((proxy.Fixture.CollisionMask & other.Fixture.CollisionLayer) == 0x0) return true;
if (!body.ShouldCollide(other.Fixture.Body)) return true;
state.entities.Add(other.Fixture.Body);
return true;
}, proxy.AABB, approximate);
}
}
}
return entities;
}
/// <summary>
/// Get all entities colliding with a certain body.
/// </summary>
/// <param name="body"></param>
/// <returns></returns>
public IEnumerable<PhysicsComponent> GetCollidingEntities(MapId mapId, in Box2 worldAABB)
{
if (mapId == MapId.Nullspace) return Array.Empty<PhysicsComponent>();
var bodies = new HashSet<PhysicsComponent>();
foreach (var gridId in _mapManager.FindGridIdsIntersecting(mapId, worldAABB, true))
{
Vector2 offset;
if (gridId == GridId.Invalid)
{
offset = Vector2.Zero;
}
else
{
offset = -_mapManager.GetGrid(gridId).WorldPosition;
}
var gridBox = worldAABB.Translated(offset);
foreach (var proxy in _graph[mapId][gridId].QueryAabb(gridBox, false))
{
if (bodies.Contains(proxy.Fixture.Body)) continue;
bodies.Add(proxy.Fixture.Body);
}
}
return bodies;
}
// TODO: This will get every body but we don't need to do that
/// <summary>
/// Checks whether a body is colliding
/// </summary>
/// <param name="body"></param>
/// <param name="offset"></param>
/// <returns></returns>
public bool IsColliding(PhysicsComponent body, Vector2 offset, bool approximate)
{
return GetCollidingEntities(body, offset, approximate).Any();
}
#endregion
#region RayCast
/// <summary>
/// Casts a ray in the world, returning the first entity it hits (or all entities it hits, if so specified)
/// </summary>
/// <param name="mapId"></param>
/// <param name="ray">Ray to cast in the world.</param>
/// <param name="maxLength">Maximum length of the ray in meters.</param>
/// <param name="predicate">A predicate to check whether to ignore an entity or not. If it returns true, it will be ignored.</param>
/// <param name="returnOnFirstHit">If true, will only include the first hit entity in results. Otherwise, returns all of them.</param>
/// <returns>A result object describing the hit, if any.</returns>
public IEnumerable<RayCastResults> IntersectRayWithPredicate(MapId mapId, CollisionRay ray,
float maxLength = 50F,
Func<IEntity, bool>? predicate = null, bool returnOnFirstHit = true)
{
List<RayCastResults> results = new();
var endPoint = ray.Position + ray.Direction.Normalized * maxLength;
var rayBox = new Box2(Vector2.ComponentMin(ray.Position, endPoint),
Vector2.ComponentMax(ray.Position, endPoint));
foreach (var gridId in _mapManager.FindGridIdsIntersecting(mapId, rayBox, true))
{
Vector2 offset;
if (gridId == GridId.Invalid)
{
offset = Vector2.Zero;
}
else
{
offset = _mapManager.GetGrid(gridId).WorldPosition;
}
var broadPhase = GetBroadPhase(mapId, gridId);
var gridRay = new CollisionRay(ray.Position - offset, ray.Direction, ray.CollisionMask);
// TODO: Probably need rotation when we get rotatable grids
broadPhase?.QueryRay((in FixtureProxy proxy, in Vector2 point, float distFromOrigin) =>
{
if (returnOnFirstHit && results.Count > 0) return true;
if (distFromOrigin > maxLength)
{
return true;
}
if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0)
{
return true;
}
if (predicate?.Invoke(proxy.Fixture.Body.Owner) == true)
{
return true;
}
// TODO: Shape raycast here
// Need to convert it back to world-space.
var result = new RayCastResults(distFromOrigin, point + offset, proxy.Fixture.Body.Owner);
results.Add(result);
EntityManager.EventBus.QueueEvent(EventSource.Local,
new DebugDrawRayMessage(
new DebugRayData(ray, maxLength, result)));
return true;
}, gridRay);
}
if (results.Count == 0)
{
EntityManager.EventBus.QueueEvent(EventSource.Local,
new DebugDrawRayMessage(
new DebugRayData(ray, maxLength, null)));
}
results.Sort((a, b) => a.Distance.CompareTo(b.Distance));
return results;
}
/// <summary>
/// Casts a ray in the world and returns the first entity it hits, or a list of all entities it hits.
/// </summary>
/// <param name="mapId"></param>
/// <param name="ray">Ray to cast in the world.</param>
/// <param name="maxLength">Maximum length of the ray in meters.</param>
/// <param name="ignoredEnt">A single entity that can be ignored by the RayCast. Useful if the ray starts inside the body of an entity.</param>
/// <param name="returnOnFirstHit">If false, will return a list of everything it hits, otherwise will just return a list of the first entity hit</param>
/// <returns>An enumerable of either the first entity hit or everything hit</returns>
public IEnumerable<RayCastResults> IntersectRay(MapId mapId, CollisionRay ray, float maxLength = 50, IEntity? ignoredEnt = null, bool returnOnFirstHit = true)
=> IntersectRayWithPredicate(mapId, ray, maxLength, entity => entity == ignoredEnt, returnOnFirstHit);
/// <summary>
/// Casts a ray in the world and returns the distance the ray traveled while colliding with entities
/// </summary>
/// <param name="mapId"></param>
/// <param name="ray">Ray to cast in the world.</param>
/// <param name="maxLength">Maximum length of the ray in meters.</param>
/// <param name="ignoredEnt">A single entity that can be ignored by the RayCast. Useful if the ray starts inside the body of an entity.</param>
/// <returns>The distance the ray traveled while colliding with entities</returns>
public float IntersectRayPenetration(MapId mapId, CollisionRay ray, float maxLength, IEntity? ignoredEnt = null)
{
var penetration = 0f;
// TODO: Just make an actual box
var rayBox = new Box2(ray.Position - maxLength, ray.Position + maxLength);
foreach (var gridId in _mapManager.FindGridIdsIntersecting(mapId, rayBox, true))
{
var offset = gridId == GridId.Invalid ? Vector2.Zero : _mapManager.GetGrid(gridId).WorldPosition;
var broadPhase = GetBroadPhase(mapId, gridId);
if (broadPhase == null) continue;
var gridRay = new CollisionRay(ray.Position - offset, ray.Direction, ray.CollisionMask);
// TODO: Probably need rotation when we get rotatable grids
broadPhase.QueryRay((in FixtureProxy proxy, in Vector2 point, float distFromOrigin) =>
{
if (distFromOrigin > maxLength || proxy.Fixture.Body.Owner == ignoredEnt) return true;
if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0)
{
return true;
}
if (new Ray(point + ray.Direction * proxy.AABB.Size.Length * 2, -ray.Direction).Intersects(
proxy.AABB, out _, out var exitPoint))
{
penetration += (point - exitPoint).Length;
}
return true;
}, gridRay);
}
return penetration;
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
using Robust.Shared.GameObjects;
namespace Robust.Shared.Physics
{
[RegisterComponent]
public sealed class BroadphaseComponent : Component
{
public override string Name => "Broadphase";
internal IBroadPhase Tree = default!;
}
}

View File

@@ -64,18 +64,19 @@ namespace Robust.Shared.Physics.Collision.Shapes
_normals = new List<Vector2>(_vertices.Count);
// Compute normals. Ensure the edges have non-zero length.
for (int i = 0; i < _vertices.Count; ++i)
for (var i = 0; i < _vertices.Count; ++i)
{
int next = i + 1 < _vertices.Count ? i + 1 : 0;
Vector2 edge = _vertices[next] - _vertices[i];
var next = i + 1 < _vertices.Count ? i + 1 : 0;
var edge = _vertices[next] - _vertices[i];
DebugTools.Assert(edge.LengthSquared > float.Epsilon * float.Epsilon);
//FPE optimization: Normals.Add(MathHelper.Cross(edge, 1.0f));
Vector2 temp = new Vector2(edge.Y, -edge.X);
var temp = new Vector2(edge.Y, -edge.X);
_normals.Add(temp.Normalized);
}
Centroid = ComputeCentroid(_vertices);
_cachedAABB = CalculateAABB();
// Compute the polygon mass data
// TODO: Update fixture. Maybe use events for it? Who tf knows.
@@ -85,6 +86,8 @@ namespace Robust.Shared.Physics.Collision.Shapes
private List<Vector2> _vertices = new();
private Box2 _cachedAABB;
internal Vector2 Centroid { get; set; } = Vector2.Zero;
[ViewVariables(VVAccess.ReadOnly)]
@@ -208,26 +211,35 @@ namespace Robust.Shared.Physics.Collision.Shapes
return true;
}
public Box2 CalculateLocalBounds(Angle rotation)
/// <summary>
/// Calculating the AABB for a polyshape isn't cheap so we'll just cache it whenever its vertices change.
/// </summary>
private Box2 CalculateAABB()
{
if (Vertices.Count == 0) return new Box2();
if (_vertices.Count == 0) return new Box2();
var aabb = new Box2();
Vector2 lower = Vertices[0];
Vector2 upper = lower;
var lower = Vertices[0];
var upper = lower;
for (int i = 1; i < Vertices.Count; ++i)
for (var i = 1; i < Vertices.Count; ++i)
{
Vector2 v = Vertices[i];
var v = Vertices[i];
lower = Vector2.ComponentMin(lower, v);
upper = Vector2.ComponentMax(upper, v);
}
Vector2 r = new Vector2(Radius, Radius);
aabb.BottomLeft = lower - r;
aabb.TopRight = upper + r;
var r = new Vector2(Radius, Radius);
return aabb;
return new Box2
{
BottomLeft = lower - r,
TopRight = upper + r
};
}
public Box2 CalculateLocalBounds(Angle rotation)
{
return rotation == Angle.Zero ? _cachedAABB : new Box2Rotated(_cachedAABB, rotation).CalcBoundingBox();
}
public void ApplyState()

Some files were not shown because too many files have changed in this diff Show More