Compare commits

...

25 Commits

Author SHA1 Message Date
Swept
95ba58f0a4 Fixes SpriteComponent error reporting 2021-07-14 22:41:51 +00:00
metalgearsloth
f780f04784 Deprecate PhysShapeGrid (#1862)
* Grid fixtures

* termp

* Fixes

* Test reversion reversion?

* Tests

* Fix Equals

* Slight box2i cleanup

* Better initializer

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

* Refactor gucci

* Done?

* Fix most tests

* nothing suss

* Vera single-handedly saving shuttles

* fex

* Copy lookup from shuttles

* sys

* comp recursion
2021-07-12 13:39:02 +02:00
Vera Aguilera Puerto
8b013cb424 Fix engine integration tests not generating Net IDs. 2021-07-12 10:42:38 +02:00
Vera Aguilera Puerto
b67d24efee ITileDefinition now has a Path string that ClydeTileDefinitionManager uses (#1860) 2021-07-12 10:29:13 +02:00
mirrorcult
d992e47f30 Remove NetMessage deprecated boilerplate entirely (#1830)
* Remove NetMessage boilerplate in favor of virtual properties

* forgot some stuff
2021-07-12 10:28:46 +02:00
Acruid
dadd7b4cc3 Remove Static Component NetIds (#1842)
* ComponentNames are not sent over the network when components are created.

* Removed ComponentStates array from EntityState, now the state is stored directly inside the CompChange struct.

* Remove the unnecessary NetID property from ComponentState.

* Remove Component.NetworkSynchronizeExistence.

* Change GetNetComponents to return both the component and the component NetId.

* Remove public usages of the Component.NetID property.

* Adds the NetIDAttribute that can be applied to components.

* Removed Component.NetID.

* Revert changes to GetComponentState and how prediction works.

* Adds component netID automatic generation.

* Modifies ClientConsoleHost so that commands can be called before Initialize().

* Completely remove static NetIds.

* Renamed NetIDAttribute to NetworkedComponentAttribute.

* Fixing unit tests.
2021-07-12 10:23:13 +02:00
Paul Ritter
baef2bc7f8 some misc serv3 fixes (#1861)
adds a typeserializer
adds null parser to read
adds another overload for deserializationresult.value

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-07-11 15:44:12 +02:00
metalgearsloth
e0b1a7d64a Add Contains Vector2 method to Box2Rotated (#1851) 2021-07-11 22:49:59 +10:00
metalgearsloth
aea5f83002 Preserve world velocities on parent change (#1859)
* Preserve world velocities on parent change

* Faster func

* Test

* lag

* uhh LAAAGGG
2021-07-11 12:25:02 +10:00
Swept
7df2d1f430 Fixes console loc string (#1858) 2021-07-09 15:13:30 +02:00
Vera Aguilera Puerto
d216c3a1f6 VV command can now get IoC services that don't have an interface. 2021-07-07 18:03:20 +02:00
metalgearsloth
986ec3ef06 Do showbb transforms in the iterator (#1847) 2021-07-04 19:52:21 +02:00
metalgearsloth
60cec9cb84 Make showbb awakeness binary (#1846)
Makes it easier to spot mapping issues because otherwise it might have a massive sleep timer and not be easily visible.
2021-07-04 22:31:34 +10:00
Vera Aguilera Puerto
c06707d519 Adds ServerOptions, improve GameControllerOptions, fix engine integration tests (#1844)
* Adds ServerOptions, improve GameControllerOptions, fix engine integration tests

* Do component auto-registration in engine integration tests by default

* Fix integration tests on content, register components ONLY if not contentstarted or options are null

* Add integration test for engine integration tests working correctly

* Move cvar overrides out of content and into engine.
2021-07-03 15:19:46 +02:00
Pieter-Jan Briers
63128324ab Stop RunTicks overriding tick deltas in integration tests.
Now listens to game timing tick rate.
2021-07-03 13:06:19 +02:00
Pieter-Jan Briers
abea3024b4 Raise event when entity paused state changes. 2021-07-02 17:19:34 +02:00
Pieter-Jan Briers
07dafeb6cd Can now rotate anchored entities 2021-07-02 01:16:25 +02:00
Pieter-Jan Briers
a726d42ae3 Fix ResetPredictedEntities applying data early in some cases.
FullState was getting updated before ResetPredictedEntities ran instead of after. So ResetPredictedEntities was applying data to stuff like containers that depends on entities that hadn't been made yet.
2021-07-01 12:16:18 +02:00
metalgearsloth
d02d186a2f Fix light pop-in 2 (#1798)
* Fix light pop-in

Doh

* Uniqueness

* Refactor to be less bad

* Better perf

* Fix perf problems

* Hide debug commands under preprocessor

Can't imagine anyone using this during live-game.

* CVars
2021-06-29 22:29:47 +10:00
154 changed files with 2374 additions and 1145 deletions

View File

@@ -4,4 +4,8 @@ entity-spawn-window-title = Entity Spawn Panel
entity-spawn-window-search-bar-placeholder = search
entity-spawn-window-clear-button = Clear
entity-spawn-window-erase-button-text = Erase Mode
entity-spawn-window-override-menu-tooltip = Override placement
entity-spawn-window-override-menu-tooltip = Override placement
## Console
console-line-edit-placeholder = Command Here

View File

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

View File

@@ -46,13 +46,15 @@ namespace Robust.Client
IoCManager.Register<IClientMapManager, ClientMapManager>();
IoCManager.Register<IEntityManager, ClientEntityManager>();
IoCManager.Register<IEntityLookup, EntityLookup>();
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
IoCManager.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();
IoCManager.Register<GameController, GameController>();
IoCManager.Register<IGameController, GameController>();
IoCManager.Register<IGameControllerInternal, GameController>();
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
IoCManager.Register<IResourceManager, ResourceCache>();
IoCManager.Register<IResourceManagerInternal, ResourceCache>();
IoCManager.Register<IResourceCache, ResourceCache>();
@@ -72,8 +74,6 @@ namespace Robust.Client
IoCManager.Register<IDebugDrawingManager, DebugDrawingManager>();
IoCManager.Register<ILightManager, LightManager>();
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IMidiManager, MidiManager>();
IoCManager.Register<IAuthManager, AuthManager>();
switch (mode)

View File

@@ -49,7 +49,11 @@ namespace Robust.Client.Console
NetManager.RegisterNetMessage<MsgConCmdAck>(HandleConCmdAck);
NetManager.RegisterNetMessage<MsgConCmd>(ProcessCommand);
Reset();
_requestedCommands = false;
NetManager.Connected += OnNetworkConnected;
LoadConsoleCommands();
SendServerCommandRequest();
LogManager.RootSawmill.AddHandler(new DebugConsoleLogHandler(this));
}
@@ -61,17 +65,6 @@ namespace Robust.Client.Console
ExecuteCommand(null, text);
}
/// <inheritdoc />
public void Reset()
{
AvailableCommands.Clear();
_requestedCommands = false;
NetManager.Connected += OnNetworkConnected;
LoadConsoleCommands();
SendServerCommandRequest();
}
/// <inheritdoc />
public event EventHandler<AddStringArgs>? AddString;
@@ -97,7 +90,7 @@ namespace Robust.Client.Console
return;
// echo the command locally
WriteError(null, "> " + command);
WriteLine(null, "> " + command);
//Commands are processed locally and then sent to the server to be processed there again.
var args = new List<string>();
@@ -142,6 +135,9 @@ namespace Robust.Client.Console
private void OutputText(string text, bool local, bool error)
{
AddString?.Invoke(this, new AddStringArgs(text, local, error));
var level = error ? LogLevel.Warning : LogLevel.Info;
Logger.LogS(level, "CON", text);
}
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)

View File

@@ -77,7 +77,7 @@ namespace Robust.Client.Console.Commands
message.Append($"net ID: {registration.NetID}");
}
message.Append($", NSE: {registration.NetworkSynchronizeExistence}, references:");
message.Append($", References:");
shell.WriteLine(message.ToString());

View File

@@ -0,0 +1,19 @@
#if DEBUG
using Robust.Client.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
namespace Robust.Client.Console.Commands
{
internal sealed class LightDebugCommand : IConsoleCommand
{
public string Command => "lightbb";
public string Description => "Toggles whether to show light bounding boxes";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugLightTreeSystem>().Enabled ^= true;
}
}
}
#endif

View File

@@ -11,11 +11,6 @@ namespace Robust.Client.Console
/// </summary>
void Initialize();
/// <summary>
/// Resets the console to a post-initialized state.
/// </summary>
void Reset();
event EventHandler<AddStringArgs> AddString;
event EventHandler<AddFormattedMessageArgs> AddFormatted;

View File

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

View File

@@ -7,13 +7,13 @@ namespace Robust.Client
#if FULL_RELEASE
throw new System.InvalidOperationException("ContentStart.Start is not available on a full release.");
#else
GameController.Start(args, true);
GameController.Start(args, new GameControllerOptions(), true);
#endif
}
public static void StartLibrary(string[] args, GameControllerOptions options)
{
GameController.Start(args, true, null, options);
GameController.Start(args, options, true, null);
}
}
}

View File

@@ -177,8 +177,9 @@ namespace Robust.Client.Debugging
foreach (var fixture in physBody.Fixtures)
{
var shape = fixture.Shape;
var sleepPercent = physBody.Awake ? physBody.SleepTime / sleepThreshold : 1.0f;
var sleepPercent = physBody.Awake ? 0.0f : 1.0f;
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
drawing.SetTransform(in Matrix3.Identity);
}
foreach (var joint in physBody.Joints)
@@ -187,6 +188,7 @@ namespace Robust.Client.Debugging
drawnJoints.Add(joint);
joint.DebugDraw(drawing, in viewport);
drawing.SetTransform(in Matrix3.Identity);
}
if (worldBox.Contains(mouseWorldPos))

View File

@@ -11,7 +11,7 @@ namespace Robust.Client
{
public void Main(IMainArgs args)
{
Start(args.Args, contentStart: false, args);
Start(args.Args, new GameControllerOptions(), contentStart: false, args);
}
}
}

View File

@@ -22,10 +22,10 @@ namespace Robust.Client
public static void Main(string[] args)
{
Start(args);
Start(args, new GameControllerOptions());
}
public static void Start(string[] args, bool contentStart = false, IMainArgs? loaderArgs=null, GameControllerOptions? options = null)
public static void Start(string[] args, GameControllerOptions options, bool contentStart = false, IMainArgs? loaderArgs=null)
{
if (_hasStarted)
{
@@ -40,7 +40,7 @@ namespace Robust.Client
}
}
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions? options)
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions options)
{
IoCManager.InitThread();
@@ -51,15 +51,13 @@ namespace Robust.Client
var gc = IoCManager.Resolve<GameController>();
gc.SetCommandLineArgs(args);
gc._loaderArgs = loaderArgs;
if(options != null)
gc.Options = options;
// When the game is ran with the startup executable being content,
// we have to disable the separate load context.
// Otherwise the content assemblies will be loaded twice which causes *many* fun bugs.
gc.ContentStart = contentStart;
gc.Run(mode);
gc.Run(mode, options);
}
public void OverrideMainLoop(IGameLoop gameLoop)
@@ -67,9 +65,9 @@ namespace Robust.Client
_mainLoop = gameLoop;
}
public void Run(DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null)
public void Run(DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
{
if (!StartupSystemSplash(logHandlerFactory))
if (!StartupSystemSplash(options, logHandlerFactory))
{
Logger.Fatal("Failed to start game controller!");
return;

View File

@@ -75,8 +75,6 @@ namespace Robust.Client
public GameControllerOptions Options { get; private set; } = new();
public InitialLaunchState LaunchState { get; private set; } = default!;
public bool LoadConfigAndUserData { get; set; } = true;
public void SetCommandLineArgs(CommandLineArgs args)
{
_commandLineArgs = args;
@@ -95,7 +93,7 @@ namespace Robust.Client
_modLoader.SetUseLoadContext(!ContentStart);
_modLoader.SetEnableSandboxing(Options.Sandboxing);
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), Options.ContentModulePrefix))
if (!_modLoader.TryLoadModulesFrom(Options.AssemblyDirectory, Options.ContentModulePrefix))
{
Logger.Fatal("Errors while loading content assemblies.");
return false;
@@ -196,8 +194,9 @@ namespace Robust.Client
return true;
}
internal bool StartupSystemSplash(Func<ILogHandler>? logHandlerFactory)
internal bool StartupSystemSplash(GameControllerOptions options, Func<ILogHandler>? logHandlerFactory)
{
Options = options;
ReadInitialLaunchState();
SetupLogging(_logManager, logHandlerFactory ?? (() => new ConsoleLogHandler()));
@@ -234,7 +233,7 @@ namespace Robust.Client
_configurationManager.LoadCVarsFromAssembly(typeof(GameController).Assembly); // Client
_configurationManager.LoadCVarsFromAssembly(typeof(IConfigurationManager).Assembly); // Shared
if (LoadConfigAndUserData)
if (Options.LoadConfigAndUserData)
{
var configFile = Path.Combine(userDataDir, Options.ConfigFileName);
if (File.Exists(configFile))
@@ -258,13 +257,13 @@ namespace Robust.Client
ProfileOptSetup.Setup(_configurationManager);
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory,
_loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory, Options.AssemblyDirectory,
Options.LoadContentResources, _loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
if (_loaderArgs != null)
{

View File

@@ -42,6 +42,11 @@ namespace Robust.Client
/// </summary>
public string ContentBuildDirectory { get; init; } = "Content.Client";
/// <summary>
/// Directory to load all assemblies from.
/// </summary>
public ResourcePath AssemblyDirectory { get; init; } = new(@"/Assemblies/");
/// <summary>
/// Directory to load all prototypes from.
/// </summary>
@@ -52,6 +57,16 @@ namespace Robust.Client
/// </summary>
public bool ResourceMountDisabled { get; init; } = false;
/// <summary>
/// Whether to mount content resources when not on FULL_RELEASE.
/// </summary>
public bool LoadContentResources { get; init; } = true;
/// <summary>
/// Whether to load config and user data.
/// </summary>
public bool LoadConfigAndUserData { get; init; } = true;
/// <summary>
/// Whether to disable command line args server auto-connecting.
/// </summary>

View File

@@ -1,12 +1,15 @@
using Robust.Shared.Console;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Physics;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
namespace Robust.Client.GameObjects
{
public class ClientComponentFactory : ComponentFactory
internal class ClientComponentFactory : ComponentFactory
{
public ClientComponentFactory()
public ClientComponentFactory(IDynamicTypeFactoryInternal typeFactory, IReflectionManager reflectionManager, IConsoleHost conHost)
: base(typeFactory, reflectionManager, conHost)
{
// Required for the engine to work
RegisterIgnore("KeyBindingInput");

View File

@@ -109,13 +109,15 @@ namespace Robust.Client.GameObjects
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message)
{
if (!component.NetID.HasValue)
var netId = ComponentFactory.GetRegistration(component.GetType()).NetID;
if (!netId.HasValue)
throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component));
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.ComponentMessage;
msg.EntityUid = entity.Uid;
msg.NetId = component.NetID.Value;
msg.NetId = netId.Value;
msg.ComponentMessage = message;
msg.SourceTick = _gameTiming.CurTick;

View File

@@ -3,8 +3,11 @@ using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Animations;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
@@ -15,12 +18,12 @@ namespace Robust.Client.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(IPointLightComponent))]
[NetworkedComponent()]
public class PointLightComponent : Component, IPointLightComponent, ISerializationHooks
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
public override string Name => "PointLight";
public override uint? NetID => NetIDs.POINT_LIGHT;
internal bool TreeUpdateQueued { get; set; }
@@ -168,6 +171,8 @@ namespace Robust.Client.GameObjects
get => _radius;
set
{
if (MathHelper.CloseTo(value, _radius)) return;
_radius = MathF.Max(value, 0.01f); // setting radius to 0 causes exceptions, so just use a value close enough to zero that it's unnoticeable.
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedEvent(this));
}
@@ -181,17 +186,8 @@ namespace Robust.Client.GameObjects
Mask = null;
}
/// <summary>
/// What MapId we are intersecting for RenderingTreeSystem.
/// </summary>
[ViewVariables]
internal MapId IntersectingMapId { get; set; } = MapId.Nullspace;
/// <summary>
/// What grids we're on for RenderingTreeSystem.
/// </summary>
[ViewVariables]
internal List<GridId> IntersectingGrids = new();
internal RenderingTreeComponent? RenderTree { get; set; }
void ISerializationHooks.AfterDeserialization()
{

View File

@@ -131,17 +131,8 @@ namespace Robust.Client.GameObjects
[DataField("directional")]
private bool _directional = true;
/// <summary>
/// What MapId we are intersecting for RenderingTreeSystem.
/// </summary>
[ViewVariables]
internal MapId IntersectingMapId { get; set; } = MapId.Nullspace;
/// <summary>
/// What grids we're on for RenderingTreeSystem.
/// </summary>
[ViewVariables]
internal List<GridId> IntersectingGrids { get; } = new();
internal RenderingTreeComponent? RenderTree { get; set; } = null;
[DataField("layerDatums")]
private List<PrototypeLayerData> LayerDatums
@@ -374,7 +365,7 @@ namespace Robust.Client.GameObjects
}
else
{
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath);
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'.", rsiPath);
}
}
}

