Compare commits

...

32 Commits

Author SHA1 Message Date
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
110 changed files with 1815 additions and 757 deletions

View File

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

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

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

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

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

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

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

@@ -250,7 +250,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

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

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

@@ -0,0 +1,136 @@
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);
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-{grid.Index}_chunk-{chunk.Indices.X}-{chunk.Indices.Y}",
Body = physicsComponent};
// TODO: Chunk will likely need multiple fixtures but future sloth problem lmao fucking dickhead
if (oldFixture?.Equals(newFixture) == true) return;
if (oldFixture != null)
physicsComponent.RemoveFixture(oldFixture);
physicsComponent.AddFixture(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

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

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

@@ -18,18 +18,6 @@ namespace Robust.Shared.GameObjects
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

@@ -73,6 +73,23 @@ 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;
}
}
/// <summary>
/// Linked-list of all of our contacts.
/// </summary>

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

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

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

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

@@ -83,7 +83,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 +100,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 +147,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>();

View File

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

View File

@@ -8,10 +8,8 @@ namespace Robust.Shared.Map
/// </summary>
internal static class GridChunkPartition
{
public static void PartitionChunk(IMapChunk chunk, ref IList<Box2> rectangles, out Box2i bounds)
public static void PartitionChunk(IMapChunk chunk, out Box2i bounds)
{
rectangles.Clear();
var size = chunk.ChunkSize;
// copy 2d img
@@ -37,8 +35,6 @@ namespace Robust.Shared.Map
var bottom = block.x1;
var top = block.x2 + 1;
rectangles.Add(new Box2(left, bottom, right, top));
if(bounds.Size.Equals(Vector2i.Zero))
bounds = new Box2i(left, bottom, right, top);
else

View File

@@ -15,10 +15,5 @@ namespace Robust.Shared.Map
/// The last game simulation tick that this chunk was modified.
/// </summary>
GameTick LastModifiedTick { get; }
/// <summary>
/// The physical collision boxes of this chunk.
/// </summary>
IEnumerable<Box2> CollisionBoxes { get; }
}
}

View File

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

View File

@@ -24,7 +24,7 @@ namespace Robust.Shared.Map
/// This wouldn't even be separate if not for the whole "ability to suppress automatic collision regeneration" thing.
/// As it is, YamlGridSerializer performs manual collision regeneration and that wasn't properly getting propagated to the grid. Thus, this needs to exist.
/// </summary>
void NotifyChunkCollisionRegenerated();
void NotifyChunkCollisionRegenerated(MapChunk chunk);
/// <summary>
/// Returns the chunk at the given indices. If the chunk does not exist,

View File

