Compare commits

...

23 Commits

Author SHA1 Message Date
Pieter-Jan Briers
31c1feca4e Debug console history improvements.
No longer blows up if history cannot be read/written thanks to file locking.

Made it more async so it won't waste main thread init time.
2021-03-09 22:28:58 +01:00
Pieter-Jan Briers
3ed1eef2ab Fix build. 2021-03-09 21:44:46 +01:00
Pieter-Jan Briers
1394a017bb Fix IL verification throwing if a verifier error does not need to be formatted. 2021-03-09 21:41:50 +01:00
metalgearsloth
6b0670d5f1 Break joints on container insertion; semi-related to break pulling on container insertion (#1620)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-09 11:51:57 -08:00
metalgearsloth
f573331541 Fix physics joint disconnect spam (#1619)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-09 11:51:35 -08:00
metalgearsloth
a7218cd3b8 Make Visible a Shared Property for sprites (#1615)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-09 11:51:16 -08:00
Acruid
f7e8178736 Added new ComponentEvents system in IEventBus. (#1601)
* Added new ComponentEventBus, combined it with IEventBus.

* Removed all traces of IEntity from ComponentDependencies.
Removed IEntityManager dependency from ComponentManager.

* Added entity create/delete events to IEntityManager.

* ComponentEvents now use EntitySystemMessages instead of their custom ComponentEvent class.

* Component events are now just overloads of entity events.

* Removed obsolete EntitySystemMessage, now everything uses the base EntityEventArgs.

* Add a bool argument for if the message should be broadcast as well as directed.
Fix ordering and init issues of events in EntityManager.

* Changed names from Component/Entity events to Directed/Broadcast.

* Fix bugs and unit tests.
2021-03-09 11:02:24 -08:00
Pieter-Jan Briers
31f921e4aa Use ProfileOptimization to speed up startup. 2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
aa1c25637c Allow disabling nvidia optimus via env var. 2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
71f2c48463 Call GC.Collect after game init.
Cleans up any gen 2 garbage from init and the stutter shouldn't be the end of the world.
2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
d65f4ca898 RSI & texture preloading.
All RSIs and textures are now loaded ahead of time in client startup. This is well threaded and is extremely fast.
2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
b35568ffe5 Disable path case checks by default.
The idea was that these are Task.Run'd so don't influence performance. That was before we started threading the hell out of startup.

We're getting more stuff like YAML linting now which should hopefully be able to catch 99% of this. And louder because it was always just a warning before.
2021-03-09 12:29:59 +01:00
Acruid
a0d241e551 Removes some things that should not have been in the last PR. 2021-03-09 02:06:13 -08:00
GraniteSidewalk
33a6934582 Large number of changes to Shaders and Overlays (#1387)
* AAAAAAAAAAAAA

* Organization

* Still doesnt work

* Formatting

* It works!!

* More changes to everything

* Beginning of changes to overlays

* Makes the overlay manager GUID based (also it was very messy, still messy but i fixed some of it)

* Stencils are easy

* Questionable changes to overlays

* Minor change to HLR

* Fixed duplicate overlays when calling some commands (Like showbb)

* Fixes misleading message

* Adds a variety of worldspaces for overlays to choose from

* Caching

* Address reviews

* Merging pains

* ah.

* ahhhhh

* minor overlaymanager changes

* Work

* fix

* Merge??

* Fixes null errors

* Force update

* Delete whatever the fuck this is?

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-03-09 01:52:16 -08:00
metalgearsloth
f237a8bbbc Optimise static body sleeping (#1618)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-09 19:47:51 +11:00
Pieter-Jan Briers
4bc775c01c RSI loader improvements:
1. Stop using NJsonSchema, it didn't do anything useful.
2. Use System.Text.Json instead of Newtonsoft.Json.
3. General cleanup of the code, using arrays instead of lists, etc...
2021-03-08 11:18:19 +01:00
Pieter-Jan Briers
93b4d81505 Optimize ImageSharp blitting. 2021-03-08 11:15:33 +01:00
Pieter-Jan Briers
0afb85a09e Fix some missing re-pooling of ImageSharp images. 2021-03-08 09:45:22 +01:00
Metal Gear Sloth
7b9315cea4 Significantly lower physics speedcap 2021-03-08 15:46:50 +11:00
Metal Gear Sloth
dc3af45096 Fix anchored message 2021-03-08 15:00:08 +11:00
metalgearsloth
00ce0179ae Allow kinematic controllers to have an impulse applied (#1612)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-08 12:10:20 +11:00
ShadowCommander
81947ba3d8 Fix buckling (#1611) 2021-03-07 15:25:11 -08:00
DrSmugleaf
49327279d0 Fix nullability errors in physics ContactHead code (#1609) 2021-03-07 23:15:55 +01:00
115 changed files with 1743 additions and 769 deletions

View File

@@ -1,8 +1,9 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
@@ -41,14 +42,14 @@ namespace Robust.Client.Debugging
_debugColliders = value;
if (value)
if (value && !_overlayManager.HasOverlay<PhysicsOverlay>())
{
_overlayManager.AddOverlay(new PhysicsOverlay(_componentManager, _eyeManager,
_prototypeManager, _inputManager, _physicsManager));
}
else
{
_overlayManager.RemoveOverlay(nameof(PhysicsOverlay));
_overlayManager.RemoveOverlay<PhysicsOverlay>();
}
}
}
@@ -66,13 +67,13 @@ namespace Robust.Client.Debugging
_debugPositions = value;
if (value)
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
{
_overlayManager.AddOverlay(new EntityPositionOverlay(_entityManager, _eyeManager));
}
else
{
_overlayManager.RemoveOverlay(nameof(EntityPositionOverlay));
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
}
}
}
@@ -91,8 +92,8 @@ namespace Robust.Client.Debugging
private Vector2 _hoverStartScreen = Vector2.Zero;
private List<IPhysBody> _hoverBodies = new();
public PhysicsOverlay(IComponentManager compMan, IEyeManager eyeMan, IPrototypeManager protoMan, IInputManager inputManager, IPhysicsManager physicsManager)
: base(nameof(PhysicsOverlay))
{
_componentManager = compMan;
_eyeManager = eyeMan;
@@ -265,7 +266,7 @@ namespace Robust.Client.Debugging
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager) : base(nameof(EntityPositionOverlay))
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager)
{
_entityManager = entityManager;
_eyeManager = eyeManager;

View File

@@ -1,10 +1,11 @@
using Robust.Shared.IoC;
using Robust.Shared.IoC;
using Robust.Shared.Network.Messages;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using Robust.Shared.Network;
namespace Robust.Client.Debugging
@@ -38,13 +39,13 @@ namespace Robust.Client.Debugging
_debugDrawRays = value;
if (value)
if (value && !_overlayManager.HasOverlay<DebugDrawRayOverlay>())
{
_overlayManager.AddOverlay(new DebugDrawRayOverlay(this));
}
else
{
_overlayManager.RemoveOverlay(nameof(DebugDrawRayOverlay));
_overlayManager.RemoveOverlay<DebugDrawRayOverlay>();
}
}
}
@@ -81,7 +82,7 @@ namespace Robust.Client.Debugging
private readonly DebugDrawingManager _owner;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugDrawRayOverlay(DebugDrawingManager owner) : base(nameof(DebugDrawRayOverlay))
public DebugDrawRayOverlay(DebugDrawingManager owner)
{
_owner = owner;
}

View File

@@ -24,6 +24,7 @@
using System;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -55,7 +56,7 @@ namespace Robust.Client.Debugging
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsDebugOverlay(this));
if (value == PhysicsDebugFlags.None)
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(nameof(PhysicsDebugOverlay));
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsDebugOverlay));
_flags = value;
}
@@ -119,7 +120,7 @@ namespace Robust.Client.Debugging
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PhysicsDebugOverlay(DebugPhysicsSystem system) : base(nameof(PhysicsDebugOverlay))
public PhysicsDebugOverlay(DebugPhysicsSystem system)
{
_physics = system;
}

View File

@@ -119,6 +119,8 @@ namespace Robust.Client
_configurationManager.OverrideConVars(_commandLineArgs.CVars);
}
ProfileOptSetup.Setup(_configurationManager);
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
ProgramShared.DoMounts(_resourceCache, _commandLineArgs?.MountOptions, "Content.Client", _loaderArgs != null);
@@ -161,6 +163,7 @@ namespace Robust.Client
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
_resourceCache.PreloadTextures();
_userInterfaceManager.Initialize();
_networkManager.Initialize(false);
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
@@ -188,6 +191,8 @@ namespace Robust.Client
_authManager.LoadFromEnv();
GC.Collect();
_clyde.Ready();
if ((_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)

View File

@@ -45,12 +45,12 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message)
public void SendSystemNetworkMessage(EntityEventArgs message)
{
SendSystemNetworkMessage(message, default(uint));
}
public void SendSystemNetworkMessage(EntitySystemMessage message, uint sequence)
public void SendSystemNetworkMessage(EntityEventArgs message, uint sequence)
{
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.SystemMessage;
@@ -62,7 +62,7 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message, INetChannel channel)
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel channel)
{
throw new NotSupportedException();
}

View File