View File

@@ -0,0 +1,57 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
public sealed class RenderingTreeComponent : Component
{
public override string Name => "RenderingTree";
internal DynamicTree<SpriteComponent> SpriteTree { get; private set; } = new(SpriteAabbFunc);
internal DynamicTree<PointLightComponent> LightTree { get; private set; } = new(LightAabbFunc);
private static Box2 SpriteAabbFunc(in SpriteComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return new Box2(pos, pos);
}
private static Box2 LightAabbFunc(in PointLightComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var boxSize = value.Radius * 2;
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return Box2.CenteredAround(pos, (boxSize, boxSize));
}
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return new Box2(pos, pos);
}
internal static Box2 LightAabbFunc(PointLightComponent value, Vector2? worldPos = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var boxSize = value.Radius * 2;
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return Box2.CenteredAround(pos, (boxSize, boxSize));
}
}
}

View File

@@ -0,0 +1,84 @@
#if DEBUG
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects
{
internal sealed class DebugLightTreeSystem : EntitySystem
{
private DebugLightOverlay? _lightOverlay;
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (_enabled)
{
_lightOverlay = new DebugLightOverlay(
IoCManager.Resolve<IEntityLookup>(),
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IMapManager>(),
Get<RenderingTreeSystem>());
overlayManager.AddOverlay(_lightOverlay);
}
else
{
overlayManager.RemoveOverlay(_lightOverlay!);
_lightOverlay = null;
}
}
}
private bool _enabled;
private sealed class DebugLightOverlay : Overlay
{
private IEntityLookup _lookup;
private IEyeManager _eyeManager;
private IMapManager _mapManager;
private RenderingTreeSystem _tree;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugLightOverlay(IEntityLookup lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
{
_lookup = lookup;
_eyeManager = eyeManager;
_mapManager = mapManager;
_tree = tree;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
var map = _eyeManager.CurrentMap;
if (map == MapId.Nullspace) return;
var viewport = _eyeManager.GetWorldViewport();
foreach (var tree in _tree.GetLightTrees(map, viewport))
{
foreach (var light in tree)
{
var aabb = _lookup.GetWorldAabbFromEntity(light.Owner);
if (!aabb.Intersects(viewport)) continue;
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.1f));
}
}
}
}
}
}
#endif

View File

@@ -3,8 +3,11 @@ using System.Drawing;
using System.Linq;
using JetBrains.Annotations;
using Robust.Client.Physics;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
@@ -18,25 +21,48 @@ namespace Robust.Client.GameObjects
[UsedImplicitly]
public sealed class RenderingTreeSystem : EntitySystem
{
internal const string LoggerSawmill = "rendertree";
// Nullspace is not indexed. Keep that in mind.
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
private readonly Dictionary<MapId, Dictionary<GridId, MapTrees>> _gridTrees = new();
[Dependency] private readonly IMapManager _mapManager = default!;
private readonly List<SpriteComponent> _spriteQueue = new();
private readonly List<PointLightComponent> _lightQueue = new();
private HashSet<EntityUid> _checkedChildren = new();
internal DynamicTree<SpriteComponent> GetSpriteTreeForMap(MapId map, GridId grid)
/// <summary>
/// <see cref="CVars.MaxLightRadius"/>
/// </summary>
public float MaxLightRadius { get; private set; }
internal IEnumerable<RenderingTreeComponent> GetRenderTrees(MapId mapId, Box2 worldAABB)
{
return _gridTrees[map][grid].SpriteTree;
if (mapId == MapId.Nullspace) yield break;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<RenderingTreeComponent>();
}
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
}
internal DynamicTree<PointLightComponent> GetLightTreeForMap(MapId map, GridId grid)
internal IEnumerable<DynamicTree<SpriteComponent>> GetSpriteTrees(MapId mapId, Box2 worldAABB)
{
return _gridTrees[map][grid].LightTree;
foreach (var comp in GetRenderTrees(mapId, worldAABB))
{
yield return comp.SpriteTree;
}
}
internal IEnumerable<DynamicTree<PointLightComponent>> GetLightTrees(MapId mapId, Box2 worldAABB)
{
foreach (var comp in GetRenderTrees(mapId, worldAABB))
{
yield return comp.LightTree;
}
}
public override void Initialize()
@@ -48,9 +74,7 @@ namespace Robust.Client.GameObjects
UpdatesAfter.Add(typeof(PhysicsSystem));
_mapManager.MapCreated += MapManagerOnMapCreated;
_mapManager.MapDestroyed += MapManagerOnMapDestroyed;
_mapManager.OnGridCreated += MapManagerOnGridCreated;
_mapManager.OnGridRemoved += MapManagerOnGridRemoved;
// Due to how recursion works, this must be done.
SubscribeLocalEvent<MoveEvent>(AnythingMoved);
@@ -65,6 +89,11 @@ namespace Robust.Client.GameObjects
SubscribeLocalEvent<PointLightComponent, PointLightRadiusChangedEvent>(PointLightRadiusChanged);
SubscribeLocalEvent<PointLightComponent, RenderTreeRemoveLightEvent>(RemoveLight);
SubscribeLocalEvent<PointLightComponent, PointLightUpdateEvent>(HandleLightUpdate);
SubscribeLocalEvent<RenderingTreeComponent, ComponentRemove>(HandleTreeRemove);
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.OnValueChanged(CVars.MaxLightRadius, value => MaxLightRadius = value, true);
}
private void HandleLightUpdate(EntityUid uid, PointLightComponent component, PointLightUpdateEvent args)
@@ -88,12 +117,12 @@ namespace Robust.Client.GameObjects
{
// To avoid doing redundant updates (and we don't need to update a grid's children ever)
if (!_checkedChildren.Add(sender.Owner.Uid) ||
sender.Owner.HasComponent<MapGridComponent>() ||
sender.Owner.HasComponent<MapComponent>()) return;
sender.Owner.HasComponent<RenderingTreeComponent>()) return;
// This recursive search is needed, as MoveEvent is defined to not care about indirect events like children.
// WHATEVER YOU DO, DON'T REPLACE THIS WITH SPAMMING EVENTS UNLESS YOU HAVE A GUARANTEE IT WON'T LAG THE GC.
// (Struct-based events ok though)
// Ironically this was lagging the GC lolz
if (sender.Owner.TryGetComponent(out SpriteComponent? sprite))
QueueSpriteUpdate(sprite);
@@ -129,16 +158,10 @@ namespace Robust.Client.GameObjects
private void ClearSprite(SpriteComponent component)
{
if (_gridTrees.TryGetValue(component.IntersectingMapId, out var gridTrees))
{
foreach (var gridId in component.IntersectingGrids)
{
if (!gridTrees.TryGetValue(gridId, out var tree)) continue;
tree.SpriteTree.Remove(component);
}
}
if (component.RenderTree == null) return;
component.IntersectingGrids.Clear();
component.RenderTree.SpriteTree.Remove(component);
component.RenderTree = null;
}
private void QueueSpriteUpdate(SpriteComponent component)
@@ -173,16 +196,10 @@ namespace Robust.Client.GameObjects
private void ClearLight(PointLightComponent component)
{
if (_gridTrees.TryGetValue(component.IntersectingMapId, out var gridTrees))
{
foreach (var gridId in component.IntersectingGrids)
{
if (!gridTrees.TryGetValue(gridId, out var tree)) continue;
tree.LightTree.Remove(component);
}
}
if (component.RenderTree == null) return;
component.IntersectingGrids.Clear();
component.RenderTree.LightTree.Remove(component);
component.RenderTree = null;
}
private void QueueLightUpdate(PointLightComponent component)
@@ -198,31 +215,23 @@ namespace Robust.Client.GameObjects
{
base.Shutdown();
_mapManager.MapCreated -= MapManagerOnMapCreated;
_mapManager.MapDestroyed -= MapManagerOnMapDestroyed;
_mapManager.OnGridCreated -= MapManagerOnGridCreated;
_mapManager.OnGridRemoved -= MapManagerOnGridRemoved;
}
private void MapManagerOnMapDestroyed(object? sender, MapEventArgs e)
private void HandleTreeRemove(EntityUid uid, RenderingTreeComponent component, ComponentRemove args)
{
foreach (var (_, gridTree) in _gridTrees[e.Map])
foreach (var sprite in component.SpriteTree)
{
foreach (var comp in gridTree.LightTree)
{
comp.IntersectingGrids.Clear();
}
foreach (var comp in gridTree.SpriteTree)
{
comp.IntersectingGrids.Clear();
}
// Just in case?
gridTree.LightTree.Clear();
gridTree.SpriteTree.Clear();
sprite.RenderTree = null;
}
_gridTrees.Remove(e.Map);
foreach (var light in component.LightTree)
{
light.RenderTree = null;
}
component.SpriteTree.Clear();
component.LightTree.Clear();
}
private void MapManagerOnMapCreated(object? sender, MapEventArgs e)
@@ -232,35 +241,30 @@ namespace Robust.Client.GameObjects
return;
}
_gridTrees.Add(e.Map, new Dictionary<GridId, MapTrees>
{
{GridId.Invalid, new MapTrees()}
});
_mapManager.GetMapEntity(e.Map).EnsureComponent<RenderingTreeComponent>();
}
private void MapManagerOnGridCreated(MapId mapId, GridId gridId)
{
_gridTrees[mapId].Add(gridId, new MapTrees());
EntityManager.GetEntity(_mapManager.GetGrid(gridId).GridEntityId).EnsureComponent<RenderingTreeComponent>();
}
private void MapManagerOnGridRemoved(MapId mapId, GridId gridId)
internal static RenderingTreeComponent? GetRenderTree(IEntity entity)
{
var gridTree = _gridTrees[mapId][gridId];
if (entity.Transform.MapID == MapId.Nullspace ||
entity.HasComponent<RenderingTreeComponent>()) return null;
foreach (var sprite in gridTree.SpriteTree)
var parent = entity.Transform.Parent?.Owner;
while (true)
{
sprite.IntersectingGrids.Remove(gridId);
if (parent == null) break;
if (parent.TryGetComponent(out RenderingTreeComponent? comp)) return comp;
parent = parent.Transform.Parent?.Owner;
}
foreach (var light in gridTree.LightTree)
{
light.IntersectingGrids.Remove(gridId);
}
// Clear in case
gridTree.LightTree.Clear();
gridTree.SpriteTree.Clear();
_gridTrees[mapId].Remove(gridId);
return null;
}
public override void FrameUpdate(float frameTime)
@@ -270,55 +274,42 @@ namespace Robust.Client.GameObjects
foreach (var sprite in _spriteQueue)
{
sprite.TreeUpdateQueued = false;
var mapId = sprite.Owner.Transform.MapID;
if (!sprite.Visible || sprite.ContainerOccluded)
{
ClearSprite(sprite);
continue;
}
// If we're on a new map then clear the old one.
if (sprite.IntersectingMapId != mapId)
var oldMapTree = sprite.RenderTree;
var newMapTree = GetRenderTree(sprite.Owner);
// TODO: Temp PVS guard
var worldPos = sprite.Owner.Transform.WorldPosition;
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
{
ClearSprite(sprite);
continue;
}
sprite.IntersectingMapId = mapId;
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos);
if (mapId == MapId.Nullspace) continue;
var mapTree = _gridTrees[mapId];
var aabb = MapTrees.SpriteAabbFunc(sprite);
var intersectingGrids = _mapManager.FindGridIdsIntersecting(mapId, aabb, true).ToList();
// Remove from old
foreach (var gridId in sprite.IntersectingGrids)
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)
{
if (intersectingGrids.Contains(gridId)) continue;
mapTree[gridId].SpriteTree.Remove(sprite);
ClearSprite(sprite);
newMapTree?.SpriteTree.Add(sprite, aabb);
}
// Rebuild in the update below
sprite.IntersectingGrids.Clear();
// Update / add to new
foreach (var gridId in intersectingGrids)
else
{
var translated = aabb.Translated(gridId == GridId.Invalid
? Vector2.Zero
: -_mapManager.GetGrid(gridId).WorldPosition);
mapTree[gridId].SpriteTree.AddOrUpdate(sprite, translated);
sprite.IntersectingGrids.Add(gridId);
newMapTree?.SpriteTree.Update(sprite, aabb);
}
sprite.RenderTree = newMapTree;
}
foreach (var light in _lightQueue)
{
light.TreeUpdateQueued = false;
var mapId = light.Owner.Transform.MapID;
if (!light.Enabled || light.ContainerOccluded)
{
@@ -326,72 +317,44 @@ namespace Robust.Client.GameObjects
continue;
}
// If we're on a new map then clear the old one.
if (light.IntersectingMapId != mapId)
var oldMapTree = light.RenderTree;
var newMapTree = GetRenderTree(light.Owner);
// TODO: Temp PVS guard
var worldPos = light.Owner.Transform.WorldPosition;
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
{
ClearLight(light);
continue;
}
light.IntersectingMapId = mapId;
if (mapId == MapId.Nullspace) continue;
var mapTree = _gridTrees[mapId];
var aabb = MapTrees.LightAabbFunc(light);
var intersectingGrids = _mapManager.FindGridIdsIntersecting(mapId, aabb, true).ToList();
// Remove from old
foreach (var gridId in light.IntersectingGrids)
// TODO: Events need a bit of cleanup so we only validate this on initialize and radius changed events
// this is fine for now IMO as it's 1 float check for every light that moves
if (light.Radius > MaxLightRadius)
{
if (intersectingGrids.Contains(gridId)) continue;
mapTree[gridId].LightTree.Remove(light);
Logger.WarningS(LoggerSawmill, $"Light radius for {light.Owner} set above max radius of {MaxLightRadius}. This may lead to pop-in.");
}
// Rebuild in the update below
light.IntersectingGrids.Clear();
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos);
// Update / add to new
foreach (var gridId in intersectingGrids)
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)
{
var translated = aabb.Translated(gridId == GridId.Invalid
? Vector2.Zero
: -_mapManager.GetGrid(gridId).WorldPosition);
mapTree[gridId].LightTree.AddOrUpdate(light, translated);
light.IntersectingGrids.Add(gridId);
ClearLight(light);
newMapTree?.LightTree.Add(light, aabb);
}
else
{
newMapTree?.LightTree.Update(light, aabb);
}
light.RenderTree = newMapTree;
}
_spriteQueue.Clear();
_lightQueue.Clear();
}
private sealed class MapTrees
{
public readonly DynamicTree<SpriteComponent> SpriteTree;
public readonly DynamicTree<PointLightComponent> LightTree;
public MapTrees()
{
SpriteTree = new DynamicTree<SpriteComponent>(SpriteAabbFunc);
LightTree = new DynamicTree<PointLightComponent>(LightAabbFunc);
}
internal static Box2 SpriteAabbFunc(in SpriteComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
return new Box2(worldPos, worldPos);
}
internal static Box2 LightAabbFunc(in PointLightComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var boxSize = value.Radius * 2;
return Box2.CenteredAround(worldPos, (boxSize, boxSize));
}
}
}
internal class RenderTreeRemoveLightEvent : EntityEventArgs