@@ -2,7 +2,9 @@ using System;
using System.Collections;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -16,6 +18,8 @@ namespace Robust.Shared.Map
/// </summary>
private const int SnapCellStartingCapacity = 1;
public GridId GridId => _grid.Index;
private readonly IMapGridInternal _grid;
private readonly Vector2i _gridIndices;
@@ -23,13 +27,12 @@ namespace Robust.Shared.Map
private readonly SnapGridCell[,] _snapGrid;
private Box2i _cachedBounds;
private IList<Box2> _colBoxes;
public Fixture? Fixture { get; set; }
/// <inheritdoc />
public GameTick LastModifiedTick { get; private set; }
public IEnumerable<Box2> CollisionBoxes => _colBoxes;
/// <summary>
/// Constructs an instance of a MapGrid chunk.
/// </summary>
@@ -46,7 +49,6 @@ namespace Robust.Shared.Map
_tiles = new Tile[ChunkSize, ChunkSize];
_snapGrid = new SnapGridCell[ChunkSize, ChunkSize];
_colBoxes = new List<Box2>(0);
}
/// <inheritdoc />
@@ -233,8 +235,8 @@ namespace Robust.Shared.Map
public void RegenerateCollision()
{
// generate collision rects
GridChunkPartition.PartitionChunk(this, ref _colBoxes, out _cachedBounds);
_grid.NotifyChunkCollisionRegenerated();
GridChunkPartition.PartitionChunk(this, out _cachedBounds);
_grid.NotifyChunkCollisionRegenerated(this);
}
/// <inheritdoc />
@@ -264,12 +266,6 @@ namespace Robust.Shared.Map
return _tiles[localIndices.X, localIndices.Y].TypeId != Tile.Empty.TypeId;
}
/// <inheritdoc />
public bool CollidesWithChunk(Box2 pos)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override string ToString()
{
@@ -281,4 +277,14 @@ namespace Robust.Shared.Map
public List<EntityUid>? Center;
}
}
internal sealed class RegenerateChunkCollisionEvent : EntityEventArgs
{
public MapChunk Chunk { get; }
public RegenerateChunkCollisionEvent(MapChunk chunk)
{
Chunk = chunk;
}
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -40,7 +41,7 @@ namespace Robust.Shared.Map
private readonly Dictionary<Vector2i, IMapChunkInternal> _chunks = new();
private readonly IMapManagerInternal _mapManager;
private readonly IEntityManager _entityManager;
private readonly IEntityManager _entityManager;
/// <summary>
/// Initializes a new instance of the <see cref="MapGrid"/> class.
@@ -111,6 +112,34 @@ namespace Robust.Shared.Map
}
}
/// <inheritdoc />
[ViewVariables]
public Matrix3 WorldMatrix
{
get
{
//TODO: Make grids real parents of entities.
if(GridEntityId.IsValid())
return _mapManager.EntityManager.GetEntity(GridEntityId).Transform.WorldMatrix;
return Matrix3.Identity;
}
}
/// <inheritdoc />
[ViewVariables]
public Matrix3 InvWorldMatrix
{
get
{
//TODO: Make grids real parents of entities.
if(GridEntityId.IsValid())
return _mapManager.EntityManager.GetEntity(GridEntityId).Transform.InvWorldMatrix;
return Matrix3.Identity;
}
}
/// <summary>
/// Expands the AABB for this grid when a new tile is added. If the tile is already inside the existing AABB,
/// nothing happens. If it is outside, the AABB is expanded to fit the new tile.
@@ -146,8 +175,18 @@ namespace Robust.Shared.Map
}
/// <inheritdoc />
public void NotifyChunkCollisionRegenerated()
public void NotifyChunkCollisionRegenerated(MapChunk chunk)
{
// TODO: Ideally we wouldn't have LocalBounds on the grid and we could just treat it like a physics object
// (eventually, well into the future).
// For now we'll just attach a fixture to each chunk.
// Not raising directed because the grid's EntityUid isn't set yet.
IoCManager
.Resolve<IEntityManager>()
.EventBus
.RaiseEvent(EventSource.Local, new RegenerateChunkCollisionEvent(chunk));
UpdateAABB();
}
@@ -478,7 +517,7 @@ namespace Robust.Shared.Map
/// <inheritdoc />
public Vector2 WorldToLocal(Vector2 posWorld)
{
return posWorld - WorldPosition;
return InvWorldMatrix.Transform(posWorld);
}
/// <inheritdoc />
@@ -498,7 +537,7 @@ namespace Robust.Shared.Map
/// <inheritdoc />
public Vector2 LocalToWorld(Vector2 posLocal)
{
return posLocal + WorldPosition;
return WorldMatrix.Transform(posLocal);
}
public Vector2i WorldToTile(Vector2 posWorld)

View File

@@ -6,6 +6,8 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -521,6 +523,7 @@ namespace Robust.Shared.Map
/// <inheritdoc />
public bool TryFindGridAt(MapId mapId, Vector2 worldPos, [NotNullWhen(true)] out IMapGrid? grid)
{
// TODO: this won't actually "work" but we need the broadphase refactor to finish it.
foreach (var mapGrid in _grids.Values)
{
if (mapGrid.ParentMapId != mapId)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -114,17 +114,6 @@ namespace Robust.Shared.Physics.Collision
Radius = rectangle.Radius;
break;
case ShapeType.Grid:
var grid = (PhysShapeGrid) shape;
var gridBounds = grid.LocalBounds;
Vertices.Add(gridBounds.BottomRight);
Vertices.Add(gridBounds.TopRight);
Vertices.Add(gridBounds.TopLeft);
Vertices.Add(gridBounds.BottomLeft);
Radius = grid.Radius;
break;
default:
throw new InvalidOperationException($"Invalid shapetype specified {shape.ShapeType}");
}

View File

@@ -33,6 +33,8 @@ namespace Robust.Shared.Physics.Collision.Shapes
[DataDefinition]
public sealed class EdgeShape : IPhysShape
{
internal Vector2 Centroid { get; set; } = Vector2.Zero;
/// <summary>
/// Edge start vertex
/// </summary>
@@ -119,6 +121,12 @@ namespace Robust.Shared.Physics.Collision.Shapes
Vertex3 == edge.Vertex3);
}
public bool Intersects(Box2 worldAABB, Vector2 worldPos, Angle worldRot)
{
var bounds = CalculateLocalBounds(worldRot).Translated(worldPos);
return bounds.Intersects(worldAABB);
}
public Box2 CalculateLocalBounds(Angle rotation)
{
Vector2 lower = Vector2.ComponentMin(Vertex1, Vertex2);

View File

@@ -12,8 +12,7 @@ namespace Robust.Shared.Physics.Collision.Shapes
Chain = 3,
Aabb = 4,
Rectangle = 5, // Look you might be able to replace this with polys but for now I have done the thing
Grid = 6,
TypeCount = 7, // Obviously increment this if you add something
TypeCount = 6, // Obviously increment this if you add something
}
/// <summary>
@@ -37,6 +36,9 @@ namespace Robust.Shared.Physics.Collision.Shapes
ShapeType ShapeType { get; }
// TODO: Like raycasts these aren't using exact shapes so need to do that.
bool Intersects(Box2 worldAABB, Vector2 worldPos, Angle worldRot);
/// <summary>
/// Calculates the AABB of the shape.
/// </summary>

