Compare commits

...

53 Commits

Author SHA1 Message Date
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
metalgearsloth
f780f04784 Deprecate PhysShapeGrid (#1862)
* Grid fixtures

* termp

* Fixes

* Test reversion reversion?

* Tests

* Fix Equals

* Slight box2i cleanup

* Better initializer

* Also add static grid assert
2021-07-14 18:47:17 +10:00
Pieter-Jan Briers
85782bda92 Make Split- and BoxContainer orientation adjustable, obsolete subtypes. 2021-07-13 17:21:21 +02:00
metalgearsloth
14a01df5b1 Add physics stacking tests (#1865)
Ported from content; only reason for PR is to make sure remote tests are also gucci.
2021-07-13 18:43:09 +10:00
metalgearsloth
644da60bfc ContactCount VV (#1864) 2021-07-13 14:11:27 +10:00
Pieter-Jan Briers
8c83999ad2 Make window creation synchronous.
The async code path isn't really async and if we ever make it so (by using a non shit rendering API) I'll just make it implicitly asynchronous.
2021-07-13 03:39:38 +02:00
Pieter-Jan Briers
24b9fc9eec Add ImageSharp to script console assemblies. 2021-07-12 17:26:54 +02:00
metalgearsloth
ba40185179 Make entitylookup grid-relative (#1849)
* Refactor entitylookups to be 30% more based

* Refactor gucci

* Done?

* Fix most tests

* nothing suss

* Vera single-handedly saving shuttles

* fex

* Copy lookup from shuttles

* sys

* comp recursion
2021-07-12 13:39:02 +02:00
Vera Aguilera Puerto
8b013cb424 Fix engine integration tests not generating Net IDs. 2021-07-12 10:42:38 +02:00
160 changed files with 3821 additions and 2760 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

@@ -1,4 +1,4 @@
using System;
using System;
using System.Net;
using Robust.Client.Debugging;
using Robust.Client.GameObjects;

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

@@ -13,10 +13,11 @@ using Microsoft.CodeAnalysis.Text;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.ViewVariables;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Scripting;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using Color = Robust.Shared.Maths.Color;
#nullable enable
@@ -116,7 +117,7 @@ namespace Robust.Client.Console
}
else
{
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager);
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager).AddReferences(typeof(Image).Assembly);
newScript = CSharpScript.Create(code, options, typeof(ScriptGlobals));
}

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

@@ -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

@@ -15,29 +15,43 @@ namespace Robust.Client.GameObjects
private static Box2 SpriteAabbFunc(in SpriteComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
return new Box2(worldPos, worldPos);
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return new Box2(pos, pos);
}
private static Box2 LightAabbFunc(in PointLightComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var boxSize = value.Radius * 2;
return Box2.CenteredAround(worldPos, (boxSize, boxSize));
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return Box2.CenteredAround(pos, (boxSize, boxSize));
}
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
return new Box2(worldPos.Value, worldPos.Value);
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return new Box2(pos, pos);
}
internal static Box2 LightAabbFunc(PointLightComponent value, Vector2? worldPos = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var boxSize = value.Radius * 2;
return Box2.CenteredAround(worldPos.Value, (boxSize, boxSize));
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return Box2.CenteredAround(pos, (boxSize, boxSize));
}
}
}

View File

@@ -24,7 +24,7 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
private SharedBroadphaseSystem _broadPhaseSystem = default!;
private readonly List<PlayingStream> _playingClydeStreams = new();
@@ -38,7 +38,7 @@ namespace Robust.Client.GameObjects
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
_broadPhaseSystem = Get<SharedBroadphaseSystem>();
}
private void StopAudioMessageHandler(StopAudioMessageClient ev)
@@ -301,7 +301,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

@@ -41,22 +41,12 @@ namespace Robust.Client.GameObjects
{
if (mapId == MapId.Nullspace) yield break;
var enclosed = false;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<RenderingTreeComponent>();
// if we're enclosed then we know no other grids relevant + don't need the map's rendertree
if (grid.WorldBounds.Encloses(in worldAABB))
{
enclosed = true;
break;
}
}
if (!enclosed)
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
}
internal IEnumerable<DynamicTree<SpriteComponent>> GetSpriteTrees(MapId mapId, Box2 worldAABB)
@@ -259,9 +249,8 @@ namespace Robust.Client.GameObjects
EntityManager.GetEntity(_mapManager.GetGrid(gridId).GridEntityId).EnsureComponent<RenderingTreeComponent>();
}
private RenderingTreeComponent? GetRenderTree(IEntity entity)
internal static RenderingTreeComponent? GetRenderTree(IEntity entity)
{
if (entity.Transform.MapID == MapId.Nullspace ||
entity.HasComponent<RenderingTreeComponent>()) return null;
@@ -302,8 +291,7 @@ namespace Robust.Client.GameObjects
continue;
}
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos).Translated(-treePos);
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos);
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)
@@ -348,7 +336,7 @@ namespace Robust.Client.GameObjects
}
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos).Translated(-treePos);
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos);
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)

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