@@ -36,7 +36,7 @@ namespace Robust.Client.GameObjects
private bool _visible = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool Visible
public override bool Visible
{
get => _visible;
set => _visible = value;

View File

@@ -9,7 +9,7 @@ namespace Robust.Client.GameObjects
public static class EntityManagerExt
{
public static void RaisePredictiveEvent<T>(this IEntityManager entityManager, T msg)
where T : EntitySystemMessage
where T : EntityEventArgs
{
var localPlayer = IoCManager.Resolve<IPlayerManager>().LocalPlayer;
DebugTools.AssertNotNull(localPlayer);

View File

@@ -106,7 +106,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Event raised by a <see cref="ClientOccluderComponent"/> when it needs to be recalculated.
/// </summary>
internal sealed class OccluderDirtyEvent : EntitySystemMessage
internal sealed class OccluderDirtyEvent : EntityEventArgs
{
public OccluderDirtyEvent(IEntity sender, (GridId grid, Vector2i pos)? lastPosition, SnapGridOffset offset)
{

View File

@@ -11,6 +11,7 @@ using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Enums;
namespace Robust.Client.GameObjects
{
@@ -42,7 +43,7 @@ namespace Robust.Client.GameObjects
{
base.Shutdown();
overlayManager.RemoveOverlay("EffectSystem");
overlayManager.RemoveOverlay(typeof(EffectOverlay));
}
public void CreateEffect(EffectSystemMessage message)
@@ -329,7 +330,6 @@ namespace Robust.Client.GameObjects
{
private readonly IPlayerManager _playerManager;
public override bool AlwaysDirty => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _unshadedShader;
@@ -337,8 +337,7 @@ namespace Robust.Client.GameObjects
private readonly IMapManager _mapManager;
private readonly IEntityManager _entityManager;
public EffectOverlay(EffectSystem owner, IPrototypeManager protoMan, IMapManager mapMan, IPlayerManager playerMan, IEntityManager entityManager) : base(
"EffectSystem")
public EffectOverlay(EffectSystem owner, IPrototypeManager protoMan, IMapManager mapMan, IPlayerManager playerMan, IEntityManager entityManager)
{
_owner = owner;
_unshadedShader = protoMan.Index<ShaderPrototype>("unshaded").Instance();

View File

@@ -157,7 +157,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Entity system message that is raised when the player changes attached entities.
/// </summary>
public class PlayerAttachSysMessage : EntitySystemMessage
public class PlayerAttachSysMessage : EntityEventArgs
{
/// <summary>
/// New entity the player is attached to.

View File

@@ -27,7 +27,7 @@ namespace Robust.Client.GameStates
private uint _nextInputCmdSeq = 1;
private readonly Queue<FullInputCmdMessage> _pendingInputs = new();
private readonly Queue<(uint sequence, GameTick sourceTick, EntitySystemMessage msg, object sessionMsg)>
private readonly Queue<(uint sequence, GameTick sourceTick, EntityEventArgs msg, object sessionMsg)>
_pendingSystemMessages
= new();
@@ -126,7 +126,7 @@ namespace Robust.Client.GameStates
_nextInputCmdSeq++;
}
public uint SystemMessageDispatched<T>(T message) where T : EntitySystemMessage
public uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs
{
if (!Predicting)
{
@@ -298,7 +298,7 @@ namespace Robust.Client.GameStates
// because the rest of the main loop will call into it with the target tick later,
// and it won't be a past prediction.
_entitySystemManager.Update((float) _timing.TickPeriod.TotalSeconds);
((IEntityEventBus) _entities.EventBus).ProcessEventQueue();
((IBroadcastEventBusInternal) _entities.EventBus).ProcessEventQueue();
}
}
}

View File

@@ -70,6 +70,6 @@ namespace Robust.Client.GameStates
/// <param name="message">Message being dispatched.</param>
void InputCommandDispatched(FullInputCmdMessage message);
uint SystemMessageDispatched<T>(T message) where T : EntitySystemMessage;
uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs;
}
}

View File

@@ -3,6 +3,7 @@ using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -33,7 +34,7 @@ namespace Robust.Client.GameStates
private readonly int _lineHeight;
private readonly List<NetEntity> _netEnts = new();
public NetEntityOverlay() : base(nameof(NetEntityOverlay))
public NetEntityOverlay()
{
IoCManager.InjectDependencies(this);
var cache = IoCManager.Resolve<IResourceCache>();
@@ -179,11 +180,10 @@ namespace Robust.Client.GameStates
return Color.Green; // Entity in PVS, but not updated recently.
}
protected override void Dispose(bool disposing)
protected override void DisposeBehavior()
{
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
base.Dispose(disposing);
base.DisposeBehavior();
}
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str, Color textColor)
@@ -238,14 +238,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if(bValue && !overlayMan.HasOverlay(nameof(NetEntityOverlay)))
if(bValue && !overlayMan.HasOverlay(typeof(NetEntityOverlay)))
{
overlayMan.AddOverlay(new NetEntityOverlay());
shell.WriteLine("Enabled network entity report overlay.");
}
else if(!bValue && overlayMan.HasOverlay(nameof(NetEntityOverlay)))
else if(!bValue && overlayMan.HasOverlay(typeof(NetEntityOverlay)))
{
overlayMan.RemoveOverlay(nameof(NetEntityOverlay));
overlayMan.RemoveOverlay(typeof(NetEntityOverlay));
shell.WriteLine("Disabled network entity report overlay.");
}
}

View File

@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -34,7 +36,7 @@ namespace Robust.Client.GameStates
private readonly List<(GameTick Tick, int Payload, int lag, int interp)> _history = new(HistorySize+10);
public NetGraphOverlay() : base(nameof(NetGraphOverlay))
public NetGraphOverlay()
{
IoCManager.InjectDependencies(this);
var cache = IoCManager.Resolve<IResourceCache>();
@@ -142,11 +144,11 @@ namespace Robust.Client.GameStates
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
}
protected override void Dispose(bool disposing)
protected override void DisposeBehavior()
{
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
base.Dispose(disposing);
base.DisposeBehavior();
}
private void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
@@ -183,14 +185,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if(bValue && !overlayMan.HasOverlay(nameof(NetGraphOverlay)))
if(bValue && !overlayMan.HasOverlay(typeof(NetGraphOverlay)))
{
overlayMan.AddOverlay(new NetGraphOverlay());
shell.WriteLine("Enabled network overlay.");
}
else if(overlayMan.HasOverlay(nameof(NetGraphOverlay)))
else if(overlayMan.HasOverlay(typeof(NetGraphOverlay)))
{
overlayMan.RemoveOverlay(nameof(NetGraphOverlay));
overlayMan.RemoveOverlay(typeof(NetGraphOverlay));
shell.WriteLine("Disabled network overlay.");
}
}

View File

@@ -1,3 +1,4 @@
using Robust.Shared.Enums;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
@@ -5,6 +6,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using System;
using Robust.Shared.Timing;
namespace Robust.Client.GameStates
@@ -18,7 +20,7 @@ namespace Robust.Client.GameStates
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _shader;
public NetInterpOverlay() : base(nameof(NetInterpOverlay))
public NetInterpOverlay()
{
IoCManager.InjectDependencies(this);
_shader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
@@ -85,14 +87,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if (bValue && !overlayMan.HasOverlay(nameof(NetInterpOverlay)))
if (bValue && !overlayMan.HasOverlay<NetInterpOverlay>())
{
overlayMan.AddOverlay(new NetInterpOverlay());
shell.WriteLine("Enabled network interp overlay.");
}
else if (overlayMan.HasOverlay(nameof(NetInterpOverlay)))
else if (overlayMan.HasOverlay<NetInterpOverlay>())
{
overlayMan.RemoveOverlay(nameof(NetInterpOverlay));
overlayMan.RemoveOverlay<NetInterpOverlay>();
shell.WriteLine("Disabled network interp overlay.");
}
}

View File

@@ -1,4 +1,4 @@
using JetBrains.Annotations;
using JetBrains.Annotations;
using Robust.Shared.Maths;
using Robust.Shared.Utility;

View File

@@ -12,7 +12,8 @@ namespace Robust.Client.Graphics.Clyde
static Clyde()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
RuntimeInformation.ProcessArchitecture == Architecture.X64)
RuntimeInformation.ProcessArchitecture == Architecture.X64 &&
Environment.GetEnvironmentVariable("ROBUST_INTEGRATED_GPU") != "1")
{
try
{

View File

@@ -7,6 +7,8 @@ using Robust.Client.ResourceManagement;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Enums;
namespace Robust.Client.Graphics.Clyde
{
@@ -68,8 +70,7 @@ namespace Robust.Client.Graphics.Clyde
RenderOverlays(OverlaySpace.ScreenSpaceBelowWorld);
_mainViewport.Eye = _eyeManager.CurrentEye;
RenderViewport(_mainViewport);
RenderViewport(_mainViewport); //Worldspace overlays are rendered here.
{
var handle = _renderHandle.DrawingHandleScreen;
var tex = _mainViewport.RenderTarget.Texture;
@@ -107,25 +108,67 @@ namespace Robust.Client.Graphics.Clyde
list.Add(overlay);
}
}
list.Sort(OverlayComparer.Instance);
foreach (var overlay in list)
{
overlay.ClydeRender(_renderHandle, space);
}
FlushRenderQueue();
list.Sort(OverlayComparer.Instance);
foreach (var overlay in list) {
if (overlay.RequestScreenTexture) {
FlushRenderQueue();
UpdateOverlayScreenTexture(space, _mainViewport.RenderTarget);
}
if (overlay.OverwriteTargetFrameBuffer()) {
ClearFramebuffer(default);
}
overlay.ClydeRender(_renderHandle, space);
FlushRenderQueue();
}
}
}
private void DrawEntitiesAndWorldOverlay(Viewport viewport, Box2 worldBounds)
private ClydeTexture? ScreenBufferTexture;
private GLHandle screenBufferHandle;
private Vector2 lastFrameSize;
/// <summary>
/// Sends SCREEN_TEXTURE to all overlays in the given OverlaySpace that request it.
/// </summary>
private bool UpdateOverlayScreenTexture(OverlaySpace space, RenderTexture texture) {
//This currently does NOT consider viewports and just grabs the current screen framebuffer. This will need to be improved upon in the future.
List<Overlay> oTargets = new List<Overlay>();
foreach (var overlay in _overlayManager.AllOverlays) {
if (overlay.RequestScreenTexture && overlay.Space == space) {
oTargets.Add(overlay);
}
}
if (oTargets.Count > 0 && ScreenBufferTexture != null) {
if (lastFrameSize != _framebufferSize) {
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Srgb8Alpha8, _framebufferSize.X, _framebufferSize.Y, 0,
PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
}
lastFrameSize = _framebufferSize;
CopyRenderTextureToTexture(texture, ScreenBufferTexture);
foreach (Overlay overlay in oTargets) {
overlay.ScreenTexture = ScreenBufferTexture;
}
oTargets.Clear();
return true;
}
return false;
}
private void DrawEntities(Viewport viewport, Box2 worldBounds)
{
if (_eyeManager.CurrentMap == MapId.Nullspace || !_mapManager.HasMapEntity(_eyeManager.CurrentMap))
{
return;
}
RenderOverlays(OverlaySpace.WorldSpaceBelowEntities);
var screenSize = viewport.Size;
// So we could calculate the correct size of the entities based on the contents of their sprite...
@@ -267,14 +310,6 @@ namespace Robust.Client.Graphics.Clyde
_drawingSpriteList.Clear();
FlushRenderQueue();
// Cleanup remainders
foreach (var overlay in worldOverlays)
{
overlay.ClydeRender(_renderHandle, OverlaySpace.WorldSpace);
}
FlushRenderQueue();
}
[MethodImpl(MethodImplOptions.NoInlining)]
@@ -368,12 +403,21 @@ namespace Robust.Client.Graphics.Clyde
// We will also render worldspace overlays here so we can do them under / above entities as necessary
using (DebugGroup("Entities"))
{
DrawEntitiesAndWorldOverlay(viewport, worldBounds);
DrawEntities(viewport, worldBounds);
}
RenderOverlays(OverlaySpace.WorldSpaceBelowFOV);
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawFov)
{
GL.Clear(ClearBufferMask.StencilBufferBit);
GL.Enable(EnableCap.StencilTest);
GL.StencilOp(OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Replace);
GL.StencilFunc(StencilFunction.Always, 1, 0xFF);
GL.StencilMask(0xFF);
ApplyFovToBuffer(viewport, eye);
GL.StencilMask(0x00);
GL.Disable(EnableCap.StencilTest);
}
}
@@ -401,6 +445,14 @@ namespace Robust.Client.Graphics.Clyde
viewport.WallBleedIntermediateRenderTarget2.Texture,
UIBox2.FromDimensions(Vector2.Zero, ScreenSize), new Color(1, 1, 1, 0.5f));
}
RenderOverlays(OverlaySpace.WorldSpace);
GL.StencilFunc(StencilFunction.Notequal, 1, 0xFF);
GL.Disable(EnableCap.DepthTest);
RenderOverlays(OverlaySpace.WorldSpaceFOVStencil);
GL.Disable(EnableCap.StencilTest);
}
PopRenderStateFull(state);

View File

@@ -5,6 +5,7 @@ using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp.PixelFormats;
using ES20 = OpenToolkit.Graphics.ES20;
namespace Robust.Client.Graphics.Clyde
@@ -31,6 +32,27 @@ namespace Robust.Client.Graphics.Clyde
CheckGlError();
GL.BindTexture(TextureTarget.Texture2D, glHandle.Handle);
CheckGlError();
GL.ActiveTexture(TextureUnit.Texture0);
}
private void CopyRenderTextureToTexture(RenderTexture source, ClydeTexture target) {
LoadedRenderTarget sourceLoaded = RtToLoaded(source);
bool pause = sourceLoaded != _currentBoundRenderTarget;
FullStoredRendererState? store = null;
if (pause) {
store = PushRenderStateFull();
BindRenderTargetFull(source);
CheckGlError();
}
GL.BindTexture(TextureTarget.Texture2D, _loadedTextures[target.TextureId].OpenGLObject.Handle);
CheckGlError();
GL.CopyTexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 0, 0, _framebufferSize.X, _framebufferSize.Y);
CheckGlError();
if (pause && store != null) {
PopRenderStateFull((FullStoredRendererState)store);
}
}
private static long EstPixelSize(PixelInternalFormat format)

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

View File