View File

@@ -38,6 +38,8 @@ namespace Robust.Shared.Physics.Collision.Shapes
private float _radius;
internal Vector2 Centroid { get; set; } = Vector2.Zero;
public ShapeType ShapeType => ShapeType.Aabb;
[DataField("bounds")]
@@ -88,6 +90,12 @@ namespace Robust.Shared.Physics.Collision.Shapes
[field: NonSerialized]
public event Action? OnDataChanged;
public bool Intersects(Box2 worldAABB, Vector2 worldPos, Angle worldRot)
{
var bounds = CalculateLocalBounds(worldRot).Translated(worldPos);
return bounds.Intersects(worldAABB);
}
/// <inheritdoc />
public Box2 CalculateLocalBounds(Angle rotation)
{

View File

@@ -58,6 +58,12 @@ namespace Robust.Shared.Physics.Collision.Shapes
}
}
public bool Intersects(Box2 worldAABB, Vector2 worldPos, Angle worldRot)
{
var bounds = CalculateLocalBounds(worldRot).Translated(worldPos);
return bounds.Intersects(worldAABB);
}
/// <inheritdoc />
public Box2 CalculateLocalBounds(Angle rotation)
{

View File

@@ -1,107 +0,0 @@
using System;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Physics.Collision.Shapes
{
/// <summary>
/// A physics shape that represents a <see cref="MapGrid"/>.
/// </summary>
[Serializable, NetSerializable]
[DataDefinition]
public class PhysShapeGrid : IPhysShape, ISerializationHooks
{
public int ChildCount => 1;
public Box2 LocalBounds => _mapGrid.LocalBounds;
/// <summary>
/// The radius of this AABB
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float Radius
{
get => _radius;
set
{
if (MathHelper.CloseTo(_radius, value)) return;
_radius = value;
}
}
private float _radius = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.PolygonRadius);
public ShapeType ShapeType => ShapeType.Grid;
[DataField("grid")]
private GridId _gridId = GridId.Invalid;
[NonSerialized]
private IMapGridInternal _mapGrid = default!;
void ISerializationHooks.AfterDeserialization()
{
var mapMan = IoCManager.Resolve<IMapManager>();
_mapGrid = (IMapGridInternal)mapMan.GetGrid(_gridId);
}
/// <inheritdoc />
public void ApplyState()
{
var mapMan = IoCManager.Resolve<IMapManager>();
_mapGrid = (IMapGridInternal)mapMan.GetGrid(_gridId);
}
public void DebugDraw(DebugDrawingHandle handle, in Matrix3 modelMatrix, in Box2 worldViewport,
float sleepPercent)
{
handle.SetTransform(modelMatrix);
foreach (var chunk in _mapGrid.GetMapChunks().Values)
{
foreach (var box in chunk.CollisionBoxes)
{
var localChunkPos = new Vector2(chunk.Indices.X, chunk.Indices.Y) * _mapGrid.ChunkSize;
var localBox = box.Translated(localChunkPos);
handle.DrawRect(localBox, handle.CalcWakeColor(handle.GridFillColor, sleepPercent));
}
}
}
/// <summary>
/// Constructs a new instance of <see cref="PhysShapeGrid"/>.
/// </summary>
public PhysShapeGrid()
{
// Better hope ExposeData get called...
}
/// <summary>
/// Constructs a new instance of <see cref="PhysShapeGrid"/>.
/// </summary>
public PhysShapeGrid(IMapGrid mapGrid)
{
_mapGrid = (IMapGridInternal)mapGrid;
_gridId = _mapGrid.Index;
}
public event Action? OnDataChanged { add { } remove { } }
/// <inheritdoc />
public Box2 CalculateLocalBounds(Angle rotation)
{
return new Box2Rotated(_mapGrid.LocalBounds, rotation).CalcBoundingBox().Scale(1 + Radius);
}
public bool Equals(IPhysShape? other)
{
if (other is not PhysShapeGrid otherGrid) return false;
return MathHelper.CloseTo(_radius, otherGrid._radius) &&
_gridId == otherGrid._gridId;
}
}
}

View File