@@ -210,11 +210,11 @@ namespace Robust.Client.Graphics.Clyde
_windowing!.WindowRequestAttention(_windowing.MainWindow!);
}
public async Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters)
public IClydeWindow CreateWindow(WindowCreateParameters parameters)
{
DebugTools.AssertNotNull(_windowing);
return await _windowing!.WindowCreate(parameters);
return _windowing!.WindowCreate(parameters);
}
private void DoDestroyWindow(WindowReg reg)

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

@@ -210,7 +210,7 @@ namespace Robust.Client.Graphics.Clyde
yield break;
}
public Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters)
public IClydeWindow CreateWindow(WindowCreateParameters parameters)
{
var window = new DummyWindow(CreateRenderTarget((123, 123), default))
{
@@ -218,7 +218,7 @@ namespace Robust.Client.Graphics.Clyde
};
_windows.Add(window);
return Task.FromResult<IClydeWindow>(window);
return window;
}
public ClydeHandle LoadShader(ParsedShader shader, string? name = null)

View File

@@ -22,8 +22,7 @@ namespace Robust.Client.Graphics.Clyde
{
internal partial class Clyde
{
// Wait for it.
private sealed partial class GlfwWindowingImpl
private unsafe sealed partial class GlfwWindowingImpl
{
private readonly List<GlfwWindowReg> _windows = new();
@@ -36,45 +35,23 @@ namespace Robust.Client.Graphics.Clyde
private int _nextWindowId = 1;
private static bool _eglLoaded;
public async Task<WindowHandle> WindowCreate(WindowCreateParameters parameters)
public WindowHandle WindowCreate(WindowCreateParameters parameters)
{
// tfw await not allowed in unsafe contexts
// GL APIs don't take kindly to making a new window without unbinding the main context. Great.
// Leaving code for async path in, in case it works on like GLX.
var unbindContextAndBlock = true;
DebugTools.AssertNotNull(_mainWindow);
Task<GlfwWindowCreateResult> task;
unsafe
{
if (unbindContextAndBlock)
GLFW.MakeContextCurrent(null);
GLFW.MakeContextCurrent(null);
task = SharedWindowCreate(
_clyde._chosenRenderer,
parameters,
_mainWindow!.GlfwWindow);
}
var task = SharedWindowCreate(
_clyde._chosenRenderer,
parameters,
_mainWindow!.GlfwWindow);
if (unbindContextAndBlock)
{
unsafe
{
// Block the main thread (to avoid stuff like texture uploads being problematic).
WaitWindowCreate(task);
// Block the main thread (to avoid stuff like texture uploads being problematic).
WaitWindowCreate(task);
if (unbindContextAndBlock)
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
}
}
else
{
await task;
}
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
var (reg, error) = await task;
var (reg, error) = task.Result;
if (reg == null)
{
@@ -85,18 +62,11 @@ namespace Robust.Client.Graphics.Clyde
_clyde.CreateWindowRenderTexture(reg);
_clyde.InitWindowBlitThread(reg);
unsafe
{
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
}
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
return reg.Handle;
}
}
// Yes, you read that right.
private sealed unsafe partial class GlfwWindowingImpl
{
public bool TryInitMainWindow(Renderer renderer, [NotNullWhen(false)] out string? error)
{
var width = _cfg.GetCVar(CVars.DisplayWidth);
@@ -167,6 +137,21 @@ namespace Robust.Client.Graphics.Clyde
WindowCreateParameters parameters,
Window* share)
{
//
// IF YOU'RE WONDERING WHY THIS IS TASK-BASED:
// I originally wanted this to be async so we could avoid blocking the main thread
// while the OS takes its stupid 100~ms just to initialize a fucking GL context.
// This doesn't *work* because
// we have to release the GL context while the shared context is being created.
// (at least on WGL, I didn't test other platforms and I don't care to.)
// Not worth it to avoid a main thread blockage by allowing Clyde to temporarily release the GL context,
// because rendering would be locked up *anyways*.
//
// Basically what I'm saying is that everything about OpenGL is a fucking mistake
// and I should get on either Veldrid or Vulkan some time.
// Probably Veldrid tbh.
//
// Yes we ping-pong this TCS through the window thread and back, deal with it.
var tcs = new TaskCompletionSource<GlfwWindowCreateResult>();
SendCmd(new CmdWinCreate(
@@ -525,7 +510,6 @@ namespace Robust.Client.Graphics.Clyde
}
return window;
}

View File

@@ -38,7 +38,7 @@ namespace Robust.Client.Graphics.Clyde
void WindowRequestAttention(WindowReg window);
void WindowSwapBuffers(WindowReg window);
uint? WindowGetX11Id(WindowReg window);
Task<WindowHandle> WindowCreate(WindowCreateParameters parameters);
WindowHandle WindowCreate(WindowCreateParameters parameters);
void WindowDestroy(WindowReg reg);
string KeyGetName(Keyboard.Key key);

View File

@@ -130,6 +130,6 @@ namespace Robust.Client.Graphics
IEnumerable<IClydeMonitor> EnumerateMonitors();
Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters);
IClydeWindow CreateWindow(WindowCreateParameters parameters);
}
}

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

@@ -6,20 +6,31 @@ namespace Robust.Client.UserInterface.Controls
{
/// <summary>
/// A container that lays out its children sequentially.
/// Use <see cref="VBoxContainer"/> or <see cref="HBoxContainer"/> for an implementation.
/// </summary>
public abstract class BoxContainer : Container
public class BoxContainer : Container
{
private LayoutOrientation _orientation;
public const string StylePropertySeparation = "separation";
private const int DefaultSeparation = 0;
private protected abstract bool Vertical { get; }
/// <summary>
/// Specifies "where" the controls should be laid out.
/// </summary>
public AlignMode Align { get; set; }
private bool Vertical => Orientation == LayoutOrientation.Vertical;
public LayoutOrientation Orientation
{
get => _orientation;
set
{
_orientation = value;
InvalidateMeasure();
}
}
private int ActualSeparation
{
get
@@ -237,5 +248,11 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
End = 2
}
public enum LayoutOrientation : byte
{
Horizontal,
Vertical
}
}
}

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

@@ -1,10 +1,16 @@
namespace Robust.Client.UserInterface.Controls
using System;
namespace Robust.Client.UserInterface.Controls
{
/// <summary>
/// Container that lays its children out horizontally: from left to right.
/// </summary>
[Obsolete("Use BoxContainer and set Orientation instead")]
public class HBoxContainer : BoxContainer
{
private protected override bool Vertical => false;
public HBoxContainer()
{
Orientation = LayoutOrientation.Horizontal;
}
}
}

View File

@@ -1,7 +1,13 @@
using System;
namespace Robust.Client.UserInterface.Controls
{
[Obsolete("Use SplitContainer directly and set Orientation")]
public class HSplitContainer : SplitContainer
{
private protected sealed override bool Vertical => false;
public HSplitContainer()
{
Orientation = SplitOrientation.Horizontal;
}
}
}

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

@@ -4,7 +4,7 @@ using Robust.Shared.Maths;
namespace Robust.Client.UserInterface.Controls
{
public abstract class SplitContainer : Container
public class SplitContainer : Container
{
/// <summary>
/// Defines how user-initiated moving of the split should work. See documentation
@@ -22,11 +22,10 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
public float SplitEdgeSeparation { get; set; }
private protected abstract bool Vertical { get; }
private float _splitCenter;
private SplitState _splitState;
private bool _dragging;
private SplitOrientation _orientation;
// min / max x and y extents in relative virtual pixels of where the split can go regardless
// of anything else.
@@ -35,6 +34,18 @@ namespace Robust.Client.UserInterface.Controls
private float SplitMax =>
Vertical ? Height - (SplitWidth + SplitEdgeSeparation) : Width - (SplitWidth + SplitEdgeSeparation);
private bool Vertical => Orientation == SplitOrientation.Vertical;
public SplitOrientation Orientation
{
get => _orientation;
set
{
_orientation = value;
InvalidateMeasure();
}
}
public SplitContainer()
{
MouseFilter = MouseFilterMode.Stop;
@@ -264,5 +275,11 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
Manual = 1
}
public enum SplitOrientation : byte
{
Horizontal,
Vertical
}
}
}

View File

@@ -1,10 +1,16 @@
namespace Robust.Client.UserInterface.Controls
using System;
namespace Robust.Client.UserInterface.Controls
{
/// <summary>
/// Container that lays its children out vertically: from top to bottom.
/// </summary>
[Obsolete("Use BoxContainer and set Orientation instead")]
public class VBoxContainer : BoxContainer
{
private protected override bool Vertical => true;
public VBoxContainer()
{
Orientation = LayoutOrientation.Vertical;
}
}
}

View File

@@ -1,7 +1,13 @@
using System;
namespace Robust.Client.UserInterface.Controls
{
[Obsolete("Use SplitContainer directly and set Orientation")]
public class VSplitContainer : SplitContainer
{
private protected sealed override bool Vertical => true;
public VSplitContainer()
{
Orientation = SplitOrientation.Vertical;
}
}
}

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

@@ -24,7 +24,7 @@ namespace Robust.Client.UserInterface
monitor = clyde.EnumerateMonitors().Single(m => m.Id == id);
}
var window = await clyde.CreateWindow(new WindowCreateParameters
var window = clyde.CreateWindow(new WindowCreateParameters
{
Maximized = true,
Title = "SS14 Debug Window",

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

@@ -1,24 +1,22 @@
using JetBrains.Annotations;
using JetBrains.Annotations;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics;
namespace Robust.Server.GameObjects
{
[UsedImplicitly]
public class PhysicsSystem : SharedPhysicsSystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
public override void Initialize()
{
base.Initialize();
_mapManager.OnGridCreated += HandleGridCreated;
SubscribeLocalEvent<GridInitializeEvent>(HandleGridInit);
LoadMetricCVar();
_configurationManager.OnValueChanged(CVars.MetricsEnabled, _ => LoadMetricCVar());
}
@@ -28,19 +26,14 @@ namespace Robust.Server.GameObjects
MetricsEnabled = _configurationManager.GetCVar(CVars.MetricsEnabled);
}
public override void Shutdown()
private void HandleGridInit(GridInitializeEvent ev)
{
base.Shutdown();
_mapManager.OnGridCreated -= HandleGridCreated;
}
var guid = ev.EntityUid;
private void HandleGridCreated(MapId mapId, GridId gridId)
{
if (!EntityManager.TryGetEntity(_mapManager.GetGrid(gridId).GridEntityId, out var gridEntity)) return;
var grid = _mapManager.GetGrid(gridId);
var collideComp = gridEntity.AddComponent<PhysicsComponent>();
if (!EntityManager.TryGetEntity(guid, out var gridEntity)) return;
var collideComp = gridEntity.EnsureComponent<PhysicsComponent>();
collideComp.CanCollide = true;
collideComp.AddFixture(new Fixture(collideComp, new PhysShapeGrid(grid)) {CollisionMask = MapGridHelpers.CollisionGroup, CollisionLayer = MapGridHelpers.CollisionGroup});
collideComp.BodyType = BodyType.Static;
}
/// <inheritdoc />

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

@@ -0,0 +1,185 @@
using System.Collections.Generic;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
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;
namespace Robust.Server.Physics
{
/// <summary>
/// Handles generating fixtures for MapGrids.
/// </summary>
internal sealed class GridFixtureSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
private 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
// (and at specific times due to race condition stuff).
// At any rate, cooldown given here if someone wants it. CD of 0 just runs it every tick.
private float _cooldown;
private float _accumulator;
private HashSet<MapChunk> _queuedChunks = new();
public override void Initialize()
{
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);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
_accumulator += frameTime;
if (_accumulator < _cooldown) return;
_accumulator -= _cooldown;
Process();
}
/// <summary>
/// Go through every dirty chunk and re-generate their fixtures.
/// </summary>
public void Process()
{
foreach (var chunk in _queuedChunks)
{
RegenerateCollision(chunk);
}
_queuedChunks.Clear();
}
/// <summary>
/// Queue the chunk to generate (if cooldown > 0) or immediately process it.
/// </summary>
/// <param name="ev"></param>
private void HandleCollisionRegenerate(RegenerateChunkCollisionEvent ev)
{
if (_cooldown <= 0f)
{
RegenerateCollision(ev.Chunk);
return;
}
_queuedChunks.Add(ev.Chunk);
}
private void RegenerateCollision(MapChunk chunk)
{
// Currently this is gonna be hella simple.
if (!_mapManager.TryGetGrid(chunk.GridId, out var grid) ||
!EntityManager.TryGetEntity(grid.GridEntityId, out var gridEnt) ||
!gridEnt.TryGetComponent(out PhysicsComponent? physicsComponent)) return;
// TODO: Lots of stuff here etc etc, make changes to mapgridchunk.
var bounds = chunk.CalcLocalBounds();
// So something goes on with the chunk's internal bounds caching where if there's no data the bound is 0 or something?
if (bounds.IsEmpty()) return;
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(
new PolygonShape
{
Vertices = new List<Vector2>
{
bounds.BottomRight,
bounds.TopRight,
bounds.TopLeft,
bounds.BottomLeft,
}
},
MapGridHelpers.CollisionGroup,
MapGridHelpers.CollisionGroup,
true) {ID = $"grid_chunk-{chunk.Indices.X}-{chunk.Indices.Y}",
Body = physicsComponent};
// 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)
_broadphase.DestroyFixture(physicsComponent, oldFixture);
_broadphase.CreateFixture(physicsComponent, newFixture);
chunk.Fixture = newFixture;
EntityManager.EventBus.RaiseLocalEvent(gridEnt.Uid,new GridFixtureChangeEvent {OldFixture = oldFixture, NewFixture = newFixture});
}
}
public sealed class GridFixtureChangeEvent : EntityEventArgs
{
public Fixture? OldFixture { get; init; }
public Fixture? NewFixture { get; init; }
}
}

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

@@ -316,7 +316,7 @@ namespace Robust.Shared.Maths
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float Area(in Box2 box)
public static float Area(in Box2 box)
=> box.Width * box.Height;
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -66,6 +66,11 @@ namespace Robust.Shared.Maths
return xOk && yOk;
}
public readonly bool IsEmpty()
{
return Bottom == Top || Left == Right;
}
/// <summary>Returns a UIBox2 translated by the given amount.</summary>
public readonly Box2i Translated(Vector2i point)
{

View File

@@ -226,6 +226,17 @@ namespace Robust.Shared
public static readonly CVarDef<float> MaxLightRadius =
CVarDef.Create("light.max_radius", 20.0f, CVar.CLIENTONLY);
/*
* Lookup
*/
/// <summary>
/// Like MaxLightRadius this is how far we enlarge lookups to find intersecting components.
/// This should be set to your maximum entity size.
/// </summary>
public static readonly CVarDef<float> LookupEnlargementRange =
CVarDef.Create("lookup.enlargement_range", 10.0f, CVar.ARCHIVE | CVar.REPLICATED | CVar.CHEAT);
/*
* LOKI
*/
@@ -451,6 +462,13 @@ namespace Robust.Shared
public static readonly CVarDef<float> MaxAngVelocity =
CVarDef.Create("physics.maxangvelocity", 15f);
/// <summary>
/// How frequently grid fixtures are updated. Given grid updates can be expensive they aren't run immediately.
/// Set to 0 to run them immediately.
/// </summary>
public static readonly CVarDef<float> GridFixtureUpdateRate =
CVarDef.Create("physics.grid_fixture_update_rate", 0.2f);
/*
* DISCORD
*/

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.
@@ -73,6 +76,64 @@ namespace Robust.Shared.GameObjects
public bool IgnoreCCD { get; set; }
[ViewVariables]
public int ContactCount
{
get
{
var count = 0;
var edge = ContactEdges;
while (edge != null)
{
edge = edge.Next;
count++;
}
return count;
}
}
[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>
@@ -119,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);
}
@@ -225,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;
@@ -429,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();
}
@@ -551,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.
@@ -640,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.
@@ -1010,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;
}
@@ -1047,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
@@ -1072,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)
@@ -1141,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()
{
@@ -1224,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 />
@@ -1237,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.
@@ -1273,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);
@@ -1339,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;
}
@@ -1365,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

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

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