@@ -280,7 +280,7 @@ namespace Robust.Client.Graphics.Clyde
}
/// <summary>
/// Flush the render handle, processing and re-pooling all the command lists.
/// Flushes the render handle, processing and re-pooling all the command lists.
/// </summary>
private void FlushRenderQueue()
{
@@ -371,6 +371,7 @@ namespace Robust.Client.Graphics.Clyde
program.Use();
int textureUnitVal = 0;
// Assign shader parameters to uniform since they may be dirty.
foreach (var (name, value) in instance.Parameters)
{
@@ -413,6 +414,15 @@ namespace Robust.Client.Graphics.Clyde
case Matrix4 matrix4:
program.SetUniform(name, matrix4);
break;
case ClydeTexture clydeTexture:
//It's important to start at Texture6 here since DrawCommandBatch uses Texture0 and Texture1 immediately after calling this
//function! If passing in textures as uniforms ever stops working it might be since someone made it use all the way up to Texture6 too.
//Might change this in the future?
TextureUnit cTarget = TextureUnit.Texture6+textureUnitVal;
SetTexture(cTarget, ((ClydeTexture)clydeTexture).TextureId);
program.SetUniformTexture(name, cTarget);
textureUnitVal++;
break;
default:
throw new InvalidOperationException($"Unable to handle shader parameter {name}: {value}");
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@@ -428,7 +428,8 @@ namespace Robust.Client.Graphics.Clyde
private protected override void SetParameterImpl(string name, Texture value)
{
throw new NotImplementedException();
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetStencilOpImpl(StencilOp op)

View File

@@ -56,19 +56,19 @@ namespace Robust.Client.Graphics.Clyde
{
// Disable sRGB so stuff doesn't get interpreter wrong.
actualParams.Srgb = false;
var img = ApplyA8Swizzle((Image<A8>) (object) image);
using var img = ApplyA8Swizzle((Image<A8>) (object) image);
return LoadTextureFromImage(img, name, loadParams);
}
if (pixelType == typeof(L8) && !actualParams.Srgb)
{
var img = ApplyL8Swizzle((Image<L8>) (object) image);
using var img = ApplyL8Swizzle((Image<L8>) (object) image);
return LoadTextureFromImage(img, name, loadParams);
}
}
// Flip image because OpenGL reads images upside down.
var copy = FlipClone(image);
using var copy = FlipClone(image);
var texture = CreateBaseTextureInternal<T>(image.Width, image.Height, actualParams, name);

View File

@@ -315,6 +315,11 @@ namespace Robust.Client.Graphics.Clyde
UniformConstantsUBO = new GLUniformBuffer<UniformConstants>(this, BindingIndexUniformConstants, nameof(UniformConstantsUBO));
CreateMainViewport();
screenBufferHandle = new GLHandle(GL.GenTexture());
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
ApplySampleParameters(TextureSampleParameters.Default);
ScreenBufferTexture = GenTexture(screenBufferHandle, _framebufferSize, true, null, TexturePixelType.Rgba32);
}
private void CreateMainViewport()

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using OpenToolkit.Graphics.OpenGL4;

View File

@@ -1,22 +1,29 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Robust.Shared.Timing;
namespace Robust.Client.Graphics
{
[PublicAPI]
public interface IOverlayManager
{
void AddOverlay(Overlay overlay);
void RemoveOverlay(string id);
bool HasOverlay(string id);
bool AddOverlay(Overlay overlay);
Overlay GetOverlay(string id);
T GetOverlay<T>(string id) where T : Overlay;
bool RemoveOverlay(Overlay overlay);
bool RemoveOverlay(Type overlayClass);
bool RemoveOverlay<T>() where T : Overlay;
bool TryGetOverlay(string id, [NotNullWhen(true)] out Overlay? overlay);
bool TryGetOverlay<T>(string id, [NotNullWhen(true)] out T? overlay) where T : Overlay;
bool TryGetOverlay(Type overlayClass, out Overlay? overlay);
bool TryGetOverlay<T>(out T? overlay) where T : Overlay;
Overlay GetOverlay(Type overlayClass);
T GetOverlay<T>() where T : Overlay;
bool HasOverlay(Type overlayClass);
bool HasOverlay<T>() where T : Overlay;
IEnumerable<Overlay> AllOverlays { get; }
}

View File

@@ -1,65 +1,54 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.IoC;
using JetBrains.Annotations;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using System;
namespace Robust.Client.Graphics
{
/// <summary>
/// An overlay is used for fullscreen drawing in the game, for example parallax.
/// An overlay is used for fullscreen drawing in the game. This can range from drawing parallax to a full screen shader.
/// </summary>
[PublicAPI]
public abstract class Overlay
{
/// <summary>
/// The ID of this overlay. This is used to identify it inside the <see cref="IOverlayManager"/>.
/// Determines when this overlay is drawn in the rendering queue.
/// </summary>
public string ID { get; }
public virtual bool AlwaysDirty => false;
public bool IsDirty => AlwaysDirty || _isDirty;
public bool Drawing { get; private set; }
public virtual OverlaySpace Space => OverlaySpace.ScreenSpace;
/// <summary>
/// If set to true, <see cref="ScreenTexture"/> will be set to the current frame (at the moment before the overlay is rendered). This can be costly to performance, but
/// some shaders will require it as a passed in uniform to operate.
/// </summary>
public virtual bool RequestScreenTexture => false;
/// <summary>
/// If <see cref="RequestScreenTexture"> is true, then this will be set to the texture corresponding to the current frame. If false, it will always be null.
/// </summary>
public Texture? ScreenTexture = null;
/// <summary>
/// Overlays on the same OverlaySpace will be drawn from lowest ZIndex to highest ZIndex. As an example, ZIndex -1 will be drawn before ZIndex 2.
/// This value is 0 by default. Overlays with same ZIndex will be drawn in an random order.
/// </summary>
public int? ZIndex { get; set; }
protected IOverlayManager OverlayManager { get; }
public int? ZIndex { get; set; }
private bool Disposed = false;
public virtual bool SubHandlesUseMainShader { get; } = true;
private bool _isDirty = true;
private readonly List<DrawingHandleBase> TempHandles = new();
private bool Disposed;
protected Overlay(string id)
public Overlay()
{
OverlayManager = IoCManager.Resolve<IOverlayManager>();
ID = id;
}
public void Dispose()
{
if (Disposed)
{
return;
}
Dispose(true);
Disposed = true;
GC.SuppressFinalize(this);
}
~Overlay()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
/// <summary>
/// If this function returns true, the target framebuffer will be wiped before applying this overlay to it.
/// </summary>
public virtual bool OverwriteTargetFrameBuffer(){
return false;
}
/// <summary>
@@ -69,50 +58,34 @@ namespace Robust.Client.Graphics
/// <param name="currentSpace">Current space that is being drawn. This function is called for every space that was set up in initialization.</param>
protected abstract void Draw(DrawingHandleBase handle, OverlaySpace currentSpace);
public void Dirty()
{
_isDirty = true;
protected internal virtual void FrameUpdate(FrameEventArgs args) { }
~Overlay() {
Dispose();
}
public void Dispose() {
if (Disposed)
return;
else
DisposeBehavior();
}
protected virtual void DisposeBehavior(){
Disposed = true;
GC.SuppressFinalize(this);
}
protected internal virtual void FrameUpdate(FrameEventArgs args) { }
internal void ClydeRender(IRenderHandle renderHandle, OverlaySpace currentSpace)
{
DrawingHandleBase handle;
if (currentSpace == OverlaySpace.WorldSpace)
handle = renderHandle.DrawingHandleWorld;
else
if (currentSpace == OverlaySpace.ScreenSpace || currentSpace == OverlaySpace.ScreenSpaceBelowWorld)
handle = renderHandle.DrawingHandleScreen;
else
handle = renderHandle.DrawingHandleWorld;
Draw(handle, currentSpace);
}
}
/// <summary>
/// Determines in which canvas layers an overlay gets drawn.
/// </summary>
[Flags]
public enum OverlaySpace : byte
{
/// <summary>
/// Used for matching bit flags.
/// </summary>
None = 0b0000,
/// <summary>
/// This overlay will be drawn in the UI root, thus being in screen space.
/// </summary>
ScreenSpace = 0b0001,
/// <summary>
/// This overlay will be drawn in the world root, thus being in world space.
/// </summary>
WorldSpace = 0b0010,
/// <summary>
/// Drawn in screen coordinates, but behind the world.
/// </summary>
ScreenSpaceBelowWorld = 0b0100,
}
}

View File

@@ -1,13 +1,15 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Log;
using Robust.Shared.Timing;
namespace Robust.Client.Graphics
{
internal class OverlayManager : IOverlayManagerInternal
{
private readonly Dictionary<string, Overlay> _overlays = new();
private readonly Dictionary<Type, Overlay> _overlays = new Dictionary<Type, Overlay>();
public IEnumerable<Overlay> AllOverlays => _overlays.Values;
public void FrameUpdate(FrameEventArgs args)
{
@@ -17,59 +19,80 @@ namespace Robust.Client.Graphics
}
}
public void AddOverlay(Overlay overlay)
public bool AddOverlay(Overlay overlay)
{
if (_overlays.ContainsKey(overlay.ID))
{
throw new InvalidOperationException($"We already have an overlay with ID '{overlay.ID}'");
if(_overlays.ContainsKey(overlay.GetType()))
return false;
_overlays.Add(overlay.GetType(), overlay);
return true;
}
public bool RemoveOverlay(Type overlayClass)
{
if(!overlayClass.IsSubclassOf(typeof(Overlay))){
Logger.Error("RemoveOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return false;
}
_overlays.Add(overlay.ID, overlay);
return _overlays.Remove(overlayClass);
}
public Overlay GetOverlay(string id)
{
return _overlays[id];
public bool RemoveOverlay<T>() where T : Overlay{
return RemoveOverlay(typeof(T));
}
public T GetOverlay<T>(string id) where T : Overlay
{
return (T) GetOverlay(id);
public bool RemoveOverlay(Overlay overlay) {
return _overlays.Remove(overlay.GetType());
}
public bool HasOverlay(string id)
{
return _overlays.ContainsKey(id);
}
public void RemoveOverlay(string id)
public bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay)
{
if (!_overlays.TryGetValue(id, out var overlay))
{
return;
overlay = null;
if (!overlayClass.IsSubclassOf(typeof(Overlay))){
Logger.Error("TryGetOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return false;
}
overlay.Dispose();
_overlays.Remove(id);
return _overlays.TryGetValue(overlayClass, out overlay);
}
public bool TryGetOverlay(string id, [NotNullWhen(true)] out Overlay? overlay)
{
return _overlays.TryGetValue(id, out overlay);
}
public bool TryGetOverlay<T>(string id, [NotNullWhen(true)] out T? overlay) where T : Overlay
{
if (_overlays.TryGetValue(id, out var value))
{
overlay = (T) value;
public bool TryGetOverlay<T>([NotNullWhen(true)] out T? overlay) where T : Overlay {
overlay = null;
if(_overlays.TryGetValue(typeof(T), out Overlay? toReturn)){
overlay = (T)toReturn;
return true;
}
overlay = default;
return false;
}
public IEnumerable<Overlay> AllOverlays => _overlays.Values;
public Overlay GetOverlay(Type overlayClass) {
return _overlays[overlayClass];
}
public T GetOverlay<T>() where T : Overlay {
return (T)_overlays[typeof(T)];
}
public bool HasOverlay(Type overlayClass) {
if (!overlayClass.IsSubclassOf(typeof(Overlay)))
Logger.Error("HasOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return _overlays.Remove(overlayClass);
}
public bool HasOverlay<T>() where T : Overlay {
return _overlays.Remove(typeof(T));
}
}
}

View File

@@ -19,10 +19,10 @@ namespace Robust.Client.Graphics
public sealed class State : IRsiStateLike
{
// List of delays for the frame to reach the next frame.
private readonly float[] Delays;
public readonly float[] Delays;
// 2D array for the texture to use for each animation frame at each direction.
private readonly Texture[][] Icons;
public readonly Texture[][] Icons;
internal State(Vector2i size, StateId stateId, DirectionType direction, float[] delays, Texture[][] icons)
{

View File

@@ -1,47 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "RSI Image Format Validation Schema V1",
"description": "Robust Station Image",
"type": "object",
"definitions": {
"size": {
"type": "object",
"properties": {
"x": {"type": "integer", "minimum": 1},
"y": {"type": "integer", "minimum": 1}
},
"required": ["x","y"]
},
"directions": {
"type": "integer",
"enum": [1,4,8]
},
"state": {
"type": "object",
"properties": {
"name": {"type": "string"},
"flags": {"type": "object"}, //To be de-serialized as a Dictionary
"directions": {"$ref": "#/definitions/directions"},
"delays": {
"type": "array",
"minItems": 1,
"items": {
"type": "array",
"items": {"type": "number", "minimum": 0, "exclusiveMinimum": true} //number == float
}
}
},
"required": ["name"] //'delays' is marked as optional in the spec
}
},
"properties": {
"version": {"type": "integer", "minimum": 1, "maximum": 1},
"size": {"$ref": "#/definitions/size"},
"states": {
"type": "array",
"items": {"$ref": "#/definitions/state"},
"minItems": 1
}
},
"required": ["version","size","states"]
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -67,7 +68,7 @@ namespace Robust.Client.Physics
public override void Shutdown()
{
base.Shutdown();
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(nameof(PhysicsIslandOverlay));
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsIslandOverlay));
}
private void HandleIslandSolveMessage(IslandSolveMessage message)
@@ -94,7 +95,7 @@ namespace Robust.Client.Physics
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PhysicsIslandOverlay() : base(nameof(PhysicsIslandOverlay))
public PhysicsIslandOverlay()
{
_islandSystem = EntitySystem.Get<DebugPhysicsIslandSystem>();
_eyeManager = IoCManager.Resolve<IEyeManager>();

View File

@@ -1,5 +1,7 @@
using Robust.Shared.Enums;
using Robust.Client.Graphics;
namespace Robust.Client.Placement
{
public partial class PlacementManager
@@ -7,10 +9,9 @@ namespace Robust.Client.Placement
internal class PlacementOverlay : Overlay
{
private readonly PlacementManager _manager;
public override bool AlwaysDirty => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PlacementOverlay(PlacementManager manager) : base("placement")
public PlacementOverlay(PlacementManager manager)
{
_manager = manager;
ZIndex = 100;

View File

@@ -10,5 +10,6 @@ namespace Robust.Client.ResourceManagement
void RsiLoaded(RsiLoadedEventArgs eventArgs);
void MountLoaderApi(IFileApi api, string apiPrefix, ResourcePath? prefix=null);
void PreloadTextures();
}
}

View File

@@ -0,0 +1,187 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Robust.Client.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
namespace Robust.Client.ResourceManagement
{
internal partial class ResourceCache
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly ILogManager _logManager = default!;
public void PreloadTextures()
{
var sawmill = _logManager.GetSawmill("res.preload");
PreloadTextures(sawmill);
PreloadRsis(sawmill);
}
private void PreloadTextures(ISawmill sawmill)
{
sawmill.Debug("Preloading textures...");
var sw = Stopwatch.StartNew();
var resList = GetTypeDict<TextureResource>();
var texList = ContentFindFiles("/Textures/")
// Skip PNG files inside RSIs.
.Where(p => p.Extension == "png" && !p.ToString().Contains(".rsi/") && !resList.ContainsKey(p))
.Select(p => new TextureResource.LoadStepData {Path = p})
.ToArray();
Parallel.ForEach(texList, data =>
{
try
{
TextureResource.LoadPreTexture(this, data);
}
catch (Exception e)
{
// Mark failed loads as bad and skip them in the next few stages.
// Avoids any silly array resizing or similar.
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
});
foreach (var data in texList)
{
if (data.Bad)
continue;
try
{
TextureResource.LoadTexture(_clyde, data);
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
}
var errors = 0;
foreach (var data in texList)
{
if (data.Bad)
{
errors += 1;
continue;
}
try
{
var texResource = new TextureResource();
texResource.LoadFinish(this, data);
resList[data.Path] = texResource;
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
errors += 1;
}
}
sawmill.Debug(
"Preloaded {CountLoaded} textures ({CountErrored} errored) in {LoadTime}",
texList.Length,
errors,
sw.Elapsed);
}
private void PreloadRsis(ISawmill sawmill)
{
var sw = Stopwatch.StartNew();
var resList = GetTypeDict<RSIResource>();
var rsiList = ContentFindFiles("/Textures/")
.Where(p => p.ToString().EndsWith(".rsi/meta.json"))
.Select(c => c.Directory)
.Where(p => !resList.ContainsKey(p))
.Select(p => new RSIResource.LoadStepData {Path = p})
.ToArray();
Parallel.ForEach(rsiList, data =>
{
try
{
RSIResource.LoadPreTexture(this, data);
}
catch (Exception e)
{
// Mark failed loads as bad and skip them in the next few stages.
// Avoids any silly array resizing or similar.
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
});
foreach (var data in rsiList)
{
if (data.Bad)
continue;
try
{
RSIResource.LoadTexture(_clyde, data);
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
}
Parallel.ForEach(rsiList, data =>
{
if (data.Bad)
return;
try
{
RSIResource.LoadPostTexture(data);
}
catch (Exception e)
{
data.Bad = true;
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
}
});
var errors = 0;
foreach (var data in rsiList)
{
if (data.Bad)
{
errors += 1;
continue;
}
try
{
var rsiRes = new RSIResource();
rsiRes.LoadFinish(this, data);
resList[data.Path] = rsiRes;
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
errors += 1;
}
}
sawmill.Debug(
"Preloaded {CountLoaded} RSIs ({CountErrored} errored) in {LoadTime}",
rsiList.Length,
errors,
sw.Elapsed);
}
}
}

View File

@@ -2,17 +2,13 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.Utility;
using Robust.Shared.Log;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Newtonsoft.Json.Linq;
#if DEBUG
using NJsonSchema;
#endif
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -24,6 +20,14 @@ namespace Robust.Client.ResourceManagement
/// </summary>
public sealed class RSIResource : BaseResource
{
private static readonly float[] OneArray = {1};
private static readonly JsonSerializerOptions SerializerOptions =
new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
AllowTrailingCommas = true
};
public RSI RSI { get; private set; } = default!;
/// <summary>
@@ -38,64 +42,59 @@ namespace Robust.Client.ResourceManagement
public override void Load(IResourceCache cache, ResourcePath path)
{
var manifestPath = path / "meta.json";
string manifestContents;
var clyde = IoCManager.Resolve<IClyde>();
using (var manifestFile = cache.ContentFileRead(manifestPath))
using (var reader = new StreamReader(manifestFile))
{
manifestContents = reader.ReadToEnd();
}
var loadStepData = new LoadStepData {Path = path};
LoadPreTexture(cache, loadStepData);
#if DEBUG
if (RSISchema != null)
{
var errors = RSISchema.Validate(manifestContents);
if (errors.Count != 0)
{
Logger.Error($"Unable to load RSI from '{path}', {errors.Count} errors:");
// Load atlas.
LoadTexture(clyde, loadStepData);
foreach (var error in errors)
{
Logger.Error("{0}", error.ToString());
}
LoadPostTexture(loadStepData);
LoadFinish(cache, loadStepData);
throw new RSILoadException($"{errors.Count} errors while loading RSI. See console.");
}
}
#endif
loadStepData.AtlasSheet.Dispose();
}
// Ok schema validated just fine.
var manifestJson = JObject.Parse(manifestContents);
internal static void LoadTexture(IClyde clyde, LoadStepData loadStepData)
{
loadStepData.AtlasTexture = clyde.LoadTextureFromImage(
loadStepData.AtlasSheet,
loadStepData.Path.ToString());
}
var toAtlas = new List<(Image<Rgba32> src, Texture[][] output, int[][] indices, Vector2i[][] offsets, int totalFrameCount)>();
internal static void LoadPreTexture(IResourceCache cache, LoadStepData data)
{
var metadata = LoadRsiMetadata(cache, data.Path);
var metaData = ParseMetaData(manifestJson);
var frameSize = metaData.Size;
var rsi = new RSI(frameSize, path);
var stateCount = metadata.States.Length;
var toAtlas = new StateReg[stateCount];
var callbackOffsets = new Dictionary<RSI.StateId, Vector2i[][]>();
var frameSize = metadata.Size;
var rsi = new RSI(frameSize, data.Path);
var callbackOffsets = new Dictionary<RSI.StateId, Vector2i[][]>(stateCount);
// Do every state.
foreach (var stateObject in metaData.States)
for (var index = 0; index < metadata.States.Length; index++)
{
// Load image from disk.
var texPath = path / (stateObject.StateId + ".png");
var stream = cache.ContentFileRead(texPath);
Image<Rgba32> image;
using (stream)
{
image = Image.Load<Rgba32>(stream);
}
var sheetSize = new Vector2i(image.Width, image.Height);
ref var reg = ref toAtlas[index];
if (sheetSize.X % frameSize.X != 0 || sheetSize.Y % frameSize.Y != 0)
var stateObject = metadata.States[index];
// Load image from disk.
var texPath = data.Path / (stateObject.StateId + ".png");
using (var stream = cache.ContentFileRead(texPath))
{
reg.Src = Image.Load<Rgba32>(stream);
}
if (reg.Src.Width % frameSize.X != 0 || reg.Src.Height % frameSize.Y != 0)
{
throw new RSILoadException("State image size is not a multiple of the icon size.");
}
// Load all frames into a list so we can operate on it more sanely.
var frameCount = stateObject.Delays.Sum(delayList => delayList.Length);
reg.TotalFrameCount = stateObject.Delays.Sum(delayList => delayList.Length);
var (foldedDelays, foldedIndices) = FoldDelays(stateObject.Delays);
@@ -108,29 +107,34 @@ namespace Robust.Client.ResourceManagement
callbackOffset[i] = new Vector2i[foldedIndices[0].Length];
}
var state = new RSI.State(frameSize, stateObject.StateId, stateObject.DirType, foldedDelays, textures);
reg.Output = textures;
reg.Indices = foldedIndices;
reg.Offsets = callbackOffset;
var state = new RSI.State(frameSize, stateObject.StateId, stateObject.DirType, foldedDelays,
textures);
rsi.AddState(state);
toAtlas.Add((image, textures, foldedIndices, callbackOffset, frameCount));
callbackOffsets[stateObject.StateId] = callbackOffset;
}
// Poorly hacked in texture atlas support here.
var totalFrameCount = toAtlas.Sum(p => p.totalFrameCount);
var totalFrameCount = toAtlas.Sum(p => p.TotalFrameCount);
// Generate atlas.
var dimensionX = (int) MathF.Ceiling(MathF.Sqrt(totalFrameCount));
var dimensionY = (int) MathF.Ceiling((float) totalFrameCount / dimensionX);
using var sheet = new Image<Rgba32>(dimensionX * frameSize.X, dimensionY * frameSize.Y);
var sheet = new Image<Rgba32>(dimensionX * frameSize.X, dimensionY * frameSize.Y);
var sheetIndex = 0;
foreach (var (src, _, _, _, frameCount) in toAtlas)
for (var index = 0; index < toAtlas.Length; index++)
{
ref var reg = ref toAtlas[index];
// Blit all the frames over.
for (var i = 0; i < frameCount; i++)
for (var i = 0; i < reg.TotalFrameCount; i++)
{
var srcWidth = (src.Width / frameSize.X);
var srcWidth = (reg.Src.Width / frameSize.X);
var srcColumn = i % srcWidth;
var srcRow = i / srcWidth;
var srcPos = (srcColumn * frameSize.X, srcRow * frameSize.Y);
@@ -141,30 +145,49 @@ namespace Robust.Client.ResourceManagement
var srcBox = UIBox2i.FromDimensions(srcPos, frameSize);
src.Blit(srcBox, sheet, sheetPos);
reg.Src.Blit(srcBox, sheet, sheetPos);
}
sheetIndex += frameCount;
sheetIndex += reg.TotalFrameCount;
}
// Load atlas.
var texture = Texture.LoadFromImage(sheet, path.ToString());
for (var i = 0; i < toAtlas.Length; i++)
{
ref var reg = ref toAtlas[i];
reg.Src.Dispose();
}
data.Rsi = rsi;
data.AtlasSheet = sheet;
data.AtlasList = toAtlas;
data.FrameSize = frameSize;
data.DimX = dimensionX;
data.CallbackOffsets = callbackOffsets;
}
internal static void LoadPostTexture(LoadStepData data)
{
var dimX = data.DimX;
var toAtlas = data.AtlasList;
var frameSize = data.FrameSize;
var texture = data.AtlasTexture;
var sheetOffset = 0;
foreach (var (_, output, indices, offsets, frameCount) in toAtlas)
for (var toAtlasIndex = 0; toAtlasIndex < toAtlas.Length; toAtlasIndex++)
{
for (var i = 0; i < indices.Length; i++)
ref var reg = ref toAtlas[toAtlasIndex];
for (var i = 0; i < reg.Indices.Length; i++)
{
var dirIndices = indices[i];
var dirOutput = output[i];
var dirOffsets = offsets[i];
var dirIndices = reg.Indices[i];
var dirOutput = reg.Output[i];
var dirOffsets = reg.Offsets[i];
for (var j = 0; j < dirIndices.Length; j++)
{
var index = sheetOffset + dirIndices[j];
var sheetColumn = index % dimensionX;
var sheetRow = index / dimensionX;
var sheetColumn = index % dimX;
var sheetRow = index / dimX;
var sheetPos = (sheetColumn * frameSize.X, sheetRow * frameSize.Y);
dirOffsets[j] = sheetPos;
@@ -172,22 +195,104 @@ namespace Robust.Client.ResourceManagement
}
}
sheetOffset += frameCount;
sheetOffset += reg.TotalFrameCount;
}
}
foreach (var (image, _, _, _, _) in toAtlas)
{
image.Dispose();
}
RSI = rsi;
internal void LoadFinish(IResourceCache cache, LoadStepData data)
{
RSI = data.Rsi;
if (cache is IResourceCacheInternal cacheInternal)
{
cacheInternal.RsiLoaded(new RsiLoadedEventArgs(path, this, sheet, callbackOffsets));
cacheInternal.RsiLoaded(new RsiLoadedEventArgs(data.Path, this, data.AtlasSheet, data.CallbackOffsets));
}
}
private static RsiMetadata LoadRsiMetadata(IResourceCache cache, ResourcePath path)
{
var manifestPath = path / "meta.json";
string manifestContents;
using (var manifestFile = cache.ContentFileRead(manifestPath))
using (var reader = new StreamReader(manifestFile))
{
manifestContents = reader.ReadToEnd();
}
// Ok schema validated just fine.
var manifestJson = JsonSerializer.Deserialize<RsiJsonMetadata>(manifestContents, SerializerOptions);
if (manifestJson == null)
throw new RSILoadException("Manifest JSON was null!");
var size = manifestJson.Size;
var states = new StateMetadata[manifestJson.States.Length];
for (var stateI = 0; stateI < manifestJson.States.Length; stateI++)
{
var stateObject = manifestJson.States[stateI];
var stateName = stateObject.Name;
RSI.State.DirectionType directions;
int dirValue;
if (stateObject.Directions is { } dirVal)
{
dirValue = dirVal;
directions = dirVal switch
{
1 => RSI.State.DirectionType.Dir1,
4 => RSI.State.DirectionType.Dir4,
8 => RSI.State.DirectionType.Dir8,
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
};
}
else
{
dirValue = 1;
directions = RSI.State.DirectionType.Dir1;
}
// We can ignore selectors and flags for now,
// because they're not used yet!
// Get the lists of delays.
float[][] delays;
if (stateObject.Delays != null)
{
delays = stateObject.Delays;
if (delays.Length != dirValue)
{
throw new RSILoadException(
"DirectionsdirectionFramesList count does not match amount of delays specified.");
}
for (var i = 0; i < delays.Length; i++)
{
var delayList = delays[i];
if (delayList.Length == 0)
{
delays[i] = OneArray;
}
}
}
else
{
delays = new float[dirValue][];
// No delays specified, default to 1 frame per dir.
for (var i = 0; i < dirValue; i++)
{
delays[i] = OneArray;
}
}
states[stateI] = new StateMetadata(new RSI.StateId(stateName), directions, delays);
}
return new RsiMetadata(size, states);
}
/// <summary>
/// Folds a per-directional sets of animation delays
/// into an equivalent set of animation delays and indices that works for every direction.
@@ -336,109 +441,38 @@ namespace Robust.Client.ResourceManagement
return (floatDelays, arrayIndices);
}
internal static RsiMetadata ParseMetaData(JObject manifestJson)
internal sealed class LoadStepData
{
var size = manifestJson["size"]!.ToObject<Vector2i>();
var states = new List<StateMetadata>();
foreach (var stateObject in manifestJson["states"]!.Cast<JObject>())
{
var stateName = stateObject["name"]!.ToObject<string>()!;
RSI.State.DirectionType directions;
int dirValue;
if (stateObject.TryGetValue("directions", out var dirJToken))
{
dirValue= dirJToken.ToObject<int>();
directions = dirValue switch
{
1 => RSI.State.DirectionType.Dir1,
4 => RSI.State.DirectionType.Dir4,
8 => RSI.State.DirectionType.Dir8,
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
};
}
else
{
dirValue = 1;
directions = RSI.State.DirectionType.Dir1;
}
// We can ignore selectors and flags for now,
// because they're not used yet!
// Get the lists of delays.
float[][] delays;
if (stateObject.TryGetValue("delays", out var delayToken))
{
delays = delayToken.ToObject<float[][]>()!;
if (delays.Length != dirValue)
{
throw new RSILoadException(
"DirectionsdirectionFramesList count does not match amount of delays specified.");
}
for (var i = 0; i < delays.Length; i++)
{
var delayList = delays[i];
if (delayList.Length == 0)
{
delays[i] = new float[] {1};
}
}
}
else
{
delays = new float[dirValue][];
// No delays specified, default to 1 frame per dir.
for (var i = 0; i < dirValue; i++)
{
delays[i] = new float[] {1};
}
}
states.Add(new StateMetadata(new RSI.StateId(stateName), directions, delays));
}
return new RsiMetadata(size, states);
public bool Bad;
public ResourcePath Path = default!;
public Image<Rgba32> AtlasSheet = default!;
public int DimX;
public StateReg[] AtlasList = default!;
public Vector2i FrameSize;
public Dictionary<RSI.StateId, Vector2i[][]> CallbackOffsets = default!;
public Texture AtlasTexture = default!;
public RSI Rsi = default!;
}
#if DEBUG
private static readonly JsonSchema? RSISchema = GetSchema();
private static JsonSchema? GetSchema()
internal struct StateReg
{
try
{
string schema;
using (var schemaStream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("Robust.Client.Graphics.RSI.RSISchema.json")!)
using (var schemaReader = new StreamReader(schemaStream))
{
schema = schemaReader.ReadToEnd();
}
return JsonSchema.FromJsonAsync(schema).Result;
}
catch (Exception e)
{
System.Console.WriteLine("Failed to load RSI JSON Schema!\n{0}", e);
return null;
}
public Image<Rgba32> Src;
public Texture[][] Output;
public int[][] Indices;
public Vector2i[][] Offsets;
public int TotalFrameCount;
}
#endif
internal sealed class RsiMetadata
{
public RsiMetadata(Vector2i size, List<StateMetadata> states)
public RsiMetadata(Vector2i size, StateMetadata[] states)
{
Size = size;
States = states;
}
public Vector2i Size { get; }
public List<StateMetadata> States { get; }
public StateMetadata[] States { get; }
}
internal sealed class StateMetadata
@@ -466,6 +500,17 @@ namespace Robust.Client.ResourceManagement
public float[][] Delays { get; }
}
// To be directly deserialized.
[UsedImplicitly]
private sealed record RsiJsonMetadata(Vector2i Size, StateJsonMetadata[] States)
{
}
[UsedImplicitly]
private sealed record StateJsonMetadata(string Name, int? Directions, float[][]? Delays)
{
}
}
[Serializable]

View File

@@ -1,7 +1,6 @@
using System.IO;
using Robust.Client.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -9,41 +8,50 @@ using YamlDotNet.RepresentationModel;
namespace Robust.Client.ResourceManagement
{
public class TextureResource : BaseResource
public sealed class TextureResource : BaseResource
{
public const float ClickThreshold = 0.25f;
public override ResourcePath? Fallback => new("/Textures/noSprite.png");
public override ResourcePath Fallback => new("/Textures/noSprite.png");
public Texture Texture { get; private set; } = default!;
public override void Load(IResourceCache cache, ResourcePath path)
{
if (!cache.TryContentFileRead(path, out var stream))
{
throw new FileNotFoundException("Content file does not exist for texture");
}
var clyde = IoCManager.Resolve<IClyde>();
using (stream)
{
// Primarily for tracking down iCCP sRGB errors in the image files.
Logger.DebugS("res.tex", $"Loading texture {path}.");
var data = new LoadStepData {Path = path};
var loadParameters = _tryLoadTextureParameters(cache, path) ?? TextureLoadParameters.Default;
var manager = IoCManager.Resolve<IClyde>();
using var image = Image.Load<Rgba32>(stream);
Texture = manager.LoadTextureFromImage(image, path.ToString(), loadParameters);
if (cache is IResourceCacheInternal cacheInternal)
{
cacheInternal.TextureLoaded(new TextureLoadedEventArgs(path, image, this));
}
}
LoadPreTexture(cache, data);
LoadTexture(clyde, data);
LoadFinish(cache, data);
}
private static TextureLoadParameters? _tryLoadTextureParameters(IResourceCache cache, ResourcePath path)
internal static void LoadPreTexture(IResourceCache cache, LoadStepData data)
{
using (var stream = cache.ContentFileRead(data.Path))
{
data.Image = Image.Load<Rgba32>(stream);
}
data.LoadParameters = TryLoadTextureParameters(cache, data.Path) ?? TextureLoadParameters.Default;
}
internal static void LoadTexture(IClyde clyde, LoadStepData data)
{
data.Texture = clyde.LoadTextureFromImage(data.Image, data.Path.ToString(), data.LoadParameters);
}
internal void LoadFinish(IResourceCache cache, LoadStepData data)
{
Texture = data.Texture;
if (cache is IResourceCacheInternal cacheInternal)
{
cacheInternal.TextureLoaded(new TextureLoadedEventArgs(data.Path, data.Image, this));
}
data.Image.Dispose();
}
private static TextureLoadParameters? TryLoadTextureParameters(IResourceCache cache, ResourcePath path)
{
var metaPath = path.WithName(path.Filename + ".yml");
if (cache.TryContentFileRead(metaPath, out var stream))
@@ -70,6 +78,15 @@ namespace Robust.Client.ResourceManagement
return null;
}
internal sealed class LoadStepData
{
public ResourcePath Path = default!;
public Image<Rgba32> Image = default!;
public TextureLoadParameters LoadParameters;
public Texture Texture = default!;
public bool Bad;
}
// TODO: Due to a bug in Roslyn, NotNullIfNotNullAttribute doesn't work.
// So this can't work with both nullables and non-nullables at the same time.
// I decided to only have it work with non-nullables as such.

View File

@@ -14,7 +14,6 @@
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Include="nfluidsynth" Version="0.3.1" />
<PackageReference Include="NJsonSchema" Version="10.3.8" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.3" />
@@ -39,9 +38,6 @@
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Graphics\RSI\RSISchema.json" Condition="'$(Configuration)' == 'Debug'">
<LogicalName>Robust.Client.Graphics.RSI.RSISchema.json</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Graphics\Clyde\Shaders\*" />
</ItemGroup>

View File

@@ -2,12 +2,14 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Robust.Client.Console;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.ContentPack;
using Robust.Shared.Input;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -301,43 +303,95 @@ namespace Robust.Client.UserInterface.CustomControls
private async void _loadHistoryFromDisk()
{
CommandBar.ClearHistory();
Stream stream;
try
var sawmill = Logger.GetSawmill("dbgconsole");
var data = await Task.Run(async () =>
{
stream = _resourceManager.UserData.OpenRead(HistoryPath);
}
catch (FileNotFoundException)
{
// Nada, nothing to load in that case.
return;
}
try
{
using (var reader = new StreamReader(stream, EncodingHelpers.UTF8))
Stream? stream = null;
for (var i = 0; i < 3; i++)
{
var data = JsonConvert.DeserializeObject<List<string>>(await reader.ReadToEndAsync());
CommandBar.ClearHistory();
CommandBar.History.AddRange(data);
CommandBar.HistoryIndex = CommandBar.History.Count;
try
{
stream = _resourceManager.UserData.OpenRead(HistoryPath);
break;
}
catch (FileNotFoundException)
{
// Nada, nothing to load in that case.
return null;
}
catch (IOException)
{
// File locked probably??
await Task.Delay(10);
}
}
}
finally
{
stream?.Dispose();
}
if (stream == null)
{
sawmill.Warning("Failed to load debug console history!");
return null;
}
try
{
return await JsonSerializer.DeserializeAsync<string[]>(stream);
}
catch (Exception e)
{
sawmill.Warning("Failed to load debug console history due to exception!\n{e}");
return null;
}
finally
{
// ReSharper disable once MethodHasAsyncOverload
stream.Dispose();
}
});
if (data == null)
return;
CommandBar.ClearHistory();
CommandBar.History.AddRange(data);
CommandBar.HistoryIndex = CommandBar.History.Count;
}
private void _flushHistoryToDisk()
private async void _flushHistoryToDisk()
{
using (var stream = _resourceManager.UserData.Create(HistoryPath))
using (var writer = new StreamWriter(stream, EncodingHelpers.UTF8))
CommandBar.HistoryIndex = CommandBar.History.Count;
var sawmill = Logger.GetSawmill("dbgconsole");
var newHistory = JsonSerializer.Serialize(CommandBar.History);
await Task.Run(async () =>
{
var data = JsonConvert.SerializeObject(CommandBar.History);
CommandBar.HistoryIndex = CommandBar.History.Count;
writer.Write(data);
}
Stream? stream = null;
for (var i = 0; i < 3; i++)
{
try
{
stream = _resourceManager.UserData.Create(HistoryPath);
break;
}
catch (IOException)
{
// Probably locking.
await Task.Delay(10);
}
}
if (stream == null)
{
sawmill.Warning("Failed to save debug console history!");
return;
}
// ReSharper disable once UseAwaitUsing
using var writer = new StreamWriter(stream, EncodingHelpers.UTF8);
// ReSharper disable once MethodHasAsyncOverload
writer.Write(newHistory);
});
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -67,6 +67,7 @@ namespace Robust.Client.UserInterface.CustomControls
Title = Loc.GetString("Entity Spawn Panel");
SetSize = (250, 300);
MinSize = (250, 200);

View File

@@ -42,19 +42,20 @@ namespace Robust.Client.Utility
{
var dstSpan = destination.GetPixelSpan();
var dstWidth = destination.Width;
var srcHeight = sourceRect.Height;
var srcWidth = sourceRect.Width;
var (ox, oy) = destinationOffset;
for (var y = 0; y < sourceRect.Height; y++)
for (var y = 0; y < srcHeight; y++)
{
var sourceRowOffset = sourceWidth * (y + sourceRect.Top) + sourceRect.Left;
var destRowOffset = dstWidth * (y + oy) + ox;
for (var x = 0; x < sourceRect.Width; x++)
{
var pixel = source[x + sourceRowOffset];
dstSpan[x + destRowOffset] = pixel;
}
var srcRow = source[sourceRowOffset..(sourceRowOffset + srcWidth)];
var dstRow = dstSpan[destRowOffset..(destRowOffset + srcWidth)];
srcRow.CopyTo(dstRow);
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
@@ -41,6 +41,40 @@ namespace Robust.Client.Utility
return specifier.RsiStateLike().Default;
}
public static float[] FrameDelays(this SpriteSpecifier specifier) {
var resc = IoCManager.Resolve<IResourceCache>();
switch (specifier) {
case SpriteSpecifier.Rsi rsi:
if (resc.TryGetResource<RSIResource>(SpriteComponent.TextureRoot / rsi.RsiPath, out var theRsi)) {
if (theRsi.RSI.TryGetState(rsi.RsiState, out var state)) {
return state.Delays;
}
}
Logger.Error("Failed to load RSI {0}", rsi.RsiPath);
return new float[0];
default:
throw new NotImplementedException();
}
}
public static Texture[] FrameArr(this SpriteSpecifier specifier) {
var resc = IoCManager.Resolve<IResourceCache>();
switch (specifier) {
case SpriteSpecifier.Rsi rsi:
if (resc.TryGetResource<RSIResource>(SpriteComponent.TextureRoot / rsi.RsiPath, out var theRsi)) {
if (theRsi.RSI.TryGetState(rsi.RsiState, out var state)) {
return state.Icons[0];
}
}
Logger.Error("Failed to load RSI {0}", rsi.RsiPath);
return new Texture[0];
default:
throw new NotImplementedException();
}
}
public static IDirectionalTextureProvider DirFrame0(this SpriteSpecifier specifier)
{
return specifier.RsiStateLike();

View File

@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Threading;
using Prometheus;
@@ -141,6 +142,10 @@ namespace Robust.Server
/// <inheritdoc />
public bool Start(Func<ILogHandler>? logHandlerFactory = null)
{
var profilePath = Path.Join(Environment.CurrentDirectory, "AAAAAAAA");
ProfileOptimization.SetProfileRoot(profilePath);
ProfileOptimization.StartProfile("AAAAAAAAAA");
_config.Initialize(true);
if (LoadConfigAndUserData)
@@ -178,6 +183,8 @@ namespace Robust.Server
_config.OverrideConVars(_commandLineArgs.CVars);
}
ProfileOptSetup.Setup(_config);
//Sets up Logging
_logHandlerFactory = logHandlerFactory;
@@ -327,6 +334,8 @@ namespace Robust.Server
WindowsTickPeriod.TimeBeginPeriod((uint) _config.GetCVar(CVars.SysWinTickPeriod));
}
GC.Collect();
return false;
}

View File

@@ -51,7 +51,7 @@ namespace Robust.Server.GameObjects
}
}
public class PlayerAttachSystemMessage : EntitySystemMessage
public class PlayerAttachSystemMessage : EntityEventArgs
{
public PlayerAttachSystemMessage(IEntity entity, IPlayerSession newPlayer)
{
@@ -63,7 +63,7 @@ namespace Robust.Server.GameObjects
public IPlayerSession NewPlayer { get; }
}
public class PlayerDetachedSystemMessage : EntitySystemMessage
public class PlayerDetachedSystemMessage : EntityEventArgs
{
public PlayerDetachedSystemMessage(IEntity entity)
{

View File

@@ -54,17 +54,19 @@ namespace Robust.Server.GameObjects
get => _drawDepth;
set
{
if (_drawDepth == value) return;
_drawDepth = value;
Dirty();
}
}
[ViewVariables(VVAccess.ReadWrite)]
public bool Visible
public override bool Visible
{
get => _visible;
set
{
if (_visible == value) return;
_visible = value;
Dirty();
}

View File

@@ -2,7 +2,7 @@ using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
{
public sealed class EntityDeletedMessage : EntitySystemMessage
public sealed class EntityDeletedMessage : EntityEventArgs
{
public IEntity Entity { get; }

View File

@@ -12,7 +12,7 @@ namespace Robust.Server.GameObjects
/// <remarks>
/// List is empty if it's no longer intersecting any.
/// </remarks>
public sealed class TileLookupUpdateMessage : EntitySystemMessage
public sealed class TileLookupUpdateMessage : EntityEventArgs
{
public Dictionary<GridId, List<Vector2i>>? NewIndices { get; }

View File

@@ -87,7 +87,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message)
public void SendSystemNetworkMessage(EntityEventArgs message)
{
var newMsg = _networkManager.CreateNetMessage<MsgEntity>();
newMsg.Type = EntityMessageType.SystemMessage;
@@ -98,7 +98,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message, INetChannel targetConnection)
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel targetConnection)
{
var newMsg = _networkManager.CreateNetMessage<MsgEntity>();
newMsg.Type = EntityMessageType.SystemMessage;

View File

@@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace Robust.Shared.Maths
@@ -16,12 +17,12 @@ namespace Robust.Shared.Maths
/// <summary>
/// The X component of the Vector2i.
/// </summary>
public int X;
[JsonInclude] public int X;
/// <summary>
/// The Y component of the Vector2i.
/// </summary>
public int Y;
[JsonInclude] public int Y;
/// <summary>
/// Construct a vector from its coordinates.

View File

@@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace Robust.Shared.Maths
@@ -12,12 +13,12 @@ namespace Robust.Shared.Maths
/// <summary>
/// The X component of the Vector2i.
/// </summary>
public uint X;
[JsonInclude] public uint X;
/// <summary>
/// The Y component of the Vector2i.
/// </summary>
public uint Y;
[JsonInclude] public uint Y;
/// <summary>
/// Construct a vector from its coordinates.

View File

@@ -74,7 +74,7 @@ namespace Robust.Shared.Audio
return GetAudio()?.Play(playerFilter, filename, coordinates, audioParams);
}
internal class QueryAudioSystem : EntitySystemMessage
internal class QueryAudioSystem : EntityEventArgs
{
public IAudioSystem? Audio { get; set; }
}

View File

@@ -93,6 +93,10 @@ namespace Robust.Shared
public static readonly CVarDef<int> SysWinTickPeriod =
CVarDef.Create("sys.win_tick_period", 3, CVar.SERVERONLY);
// On non-FULL_RELEASE builds, use ProfileOptimization/tiered JIT to speed up game startup.
public static readonly CVarDef<bool> SysProfileOpt =
CVarDef.Create("sys.profile_opt", true);
#if DEBUG
public static readonly CVarDef<float> NetFakeLoss = CVarDef.Create("net.fakeloss", 0f, CVar.CHEAT);
public static readonly CVarDef<float> NetFakeLagMin = CVarDef.Create("net.fakelagmin", 0f, CVar.CHEAT);
@@ -331,6 +335,9 @@ namespace Robust.Shared
/// A small length used as a collision and constraint tolerance. Usually it is
/// chosen to be numerically significant, but visually insignificant.
/// </summary>
/// <remarks>
/// Note that some joints may have this cached and not update on value change.
/// </remarks>
public static readonly CVarDef<float> LinearSlop =
CVarDef.Create("physics.linearslop", 0.005f);
@@ -372,8 +379,9 @@ namespace Robust.Shared
// - Maximums
// Squared
// 35 m/s, AKA half a tile per frame allowed. Divide this by frametime to get units per second.
public static readonly CVarDef<float> MaxLinVelocity =
CVarDef.Create("physics.maxlinvelocity", 4.0f);
CVarDef.Create("physics.maxlinvelocity", 0.56f);
// Squared
public static readonly CVarDef<float> MaxAngVelocity =
@@ -385,5 +393,12 @@ namespace Robust.Shared
public static readonly CVarDef<bool> DiscordEnabled =
CVarDef.Create("discord.enabled", true, CVar.CLIENTONLY);
/*
* RES
*/
public static readonly CVarDef<bool> ResCheckPathCasing =
CVarDef.Create("res.checkpathcasing", false);
}
}

View File

@@ -7,7 +7,7 @@ namespace Robust.Shared.Containers
/// Raised when the contents of a container have been modified.
/// </summary>
[PublicAPI]
public abstract class ContainerModifiedMessage : EntitySystemMessage
public abstract class ContainerModifiedMessage : EntityEventArgs
{
/// <summary>
/// The container being acted upon.

View File

@@ -219,7 +219,8 @@ namespace Robust.Shared.ContentPack
continue;
}
var msg = $"{name}: ILVerify: {string.Format(res.Message, res.Args)}";
var formatted = res.Args == null ? res.Message : string.Format(res.Message, res.Args);
var msg = $"{name}: ILVerify: {formatted}";
try
{

View File

@@ -18,16 +18,19 @@ namespace Robust.Shared.ContentPack
{
private readonly DirectoryInfo _directory;
private readonly ISawmill _sawmill;
private readonly bool _checkCasing;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="directory">Directory to mount.</param>
/// <param name="sawmill"></param>
public DirLoader(DirectoryInfo directory, ISawmill sawmill)
/// <param name="checkCasing"></param>
public DirLoader(DirectoryInfo directory, ISawmill sawmill, bool checkCasing)
{
_directory = directory;
_sawmill = sawmill;
_checkCasing = checkCasing;
}
/// <inheritdoc />
@@ -80,6 +83,9 @@ namespace Robust.Shared.ContentPack
[Conditional("DEBUG")]
private void CheckPathCasing(ResourcePath path)
{
if (!_checkCasing)
return;
// Run this inside the thread pool due to overhead.
Task.Run(() =>
{

View File

@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
/// <summary>
/// Get the full directory path that the executable is located in.
/// </summary>
private static string GetExecutableDirectory()
internal static string GetExecutableDirectory()
{
// TODO: remove this shitty hack, either through making it less hardcoded into shared,
// or by making our file structure less spaghetti somehow.

View File

@@ -135,7 +135,7 @@ namespace Robust.Shared.ContentPack
throw new DirectoryNotFoundException("Specified directory does not exist: " + pathInfo.FullName);
}
var loader = new DirLoader(pathInfo, Logger.GetSawmill("res"));
var loader = new DirLoader(pathInfo, Logger.GetSawmill("res"), _config.GetCVar(CVars.ResCheckPathCasing));
AddRoot(prefix, loader);
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Robust.Shared.Enums {
/// <summary>
/// Determines in which canvas layers an overlay gets drawn.
/// </summary>
[Flags]
public enum OverlaySpace {
/// <summary>
/// Used for matching bit flags.
/// </summary>
None = 0b000000,
/// <summary>
/// This overlay will be drawn in screen coordinates in the UI space above the world.
/// </summary>
ScreenSpace = 0b000001,
/// <summary>
/// This overlay will be drawn directly above normal worldspace, but a stencil equivalent to the FOV will be applied.
/// You likely want to use <see cref="WorldSpaceBelowFOV"/>. This space should only be used if you fully understand why you need this.
/// </summary>
WorldSpaceFOVStencil = 0b000010,
/// <summary>
/// This overlay will be drawn above entities, lighting, and FOV.
/// </summary>
WorldSpace = 0b000100,
/// <summary>
/// This overlay will be drawn beneath FOV; above lighting and entities.
/// </summary>
WorldSpaceBelowFOV = 0b001000,
/// <summary>
/// This overlay will be drawn beneath entities, lighting, and FOV; above grids.
/// </summary>
WorldSpaceBelowEntities = 0b010000,
/// <summary>
/// This overlay will be drawn in screen coordinates behind the world.
/// </summary>
ScreenSpaceBelowWorld = 0b100000,
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -12,6 +12,7 @@ namespace Robust.Shared.GameObjects
public class ComponentDependencyManager : IComponentDependencyManager
{
[IoC.Dependency] private readonly IComponentFactory _componentFactory = null!;
[IoC.Dependency] private readonly IComponentManager _componentManager = null!;
/// <summary>
/// Cache of queries and their corresponding field offsets
@@ -22,18 +23,16 @@ namespace Robust.Shared.GameObjects
new();
/// <inheritdoc />
public void OnComponentAdd(IEntity entity, IComponent newComp)
public void OnComponentAdd(EntityUid eUid, IComponent newComp)
{
SetDependencyForEntityComponents(entity, newComp.GetType(), newComp);
InjectIntoComponent(entity, newComp);
SetDependencyForEntityComponents(eUid, newComp, newComp.GetType());
InjectIntoComponent(eUid, newComp);
}
/// <summary>
/// Filling the dependencies of newComp by iterating over entity's components
/// </summary>
/// <param name="entity"></param>
/// <param name="newComp"></param>
public void InjectIntoComponent(IEntity entity, IComponent newComp)
private void InjectIntoComponent(EntityUid eUid, IComponent newComp)
{
var queries = GetPointerQueries(newComp);
@@ -43,7 +42,7 @@ namespace Robust.Shared.GameObjects
}
//get all present components in entity
foreach (var entityComp in entity.GetAllComponents())
foreach (var entityComp in _componentManager.GetComponents(eUid))
{
var entityCompReg = _componentFactory.GetRegistration(entityComp);
foreach (var reference in entityCompReg.References)
@@ -61,24 +60,22 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public void OnComponentRemove(IEntity entity, IComponent removedComp)
public void OnComponentRemove(EntityUid eUid, IComponent removedComp)
{
ClearRemovedComponentDependencies(removedComp);
SetDependencyForEntityComponents(entity, removedComp.GetType(), null);
SetDependencyForEntityComponents(eUid, null, removedComp.GetType());
}
/// <summary>
/// Updates all dependencies to type compType on the entity (in fields on components), to the value of comp
/// </summary>
/// <param name="entity"></param>
/// <param name="compType"></param>
/// <param name="comp"></param>
private void SetDependencyForEntityComponents(IEntity entity, Type compType, IComponent? comp)
private void SetDependencyForEntityComponents(EntityUid eUid, IComponent? comp, Type compType)
{
var compReg = _componentFactory.GetRegistration(compType);
//check if any are requesting our component as a dependency
foreach (var entityComponent in entity.GetAllComponents())
var entityComponents = _componentManager.GetComponents(eUid);
foreach (var entityComponent in entityComponents)
{
//get entry for out entityComponent
var queries = GetPointerQueries(entityComponent);
@@ -113,7 +110,7 @@ namespace Robust.Shared.GameObjects
}
}
private void SetField(object o, int offset, object? value)
private static void SetField(object o, int offset, object? value)
{
var asDummy = Unsafe.As<FieldOffsetDummy>(o);
ref var @ref = ref Unsafe.Add(ref asDummy.A, offset);
@@ -182,7 +179,7 @@ namespace Robust.Shared.GameObjects
return queries;
}
private Action<object>? GetEventMethod(MethodInfo[] methods, string methodName, MethodInfo getterMethod)
private static Action<object>? GetEventMethod(MethodInfo[] methods, string methodName, MethodInfo getterMethod)
{
var method = methods.FirstOrDefault(m => m.Name == methodName);
if (method == null) return null;
@@ -196,7 +193,7 @@ namespace Robust.Shared.GameObjects
return o => @delegate((T) o);
}
private int GetFieldOffset(Type type, FieldInfo field)
private static int GetFieldOffset(Type type, FieldInfo field)
{
var fieldOffsetField = typeof(FieldOffsetDummy).GetField("A")!;
var dynamicMethod = new DynamicMethod(

View File

@@ -1,4 +1,4 @@
#nullable enable
#nullable enable
using System;
namespace Robust.Shared.GameObjects
@@ -15,12 +15,21 @@ namespace Robust.Shared.GameObjects
/// </summary>
public IComponent Component { get; }
/// <summary>
/// EntityUid of the entity this component belongs to.
/// </summary>
public EntityUid OwnerUid { get; }
/// <summary>
/// Constructs a new instance of <see cref="ComponentEventArgs"/>.
/// </summary>
/// <param name="component">The relevant component</param>
protected ComponentEventArgs(IComponent component) => Component = component;
/// <param name="ownerUid">EntityUid of the entity this component belongs to.</param>
protected ComponentEventArgs(IComponent component, EntityUid ownerUid)
{
Component = component;
OwnerUid = ownerUid;
}
}
/// <summary>
@@ -32,7 +41,8 @@ namespace Robust.Shared.GameObjects
/// Constructs a new instance of <see cref="AddedComponentEventArgs"/>.
/// </summary>
/// <param name="component">The relevant component</param>
public AddedComponentEventArgs(IComponent component) : base(component) { }
/// <param name="uid">EntityUid of the entity this component belongs to.</param>
public AddedComponentEventArgs(IComponent component, EntityUid uid) : base(component, uid) { }
}
/// <summary>
@@ -44,7 +54,8 @@ namespace Robust.Shared.GameObjects
/// Constructs a new instance of <see cref="RemovedComponentEventArgs"/>.
/// </summary>
/// <param name="component">The relevant component</param>
public RemovedComponentEventArgs(IComponent component) : base(component) { }
/// <param name="uid">EntityUid of the entity this component belongs to.</param>
public RemovedComponentEventArgs(IComponent component, EntityUid uid) : base(component, uid) { }
}
/// <summary>
@@ -56,7 +67,7 @@ namespace Robust.Shared.GameObjects
/// Constructs a new instance of <see cref="DeletedComponentEventArgs"/>.
/// </summary>
/// <param name="component">The relevant component</param>
public DeletedComponentEventArgs(IComponent component) : base(component) { }
/// <param name="uid">EntityUid of the entity this component belongs to.</param>
public DeletedComponentEventArgs(IComponent component, EntityUid uid) : base(component, uid) { }
}
}

View File

@@ -64,6 +64,15 @@ namespace Robust.Shared.GameObjects
/// </summary>
private readonly HashSet<string> IgnoredComponentNames = new();
/// <inheritdoc />
public event Action<IComponentRegistration>? ComponentAdded;
/// <inheritdoc />
public event Action<(IComponentRegistration, Type)>? ComponentReferenceAdded;
/// <inheritdoc />
public event Action<string>? ComponentIgnoreAdded;
/// <inheritdoc />
public IEnumerable<Type> AllRegisteredTypes => types.Keys;
@@ -137,6 +146,7 @@ namespace Robust.Shared.GameObjects
{
netIDs[netID.Value] = registration;
}
ComponentAdded?.Invoke(registration);
}
[Obsolete("Use RegisterClass and Attributes instead of the Register/RegisterReference combo")]
@@ -158,6 +168,7 @@ namespace Robust.Shared.GameObjects
throw new InvalidOperationException($"Attempted to register a reference twice: {@interface}");
}
registration.References.Add(@interface);
ComponentReferenceAdded?.Invoke((registration, @interface));
}
public void RegisterIgnore(string name, bool overwrite = false)
@@ -178,12 +189,13 @@ namespace Robust.Shared.GameObjects
}
IgnoredComponentNames.Add(name);
ComponentIgnoreAdded?.Invoke(name);
}
private void RemoveComponent(string name)
{
var registration = names[name];
names.Remove(registration.Name);
_lowerCaseNames.Remove(registration.Name.ToLowerInvariant());
types.Remove(registration.Type);

View File

@@ -15,7 +15,6 @@ namespace Robust.Shared.GameObjects
public class ComponentManager : IComponentManager
{
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IComponentDependencyManager _componentDependencyManager = default!;
#if EXCEPTION_TOLERANCE
@@ -45,9 +44,17 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public event EventHandler<ComponentEventArgs>? ComponentDeleted;
private bool initialized;
public void Initialize()
{
if (initialized)
throw new InvalidOperationException("Already initialized.");
initialized = true;
FillComponentDict();
_componentFactory.ComponentAdded += OnComponentAdded;
_componentFactory.ComponentReferenceAdded += OnComponentReferenceAdded;
}
/// <inheritdoc />
@@ -59,6 +66,26 @@ namespace Robust.Shared.GameObjects
_deleteSet.Clear();
FillComponentDict();
}
public void Dispose()
{
_componentFactory.ComponentAdded -= OnComponentAdded;
_componentFactory.ComponentReferenceAdded -= OnComponentReferenceAdded;
}
private void OnComponentAdded(IComponentRegistration obj)
{
_entTraitDict.Add(obj.Type, new Dictionary<EntityUid, Component>());
var netID = obj.NetID;
if (netID.HasValue)
_entNetIdDict.Add(netID.Value, new Dictionary<EntityUid, Component>());
}
private void OnComponentReferenceAdded((IComponentRegistration, Type) obj)
{
_entTraitDict.Add(obj.Item2, new Dictionary<EntityUid, Component>());
}
#region Component Management
@@ -120,17 +147,16 @@ namespace Robust.Shared.GameObjects
if (component.NetID != null)
{
// the main comp grid keeps this in sync
var netId = component.NetID.Value;
_entNetIdDict[netId].Add(uid, component);
// mark the component as dirty for networking
component.Dirty();
ComponentAdded?.Invoke(this, new AddedComponentEventArgs(component));
ComponentAdded?.Invoke(this, new AddedComponentEventArgs(component, uid));
}
_componentDependencyManager.OnComponentAdd(entity, component);
_componentDependencyManager.OnComponentAdd(entity.Uid, component);
component.OnAdd();
@@ -243,8 +269,8 @@ namespace Robust.Shared.GameObjects
component.Running = false;
component.OnRemove();
_componentDependencyManager.OnComponentRemove(_entityManager.GetEntity(uid), component);
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component));
_componentDependencyManager.OnComponentRemove(uid, component);
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component, uid));
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
@@ -270,7 +296,7 @@ namespace Robust.Shared.GameObjects
component.Running = false;
component.OnRemove(); // Sets delete
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component));
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component, component.Owner.Uid));
}
@@ -308,7 +334,7 @@ namespace Robust.Shared.GameObjects
// mark the owning entity as dirty for networking
component.Owner.Dirty();
ComponentDeleted?.Invoke(this, new DeletedComponentEventArgs(component));
ComponentDeleted?.Invoke(this, new DeletedComponentEventArgs(component, entityUid));
}
/// <inheritdoc />
@@ -354,8 +380,7 @@ namespace Robust.Shared.GameObjects
}
}
var ent = _entityManager.GetEntity(uid);
throw new KeyNotFoundException($"Entity {ent} does not have a component of type {type}");
throw new KeyNotFoundException($"Entity {uid} does not have a component of type {type}");
}
/// <inheritdoc />
@@ -371,8 +396,7 @@ namespace Robust.Shared.GameObjects
}
}
var ent = _entityManager.GetEntity(uid);
throw new KeyNotFoundException($"Entity {ent} does not have a component of NetID {netId}");
throw new KeyNotFoundException($"Entity {uid} does not have a component of NetID {netId}");
}
/// <inheritdoc />