@@ -37,6 +37,8 @@ namespace Robust.Shared.Physics.Collision.Shapes
private float _radius = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.PolygonRadius);
internal Vector2 Centroid { get; set; } = Vector2.Zero;
public ShapeType ShapeType => ShapeType.Rectangle;
/// <summary>
@@ -65,6 +67,12 @@ namespace Robust.Shared.Physics.Collision.Shapes
[field: NonSerialized]
public event Action? OnDataChanged;
public bool Intersects(Box2 worldAABB, Vector2 worldPos, Angle worldRot)
{
var bounds = CalculateLocalBounds(worldRot).Translated(worldPos);
return bounds.Intersects(worldAABB);
}
public Box2 CalculateLocalBounds(Angle rotation)
{
_cachedBounds = new Box2Rotated(Rectangle, rotation.Opposite(), Vector2.Zero).CalcBoundingBox().Scale(1 + Radius);

View File

@@ -75,6 +75,8 @@ namespace Robust.Shared.Physics.Collision.Shapes
_normals.Add(temp.Normalized);
}
Centroid = ComputeCentroid(_vertices);
// Compute the polygon mass data
// TODO: Update fixture. Maybe use events for it? Who tf knows.
// If we get grid polys then we'll actually need runtime updating of bbs.
@@ -83,6 +85,8 @@ namespace Robust.Shared.Physics.Collision.Shapes
private List<Vector2> _vertices = new();
internal Vector2 Centroid { get; set; } = Vector2.Zero;
[ViewVariables(VVAccess.ReadOnly)]
public List<Vector2> Normals => _normals;
@@ -107,6 +111,44 @@ namespace Robust.Shared.Physics.Collision.Shapes
private float _radius;
public static Vector2 ComputeCentroid(List<Vector2> vertices)
{
DebugTools.Assert(vertices.Count >= 3);
var c = new Vector2(0.0f, 0.0f);
float area = 0.0f;
// Get a reference point for forming triangles.
// Use the first vertex to reduce round-off errors.
var s = vertices[0];
const float inv3 = 1.0f / 3.0f;
for (var i = 0; i < vertices.Count; ++i)
{
// Triangle vertices.
var p1 = vertices[0] - s;
var p2 = vertices[i] - s;
var p3 = i + 1 < vertices.Count ? vertices[i+1] - s : vertices[0] - s;
var e1 = p2 - p1;
var e2 = p3 - p1;
float D = Vector2.Cross(e1, e2);
float triangleArea = 0.5f * D;
area += triangleArea;
// Area weighted centroid
c += (p1 + p2 + p3) * triangleArea * inv3;
}
// Centroid
DebugTools.Assert(area > float.Epsilon);
c = c * (1.0f / area) + s;
return c;
}
public ShapeType ShapeType => ShapeType.Polygon;
public PolygonShape()
@@ -157,6 +199,15 @@ namespace Robust.Shared.Physics.Collision.Shapes
return true;
}
public bool Intersects(Box2 worldAABB, Vector2 worldPos, Angle worldRot)
{
var aabb = CalculateLocalBounds(worldRot).Translated(worldPos);
if (!worldAABB.Intersects(aabb)) return false;
// TODO
return true;
}
public Box2 CalculateLocalBounds(Angle rotation)
{
if (Vertices.Count == 0) return new Box2();

View File

@@ -34,6 +34,7 @@ using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics.Contacts;
@@ -63,7 +64,7 @@ namespace Robust.Shared.Physics.Dynamics
/// <summary>
/// Invoked whenever a KinematicController body collides. The first body is always guaranteed to be a KinematicController
/// </summary>
internal event Action<Fixture, Fixture, float, Manifold>? KinematicControllerCollision;
internal event Action<Fixture, Fixture, float, Vector2>? KinematicControllerCollision;
// TODO: Need to migrate the interfaces to comp bus when possible
// TODO: Also need to clean the station up to not have 160 contacts on roundstart
@@ -412,20 +413,6 @@ namespace Robust.Shared.Physics.Dynamics
_entityManager.EventBus.RaiseLocalEvent(bodyA.Owner.Uid, new EndCollideEvent(fixtureA, fixtureB, manifold));
_entityManager.EventBus.RaiseLocalEvent(bodyB.Owner.Uid, new EndCollideEvent(fixtureB, fixtureA, manifold));
#pragma warning disable 618
foreach (var comp in bodyA.Owner.GetAllComponents<IEndCollide>().ToArray())
{
if (bodyB.Deleted) break;
comp.CollideWith(fixtureA, fixtureB, manifold);
}
foreach (var comp in bodyB.Owner.GetAllComponents<IEndCollide>().ToArray())
{
if (bodyA.Deleted) break;
comp.CollideWith(fixtureB, fixtureA, manifold);
}
#pragma warning restore 618
}
_startCollisions.Clear();
@@ -434,26 +421,29 @@ namespace Robust.Shared.Physics.Dynamics
public void PreSolve(float frameTime)
{
Span<Vector2> points = stackalloc Vector2[2];
// We'll do pre and post-solve around all islands rather than each specific island as it seems cleaner with race conditions.
for (var contact = ContactList.Next; contact != ContactList; contact = contact?.Next)
{
if (contact == null || !contact.IsTouching || !contact.Enabled)
if (contact is not {IsTouching: true, Enabled: true})
{
continue;
}
var bodyA = contact.FixtureA!.Body;
var bodyB = contact.FixtureB!.Body;
contact.GetWorldManifold(out var worldNormal, points);
// Didn't use an EntitySystemMessage as this is called FOR EVERY COLLISION AND IS REALLY EXPENSIVE
// so we just use the Action. Also we'll sort out BodyA / BodyB for anyone listening first.
if (bodyA.BodyType == BodyType.KinematicController)
{
KinematicControllerCollision?.Invoke(contact.FixtureA!, contact.FixtureB!, frameTime, contact.Manifold);
KinematicControllerCollision?.Invoke(contact.FixtureA!, contact.FixtureB!, frameTime, -worldNormal);
}
else if (bodyB.BodyType == BodyType.KinematicController)
{
KinematicControllerCollision?.Invoke(contact.FixtureB!, contact.FixtureA!, frameTime, contact.Manifold);
KinematicControllerCollision?.Invoke(contact.FixtureB!, contact.FixtureA!, frameTime, worldNormal);
}
}
}
@@ -472,20 +462,18 @@ namespace Robust.Shared.Physics.Dynamics
{
public Fixture OurFixture { get; }
public Fixture OtherFixture { get; }
public Manifold Manifold { get; }
public CollideEvent(Fixture ourFixture, Fixture otherFixture, Manifold manifold)
public CollideEvent(Fixture ourFixture, Fixture otherFixture)
{
OurFixture = ourFixture;
OtherFixture = otherFixture;
Manifold = manifold;
}
}
public sealed class StartCollideEvent : CollideEvent
{
public StartCollideEvent(Fixture ourFixture, Fixture otherFixture, Manifold manifold)
: base(ourFixture, otherFixture, manifold)
: base(ourFixture, otherFixture)
{
}
}
@@ -493,7 +481,7 @@ namespace Robust.Shared.Physics.Dynamics
public sealed class EndCollideEvent : CollideEvent
{
public EndCollideEvent(Fixture ourFixture, Fixture otherFixture, Manifold manifold)
: base(ourFixture, otherFixture, manifold)
: base(ourFixture, otherFixture)
{
}
}