View File

@@ -50,13 +50,11 @@ namespace Robust.Client.GameObjects
return;
}
foreach (var gridId in _mapManager.FindGridIdsIntersecting(currentMap, pvsBounds, true))
foreach (var comp in _treeSystem.GetRenderTrees(currentMap, pvsBounds))
{
var gridBounds = gridId == GridId.Invalid ? pvsBounds : pvsBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
var bounds = pvsBounds.Translated(-comp.Owner.Transform.WorldPosition);
var mapTree = _treeSystem.GetSpriteTreeForMap(currentMap, gridId);
mapTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
comp.SpriteTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
{
if (value.IsInert)
{
@@ -65,7 +63,7 @@ namespace Robust.Client.GameObjects
value.FrameUpdate(state);
return true;
}, gridBounds, approx: true);
}, bounds, true);
}
}
}

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Client.Map;
@@ -25,6 +26,7 @@ using Robust.Shared.Utility;
namespace Robust.Client.GameStates
{
/// <inheritdoc />
[UsedImplicitly]
public class ClientGameStateManager : IClientGameStateManager
{
private GameStateProcessor _processor = default!;
@@ -36,6 +38,8 @@ namespace Robust.Client.GameStates
_pendingSystemMessages
= new();
private uint _metaCompNetId;
[Dependency] private readonly IComponentFactory _compFactory = default!;
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
@@ -99,6 +103,12 @@ namespace Robust.Client.GameStates
Predicting = _config.GetCVar(CVars.NetPredict);
PredictTickBias = _config.GetCVar(CVars.NetPredictTickBias);
PredictLagBias = _config.GetCVar(CVars.NetPredictLagBias);
var metaId = _compFactory.GetRegistration(typeof(MetaDataComponent)).NetID;
if (!metaId.HasValue)
throw new InvalidOperationException("MetaDataComponent does not have a NetId.");
_metaCompNetId = metaId.Value;
}
/// <inheritdoc />
@@ -210,6 +220,11 @@ namespace Robust.Client.GameStates
ResetPredictedEntities(_timing.CurTick);
}
if (!curState.Extrapolated)
{
_processor.UpdateFullRep(curState);
}
// Store last tick we got from the GameStateProcessor.
_lastProcessedTick = _timing.CurTick;
@@ -338,11 +353,11 @@ namespace Robust.Client.GameStates
}
// TODO: handle component deletions/creations.
foreach (var comp in _componentManager.GetNetComponents(entity.Uid))
foreach (var (netId, comp) in _componentManager.GetNetComponents(entity.Uid))
{
DebugTools.AssertNotNull(comp.NetID);
DebugTools.AssertNotNull(netId);
if (comp.LastModifiedTick < curTick || !last.TryGetValue(comp.NetID!.Value, out var compState))
if (comp.LastModifiedTick < curTick || !last.TryGetValue(netId, out var compState))
{
continue;
}
@@ -363,24 +378,22 @@ namespace Robust.Client.GameStates
// so that we can later roll back to it (if necessary).
var outputData = new Dictionary<EntityUid, Dictionary<uint, ComponentState>>();
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
var player = _players.LocalPlayer.Session;
foreach (var createdEntity in createdEntities)
{
var compData = new Dictionary<uint, ComponentState>();
outputData.Add(createdEntity, compData);
foreach (var component in _componentManager.GetNetComponents(createdEntity))
foreach (var (netId, component) in _componentManager.GetNetComponents(createdEntity))
{
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
var player = _players.LocalPlayer.Session;
var state = component.GetComponentState(player);
if (state.GetType() == typeof(ComponentState))
{
if(state.GetType() == typeof(ComponentState))
continue;
}
compData.Add(state.NetID, state);
compData.Add(netId, state);
}
}
@@ -422,16 +435,17 @@ namespace Robust.Client.GameStates
//Known entities
if (_entities.TryGetEntity(es.Uid, out var entity))
{
// Logger.Debug($"[{IGameTiming.TickStampStatic}] MOD {es.Uid}");
toApply.Add(entity, (es, null));
}
else //Unknown entities
{
var metaState = (MetaDataComponentState?) es.ComponentStates
?.FirstOrDefault(c => c.NetID == NetIDs.META_DATA);
var metaState = (MetaDataComponentState?) es.ComponentChanges?.FirstOrDefault(c => c.NetID == _metaCompNetId).State;
if (metaState == null)
{
throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!");
}
// Logger.Debug($"[{IGameTiming.TickStampStatic}] CREATE {es.Uid} {metaState.PrototypeId}");
var newEntity = (Entity)_entities.CreateEntity(metaState.PrototypeId, es.Uid);
toApply.Add(newEntity, (es, null));
toInitialize.Add(newEntity);
@@ -471,6 +485,7 @@ namespace Robust.Client.GameStates
foreach (var id in deletions)
{
// Logger.Debug($"[{IGameTiming.TickStampStatic}] DELETE {id}");
_entities.DeleteEntity(id);
}
@@ -535,7 +550,7 @@ namespace Robust.Client.GameStates
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityEventBus bus, EntityState? curState,
EntityState? nextState)
{
var compStateWork = new Dictionary<uint, (ComponentState? curState, ComponentState? nextState)>();
var compStateWork = new Dictionary<ushort, (ComponentState? curState, ComponentState? nextState)>();
var entityUid = entity.Uid;
if (curState?.ComponentChanges != null)
@@ -551,42 +566,46 @@ namespace Robust.Client.GameStates
}
else
{
//Right now we just assume every state from an unseen entity is added
if (compMan.HasComponent(entityUid, compChange.NetID))
continue;
var newComp = (Component) _compFactory.GetComponent(compChange.ComponentName!);
var newComp = (Component) _compFactory.GetComponent(compChange.NetID);
newComp.Owner = entity;
compMan.AddComponent(entity, newComp, true);
compStateWork[compChange.NetID] = (compChange.State, null);
}
}
}
if (curState?.ComponentStates != null)
if (curState?.ComponentChanges != null)
{
foreach (var compState in curState.ComponentStates)
foreach (var compChange in curState.ComponentChanges)
{
compStateWork[compState.NetID] = (compState, null);
compStateWork[compChange.NetID] = (compChange.State, null);
}
}
if (nextState?.ComponentStates != null)
if (nextState?.ComponentChanges != null)
{
foreach (var compState in nextState.ComponentStates)
foreach (var compState in nextState.ComponentChanges)
{
if (compStateWork.TryGetValue(compState.NetID, out var state))
{
compStateWork[compState.NetID] = (state.curState, compState);
compStateWork[compState.NetID] = (state.curState, compState.State);
}
else
{
compStateWork[compState.NetID] = (null, compState);
compStateWork[compState.NetID] = (null, compState.State);
}
}
}
foreach (var (netId, (cur, next)) in compStateWork)
{
if (compMan.TryGetComponent(entityUid, netId, out var component))
if (compMan.TryGetComponent(entityUid, (ushort) netId, out var component))
{
try
{

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
@@ -149,11 +149,6 @@ namespace Robust.Client.GameStates
{
Logger.DebugS("net.state", $"Applying State: ext={curState!.Extrapolated}, cTick={_timing.CurTick}, fSeq={curState.FromSequence}, tSeq={curState.ToSequence}, buf={_stateBuffer.Count}");
}
if (!curState!.Extrapolated)
{
UpdateFullRep(curState);
}
}
var cState = curState!;
@@ -162,8 +157,10 @@ namespace Robust.Client.GameStates
return applyNextState;
}
private void UpdateFullRep(GameState state)
public void UpdateFullRep(GameState state)
{
// Logger.Debug($"UPDATE FULL REP: {string.Join(", ", state.EntityStates?.Select(e => e.Uid) ?? Enumerable.Empty<EntityUid>())}");
if (state.FromSequence == GameTick.Zero)
{
// Full state.
@@ -198,14 +195,10 @@ namespace Robust.Client.GameStates
{
compData.Remove(change.NetID);
}
}
}
if (entityState.ComponentStates != null)
{
foreach (var compState in entityState.ComponentStates)
{
compData[compState.NetID] = compState;
else if (change.State is not null)
{
compData[change.NetID] = change.State;
}
}
}
}

View File

@@ -21,6 +21,7 @@ namespace Robust.Client.GameStates
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
private const int HistorySize = 60 * 3; // number of ticks to keep in history.
private const int TargetPayloadBps = 56000 / 8; // Target Payload size in Bytes per second. A mind-numbing fifty-six thousand bits per second, who would ever need more?
@@ -90,17 +91,14 @@ namespace Robust.Client.GameStates
sb.Append($"\n Changes:");
foreach (var compChange in entState.ComponentChanges)
{
var del = compChange.Deleted ? 'D' : 'C';
sb.Append($"\n [{del}]{compChange.NetID}:{compChange.ComponentName}");
}
}
var registration = _componentFactory.GetRegistration(compChange.NetID);
var create = compChange.Created ? 'C' : '\0';
var mod = !(compChange.Created || compChange.Created) ? 'M' : '\0';
var del = compChange.Deleted ? 'D' : '\0';
sb.Append($"\n [{create}{mod}{del}]{compChange.NetID}:{registration.Name}");
if (entState.ComponentStates is not null)
{
sb.Append($"\n States:");
foreach (var compState in entState.ComponentStates)
{
sb.Append($"\n {compState.NetID}:{compState.GetType().Name}");
if(compChange.State is not null)
sb.Append($"\n STATE:{compChange.State.GetType().Name}");
}
}
}

