Compare commits

..

21 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
147 changed files with 2065 additions and 885 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

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

@@ -5,6 +5,7 @@ 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;
@@ -17,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; }

View File

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

View File

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

View File

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

View File

@@ -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 />
@@ -343,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;
}
@@ -368,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);
}
}
@@ -432,8 +440,7 @@ namespace Robust.Client.GameStates
}
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!");
@@ -543,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)
@@ -559,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;
@@ -195,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

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

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

View File

@@ -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
{
@@ -145,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; }
@@ -896,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

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

@@ -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()
{

View File

@@ -0,0 +1,10 @@
using System;
namespace Robust.Shared.GameStates
{
/// <summary>
/// This attribute marks a component as networked, so that it is replicated to clients.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class NetworkedComponentAttribute : Attribute { }
}

View File

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

View File

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

View File

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

View File

@@ -25,6 +25,11 @@
/// </summary>
string SpriteName { get; }
/// <summary>
/// Path to the folder where the tile sprite is contained.
/// </summary>
string Path { get; }
/// <summary>
/// Physics objects that are interacting on this tile are slowed down by this float.
/// </summary>

View File

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

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -40,7 +41,7 @@ namespace Robust.Shared.Map
private readonly Dictionary<Vector2i, IMapChunkInternal> _chunks = new();
private readonly IMapManagerInternal _mapManager;
private readonly IEntityManager _entityManager;
private readonly IEntityManager _entityManager;
/// <summary>
/// Initializes a new instance of the <see cref="MapGrid"/> class.
@@ -146,8 +147,18 @@ namespace Robust.Shared.Map
}
/// <inheritdoc />
public void NotifyChunkCollisionRegenerated()
public void NotifyChunkCollisionRegenerated(MapChunk chunk)
{
// TODO: Ideally we wouldn't have LocalBounds on the grid and we could just treat it like a physics object
// (eventually, well into the future).
// For now we'll just attach a fixture to each chunk.
// Not raising directed because the grid's EntityUid isn't set yet.
IoCManager
.Resolve<IEntityManager>()
.EventBus
.RaiseEvent(EventSource.Local, new RegenerateChunkCollisionEvent(chunk));
UpdateAABB();
}

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