View File

@@ -67,6 +67,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
private ContactType _type;
// TODO: Jesus we should really have a test for this
/// <summary>
/// Ordering is under <see cref="ShapeType"/>
/// uses enum to work out which collision evaluation to use.
@@ -80,7 +81,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
ContactType.ChainAndCircle,
ContactType.AabbAndCircle,
ContactType.RectAndCircle,
ContactType.NotSupported,
},
{
// Edge register
@@ -90,7 +90,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
ContactType.NotSupported, // Chain
ContactType.NotSupported, // Aabb
ContactType.NotSupported, // Rect
ContactType.NotSupported,
},
{
// Polygon register
@@ -100,7 +99,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
ContactType.ChainAndPolygon,
ContactType.AabbAndPolygon,
ContactType.RectAndPolygon,
ContactType.NotSupported,
},
{
// Chain register
@@ -110,7 +108,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
ContactType.NotSupported, // Chain
ContactType.NotSupported, // Aabb - TODO Just cast to poly
ContactType.NotSupported, // Rect - TODO Just cast to poly
ContactType.NotSupported,
},
{
// Aabb register
@@ -120,7 +117,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
ContactType.NotSupported, // Chain - TODO Just cast to poly
ContactType.Aabb,
ContactType.AabbAndRect,
ContactType.NotSupported,
},
{
// Rectangle register
@@ -130,18 +126,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
ContactType.NotSupported, // Chain - TODO Just cast to poly
ContactType.AabbAndRect,
ContactType.Rect,
ContactType.NotSupported,
},
{
// Grid register
ContactType.NotSupported,
ContactType.NotSupported,
ContactType.NotSupported,
ContactType.NotSupported,
ContactType.NotSupported,
ContactType.NotSupported,
ContactType.Grids,
},
}
};
/// <summary>
@@ -494,9 +479,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
case ContactType.RectAndPolygon:
_collisionManager.CollideRectAndPolygon(ref manifold, (PhysShapeRect) FixtureA!.Shape, transformA, (PolygonShape) FixtureB!.Shape, transformB);
break;
case ContactType.Grids:
// TODO: Dis
throw new NotImplementedException();
default:
throw new ArgumentOutOfRangeException($"Collision between {FixtureA!.Shape.GetType()} and {FixtureB!.Shape.GetType()} not supported");
}
@@ -531,7 +513,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
Rect,
RectAndCircle,
RectAndPolygon,
Grids,
}
public bool Equals(Contact? other)

View File