@@ -400,7 +400,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public void ClearEventTables()
{
_eventTables.ClearEntities();
_eventTables.Clear();
}
public void Dispose()

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

@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
@@ -66,15 +68,24 @@ namespace Robust.Shared.GameObjects
[UsedImplicitly]
public class EntityLookup : IEntityLookup, IEntityEventSubscriber
{
private readonly IComponentManager _compManager;
private readonly IEntityManager _entityManager;
private readonly IMapManager _mapManager;
private readonly Dictionary<MapId, DynamicTree<IEntity>> _entityTreesPerMap = new();
private const int GrowthRate = 256;
private const float PointEnlargeRange = .00001f / 2;
// Using stacks so we always use latest data (given we only run it once per entity).
private readonly Stack<MoveEvent> _moveQueue = new();
private readonly Stack<RotateEvent> _rotateQueue = new();
private readonly Queue<EntMapIdChangedMessage> _mapChangeQueue = new();
private readonly Queue<EntParentChangedMessage> _parentChangeQueue = new();
/// <summary>
/// Like RenderTree we need to enlarge our lookup range for EntityLookupComponent as an entity is only ever on
/// 1 EntityLookupComponent at a time (hence it may overlap without another lookup).
/// </summary>
private float _lookupEnlargementRange;
/// <summary>
/// Move and rotate events generate the same update so no point duplicating work in the same tick.
@@ -83,10 +94,17 @@ namespace Robust.Shared.GameObjects
// TODO: Should combine all of the methods that check for IPhysBody and just use the one GetWorldAabbFromEntity method
// TODO: Combine GridTileLookupSystem and entity anchoring together someday.
// Queries are a bit of spaghet rn but ideally you'd just have:
// A) The fast tile-based one
// B) The physics-only one (given physics needs it to be fast af)
// C) A generic use one that covers anything not caught in the above.
public bool Started { get; private set; } = false;
public EntityLookup(IEntityManager entityManager, IMapManager mapManager)
public EntityLookup(IComponentManager compManager, IEntityManager entityManager, IMapManager mapManager)
{
_compManager = compManager;
_entityManager = entityManager;
_mapManager = mapManager;
}
@@ -98,14 +116,21 @@ namespace Robust.Shared.GameObjects
throw new InvalidOperationException("Startup() called multiple times.");
}
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.OnValueChanged(CVars.LookupEnlargementRange, value => _lookupEnlargementRange = value, true);
var eventBus = _entityManager.EventBus;
eventBus.SubscribeEvent<MoveEvent>(EventSource.Local, this, ev => _moveQueue.Push(ev));
eventBus.SubscribeEvent<RotateEvent>(EventSource.Local, this, ev => _rotateQueue.Push(ev));
eventBus.SubscribeEvent<EntMapIdChangedMessage>(EventSource.Local, this, ev => _mapChangeQueue.Enqueue(ev));
eventBus.SubscribeEvent<EntParentChangedMessage>(EventSource.Local, this, ev => _parentChangeQueue.Enqueue(ev));
eventBus.SubscribeLocalEvent<EntityLookupComponent, ComponentInit>(HandleLookupInit);
eventBus.SubscribeLocalEvent<EntityLookupComponent, ComponentShutdown>(HandleLookupShutdown);
eventBus.SubscribeEvent<GridInitializeEvent>(EventSource.Local, this, HandleGridInit);
_entityManager.EntityDeleted += HandleEntityDeleted;
_entityManager.EntityStarted += HandleEntityStarted;
_mapManager.MapCreated += HandleMapCreated;
_mapManager.MapDestroyed += HandleMapDestroyed;
Started = true;
}
@@ -118,17 +143,43 @@ namespace Robust.Shared.GameObjects
_moveQueue.Clear();
_rotateQueue.Clear();
_handledThisTick.Clear();
_mapChangeQueue.Clear();
_entityTreesPerMap.Clear();
_parentChangeQueue.Clear();
_entityManager.EventBus.UnsubscribeEvents(this);
_entityManager.EntityDeleted -= HandleEntityDeleted;
_entityManager.EntityStarted -= HandleEntityStarted;
_mapManager.MapCreated -= HandleMapCreated;
_mapManager.MapDestroyed -= HandleMapDestroyed;
Started = false;
}
private void HandleLookupShutdown(EntityUid uid, EntityLookupComponent component, ComponentShutdown args)
{
component.Tree.Clear();
}
private void HandleGridInit(GridInitializeEvent ev)
{
_entityManager.GetEntity(ev.EntityUid).EnsureComponent<EntityLookupComponent>();
}
private void HandleLookupInit(EntityUid uid, EntityLookupComponent component, ComponentInit args)
{
var capacity = (int) Math.Min(256, Math.Ceiling(component.Owner.Transform.ChildCount / (float) GrowthRate) * GrowthRate);
component.Tree = new DynamicTree<IEntity>(
GetRelativeAABBFromEntity,
capacity: capacity,
growthFunc: x => x == GrowthRate ? GrowthRate * 8 : x + GrowthRate
);
}
private static Box2 GetRelativeAABBFromEntity(in IEntity entity)
{
var aabb = GetWorldAABB(entity);
var tree = GetLookup(entity);
return aabb.Translated(-tree?.Owner.Transform.WorldPosition ?? Vector2.Zero);
}
private void HandleEntityDeleted(object? sender, EntityUid uid)
{
RemoveFromEntityTrees(_entityManager.GetEntity(uid));
@@ -141,63 +192,78 @@ namespace Robust.Shared.GameObjects
private void HandleMapCreated(object? sender, MapEventArgs eventArgs)
{
_entityTreesPerMap[eventArgs.Map] = new DynamicTree<IEntity>(
GetWorldAabbFromEntity,
capacity: 16,
growthFunc: x => x == 16 ? 3840 : x + 256
);
}
if (eventArgs.Map == MapId.Nullspace) return;
private void HandleMapDestroyed(object? sender, MapEventArgs eventArgs)
{
_entityTreesPerMap.Remove(eventArgs.Map);
_mapManager.GetMapEntity(eventArgs.Map).EnsureComponent<EntityLookupComponent>();
}
public void Update()
{
// Acruid said he'd deal with Update being called around IEntityManager later.
_handledThisTick.Clear();
while (_mapChangeQueue.TryDequeue(out var mapChangeEvent))
// Could be more efficient but essentially nuke their old lookup and add to new lookup if applicable.
while (_parentChangeQueue.TryDequeue(out var mapChangeEvent))
{
if (mapChangeEvent.Entity.Deleted) continue;
RemoveFromEntityTree(mapChangeEvent.Entity, mapChangeEvent.OldMapId);
UpdateEntityTree(mapChangeEvent.Entity, GetWorldAabbFromEntity(mapChangeEvent.Entity));
_handledThisTick.Add(mapChangeEvent.Entity.Uid);
RemoveFromEntityTrees(mapChangeEvent.Entity);
if (mapChangeEvent.Entity.Deleted) continue;
UpdateEntityTree(mapChangeEvent.Entity, GetWorldAabbFromEntity(mapChangeEvent.Entity));
}
while (_moveQueue.TryPop(out var moveEvent))
{
if (moveEvent.Sender.Deleted || _handledThisTick.Contains(moveEvent.Sender.Uid)) continue;
if (moveEvent.Sender.Deleted || !_handledThisTick.Add(moveEvent.Sender.Uid)) continue;
UpdateEntityTree(moveEvent.Sender, moveEvent.WorldAABB);
_handledThisTick.Add(moveEvent.Sender.Uid);
}
while (_rotateQueue.TryPop(out var rotateEvent))
{
if (rotateEvent.Sender.Deleted || _handledThisTick.Contains(rotateEvent.Sender.Uid)) continue;
if (rotateEvent.Sender.Deleted || !_handledThisTick.Add(rotateEvent.Sender.Uid)) continue;
UpdateEntityTree(rotateEvent.Sender, rotateEvent.WorldAABB);
}
_handledThisTick.Clear();
}
#region Spatial Queries
private IEnumerable<EntityLookupComponent> GetLookupsIntersecting(MapId mapId, Box2 worldAABB)
{
if (mapId == MapId.Nullspace) yield break;
// TODO: Recursive and all that.
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB.Enlarged(_lookupEnlargementRange)))
{
yield return _entityManager.GetEntity(grid.GridEntityId).GetComponent<EntityLookupComponent>();
}
yield return _mapManager.GetMapEntity(mapId).GetComponent<EntityLookupComponent>();
}
/// <inheritdoc />
public bool AnyEntitiesIntersecting(MapId mapId, Box2 box, bool approximate = false)
{
var found = false;
_entityTreesPerMap[mapId].QueryAabb(ref found, (ref bool found, in IEntity ent) =>
{
if (!ent.Deleted)
{
found = true;
return false;
}
return true;
}, box, approximate);
foreach (var lookup in GetLookupsIntersecting(mapId, box))
{
var offsetBox = box.Translated(-lookup.Owner.Transform.WorldPosition);
lookup.Tree.QueryAabb(ref found, (ref bool found, in IEntity ent) =>
{
if (!ent.Deleted)
{
found = true;
return false;
}
return true;
}, offsetBox, approximate);
}
return found;
}
@@ -205,31 +271,34 @@ namespace Robust.Shared.GameObjects
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void FastEntitiesIntersecting(in MapId mapId, ref Box2 position, EntityQueryCallback callback)
{
if (mapId == MapId.Nullspace)
return;
foreach (var lookup in GetLookupsIntersecting(mapId, position))
{
var offsetBox = position.Translated(-lookup.Owner.Transform.WorldPosition);
_entityTreesPerMap[mapId]._b2Tree
.FastQuery(ref position, (ref IEntity data) => callback(data));
lookup.Tree._b2Tree.FastQuery(ref offsetBox, (ref IEntity data) => callback(data));
}
}
/// <inheritdoc />
public IEnumerable<IEntity> GetEntitiesIntersecting(MapId mapId, Box2 position, bool approximate = false)
{
if (!_entityTreesPerMap.TryGetValue(mapId, out var mapTree))
{
return Enumerable.Empty<IEntity>();
}
if (mapId == MapId.Nullspace) return Enumerable.Empty<IEntity>();
var list = new List<IEntity>();
mapTree.QueryAabb(ref list, (ref List<IEntity> list, in IEntity ent) =>
foreach (var lookup in GetLookupsIntersecting(mapId, position))
{
if (!ent.Deleted)
var offsetBox = position.Translated(-lookup.Owner.Transform.WorldPosition);
lookup.Tree.QueryAabb(ref list, (ref List<IEntity> list, in IEntity ent) =>
{
list.Add(ent);
}
return true;
}, position, approximate);
if (!ent.Deleted)
{
list.Add(ent);
}
return true;
}, offsetBox, approximate);
}
return list;
}
@@ -237,25 +306,25 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public IEnumerable<IEntity> GetEntitiesIntersecting(MapId mapId, Vector2 position, bool approximate = false)
{
const float range = .00001f / 2;
var aabb = new Box2(position, position).Enlarged(range);
if (mapId == MapId.Nullspace)
{
return Enumerable.Empty<IEntity>();
}
if (mapId == MapId.Nullspace) return Enumerable.Empty<IEntity>();
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
var list = new List<IEntity>();
var state = (list, position);
_entityTreesPerMap[mapId].QueryAabb(ref state, (ref (List<IEntity> list, Vector2 position) state, in IEntity ent) =>
foreach (var lookup in GetLookupsIntersecting(mapId, aabb))
{
if (Intersecting(ent, state.position))
var offsetBox = aabb.Translated(-lookup.Owner.Transform.WorldPosition);
lookup.Tree.QueryAabb(ref state, (ref (List<IEntity> list, Vector2 position) state, in IEntity ent) =>
{
state.list.Add(ent);
}
return true;
}, aabb, approximate);
if (Intersecting(ent, state.position))
{
state.list.Add(ent);
}
return true;
}, offsetBox, approximate);
}
return list;
}
@@ -365,34 +434,44 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public IEnumerable<IEntity> GetEntitiesInMap(MapId mapId)
{
if (!_entityTreesPerMap.TryGetValue(mapId, out var trees))
yield break;
foreach (var entity in trees)
foreach (EntityLookupComponent comp in _compManager.EntityQuery<EntityLookupComponent>(true))
{
if (!entity.Deleted)
foreach (var entity in comp.Tree)
{
if (entity.Deleted) continue;
yield return entity;
}
}
}
/// <inheritdoc />
public IEnumerable<IEntity> GetEntitiesAt(MapId mapId, Vector2 position, bool approximate = false)
{
if (mapId == MapId.Nullspace) return Enumerable.Empty<IEntity>();
var list = new List<IEntity>();
var state = (list, position);
_entityTreesPerMap[mapId].QueryPoint(ref state, (ref (List<IEntity> list, Vector2 position) state, in IEntity ent) =>
{
var transform = ent.Transform;
if (MathHelper.CloseTo(transform.Coordinates.X, state.position.X) &&
MathHelper.CloseTo(transform.Coordinates.Y, state.position.Y))
{
state.list.Add(ent);
}
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
return true;
}, position, approximate);
foreach (var lookup in GetLookupsIntersecting(mapId, aabb))
{
var offsetPos = position -lookup.Owner.Transform.WorldPosition;
lookup.Tree.QueryPoint(ref state, (ref (List<IEntity> list, Vector2 position) state, in IEntity ent) =>
{
var transform = ent.Transform;
if (MathHelper.CloseTo(transform.Coordinates.X, state.position.X) &&
MathHelper.CloseTo(transform.Coordinates.Y, state.position.Y))
{
state.list.Add(ent);
}
return true;
}, offsetPos, approximate);
}
return list;
}
@@ -400,88 +479,119 @@ namespace Robust.Shared.GameObjects
#endregion
#region Entity DynamicTree
private static EntityLookupComponent? GetLookup(IEntity entity)
{
if (entity.Transform.MapID == MapId.Nullspace)
{
return null;
}
// if it's map return null. Grids should return the map's broadphase.
if (entity.HasComponent<EntityLookupComponent>() &&
entity.Transform.Parent == null)
{
return null;
}
var parent = entity.Transform.Parent?.Owner;
while (true)
{
if (parent == null) break;
if (parent.TryGetComponent(out EntityLookupComponent? comp)) return comp;
parent = parent.Transform.Parent?.Owner;
}
return null;
}
/// <inheritdoc />
public virtual bool UpdateEntityTree(IEntity entity, Box2? worldAABB = null)
{
// look there's JANK everywhere but I'm just bandaiding it for now for shuttles and we'll fix it later when
// PVS is more stable and entity anchoring has been battle-tested.
if (entity.Deleted)
{
RemoveFromEntityTrees(entity);
return true;
}
if (!entity.Initialized || !_entityManager.EntityExists(entity.Uid))
if (!entity.Initialized)
{
return false;
}
var transform = entity.Transform;
var lookup = GetLookup(entity);
if (lookup == null)
{
RemoveFromEntityTrees(entity);
return true;
}
// Temp PVS guard for when we clear dynamictree for now.
worldAABB ??= GetWorldAabbFromEntity(entity);
var center = worldAABB.Value.Center;
if (float.IsNaN(center.X) || float.IsNaN(center.Y))
{
RemoveFromEntityTrees(entity);
return true;
}
var transform = entity.Transform;
DebugTools.Assert(transform.Initialized);
var mapId = transform.MapID;
if (!_entityTreesPerMap.TryGetValue(mapId, out var entTree))
{
entTree = new DynamicTree<IEntity>(
GetWorldAabbFromEntity,
capacity: 16,
growthFunc: x => x == 16 ? 3840 : x + 256
);
_entityTreesPerMap.Add(mapId, entTree);
}
var aabb = worldAABB.Value.Translated(-lookup.Owner.Transform.WorldPosition);
// for debugging
var necessary = 0;
if (entTree.AddOrUpdate(entity, worldAABB))
if (lookup.Tree.AddOrUpdate(entity, aabb))
{
++necessary;
}
foreach (var childTx in entity.Transform.ChildEntityUids)
if (!entity.HasComponent<EntityLookupComponent>())
{
if (UpdateEntityTree(_entityManager.GetEntity(childTx)))
foreach (var childTx in entity.Transform.ChildEntityUids)
{
++necessary;
if (!_handledThisTick.Add(childTx)) continue;
if (UpdateEntityTree(_entityManager.GetEntity(childTx)))
{
++necessary;
}
}
}
return necessary > 0;
}
public bool RemoveFromEntityTree(IEntity entity, MapId mapId)
{
if (_entityTreesPerMap.TryGetValue(mapId, out var tree))
{
return tree.Remove(entity);
}
return false;
}
/// <inheritdoc />
public void RemoveFromEntityTrees(IEntity entity)
{
foreach (var mapId in _mapManager.GetAllMapIds())
foreach (var lookup in _compManager.EntityQuery<EntityLookupComponent>(true))
{
if (_entityTreesPerMap.TryGetValue(mapId, out var entTree))
{
entTree.Remove(entity);
}
lookup.Tree.Remove(entity);
}
}
public Box2 GetWorldAabbFromEntity(in IEntity ent)
{
if (ent.Deleted)
return new Box2(0, 0, 0, 0);
return GetWorldAABB(ent);
}
private static Box2 GetWorldAABB(in IEntity ent)
{
var pos = ent.Transform.WorldPosition;
if (ent.TryGetComponent(out IPhysBody? collider))
return collider.GetWorldAABB(pos);
if (ent.Deleted || !ent.TryGetComponent(out PhysicsComponent? physics))
return new Box2(pos, pos);
return new Box2(pos, pos);
return physics.GetWorldAABB(pos);
}
#endregion

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();
@@ -100,6 +101,7 @@ namespace Robust.Shared.GameObjects
_mapManager.MapCreated += HandleMapCreated;
_mapManager.MapDestroyed += HandleMapDestroyed;
SubscribeLocalEvent<GridInitializeEvent>(HandleGridInit);
SubscribeLocalEvent<PhysicsUpdateMessage>(HandlePhysicsUpdateMessage);
SubscribeLocalEvent<PhysicsWakeMessage>(HandleWakeMessage);
SubscribeLocalEvent<PhysicsSleepMessage>(HandleSleepMessage);
@@ -146,6 +148,13 @@ namespace Robust.Shared.GameObjects
body.AngularVelocity += angularVelocityDiff;
}
private void HandleGridInit(GridInitializeEvent ev)
{
if (!EntityManager.TryGetEntity(ev.EntityUid, out var gridEntity)) return;
var collideComp = gridEntity.EnsureComponent<PhysicsComponent>();
collideComp.BodyType = BodyType.Static;
}
private void BuildControllers()
{
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
@@ -347,6 +356,8 @@ namespace Robust.Shared.GameObjects
if (mapId == MapId.Nullspace) continue;
map.ProcessQueue();
}
_physicsManager.ClearTransforms();
}
}
}

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>

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