View File

@@ -366,24 +366,11 @@ namespace Robust.Client.Graphics.Clyde
private void ProcessSpriteEntities(MapId map, Box2 worldBounds,
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list)
{
var spriteSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
foreach (var comp in _entitySystemManager.GetEntitySystem<RenderingTreeSystem>().GetRenderTrees(map, worldBounds))
{
Box2 gridBounds;
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
if (gridId == GridId.Invalid)
{
gridBounds = worldBounds;
}
else
{
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
}
var tree = spriteSystem.GetSpriteTreeForMap(map, gridId);
tree.QueryAabb(ref list, ((
comp.SpriteTree.QueryAabb(ref list, ((
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
in SpriteComponent value) =>
{
@@ -398,7 +385,7 @@ namespace Robust.Client.Graphics.Clyde
entry.yWorldPos = worldPos.Y;
return true;
}), gridBounds, approx: true);
}), bounds, true);
}
}

View File

@@ -494,24 +494,16 @@ namespace Robust.Client.Graphics.Clyde
GetLightsToRender(MapId map, in Box2 worldBounds)
{
var renderingTreeSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
var enlargedBounds = worldBounds.Enlarged(renderingTreeSystem.MaxLightRadius);
// Use worldbounds for this one as we only care if the light intersects our actual bounds
var state = (this, worldBounds, count: 0);
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
foreach (var comp in renderingTreeSystem.GetRenderTrees(map, enlargedBounds))
{
Box2 gridBounds;
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
if (gridId == GridId.Invalid)
{
gridBounds = worldBounds;
}
else
{
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
}
var lightTree = renderingTreeSystem.GetLightTreeForMap(map, gridId);
lightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
{
var transform = light.Owner.Transform;
@@ -535,7 +527,7 @@ namespace Robust.Client.Graphics.Clyde
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
return true;
}, gridBounds);
}, bounds);
}
if (state.count > _maxLightsPerScene)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,8 +10,7 @@ namespace Robust.Client
GameControllerOptions Options { get; }
bool ContentStart { get; set; }
void SetCommandLineArgs(CommandLineArgs args);
bool LoadConfigAndUserData { get; set; }
void Run(GameController.DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null);
void Run(GameController.DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null);
void KeyDown(KeyEventArgs keyEvent);
void KeyUp(KeyEventArgs keyEvent);
void TextEntered(TextEventArgs textEvent);

View File