@@ -21,9 +21,6 @@
*/
using System;
using System.IO;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Utility;
@@ -32,8 +29,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
{
internal sealed class ContactSolver
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
private bool _warmStarting;
private float _velocityThreshold;
private float _baumgarte;
@@ -52,24 +47,13 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
private ContactVelocityConstraint[] _velocityConstraints = Array.Empty<ContactVelocityConstraint>();
private ContactPositionConstraint[] _positionConstraints = Array.Empty<ContactPositionConstraint>();
public void Initialize()
public void LoadConfig(in IslandCfg cfg)
{
IoCManager.InjectDependencies(this);
_warmStarting = _configManager.GetCVar(CVars.WarmStarting);
_configManager.OnValueChanged(CVars.WarmStarting, value => _warmStarting = value);
_velocityThreshold = _configManager.GetCVar(CVars.VelocityThreshold);
_configManager.OnValueChanged(CVars.VelocityThreshold, value => _velocityThreshold = value);
_baumgarte = _configManager.GetCVar(CVars.Baumgarte);
_configManager.OnValueChanged(CVars.Baumgarte, value => _baumgarte = value);
_linearSlop = _configManager.GetCVar(CVars.LinearSlop);
_configManager.OnValueChanged(CVars.LinearSlop, value => _linearSlop = value);
_maxLinearCorrection = _configManager.GetCVar(CVars.MaxLinearCorrection);
_configManager.OnValueChanged(CVars.MaxLinearCorrection, value => _maxLinearCorrection = value);
_warmStarting = cfg.WarmStarting;
_velocityThreshold = cfg.VelocityThreshold;
_baumgarte = cfg.Baumgarte;
_linearSlop = cfg.LinearSlop;
_maxLinearCorrection = cfg.MaxLinearCorrection;
}
public void Reset(SolverData data, int contactCount, Contact[] contacts)

View File

@@ -427,9 +427,6 @@ namespace Robust.Shared.Physics.Dynamics
case PhysShapeCircle circle:
ComputeCircle(circle);
break;
case PhysShapeGrid grid:
ComputeGrid(grid);
break;
case PhysShapeRect rect:
ComputeRect(rect);
break;
@@ -580,23 +577,6 @@ namespace Robust.Shared.Physics.Dynamics
{
_centroid = (edge.Vertex1 + edge.Vertex2) * 0.5f;
}
private void ComputeGrid(PhysShapeGrid grid)
{
var area = grid.LocalBounds.Width * grid.LocalBounds.Height;
float I = 0.0f;
// Probably nothing bad happening if the area is 0?
// Total mass
var density = area > 0.0f ? Mass / area : 0.0f;
// Center of mass
_centroid = Vector2.Zero;
// Inertia tensor relative to the local origin (point s).
_inertia = density * I;
}
#endregion
// This is a crude equals mainly to avoid having to re-create the fixtures every time a state comes in.

View File

@@ -22,15 +22,12 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using System.Diagnostics;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Dynamics.Contacts;
using Robust.Shared.Physics.Dynamics.Joints;
using Robust.Shared.Utility;
namespace Robust.Shared.Physics.Dynamics
{
@@ -140,13 +137,13 @@ stored in a single array since multiple arrays lead to multiple misses.
*/
public sealed class PhysicsIsland
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
// if you add new deps to this, the IoCManager inject dependencies is behind #if too.
#if DEBUG
[Dependency] private readonly IEntityManager _entityManager = default!;
private List<IPhysBody> _debugBodies = new(8);
#endif
private ContactSolver _contactSolver = default!;
private readonly ContactSolver _contactSolver = new();
internal int ID { get; set; } = -1;
@@ -211,53 +208,25 @@ stored in a single array since multiple arrays lead to multiple misses.
/// </summary>
public int JointCount { get; private set; }
public void Initialize()
[Conditional("DEBUG")]
internal void Initialize()
{
IoCManager.InjectDependencies(this);
_contactSolver = new ContactSolver();
_contactSolver.Initialize();
}
// Values
_angTolSqr = MathF.Pow(_configManager.GetCVar(CVars.AngularSleepTolerance), 2);
_configManager.OnValueChanged(CVars.AngularSleepTolerance, value => _angTolSqr = MathF.Pow(value, 2));
internal void LoadConfig(in IslandCfg cfg)
{
_angTolSqr = cfg.AngTolSqr;
_linTolSqr = cfg.LinTolSqr;
_warmStarting = cfg.WarmStarting;
_velocityIterations = cfg.VelocityIterations;
_maxLinearVelocity = cfg.MaxLinearVelocity;
_maxAngularVelocity = cfg.MaxAngularVelocity;
_positionIterations = cfg.PositionIterations;
_sleepAllowed = cfg.SleepAllowed;
_timeToSleep = cfg.TimeToSleep;
_linTolSqr = MathF.Pow(_configManager.GetCVar(CVars.LinearSleepTolerance), 2);
_configManager.OnValueChanged(CVars.LinearSleepTolerance, value => _linTolSqr = MathF.Pow(value, 2));
_warmStarting = _configManager.GetCVar(CVars.WarmStarting);
_configManager.OnValueChanged(CVars.WarmStarting, value => _warmStarting = value);
_velocityIterations = _configManager.GetCVar(CVars.VelocityIterations);
_configManager.OnValueChanged(CVars.VelocityIterations, value => _velocityIterations = value);
_configManager.OnValueChanged(CVars.MaxLinVelocity, value => SetMaxLinearVelocity(value, null));
_configManager.OnValueChanged(CVars.NetTickrate, value => SetMaxLinearVelocity(null, value));
void SetMaxLinearVelocity(float? maxLinVelocity = null, int? tickrate = null)
{
maxLinVelocity ??= _configManager.GetCVar(CVars.MaxLinVelocity);
tickrate ??= _configManager.GetCVar(CVars.NetTickrate);
_maxLinearVelocity = (float)(maxLinVelocity / tickrate);
}
SetMaxLinearVelocity();
_configManager.OnValueChanged(CVars.MaxAngVelocity, value => SetMaxAngularVelocity(value, null));
_configManager.OnValueChanged(CVars.NetTickrate, value => SetMaxAngularVelocity(null, value));
void SetMaxAngularVelocity(float? maxAngVelocity = null, int? tickrate = null)
{
maxAngVelocity ??= _configManager.GetCVar(CVars.MaxAngVelocity);
tickrate ??= _configManager.GetCVar(CVars.NetTickrate);
_maxAngularVelocity = (float)((MathF.PI * 2 * maxAngVelocity) / tickrate);
}
SetMaxAngularVelocity();
_positionIterations = _configManager.GetCVar(CVars.PositionIterations);
_configManager.OnValueChanged(CVars.PositionIterations, value => _positionIterations = value);
_sleepAllowed = _configManager.GetCVar(CVars.SleepAllowed);
_configManager.OnValueChanged(CVars.SleepAllowed, value => _sleepAllowed = value);
_timeToSleep = _configManager.GetCVar(CVars.TimeToSleep);
_configManager.OnValueChanged(CVars.TimeToSleep, value => _timeToSleep = value);
_contactSolver.LoadConfig(cfg);
}
public void Append(List<IPhysBody> bodies, List<Contact> contacts, List<Joint> joints)
@@ -631,4 +600,24 @@ stored in a single array since multiple arrays lead to multiple misses.
public Vector2[] Positions { get; set; } = default!;
public float[] Angles { get; set; } = default!;
}
/// <summary>
/// Contains all configuration parameters that need to be passed to physics islands.
/// </summary>
internal struct IslandCfg
{
public float AngTolSqr;
public float LinTolSqr;
public bool SleepAllowed;
public bool WarmStarting;
public int VelocityIterations;
public float MaxLinearVelocity;
public float MaxAngularVelocity;
public int PositionIterations;
public float TimeToSleep;
public float VelocityThreshold;
public float Baumgarte;
public float LinearSlop;
public float MaxLinearCorrection;
}
}