View File

@@ -1,6 +1,6 @@
namespace Robust.Shared.GameObjects
{
public class CollisionChangeMessage : EntitySystemMessage
public class CollisionChangeMessage : EntityEventArgs
{
public PhysicsComponent Body { get; }

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics;
@@ -9,7 +9,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Called every tick for colliding bodies. Called once per pair.
/// </summary>
public sealed class CollisionMessage : EntitySystemMessage
public sealed class CollisionMessage : EntityEventArgs
{
public readonly IPhysBody BodyA;
public readonly IPhysBody BodyB;
@@ -65,7 +65,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Sent whenever a <see cref="IPhysBody"/> is changed.
/// </summary>
public sealed class PhysicsUpdateMessage : EntitySystemMessage
public sealed class PhysicsUpdateMessage : EntityEventArgs
{
public PhysicsComponent Component { get; }
@@ -75,7 +75,7 @@ namespace Robust.Shared.GameObjects
}
}
public sealed class FixtureUpdateMessage : EntitySystemMessage
public sealed class FixtureUpdateMessage : EntityEventArgs
{
public PhysicsComponent Body { get; }

View File

@@ -102,6 +102,7 @@ namespace Robust.Shared.GameObjects
if (_bodyType == value)
return;
var oldAnchored = _bodyType == BodyType.Static;
_bodyType = value;
ResetMassData();
@@ -120,8 +121,7 @@ namespace Robust.Shared.GameObjects
RegenerateContacts();
var oldAnchored = _bodyType == BodyType.Static;
var anchored = _bodyType == BodyType.Static;
var anchored = value == BodyType.Static;
if (oldAnchored != anchored)
{
@@ -952,7 +952,7 @@ namespace Robust.Shared.GameObjects
public void ApplyLinearImpulse(in Vector2 impulse)
{
if (_bodyType != BodyType.Dynamic) return;
if ((_bodyType & (BodyType.Dynamic | BodyType.KinematicController)) == 0x0) return;
Awake = true;
LinearVelocity += impulse * _invMass;
@@ -960,7 +960,7 @@ namespace Robust.Shared.GameObjects
public void ApplyAngularImpulse(float impulse)
{
if (_bodyType != BodyType.Dynamic) return;
if ((_bodyType & (BodyType.Dynamic | BodyType.KinematicController)) == 0x0) return;
Awake = true;
AngularVelocity += impulse * InvI;

View File

@@ -1,18 +1,18 @@
namespace Robust.Shared.GameObjects
namespace Robust.Shared.GameObjects
{
public interface IComponentDependencyManager
{
/// <summary>
/// Called when newComp has just been added into entity.
/// </summary>
/// <param name="entity">Entity in which newComp was added</param>
/// <param name="eUid">Entity in which newComp was added</param>
/// <param name="newComp">Added component</param>
public void OnComponentAdd(IEntity entity, IComponent newComp);
public void OnComponentAdd(EntityUid eUid, IComponent newComp);
/// <summary>
/// Called when newComp has just been removed from entity.
/// </summary>
/// <param name="entity">Entity out of which newComp was removed</param>
/// <param name="eUid">Entity out of which newComp was removed</param>
/// <param name="removedComp">Removed component</param>
public void OnComponentRemove(IEntity entity, IComponent removedComp); }
public void OnComponentRemove(EntityUid eUid, IComponent removedComp); }
}

View File

@@ -129,5 +129,6 @@ namespace Robust.Shared.GameObjects
IEnumerable<EntityUid> ChildEntityUids { get; }
Matrix3 GetLocalMatrix();
Matrix3 GetLocalMatrixInv();
void DetachParentToNull();
}
}

View File

@@ -57,9 +57,9 @@ namespace Robust.Shared.GameObjects
EntitySystem.Get<OccluderSystem>().AddOrUpdateEntity(Owner, Owner.Transform.Coordinates);
}
public override void OnRemove()
protected override void Shutdown()
{
base.OnRemove();
base.Shutdown();
var transform = Owner.Transform;
var map = transform.MapID;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
@@ -51,20 +51,6 @@ namespace Robust.Shared.GameObjects
_gridIndex = GridId.Invalid;
}
public override void OnRemove()
{
if(GridIndex != GridId.Invalid)
{
if(_mapManager.GridExists(_gridIndex))
{
Logger.DebugS("map", $"Entity {Owner.Uid} removed grid component, removing bound grid {_gridIndex}");
_mapManager.DeleteGrid(_gridIndex);
}
}
base.OnRemove();
}
/// <param name="player"></param>
/// <inheritdoc />
public override ComponentState GetComponentState(ICommonSession player)

View File

@@ -7,11 +7,13 @@ using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects
{
public class SharedSpriteComponent : Component
public abstract class SharedSpriteComponent : Component
{
public override string Name => "Sprite";
public override uint? NetID => NetIDs.SPRITE;
public abstract bool Visible { get; set; }
/// <summary>
/// The resource path from which all texture paths are relative to.
/// </summary>

View File

@@ -241,7 +241,7 @@ namespace Robust.Shared.GameObjects
{
if (!_parent.IsValid())
{
DebugTools.Assert("Tried to move root node.");
DebugTools.Assert("Parent is invalid while attempting to set WorldPosition - did you try to move root node?");
return;
}
@@ -433,25 +433,6 @@ namespace Robust.Shared.GameObjects
UpdateEntityTree();
}
/// <inheritdoc />
public override void OnRemove()
{
// DeleteEntity modifies our _children collection, we must cache the collection to iterate properly
foreach (var childUid in _children.ToArray())
{
// Recursion: DeleteEntity calls the Transform.OnRemove function of child entities.
Owner.EntityManager.DeleteEntity(childUid);
}
// map does not have a parent node
if (Parent != null)
{
DetachParentToNull();
}
base.OnRemove();
}
public void RunDeferred(Box2 worldAABB)
{
// if we resolved to (close enough) to the OG position then no update.
@@ -525,7 +506,8 @@ namespace Robust.Shared.GameObjects
Dirty();
}
private void DetachParentToNull()
/// <inheritdoc />
public void DetachParentToNull()
{
var oldParent = Parent;
if (oldParent == null)
@@ -872,7 +854,7 @@ namespace Robust.Shared.GameObjects
/// Raised whenever an entity moves.
/// There is no guarantee it will be raised if they move in worldspace, only when moved relative to their parent.
/// </summary>
public class MoveEvent : EntitySystemMessage
public class MoveEvent : HandledEntityEventArgs
{
public MoveEvent(IEntity sender, EntityCoordinates oldPos, EntityCoordinates newPos, Box2? worldAABB = null)
{
@@ -885,7 +867,6 @@ namespace Robust.Shared.GameObjects
public IEntity Sender { get; }
public EntityCoordinates OldPosition { get; }
public EntityCoordinates NewPosition { get; }
public bool Handled { get; set; }
/// <summary>
/// New AABB of the entity.
@@ -896,7 +877,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Raised whenever this entity rotates in relation to their parent.
/// </summary>
public class RotateEvent : EntitySystemMessage
public class RotateEvent : EntityEventArgs
{
public RotateEvent(IEntity sender, Angle oldRotation, Angle newRotation, Box2? worldAABB = null)
{

View File

@@ -310,8 +310,6 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public void Shutdown()
{
EntityManager.ComponentManager.DisposeComponents(Uid);
// Entity manager culls us because we're set to Deleted.
Deleted = true;
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -12,7 +12,7 @@ namespace Robust.Shared.GameObjects
/// EntitySystems communicate with each other.
/// </summary>
[PublicAPI]
public interface IEventBus
public interface IBroadcastEventBus
{
/// <summary>
/// Subscribes an event handler for a event type.
@@ -73,7 +73,7 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
internal interface IEntityEventBus : IEventBus
internal interface IBroadcastEventBusInternal : IBroadcastEventBus
{
/// <summary>
/// Raises all queued events onto the event bus. This needs to be called often.
@@ -91,8 +91,10 @@ namespace Robust.Shared.GameObjects
All = Local | Network,
}
/// <inheritdoc />
internal class EntityEventBus : IEntityEventBus
/// <summary>
/// Implements the event broadcast functions.
/// </summary>
internal partial class EntityEventBus : IBroadcastEventBusInternal
{
private delegate void EventHandler(object ev);

View File

@@ -0,0 +1,263 @@
using System;
using System.Collections.Generic;
namespace Robust.Shared.GameObjects
{
public interface IEventBus : IDirectedEventBus, IBroadcastEventBus { }
public interface IDirectedEventBus
{
void RaiseLocalEvent<TEvent>(EntityUid uid, TEvent args, bool broadcast = true)
where TEvent:EntityEventArgs;
void SubscribeLocalEvent<TComp, TEvent>(ComponentEventHandler<TComp, TEvent> handler)
where TComp : IComponent
where TEvent : EntityEventArgs;
void UnsubscribeLocalEvent<TComp, TEvent>(ComponentEventHandler<TComp, TEvent> handler)
where TComp : IComponent
where TEvent : EntityEventArgs;
}
internal partial class EntityEventBus : IDirectedEventBus, IEventBus, IDisposable
{
private delegate void DirectedEventHandler(EntityUid uid, IComponent comp, EntityEventArgs args);
private IEntityManager _entMan;
private EventTables _eventTables;
/// <summary>
/// Constructs a new instance of <see cref="EntityEventBus"/>.
/// </summary>
/// <param name="entMan">The entity manager to watch for entity/component events.</param>
public EntityEventBus(IEntityManager entMan)
{
_entMan = entMan;
_eventTables = new EventTables(_entMan);
}
/// <inheritdoc />
public void RaiseLocalEvent<TEvent>(EntityUid uid, TEvent args, bool broadcast = true)
where TEvent:EntityEventArgs
{
_eventTables.Dispatch(uid, typeof(TEvent), args);
// we also broadcast it so the call site does not have to.
if(broadcast)
RaiseEvent(EventSource.Local, args);
}
/// <inheritdoc />
public void SubscribeLocalEvent<TComp, TEvent>(ComponentEventHandler<TComp, TEvent> handler)
where TComp : IComponent
where TEvent : EntityEventArgs
{
void EventHandler(EntityUid uid, IComponent comp, EntityEventArgs args)
=> handler(uid, (TComp) comp, (TEvent) args);
_eventTables.Subscribe(typeof(TComp), typeof(TEvent), EventHandler);
}
/// <inheritdoc />
public void UnsubscribeLocalEvent<TComp, TEvent>(ComponentEventHandler<TComp, TEvent> handler)
where TComp : IComponent
where TEvent : EntityEventArgs
{
_eventTables.Unsubscribe(typeof(TComp), typeof(TEvent));
}
private class EventTables : IDisposable
{
private IEntityManager _entMan;
// eUid -> EventType -> { CompType1, ... CompTypeN }
private Dictionary<EntityUid, Dictionary<Type, HashSet<Type>>> _eventTables;
// EventType -> CompType -> Handler
private Dictionary<Type, Dictionary<Type, DirectedEventHandler>> _subscriptions;
// prevents shitcode, get your subscriptions figured out before you start spawning entities
private bool _subscriptionLock;
public EventTables(IEntityManager entMan)
{
_entMan = entMan;
_entMan.EntityAdded += OnEntityAdded;
_entMan.EntityDeleted += OnEntityDeleted;
_entMan.ComponentManager.ComponentAdded += OnComponentAdded;
_entMan.ComponentManager.ComponentRemoved += OnComponentRemoved;
_eventTables = new();
_subscriptions = new();
_subscriptionLock = false;
}
private void OnEntityAdded(object? sender, EntityUid e)
{
AddEntity(e);
}
private void OnEntityDeleted(object? sender, EntityUid e)
{
RemoveEntity(e);
}
private void OnComponentAdded(object? sender, ComponentEventArgs e)
{
_subscriptionLock = true;
AddComponent(e.OwnerUid, e.Component.GetType());
}
private void OnComponentRemoved(object? sender, ComponentEventArgs e)
{
RemoveComponent(e.OwnerUid, e.Component.GetType());
}
public void Subscribe(Type compType, Type eventType, DirectedEventHandler handler)
{
if (_subscriptionLock)
throw new InvalidOperationException("Subscription locked.");
if (!_subscriptions.TryGetValue(compType, out var compSubs))
{
compSubs = new Dictionary<Type, DirectedEventHandler>();
_subscriptions.Add(compType, compSubs);
compSubs.Add(eventType, handler);
}
else
{
if (compSubs.ContainsKey(eventType))
throw new InvalidOperationException($"Duplicate Subscriptions for comp={compType.Name}, event={eventType.Name}");
compSubs.Add(eventType, handler);
}
}
public void Unsubscribe(Type compType, Type eventType)
{
if (_subscriptionLock)
throw new InvalidOperationException("Subscription locked.");
if (!_subscriptions.TryGetValue(compType, out var compSubs))
return;
compSubs.Remove(eventType);
}
private void AddEntity(EntityUid euid)
{
// odds are at least 1 component will subscribe to an event on the entity, so just
// preallocate the table now. Dispatch does not need to check this later.
_eventTables.Add(euid, new Dictionary<Type, HashSet<Type>>());
}
private void RemoveEntity(EntityUid euid)
{
_eventTables.Remove(euid);
}
private void AddComponent(EntityUid euid, Type compType)
{
var eventTable = _eventTables[euid];
if (!_subscriptions.TryGetValue(compType, out var compSubs))
return;
foreach (var kvSub in compSubs)
{
if(!eventTable.TryGetValue(kvSub.Key, out var subscribedComps))
{
subscribedComps = new HashSet<Type>();
eventTable.Add(kvSub.Key, subscribedComps);
}
subscribedComps.Add(compType);
}
}
private void RemoveComponent(EntityUid euid, Type compType)
{
var eventTable = _eventTables[euid];
if (!_subscriptions.TryGetValue(compType, out var compSubs))
return;
foreach (var kvSub in compSubs)
{
if (!eventTable.TryGetValue(kvSub.Key, out var subscribedComps))
return;
subscribedComps.Remove(compType);
}
}
public void Dispatch(EntityUid euid, Type eventType, EntityEventArgs args)
{
var eventTable = _eventTables[euid];
if(!eventTable.TryGetValue(eventType, out var subscribedComps))
return;
foreach (var compType in subscribedComps)
{
if(!_subscriptions.TryGetValue(compType, out var compSubs))
return;
if(!compSubs.TryGetValue(eventType, out var handler))
return;
var component = _entMan.ComponentManager.GetComponent(euid, compType);
handler(euid, component, args);
}
}
public void ClearEntities()
{
_eventTables = new();
_subscriptionLock = false;
}
public void Clear()
{
ClearEntities();
_subscriptions = new();
}
public void Dispose()
{
_entMan.EntityAdded -= OnEntityAdded;
_entMan.EntityDeleted -= OnEntityDeleted;
_entMan.ComponentManager.ComponentAdded -= OnComponentAdded;
_entMan.ComponentManager.ComponentRemoved -= OnComponentRemoved;
// punishment for use-after-free
_entMan = null!;
_eventTables = null!;
_subscriptions = null!;
}
}
/// <inheritdoc />
public void ClearEventTables()
{
_eventTables.ClearEntities();
}
public void Dispose()
{
_eventTables.Dispose();
_eventTables = null!;
_entMan = null!;
}
}
public delegate void ComponentEventHandler<in TComp, in TEvent>(EntityUid uid, TComp component, TEvent args)
where TComp : IComponent
where TEvent : EntityEventArgs;
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
@@ -11,7 +11,16 @@ namespace Robust.Shared.GameObjects
public delegate void EntitySessionEventHandler<in T>(T msg, EntitySessionEventArgs args);
[Serializable, NetSerializable]
public class EntityEventArgs : EventArgs { }
public abstract class EntityEventArgs { }
[Serializable, NetSerializable]
public abstract class HandledEntityEventArgs : EntityEventArgs
{
/// <summary>
/// If this message has already been "handled" by a previous system.
/// </summary>
public bool Handled { get; set; }
}
public readonly struct EntitySessionEventArgs
{

View File

@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Prometheus;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
@@ -47,15 +48,28 @@ namespace Robust.Shared.GameObjects
protected readonly List<Entity> AllEntities = new();
private readonly EntityEventBus _eventBus = new();
private EntityEventBus _eventBus = null!;
/// <inheritdoc />
public IEventBus EventBus => _eventBus;
public event EventHandler<EntityUid>? EntityAdded;
public event EventHandler<EntityUid>? EntityInitialized;
public event EventHandler<EntityUid>? EntityDeleted;
public bool Started { get; protected set; }
/// <summary>
/// Constructs a new instance of <see cref="EntityManager"/>.
/// </summary>
public EntityManager()
{
}
public virtual void Initialize()
{
_eventBus = new EntityEventBus(this);
EntityNetworkManager.SetupNetworking();
EntityNetworkManager.ReceivedComponentMessage += (sender, compMsg) => DispatchComponentMessage(compMsg);
EntityNetworkManager.ReceivedSystemMessage += (sender, systemMsg) => EventBus.RaiseEvent(EventSource.Network, systemMsg);
@@ -71,6 +85,7 @@ namespace Robust.Shared.GameObjects
public virtual void Shutdown()
{
FlushEntities();
_eventBus.ClearEventTables();
EntitySystemManager.Shutdown();
Started = false;
_componentManager.Clear();
@@ -215,9 +230,43 @@ namespace Robust.Shared.GameObjects
/// <param name="e">Entity to remove</param>
public virtual void DeleteEntity(IEntity e)
{
e.Shutdown();
// Networking blindly spams entities at this function, they can already be
// deleted from being a child of a previously deleted entity
// TODO: Why does networking need to send deletes for child entities?
if (e.Deleted)
return;
RecursiveDeleteEntity(e);
}
private void RecursiveDeleteEntity(IEntity entity)
{
if(entity.Deleted) //TODO: Why was this still a child if it was already deleted?
return;
var transform = entity.Transform;
// DeleteEntity modifies our _children collection, we must cache the collection to iterate properly
foreach (var childTransform in transform.Children.ToArray())
{
// Recursion Alert
RecursiveDeleteEntity(childTransform.Owner);
}
// Dispose all my components, in a safe order so transform is available
ComponentManager.DisposeComponents(entity.Uid);
// map does not have a parent node, everything else needs to be detached
if (transform.ParentUid != EntityUid.Invalid)
{
// Detach from my parent, if any
transform.DetachParentToNull();
}
entity.Shutdown();
EntityDeleted?.Invoke(this, entity.Uid);
}
public void DeleteEntity(EntityUid uid)
{
if (TryGetEntity(uid, out var entity))
@@ -239,7 +288,7 @@ namespace Robust.Shared.GameObjects
{
foreach (var e in GetEntities())
{
e.Shutdown();
DeleteEntity(e);
}
CullDeletedEntities();
@@ -281,6 +330,9 @@ namespace Robust.Shared.GameObjects
var entity = new Entity(this, uid.Value);
// we want this called before adding components
EntityAdded?.Invoke(this, entity.Uid);
// allocate the required MetaDataComponent
_componentManager.AddComponent<MetaDataComponent>(entity);
@@ -326,6 +378,7 @@ namespace Robust.Shared.GameObjects
try
{
InitializeEntity(entity);
EntityInitialized?.Invoke(this, entity.Uid);
StartEntity(entity);
}
catch (Exception e)
@@ -367,7 +420,7 @@ namespace Robust.Shared.GameObjects
}
}
#endregion Entity Management
#endregion Entity Management
private void DispatchComponentMessage(NetworkComponentMessage netMsg)
{
@@ -393,7 +446,7 @@ namespace Robust.Shared.GameObjects
protected abstract EntityUid GenerateEntityUid();
#region Spatial Queries
#region Spatial Queries
/// <inheritdoc />
public bool AnyEntitiesIntersecting(MapId mapId, Box2 box, bool approximate = false)
@@ -562,10 +615,10 @@ namespace Robust.Shared.GameObjects
}
}
#endregion
#endregion
#region Entity DynamicTree
#region Entity DynamicTree
private readonly Dictionary<MapId, DynamicTree<IEntity>> _entityTreesPerMap =
new();
@@ -651,7 +704,7 @@ namespace Robust.Shared.GameObjects
return new Box2(pos, pos);
}
#endregion
#endregion
public virtual void Update()
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -96,27 +96,47 @@ namespace Robust.Shared.GameObjects
EntityManager.EventBus.RaiseEvent(EventSource.Local, message);
}
protected void QueueLocalEvent(EntitySystemMessage message)
protected void QueueLocalEvent(EntityEventArgs message)
{
EntityManager.EventBus.QueueEvent(EventSource.Local, message);
}
protected void RaiseNetworkEvent(EntitySystemMessage message)
protected void RaiseNetworkEvent(EntityEventArgs message)
{
EntityNetworkManager.SendSystemNetworkMessage(message);
}
protected void RaiseNetworkEvent(EntitySystemMessage message, INetChannel channel)
protected void RaiseNetworkEvent(EntityEventArgs message, INetChannel channel)
{
EntityNetworkManager.SendSystemNetworkMessage(message, channel);
}
protected Task<T> AwaitNetworkEvent<T>(CancellationToken cancellationToken)
where T : EntitySystemMessage
where T : EntityEventArgs
{
return EntityManager.EventBus.AwaitEvent<T>(EventSource.Network, cancellationToken);
}
protected void SubscribeLocalEvent<TComp, TEvent>(ComponentEventHandler<TComp, TEvent> handler)
where TComp : IComponent
where TEvent : EntityEventArgs
{
EntityManager.EventBus.SubscribeLocalEvent(handler);
}
protected void UnsubscribeLocalEvent<TComp, TEvent>(ComponentEventHandler<TComp, TEvent> handler)
where TComp : IComponent
where TEvent : EntityEventArgs
{
EntityManager.EventBus.UnsubscribeLocalEvent(handler);
}
protected void RaiseLocalEvent<TEvent>(EntityUid uid, TEvent args, bool broadcast = true)
where TEvent : EntityEventArgs
{
EntityManager.EventBus.RaiseLocalEvent(uid, args, broadcast);
}
#endregion
#region Static Helpers

View File

@@ -1,10 +0,0 @@
using System;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects
{
[Serializable, NetSerializable]
public class EntitySystemMessage : EntityEventArgs
{
}
}

View File

@@ -11,7 +11,7 @@ namespace Robust.Shared.GameObjects
// Sending bus names and file names as strings is expensive and can be optimized.
// Also there's redundant fields in AudioParams in most cases.
[Serializable, NetSerializable]
public abstract class AudioMessage : EntitySystemMessage
public abstract class AudioMessage : EntityEventArgs
{
public uint Identifier { get; set; }
public string FileName { get; set; }
@@ -19,7 +19,7 @@ namespace Robust.Shared.GameObjects
}
[Serializable, NetSerializable]
public class StopAudioMessageClient : EntitySystemMessage
public class StopAudioMessageClient : EntityEventArgs
{
public uint Identifier {get; set;}
}

View File

@@ -6,7 +6,7 @@ using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects
{
[Serializable, NetSerializable]
public class EffectSystemMessage : EntitySystemMessage
public class EffectSystemMessage : EntityEventArgs
{
/// <summary>
/// Path to the texture used for the effect.

View File

@@ -3,7 +3,7 @@
/// <summary>
/// Raised when an entity parent is changed.
/// </summary>
public class EntParentChangedMessage : EntitySystemMessage
public class EntParentChangedMessage : EntityEventArgs
{
/// <summary>
/// Entity that was adopted. The transform component has a property with the new parent.

View File

@@ -1,6 +1,6 @@
namespace Robust.Shared.GameObjects
{
public sealed class EntityInitializedMessage : EntitySystemMessage
public sealed class EntityInitializedMessage : EntityEventArgs
{
public IEntity Entity { get; }

View File

@@ -1,6 +1,6 @@
namespace Robust.Shared.GameObjects
{
internal sealed class TransformStartLerpMessage : EntitySystemMessage
internal sealed class TransformStartLerpMessage : EntityEventArgs
{
public TransformStartLerpMessage(TransformComponent transform)
{

View File

@@ -49,6 +49,10 @@ namespace Robust.Shared.GameObjects
/// <seealso cref="IComponent" />
public interface IComponentFactory
{
event Action<IComponentRegistration> ComponentAdded;
event Action<(IComponentRegistration, Type)> ComponentReferenceAdded;
event Action<string> ComponentIgnoreAdded;
/// <summary>
/// All IComponent types that are currently registered to this factory.
/// </summary>

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
@@ -9,7 +9,7 @@ namespace Robust.Shared.GameObjects
/// Holds a collection of ECS components that are attached to entities.
/// </summary>
[PublicAPI]
public interface IComponentManager
public interface IComponentManager : IDisposable
{
/// <summary>
/// A component was added to the manager.

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Prometheus;
using Robust.Shared.Map;
@@ -31,6 +32,10 @@ namespace Robust.Shared.GameObjects
#region Entity Management
event EventHandler<EntityUid>? EntityAdded;
event EventHandler<EntityUid>? EntityInitialized;
event EventHandler<EntityUid>? EntityDeleted;
IEntity CreateEntityUninitialized(string? prototypeName);
IEntity CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates);

View File

@@ -44,9 +44,9 @@ namespace Robust.Shared.GameObjects
/// Server: Use the alternative overload to send to a single client.
/// </summary>
/// <param name="message">Message that should be sent.</param>
void SendSystemNetworkMessage(EntitySystemMessage message);
void SendSystemNetworkMessage(EntityEventArgs message);
void SendSystemNetworkMessage(EntitySystemMessage message, uint sequence)
void SendSystemNetworkMessage(EntityEventArgs message, uint sequence)
{
throw new NotSupportedException();
}
@@ -60,7 +60,7 @@ namespace Robust.Shared.GameObjects
/// <exception cref="NotSupportedException">
/// Thrown if called on the client.
/// </exception>
void SendSystemNetworkMessage(EntitySystemMessage message, INetChannel channel);
void SendSystemNetworkMessage(EntityEventArgs message, INetChannel channel);
/// <summary>
/// Sends out queued messages based on current tick.

View File

@@ -46,7 +46,7 @@ namespace Robust.Shared.GameObjects
grid.GridEntityId != entity.Uid)
{
// Also this may deparent if 2 entities are parented but not using containers so fix that
if (grid.GridEntityId != transform.ParentUid)
if (grid.Index != transform.GridID)
{
transform.AttachParent(EntityManager.GetEntity(grid.GridEntityId));
}

View File

@@ -179,11 +179,11 @@ namespace Robust.Shared.GameObjects
if (!message.Entity.TryGetComponent(out PhysicsComponent? physicsComponent))
return;
physicsComponent.ClearJoints();
var oldMapId = message.OldMapId;
if (oldMapId != MapId.Nullspace)
{
_maps[oldMapId].RemoveBody(physicsComponent);
physicsComponent.ClearJoints();
}
var newMapId = message.Entity.Transform.MapID;
@@ -236,6 +236,7 @@ namespace Robust.Shared.GameObjects
var mapId = message.Container.Owner.Transform.MapID;
physicsComponent.ClearJoints();
_maps[mapId].RemoveBody(physicsComponent);
}

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace Robust.Shared.GameObjects
{
@@ -31,7 +31,6 @@ namespace Robust.Shared.GameObjects
continue;
RaiseLocalEvent(ev);
ev.Handled = true;
}
}
}

View File

@@ -10,7 +10,7 @@ namespace Robust.Shared.Input
/// Abstract class that all Input Commands derive from.
/// </summary>
[Serializable, NetSerializable]
public abstract class InputCmdMessage : EntitySystemMessage, IComparable<InputCmdMessage>
public abstract class InputCmdMessage : EntityEventArgs, IComparable<InputCmdMessage>
{
/// <summary>
/// Client tick this was created.

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
@@ -253,7 +253,11 @@ namespace Robust.Shared.Map
// remove the existing client entity.
var cEntity = _entityManager.GetEntity(grid.GridEntityId);
var cGridComp = cEntity.GetComponent<IMapGridComponent>();
cGridComp.ClearGridId();
// prevents us from deleting the grid when deleting the grid entity
if(cEntity.Uid.IsClientSide())
cGridComp.ClearGridId();
cEntity.Delete(); // normal entities are already parented to the shared comp, client comp has no children
var gridComps = _entityManager.ComponentManager.EntityQuery<IMapGridComponent>(true);

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -86,6 +86,8 @@ namespace Robust.Shared.Map
Logger.DebugS("map", "Starting...");
_entityManager.ComponentManager.ComponentRemoved += OnComponentRemoved;
if (!_maps.Contains(MapId.Nullspace))
{
CreateMap(MapId.Nullspace);
@@ -104,6 +106,24 @@ namespace Robust.Shared.Map
DebugTools.Assert(!GridExists(GridId.Invalid));
}
private void OnComponentRemoved(object? sender, ComponentEventArgs e)
{
if(e.Component is not IMapGridComponent)
return;
var gridComp = (IMapGridComponent)e.Component;
var gridIndex = gridComp.GridIndex;
if (gridIndex != GridId.Invalid)
{
if (GridExists(gridIndex))
{
Logger.DebugS("map", $"Entity {e.OwnerUid} removed grid component, removing bound grid {gridIndex}");
DeleteGrid(gridIndex);
}
}
}
/// <inheritdoc />
public void Shutdown()
{
@@ -112,6 +132,8 @@ namespace Robust.Shared.Map
#endif
Logger.DebugS("map", "Stopping...");
_entityManager.ComponentManager.ComponentRemoved -= OnComponentRemoved;
foreach (var map in _maps.ToArray())
{
if (map != MapId.Nullspace)

View File

@@ -22,7 +22,7 @@ namespace Robust.Shared.Network.Messages
public EntityMessageType Type { get; set; }
public EntitySystemMessage SystemMessage { get; set; }
public EntityEventArgs SystemMessage { get; set; }
public ComponentMessage ComponentMessage { get; set; }
public EntityUid EntityUid { get; set; }
@@ -43,7 +43,7 @@ namespace Robust.Shared.Network.Messages
var serializer = IoCManager.Resolve<IRobustSerializer>();
int length = buffer.ReadVariableInt32();
using var stream = buffer.ReadAlignedMemory(length);
SystemMessage = serializer.Deserialize<EntitySystemMessage>(stream);
SystemMessage = serializer.Deserialize<EntityEventArgs>(stream);
}
break;

View File

@@ -2,7 +2,7 @@ using Robust.Shared.GameObjects;
namespace Robust.Shared.Physics
{
public sealed class DebugDrawRayMessage : EntitySystemMessage
public sealed class DebugDrawRayMessage : EntityEventArgs
{
public DebugRayData Data { get; }

View File

@@ -47,12 +47,11 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
private struct ContactEnumerator : IEnumerator<Contact>
{
private ContactHead? _head;
private Contact? _current;
public Contact? Current => _current;
object? IEnumerator.Current => _current;
private ContactHead _head;
private Contact _current;
public Contact Current => _current;
object IEnumerator.Current => _current;
public ContactEnumerator(ContactHead contact)
{
@@ -67,14 +66,14 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
public bool MoveNext()
{
_current = _current?.Next;
return (_current != _head);
_current = _current.Next!;
return _current != _head;
}
public void Dispose()
{
_head = null;
_current = null;
_head = null!;
_current = null!;
}
}

View File

@@ -36,7 +36,6 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.Physics.Dynamics.Joints
{
// TODO: FOR THIS LICENCE ALSO PUT BOX2D ON IT.
// 1-D rained system
// m (v2 - v1) = lambda
// v2 + (beta/h) * x1 + gamma * lambda = 0, gamma has units of inverse mass.
@@ -87,6 +86,8 @@ namespace Robust.Shared.Physics.Dynamics.Joints
[NonSerialized] private float _currentLength;
[NonSerialized] private float _softMass;
[NonSerialized] private float _linearSlop;
public override JointType JointType => JointType.Distance;
/// <summary>
@@ -110,6 +111,7 @@ namespace Robust.Shared.Physics.Dynamics.Joints
var configManager = IoCManager.Resolve<IConfigurationManager>();
Length = MathF.Max(configManager.GetCVar(CVars.LinearSlop), (BodyB.GetWorldPoint(anchorB) - BodyA.GetWorldPoint(anchorA)).Length);
WarmStarting = configManager.GetCVar(CVars.WarmStarting);
_linearSlop = configManager.GetCVar(CVars.LinearSlop);
_minLength = _length;
_maxLength = _length;
}
@@ -557,7 +559,7 @@ namespace Robust.Shared.Physics.Dynamics.Joints
data.Positions[_indexB] = cB;
data.Angles[_indexB] = aB;
return MathF.Abs(C) < IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.LinearSlop);
return MathF.Abs(C) < _linearSlop;
}
public bool Equals(DistanceJoint? other)

View File

@@ -61,7 +61,7 @@ namespace Robust.Shared.Physics.Dynamics.Joints
public abstract class Joint : IEquatable<Joint>
{
/// <summary>
/// Indicate if this join is enabled or not. Disabling a joint
/// Indicate if this joint is enabled or not. Disabling a joint
/// means it is still in the simulation, but inactive.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
@@ -212,12 +212,7 @@ namespace Robust.Shared.Physics.Dynamics.Joints
return;
Enabled = false;
// BodyA
/* TODO: Dis, just use comp messages and a system message
if (Broke != null)
Broke(this, MathF.Sqrt(jointErrorSquared));
*/
BodyA.Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new JointBreakMessage(this, MathF.Sqrt(jointErrorSquared)));
}
internal abstract void SolveVelocityConstraints(SolverData data);
@@ -239,5 +234,17 @@ namespace Robust.Shared.Physics.Dynamics.Joints
CollideConnected == other.CollideConnected &&
MathHelper.CloseTo(_breakpoint, other._breakpoint);
}
public sealed class JointBreakMessage : EntityEventArgs
{
public Joint Joint { get; }
public float JointError { get; }
public JointBreakMessage(Joint joint, float jointError)
{
Joint = joint;
JointError = jointError;
}
}
}
}

View File

@@ -342,6 +342,8 @@ namespace Robust.Shared.Physics.Dynamics
{
bool collideConnected = joint.CollideConnected;
// TODO: See above how much I hate joints rn
if (!Joints.Contains(joint)) continue;
// Remove from the world list.
Joints.Remove(joint);
@@ -531,11 +533,13 @@ namespace Robust.Shared.Physics.Dynamics
var body = _stack[--stackCount];
_island.Add(body);
_islandSet.Add(body);
body.Awake = true;
// Static bodies don't propagate islands
if (body.BodyType == BodyType.Static) continue;
// As static bodies can never be awake (unlike Farseer) we'll set this after the check.
body.Awake = true;
for (var contactEdge = body.ContactEdges; contactEdge != null; contactEdge = contactEdge.Next)
{
var contact = contactEdge.Contact!;

View File

@@ -3,7 +3,7 @@ using Robust.Shared.GameObjects;
namespace Robust.Shared.Physics
{
internal class IslandSolveMessage : EntitySystemMessage
internal class IslandSolveMessage : EntityEventArgs
{
public List<IPhysBody> Bodies { get; }

View File

@@ -3,7 +3,7 @@ using Robust.Shared.GameObjects;
namespace Robust.Shared.Physics
{
// Real pros use the system messages
public sealed class PhysicsWakeMessage : EntitySystemMessage
public sealed class PhysicsWakeMessage : EntityEventArgs
{
public PhysicsComponent Body { get; }
@@ -13,7 +13,7 @@ namespace Robust.Shared.Physics
}
}
public sealed class PhysicsSleepMessage : EntitySystemMessage
public sealed class PhysicsSleepMessage : EntityEventArgs
{
public PhysicsComponent Body { get; }

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