@@ -32,12 +32,12 @@ namespace Robust.Client.Map
//get shared euid of map comp entity
foreach (var entityState in entityStates!)
{
if(entityState.ComponentStates is null)
if(entityState.ComponentChanges is null)
continue;
foreach (var compState in entityState.ComponentStates)
foreach (var compChange in entityState.ComponentChanges)
{
if (compState is not MapComponentState mapCompState || mapCompState.MapId != mapId)
if (compChange.State is not MapComponentState mapCompState || mapCompState.MapId != mapId)
continue;
mapEuid = entityState.Uid;
@@ -67,12 +67,12 @@ namespace Robust.Client.Map
//get shared euid of map comp entity
foreach (var entityState in entityStates!)
{
if(entityState.ComponentStates is null)
if (entityState.ComponentChanges is null)
continue;
foreach (var compState in entityState.ComponentStates)
foreach (var compState in entityState.ComponentChanges)
{
if (compState is not MapGridComponentState gridCompState || gridCompState.GridIndex != gridId)
if (compState.State is not MapGridComponentState gridCompState || gridCompState.GridIndex != gridId)
continue;
gridEuid = entityState.Uid;

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
@@ -17,7 +18,7 @@ namespace Robust.Client.Map
[Dependency] private readonly IResourceCache _resourceCache = default!;
private Texture? _tileTextureAtlas;
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
private readonly Dictionary<ushort, Box2> _tileRegions = new();
@@ -61,7 +62,7 @@ namespace Robust.Client.Map
var row = i / dimensionX;
Image<Rgba32> image;
using (var stream = _resourceCache.ContentFileRead($"/Textures/Constructible/Tiles/{def.SpriteName}.png"))
using (var stream = _resourceCache.ContentFileRead(Path.Join(def.Path, $"{def.SpriteName}.png")))
{
image = Image.Load<Rgba32>(stream);
}

View File

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

View File

@@ -10,7 +10,7 @@ namespace Robust.Client.UserInterface.Controls
{
public Label Label { get; }
public Button() : base()
public Button()
{
AddStyleClass(StyleClassButton);
Label = new Label

View File

@@ -14,7 +14,7 @@ namespace Robust.Client.UserInterface.Controls
public Label Label { get; }
public TextureRect TextureRect { get; }
public CheckBox() : base()
public CheckBox()
{
ToggleMode = true;

View File

@@ -13,7 +13,7 @@ namespace Robust.Client.UserInterface.Controls
public const string StylePseudoClassHover = "hover";
public const string StylePseudoClassDisabled = "disabled";
public ContainerButton() : base()
public ContainerButton()
{
DrawModeChanged();
}

View File

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

View File

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

View File

@@ -52,7 +52,7 @@ namespace Robust.Client.UserInterface.Controls
public event EventHandler<ValueChangedEventArgs>? ValueChanged;
public SpinBox() : base()
public SpinBox()
{
MouseFilter = MouseFilterMode.Pass;

View File

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

View File

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

View File

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

View File

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

View File

@@ -33,6 +33,9 @@ namespace Robust.Client.ViewVariables
var valArg = args[0];
if (valArg.StartsWith("SI"))
{
if (valArg.StartsWith("SIoC"))
valArg = valArg.Substring(4);
// Server-side IoC selector.
var selector = new ViewVariablesIoCSelector(valArg.Substring(1));
vvm.OpenVV(selector);
@@ -41,6 +44,9 @@ namespace Robust.Client.ViewVariables
if (valArg.StartsWith("I"))
{
if (valArg.StartsWith("IoC"))
valArg = valArg.Substring(3);
// Client-side IoC selector.
var reflection = IoCManager.Resolve<IReflectionManager>();
if (!reflection.TryLooseGetType(valArg, out var type))

View File

@@ -102,6 +102,8 @@ namespace Robust.Server
private readonly ManualResetEventSlim _shutdownEvent = new(false);
public ServerOptions Options { get; private set; } = new();
/// <inheritdoc />
public int MaxPlayers => _config.GetCVar(CVars.GameMaxPlayers);
@@ -114,7 +116,7 @@ namespace Robust.Server
Logger.InfoS("srv", "Restarting Server...");
Cleanup();
Start(_logHandlerFactory);
Start(Options, _logHandlerFactory);
}
/// <inheritdoc />
@@ -141,15 +143,16 @@ namespace Robust.Server
}
/// <inheritdoc />
public bool Start(Func<ILogHandler>? logHandlerFactory = null)
public bool Start(ServerOptions options, Func<ILogHandler>? logHandlerFactory = null)
{
Options = options;
var profilePath = Path.Join(Environment.CurrentDirectory, "AAAAAAAA");
ProfileOptimization.SetProfileRoot(profilePath);
ProfileOptimization.StartProfile("AAAAAAAAAA");
_config.Initialize(true);
if (LoadConfigAndUserData)
if (Options.LoadConfigAndUserData)
{
// Sets up the configMgr
// If a config file path was passed, use it literally.
@@ -269,22 +272,26 @@ namespace Robust.Server
return true;
}
var dataDir = LoadConfigAndUserData
var dataDir = Options.LoadConfigAndUserData
? _commandLineArgs?.DataDir ?? PathHelpers.ExecutableRelativeFile("data")
: null;
// Set up the VFS
_resources.Initialize(dataDir);
ProgramShared.DoMounts(_resources, _commandLineArgs?.MountOptions, "Content.Server", contentStart:ContentStart);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
ProgramShared.DoMounts(_resources, mountOptions, Options.ContentBuildDirectory, Options.AssemblyDirectory,
Options.LoadContentResources, Options.ResourceMountDisabled, ContentStart);
// When the game is ran with the startup executable being content,
// we have to disable the separate load context.
// Otherwise the content assemblies will be loaded twice which causes *many* fun bugs.
_modLoader.SetUseLoadContext(!ContentStart);
_modLoader.SetEnableSandboxing(false);
_modLoader.SetEnableSandboxing(Options.Sandboxing);
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), "Content."))
if (!_modLoader.TryLoadModulesFrom(Options.AssemblyDirectory, Options.ContentModulePrefix))
{
Logger.Fatal("Errors while loading content assemblies.");
return true;
@@ -335,13 +342,23 @@ namespace Robust.Server
// otherwise the prototypes will be cleared
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
prototypeManager.Initialize();
prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes"));
prototypeManager.LoadDirectory(Options.PrototypeDirectory);
prototypeManager.Resync();
IoCManager.Resolve<IServerConsoleHost>().Initialize();
_entityManager.Startup();
IoCManager.Resolve<IEntityLookup>().Startup();
_stateManager.Initialize();
// sometime after content init
{
var reg = _entityManager.ComponentFactory.GetRegistration<TransformComponent>();
if (!reg.NetID.HasValue)
throw new InvalidOperationException("TransformComponent does not have a NetId.");
_stateManager.SetTransformNetId(reg.NetID.Value);
}
_scriptHost.Initialize();
_modLoader.BroadcastRunLevel(ModRunLevel.PostInit);
@@ -492,7 +509,6 @@ namespace Robust.Server
}
public bool ContentStart { get; set; }
public bool LoadConfigAndUserData { private get; set; } = true;
public void OverrideMainLoop(IGameLoop gameLoop)
{

View File

@@ -7,13 +7,13 @@ namespace Robust.Server
#if FULL_RELEASE
throw new System.InvalidOperationException("ContentStart.Start is not available on a full release.");
#else
Program.Start(args, true);
Program.Start(args, new ServerOptions(), true);
#endif
}
public static void StartLibrary(string[] args)
public static void StartLibrary(string[] args, ServerOptions options)
{
Program.Start(args, true);
Program.Start(args, options, true);
}
}
}

View File

@@ -1,4 +1,5 @@
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;

View File

@@ -1,4 +1,5 @@
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
@@ -10,6 +11,7 @@ namespace Robust.Server.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(IPointLightComponent))]
[NetworkedComponent()]
public class PointLightComponent : Component, IPointLightComponent
{
[DataField("color")]
@@ -22,7 +24,6 @@ namespace Robust.Server.GameObjects
private Vector2 _offset = Vector2.Zero;
public override string Name => "PointLight";
public override uint? NetID => NetIDs.POINT_LIGHT;
[ViewVariables(VVAccess.ReadWrite)]
public Color Color

View File

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

View File

@@ -1,11 +1,15 @@
using Robust.Shared.Console;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
namespace Robust.Server.GameObjects
{
public class ServerComponentFactory : ComponentFactory
internal class ServerComponentFactory : ComponentFactory
{
public ServerComponentFactory()
public ServerComponentFactory(IDynamicTypeFactoryInternal typeFactory, IReflectionManager reflectionManager, IConsoleHost conHost)
: base(typeFactory, reflectionManager, conHost)
{
RegisterIgnore("Input");
RegisterIgnore("AnimationPlayer");

View File

@@ -75,7 +75,7 @@ namespace Robust.Server.GameObjects
// As such, we can reset the modified ticks to Zero,
// which indicates "not different from client's own deserialization".
// So the initial data for the component or even the creation doesn't have to be sent over the wire.
foreach (var component in ComponentManager.GetNetComponents(entity.Uid))
foreach (var (netId, component) in ComponentManager.GetNetComponents(entity.Uid))
{
// Make sure to ONLY get components that are defined in the prototype.
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
@@ -143,13 +143,15 @@ namespace Robust.Server.GameObjects
if (_networkManager.IsClient)
return;
if (!component.NetID.HasValue)
var netId = ComponentFactory.GetRegistration(component.GetType()).NetID;
if (!netId.HasValue)
throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component));
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.ComponentMessage;
msg.EntityUid = entity.Uid;
msg.NetId = component.NetID.Value;
msg.NetId = netId.Value;
msg.ComponentMessage = message;
msg.SourceTick = _gameTiming.CurTick;

View File

@@ -39,6 +39,8 @@ namespace Robust.Server.GameStates
private readonly ObjectPool<HashSet<EntityUid>> _viewerEntsPool
= new DefaultObjectPool<HashSet<EntityUid>>(new DefaultPooledObjectPolicy<HashSet<EntityUid>>(), MaxVisPoolSize);
private ushort _transformNetId = 0;
/// <summary>
/// Is view culling enabled, or will we send the whole map?
/// </summary>
@@ -58,6 +60,11 @@ namespace Robust.Server.GameStates
_lookup = lookup;
}
public void SetTransformNetId(ushort value)
{
_transformNetId = value;
}
// Not thread safe
public void EntityDeleted(EntityUid e)
{
@@ -204,17 +211,14 @@ namespace Robust.Server.GameStates
var oldState = (TransformComponent.TransformComponentState) xform.GetComponentState(session);
entityStates.Add(new EntityState(entityUid,
new ComponentChanged[]
new[]
{
new(false, NetIDs.TRANSFORM, "Transform")
},
new ComponentState[]
{
new TransformComponent.TransformComponentState(Vector2NaN,
oldState.Rotation,
oldState.ParentID,
oldState.NoLocalRotation,
oldState.Anchored)
ComponentChange.Changed(_transformNetId,
new TransformComponent.TransformComponentState(Vector2NaN,
oldState.Rotation,
oldState.ParentID,
oldState.NoLocalRotation,
oldState.Anchored)),
}));
}

View File

@@ -17,5 +17,6 @@ namespace Robust.Server.GameStates
bool PvsEnabled { get; set; }
float PvsRange { get; set; }
void SetTransformNetId(ushort netId);
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.Map;
using Robust.Server.Player;
@@ -21,6 +22,7 @@ using Robust.Shared.Utility;
namespace Robust.Server.GameStates
{
/// <inheritdoc cref="IServerGameStateManager"/>
[UsedImplicitly]
public class ServerGameStateManager : IServerGameStateManager, IPostInjectInit
{
// Mapping of net UID of clients -> last known acked state.
@@ -53,6 +55,11 @@ namespace Robust.Server.GameStates
set => _configurationManager.SetCVar(CVars.NetMaxUpdateRange, value);
}
public void SetTransformNetId(ushort netId)
{
_entityView.SetTransformNetId(netId);
}
public void PostInject()
{
_logger = Logger.GetSawmill("PVS");
@@ -249,38 +256,44 @@ namespace Robust.Server.GameStates
/// <returns>New entity State for the given entity.</returns>
internal static EntityState GetEntityState(IComponentManager compMan, ICommonSession player, EntityUid entityUid, GameTick fromTick)
{
var compStates = new List<ComponentState>();
var changed = new List<ComponentChanged>();
var changed = new List<ComponentChange>();
foreach (var comp in compMan.GetNetComponents(entityUid))
foreach (var (netId, component) in compMan.GetNetComponents(entityUid))
{
DebugTools.Assert(comp.Initialized);
DebugTools.Assert(component.Initialized);
// NOTE: When LastModifiedTick or CreationTick are 0 it means that the relevant data is
// "not different from entity creation".
// i.e. when the client spawns the entity and loads the entity prototype,
// the data it deserializes from the prototype SHOULD be equal
// to what the component state / ComponentChanged would send.
// to what the component state / ComponentChange would send.
// As such, we can avoid sending this data in this case since the client "already has it".
if (comp.NetSyncEnabled && comp.LastModifiedTick != GameTick.Zero && comp.LastModifiedTick >= fromTick)
compStates.Add(comp.GetComponentState(player));
DebugTools.Assert(component.LastModifiedTick >= component.CreationTick);
if (comp.CreationTick != GameTick.Zero && comp.CreationTick >= fromTick && !comp.Deleted)
if (component.CreationTick != GameTick.Zero && component.CreationTick >= fromTick && !component.Deleted)
{
ComponentState? state = null;
if (component.NetSyncEnabled && component.LastModifiedTick != GameTick.Zero && component.LastModifiedTick >= fromTick)
state = component.GetComponentState(player);
// Can't be null since it's returned by GetNetComponents
// ReSharper disable once PossibleInvalidOperationException
changed.Add(ComponentChanged.Added(comp.NetID!.Value, comp.Name));
changed.Add(ComponentChange.Added(netId, state));
}
else if (comp.Deleted && comp.LastModifiedTick >= fromTick)
else if (component.NetSyncEnabled && component.LastModifiedTick != GameTick.Zero && component.LastModifiedTick >= fromTick)
{
changed.Add(ComponentChange.Changed(netId, component.GetComponentState(player)));
}
else if (component.Deleted && component.LastModifiedTick >= fromTick)
{
// Can't be null since it's returned by GetNetComponents
// ReSharper disable once PossibleInvalidOperationException
changed.Add(ComponentChanged.Removed(comp.NetID!.Value));
changed.Add(ComponentChange.Removed(netId));
}
}
return new EntityState(entityUid, changed.ToArray(), compStates.ToArray());
return new EntityState(entityUid, changed.ToArray());
}
/// <summary>

View File

@@ -23,7 +23,7 @@ namespace Robust.Server
/// Sets up the server, loads the game, gets ready for client connections.
/// </summary>
/// <returns></returns>
bool Start(Func<ILogHandler>? logHandler = null);
bool Start(ServerOptions options, Func<ILogHandler>? logHandler = null);
/// <summary>
/// Hard restarts the server, shutting it down, kicking all players, and starting the server again.
@@ -44,11 +44,10 @@ namespace Robust.Server
internal interface IBaseServerInternal : IBaseServer
{
ServerOptions Options { get; }
bool ContentStart { set; }
bool LoadConfigAndUserData { set; }
void OverrideMainLoop(IGameLoop gameLoop);
void SetCommandLineArgs(CommandLineArgs args);
}
}

View File

@@ -409,7 +409,7 @@ namespace Robust.Server.Maps
continue;
}
foreach (var component in _componentManager.GetNetComponents(entity.Uid))
foreach (var (netId, component) in _componentManager.GetNetComponents(entity.Uid))
{
var castComp = (Component) component;

View File

@@ -0,0 +1,136 @@
using System.Collections.Generic;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Dynamics;
namespace Robust.Server.Physics
{
/// <summary>
/// Handles generating fixtures for MapGrids.
/// </summary>
internal sealed class GridFixtureSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
private SharedBroadPhaseSystem _broadphase = default!;
// Is delaying fixture updates a good idea? IDEK. We definitely can't do them on every tile changed
// because if someone changes 50 tiles that will kill perf. We could probably just run it every Update
// (and at specific times due to race condition stuff).
// At any rate, cooldown given here if someone wants it. CD of 0 just runs it every tick.
private float _cooldown;
private float _accumulator;
private HashSet<MapChunk> _queuedChunks = new();
public override void Initialize()
{
base.Initialize();
UpdatesBefore.Add(typeof(PhysicsSystem));
SubscribeLocalEvent<RegenerateChunkCollisionEvent>(HandleCollisionRegenerate);
_broadphase = Get<SharedBroadPhaseSystem>();
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.OnValueChanged(CVars.GridFixtureUpdateRate, value => _cooldown = value, true);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
_accumulator += frameTime;
if (_accumulator < _cooldown) return;
_accumulator -= _cooldown;
Process();
}
/// <summary>
/// Go through every dirty chunk and re-generate their fixtures.
/// </summary>
public void Process()
{
foreach (var chunk in _queuedChunks)
{
RegenerateCollision(chunk);
}
_queuedChunks.Clear();
}
/// <summary>
/// Queue the chunk to generate (if cooldown > 0) or immediately process it.
/// </summary>
/// <param name="ev"></param>
private void HandleCollisionRegenerate(RegenerateChunkCollisionEvent ev)
{
if (_cooldown <= 0f)
{
RegenerateCollision(ev.Chunk);
return;
}
_queuedChunks.Add(ev.Chunk);
}
private void RegenerateCollision(MapChunk chunk)
{
// Currently this is gonna be hella simple.
if (!_mapManager.TryGetGrid(chunk.GridId, out var grid) ||
!EntityManager.TryGetEntity(grid.GridEntityId, out var gridEnt) ||
!gridEnt.TryGetComponent(out PhysicsComponent? physicsComponent)) return;
// TODO: Lots of stuff here etc etc, make changes to mapgridchunk.
var bounds = chunk.CalcLocalBounds();
// So something goes on with the chunk's internal bounds caching where if there's no data the bound is 0 or something?
if (bounds.IsEmpty()) return;
var origin = chunk.Indices * chunk.ChunkSize;
bounds = bounds.Translated(origin);
var oldFixture = chunk.Fixture;
var newFixture = new Fixture(
new PolygonShape
{
Vertices = new List<Vector2>
{
bounds.BottomRight,
bounds.TopRight,
bounds.TopLeft,
bounds.BottomLeft,
}
},
MapGridHelpers.CollisionGroup,
MapGridHelpers.CollisionGroup,
true) {ID = $"grid-{grid.Index}_chunk-{chunk.Indices.X}-{chunk.Indices.Y}",
Body = physicsComponent};
// TODO: Chunk will likely need multiple fixtures but future sloth problem lmao fucking dickhead
if (oldFixture?.Equals(newFixture) == true) return;
if (oldFixture != null)
physicsComponent.RemoveFixture(oldFixture);
physicsComponent.AddFixture(newFixture);
chunk.Fixture = newFixture;
EntityManager.EventBus.RaiseLocalEvent(gridEnt.Uid,new GridFixtureChangeEvent {OldFixture = oldFixture, NewFixture = newFixture});
}
}
public sealed class GridFixtureChangeEvent : EntityEventArgs
{
public Fixture? OldFixture { get; init; }
public Fixture? NewFixture { get; init; }
}
}

View File

@@ -21,10 +21,10 @@ namespace Robust.Server
internal static void Main(string[] args)
{
Start(args);
Start(args, new ServerOptions());
}
internal static void Start(string[] args, bool contentStart = false)
internal static void Start(string[] args, ServerOptions options, bool contentStart = false)
{
if (_hasStarted)
{
@@ -47,11 +47,11 @@ namespace Robust.Server
TaskCreationOptions.LongRunning,
TaskContinuationOptions.None,
RobustTaskScheduler.Instance
).StartNew(() => ParsedMain(parsed, contentStart))
).StartNew(() => ParsedMain(parsed, contentStart, options))
.GetAwaiter().GetResult();
}
private static void ParsedMain(CommandLineArgs args, bool contentStart)
private static void ParsedMain(CommandLineArgs args, bool contentStart, ServerOptions options)
{
Thread.CurrentThread.Name = "Main Thread";
IoCManager.InitThread();
@@ -67,7 +67,7 @@ namespace Robust.Server
Logger.Info("Server -> Starting");
if (server.Start())
if (server.Start(options))
{
Logger.Fatal("Server -> Can not start server");
//Not like you'd see this, haha. Perhaps later for logging.

View File

@@ -39,10 +39,11 @@ namespace Robust.Server
IoCManager.Register<IBaseServerInternal, BaseServer>();
IoCManager.Register<BaseServer, BaseServer>();
IoCManager.Register<IGameTiming, GameTiming>();
IoCManager.Register<IReflectionManager, ServerReflectionManager>();
IoCManager.Register<IConsoleHost, ServerConsoleHost>();
IoCManager.Register<IServerConsoleHost, ServerConsoleHost>();
IoCManager.Register<IComponentFactory, ServerComponentFactory>();
IoCManager.Register<IConGroupController, ConGroupController>();
IoCManager.Register<IServerConsoleHost, ServerConsoleHost>();
IoCManager.Register<IConsoleHost, ServerConsoleHost>();
IoCManager.Register<IMapManager, ServerMapManager>();
IoCManager.Register<IMapManagerInternal, ServerMapManager>();
IoCManager.Register<IServerMapManager, ServerMapManager>();
@@ -55,7 +56,6 @@ namespace Robust.Server
IoCManager.Register<IPlayerManager, PlayerManager>();
IoCManager.Register<ISharedPlayerManager, PlayerManager>();
IoCManager.Register<IPrototypeManager, ServerPrototypeManager>();
IoCManager.Register<IReflectionManager, ServerReflectionManager>();
IoCManager.Register<IResourceManager, ResourceManager>();
IoCManager.Register<IResourceManagerInternal, ResourceManager>();
IoCManager.Register<IServerEntityManager, ServerEntityManager>();

View File

@@ -0,0 +1,54 @@
using Robust.Shared;
using Robust.Shared.Utility;
namespace Robust.Server
{
public class ServerOptions
{
/// <summary>
/// Whether content sandboxing will be enabled & enforced.
/// </summary>
public bool Sandboxing { get; init; } = false;
// TODO: Expose mounting methods to games using Robust as a library.
/// <summary>
/// Lists of mount options to mount.
/// </summary>
public MountOptions MountOptions { get; init; } = new();
/// <summary>
/// Assemblies with this prefix will be loaded.
/// </summary>
public string ContentModulePrefix { get; init; } = "Content.";
/// <summary>
/// Name of the content build directory, for game pack mounting purposes.
/// </summary>
public string ContentBuildDirectory { get; init; } = "Content.Server";
/// <summary>
/// Directory to load all assemblies from.
/// </summary>
public ResourcePath AssemblyDirectory { get; init; } = new(@"/Assemblies/");
/// <summary>
/// Directory to load all prototypes from.
/// </summary>
public ResourcePath PrototypeDirectory { get; init; } = new(@"/Prototypes/");
/// <summary>
/// Whether to disable mounting the "Resources/" folder on FULL_RELEASE.
/// </summary>
public bool ResourceMountDisabled { get; init; } = false;
/// <summary>
/// Whether to mount content resources when not on FULL_RELEASE.
/// </summary>
public bool LoadContentResources { get; init; } = true;
/// <summary>
/// Whether to load config and user data.
/// </summary>
public bool LoadConfigAndUserData { get; init; } = true;
}
}

View File

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

View File

@@ -149,6 +149,26 @@ namespace Robust.Shared.Maths
return new Box2(X0, Y0, X1, Y1);
}
public bool Contains(Vector2 worldPoint)
{
// Get the worldpoint in our frame of reference so we can do a faster AABB check.
var localPoint = GetLocalPoint(worldPoint);
return Box.Contains(localPoint);
}
/// <summary>
/// Convert a point in world-space coordinates to our local coordinates.
/// </summary>
private Vector2 GetLocalPoint(Vector2 point)
{
// Could make this more efficient but works for now I guess...
var boxCenter = Box.Center;
var result = point - boxCenter;
result = Origin + Rotation.RotateVec(result - Origin);
return result + boxCenter;
}
#region Equality
/// <inheritdoc />

View File

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

View File

@@ -211,6 +211,32 @@ namespace Robust.Shared
public static readonly CVarDef<bool> LogRuntimeLog =
CVarDef.Create("log.runtimelog", true, CVar.ARCHIVE | CVar.SERVERONLY);
/*
* Light
*/
/// <summary>
/// This is the maximum the viewport is enlarged to check for any intersecting render-trees for lights.
/// This should be set to your maximum light radius.
/// </summary>
/// <remarks>
/// If this value is too small it just means there may be pop-in where a light is located on a render-tree
/// outside of our viewport.
/// </remarks>
public static readonly CVarDef<float> MaxLightRadius =
CVarDef.Create("light.max_radius", 20.0f, CVar.CLIENTONLY);
/*
* Lookup
*/
/// <summary>
/// Like MaxLightRadius this is how far we enlarge lookups to find intersecting components.
/// This should be set to your maximum entity size.
/// </summary>
public static readonly CVarDef<float> LookupEnlargementRange =
CVarDef.Create("lookup.enlargement_range", 10.0f, CVar.ARCHIVE | CVar.REPLICATED | CVar.CHEAT);
/*
* LOKI
*/
@@ -436,6 +462,13 @@ namespace Robust.Shared
public static readonly CVarDef<float> MaxAngVelocity =
CVarDef.Create("physics.maxangvelocity", 15f);
/// <summary>
/// How frequently grid fixtures are updated. Given grid updates can be expensive they aren't run immediately.
/// Set to 0 to run them immediately.
/// </summary>
public static readonly CVarDef<float> GridFixtureUpdateRate =
CVarDef.Create("physics.grid_fixture_update_rate", 0.2f);
/*
* DISCORD
*/

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
@@ -17,6 +18,7 @@ namespace Robust.Shared.Containers
/// Holds data about a set of entity containers on this entity.
/// </summary>
[ComponentReference(typeof(IContainerManager))]
[NetworkedComponent()]
public class ContainerManagerComponent : Component, IContainerManager
{
[Dependency] private readonly IRobustSerializer _serializer = default!;
@@ -29,9 +31,6 @@ namespace Robust.Shared.Containers
/// <inheritdoc />
public sealed override string Name => "ContainerContainer";
/// <inheritdoc />
public sealed override uint? NetID => NetIDs.CONTAINER_MANAGER;
/// <inheritdoc />
protected override void OnRemove()
{
@@ -281,7 +280,7 @@ namespace Robust.Shared.Containers
{
public List<ContainerData> ContainerSet;
public ContainerManagerComponentState(List<ContainerData> containers) : base(NetIDs.CONTAINER_MANAGER)
public ContainerManagerComponentState(List<ContainerData> containers)
{
ContainerSet = containers;
}

View File

@@ -1,4 +1,5 @@
using System;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Reflection;
@@ -18,14 +19,6 @@ namespace Robust.Shared.GameObjects
[ViewVariables]
public abstract string Name { get; }
/// <inheritdoc />
[ViewVariables]
public virtual uint? NetID => null;
/// <inheritdoc />
[ViewVariables]
public virtual bool NetworkSynchronizeExistence => false;
/// <inheritdoc />
[ViewVariables]
[DataField("netsync")]
@@ -249,14 +242,16 @@ namespace Robust.Shared.GameObjects
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public virtual void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null) { }
private static readonly ComponentState DefaultComponentState = new();
/// <param name="player"></param>
/// <inheritdoc />
public virtual ComponentState GetComponentState(ICommonSession player)
{
if (NetID == null)
throw new InvalidOperationException($"Cannot make state for component without Net ID: {GetType()}");
if (!(Attribute.GetCustomAttribute(GetType(), typeof(NetworkedComponentAttribute)) is NetworkedComponentAttribute))
throw new InvalidOperationException($"Calling base {nameof(GetComponentState)} without being networked.");
return new ComponentState(NetID.Value);
return DefaultComponentState;
}
/// <inheritdoc />

View File

@@ -3,31 +3,33 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.Serialization;
using JetBrains.Annotations;
using Robust.Shared.Console;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects
{
public class ComponentFactory : IComponentFactory
internal class ComponentFactory : IComponentFactory
{
[Dependency] private readonly IDynamicTypeFactoryInternal _typeFactory = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
private readonly IDynamicTypeFactoryInternal _typeFactory;
private readonly IReflectionManager _reflectionManager;
private class ComponentRegistration : IComponentRegistration
{
public string Name { get; }
public uint? NetID { get; }
public bool NetworkSynchronizeExistence { get; }
public ushort? NetID { get; set; }
public Type Type { get; }
internal readonly List<Type> References = new();
IReadOnlyList<Type> IComponentRegistration.References => References;
public ComponentRegistration(string name, Type type, uint? netID, bool networkSynchronizeExistence)
public ComponentRegistration(string name, Type type)
{
Name = name;
NetID = netID;
NetworkSynchronizeExistence = networkSynchronizeExistence;
NetID = null;
Type = type;
References.Add(type);
}
@@ -52,7 +54,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Mapping of network ID to type.
/// </summary>
private readonly Dictionary<uint, ComponentRegistration> netIDs = new();
private List<IComponentRegistration>? _networkedComponents;
/// <summary>
/// Mapping of concrete component types to their registration.
@@ -76,8 +78,34 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public IEnumerable<Type> AllRegisteredTypes => types.Keys;
/// <inheritdoc />
public IReadOnlyList<IComponentRegistration>? NetworkedComponents => _networkedComponents;
private IEnumerable<ComponentRegistration> AllRegistrations => types.Values;
public ComponentFactory(IDynamicTypeFactoryInternal typeFactory, IReflectionManager reflectionManager, IConsoleHost conHost)
{
_typeFactory = typeFactory;
_reflectionManager = reflectionManager;
conHost.RegisterCommand("dump_net_comps", "Prints the table of networked components.", "dump_net_comps", (shell, argStr, args) =>
{
if (_networkedComponents is null)
{
shell.WriteError("Registration still writeable, network ids have not been generated.");
return;
}
shell.WriteLine("Networked Component Registrations:");
for (int netId = 0; netId < _networkedComponents.Count; netId++)
{
var registration = _networkedComponents[netId];
shell.WriteLine($" [{netId,4}] {registration.Name,-16} {registration.Type.Name}");
}
});
}
[Obsolete("Use RegisterClass and Attributes instead of the Register/RegisterReference combo")]
public void Register<T>(bool overwrite = false) where T : IComponent, new()
{
@@ -86,6 +114,9 @@ namespace Robust.Shared.GameObjects
private void Register(Type type, bool overwrite = false)
{
if (_networkedComponents is not null)
throw new ComponentRegistrationLockException();
if (types.ContainsKey(type))
{
throw new InvalidOperationException($"Type is already registered: {type}");
@@ -97,8 +128,6 @@ namespace Robust.Shared.GameObjects
var name = dummy.Name;
var lowerCaseName = name.ToLowerInvariant();
var netID = dummy.NetID;
var netSyncExist = dummy.NetworkSynchronizeExistence;
if (IgnoredComponentNames.Contains(name))
{
@@ -128,24 +157,11 @@ namespace Robust.Shared.GameObjects
}
}
if (netID != null && netIDs.ContainsKey(netID.Value))
{
if (!overwrite)
{
throw new InvalidOperationException($"{name} has duplicate network ID {netID}, previous: {netIDs[netID.Value]}");
}
RemoveComponent(netIDs[netID.Value].Name);
}
var registration = new ComponentRegistration(name, type, netID, netSyncExist);
var registration = new ComponentRegistration(name, type);
names[name] = registration;
_lowerCaseNames[lowerCaseName] = name;
types[type] = registration;
if (netID != null)
{
netIDs[netID.Value] = registration;
}
ComponentAdded?.Invoke(registration);
}
@@ -157,6 +173,9 @@ namespace Robust.Shared.GameObjects
private void RegisterReference(Type target, Type @interface)
{
if (_networkedComponents is not null)
throw new ComponentRegistrationLockException();
if (!types.ContainsKey(target))
{
throw new InvalidOperationException($"Unregistered type: {target}");
@@ -194,15 +213,14 @@ namespace Robust.Shared.GameObjects
private void RemoveComponent(string name)
{
if (_networkedComponents is not null)
throw new ComponentRegistrationLockException();
var registration = names[name];
names.Remove(registration.Name);
_lowerCaseNames.Remove(registration.Name.ToLowerInvariant());
types.Remove(registration.Type);
if (registration.NetID != null)
{
netIDs.Remove(registration.NetID.Value);
}
}
public ComponentAvailability GetComponentAvailability(string componentName, bool ignoreCase = false)
@@ -253,7 +271,7 @@ namespace Robust.Shared.GameObjects
return _typeFactory.CreateInstanceUnchecked<IComponent>(GetRegistration(componentName).Type);
}
public IComponent GetComponent(uint netId)
public IComponent GetComponent(ushort netId)
{
return _typeFactory.CreateInstanceUnchecked<IComponent>(GetRegistration(netId).Type);
}
@@ -275,11 +293,14 @@ namespace Robust.Shared.GameObjects
}
}
public IComponentRegistration GetRegistration(uint netID)
public IComponentRegistration GetRegistration(ushort netID)
{
if (_networkedComponents is null)
throw new ComponentRegistrationLockException();
try
{
return netIDs[netID];
return _networkedComponents[netID];
}
catch (KeyNotFoundException)
{
@@ -343,9 +364,9 @@ namespace Robust.Shared.GameObjects
return TryGetRegistration(typeof(T), out registration);
}
public bool TryGetRegistration(uint netID, [NotNullWhen(true)] out IComponentRegistration? registration)
public bool TryGetRegistration(ushort netID, [NotNullWhen(true)] out IComponentRegistration? registration)
{
if (netIDs.TryGetValue(netID, out var tempRegistration))
if (_networkedComponents is not null && _networkedComponents.TryGetValue(netID, out var tempRegistration))
{
registration = tempRegistration;
return true;
@@ -406,15 +427,36 @@ namespace Robust.Shared.GameObjects
return AllRegistrations.SelectMany(r => r.References).Distinct();
}
public IEnumerable<uint> GetAllNetIds()
/// <inheritdoc />
public void GenerateNetIds()
{
foreach (var registration in AllRegistrations)
// assumptions:
// component names are guaranteed to be unique
// component names are 1:1 with component concrete types
// a subset of component names are networked
var networkedRegs = new List<IComponentRegistration>(names.Count);
foreach (var kvRegistration in names)
{
if (registration.NetID != null)
var registration = kvRegistration.Value;
if (Attribute.GetCustomAttribute(registration.Type, typeof(NetworkedComponentAttribute)) is NetworkedComponentAttribute)
{
yield return registration.NetID.Value;
networkedRegs.Add(registration);
}
}
// The sorting implementation is unstable, but there are no duplicate names, so that isn't a problem.
// Ordinal comparison is used so that the resulting order is always identical on every computer.
networkedRegs.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
for (ushort i = 0; i < networkedRegs.Count; i++)
{
var registration = (ComponentRegistration) networkedRegs[i];
registration.NetID = i;
}
_networkedComponents = networkedRegs;
}
}
@@ -434,4 +476,6 @@ namespace Robust.Shared.GameObjects
SerializationInfo info,
StreamingContext context) : base(info, context) { }
}
public class ComponentRegistrationLockException : Exception { }
}

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Internal.TypeSystem;
using Robust.Shared.Exceptions;
using Robust.Shared.Physics;
using Robust.Shared.Serialization;
@@ -28,7 +30,7 @@ namespace Robust.Shared.GameObjects
private const int EntityCapacity = 1024;
private const int NetComponentCapacity = 8;
private readonly Dictionary<EntityUid, Dictionary<uint, Component>> _netComponents
private readonly Dictionary<EntityUid, Dictionary<ushort, Component>> _netComponents
= new(EntityCapacity);
private readonly Dictionary<Type, Dictionary<EntityUid, Component>> _entTraitDict
@@ -144,14 +146,14 @@ namespace Robust.Shared.GameObjects
}
// add the component to the netId grid
if (component.NetID != null)
if (reg.NetID != null)
{
// the main comp grid keeps this in sync
var netId = component.NetID.Value;
var netId = reg.NetID.Value;
if (!_netComponents.TryGetValue(uid, out var netSet))
{
netSet = new Dictionary<uint, Component>(NetComponentCapacity);
netSet = new Dictionary<ushort, Component>(NetComponentCapacity);
_netComponents.Add(uid, netSet);
}
netSet.Add(netId, component);
@@ -191,7 +193,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveComponent(EntityUid uid, uint netId)
public void RemoveComponent(EntityUid uid, ushort netId)
{
RemoveComponentDeferred((Component)GetComponent(uid, netId), uid, false);
}
@@ -335,13 +337,13 @@ namespace Robust.Shared.GameObjects
}
// ReSharper disable once InvertIf
if (component.NetID != null)
if (reg.NetID != null)
{
var netSet = _netComponents[entityUid];
if (netSet.Count == 1)
_netComponents.Remove(entityUid);
else
netSet.Remove(component.NetID.Value);
netSet.Remove(reg.NetID.Value);
component.Owner.Dirty();
}
@@ -367,7 +369,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasComponent(EntityUid uid, uint netId)
public bool HasComponent(EntityUid uid, ushort netId)
{
return _netComponents.TryGetValue(uid, out var netSet)
&& netSet.ContainsKey(netId);
@@ -397,7 +399,7 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public IComponent GetComponent(EntityUid uid, uint netId)
public IComponent GetComponent(EntityUid uid, ushort netId)
{
return _netComponents[uid][netId];
}
@@ -436,7 +438,7 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public bool TryGetComponent(EntityUid uid, uint netId, [MaybeNullWhen(false)] out IComponent component)
public bool TryGetComponent(EntityUid uid, ushort netId, [MaybeNullWhen(false)] out IComponent component)
{
if (_netComponents.TryGetValue(uid, out var netSet)
&& netSet.TryGetValue(netId, out var comp))
@@ -474,9 +476,9 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public IEnumerable<IComponent> GetNetComponents(EntityUid uid)
public NetComponentEnumerable GetNetComponents(EntityUid uid)
{
return _netComponents[uid].Values;
return new NetComponentEnumerable(_netComponents[uid]);
}
#region Join Functions
@@ -597,4 +599,30 @@ namespace Robust.Shared.GameObjects
}
}
}
public readonly struct NetComponentEnumerable
{
private readonly Dictionary<ushort, Component> _dictionary;
public NetComponentEnumerable(Dictionary<ushort, Component> dictionary) => _dictionary = dictionary;
public NetComponentEnumerator GetEnumerator() => new(_dictionary);
}
public struct NetComponentEnumerator
{
// DO NOT MAKE THIS READONLY
private Dictionary<ushort, Component>.Enumerator _dictEnum;
public NetComponentEnumerator(Dictionary<ushort, Component> dictionary) => _dictEnum = dictionary.GetEnumerator();
public bool MoveNext() => _dictEnum.MoveNext();
public (ushort netId, IComponent component) Current
{
get
{
var val = _dictEnum.Current;
return (val.Key, val.Value);
}
}
}
}

View File

@@ -1,18 +1,10 @@
using Robust.Shared.Serialization;
using System;
using Robust.Shared.Analyzers;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects
{
[RequiresSerializable]
[Serializable, NetSerializable]
public class ComponentState
{
public uint NetID { get; }
public ComponentState(uint netID)
{
NetID = netID;
}
}
public class ComponentState { }
}

View File

@@ -1,6 +1,7 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects
@@ -11,10 +12,10 @@ namespace Robust.Shared.GameObjects
/// The data works using a simple key/value system. It is recommended to use enum keys to prevent errors.
/// Visualization works client side with overrides of the <c>AppearanceVisualizer</c> class.
/// </summary>
[NetworkedComponent()]
public abstract class SharedAppearanceComponent : Component
{
public override string Name => "Appearance";
public override uint? NetID => NetIDs.APPEARANCE;
public abstract void SetData(string key, object value);
public abstract void SetData(Enum key, object value);
@@ -30,7 +31,7 @@ namespace Robust.Shared.GameObjects
{
public readonly Dictionary<object, object> Data;
public AppearanceComponentState(Dictionary<object, object> data) : base(NetIDs.APPEARANCE)
public AppearanceComponentState(Dictionary<object, object> data)
{
Data = data;
}

View File

@@ -9,7 +9,7 @@ namespace Robust.Shared.GameObjects
{
public Box2? LocalBounds { get; }
public ClickableComponentState(Box2? localBounds) : base(NetIDs.CLICKABLE)
public ClickableComponentState(Box2? localBounds)
{
LocalBounds = localBounds;
}

View File

@@ -26,6 +26,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
@@ -44,6 +45,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
[ComponentReference(typeof(IPhysBody))]
[NetworkedComponent()]
public sealed class PhysicsComponent : Component, IPhysBody, ISerializationHooks
{
[DataField("status", readOnly: true)]
@@ -52,9 +54,6 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public override string Name => "Physics";
/// <inheritdoc />
public override uint? NetID => NetIDs.PHYSICS;
/// <summary>
/// Has this body been added to an island previously in this tick.
/// </summary>
@@ -74,6 +73,23 @@ namespace Robust.Shared.GameObjects
public bool IgnoreCCD { get; set; }
[ViewVariables]
public int ContactCount
{
get
{
var count = 0;
var edge = ContactEdges;
while (edge != null)
{
edge = edge.Next;
count++;
}
return count;
}
}
/// <summary>
/// Linked-list of all of our contacts.
/// </summary>
@@ -834,6 +850,32 @@ namespace Robust.Shared.GameObjects
[DataField("angularDamping")]
private float _angularDamping = 0.02f;
/// <summary>
/// Get the linear and angular velocities at the same time.
/// </summary>
public (Vector2 Linear, float Angular) MapVelocities
{
get
{
var linearVelocity = _linVelocity;
var angularVelocity = _angVelocity;
var parent = Owner.Transform.Parent?.Owner;
while (parent != null)
{
if (parent.TryGetComponent(out PhysicsComponent? body))
{
linearVelocity += body.LinearVelocity;
angularVelocity += body.AngularVelocity;
}
parent = parent.Transform.Parent?.Owner;
}
return (linearVelocity, angularVelocity);
}
}
/// <summary>
/// Current linear velocity of the entity in meters per second.
/// </summary>
@@ -862,6 +904,33 @@ namespace Robust.Shared.GameObjects
private Vector2 _linVelocity;
/// <summary>
/// Get the body's LinearVelocity in map terms.
/// </summary>
/// <remarks>
/// Consider using <see cref="MapVelocities"/> if you need linear and angular at the same time.
/// </remarks>
public Vector2 MapLinearVelocity
{
get
{
var velocity = _linVelocity;
var parent = Owner.Transform.Parent?.Owner;
while (parent != null)
{
if (parent.TryGetComponent(out PhysicsComponent? body))
{
velocity += body.LinearVelocity;
}
parent = parent.Transform.Parent?.Owner;
}
return velocity;
}
}
/// <summary>
/// Current angular velocity of the entity in radians per sec.
/// </summary>
@@ -890,6 +959,33 @@ namespace Robust.Shared.GameObjects
private float _angVelocity;
/// <summary>
/// Get the body's AngularVelocity in map terms.
/// </summary>
/// <remarks>
/// Consider using <see cref="MapVelocities"/> if you need linear and angular at the same time.
/// </remarks>
public float MapAngularVelocity
{
get
{
var velocity = _angVelocity;
var parent = Owner.Transform.Parent?.Owner;
while (parent != null)
{
if (parent.TryGetComponent(out PhysicsComponent? body))
{
velocity += body.AngularVelocity;
}
parent = parent.Transform.Parent?.Owner;
}
return velocity;
}
}
/// <summary>
/// Current momentum of the entity in kilogram meters per second
/// </summary>

View File

@@ -44,7 +44,6 @@ namespace Robust.Shared.GameObjects
Vector2 linearVelocity,
float angularVelocity,
BodyType bodyType)
: base(NetIDs.PHYSICS)
{
CanCollide = canCollide;
SleepingAllowed = sleepingAllowed;

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using Robust.Shared.GameStates;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
@@ -11,10 +12,10 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// An optimisation component for stuff that should be set as collidable when it's awake and non-collidable when asleep.
/// </summary>
[NetworkedComponent()]
public sealed class CollisionWakeComponent : Component
{
public override string Name => "CollisionWake";
public override uint? NetID => NetIDs.COLLISION_WAKE;
[DataField("enabled")]
private bool _enabled = true;
@@ -64,7 +65,7 @@ namespace Robust.Shared.GameObjects
{
public bool Enabled { get; }
public CollisionWakeState(bool enabled) : base(NetIDs.COLLISION_WAKE)
public CollisionWakeState(bool enabled)
{
Enabled = enabled;
}

View File

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

View File

@@ -1,14 +1,15 @@
using System;
using Robust.Shared.GameStates;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
[NetworkedComponent()]
public class SharedEyeComponent : Component
{
public override string Name => "Eye";
public override uint? NetID => NetIDs.EYE;
[ViewVariables(VVAccess.ReadWrite)]
public virtual bool DrawFov { get; set; }
@@ -39,7 +40,7 @@ namespace Robust.Shared.GameObjects
public Angle Rotation { get; }
public uint VisibilityMask { get; }
public EyeComponentState(bool drawFov, Vector2 zoom, Vector2 offset, Angle rotation, uint visibilityMask) : base(NetIDs.EYE)
public EyeComponentState(bool drawFov, Vector2 zoom, Vector2 offset, Angle rotation, uint visibilityMask)
{
DrawFov = drawFov;
Zoom = zoom;

View File

@@ -1,4 +1,5 @@
using System;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
@@ -9,10 +10,10 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
[NetworkedComponent()]
public class OccluderComponent : Component
{
public sealed override string Name => "Occluder";
public sealed override uint? NetID => NetIDs.OCCLUDER;
[DataField("enabled")]
private bool _enabled = true;
@@ -94,7 +95,7 @@ namespace Robust.Shared.GameObjects
public bool Enabled { get; }
public Box2 BoundingBox { get; }
public OccluderComponentState(bool enabled, Box2 boundingBox) : base(NetIDs.OCCLUDER)
public OccluderComponentState(bool enabled, Box2 boundingBox)
{
Enabled = enabled;
BoundingBox = boundingBox;

View File

@@ -14,7 +14,6 @@ namespace Robust.Shared.GameObjects
public readonly Vector2 Offset;
public PointLightComponentState(bool enabled, Color color, float radius, Vector2 offset)
: base(NetIDs.POINT_LIGHT)
{
Enabled = enabled;
Color = color;

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Enums;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
@@ -10,10 +11,10 @@ namespace Robust.Shared.GameObjects.Components.Localization
/// Overrides grammar attributes specified in prototypes or localization files.
/// </summary>
[RegisterComponent]
[NetworkedComponent()]
public class GrammarComponent : Component
{
public override string Name => "Grammar";
public override uint? NetID => NetIDs.GRAMMAR;
[ViewVariables]
[DataField("attributes")]

View File

@@ -1,4 +1,5 @@
using System;
using System;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
@@ -18,6 +19,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc cref="IMapComponent"/>
[ComponentReference(typeof(IMapComponent))]
[NetworkedComponent()]
public class MapComponent : Component, IMapComponent
{
[ViewVariables(VVAccess.ReadOnly)]
@@ -27,9 +29,6 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public override string Name => "Map";
/// <inheritdoc />
public override uint? NetID => NetIDs.MAP_MAP;
/// <inheritdoc />
public MapId WorldMap
{
@@ -73,7 +72,6 @@ namespace Robust.Shared.GameObjects
public MapId MapId { get; }
public MapComponentState(MapId mapId)
: base(NetIDs.MAP_MAP)
{
MapId = mapId;
}

View File

@@ -1,4 +1,5 @@
using System;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -25,6 +26,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc cref="IMapGridComponent"/>
[ComponentReference(typeof(IMapGridComponent))]
[NetworkedComponent()]
internal class MapGridComponent : Component, IMapGridComponent
{
[Dependency] private readonly IMapManager _mapManager = default!;
@@ -36,9 +38,6 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public override string Name => "MapGrid";
/// <inheritdoc />
public override uint? NetID => NetIDs.MAP_GRID;
/// <inheritdoc />
public GridId GridIndex
{
@@ -79,7 +78,6 @@ namespace Robust.Shared.GameObjects
xform.Parent = Owner.Transform;
// anchor snapping
xform.LocalRotation = xform.LocalRotation.GetCardinalDir().ToAngle();
xform.LocalPosition = Grid.GridTileToLocal(Grid.TileIndicesFor(xform.LocalPosition)).Position;
xform.SetAnchored(result);
@@ -146,7 +144,6 @@ namespace Robust.Shared.GameObjects
/// </summary>
/// <param name="gridIndex">Index of the grid this component is linked to.</param>
public MapGridComponentState(GridId gridIndex)
: base(NetIDs.MAP_GRID)
{
GridIndex = gridIndex;
}

View File

@@ -1,4 +1,5 @@
using System;
using System;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
@@ -35,7 +36,6 @@ namespace Robust.Shared.GameObjects
/// <param name="description">The in-game description of this entity.</param>
/// <param name="prototypeId">The prototype this entity was created from, if any.</param>
public MetaDataComponentState(string? name, string? description, string? prototypeId)
: base(NetIDs.META_DATA)
{
Name = name;
Description = description;
@@ -66,6 +66,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc cref="IMetaDataComponent"/>
[ComponentReference(typeof(IMetaDataComponent))]
[NetworkedComponent()]
internal class MetaDataComponent : Component, IMetaDataComponent
{
[Dependency] private readonly IPrototypeManager _prototypes = default!;
@@ -79,9 +80,6 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public override string Name => "MetaData";
/// <inheritdoc />
public override uint? NetID => NetIDs.META_DATA;
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
public string EntityName

View File

@@ -1,21 +0,0 @@
namespace Robust.Shared.GameObjects
{
public static class NetIDs
{
public const uint MAP_MAP = 1;
public const uint MAP_GRID = 2;
public const uint META_DATA = 4;
public const uint TRANSFORM = 5;
public const uint SPRITE = 8;
public const uint POINT_LIGHT = 10;
public const uint PHYSICS = 12;
public const uint CLICKABLE = 14;
public const uint APPEARANCE = 22;
public const uint USERINTERFACE = 24;
public const uint CONTAINER_MANAGER = 25;
public const uint OCCLUDER = 26;
public const uint EYE = 28;
public const uint GRAMMAR = 29;
public const uint COLLISION_WAKE = 30;
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameStates;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -7,10 +8,10 @@ using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects
{
[NetworkedComponent()]
public abstract class SharedSpriteComponent : Component
{
public override string Name => "Sprite";
public override uint? NetID => NetIDs.SPRITE;
public abstract bool Visible { get; set; }
@@ -42,7 +43,6 @@ namespace Robust.Shared.GameObjects
string? baseRsiPath,
List<PrototypeLayerData> layers,
uint renderOrder)
: base(NetIDs.SPRITE)
{
Visible = visible;
DrawDepth = drawDepth;

View File

@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.Animations;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -16,6 +17,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
[ComponentReference(typeof(ITransformComponent))]
[NetworkedComponent()]
internal class TransformComponent : Component, ITransformComponent, IComponentDebug
{
[DataField("parent")]
@@ -54,9 +56,6 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public override string Name => "Transform";
/// <inheritdoc />
public sealed override uint? NetID => NetIDs.TRANSFORM;
/// <inheritdoc />
[ViewVariables]
public MapId MapID { get; private set; }
@@ -113,9 +112,6 @@ namespace Robust.Shared.GameObjects
if (_localRotation.EqualsApprox(value, 0.00001))
return;
if(Anchored)
return;
var oldRotation = _localRotation;
// Set _nextRotation to null to break any active lerps if this is a client side prediction.
@@ -899,7 +895,6 @@ namespace Robust.Shared.GameObjects
/// <param name="parentId">Current parent transform of this entity.</param>
/// <param name="noLocalRotation"></param>
public TransformComponentState(Vector2 localPosition, Angle rotation, EntityUid parentId, bool noLocalRotation, bool anchored)
: base(NetIDs.TRANSFORM)
{
LocalPosition = localPosition;
Rotation = rotation;

View File

@@ -1,4 +1,5 @@
using System;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
@@ -6,10 +7,10 @@ using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.GameObjects
{
[NetworkedComponent()]
public abstract class SharedUserInterfaceComponent : Component
{
public sealed override string Name => "UserInterface";
public sealed override uint? NetID => NetIDs.USERINTERFACE;
[DataDefinition]
public sealed class PrototypeData : ISerializationHooks

View File

@@ -84,6 +84,7 @@ namespace Robust.Shared.GameObjects
return;
_paused = value;
EntityManager.EventBus.RaiseLocalEvent(Uid, new EntityPausedEvent(Uid, value));
}
}

View File

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

View File

@@ -21,7 +21,7 @@ namespace Robust.Shared.GameObjects
[IoC.Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
[IoC.Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!;
[IoC.Dependency] private readonly IComponentFactory ComponentFactory = default!;
[IoC.Dependency] protected readonly IComponentFactory ComponentFactory = default!;
[IoC.Dependency] private readonly IComponentManager _componentManager = default!;
[IoC.Dependency] private readonly IMapManager _mapManager = default!;
[IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
@@ -32,6 +32,8 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public GameTick CurrentTick => _gameTiming.CurTick;
IComponentFactory IEntityManager.ComponentFactory => ComponentFactory;
/// <inheritdoc />
public IComponentManager ComponentManager => _componentManager;
@@ -479,7 +481,7 @@ namespace Robust.Shared.GameObjects
var uid = netMsg.EntityUid;
if (compMsg.Directed)
{
if (_componentManager.TryGetComponent(uid, netMsg.NetId, out var component))
if (_componentManager.TryGetComponent(uid, (ushort) netMsg.NetId, out var component))
component.HandleNetworkMessage(compMsg, compChannel, session);
}
else

View File

@@ -7,61 +7,71 @@ namespace Robust.Shared.GameObjects
public sealed class EntityState
{
public EntityUid Uid { get; }
public ComponentChanged[]? ComponentChanges { get; }
public ComponentState[]? ComponentStates { get; }
public bool Empty => ComponentChanges is null && ComponentStates is null;
public ComponentChange[]? ComponentChanges { get; }
public EntityState(EntityUid uid, ComponentChanged[]? changedComponents, ComponentState[]? componentStates)
public bool Empty => ComponentChanges is null;
public EntityState(EntityUid uid, ComponentChange[]? changedComponents)
{
Uid = uid;
// empty lists are 5 bytes each
ComponentChanges = changedComponents == null || changedComponents.Length == 0 ? null : changedComponents;
ComponentStates = componentStates == null || componentStates.Length == 0 ? null : componentStates;
}
}
[Serializable, NetSerializable]
public readonly struct ComponentChanged
public readonly struct ComponentChange
{
// 15ish bytes to create a component (strings are big), 5 bytes to remove one
/// <summary>
/// Was the component added or removed from the entity.
/// Was the component removed from the entity.
/// </summary>
public readonly bool Deleted;
/// <summary>
/// The Network ID of the component to remove.
/// Was the component added to the entity.
/// </summary>
public readonly uint NetID;
public readonly bool Created;
/// <summary>
/// The prototype name of the component to add.
/// State data for the created/modified component, if any.
/// </summary>
public readonly string? ComponentName;
public readonly ComponentState? State;
public ComponentChanged(bool deleted, uint netId, string? componentName)
/// <summary>
/// The Network ID of the component to remove.
/// </summary>
public readonly ushort NetID;
public ComponentChange(ushort netId, bool created, bool deleted, ComponentState? state)
{
Deleted = deleted;
State = state;
NetID = netId;
ComponentName = componentName;
Created = created;
}
public override string ToString()
{
return $"{(Deleted ? "D" : "C")} {NetID} {ComponentName}";
return $"{(Deleted ? "D" : "C")} {NetID} {State?.GetType().Name}";
}
public static ComponentChanged Added(uint netId, string componentName)
public static ComponentChange Added(ushort netId, ComponentState? state)
{
return new(false, netId, componentName);
return new(netId, true, false, state);
}
public static ComponentChanged Removed(uint netId)
public static ComponentChange Changed(ushort netId, ComponentState state)
{
return new(true, netId, null);
return new(netId, false, false, state);
}
public static ComponentChange Removed(ushort netId)
{
return new(netId, false, true, null);
}
}
}

View File

@@ -0,0 +1,14 @@
namespace Robust.Shared.GameObjects
{
public sealed class EntityPausedEvent : EntityEventArgs
{
public EntityUid Entity { get; }
public bool Paused { get; }
public EntityPausedEvent(EntityUid entity, bool paused)
{
Entity = entity;
Paused = paused;
}
}
}

View File

@@ -1,7 +1,7 @@
using System;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
namespace Robust.Shared.GameObjects
@@ -13,16 +13,6 @@ namespace Robust.Shared.GameObjects
/// </remarks>
public interface IComponent
{
/// <summary>
/// Represents the network ID for the component.
/// The network ID is used to determine which component will receive the component state
/// on the other side of the network.
/// If this is <c>null</c>, the component is not replicated across the network.
/// </summary>
/// <seealso cref="NetworkSynchronizeExistence" />
/// <seealso cref="IComponentRegistration.NetID" />
uint? NetID { get; }
/// <summary>
/// Name that this component is represented with in prototypes.
/// </summary>
@@ -40,21 +30,11 @@ namespace Robust.Shared.GameObjects
/// </summary>
ComponentLifeStage LifeStage { get; }
/// <summary>
/// Whether the client should synchronize component additions and removals.
/// If this is false and the component gets added or removed server side, the client will not do the same.
/// If this is true and the server adds or removes the component, the client will do as such too.
/// This flag has no effect if <see cref="NetID" /> is <c>null</c>.
/// This is disabled by default, usually the client builds their instance from a prototype.
/// </summary>
/// <seealso cref="IComponentRegistration.NetworkSynchronizeExistence" />
bool NetworkSynchronizeExistence { get; }
/// <summary>
/// Whether this component should be synchronized with clients when modified.
/// If this is true, the server will synchronize all client instances with the data in this instance.
/// If this is false, clients can modify the data in their instances without being overwritten by the server.
/// This flag has no effect if <see cref="NetID" /> is <c>null</c>.
/// This flag has no effect if <see cref="NetworkedComponentAttribute" /> is not defined on the component.
/// This is enabled by default.
/// </summary>
bool NetSyncEnabled { get; }

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Robust.Shared.GameObjects
@@ -58,6 +59,16 @@ namespace Robust.Shared.GameObjects
/// </summary>
IEnumerable<Type> AllRegisteredTypes { get; }
/// <summary>
/// The subset of all registered components that are networked, so that they can be
/// referenced between the client and the server.
/// </summary>
/// <remarks>
/// This will be null if the network Ids have not been generated yet.
/// </remarks>
/// <seealso cref="GenerateNetIds"/>
IReadOnlyList<IComponentRegistration>? NetworkedComponents { get; }
/// <summary>
/// Get whether a component is available right now.
/// </summary>
@@ -142,7 +153,7 @@ namespace Robust.Shared.GameObjects
/// <exception cref="UnknownComponentException">
/// Thrown if no component exists with the given id <see cref="netId"/>.
/// </exception>
IComponent GetComponent(uint netId);
IComponent GetComponent(ushort netId);
/// <summary>
/// Gets the registration belonging to a component, throwing an exception if it does not exist.
@@ -181,7 +192,7 @@ namespace Robust.Shared.GameObjects
/// <exception cref="UnknownComponentException">
/// Thrown if no component with id <see cref="netID"/> exists.
/// </exception>
IComponentRegistration GetRegistration(uint netID);
IComponentRegistration GetRegistration(ushort netID);
/// <summary>
/// Gets the registration of a component, throwing an exception if
@@ -225,7 +236,7 @@ namespace Robust.Shared.GameObjects
/// <param name="netID">The network ID corresponding to the component.</param>
/// <param name="registration">The registration if found, null otherwise.</param>
/// <returns>true it found, false otherwise.</returns>
bool TryGetRegistration(uint netID, [NotNullWhen(true)] out IComponentRegistration? registration);
bool TryGetRegistration(ushort netID, [NotNullWhen(true)] out IComponentRegistration? registration);
/// <summary>
/// Tries to get the registration of a component.
@@ -241,8 +252,7 @@ namespace Robust.Shared.GameObjects
void DoAutoRegistrations();
IEnumerable<Type> GetAllRefTypes();
IEnumerable<uint> GetAllNetIds();
void GenerateNetIds();
}
/// <summary>
@@ -263,18 +273,8 @@ namespace Robust.Shared.GameObjects
/// ID used to reference the component type across the network.
/// If null, no network synchronization will be available for this component.
/// </summary>
/// <seealso cref="IComponent.NetID" />
uint? NetID { get; }
/// <summary>
/// True if the addition and removal of the component will be synchronized to clients.
/// This means that if the server adds or removes the component outside of prototype-based creation,
/// the client will update accordingly.
/// If false the client will ignore missing components even when the net ID checks out and could be instantiated.
/// and the client won't delete the component if no state was sent for it.
/// </summary>
/// <seealso cref="IComponent.NetworkSynchronizeExistence" />
bool NetworkSynchronizeExistence { get; }
/// <seealso cref="NetworkedComponentAttribute" />
ushort? NetID { get; }
/// <summary>
/// The type that will be instantiated if this component is created.

View File

@@ -72,7 +72,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
/// <param name="uid">Entity UID to modify.</param>
/// <param name="netID">Network ID of the component to remove.</param>
void RemoveComponent(EntityUid uid, uint netID);
void RemoveComponent(EntityUid uid, ushort netID);
/// <summary>
/// Removes the specified component.
@@ -118,7 +118,7 @@ namespace Robust.Shared.GameObjects
/// <param name="uid">Entity UID to check.</param>
/// <param name="netId">Network ID to check for.</param>
/// <returns>True if the entity has a component with the given network ID, otherwise false.</returns>
bool HasComponent(EntityUid uid, uint netId);
bool HasComponent(EntityUid uid, ushort netId);
/// <summary>
/// Returns the component of a specific type.
@@ -143,7 +143,7 @@ namespace Robust.Shared.GameObjects
/// <param name="uid">Entity UID to look on.</param>
/// <param name="netId">Network ID of the component to retrieve.</param>
/// <returns>The component with the specified network id.</returns>
IComponent GetComponent(EntityUid uid, uint netId);
IComponent GetComponent(EntityUid uid, ushort netId);
/// <summary>
/// Returns the component of a specific type.
@@ -171,7 +171,7 @@ namespace Robust.Shared.GameObjects
/// <param name="netId">Component Network ID to check for.</param>
/// <param name="component">Component with the specified network id.</param>
/// <returns>If the component existed in the entity.</returns>
bool TryGetComponent(EntityUid uid, uint netId, [NotNullWhen(true)] out IComponent? component);
bool TryGetComponent(EntityUid uid, ushort netId, [NotNullWhen(true)] out IComponent? component);
/// <summary>
/// Returns ALL component type instances on an entity. A single component instance
@@ -195,7 +195,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
/// <param name="uid">Entity UID to look on.</param>
/// <returns>All components that have a network ID.</returns>
IEnumerable<IComponent> GetNetComponents(EntityUid uid);
NetComponentEnumerable GetNetComponents(EntityUid uid);
/// <summary>
/// Returns ALL component instances of a specified type.

View File

@@ -24,6 +24,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
void FrameUpdate(float frameTime);
IComponentFactory ComponentFactory { get; }
IComponentManager ComponentManager { get; }
IEntitySystemManager EntitySysManager { get; }
IEntityNetworkManager? EntityNetManager { get; }

View File

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

View File

@@ -100,19 +100,59 @@ namespace Robust.Shared.GameObjects
_mapManager.MapCreated += HandleMapCreated;
_mapManager.MapDestroyed += HandleMapDestroyed;
SubscribeLocalEvent<GridInitializeEvent>(HandleGridInit);
SubscribeLocalEvent<PhysicsUpdateMessage>(HandlePhysicsUpdateMessage);
SubscribeLocalEvent<PhysicsWakeMessage>(HandleWakeMessage);
SubscribeLocalEvent<PhysicsSleepMessage>(HandleSleepMessage);
SubscribeLocalEvent<EntMapIdChangedMessage>(HandleMapChange);
SubscribeLocalEvent<EntInsertedIntoContainerMessage>(HandleContainerInserted);
SubscribeLocalEvent<EntRemovedFromContainerMessage>(HandleContainerRemoved);
SubscribeLocalEvent<EntParentChangedMessage>(HandleParentChange);
BuildControllers();
Logger.DebugS("physics", $"Found {_controllers.Count} physics controllers.");
IoCManager.Resolve<IIslandManager>().Initialize();
}
private void HandleParentChange(EntParentChangedMessage args)
{
var entity = args.Entity;
if (!entity.Initialized ||
!entity.TryGetComponent(out PhysicsComponent? body) ||
entity.IsInContainer()) return;
var oldParent = args.OldParent;
var linearVelocityDiff = Vector2.Zero;
var angularVelocityDiff = 0f;
if (oldParent != null && oldParent.TryGetComponent(out PhysicsComponent? oldBody))
{
var (linear, angular) = oldBody.MapVelocities;
linearVelocityDiff += linear;
angularVelocityDiff += angular;
}
if (entity.Transform.Parent!.Owner.TryGetComponent(out PhysicsComponent? newBody))
{
var (linear, angular) = newBody.MapVelocities;
linearVelocityDiff -= linear;
angularVelocityDiff -= angular;
}
body.LinearVelocity += linearVelocityDiff;
body.AngularVelocity += angularVelocityDiff;
}
private void HandleGridInit(GridInitializeEvent ev)
{
if (!EntityManager.TryGetEntity(ev.EntityUid, out var gridEntity)) return;
var collideComp = gridEntity.EnsureComponent<PhysicsComponent>();
collideComp.BodyType = BodyType.Static;
}
private void BuildControllers()
{

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