View File

@@ -10,7 +10,11 @@ subject to the following restrictions:
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
using System;
using System.Collections.Generic;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Utility;
@@ -31,6 +35,8 @@ namespace Robust.Shared.Physics
/// </summary>
internal sealed class IslandManager : IIslandManager
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
private static readonly IslandBodyCapacitySort CapacitySort = new();
private static readonly IslandBodyCountSort CountSort = new();
@@ -67,13 +73,83 @@ namespace Robust.Shared.Physics
}
}
private int _tickRate;
private float _maxLinearVelocityRaw;
private float _maxAngularVelocityRaw;
private IslandCfg _islandCfg;
public void Initialize()
{
InitConfig();
_loneIsland.Initialize();
// Set an initial size so we don't spam a bunch of array resizes at the start
_loneIsland.Resize(64, 32, 8);
}
private void InitConfig()
{
// Config stuff.
CfgVar(CVars.AngularSleepTolerance, value => _islandCfg.AngTolSqr = value * value);
CfgVar(CVars.LinearSleepTolerance, value => _islandCfg.LinTolSqr = value * value);
CfgVar(CVars.WarmStarting, value => _islandCfg.WarmStarting = value);
CfgVar(CVars.VelocityIterations, value => _islandCfg.VelocityIterations = value);
CfgVar(CVars.PositionIterations, value => _islandCfg.PositionIterations = value);
CfgVar(CVars.SleepAllowed, value => _islandCfg.SleepAllowed = value);
CfgVar(CVars.TimeToSleep, value => _islandCfg.TimeToSleep = value);
CfgVar(CVars.VelocityThreshold, value => _islandCfg.VelocityThreshold = value);
CfgVar(CVars.Baumgarte, value => _islandCfg.Baumgarte = value);
CfgVar(CVars.LinearSlop, value => _islandCfg.LinearSlop = value);
CfgVar(CVars.MaxLinearCorrection, value => _islandCfg.MaxLinearCorrection = value);
CfgVar(CVars.MaxLinVelocity, value =>
{
_maxLinearVelocityRaw = value;
UpdateMaxLinearVelocity();
});
CfgVar(CVars.MaxAngVelocity, value =>
{
_maxAngularVelocityRaw = value;
UpdateMaxAngularVelocity();
});
CfgVar(CVars.NetTickrate, value =>
{
_tickRate = value;
UpdateMaxLinearVelocity();
UpdateMaxAngularVelocity();
});
void UpdateMaxLinearVelocity()
{
_islandCfg.MaxLinearVelocity = _maxLinearVelocityRaw / _tickRate;
}
void UpdateMaxAngularVelocity()
{
_islandCfg.MaxAngularVelocity = (MathF.PI * 2 * _maxAngularVelocityRaw) / _tickRate;
}
void CfgVar<T>(CVarDef<T> cVar, Action<T> callback) where T : notnull
{
_cfg.OnValueChanged(cVar, value =>
{
callback(value);
UpdateIslandCfg();
}, true);
}
}
private void UpdateIslandCfg()
{
// OOP bad
_loneIsland.LoadConfig(_islandCfg);
foreach (var island in _allocatedIslands)
{
island.LoadConfig(_islandCfg);
}
}
public void InitializePools()
{
_loneIsland.Clear();
@@ -159,6 +235,7 @@ namespace Robust.Shared.Physics
{
island = new PhysicsIsland();
island.Initialize();
island.LoadConfig(_islandCfg);
_allocatedIslands.Add(island);
}

View File

@@ -131,6 +131,8 @@ namespace Robust.Shared.Physics
public float C;
public float S;
public float Angle => MathF.Atan2(S, C);
public Quaternion2D(float cos, float sin)
{
C = cos;

View File

@@ -214,9 +214,9 @@ namespace Robust.Shared.Serialization
/// <seealso cref="OnClientCompleteHandshake"/>
private void NetworkInitialize()
{
_net.RegisterNetMessage<MsgMapStrServerHandshake>(HandleServerHandshake, NetMessageAccept.Client);
_net.RegisterNetMessage<MsgMapStrClientHandshake>(HandleClientHandshake, NetMessageAccept.Server);
_net.RegisterNetMessage<MsgMapStrStrings>(HandleStringsMessage, NetMessageAccept.Client);
_net.RegisterNetMessage<MsgMapStrServerHandshake>(HandleServerHandshake, NetMessageAccept.Client | NetMessageAccept.Handshake);
_net.RegisterNetMessage<MsgMapStrClientHandshake>(HandleClientHandshake, NetMessageAccept.Server | NetMessageAccept.Handshake);
_net.RegisterNetMessage<MsgMapStrStrings>(HandleStringsMessage, NetMessageAccept.Client | NetMessageAccept.Handshake);
_net.Disconnect += NetOnDisconnect;
}

View File

@@ -1,41 +0,0 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Robust.Shared.Utility
{
public static class ProcessExt
{
public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource<bool>();
void ProcessExited(object? sender, EventArgs e)
{
tcs.TrySetResult(true);
}
process.EnableRaisingEvents = true;
process.Exited += ProcessExited;
try
{
if (process.HasExited)
{
return;
}
using (cancellationToken.Register(() => tcs.TrySetCanceled()))
{
await tcs.Task;
}
}
finally
{
process.Exited -= ProcessExited;
}
}
}
}

View File

@@ -2,6 +2,7 @@ using NUnit.Framework;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.UnitTesting.Client.UserInterface.Controls
{
@@ -15,7 +16,11 @@ namespace Robust.UnitTesting.Client.UserInterface.Controls
public void TestLayoutBasic()
{
var root = new LayoutContainer();
var boxContainer = new VBoxContainer {MinSize = (50, 60)};
var boxContainer = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
MinSize = (50, 60)
};
var control1 = new Control {MinSize = (20, 20)};
var control2 = new Control {MinSize = (30, 30)};
@@ -37,7 +42,11 @@ namespace Robust.UnitTesting.Client.UserInterface.Controls
public void TestLayoutExpand()
{
var root = new LayoutContainer();
var boxContainer = new VBoxContainer {MinSize = (50, 60)};
var boxContainer = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
MinSize = (50, 60)
};
var control1 = new Control
{
VerticalExpand = true
@@ -61,7 +70,10 @@ namespace Robust.UnitTesting.Client.UserInterface.Controls
[Test]
public void TestCalcMinSize()
{
var boxContainer = new VBoxContainer();
var boxContainer = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
var control1 = new Control
{
MinSize = (50, 30)
@@ -80,7 +92,11 @@ namespace Robust.UnitTesting.Client.UserInterface.Controls
public void TestTwoExpand()
{
var root = new LayoutContainer();
var boxContainer = new VBoxContainer {MinSize = (30, 80)};
var boxContainer = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
MinSize = (30, 80)
};
var control1 = new Control
{
VerticalExpand = true,

View File

@@ -387,6 +387,8 @@ namespace Robust.UnitTesting
public bool IsConnected { get; set; }
public NetUserData UserData { get; }
// integration tests don't simulate serializer handshake so this is always true.
public bool IsHandshakeComplete => true;
// TODO: Should this port value make sense?
public IPEndPoint RemoteEndPoint { get; } = new(IPAddress.Loopback, 1212);

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