mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e16732eb7b | ||
|
|
91f61bb9de | ||
|
|
ddc91d05ec | ||
|
|
ef22842b90 | ||
|
|
303e2152d2 | ||
|
|
37fc0d0d2a | ||
|
|
53987e1e5d | ||
|
|
3216d7770b | ||
|
|
3203ca2ff4 | ||
|
|
e22254cd51 | ||
|
|
7ed722f669 | ||
|
|
4864096b2a | ||
|
|
5161385de4 | ||
|
|
98e009b38f | ||
|
|
3863ab8f62 | ||
|
|
f576eb5125 | ||
|
|
314742ccd8 | ||
|
|
f9074811f9 | ||
|
|
5f3e1eb378 | ||
|
|
3c1ee20ca1 | ||
|
|
3768f5e68e | ||
|
|
765a560380 |
Submodule Lidgren.Network/Lidgren.Network updated: 73554e6061...5fc11c2b2b
@@ -27,8 +27,6 @@ using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Robust.Client
|
||||
{
|
||||
@@ -66,8 +64,6 @@ namespace Robust.Client
|
||||
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
|
||||
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IFontManager, FontManager>();
|
||||
IoCManager.Register<IFontManagerInternal, FontManager>();
|
||||
IoCManager.Register<IMidiManager, MidiManager>();
|
||||
IoCManager.Register<IAuthManager, AuthManager>();
|
||||
switch (mode)
|
||||
@@ -94,8 +90,9 @@ namespace Robust.Client
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
IoCManager.Register<IFontManager, FontManager>();
|
||||
IoCManager.Register<IFontManagerInternal, FontManager>();
|
||||
IoCManager.Register<IEyeManager, EyeManager>();
|
||||
|
||||
IoCManager.Register<IPlacementManager, PlacementManager>();
|
||||
IoCManager.Register<IOverlayManager, OverlayManager>();
|
||||
IoCManager.Register<IOverlayManagerInternal, OverlayManager>();
|
||||
|
||||
44
Robust.Client/Console/Commands/MonitorCommands.cs
Normal file
44
Robust.Client/Console/Commands/MonitorCommands.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class LsMonitorCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "lsmonitor";
|
||||
public string Description => "";
|
||||
public string Help => "";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
|
||||
foreach (var monitor in clyde.EnumerateMonitors())
|
||||
{
|
||||
shell.WriteLine(
|
||||
$"[{monitor.Id}] {monitor.Name}: {monitor.Size.X}x{monitor.Size.Y}@{monitor.RefreshRate}Hz");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class SetMonitorCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "setmonitor";
|
||||
public string Description => "";
|
||||
public string Help => "Usage: setmonitor <id>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
|
||||
var id = int.Parse(args[0]);
|
||||
var monitor = clyde.EnumerateMonitors().Single(m => m.Id == id);
|
||||
clyde.SetWindowMonitor(monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,6 +161,8 @@ namespace Robust.Client.Debugging
|
||||
if (viewport.IsEmpty()) return;
|
||||
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
|
||||
var colorEdge = Color.Red.WithAlpha(0.33f);
|
||||
|
||||
foreach (var physBody in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(mapId, viewport))
|
||||
{
|
||||
@@ -170,9 +172,6 @@ namespace Robust.Client.Debugging
|
||||
var worldBox = physBody.GetWorldAABB();
|
||||
if (worldBox.IsEmpty()) continue;
|
||||
|
||||
var colorEdge = Color.Red.WithAlpha(0.33f);
|
||||
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
|
||||
|
||||
foreach (var fixture in physBody.Fixtures)
|
||||
{
|
||||
var shape = fixture.Shape;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Management;
|
||||
using System.Net;
|
||||
@@ -82,65 +82,8 @@ namespace Robust.Client
|
||||
|
||||
public bool Startup(Func<ILogHandler>? logHandlerFactory = null)
|
||||
{
|
||||
ReadInitialLaunchState();
|
||||
|
||||
SetupLogging(_logManager, logHandlerFactory ?? (() => new ConsoleLogHandler()));
|
||||
|
||||
_taskManager.Initialize();
|
||||
|
||||
// Figure out user data directory.
|
||||
var userDataDir = GetUserDataDir();
|
||||
|
||||
_configurationManager.Initialize(false);
|
||||
|
||||
// MUST load cvars before loading from config file so the cfg manager is aware of secure cvars.
|
||||
// So SECURE CVars are blacklisted from config.
|
||||
_configurationManager.LoadCVarsFromAssembly(typeof(GameController).Assembly); // Client
|
||||
_configurationManager.LoadCVarsFromAssembly(typeof(IConfigurationManager).Assembly); // Shared
|
||||
|
||||
if (LoadConfigAndUserData)
|
||||
{
|
||||
var configFile = Path.Combine(userDataDir, "client_config.toml");
|
||||
if (File.Exists(configFile))
|
||||
{
|
||||
// Load config from user data if available.
|
||||
_configurationManager.LoadFromFile(configFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Else we just use code-defined defaults and let it save to file when the user changes things.
|
||||
_configurationManager.SetSaveFile(configFile);
|
||||
}
|
||||
}
|
||||
|
||||
_configurationManager.OverrideConVars(EnvironmentVariables.GetEnvironmentCVars());
|
||||
|
||||
if (_commandLineArgs != null)
|
||||
{
|
||||
_configurationManager.OverrideConVars(_commandLineArgs.CVars);
|
||||
}
|
||||
|
||||
ProfileOptSetup.Setup(_configurationManager);
|
||||
|
||||
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
|
||||
|
||||
ProgramShared.DoMounts(_resourceCache, _commandLineArgs?.MountOptions, "Content.Client", _loaderArgs != null);
|
||||
if (_loaderArgs != null)
|
||||
{
|
||||
_stringSerializer.EnableCaching = false;
|
||||
_resourceCache.MountLoaderApi(_loaderArgs.FileApi, "Resources/");
|
||||
_modLoader.VerifierExtraLoadHandler = VerifierExtraLoadHandler;
|
||||
}
|
||||
|
||||
// Bring display up as soon as resources are mounted.
|
||||
if (!_clyde.Initialize())
|
||||
{
|
||||
if (!StartupSystemSplash(logHandlerFactory))
|
||||
return false;
|
||||
}
|
||||
|
||||
_clyde.SetWindowTitle("Space Station 14");
|
||||
|
||||
_fontManager.Initialize();
|
||||
|
||||
// Disable load context usage on content start.
|
||||
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
|
||||
@@ -205,6 +148,77 @@ namespace Robust.Client
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool StartupSystemSplash(Func<ILogHandler>? logHandlerFactory)
|
||||
{
|
||||
ReadInitialLaunchState();
|
||||
|
||||
SetupLogging(_logManager, logHandlerFactory ?? (() => new ConsoleLogHandler()));
|
||||
|
||||
_taskManager.Initialize();
|
||||
|
||||
// Figure out user data directory.
|
||||
var userDataDir = GetUserDataDir();
|
||||
|
||||
_configurationManager.Initialize(false);
|
||||
|
||||
// MUST load cvars before loading from config file so the cfg manager is aware of secure cvars.
|
||||
// So SECURE CVars are blacklisted from config.
|
||||
_configurationManager.LoadCVarsFromAssembly(typeof(GameController).Assembly); // Client
|
||||
_configurationManager.LoadCVarsFromAssembly(typeof(IConfigurationManager).Assembly); // Shared
|
||||
|
||||
if (LoadConfigAndUserData)
|
||||
{
|
||||
var configFile = Path.Combine(userDataDir, "client_config.toml");
|
||||
if (File.Exists(configFile))
|
||||
{
|
||||
// Load config from user data if available.
|
||||
_configurationManager.LoadFromFile(configFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Else we just use code-defined defaults and let it save to file when the user changes things.
|
||||
_configurationManager.SetSaveFile(configFile);
|
||||
}
|
||||
}
|
||||
|
||||
_configurationManager.OverrideConVars(EnvironmentVariables.GetEnvironmentCVars());
|
||||
|
||||
if (_commandLineArgs != null)
|
||||
{
|
||||
_configurationManager.OverrideConVars(_commandLineArgs.CVars);
|
||||
}
|
||||
|
||||
ProfileOptSetup.Setup(_configurationManager);
|
||||
|
||||
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
|
||||
|
||||
ProgramShared.DoMounts(_resourceCache, _commandLineArgs?.MountOptions, "Content.Client", _loaderArgs != null);
|
||||
if (_loaderArgs != null)
|
||||
{
|
||||
_stringSerializer.EnableCaching = false;
|
||||
_resourceCache.MountLoaderApi(_loaderArgs.FileApi, "Resources/");
|
||||
_modLoader.VerifierExtraLoadHandler = VerifierExtraLoadHandler;
|
||||
}
|
||||
|
||||
_clyde.TextEntered += TextEntered;
|
||||
_clyde.MouseMove += MouseMove;
|
||||
_clyde.KeyUp += KeyUp;
|
||||
_clyde.KeyDown += KeyDown;
|
||||
_clyde.MouseWheel += MouseWheel;
|
||||
_clyde.CloseWindow += Shutdown;
|
||||
|
||||
// Bring display up as soon as resources are mounted.
|
||||
if (!_clyde.Initialize())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_clyde.SetWindowTitle("Space Station 14");
|
||||
|
||||
_fontManager.SetFontDpi((uint) _configurationManager.GetCVar(CVars.DisplayFontDpi));
|
||||
return true;
|
||||
}
|
||||
|
||||
private Stream? VerifierExtraLoadHandler(string arg)
|
||||
{
|
||||
DebugTools.AssertNotNull(_loaderArgs);
|
||||
@@ -278,17 +292,13 @@ namespace Robust.Client
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs);
|
||||
_timerManager.UpdateTimers(frameEventArgs);
|
||||
_taskManager.ProcessPendingTasks();
|
||||
_userInterfaceManager.Update(frameEventArgs);
|
||||
|
||||
// GameStateManager is in full control of the simulation update.
|
||||
if (_client.RunLevel >= ClientRunLevel.Connected)
|
||||
{
|
||||
_componentManager.CullRemovedComponents();
|
||||
_gameStateManager.ApplyGameState();
|
||||
_entityManager.Update(frameEventArgs.DeltaSeconds);
|
||||
_playerManager.Update(frameEventArgs.DeltaSeconds);
|
||||
}
|
||||
|
||||
_stateManager.Update(frameEventArgs);
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Robust.Client.GameObjects
|
||||
_networkManager.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, HandleEntityNetworkMessage);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
public void TickUpdate()
|
||||
{
|
||||
while (_queue.Count != 0 && _queue.Peek().msg.SourceTick <= _gameStateManager.CurServerTick)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
@@ -152,6 +152,7 @@ namespace Robust.Client.GameObjects
|
||||
Zoom = state.Zoom;
|
||||
Offset = state.Offset;
|
||||
Rotation = state.Rotation;
|
||||
VisibilityMask = state.VisibilityMask;
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
|
||||
@@ -2150,11 +2150,6 @@ namespace Robust.Client.GameObjects
|
||||
return null!;
|
||||
}
|
||||
|
||||
public IComponent GetComponent(uint netID)
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component) where T : class
|
||||
{
|
||||
component = null;
|
||||
@@ -2181,17 +2176,6 @@ namespace Robust.Client.GameObjects
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool TryGetComponent(uint netId, [NotNullWhen(true)] out IComponent? component)
|
||||
{
|
||||
component = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public IComponent? GetComponentOrNull(uint netId)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -242,65 +242,67 @@ namespace Robust.Client.GameStates
|
||||
|
||||
if (!Predicting) return;
|
||||
|
||||
using var _ = _timing.StartPastPredictionArea();
|
||||
|
||||
|
||||
if (_pendingInputs.Count > 0)
|
||||
using(var _ = _timing.StartPastPredictionArea())
|
||||
{
|
||||
Logger.DebugS(CVars.NetPredict.Name, "CL> Predicted:");
|
||||
if (_pendingInputs.Count > 0)
|
||||
{
|
||||
Logger.DebugS(CVars.NetPredict.Name, "CL> Predicted:");
|
||||
}
|
||||
|
||||
var pendingInputEnumerator = _pendingInputs.GetEnumerator();
|
||||
var pendingMessagesEnumerator = _pendingSystemMessages.GetEnumerator();
|
||||
var hasPendingInput = pendingInputEnumerator.MoveNext();
|
||||
var hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
|
||||
var ping = _network.ServerChannel!.Ping / 1000f + PredictLagBias; // seconds.
|
||||
var targetTick = _timing.CurTick.Value + _processor.TargetBufferSize +
|
||||
(int) Math.Ceiling(_timing.TickRate * ping) + PredictTickBias;
|
||||
|
||||
// Logger.DebugS("net.predict", $"Predicting from {_lastProcessedTick} to {targetTick}");
|
||||
|
||||
for (var t = _lastProcessedTick.Value + 1; t <= targetTick; t++)
|
||||
{
|
||||
var tick = new GameTick(t);
|
||||
_timing.CurTick = tick;
|
||||
|
||||
while (hasPendingInput && pendingInputEnumerator.Current.Tick <= tick)
|
||||
{
|
||||
var inputCmd = pendingInputEnumerator.Current;
|
||||
|
||||
_inputManager.NetworkBindMap.TryGetKeyFunction(inputCmd.InputFunctionId, out var boundFunc);
|
||||
|
||||
Logger.DebugS(CVars.NetPredict.Name,
|
||||
$" seq={inputCmd.InputSequence}, sub={inputCmd.SubTick}, dTick={tick}, func={boundFunc.FunctionName}, " +
|
||||
$"state={inputCmd.State}");
|
||||
|
||||
|
||||
input.PredictInputCommand(inputCmd);
|
||||
|
||||
hasPendingInput = pendingInputEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
while (hasPendingMessage && pendingMessagesEnumerator.Current.sourceTick <= tick)
|
||||
{
|
||||
var msg = pendingMessagesEnumerator.Current.msg;
|
||||
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, msg);
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.sessionMsg);
|
||||
|
||||
hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
if (t != targetTick)
|
||||
{
|
||||
// Don't run EntitySystemManager.TickUpdate if this is the target tick,
|
||||
// because the rest of the main loop will call into it with the target tick later,
|
||||
// and it won't be a past prediction.
|
||||
_entitySystemManager.TickUpdate((float) _timing.TickPeriod.TotalSeconds);
|
||||
((IBroadcastEventBusInternal) _entities.EventBus).ProcessEventQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pendingInputEnumerator = _pendingInputs.GetEnumerator();
|
||||
var pendingMessagesEnumerator = _pendingSystemMessages.GetEnumerator();
|
||||
var hasPendingInput = pendingInputEnumerator.MoveNext();
|
||||
var hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
|
||||
var ping = _network.ServerChannel!.Ping / 1000f + PredictLagBias; // seconds.
|
||||
var targetTick = _timing.CurTick.Value + _processor.TargetBufferSize +
|
||||
(int) Math.Ceiling(_timing.TickRate * ping) + PredictTickBias;
|
||||
|
||||
// Logger.DebugS("net.predict", $"Predicting from {_lastProcessedTick} to {targetTick}");
|
||||
|
||||
for (var t = _lastProcessedTick.Value + 1; t <= targetTick; t++)
|
||||
{
|
||||
var tick = new GameTick(t);
|
||||
_timing.CurTick = tick;
|
||||
|
||||
while (hasPendingInput && pendingInputEnumerator.Current.Tick <= tick)
|
||||
{
|
||||
var inputCmd = pendingInputEnumerator.Current;
|
||||
|
||||
_inputManager.NetworkBindMap.TryGetKeyFunction(inputCmd.InputFunctionId, out var boundFunc);
|
||||
|
||||
Logger.DebugS(CVars.NetPredict.Name,
|
||||
$" seq={inputCmd.InputSequence}, sub={inputCmd.SubTick}, dTick={tick}, func={boundFunc.FunctionName}, " +
|
||||
$"state={inputCmd.State}");
|
||||
|
||||
|
||||
input.PredictInputCommand(inputCmd);
|
||||
|
||||
hasPendingInput = pendingInputEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
while (hasPendingMessage && pendingMessagesEnumerator.Current.sourceTick <= tick)
|
||||
{
|
||||
var msg = pendingMessagesEnumerator.Current.msg;
|
||||
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, msg);
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.sessionMsg);
|
||||
|
||||
hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
if (t != targetTick)
|
||||
{
|
||||
// Don't run EntitySystemManager.Update if this is the target tick,
|
||||
// because the rest of the main loop will call into it with the target tick later,
|
||||
// and it won't be a past prediction.
|
||||
_entitySystemManager.Update((float) _timing.TickPeriod.TotalSeconds);
|
||||
((IBroadcastEventBusInternal) _entities.EventBus).ProcessEventQueue();
|
||||
}
|
||||
}
|
||||
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds);
|
||||
}
|
||||
|
||||
private void ResetPredictedEntities(GameTick curTick)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
@@ -9,6 +10,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameStates
|
||||
{
|
||||
@@ -28,7 +30,7 @@ namespace Robust.Client.GameStates
|
||||
private const int TrafficHistorySize = 64; // Size of the traffic history bar in game ticks.
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace | OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly Font _font;
|
||||
private readonly int _lineHeight;
|
||||
@@ -95,9 +97,9 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
bool pvsEnabled = _configurationManager.GetCVar<bool>("net.pvs");
|
||||
float pvsSize = _configurationManager.GetCVar<float>("net.maxupdaterange");
|
||||
float pvsRange = _configurationManager.GetCVar<float>("net.maxupdaterange");
|
||||
var pvsCenter = _eyeManager.CurrentEye.Position;
|
||||
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsSize*2, pvsSize*2));
|
||||
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsRange*2, pvsRange*2));
|
||||
|
||||
int timeout = _gameTiming.TickRate * 3;
|
||||
for (int i = 0; i < _netEnts.Count; i++)
|
||||
@@ -130,16 +132,52 @@ namespace Robust.Client.GameStates
|
||||
if (!_netManager.IsConnected)
|
||||
return;
|
||||
|
||||
switch (currentSpace)
|
||||
{
|
||||
case OverlaySpace.ScreenSpace:
|
||||
DrawScreen(handle);
|
||||
break;
|
||||
case OverlaySpace.WorldSpace:
|
||||
DrawWorld(handle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawWorld(DrawingHandleBase handle)
|
||||
{
|
||||
bool pvsEnabled = _configurationManager.GetCVar<bool>("net.pvs");
|
||||
|
||||
if(!pvsEnabled)
|
||||
return;
|
||||
|
||||
float pvsSize = _configurationManager.GetCVar<float>("net.maxupdaterange");
|
||||
var pvsCenter = _eyeManager.CurrentEye.Position;
|
||||
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsSize, pvsSize));
|
||||
|
||||
var worldHandle = (DrawingHandleWorld)handle;
|
||||
|
||||
worldHandle.DrawRect(pvsBox, Color.Red, false);
|
||||
}
|
||||
|
||||
private void DrawScreen(DrawingHandleBase handle)
|
||||
{
|
||||
// remember, 0,0 is top left of ui with +X right and +Y down
|
||||
var screenHandle = (DrawingHandleScreen)handle;
|
||||
var screenHandle = (DrawingHandleScreen) handle;
|
||||
|
||||
for (int i = 0; i < _netEnts.Count; i++)
|
||||
{
|
||||
var netEnt = _netEnts[i];
|
||||
|
||||
if (!_entityManager.TryGetEntity(netEnt.Id, out var ent))
|
||||
{
|
||||
_netEnts.RemoveSwap(i);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
var xPos = 100;
|
||||
var yPos = 10 + _lineHeight * i;
|
||||
var name = $"({netEnt.Id}) {_entityManager.GetEntity(netEnt.Id).Prototype?.ID}";
|
||||
var name = $"({netEnt.Id}) {ent.Prototype?.ID}";
|
||||
var color = CalcTextColor(ref netEnt);
|
||||
DrawString(screenHandle, _font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
|
||||
DrawTrafficBox(screenHandle, ref netEnt, xPos, yPos);
|
||||
@@ -225,7 +263,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteError("Invalid argument amount. Expected 2 arguments.");
|
||||
shell.WriteError("Invalid argument amount. Expected 1 arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
@@ -36,6 +38,10 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private readonly List<(GameTick Tick, int Payload, int lag, int interp)> _history = new(HistorySize+10);
|
||||
|
||||
private int _totalHistoryPayload; // sum of all data point sizes in bytes
|
||||
|
||||
public EntityUid WatchEntId { get; set; }
|
||||
|
||||
public NetGraphOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
@@ -60,7 +66,73 @@ namespace Robust.Client.GameStates
|
||||
// calc interp info
|
||||
var interpBuff = _gameStateManager.CurrentBufferSize - _gameStateManager.MinBufferSize;
|
||||
|
||||
_totalHistoryPayload += sz;
|
||||
_history.Add((toSeq, sz, lag, interpBuff));
|
||||
|
||||
// not watching an ent
|
||||
if(!WatchEntId.IsValid() || WatchEntId.IsClientSide())
|
||||
return;
|
||||
|
||||
string? entStateString = null;
|
||||
string? entDelString = null;
|
||||
var conShell = IoCManager.Resolve<IConsoleHost>().LocalShell;
|
||||
|
||||
var entStates = args.AppliedState.EntityStates;
|
||||
if (entStates is not null)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var entState in entStates)
|
||||
{
|
||||
if (entState.Uid == WatchEntId)
|
||||
{
|
||||
if(entState.ComponentChanges is not null)
|
||||
{
|
||||
sb.Append($"\n Changes:");
|
||||
foreach (var compChange in entState.ComponentChanges)
|
||||
{
|
||||
var del = compChange.Deleted ? 'D' : 'C';
|
||||
sb.Append($"\n [{del}]{compChange.NetID}:{compChange.ComponentName}");
|
||||
}
|
||||
}
|
||||
|
||||
if (entState.ComponentStates is not null)
|
||||
{
|
||||
sb.Append($"\n States:");
|
||||
foreach (var compState in entState.ComponentStates)
|
||||
{
|
||||
sb.Append($"\n {compState.NetID}:{compState.GetType().Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entStateString = sb.ToString();
|
||||
}
|
||||
|
||||
var entDeletes = args.AppliedState.EntityDeletions;
|
||||
if (entDeletes is not null)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var entDelete in entDeletes)
|
||||
{
|
||||
if (entDelete == WatchEntId)
|
||||
{
|
||||
entDelString = "\n Deleted";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(entStateString) || !string.IsNullOrWhiteSpace(entDelString))
|
||||
{
|
||||
var fullString = $"watchEnt: from={args.AppliedState.FromSequence}, to={args.AppliedState.ToSequence}, eid={WatchEntId}";
|
||||
if (!string.IsNullOrWhiteSpace(entStateString))
|
||||
fullString += entStateString;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(entDelString))
|
||||
fullString += entDelString;
|
||||
|
||||
conShell.WriteLine(fullString + "\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -69,10 +141,16 @@ namespace Robust.Client.GameStates
|
||||
base.FrameUpdate(args);
|
||||
|
||||
var over = _history.Count - HistorySize;
|
||||
if (over > 0)
|
||||
if (over <= 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < over; i++)
|
||||
{
|
||||
_history.RemoveRange(0, over);
|
||||
var point = _history[i];
|
||||
_totalHistoryPayload -= point.Payload;
|
||||
}
|
||||
|
||||
_history.RemoveRange(0, over);
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
@@ -82,6 +160,7 @@ namespace Robust.Client.GameStates
|
||||
var leftMargin = 300;
|
||||
var width = HistorySize;
|
||||
var height = 500;
|
||||
var drawSizeThreshold = Math.Min(_totalHistoryPayload / HistorySize, 300);
|
||||
|
||||
// bottom payload line
|
||||
handle.DrawLine(new Vector2(leftMargin, height), new Vector2(leftMargin + width, height), Color.DarkGray.WithAlpha(0.8f));
|
||||
@@ -101,6 +180,12 @@ namespace Robust.Client.GameStates
|
||||
var yoff = height - state.Payload / BytesPerPixel;
|
||||
handle.DrawLine(new Vector2(xOff, height), new Vector2(xOff, yoff), Color.LightGreen.WithAlpha(0.8f));
|
||||
|
||||
// Draw size if above average
|
||||
if (drawSizeThreshold * 1.5 < state.Payload)
|
||||
{
|
||||
DrawString((DrawingHandleScreen) handle, _font, new Vector2(xOff, yoff - _font.GetLineHeight(1)), state.Payload.ToString());
|
||||
}
|
||||
|
||||
// second tick marks
|
||||
if (state.Tick.Value % _gameTiming.TickRate == 0)
|
||||
{
|
||||
@@ -125,6 +210,10 @@ namespace Robust.Client.GameStates
|
||||
handle.DrawLine(new Vector2(xOff, height + LowerGraphOffset), new Vector2(xOff, height + LowerGraphOffset + state.interp * 6), interpColor.WithAlpha(0.8f));
|
||||
}
|
||||
|
||||
// average payload line
|
||||
var avgyoff = height - drawSizeThreshold / BytesPerPixel;
|
||||
handle.DrawLine(new Vector2(leftMargin, avgyoff), new Vector2(leftMargin + width, avgyoff), Color.DarkGray.WithAlpha(0.8f));
|
||||
|
||||
// top payload warning line
|
||||
var warnYoff = height - _warningPayloadSize / BytesPerPixel;
|
||||
handle.DrawLine(new Vector2(leftMargin, warnYoff), new Vector2(leftMargin + width, warnYoff), Color.DarkGray.WithAlpha(0.8f));
|
||||
@@ -197,5 +286,36 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class NetWatchEntCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "net_watchent";
|
||||
public string Help => "net_watchent <0|EntityUid>";
|
||||
public string Description => "Dumps all network updates for an EntityId to the console.";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteError("Invalid argument amount. Expected 1 argument.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityUid.TryParse(args[0], out var eValue))
|
||||
{
|
||||
shell.WriteError("Invalid argument: Needs to be 0 or an entityId.");
|
||||
return;
|
||||
}
|
||||
|
||||
var overlayMan = IoCManager.Resolve<IOverlayManager>();
|
||||
|
||||
if (overlayMan.HasOverlay(typeof(NetGraphOverlay)))
|
||||
{
|
||||
var netOverlay = overlayMan.GetOverlay<NetGraphOverlay>();
|
||||
|
||||
netOverlay.WatchEntId = eValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
|
||||
|
||||
_configurationManager.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
}
|
||||
|
||||
private void _audioCreateContext()
|
||||
@@ -81,7 +81,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void _audioOpenDevice()
|
||||
{
|
||||
var preferredDevice = _configurationManager.GetCVar(CVars.AudioDevice);
|
||||
var preferredDevice = ConfigurationManager.GetCVar(CVars.AudioDevice);
|
||||
|
||||
// Open device.
|
||||
if (!string.IsNullOrEmpty(preferredDevice))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Log;
|
||||
@@ -115,7 +115,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var prev = cap;
|
||||
var cVarName = $"display.ogl_block_{capName}";
|
||||
var block = _configurationManager.GetCVar<bool>(cVarName);
|
||||
var block = ConfigurationManager.GetCVar<bool>(cVarName);
|
||||
|
||||
if (block)
|
||||
{
|
||||
@@ -146,7 +146,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
foreach (var cvar in cvars)
|
||||
{
|
||||
_configurationManager.RegisterCVar($"display.ogl_block_{cvar}", false);
|
||||
ConfigurationManager.RegisterCVar($"display.ogl_block_{cvar}", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using OpenToolkit;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.Utility;
|
||||
@@ -14,6 +15,7 @@ using Robust.Shared;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using static Robust.Client.Utility.LiterallyJustMessageBox;
|
||||
@@ -46,6 +48,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// Keep delegates around to prevent GC issues.
|
||||
private GLFWCallbacks.ErrorCallback _errorCallback = default!;
|
||||
private GLFWCallbacks.MonitorCallback _monitorCallback = default!;
|
||||
private GLFWCallbacks.CharCallback _charCallback = default!;
|
||||
private GLFWCallbacks.CursorPosCallback _cursorPosCallback = default!;
|
||||
private GLFWCallbacks.KeyCallback _keyCallback = default!;
|
||||
@@ -73,6 +76,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private Vector2 _lastMousePos;
|
||||
|
||||
// Can't use ClydeHandle because it's 64 bit.
|
||||
private int _nextWindowId = 1;
|
||||
private readonly Dictionary<int, MonitorReg> _monitors = new();
|
||||
|
||||
public event Action<TextEventArgs>? TextEntered;
|
||||
public event Action<MouseMoveEventArgs>? MouseMove;
|
||||
public event Action<KeyEventArgs>? KeyUp;
|
||||
public event Action<KeyEventArgs>? KeyDown;
|
||||
public event Action<MouseWheelEventArgs>? MouseWheel;
|
||||
public event Action<string>? CloseWindow;
|
||||
public event Action? OnWindowScaleChanged;
|
||||
|
||||
// NOTE: in engine we pretend the framebuffer size is the screen size..
|
||||
// For practical reasons like UI rendering.
|
||||
public override Vector2i ScreenSize => _framebufferSize;
|
||||
@@ -148,24 +163,71 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return false;
|
||||
}
|
||||
|
||||
InitMonitors();
|
||||
InitCursors();
|
||||
|
||||
return InitWindow();
|
||||
}
|
||||
|
||||
private void InitMonitors()
|
||||
{
|
||||
var monitors = GLFW.GetMonitorsRaw(out var count);
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
SetupMonitor(monitors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupMonitor(Monitor* monitor)
|
||||
{
|
||||
var handle = _nextWindowId++;
|
||||
|
||||
DebugTools.Assert(GLFW.GetMonitorUserPointer(monitor) == null, "GLFW window already has user pointer??");
|
||||
|
||||
var name = GLFW.GetMonitorName(monitor);
|
||||
var videoMode = GLFW.GetVideoMode(monitor);
|
||||
var impl = new ClydeMonitorImpl(handle, name, (videoMode->Width, videoMode->Height), videoMode->RefreshRate);
|
||||
|
||||
GLFW.SetMonitorUserPointer(monitor, (void*) handle);
|
||||
_monitors[handle] = new MonitorReg
|
||||
{
|
||||
Id = handle,
|
||||
Impl = impl,
|
||||
Monitor = monitor
|
||||
};
|
||||
}
|
||||
|
||||
private void DestroyMonitor(Monitor* monitor)
|
||||
{
|
||||
var ptr = GLFW.GetMonitorUserPointer(monitor);
|
||||
|
||||
if (ptr == null)
|
||||
{
|
||||
var name = GLFW.GetMonitorName(monitor);
|
||||
Logger.WarningS("clyde.win", $"Monitor '{name}' had no user pointer set??");
|
||||
return;
|
||||
}
|
||||
|
||||
_monitors.Remove((int) ptr);
|
||||
GLFW.SetMonitorUserPointer(monitor, null);
|
||||
}
|
||||
|
||||
private bool InitWindow()
|
||||
{
|
||||
var width = _configurationManager.GetCVar(CVars.DisplayWidth);
|
||||
var height = _configurationManager.GetCVar(CVars.DisplayHeight);
|
||||
var width = ConfigurationManager.GetCVar(CVars.DisplayWidth);
|
||||
var height = ConfigurationManager.GetCVar(CVars.DisplayHeight);
|
||||
|
||||
Monitor* monitor = null;
|
||||
|
||||
if (WindowMode == WindowMode.Fullscreen)
|
||||
{
|
||||
monitor = GLFW.GetPrimaryMonitor();
|
||||
monitor = GLFW.GetMonitors()[1];
|
||||
var mode = GLFW.GetVideoMode(monitor);
|
||||
width = mode->Width;
|
||||
height = mode->Height;
|
||||
|
||||
GLFW.WindowHint(WindowHintInt.RefreshRate, mode->RefreshRate);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
@@ -174,13 +236,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GLFW.WindowHint(WindowHintString.X11ClassName, "SS14");
|
||||
GLFW.WindowHint(WindowHintString.X11InstanceName, "SS14");
|
||||
|
||||
var renderer = (Renderer) _configurationManager.GetCVar<int>(CVars.DisplayRenderer);
|
||||
var renderer = (Renderer) ConfigurationManager.GetCVar<int>(CVars.DisplayRenderer);
|
||||
|
||||
Span<Renderer> renderers = (renderer == Renderer.Default) ? stackalloc Renderer[] {
|
||||
Renderer.OpenGL33,
|
||||
Renderer.OpenGL31,
|
||||
Renderer.OpenGLES2
|
||||
} : stackalloc Renderer[] {renderer};
|
||||
Span<Renderer> renderers = (renderer == Renderer.Default)
|
||||
? stackalloc Renderer[]
|
||||
{
|
||||
Renderer.OpenGL33,
|
||||
Renderer.OpenGL31,
|
||||
Renderer.OpenGLES2
|
||||
}
|
||||
: stackalloc Renderer[] {renderer};
|
||||
|
||||
foreach (Renderer r in renderers)
|
||||
{
|
||||
@@ -193,6 +258,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_isCore = renderer == Renderer.OpenGL33;
|
||||
break;
|
||||
}
|
||||
|
||||
// Window failed to init due to error.
|
||||
// Try not to treat the error code seriously.
|
||||
var code = GLFW.GetError(out string desc);
|
||||
@@ -206,9 +272,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var code = GLFW.GetError(out string desc);
|
||||
|
||||
var errorContent = "Failed to create the game window. " +
|
||||
"This probably means your GPU is too old to play the game. " +
|
||||
"That or update your graphic drivers\n" +
|
||||
$"The exact error is: [{code}]\n {desc}";
|
||||
"This probably means your GPU is too old to play the game. " +
|
||||
"That or update your graphic drivers\n" +
|
||||
$"The exact error is: [{code}]\n {desc}";
|
||||
|
||||
MessageBoxW(null,
|
||||
errorContent,
|
||||
@@ -301,6 +367,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
|
||||
GLFW.WindowHint(WindowHintBool.SrgbCapable, false);
|
||||
}
|
||||
|
||||
_glfwWindow = GLFW.CreateWindow(width, height, string.Empty, monitor, null);
|
||||
}
|
||||
}
|
||||
@@ -393,11 +460,30 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Logger.ErrorS("clyde.win.glfw", "GLFW Error: [{0}] {1}", code, description);
|
||||
}
|
||||
|
||||
private void OnGlfwMonitor(Monitor* monitor, ConnectedState state)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (state == ConnectedState.Connected)
|
||||
{
|
||||
SetupMonitor(monitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
DestroyMonitor(monitor);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CatchCallbackException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGlfwChar(Window* window, uint codepoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
_gameController.TextEntered(new TextEventArgs(codepoint));
|
||||
TextEntered?.Invoke(new TextEventArgs(codepoint));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -413,8 +499,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var delta = newPos - _lastMousePos;
|
||||
_lastMousePos = newPos;
|
||||
|
||||
var ev = new MouseMoveEventArgs(delta, newPos);
|
||||
_gameController.MouseMove(ev);
|
||||
MouseMove?.Invoke(new MouseMoveEventArgs(delta, newPos));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -461,11 +546,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
switch (action)
|
||||
{
|
||||
case InputAction.Release:
|
||||
_gameController.KeyUp(ev);
|
||||
KeyUp?.Invoke(ev);
|
||||
break;
|
||||
case InputAction.Press:
|
||||
case InputAction.Repeat:
|
||||
_gameController.KeyDown(ev);
|
||||
KeyDown?.Invoke(ev);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(action), action, null);
|
||||
@@ -477,7 +562,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
try
|
||||
{
|
||||
var ev = new MouseWheelEventArgs(((float) offsetX, (float) offsetY), _lastMousePos);
|
||||
_gameController.MouseWheel(ev);
|
||||
MouseWheel?.Invoke(ev);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -489,7 +574,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
try
|
||||
{
|
||||
_gameController.Shutdown("Window closed");
|
||||
CloseWindow?.Invoke("Window closed");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -533,6 +618,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
try
|
||||
{
|
||||
_windowScale = (xScale, yScale);
|
||||
OnWindowScaleChanged?.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -568,6 +654,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private void StoreCallbacks()
|
||||
{
|
||||
_errorCallback = OnGlfwError;
|
||||
_monitorCallback = OnGlfwMonitor;
|
||||
_charCallback = OnGlfwChar;
|
||||
_cursorPosCallback = OnGlfwCursorPos;
|
||||
_keyCallback = OnGlfwKey;
|
||||
@@ -590,6 +677,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GLFW.SetWindowTitle(_glfwWindow, title);
|
||||
}
|
||||
|
||||
public void SetWindowMonitor(IClydeMonitor monitor)
|
||||
{
|
||||
var monitorImpl = (ClydeMonitorImpl) monitor;
|
||||
var reg = _monitors[monitorImpl.Id];
|
||||
|
||||
GLFW.SetWindowMonitor(
|
||||
_glfwWindow,
|
||||
reg.Monitor,
|
||||
0, 0,
|
||||
monitorImpl.Size.X, monitorImpl.Size.Y,
|
||||
monitorImpl.RefreshRate);
|
||||
}
|
||||
|
||||
public void RequestWindowAttention()
|
||||
{
|
||||
GLFW.RequestWindowAttention(_glfwWindow);
|
||||
@@ -654,18 +754,40 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
GLFW.GetWindowPos(_glfwWindow, out var x, out var y);
|
||||
_prevWindowPos = (x, y);
|
||||
var monitor = GLFW.GetPrimaryMonitor();
|
||||
var monitor = MonitorForWindow(_glfwWindow);
|
||||
var mode = GLFW.GetVideoMode(monitor);
|
||||
|
||||
GLFW.SetWindowMonitor(_glfwWindow, GLFW.GetPrimaryMonitor(), 0, 0, mode->Width, mode->Height,
|
||||
GLFW.SetWindowMonitor(_glfwWindow, monitor, 0, 0, mode->Width, mode->Height,
|
||||
mode->RefreshRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
GLFW.SetWindowMonitor(_glfwWindow, null, _prevWindowPos.X, _prevWindowPos.Y, _prevWindowSize.X, _prevWindowSize.Y, 0);
|
||||
GLFW.SetWindowMonitor(_glfwWindow, null, _prevWindowPos.X, _prevWindowPos.Y, _prevWindowSize.X,
|
||||
_prevWindowSize.Y, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// glfwGetWindowMonitor only works for fullscreen windows.
|
||||
// Picks the monitor with the top-left corner of the window.
|
||||
private Monitor* MonitorForWindow(Window* window)
|
||||
{
|
||||
GLFW.GetWindowPos(window, out var winPosX, out var winPosY);
|
||||
var monitors = GLFW.GetMonitorsRaw(out var count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var monitor = monitors[i];
|
||||
GLFW.GetMonitorPos(monitor, out var monPosX, out var monPosY);
|
||||
var videoMode = GLFW.GetVideoMode(monitor);
|
||||
|
||||
var box = Box2i.FromDimensions(monPosX, monPosY, videoMode->Width, videoMode->Height);
|
||||
if (box.Contains(winPosX, winPosY))
|
||||
return monitor;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return GLFW.GetPrimaryMonitor();
|
||||
}
|
||||
|
||||
string IClipboardManager.GetText()
|
||||
{
|
||||
return GLFW.GetClipboardString(_glfwWindow);
|
||||
@@ -676,6 +798,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GLFW.SetClipboardString(_glfwWindow, text);
|
||||
}
|
||||
|
||||
public IEnumerable<IClydeMonitor> EnumerateMonitors()
|
||||
{
|
||||
return _monitors.Values.Select(c => c.Impl);
|
||||
}
|
||||
|
||||
// We can't let exceptions unwind into GLFW, as that can cause the CLR to crash.
|
||||
// And it probably messes up GLFW too.
|
||||
// So all the callbacks are passed to this method.
|
||||
@@ -689,5 +816,28 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
_glfwExceptionList.Add(e);
|
||||
}
|
||||
|
||||
private sealed class MonitorReg
|
||||
{
|
||||
public int Id;
|
||||
public Monitor* Monitor;
|
||||
public ClydeMonitorImpl Impl = default!;
|
||||
}
|
||||
|
||||
private sealed class ClydeMonitorImpl : IClydeMonitor
|
||||
{
|
||||
public ClydeMonitorImpl(int id, string name, Vector2i size, int refreshRate)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Size = size;
|
||||
RefreshRate = refreshRate;
|
||||
}
|
||||
|
||||
public int Id { get; }
|
||||
public string Name { get; }
|
||||
public Vector2i Size { get; }
|
||||
public int RefreshRate { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_configurationManager.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
|
||||
|
||||
if (!InitWindowing())
|
||||
{
|
||||
@@ -124,9 +124,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
protected override void ReadConfig()
|
||||
{
|
||||
base.ReadConfig();
|
||||
_lightmapDivider = _configurationManager.GetCVar(CVars.DisplayLightMapDivider);
|
||||
_maxLightsPerScene = _configurationManager.GetCVar(CVars.DisplayMaxLightsPerScene);
|
||||
_enableSoftShadows = _configurationManager.GetCVar(CVars.DisplaySoftShadows);
|
||||
_lightmapDivider = ConfigurationManager.GetCVar(CVars.DisplayLightMapDivider);
|
||||
_maxLightsPerScene = ConfigurationManager.GetCVar(CVars.DisplayMaxLightsPerScene);
|
||||
_enableSoftShadows = ConfigurationManager.GetCVar(CVars.DisplaySoftShadows);
|
||||
}
|
||||
|
||||
protected override void ReloadConfig()
|
||||
@@ -238,7 +238,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private (int major, int minor)? ParseGLOverrideVersion()
|
||||
{
|
||||
var overrideGLVersion = _configurationManager.GetCVar(CVars.DisplayOGLOverrideVersion);
|
||||
var overrideGLVersion = ConfigurationManager.GetCVar(CVars.DisplayOGLOverrideVersion);
|
||||
if (string.IsNullOrEmpty(overrideGLVersion))
|
||||
{
|
||||
return null;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
@@ -37,6 +38,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public IClydeDebugInfo DebugInfo { get; } = new DummyDebugInfo();
|
||||
public IClydeDebugStats DebugStats { get; } = new DummyDebugStats();
|
||||
|
||||
public event Action<TextEventArgs>? TextEntered;
|
||||
public event Action<MouseMoveEventArgs>? MouseMove;
|
||||
public event Action<KeyEventArgs>? KeyUp;
|
||||
public event Action<KeyEventArgs>? KeyDown;
|
||||
public event Action<MouseWheelEventArgs>? MouseWheel;
|
||||
public event Action<string>? CloseWindow;
|
||||
|
||||
public Texture GetStockTexture(ClydeStockTexture stockTexture)
|
||||
{
|
||||
return new DummyTexture((1, 1));
|
||||
@@ -63,6 +71,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetWindowMonitor(IClydeMonitor monitor)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void RequestWindowAttention()
|
||||
{
|
||||
// Nada.
|
||||
@@ -86,6 +99,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event Action OnWindowScaleChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
// Nada.
|
||||
@@ -156,6 +175,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return new Viewport();
|
||||
}
|
||||
|
||||
public IEnumerable<IClydeMonitor> EnumerateMonitors()
|
||||
{
|
||||
// TODO: Actually return something.
|
||||
yield break;
|
||||
}
|
||||
|
||||
public ClydeHandle LoadShader(ParsedShader shader, string? name = null)
|
||||
{
|
||||
return default;
|
||||
|
||||
@@ -18,8 +18,7 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
internal abstract class ClydeBase
|
||||
{
|
||||
[Dependency] protected readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] protected readonly IGameControllerInternal _gameController = default!;
|
||||
[Dependency] protected readonly IConfigurationManager ConfigurationManager = default!;
|
||||
|
||||
protected WindowMode WindowMode { get; private set; } = WindowMode.Windowed;
|
||||
protected bool VSync { get; private set; } = true;
|
||||
@@ -31,11 +30,11 @@ namespace Robust.Client.Graphics
|
||||
|
||||
public virtual bool Initialize()
|
||||
{
|
||||
_configurationManager.OnValueChanged(CVars.DisplayVSync, _vSyncChanged, true);
|
||||
_configurationManager.OnValueChanged(CVars.DisplayWindowMode, _windowModeChanged, true);
|
||||
_configurationManager.OnValueChanged(CVars.DisplayLightMapDivider, LightmapDividerChanged, true);
|
||||
_configurationManager.OnValueChanged(CVars.DisplayMaxLightsPerScene, MaxLightsPerSceneChanged, true);
|
||||
_configurationManager.OnValueChanged(CVars.DisplaySoftShadows, SoftShadowsChanged, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayVSync, _vSyncChanged, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayWindowMode, _windowModeChanged, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayLightMapDivider, LightmapDividerChanged, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayMaxLightsPerScene, MaxLightsPerSceneChanged, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplaySoftShadows, SoftShadowsChanged, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -51,8 +50,8 @@ namespace Robust.Client.Graphics
|
||||
|
||||
protected virtual void ReadConfig()
|
||||
{
|
||||
WindowMode = (WindowMode) _configurationManager.GetCVar(CVars.DisplayWindowMode);
|
||||
VSync = _configurationManager.GetCVar(CVars.DisplayVSync);
|
||||
WindowMode = (WindowMode) ConfigurationManager.GetCVar(CVars.DisplayWindowMode);
|
||||
VSync = ConfigurationManager.GetCVar(CVars.DisplayVSync);
|
||||
}
|
||||
|
||||
private void _vSyncChanged(bool newValue)
|
||||
|
||||
@@ -4,9 +4,6 @@ using System.IO;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SharpFont;
|
||||
@@ -20,18 +17,18 @@ namespace Robust.Client.Graphics
|
||||
private const int SheetWidth = 256;
|
||||
private const int SheetHeight = 256;
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
private readonly IClyde _clyde;
|
||||
|
||||
private uint BaseFontDPI;
|
||||
private uint _baseFontDpi = 96;
|
||||
|
||||
private readonly Library _library;
|
||||
|
||||
private readonly Dictionary<(FontFaceHandle, int fontSize), FontInstanceHandle> _loadedInstances =
|
||||
new();
|
||||
|
||||
public FontManager()
|
||||
public FontManager(IClyde clyde)
|
||||
{
|
||||
_clyde = clyde;
|
||||
_library = new Library();
|
||||
}
|
||||
|
||||
@@ -42,9 +39,9 @@ namespace Robust.Client.Graphics
|
||||
return handle;
|
||||
}
|
||||
|
||||
void IFontManagerInternal.Initialize()
|
||||
void IFontManagerInternal.SetFontDpi(uint fontDpi)
|
||||
{
|
||||
BaseFontDPI = (uint) _configuration.GetCVar(CVars.DisplayFontDpi);
|
||||
_baseFontDpi = fontDpi;
|
||||
}
|
||||
|
||||
public IFontInstanceHandle MakeInstance(IFontFaceHandle handle, int size)
|
||||
@@ -64,7 +61,7 @@ namespace Robust.Client.Graphics
|
||||
private ScaledFontData _generateScaledDatum(FontInstanceHandle instance, float scale)
|
||||
{
|
||||
var ftFace = instance.FaceHandle.Face;
|
||||
ftFace.SetCharSize(0, instance.Size, 0, (uint) (BaseFontDPI * scale));
|
||||
ftFace.SetCharSize(0, instance.Size, 0, (uint) (_baseFontDpi * scale));
|
||||
|
||||
var ascent = ftFace.Size.Metrics.Ascender.ToInt32();
|
||||
var descent = -ftFace.Size.Metrics.Descender.ToInt32();
|
||||
@@ -83,7 +80,7 @@ namespace Robust.Client.Graphics
|
||||
return;
|
||||
|
||||
var face = instance.FaceHandle.Face;
|
||||
face.SetCharSize(0, instance.Size, 0, (uint) (BaseFontDPI * scale));
|
||||
face.SetCharSize(0, instance.Size, 0, (uint) (_baseFontDpi * scale));
|
||||
face.LoadGlyph(glyph, LoadFlags.Default, LoadTarget.Normal);
|
||||
face.Glyph.RenderGlyph(RenderMode.Normal);
|
||||
|
||||
@@ -189,7 +186,7 @@ namespace Robust.Client.Graphics
|
||||
OwnedTexture GenSheet()
|
||||
{
|
||||
var sheet = _clyde.CreateBlankTexture<A8>((SheetWidth, SheetHeight),
|
||||
$"font-{face.FamilyName}-{instance.Size}-{(uint) (BaseFontDPI * scale)}-sheet{scaled.AtlasTextures.Count}");
|
||||
$"font-{face.FamilyName}-{instance.Size}-{(uint) (_baseFontDpi * scale)}-sheet{scaled.AtlasTextures.Count}");
|
||||
scaled.AtlasTextures.Add(sheet);
|
||||
return sheet;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -21,6 +22,7 @@ namespace Robust.Client.Graphics
|
||||
Vector2 DefaultWindowScale { get; }
|
||||
|
||||
void SetWindowTitle(string title);
|
||||
void SetWindowMonitor(IClydeMonitor monitor);
|
||||
|
||||
/// <summary>
|
||||
/// This is the magic method to make the game window ping you in the task bar.
|
||||
@@ -31,6 +33,8 @@ namespace Robust.Client.Graphics
|
||||
|
||||
event Action<WindowFocusedEventArgs> OnWindowFocused;
|
||||
|
||||
event Action OnWindowScaleChanged;
|
||||
|
||||
OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
|
||||
TextureLoadParameters? loadParams = null);
|
||||
|
||||
@@ -104,6 +108,8 @@ namespace Robust.Client.Graphics
|
||||
}
|
||||
|
||||
IClydeViewport CreateViewport(Vector2i size, string? name = null);
|
||||
|
||||
IEnumerable<IClydeMonitor> EnumerateMonitors();
|
||||
}
|
||||
|
||||
// TODO: Maybe implement IDisposable for render targets. I got lazy and didn't.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -16,6 +17,13 @@ namespace Robust.Client.Graphics
|
||||
bool Initialize();
|
||||
void Ready();
|
||||
|
||||
event Action<TextEventArgs> TextEntered;
|
||||
event Action<MouseMoveEventArgs> MouseMove;
|
||||
event Action<KeyEventArgs> KeyUp;
|
||||
event Action<KeyEventArgs> KeyDown;
|
||||
event Action<MouseWheelEventArgs> MouseWheel;
|
||||
event Action<string> CloseWindow;
|
||||
|
||||
ClydeHandle LoadShader(ParsedShader shader, string? name = null);
|
||||
|
||||
void ReloadShader(ClydeHandle handle, ParsedShader newShader);
|
||||
|
||||
18
Robust.Client/Graphics/IClydeMonitor.cs
Normal file
18
Robust.Client/Graphics/IClydeMonitor.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a connected monitor on the user's system.
|
||||
/// </summary>
|
||||
public interface IClydeMonitor
|
||||
{
|
||||
/// <summary>
|
||||
/// This ID is not consistent between startups of the game.
|
||||
/// </summary>
|
||||
int Id { get; }
|
||||
string Name { get; }
|
||||
Vector2i Size { get; }
|
||||
int RefreshRate { get; }
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
IFontFaceHandle Load(Stream stream);
|
||||
IFontInstanceHandle MakeInstance(IFontFaceHandle handle, int size);
|
||||
void Initialize();
|
||||
void SetFontDpi(uint fontDpi);
|
||||
}
|
||||
|
||||
internal interface IFontFaceHandle
|
||||
|
||||
@@ -87,11 +87,11 @@ namespace Robust.Client.Graphics
|
||||
public bool HasOverlay(Type overlayClass) {
|
||||
if (!overlayClass.IsSubclassOf(typeof(Overlay)))
|
||||
Logger.Error("HasOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
|
||||
return _overlays.Remove(overlayClass);
|
||||
return _overlays.ContainsKey(overlayClass);
|
||||
}
|
||||
|
||||
public bool HasOverlay<T>() where T : Overlay {
|
||||
return _overlays.Remove(typeof(T));
|
||||
return _overlays.ContainsKey(typeof(T));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace Robust.Client.Player
|
||||
|
||||
void Initialize();
|
||||
void Startup(INetChannel channel);
|
||||
void Update(float frameTime);
|
||||
void Shutdown();
|
||||
|
||||
void ApplyPlayerStates(IEnumerable<PlayerState>? list);
|
||||
|
||||
@@ -95,12 +95,6 @@ namespace Robust.Client.Player
|
||||
_network.ClientSendMessage(msgList);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
// Uh, nothing anymore I guess.
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Shutdown()
|
||||
{
|
||||
|
||||
@@ -9,9 +9,7 @@ namespace Robust.Client.State
|
||||
|
||||
State CurrentState { get; }
|
||||
void RequestStateChange<T>() where T : State, new();
|
||||
void Update(FrameEventArgs e);
|
||||
void FrameUpdate(FrameEventArgs e);
|
||||
void FormResize();
|
||||
void RequestStateChange(Type type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.State
|
||||
{
|
||||
@@ -14,16 +14,6 @@ namespace Robust.Client.State
|
||||
/// </summary>
|
||||
public abstract void Shutdown();
|
||||
|
||||
/// <summary>
|
||||
/// Update the contents of this screen.
|
||||
/// </summary>
|
||||
public virtual void Update(FrameEventArgs e) { }
|
||||
|
||||
public virtual void FrameUpdate(FrameEventArgs e) { }
|
||||
|
||||
/// <summary>
|
||||
/// The screen has changed size, usually from resizing window. This is called automatically right after Startup.
|
||||
/// </summary>
|
||||
public virtual void FormResize() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Log;
|
||||
using System;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -17,21 +17,11 @@ namespace Robust.Client.State
|
||||
CurrentState = new DefaultState();
|
||||
}
|
||||
|
||||
public void Update(FrameEventArgs e)
|
||||
{
|
||||
CurrentState?.Update(e);
|
||||
}
|
||||
|
||||
public void FrameUpdate(FrameEventArgs e)
|
||||
{
|
||||
CurrentState?.FrameUpdate(e);
|
||||
}
|
||||
|
||||
public void FormResize()
|
||||
{
|
||||
CurrentState?.FormResize();
|
||||
}
|
||||
|
||||
public void RequestStateChange<T>() where T : State, new()
|
||||
{
|
||||
RequestStateChange(typeof(T));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -817,25 +817,7 @@ namespace Robust.Client.UserInterface
|
||||
/// <summary>
|
||||
/// Called when the size of the control changes.
|
||||
/// </summary>
|
||||
protected virtual void Resized()
|
||||
{
|
||||
}
|
||||
|
||||
internal void DoUpdate(FrameEventArgs args)
|
||||
{
|
||||
Update(args);
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.DoUpdate(args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is called every process frame.
|
||||
/// </summary>
|
||||
protected virtual void Update(FrameEventArgs args)
|
||||
{
|
||||
}
|
||||
protected virtual void Resized() { }
|
||||
|
||||
internal void DoFrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -45,9 +45,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
Visible = false;
|
||||
}
|
||||
|
||||
protected override void Update(FrameEventArgs args)
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.Update(args);
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if ((_gameTiming.RealTime - _lastUpdate).Seconds < 1 || !VisibleInTree)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -51,9 +51,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
MouseFilter = contents.MouseFilter = MouseFilterMode.Ignore;
|
||||
}
|
||||
|
||||
protected override void Update(FrameEventArgs args)
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.Update(args);
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if ((GameTiming.RealTime - LastUpdate).Seconds < 1 || !VisibleInTree)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -36,9 +36,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
HorizontalAlignment = HAlignment.Left;
|
||||
}
|
||||
|
||||
protected override void Update(FrameEventArgs args)
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.Update(args);
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (!VisibleInTree)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
ShadowOffsetYOverride = 1;
|
||||
}
|
||||
|
||||
protected override void Update(FrameEventArgs args)
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
if (!VisibleInTree)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Robust.Client.Graphics;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -31,9 +33,11 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
// We keep track of frame times in a ring buffer.
|
||||
private readonly float[] _frameTimes = new float[TrackedFrames];
|
||||
private readonly BitArray _gcMarkers = new(TrackedFrames);
|
||||
|
||||
// Position of the last frame in the ring buffer.
|
||||
private int _frameIndex;
|
||||
private int _lastGCCount;
|
||||
|
||||
public FrameGraph(IGameTiming gameTiming)
|
||||
{
|
||||
@@ -49,14 +53,21 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
var gcCount = GC.CollectionCount(0);
|
||||
|
||||
_frameTimes[_frameIndex] = (float)_gameTiming.RealFrameTime.TotalSeconds;
|
||||
_gcMarkers[_frameIndex] = gcCount > _lastGCCount;
|
||||
|
||||
_frameIndex = (_frameIndex + 1) % TrackedFrames;
|
||||
_lastGCCount = gcCount;
|
||||
}
|
||||
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
Span<Vector2> triangle = stackalloc Vector2[3];
|
||||
|
||||
float maxHeight = 0;
|
||||
for (var i = 0; i < _frameTimes.Length; i++)
|
||||
{
|
||||
@@ -88,6 +99,16 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
color = Color.Lime;
|
||||
}
|
||||
handle.DrawRect(rect, color);
|
||||
|
||||
var gc = _gcMarkers[currentFrameIndex];
|
||||
if (gc)
|
||||
{
|
||||
triangle[0] = (rect.Left, 0);
|
||||
triangle[1] = (rect.Right, 0);
|
||||
triangle[2] = (rect.Center.X, 5);
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, triangle, Color.LightBlue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
// Prevent window headers from getting off screen due to game window resizes.
|
||||
|
||||
protected override void Update(FrameEventArgs args)
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
var (spaceX, spaceY) = Parent!.Size;
|
||||
if (Position.Y > spaceY)
|
||||
|
||||
@@ -17,8 +17,6 @@ namespace Robust.Client.UserInterface
|
||||
void Initialize();
|
||||
void InitializeTesting();
|
||||
|
||||
void Update(FrameEventArgs args);
|
||||
|
||||
void FrameUpdate(FrameEventArgs args);
|
||||
|
||||
/// <returns>True if a UI control was hit and the key event should not pass through past UI.</returns>
|
||||
|
||||
@@ -133,6 +133,7 @@ namespace Robust.Client.UserInterface
|
||||
QueueMeasureUpdate(RootControl);
|
||||
|
||||
_displayManager.OnWindowResized += args => _updateRootSize();
|
||||
_displayManager.OnWindowScaleChanged += UpdateUIScale;
|
||||
|
||||
StateRoot = new LayoutContainer
|
||||
{
|
||||
@@ -174,11 +175,7 @@ namespace Robust.Client.UserInterface
|
||||
_initializeCommon();
|
||||
}
|
||||
|
||||
public void Update(FrameEventArgs args)
|
||||
{
|
||||
RootControl.DoUpdate(args);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
// Process queued style & layout updates.
|
||||
@@ -833,7 +830,13 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
private void _uiScaleChanged(float newValue)
|
||||
{
|
||||
UIScale = newValue == 0f ? DefaultUIScale : newValue;
|
||||
UpdateUIScale();
|
||||
}
|
||||
|
||||
private void UpdateUIScale()
|
||||
{
|
||||
var newVal = _configurationManager.GetCVar(CVars.DisplayUIScale);
|
||||
UIScale = newVal == 0f ? DefaultUIScale : newVal;
|
||||
|
||||
if (RootControl == null)
|
||||
{
|
||||
|
||||
@@ -94,8 +94,8 @@ namespace Robust.Server
|
||||
private IGameLoop _mainLoop = default!;
|
||||
|
||||
private TimeSpan _lastTitleUpdate;
|
||||
private int _lastReceivedBytes;
|
||||
private int _lastSentBytes;
|
||||
private long _lastReceivedBytes;
|
||||
private long _lastSentBytes;
|
||||
|
||||
private string? _shutdownReason;
|
||||
|
||||
@@ -573,7 +573,7 @@ namespace Robust.Server
|
||||
}
|
||||
|
||||
// Pass Histogram into the IEntityManager.Update so it can do more granular measuring.
|
||||
_entities.Update(frameEventArgs.DeltaSeconds, TickUsage);
|
||||
_entities.TickUpdate(frameEventArgs.DeltaSeconds, TickUsage);
|
||||
|
||||
using (TickUsage.WithLabels("PostEngine").NewTimer())
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Robust.Server.GameObjects
|
||||
private Vector2 _zoom = Vector2.One/2f;
|
||||
private Vector2 _offset;
|
||||
private Angle _rotation;
|
||||
private uint _visibilityMask;
|
||||
|
||||
public override bool DrawFov
|
||||
{
|
||||
@@ -69,9 +70,22 @@ namespace Robust.Server.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public override uint VisibilityMask
|
||||
{
|
||||
get => _visibilityMask;
|
||||
set
|
||||
{
|
||||
if(_visibilityMask == value)
|
||||
return;
|
||||
|
||||
_visibilityMask = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState(ICommonSession player)
|
||||
{
|
||||
return new EyeComponentState(DrawFov, Zoom, Offset, Rotation);
|
||||
return new EyeComponentState(DrawFov, Zoom, Offset, Rotation, VisibilityMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
public interface IServerEntityManager : IEntityManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all entity states that have been modified after and including the provided tick.
|
||||
/// </summary>
|
||||
List<EntityState>? GetEntityStates(GameTick fromTick, IPlayerSession player);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all entity states within an AABB that have been modified after and including the provided tick.
|
||||
/// </summary>
|
||||
List<EntityState>? UpdatePlayerSeenEntityStates(GameTick fromTick, IPlayerSession player, float range);
|
||||
|
||||
// Keep track of deleted entities so we can sync deletions with the client.
|
||||
/// <summary>
|
||||
/// Gets a list of all entity UIDs that were deleted between <paramref name="fromTick" /> and now.
|
||||
/// </summary>
|
||||
List<EntityUid>? GetDeletedEntities(GameTick fromTick);
|
||||
|
||||
/// <summary>
|
||||
/// Remove deletion history.
|
||||
/// </summary>
|
||||
/// <param name="toTick">The last tick to delete the history for. Inclusive.</param>
|
||||
void CullDeletionHistory(GameTick toTick);
|
||||
|
||||
/// <summary>
|
||||
/// Removes entity state persistence information from the entity manager for a player.
|
||||
/// </summary>
|
||||
/// <param name="player"></param>
|
||||
void DropPlayerState(IPlayerSession player);
|
||||
|
||||
float MaxUpdateRange { get; }
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Server side version of the <see cref="IEntityManager"/>.
|
||||
/// </summary>
|
||||
public interface IServerEntityManager : IEntityManager { }
|
||||
}
|
||||
|
||||
@@ -1,55 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using JetBrains.Annotations;
|
||||
using Prometheus;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Shared.GameObjects.TransformComponent;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Manager for entities -- controls things like template loading and instantiation
|
||||
/// </summary>
|
||||
[UsedImplicitly] // DI Container
|
||||
public sealed class ServerEntityManager : EntityManager, IServerEntityManagerInternal
|
||||
{
|
||||
private static readonly Gauge EntitiesCount = Metrics.CreateGauge(
|
||||
"robust_entities_count",
|
||||
"Amount of alive entities.");
|
||||
|
||||
private const float MinimumMotionForMovers = 1 / 128f;
|
||||
|
||||
#region IEntityManager Members
|
||||
|
||||
[Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IPauseManager _pauseManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
|
||||
private float? _maxUpdateRangeCache;
|
||||
|
||||
public float MaxUpdateRange => _maxUpdateRangeCache
|
||||
??= _configurationManager.GetCVar(CVars.NetMaxUpdateRange);
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPauseManager _pauseManager = default!;
|
||||
|
||||
private int _nextServerEntityUid = (int) EntityUid.FirstUid;
|
||||
|
||||
private readonly List<(GameTick tick, EntityUid uid)> _deletionHistory = new();
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
_maxUpdateRangeCache = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEntity CreateEntityUninitialized(string? prototypeName)
|
||||
{
|
||||
@@ -79,33 +53,6 @@ namespace Robust.Server.GameObjects
|
||||
return newEntity;
|
||||
}
|
||||
|
||||
private Entity CreateEntityServer(string? prototypeName)
|
||||
{
|
||||
var entity = CreateEntity(prototypeName);
|
||||
|
||||
if (prototypeName != null)
|
||||
{
|
||||
var prototype = PrototypeManager.Index<EntityPrototype>(prototypeName);
|
||||
|
||||
// At this point in time, all data configure on the entity *should* be purely from the prototype.
|
||||
// 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))
|
||||
{
|
||||
// Make sure to ONLY get components that are defined in the prototype.
|
||||
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
|
||||
// And those aren't guaranteed to exist on the client, so don't clear them.
|
||||
if (prototype.Components.ContainsKey(component.Name))
|
||||
{
|
||||
((Component) component).ClearTicks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEntity SpawnEntity(string? protoName, EntityCoordinates coordinates)
|
||||
{
|
||||
@@ -116,10 +63,7 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
InitializeAndStartEntity((Entity) entity);
|
||||
|
||||
if (_pauseManager.IsMapInitialized(coordinates.GetMapId(this)))
|
||||
{
|
||||
entity.RunMapInit();
|
||||
}
|
||||
if (_pauseManager.IsMapInitialized(coordinates.GetMapId(this))) entity.RunMapInit();
|
||||
|
||||
return entity;
|
||||
}
|
||||
@@ -133,720 +77,26 @@ namespace Robust.Server.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<EntityState>? GetEntityStates(GameTick fromTick, IPlayerSession player)
|
||||
public override void Startup()
|
||||
{
|
||||
var stateEntities = new List<EntityState>();
|
||||
foreach (var entity in AllEntities)
|
||||
{
|
||||
if (entity.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(entity.Initialized);
|
||||
|
||||
if (entity.LastModifiedTick <= fromTick)
|
||||
continue;
|
||||
|
||||
stateEntities.Add(GetEntityState(ComponentManager, entity.Uid, fromTick, player));
|
||||
}
|
||||
|
||||
// no point sending an empty collection
|
||||
return stateEntities.Count == 0 ? default : stateEntities;
|
||||
base.Startup();
|
||||
EntitySystemManager.Initialize();
|
||||
Started = true;
|
||||
}
|
||||
|
||||
private readonly Dictionary<IPlayerSession, SortedSet<EntityUid>> _seenMovers
|
||||
= new();
|
||||
|
||||
// Is thread safe.
|
||||
private SortedSet<EntityUid> GetSeenMovers(IPlayerSession player)
|
||||
{
|
||||
lock (_seenMovers)
|
||||
{
|
||||
return GetSeenMoversUnlocked(player);
|
||||
}
|
||||
}
|
||||
|
||||
private SortedSet<EntityUid> GetSeenMoversUnlocked(IPlayerSession player)
|
||||
{
|
||||
if (!_seenMovers.TryGetValue(player, out var movers))
|
||||
{
|
||||
movers = new SortedSet<EntityUid>();
|
||||
_seenMovers.Add(player, movers);
|
||||
}
|
||||
|
||||
return movers;
|
||||
}
|
||||
|
||||
private void AddToSeenMovers(IPlayerSession player, EntityUid entityUid)
|
||||
{
|
||||
var movers = GetSeenMoversUnlocked(player);
|
||||
|
||||
movers.Add(entityUid);
|
||||
}
|
||||
|
||||
private readonly Dictionary<IPlayerSession, Dictionary<EntityUid, GameTick>> _playerLastSeen
|
||||
= new();
|
||||
|
||||
private static readonly Vector2 Vector2NaN = new(float.NaN, float.NaN);
|
||||
|
||||
private Dictionary<EntityUid, GameTick> GetLastSeen(IPlayerSession player)
|
||||
{
|
||||
lock (_playerLastSeen)
|
||||
{
|
||||
if (!_playerLastSeen.TryGetValue(player, out var lastSeen))
|
||||
{
|
||||
lastSeen = new Dictionary<EntityUid, GameTick>();
|
||||
_playerLastSeen.Add(player, lastSeen);
|
||||
}
|
||||
|
||||
return lastSeen;
|
||||
}
|
||||
}
|
||||
|
||||
private static GameTick GetLastSeenTick(Dictionary<EntityUid, GameTick> lastSeen, EntityUid uid)
|
||||
{
|
||||
if (!lastSeen.TryGetValue(uid, out var tick))
|
||||
{
|
||||
tick = GameTick.First;
|
||||
}
|
||||
|
||||
return tick;
|
||||
}
|
||||
|
||||
private static GameTick UpdateLastSeenTick(Dictionary<EntityUid, GameTick> lastSeen, EntityUid uid, GameTick newTick)
|
||||
{
|
||||
if (!lastSeen.TryGetValue(uid, out var oldTick))
|
||||
{
|
||||
oldTick = GameTick.First;
|
||||
}
|
||||
|
||||
lastSeen[uid] = newTick;
|
||||
|
||||
return oldTick;
|
||||
}
|
||||
|
||||
private static IEnumerable<EntityUid> GetLastSeenAfter(Dictionary<EntityUid, GameTick> lastSeen, GameTick fromTick)
|
||||
{
|
||||
foreach (var (uid, tick) in lastSeen)
|
||||
{
|
||||
if (tick > fromTick)
|
||||
{
|
||||
yield return uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<EntityUid> GetLastSeenOn(Dictionary<EntityUid, GameTick> lastSeen, GameTick fromTick)
|
||||
{
|
||||
foreach (var (uid, tick) in lastSeen)
|
||||
{
|
||||
if (tick == fromTick)
|
||||
{
|
||||
yield return uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetLastSeenTick(Dictionary<EntityUid, GameTick> lastSeen, EntityUid uid, GameTick tick)
|
||||
{
|
||||
lastSeen[uid] = tick;
|
||||
}
|
||||
|
||||
private static void ClearLastSeenTick(Dictionary<EntityUid, GameTick> lastSeen, EntityUid uid)
|
||||
{
|
||||
lastSeen.Remove(uid);
|
||||
}
|
||||
|
||||
public void DropPlayerState(IPlayerSession player)
|
||||
{
|
||||
lock (_playerLastSeen)
|
||||
{
|
||||
_playerLastSeen.Remove(player);
|
||||
}
|
||||
}
|
||||
|
||||
private void IncludeRelatives(IEnumerable<IEntity> children, HashSet<IEntity> set)
|
||||
{
|
||||
foreach (var child in children)
|
||||
{
|
||||
var ent = child!;
|
||||
|
||||
while (ent != null && !ent.Deleted)
|
||||
{
|
||||
if (set.Add(ent))
|
||||
{
|
||||
AddContainedRecursive(ent, set);
|
||||
|
||||
ent = ent.Transform.Parent?.Owner!;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Already processed this entity once.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddContainedRecursive(IEntity ent, HashSet<IEntity> set)
|
||||
{
|
||||
if (!ent.TryGetComponent(out ContainerManagerComponent? contMgr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var container in contMgr.GetAllContainers())
|
||||
{
|
||||
// Manual for loop to cut out allocations.
|
||||
// ReSharper disable once ForCanBeConvertedToForeach
|
||||
for (var i = 0; i < container.ContainedEntities.Count; i++)
|
||||
{
|
||||
var contEnt = container.ContainedEntities[i];
|
||||
set.Add(contEnt);
|
||||
AddContainedRecursive(contEnt, set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PlayerSeenEntityStatesResources
|
||||
{
|
||||
public readonly HashSet<EntityUid> IncludedEnts = new();
|
||||
|
||||
public readonly List<EntityState> EntityStates = new();
|
||||
|
||||
public readonly HashSet<EntityUid> NeededEnts = new();
|
||||
|
||||
public readonly HashSet<IEntity> Relatives = new();
|
||||
}
|
||||
|
||||
private readonly ThreadLocal<PlayerSeenEntityStatesResources> _playerSeenEntityStatesResources
|
||||
= new(() => new PlayerSeenEntityStatesResources());
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<EntityState>? UpdatePlayerSeenEntityStates(GameTick fromTick, IPlayerSession player, float range)
|
||||
public override void TickUpdate(float frameTime, Histogram? histogram)
|
||||
{
|
||||
var playerEnt = player.AttachedEntity;
|
||||
if (playerEnt == null)
|
||||
{
|
||||
// super-observer?
|
||||
return GetEntityStates(fromTick, player);
|
||||
}
|
||||
base.TickUpdate(frameTime, histogram);
|
||||
|
||||
var playerUid = playerEnt.Uid;
|
||||
|
||||
var transform = playerEnt.Transform;
|
||||
var position = transform.WorldPosition;
|
||||
var mapId = transform.MapID;
|
||||
var viewbox = new Box2(position, position).Enlarged(MaxUpdateRange);
|
||||
|
||||
var seenMovers = GetSeenMovers(player);
|
||||
var lSeen = GetLastSeen(player);
|
||||
|
||||
var pseStateRes = _playerSeenEntityStatesResources.Value!;
|
||||
var checkedEnts = pseStateRes.IncludedEnts;
|
||||
var entityStates = pseStateRes.EntityStates;
|
||||
var neededEnts = pseStateRes.NeededEnts;
|
||||
var relatives = pseStateRes.Relatives;
|
||||
checkedEnts.Clear();
|
||||
entityStates.Clear();
|
||||
neededEnts.Clear();
|
||||
relatives.Clear();
|
||||
|
||||
foreach (var uid in seenMovers.ToList())
|
||||
{
|
||||
if (!TryGetEntity(uid, out var entity) || entity.Deleted)
|
||||
{
|
||||
seenMovers.Remove(uid);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out IPhysBody? body))
|
||||
{
|
||||
if (body.LinearVelocity.EqualsApprox(Vector2.Zero, MinimumMotionForMovers))
|
||||
{
|
||||
if (AnyParentInSet(uid, seenMovers))
|
||||
{
|
||||
// parent is moving
|
||||
continue;
|
||||
}
|
||||
|
||||
if (MathF.Abs(body.AngularVelocity) > 0)
|
||||
{
|
||||
if (entity.TryGetComponent(out TransformComponent? txf) && txf.ChildCount > 0)
|
||||
{
|
||||
// has children spinning
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
seenMovers.Remove(uid);
|
||||
}
|
||||
}
|
||||
|
||||
var state = GetEntityState(ComponentManager, uid, fromTick, player);
|
||||
|
||||
if (checkedEnts.Add(uid))
|
||||
{
|
||||
entityStates.Add(state);
|
||||
|
||||
// mover did not change
|
||||
if (state.ComponentStates != null)
|
||||
{
|
||||
// mover can be seen
|
||||
if (!viewbox.Intersects(GetWorldAabbFromEntity(entity)))
|
||||
{
|
||||
// mover changed and can't be seen
|
||||
var idx = Array.FindIndex(state.ComponentStates,
|
||||
x => x is TransformComponentState);
|
||||
|
||||
if (idx != -1)
|
||||
{
|
||||
// mover changed positional data and can't be seen
|
||||
var oldState =
|
||||
(TransformComponentState) state.ComponentStates[idx];
|
||||
var newState = new TransformComponentState(Vector2NaN,
|
||||
oldState.Rotation, oldState.ParentID, oldState.NoLocalRotation);
|
||||
state.ComponentStates[idx] = newState;
|
||||
seenMovers.Remove(uid);
|
||||
ClearLastSeenTick(lSeen, uid);
|
||||
|
||||
checkedEnts.Add(uid);
|
||||
|
||||
var needed = oldState.ParentID;
|
||||
if (!needed.IsValid() || checkedEnts.Contains(needed))
|
||||
{
|
||||
// either no parent attached or parent already included
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GetLastSeenTick(lSeen, needed) == GameTick.Zero)
|
||||
{
|
||||
neededEnts.Add(needed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// mover already added?
|
||||
if (!viewbox.Intersects(GetWorldAabbFromEntity(entity)))
|
||||
{
|
||||
// mover can't be seen
|
||||
var oldState =
|
||||
(TransformComponentState) entity.Transform.GetComponentState(player);
|
||||
entityStates.Add(new EntityState(uid,
|
||||
new ComponentChanged[]
|
||||
{
|
||||
new(false, NetIDs.TRANSFORM, "Transform")
|
||||
},
|
||||
new ComponentState[]
|
||||
{
|
||||
new TransformComponentState(Vector2NaN, oldState.Rotation,
|
||||
oldState.ParentID, oldState.NoLocalRotation)
|
||||
}));
|
||||
|
||||
seenMovers.Remove(uid);
|
||||
ClearLastSeenTick(lSeen, uid);
|
||||
checkedEnts.Add(uid);
|
||||
|
||||
var needed = oldState.ParentID;
|
||||
if (!needed.IsValid() || checkedEnts.Contains(needed))
|
||||
{
|
||||
// either no parent attached or parent already included
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GetLastSeenTick(lSeen, needed) == GameTick.Zero)
|
||||
{
|
||||
neededEnts.Add(needed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var currentTick = CurrentTick;
|
||||
|
||||
// scan pvs box and include children and parents recursively
|
||||
IncludeRelatives(GetEntitiesInRange(mapId, position, range, true), relatives);
|
||||
|
||||
// Exclude any entities that are currently invisible to the player.
|
||||
ExcludeInvisible(relatives, player.VisibilityMask);
|
||||
|
||||
// Always send updates for all grid and map entities.
|
||||
// If we don't, the client-side game state manager WILL blow up.
|
||||
// TODO: Make map manager netcode aware of PVS to avoid the need for this workaround.
|
||||
IncludeMapCriticalEntities(relatives);
|
||||
|
||||
foreach (var entity in relatives)
|
||||
{
|
||||
DebugTools.Assert(entity.Initialized && !entity.Deleted);
|
||||
|
||||
var lastChange = entity.LastModifiedTick;
|
||||
|
||||
var uid = entity.Uid;
|
||||
|
||||
var lastSeen = UpdateLastSeenTick(lSeen, uid, currentTick);
|
||||
|
||||
DebugTools.Assert(lastSeen != currentTick);
|
||||
|
||||
/*
|
||||
if (uid != playerUid && entity.Prototype == playerEnt.Prototype && lastSeen < fromTick)
|
||||
{
|
||||
Logger.DebugS("pvs", $"Player {playerUid} is seeing player {uid}.");
|
||||
}
|
||||
*/
|
||||
|
||||
if (checkedEnts.Contains(uid))
|
||||
{
|
||||
// already have it
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastChange <= lastSeen)
|
||||
{
|
||||
// hasn't changed since last seen
|
||||
continue;
|
||||
}
|
||||
|
||||
// should this be lastSeen or fromTick?
|
||||
var entityState = GetEntityState(ComponentManager, uid, lastSeen, player);
|
||||
|
||||
checkedEnts.Add(uid);
|
||||
|
||||
if (entityState.ComponentStates == null)
|
||||
{
|
||||
// no changes
|
||||
continue;
|
||||
}
|
||||
|
||||
entityStates.Add(entityState);
|
||||
|
||||
if (uid == playerUid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out IPhysBody? body))
|
||||
{
|
||||
// can't be a mover w/o physics
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!body.LinearVelocity.EqualsApprox(Vector2.Zero, MinimumMotionForMovers))
|
||||
{
|
||||
// has motion
|
||||
seenMovers.Add(uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
// not moving
|
||||
seenMovers.Remove(uid);
|
||||
}
|
||||
}
|
||||
|
||||
var priorTick = new GameTick(fromTick.Value - 1);
|
||||
foreach (var uid in GetLastSeenOn(lSeen, priorTick))
|
||||
{
|
||||
if (checkedEnts.Contains(uid))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (uid == playerUid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryGetEntity(uid, out var entity) || entity.Deleted)
|
||||
{
|
||||
// TODO: remove from states list being sent?
|
||||
continue;
|
||||
}
|
||||
|
||||
if (viewbox.Intersects(GetWorldAabbFromEntity(entity)))
|
||||
{
|
||||
// can be seen
|
||||
continue;
|
||||
}
|
||||
|
||||
var state = GetEntityState(ComponentManager, uid, fromTick, player);
|
||||
|
||||
if (state.ComponentStates == null)
|
||||
{
|
||||
// nothing changed
|
||||
continue;
|
||||
}
|
||||
|
||||
checkedEnts.Add(uid);
|
||||
entityStates.Add(state);
|
||||
|
||||
seenMovers.Remove(uid);
|
||||
ClearLastSeenTick(lSeen, uid);
|
||||
|
||||
var idx = Array.FindIndex(state.ComponentStates, x => x is TransformComponentState);
|
||||
|
||||
if (idx == -1)
|
||||
{
|
||||
// no transform changes
|
||||
continue;
|
||||
}
|
||||
|
||||
var oldState = (TransformComponentState) state.ComponentStates[idx];
|
||||
var newState =
|
||||
new TransformComponentState(Vector2NaN, oldState.Rotation, oldState.ParentID, oldState.NoLocalRotation);
|
||||
state.ComponentStates[idx] = newState;
|
||||
|
||||
|
||||
var needed = oldState.ParentID;
|
||||
if (!needed.IsValid() || checkedEnts.Contains(needed))
|
||||
{
|
||||
// don't need to include parent or already included
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GetLastSeenTick(lSeen, needed) == GameTick.First)
|
||||
{
|
||||
neededEnts.Add(needed);
|
||||
}
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
var moreNeededEnts = new HashSet<EntityUid>();
|
||||
|
||||
foreach (var uid in moreNeededEnts)
|
||||
{
|
||||
if (checkedEnts.Contains(uid))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var entity = GetEntity(uid);
|
||||
var state = GetEntityState(ComponentManager, uid, fromTick, player);
|
||||
|
||||
if (state.ComponentStates == null || viewbox.Intersects(GetWorldAabbFromEntity(entity)))
|
||||
{
|
||||
// no states or should already be seen
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
checkedEnts.Add(uid);
|
||||
entityStates.Add(state);
|
||||
|
||||
var idx = Array.FindIndex(state.ComponentStates,
|
||||
x => x is TransformComponentState);
|
||||
|
||||
if (idx == -1)
|
||||
{
|
||||
// no transform state
|
||||
continue;
|
||||
}
|
||||
|
||||
var oldState = (TransformComponentState) state.ComponentStates[idx];
|
||||
var newState =
|
||||
new TransformComponentState(Vector2NaN, oldState.Rotation,
|
||||
oldState.ParentID, oldState.NoLocalRotation);
|
||||
state.ComponentStates[idx] = newState;
|
||||
seenMovers.Remove(uid);
|
||||
|
||||
ClearLastSeenTick(lSeen, uid);
|
||||
var needed = oldState.ParentID;
|
||||
|
||||
if (!needed.IsValid() || checkedEnts.Contains(needed))
|
||||
{
|
||||
// done here
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if further needed
|
||||
if (!checkedEnts.Contains(uid) && GetLastSeenTick(lSeen, needed) == GameTick.Zero)
|
||||
{
|
||||
moreNeededEnts.Add(needed);
|
||||
}
|
||||
}
|
||||
|
||||
neededEnts = moreNeededEnts;
|
||||
} while (neededEnts.Count > 0);
|
||||
|
||||
// help the client out
|
||||
entityStates.Sort((a, b) => a.Uid.CompareTo(b.Uid));
|
||||
|
||||
#if DEBUG_NULL_ENTITY_STATES
|
||||
foreach ( var state in entityStates ) {
|
||||
if (state.ComponentStates == null)
|
||||
{
|
||||
throw new NotImplementedException("Shouldn't send null states.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// no point sending an empty collection
|
||||
return entityStates.Count == 0 ? default : entityStates;
|
||||
EntitiesCount.Set(AllEntities.Count);
|
||||
}
|
||||
|
||||
public override void DeleteEntity(IEntity e)
|
||||
{
|
||||
base.DeleteEntity(e);
|
||||
EventBus.RaiseEvent(EventSource.Local, new EntityDeletedMessage(e));
|
||||
_deletionHistory.Add((CurrentTick, e.Uid));
|
||||
}
|
||||
|
||||
public List<EntityUid>? GetDeletedEntities(GameTick fromTick)
|
||||
{
|
||||
var list = new List<EntityUid>();
|
||||
foreach (var (tick, id) in _deletionHistory)
|
||||
{
|
||||
if (tick >= fromTick)
|
||||
{
|
||||
list.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
// no point sending an empty collection
|
||||
return list.Count == 0 ? default : list;
|
||||
}
|
||||
|
||||
public void CullDeletionHistory(GameTick toTick)
|
||||
{
|
||||
_deletionHistory.RemoveAll(hist => hist.tick <= toTick);
|
||||
}
|
||||
|
||||
public override bool UpdateEntityTree(IEntity entity, Box2? worldAABB = null)
|
||||
{
|
||||
var currentTick = CurrentTick;
|
||||
var updated = base.UpdateEntityTree(entity, worldAABB);
|
||||
|
||||
if (entity.Deleted
|
||||
|| !entity.Initialized
|
||||
|| !Entities.ContainsKey(entity.Uid))
|
||||
{
|
||||
return updated;
|
||||
}
|
||||
|
||||
DebugTools.Assert(entity.Transform.Initialized);
|
||||
|
||||
// note: updated can be false even if something moved a bit
|
||||
worldAABB ??= GetWorldAabbFromEntity(entity);
|
||||
|
||||
foreach (var (player, lastSeen) in _playerLastSeen)
|
||||
{
|
||||
var playerEnt = player.AttachedEntity;
|
||||
if (playerEnt == null)
|
||||
{
|
||||
// player has no entity, gaf?
|
||||
continue;
|
||||
}
|
||||
|
||||
var playerUid = playerEnt.Uid;
|
||||
|
||||
var entityUid = entity.Uid;
|
||||
|
||||
if (entityUid == playerUid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!lastSeen.TryGetValue(playerUid, out var playerTick))
|
||||
{
|
||||
// player can't "see" itself, gaf?
|
||||
continue;
|
||||
}
|
||||
|
||||
var playerPos = playerEnt.Transform.WorldPosition;
|
||||
|
||||
var viewbox = new Box2(playerPos, playerPos).Enlarged(MaxUpdateRange);
|
||||
|
||||
if (!lastSeen.TryGetValue(entityUid, out var tick))
|
||||
{
|
||||
// never saw it other than first tick or was cleared
|
||||
if (!AnyParentMoving(player, entityUid))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (tick >= currentTick)
|
||||
{
|
||||
// currently seeing it
|
||||
continue;
|
||||
}
|
||||
// saw it previously
|
||||
|
||||
// player can't see it now
|
||||
if (!viewbox.Intersects(worldAABB.Value))
|
||||
{
|
||||
var addToMovers = false;
|
||||
if (entity.Transform.LastModifiedTick >= currentTick)
|
||||
{
|
||||
addToMovers = true;
|
||||
}
|
||||
else if (entity.TryGetComponent(out IPhysBody? physics)
|
||||
&& physics.LastModifiedTick >= currentTick)
|
||||
{
|
||||
addToMovers = true;
|
||||
}
|
||||
|
||||
if (addToMovers)
|
||||
{
|
||||
AddToSeenMovers(player, entityUid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private bool AnyParentMoving(IPlayerSession player, EntityUid entityUid)
|
||||
{
|
||||
var seenMovers = GetSeenMoversUnlocked(player);
|
||||
if (seenMovers == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return AnyParentInSet(entityUid, seenMovers);
|
||||
}
|
||||
|
||||
private bool AnyParentInSet(EntityUid entityUid, SortedSet<EntityUid> set)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
if (!TryGetEntity(entityUid, out var ent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var txf = ent.Transform;
|
||||
|
||||
entityUid = txf.ParentUid;
|
||||
|
||||
if (entityUid == EntityUid.Invalid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (set.Contains(entityUid))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion IEntityManager Members
|
||||
|
||||
IEntity IServerEntityManagerInternal.AllocEntity(string? prototypeName, EntityUid? uid)
|
||||
{
|
||||
return AllocEntity(prototypeName, uid);
|
||||
}
|
||||
|
||||
protected override EntityUid GenerateEntityUid()
|
||||
{
|
||||
return new(_nextServerEntityUid++);
|
||||
}
|
||||
|
||||
void IServerEntityManagerInternal.FinishEntityLoad(IEntity entity, IEntityLoadContext? context)
|
||||
{
|
||||
LoadEntity((Entity) entity, context);
|
||||
@@ -863,95 +113,33 @@ namespace Robust.Server.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Startup()
|
||||
protected override EntityUid GenerateEntityUid()
|
||||
{
|
||||
base.Startup();
|
||||
EntitySystemManager.Initialize();
|
||||
Started = true;
|
||||
return new(_nextServerEntityUid++);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a network entity state for the given entity.
|
||||
/// </summary>
|
||||
/// <param name="compMan">ComponentManager that contains the components for the entity.</param>
|
||||
/// <param name="entityUid">Uid of the entity to generate the state from.</param>
|
||||
/// <param name="fromTick">Only provide delta changes from this tick.</param>
|
||||
/// <param name="player">The player to generate this state for.</param>
|
||||
/// <returns>New entity State for the given entity.</returns>
|
||||
private static EntityState GetEntityState(IComponentManager compMan, EntityUid entityUid, GameTick fromTick, IPlayerSession player)
|
||||
private Entity CreateEntityServer(string? prototypeName)
|
||||
{
|
||||
var compStates = new List<ComponentState>();
|
||||
var changed = new List<ComponentChanged>();
|
||||
var entity = CreateEntity(prototypeName);
|
||||
|
||||
foreach (var comp in compMan.GetNetComponents(entityUid))
|
||||
if (prototypeName != null)
|
||||
{
|
||||
DebugTools.Assert(comp.Initialized);
|
||||
var prototype = PrototypeManager.Index<EntityPrototype>(prototypeName);
|
||||
|
||||
// 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.
|
||||
// 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));
|
||||
|
||||
if (comp.CreationTick != GameTick.Zero && comp.CreationTick >= fromTick && !comp.Deleted)
|
||||
// At this point in time, all data configure on the entity *should* be purely from the prototype.
|
||||
// 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))
|
||||
{
|
||||
// Can't be null since it's returned by GetNetComponents
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
changed.Add(ComponentChanged.Added(comp.NetID!.Value, comp.Name));
|
||||
}
|
||||
else if (comp.Deleted && comp.LastModifiedTick >= fromTick)
|
||||
{
|
||||
// Can't be null since it's returned by GetNetComponents
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
changed.Add(ComponentChanged.Removed(comp.NetID!.Value));
|
||||
// Make sure to ONLY get components that are defined in the prototype.
|
||||
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
|
||||
// And those aren't guaranteed to exist on the client, so don't clear them.
|
||||
if (prototype.Components.ContainsKey(component.Name)) ((Component) component).ClearTicks();
|
||||
}
|
||||
}
|
||||
|
||||
return new EntityState(entityUid, changed.ToArray(), compStates.ToArray());
|
||||
}
|
||||
|
||||
|
||||
private void IncludeMapCriticalEntities(HashSet<IEntity> set)
|
||||
{
|
||||
foreach (var mapId in _mapManager.GetAllMapIds())
|
||||
{
|
||||
if (_mapManager.HasMapEntity(mapId))
|
||||
{
|
||||
set.Add(_mapManager.GetMapEntity(mapId));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var grid in _mapManager.GetAllGrids())
|
||||
{
|
||||
if (grid.GridEntityId != EntityUid.Invalid)
|
||||
{
|
||||
set.Add(GetEntity(grid.GridEntityId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ExcludeInvisible(HashSet<IEntity> set, int visibilityMask)
|
||||
{
|
||||
set.RemoveWhere(e =>
|
||||
{
|
||||
if (!e.TryGetComponent(out VisibilityComponent? visibility))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (visibilityMask & visibility.Layer) == 0;
|
||||
});
|
||||
}
|
||||
|
||||
public override void Update(float frameTime, Histogram? histogram)
|
||||
{
|
||||
base.Update(frameTime, histogram);
|
||||
|
||||
EntitiesCount.Set(AllEntities.Count);
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Robust.Server.GameObjects
|
||||
_configurationManager.OnValueChanged(CVars.NetLogLateMsg, b => _logLateMsgs = b, true);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
public void TickUpdate()
|
||||
{
|
||||
while (_queue.Count != 0 && _queue.Peek().SourceTick <= _gameTiming.CurTick)
|
||||
{
|
||||
|
||||
324
Robust.Server/GameStates/EntityViewCulling.cs
Normal file
324
Robust.Server/GameStates/EntityViewCulling.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameStates
|
||||
{
|
||||
internal class EntityViewCulling
|
||||
{
|
||||
private const int ViewSetCapacity = 128; // starting number of entities that are in view
|
||||
private const int PlayerSetSize = 64; // Starting number of players
|
||||
private const int MaxVisPoolSize = 1024; // Maximum number of pooled objects
|
||||
|
||||
private static readonly Vector2 Vector2NaN = new(float.NaN, float.NaN);
|
||||
|
||||
private readonly IServerEntityManager _entMan;
|
||||
private readonly IComponentManager _compMan;
|
||||
private readonly IMapManager _mapManager;
|
||||
|
||||
private readonly Dictionary<ICommonSession, HashSet<EntityUid>> _playerVisibleSets = new(PlayerSetSize);
|
||||
|
||||
private readonly ConcurrentDictionary<ICommonSession, GameTick> _playerLastFullMap = new();
|
||||
|
||||
private readonly List<(GameTick tick, EntityUid uid)> _deletionHistory = new();
|
||||
|
||||
private readonly ObjectPool<HashSet<EntityUid>> _visSetPool
|
||||
= new DefaultObjectPool<HashSet<EntityUid>>(new DefaultPooledObjectPolicy<HashSet<EntityUid>>(), MaxVisPoolSize);
|
||||
|
||||
private readonly ObjectPool<HashSet<EntityUid>> _viewerEntsPool
|
||||
= new DefaultObjectPool<HashSet<EntityUid>>(new DefaultPooledObjectPolicy<HashSet<EntityUid>>(), MaxVisPoolSize);
|
||||
|
||||
/// <summary>
|
||||
/// Is view culling enabled, or will we send the whole map?
|
||||
/// </summary>
|
||||
public bool CullingEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the side of the view bounds square.
|
||||
/// </summary>
|
||||
public float ViewSize { get; set; }
|
||||
|
||||
public EntityViewCulling(IServerEntityManager entMan, IMapManager mapManager)
|
||||
{
|
||||
_entMan = entMan;
|
||||
_compMan = entMan.ComponentManager;
|
||||
_mapManager = mapManager;
|
||||
_compMan = _entMan.ComponentManager;
|
||||
}
|
||||
|
||||
// Not thread safe
|
||||
public void EntityDeleted(EntityUid e)
|
||||
{
|
||||
// Not aware of prediction
|
||||
_deletionHistory.Add((_entMan.CurrentTick, e));
|
||||
}
|
||||
|
||||
// Not thread safe
|
||||
public void CullDeletionHistory(GameTick oldestAck)
|
||||
{
|
||||
_deletionHistory.RemoveAll(hist => hist.tick < oldestAck);
|
||||
}
|
||||
|
||||
private List<EntityUid> GetDeletedEntities(GameTick fromTick)
|
||||
{
|
||||
var list = new List<EntityUid>();
|
||||
foreach (var (tick, id) in _deletionHistory)
|
||||
{
|
||||
if (tick >= fromTick) list.Add(id);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
// Not thread safe
|
||||
public void AddPlayer(ICommonSession session)
|
||||
{
|
||||
_playerVisibleSets.Add(session, new HashSet<EntityUid>(ViewSetCapacity));
|
||||
}
|
||||
|
||||
// Not thread safe
|
||||
public void RemovePlayer(ICommonSession session)
|
||||
{
|
||||
_playerVisibleSets.Remove(session);
|
||||
_playerLastFullMap.Remove(session, out _);
|
||||
}
|
||||
|
||||
// thread safe
|
||||
public bool IsPointVisible(ICommonSession session, in MapCoordinates position)
|
||||
{
|
||||
var viewables = GetSessionViewers(session);
|
||||
|
||||
bool CheckInView(MapCoordinates mapCoordinates, HashSet<EntityUid> entityUids)
|
||||
{
|
||||
foreach (var euid in entityUids)
|
||||
{
|
||||
var (viewBox, mapId) = CalcViewBounds(in euid);
|
||||
|
||||
if (mapId != mapCoordinates.MapId)
|
||||
continue;
|
||||
|
||||
if (!CullingEnabled)
|
||||
return true;
|
||||
|
||||
if (viewBox.Contains(mapCoordinates.Position))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = CheckInView(position, viewables);
|
||||
|
||||
viewables.Clear();
|
||||
_viewerEntsPool.Return(viewables);
|
||||
return result;
|
||||
}
|
||||
|
||||
private HashSet<EntityUid> GetSessionViewers(ICommonSession session)
|
||||
{
|
||||
var viewers = _viewerEntsPool.Get();
|
||||
if (session.Status != SessionStatus.InGame || session.AttachedEntityUid is null)
|
||||
return viewers;
|
||||
|
||||
var query = _compMan.EntityQuery<BasicActorComponent>();
|
||||
|
||||
foreach (var actorComp in query)
|
||||
{
|
||||
if (actorComp.playerSession == session)
|
||||
viewers.Add(actorComp.Owner.Uid);
|
||||
}
|
||||
|
||||
return viewers;
|
||||
}
|
||||
|
||||
// thread safe
|
||||
public (List<EntityState>? updates, List<EntityUid>? deletions) CalculateEntityStates(ICommonSession session, GameTick fromTick, GameTick toTick)
|
||||
{
|
||||
DebugTools.Assert(session.Status == SessionStatus.InGame);
|
||||
|
||||
//TODO: Stop sending all entities to every player first tick
|
||||
List<EntityUid>? deletions;
|
||||
if (!CullingEnabled || fromTick == GameTick.Zero)
|
||||
{
|
||||
var allStates = ServerGameStateManager.GetAllEntityStates(_entMan, session, fromTick);
|
||||
deletions = GetDeletedEntities(fromTick);
|
||||
_playerLastFullMap.AddOrUpdate(session, toTick, (_, _) => toTick);
|
||||
return (allStates, deletions);
|
||||
}
|
||||
|
||||
var lastMapUpdate = _playerLastFullMap.GetValueOrDefault(session);
|
||||
var currentSet = CalcCurrentViewSet(session);
|
||||
|
||||
// If they don't have a usable eye, nothing to send, and map remove will deal with ent removal
|
||||
if (currentSet is null)
|
||||
return (null, null);
|
||||
|
||||
deletions = GetDeletedEntities(fromTick);
|
||||
|
||||
// pretty big allocations :(
|
||||
List<EntityState> entityStates = new(currentSet.Count);
|
||||
var previousSet = _playerVisibleSets[session];
|
||||
|
||||
// complement set
|
||||
foreach (var entityUid in previousSet)
|
||||
{
|
||||
if (!currentSet.Contains(entityUid) && !deletions.Contains(entityUid))
|
||||
{
|
||||
if(_compMan.HasComponent<SnapGridComponent>(entityUid))
|
||||
continue;
|
||||
|
||||
// PVS leave message
|
||||
//TODO: Remove NaN as the signal to leave PVS
|
||||
var xform = _compMan.GetComponent<ITransformComponent>(entityUid);
|
||||
var oldState = (TransformComponent.TransformComponentState)xform.GetComponentState(session);
|
||||
entityStates.Add(new EntityState(entityUid,
|
||||
new ComponentChanged[]
|
||||
{
|
||||
new(false, NetIDs.TRANSFORM, "Transform")
|
||||
},
|
||||
new ComponentState[]
|
||||
{
|
||||
new TransformComponent.TransformComponentState(Vector2NaN, oldState.Rotation,
|
||||
oldState.ParentID, oldState.NoLocalRotation)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var entityUid in currentSet)
|
||||
{
|
||||
if (previousSet.Contains(entityUid))
|
||||
{
|
||||
//Still Visible
|
||||
// only send new changes
|
||||
var newState = ServerGameStateManager.GetEntityState(_entMan.ComponentManager, session, entityUid, fromTick);
|
||||
|
||||
if (!newState.Empty)
|
||||
entityStates.Add(newState);
|
||||
}
|
||||
else
|
||||
{
|
||||
// PVS enter message
|
||||
|
||||
// skip sending anchored entities (walls)
|
||||
if (_compMan.HasComponent<SnapGridComponent>(entityUid) && _entMan.GetEntity(entityUid).LastModifiedTick <= lastMapUpdate)
|
||||
continue;
|
||||
|
||||
// don't assume the client knows anything about us
|
||||
var newState = ServerGameStateManager.GetEntityState(_entMan.ComponentManager, session, entityUid, GameTick.Zero);
|
||||
entityStates.Add(newState);
|
||||
}
|
||||
}
|
||||
|
||||
// swap out vis sets
|
||||
_playerVisibleSets[session] = currentSet;
|
||||
previousSet.Clear();
|
||||
_visSetPool.Return(previousSet);
|
||||
|
||||
// no point sending an empty collection
|
||||
deletions = deletions?.Count == 0 ? default : deletions;
|
||||
|
||||
return (entityStates, deletions);
|
||||
}
|
||||
|
||||
private HashSet<EntityUid>? CalcCurrentViewSet(ICommonSession session)
|
||||
{
|
||||
if (!CullingEnabled)
|
||||
return null;
|
||||
|
||||
// if you don't have an attached entity, you don't see the world.
|
||||
if (session.AttachedEntityUid is null)
|
||||
return null;
|
||||
|
||||
var visibleEnts = _visSetPool.Get();
|
||||
var viewers = GetSessionViewers(session);
|
||||
|
||||
foreach (var eyeEuid in viewers)
|
||||
{
|
||||
var (viewBox, mapId) = CalcViewBounds(in eyeEuid);
|
||||
|
||||
uint visMask = 0;
|
||||
if (_compMan.TryGetComponent<EyeComponent>(eyeEuid, out var eyeComp))
|
||||
visMask = eyeComp.VisibilityMask;
|
||||
|
||||
//Always include the map entity
|
||||
visibleEnts.Add(_mapManager.GetMapEntityId(mapId));
|
||||
|
||||
//Always include viewable ent itself
|
||||
visibleEnts.Add(eyeEuid);
|
||||
|
||||
// grid entity should be added through this
|
||||
// assume there are no deleted ents in here, cull them first in ent/comp manager
|
||||
_entMan.FastEntitiesIntersecting(in mapId, ref viewBox, entity => RecursiveAdd((TransformComponent)entity.Transform, visibleEnts, visMask));
|
||||
}
|
||||
|
||||
viewers.Clear();
|
||||
_viewerEntsPool.Return(viewers);
|
||||
|
||||
return visibleEnts;
|
||||
}
|
||||
|
||||
// Read Safe
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
private bool RecursiveAdd(TransformComponent xform, HashSet<EntityUid> visSet, uint visMask)
|
||||
{
|
||||
var xformUid = xform.Owner.Uid;
|
||||
|
||||
// we are done, this ent has already been checked and is visible
|
||||
if (visSet.Contains(xformUid))
|
||||
return true;
|
||||
|
||||
// if we are invisible, we are not going into the visSet, so don't worry about parents, and children are not going in
|
||||
if (_compMan.TryGetComponent<VisibilityComponent>(xformUid, out var visComp))
|
||||
{
|
||||
if ((visMask & visComp.Layer) == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
var xformParentUid = xform.ParentUid;
|
||||
|
||||
// this is the world entity, it is always visible
|
||||
if (!xformParentUid.IsValid())
|
||||
{
|
||||
visSet.Add(xformUid);
|
||||
return true;
|
||||
}
|
||||
|
||||
// parent is already in the set
|
||||
if (visSet.Contains(xformParentUid))
|
||||
{
|
||||
visSet.Add(xformUid);
|
||||
return true;
|
||||
}
|
||||
|
||||
// parent was not added, so we are not either
|
||||
var xformParent = _compMan.GetComponent<TransformComponent>(xformParentUid);
|
||||
if (!RecursiveAdd(xformParent, visSet, visMask))
|
||||
return false;
|
||||
|
||||
// add us
|
||||
visSet.Add(xformUid);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read Safe
|
||||
private (Box2 view, MapId mapId) CalcViewBounds(in EntityUid euid)
|
||||
{
|
||||
var xform = _compMan.GetComponent<ITransformComponent>(euid);
|
||||
|
||||
var view = Box2.UnitCentered.Scale(ViewSize).Translated(xform.WorldPosition);
|
||||
var map = xform.MapID;
|
||||
|
||||
return (view, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,21 @@
|
||||
namespace Robust.Server.GameStates
|
||||
{
|
||||
/// <summary>
|
||||
/// Engine service that provides creating and dispatching of game states.
|
||||
/// Engine service that provides creating and dispatching of game states.
|
||||
/// </summary>
|
||||
public interface IServerGameStateManager
|
||||
{
|
||||
/// <summary>
|
||||
/// One time initialization of the service.
|
||||
/// One time initialization of the service.
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Create and dispatch game states to all connected sessions.
|
||||
/// Create and dispatch game states to all connected sessions.
|
||||
/// </summary>
|
||||
void SendGameStateUpdate();
|
||||
|
||||
bool PvsEnabled { get; }
|
||||
float PvsRange { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
@@ -9,22 +10,24 @@ using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameStates
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class ServerGameStateManager : IServerGameStateManager
|
||||
/// <inheritdoc cref="IServerGameStateManager"/>
|
||||
public class ServerGameStateManager : IServerGameStateManager, IPostInjectInit
|
||||
{
|
||||
// Mapping of net UID of clients -> last known acked state.
|
||||
private readonly Dictionary<long, GameTick> _ackedStates = new();
|
||||
private GameTick _lastOldestAck = GameTick.Zero;
|
||||
|
||||
private EntityViewCulling _entityView = null!;
|
||||
|
||||
[Dependency] private readonly IServerEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IServerNetManager _networkManager = default!;
|
||||
@@ -35,6 +38,12 @@ namespace Robust.Server.GameStates
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
|
||||
public bool PvsEnabled => _configurationManager.GetCVar(CVars.NetPVS);
|
||||
public float PvsRange => _configurationManager.GetCVar(CVars.NetMaxUpdateRange);
|
||||
|
||||
public void PostInject()
|
||||
{
|
||||
_entityView = new EntityViewCulling(_entityManager, _mapManager);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
@@ -44,6 +53,27 @@ namespace Robust.Server.GameStates
|
||||
|
||||
_networkManager.Connected += HandleClientConnected;
|
||||
_networkManager.Disconnect += HandleClientDisconnect;
|
||||
|
||||
_playerManager.PlayerStatusChanged += HandlePlayerStatusChanged;
|
||||
|
||||
_entityManager.EntityDeleted += HandleEntityDeleted;
|
||||
}
|
||||
|
||||
private void HandleEntityDeleted(object? sender, EntityUid e)
|
||||
{
|
||||
_entityView.EntityDeleted(e);
|
||||
}
|
||||
|
||||
private void HandlePlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus == SessionStatus.InGame)
|
||||
{
|
||||
_entityView.AddPlayer(e.Session);
|
||||
}
|
||||
else if(e.OldStatus == SessionStatus.InGame)
|
||||
{
|
||||
_entityView.RemovePlayer(e.Session);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleClientConnected(object? sender, NetChannelArgs e)
|
||||
@@ -57,13 +87,6 @@ namespace Robust.Server.GameStates
|
||||
private void HandleClientDisconnect(object? sender, NetChannelArgs e)
|
||||
{
|
||||
_ackedStates.Remove(e.Channel.ConnectionId);
|
||||
|
||||
if (!_playerManager.TryGetSessionByChannel(e.Channel, out var session))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_entityManager.DropPlayerState(session);
|
||||
}
|
||||
|
||||
private void HandleStateAck(MsgStateAck msg)
|
||||
@@ -98,12 +121,13 @@ namespace Robust.Server.GameStates
|
||||
{
|
||||
DebugTools.Assert(_networkManager.IsServer);
|
||||
|
||||
_entityManager.Update();
|
||||
_entityView.ViewSize = PvsRange * 2;
|
||||
_entityView.CullingEnabled = PvsEnabled;
|
||||
|
||||
if (!_networkManager.IsConnected)
|
||||
{
|
||||
// Prevent deletions piling up if we have no clients.
|
||||
_entityManager.CullDeletionHistory(GameTick.MaxValue);
|
||||
_entityView.CullDeletionHistory(GameTick.MaxValue);
|
||||
_mapManager.CullDeletionHistory(GameTick.MaxValue);
|
||||
return;
|
||||
}
|
||||
@@ -112,16 +136,14 @@ namespace Robust.Server.GameStates
|
||||
|
||||
var oldestAck = GameTick.MaxValue;
|
||||
|
||||
var oldDeps = IoCManager.Resolve<IDependencyCollection>();
|
||||
|
||||
var deps = new DependencyCollection();
|
||||
deps.RegisterInstance<ILogManager>(new ProxyLogManager(IoCManager.Resolve<ILogManager>()));
|
||||
deps.BuildGraph();
|
||||
|
||||
var mainThread = Thread.CurrentThread;
|
||||
(MsgState, INetChannel) GenerateMail(IPlayerSession session)
|
||||
{
|
||||
IoCManager.InitThread(deps, true);
|
||||
// KILL IT WITH FIRE
|
||||
if(mainThread != Thread.CurrentThread)
|
||||
IoCManager.InitThread(new DependencyCollection(), true);
|
||||
|
||||
// people not in the game don't get states
|
||||
if (session.Status != SessionStatus.InGame)
|
||||
{
|
||||
return default;
|
||||
@@ -134,11 +156,8 @@ namespace Robust.Server.GameStates
|
||||
DebugTools.Assert("Why does this channel not have an entry?");
|
||||
}
|
||||
|
||||
var entStates = lastAck == GameTick.Zero || !PvsEnabled
|
||||
? _entityManager.GetEntityStates(lastAck, session)
|
||||
: _entityManager.UpdatePlayerSeenEntityStates(lastAck, session, _entityManager.MaxUpdateRange);
|
||||
var (entStates, deletions) = _entityView.CalculateEntityStates(session, lastAck, _gameTiming.CurTick);
|
||||
var playerStates = _playerManager.GetPlayerStates(lastAck);
|
||||
var deletions = _entityManager.GetDeletedEntities(lastAck);
|
||||
var mapData = _mapManager.GetStateData(lastAck);
|
||||
|
||||
// lastAck varies with each client based on lag and such, we can't just make 1 global state and send it to everyone
|
||||
@@ -167,15 +186,9 @@ namespace Robust.Server.GameStates
|
||||
}
|
||||
|
||||
var mailBag = _playerManager.GetAllPlayers()
|
||||
.AsParallel().Select(GenerateMail).ToList();
|
||||
|
||||
// TODO: oh god oh fuck kill it with fire.
|
||||
// PLINQ *seems* to be scheduling to the main thread partially (I guess that makes sense?)
|
||||
// Which causes that IoC "hack" up there to override IoC in the main thread, nuking the game.
|
||||
// At least, that's our running theory. I reproduced it once locally and can't reproduce it again.
|
||||
// Throwing shit at the wall to hope it fixes it.
|
||||
IoCManager.InitThread(oldDeps, true);
|
||||
|
||||
.AsParallel()
|
||||
.Where(s=>s.Status == SessionStatus.InGame).Select(GenerateMail);
|
||||
|
||||
foreach (var (msg, chan) in mailBag)
|
||||
{
|
||||
// see session.Status != SessionStatus.InGame above
|
||||
@@ -187,9 +200,78 @@ namespace Robust.Server.GameStates
|
||||
if (oldestAck > _lastOldestAck)
|
||||
{
|
||||
_lastOldestAck = oldestAck;
|
||||
_entityManager.CullDeletionHistory(oldestAck);
|
||||
_entityView.CullDeletionHistory(oldestAck);
|
||||
_mapManager.CullDeletionHistory(oldestAck);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a network entity state for the given entity.
|
||||
/// </summary>
|
||||
/// <param name="compMan">ComponentManager that contains the components for the entity.</param>
|
||||
/// <param name="player">The player to generate this state for.</param>
|
||||
/// <param name="entityUid">Uid of the entity to generate the state from.</param>
|
||||
/// <param name="fromTick">Only provide delta changes from this tick.</param>
|
||||
/// <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>();
|
||||
|
||||
foreach (var comp in compMan.GetNetComponents(entityUid))
|
||||
{
|
||||
DebugTools.Assert(comp.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.
|
||||
// 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));
|
||||
|
||||
if (comp.CreationTick != GameTick.Zero && comp.CreationTick >= fromTick && !comp.Deleted)
|
||||
{
|
||||
// Can't be null since it's returned by GetNetComponents
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
changed.Add(ComponentChanged.Added(comp.NetID!.Value, comp.Name));
|
||||
}
|
||||
else if (comp.Deleted && comp.LastModifiedTick >= fromTick)
|
||||
{
|
||||
// Can't be null since it's returned by GetNetComponents
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
changed.Add(ComponentChanged.Removed(comp.NetID!.Value));
|
||||
}
|
||||
}
|
||||
|
||||
return new EntityState(entityUid, changed.ToArray(), compStates.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all entity states that have been modified after and including the provided tick.
|
||||
/// </summary>
|
||||
internal static List<EntityState>? GetAllEntityStates(IEntityManager entityMan, ICommonSession player, GameTick fromTick)
|
||||
{
|
||||
var stateEntities = new List<EntityState>();
|
||||
foreach (var entity in entityMan.GetEntities())
|
||||
{
|
||||
if (entity.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(entity.Initialized);
|
||||
|
||||
if (entity.LastModifiedTick <= fromTick)
|
||||
continue;
|
||||
|
||||
stateEntities.Add(GetEntityState(entityMan.ComponentManager, player, entity.Uid, fromTick));
|
||||
}
|
||||
|
||||
// no point sending an empty collection
|
||||
return stateEntities.Count == 0 ? default : stateEntities;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,6 @@ namespace Robust.Server.Player
|
||||
{
|
||||
DateTime ConnectedTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The visibility mask for this player.
|
||||
/// The player will be able to get updates for entities whose layers match the mask.
|
||||
/// </summary>
|
||||
int VisibilityMask { get; set; }
|
||||
|
||||
event EventHandler<SessionStatusEventArgs> PlayerStatusChanged;
|
||||
|
||||
void JoinGame();
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="5.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="5.0.4" />
|
||||
<PackageReference Include="prometheus-net" Version="4.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Loki" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Primitives" Version="5.0.0" />
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Robust.Shared
|
||||
CVarDef.Create("net.cmdrate", 30, CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
|
||||
public static readonly CVarDef<int> NetRate =
|
||||
CVarDef.Create("net.rate", 10240, CVar.ARCHIVE | CVar.REPLICATED | CVar.CLIENTONLY);
|
||||
CVarDef.Create("net.rate", 10240, CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
|
||||
// That's comma-separated, btw.
|
||||
public static readonly CVarDef<string> NetBindTo =
|
||||
|
||||
@@ -19,15 +19,29 @@ namespace Robust.Shared.Containers
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ContainedEntity == null) return Array.Empty<IEntity>();
|
||||
if (ContainedEntity == null)
|
||||
return Array.Empty<IEntity>();
|
||||
|
||||
return new List<IEntity> {ContainedEntity};
|
||||
// Cast to handle nullability.
|
||||
return (IEntity[]) _containedEntityArray!;
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
[field: DataField("ent")]
|
||||
public IEntity? ContainedEntity { get; private set; }
|
||||
[DataField("ent")]
|
||||
public IEntity? ContainedEntity
|
||||
{
|
||||
get => _containedEntity;
|
||||
private set
|
||||
{
|
||||
_containedEntity = value;
|
||||
_containedEntityArray[0] = value;
|
||||
}
|
||||
}
|
||||
|
||||
private IEntity? _containedEntity;
|
||||
// Used by ContainedEntities to avoid allocating.
|
||||
private readonly IEntity?[] _containedEntityArray = new IEntity[1];
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ContainerType => ClassName;
|
||||
|
||||
@@ -23,9 +23,11 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private const int TypeCapacity = 32;
|
||||
private const int ComponentCollectionCapacity = 1024;
|
||||
private const int EntityCapacity = 1024;
|
||||
private const int NetComponentCapacity = 8;
|
||||
|
||||
private readonly Dictionary<uint, Dictionary<EntityUid, Component>> _entNetIdDict
|
||||
= new();
|
||||
private readonly Dictionary<EntityUid, Dictionary<uint, Component>> _netComponents
|
||||
= new(EntityCapacity);
|
||||
|
||||
private readonly Dictionary<Type, Dictionary<EntityUid, Component>> _entTraitDict
|
||||
= new();
|
||||
@@ -60,7 +62,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
_entNetIdDict.Clear();
|
||||
_netComponents.Clear();
|
||||
_entTraitDict.Clear();
|
||||
_entCompIndex.Clear();
|
||||
_deleteSet.Clear();
|
||||
@@ -76,10 +78,6 @@ namespace Robust.Shared.GameObjects
|
||||
private void OnComponentAdded(IComponentRegistration obj)
|
||||
{
|
||||
_entTraitDict.Add(obj.Type, new Dictionary<EntityUid, Component>());
|
||||
|
||||
var netID = obj.NetID;
|
||||
if (netID.HasValue)
|
||||
_entNetIdDict.Add(netID.Value, new Dictionary<EntityUid, Component>());
|
||||
}
|
||||
|
||||
private void OnComponentReferenceAdded((IComponentRegistration, Type) obj)
|
||||
@@ -148,7 +146,13 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
// the main comp grid keeps this in sync
|
||||
var netId = component.NetID.Value;
|
||||
_entNetIdDict[netId].Add(uid, component);
|
||||
|
||||
if (!_netComponents.TryGetValue(uid, out var netSet))
|
||||
{
|
||||
netSet = new Dictionary<uint, Component>(NetComponentCapacity);
|
||||
_netComponents.Add(uid, netSet);
|
||||
}
|
||||
netSet.Add(netId, component);
|
||||
|
||||
// mark the component as dirty for networking
|
||||
component.Dirty();
|
||||
@@ -325,15 +329,19 @@ namespace Robust.Shared.GameObjects
|
||||
_entTraitDict[refType].Remove(entityUid);
|
||||
}
|
||||
|
||||
if (component.NetID == null) return;
|
||||
// ReSharper disable once InvertIf
|
||||
if (component.NetID != null)
|
||||
{
|
||||
var netSet = _netComponents[entityUid];
|
||||
if (netSet.Count == 1)
|
||||
_netComponents.Remove(entityUid);
|
||||
else
|
||||
netSet.Remove(component.NetID.Value);
|
||||
|
||||
component.Owner.Dirty();
|
||||
}
|
||||
|
||||
var netId = component.NetID.Value;
|
||||
_entNetIdDict[netId].Remove(entityUid);
|
||||
_entCompIndex.Remove(entityUid, component);
|
||||
|
||||
// mark the owning entity as dirty for networking
|
||||
component.Owner.Dirty();
|
||||
|
||||
ComponentDeleted?.Invoke(this, new DeletedComponentEventArgs(component, entityUid));
|
||||
}
|
||||
|
||||
@@ -356,8 +364,8 @@ namespace Robust.Shared.GameObjects
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool HasComponent(EntityUid uid, uint netId)
|
||||
{
|
||||
var dict = _entNetIdDict[netId];
|
||||
return dict.TryGetValue(uid, out var comp) && !comp.Deleted;
|
||||
return _netComponents.TryGetValue(uid, out var netSet)
|
||||
&& netSet.ContainsKey(netId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -386,17 +394,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public IComponent GetComponent(EntityUid uid, uint netId)
|
||||
{
|
||||
// ReSharper disable once InvertIf
|
||||
var dict = _entNetIdDict[netId];
|
||||
if (dict.TryGetValue(uid, out var comp))
|
||||
{
|
||||
if (!comp.Deleted)
|
||||
{
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException($"Entity {uid} does not have a component of NetID {netId}");
|
||||
return _netComponents[uid][netId];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -433,19 +431,16 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetComponent(EntityUid uid, uint netId, [NotNullWhen(true)] out IComponent? component)
|
||||
public bool TryGetComponent(EntityUid uid, uint netId, [MaybeNullWhen(false)] out IComponent component)
|
||||
{
|
||||
var dict = _entNetIdDict[netId];
|
||||
if (dict.TryGetValue(uid, out var comp))
|
||||
if (_netComponents.TryGetValue(uid, out var netSet)
|
||||
&& netSet.TryGetValue(netId, out var comp))
|
||||
{
|
||||
if (!comp.Deleted)
|
||||
{
|
||||
component = comp;
|
||||
return true;
|
||||
}
|
||||
component = comp;
|
||||
return true;
|
||||
}
|
||||
|
||||
component = null;
|
||||
component = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -476,13 +471,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IComponent> GetNetComponents(EntityUid uid)
|
||||
{
|
||||
var comps = _entCompIndex[uid];
|
||||
foreach (var comp in comps)
|
||||
{
|
||||
if (comp.Deleted || comp.NetID == null) continue;
|
||||
|
||||
yield return comp;
|
||||
}
|
||||
return _netComponents[uid].Values;
|
||||
}
|
||||
|
||||
#region Join Functions
|
||||
@@ -601,11 +590,6 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
_entTraitDict.Add(refType, new Dictionary<EntityUid, Component>());
|
||||
}
|
||||
|
||||
foreach (var netId in _componentFactory.GetAllNetIds())
|
||||
{
|
||||
_entNetIdDict.Add(netId, new Dictionary<EntityUid, Component>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -21,6 +21,13 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public virtual Angle Rotation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The visibility mask for this eye.
|
||||
/// The player will be able to get updates for entities whose layers match the mask.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public virtual uint VisibilityMask { get; set; }
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
@@ -30,13 +37,15 @@ namespace Robust.Shared.GameObjects
|
||||
public Vector2 Zoom { get; }
|
||||
public Vector2 Offset { get; }
|
||||
public Angle Rotation { get; }
|
||||
public uint VisibilityMask { get; }
|
||||
|
||||
public EyeComponentState(bool drawFov, Vector2 zoom, Vector2 offset, Angle rotation) : base(NetIDs.EYE)
|
||||
public EyeComponentState(bool drawFov, Vector2 zoom, Vector2 offset, Angle rotation, uint visibilityMask) : base(NetIDs.EYE)
|
||||
{
|
||||
DrawFov = drawFov;
|
||||
Zoom = zoom;
|
||||
Offset = offset;
|
||||
Rotation = rotation;
|
||||
VisibilityMask = visibilityMask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,14 +269,6 @@ namespace Robust.Shared.GameObjects
|
||||
return EntityManager.ComponentManager.GetComponent(Uid, type);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IComponent GetComponent(uint netId)
|
||||
{
|
||||
DebugTools.Assert(!Deleted, "Tried to get component on a deleted entity.");
|
||||
|
||||
return EntityManager.ComponentManager.GetComponent(Uid, netId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component) where T : class
|
||||
{
|
||||
@@ -303,19 +295,6 @@ namespace Robust.Shared.GameObjects
|
||||
return TryGetComponent(type, out var component) ? component : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetComponent(uint netId, [NotNullWhen(true)] out IComponent? component)
|
||||
{
|
||||
DebugTools.Assert(!Deleted, "Tried to get component on a deleted entity.");
|
||||
|
||||
return EntityManager.ComponentManager.TryGetComponent(Uid, netId, out component);
|
||||
}
|
||||
|
||||
public IComponent? GetComponentOrNull(uint netId)
|
||||
{
|
||||
return TryGetComponent(netId, out var component) ? component : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Delete()
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Prometheus;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -14,18 +15,20 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public delegate void EntityQueryCallback(IEntity entity);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract class EntityManager : IEntityManager
|
||||
{
|
||||
#region Dependencies
|
||||
|
||||
[Dependency] private readonly IEntityNetworkManager EntityNetworkManager = default!;
|
||||
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
||||
[Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!;
|
||||
[Dependency] private readonly IComponentFactory ComponentFactory = default!;
|
||||
[Dependency] private readonly IComponentManager _componentManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[IoC.Dependency] private readonly IEntityNetworkManager EntityNetworkManager = default!;
|
||||
[IoC.Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
||||
[IoC.Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!;
|
||||
[IoC.Dependency] private readonly IComponentFactory ComponentFactory = default!;
|
||||
[IoC.Dependency] private readonly IComponentManager _componentManager = default!;
|
||||
[IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[IoC.Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
#endregion Dependencies
|
||||
|
||||
@@ -91,16 +94,16 @@ namespace Robust.Shared.GameObjects
|
||||
_componentManager.Clear();
|
||||
}
|
||||
|
||||
public virtual void Update(float frameTime, Histogram? histogram)
|
||||
public virtual void TickUpdate(float frameTime, Histogram? histogram)
|
||||
{
|
||||
using (histogram?.WithLabels("EntityNet").NewTimer())
|
||||
{
|
||||
EntityNetworkManager.Update();
|
||||
EntityNetworkManager.TickUpdate();
|
||||
}
|
||||
|
||||
using (histogram?.WithLabels("EntitySystems").NewTimer())
|
||||
{
|
||||
EntitySystemManager.Update(frameTime);
|
||||
EntitySystemManager.TickUpdate(frameTime);
|
||||
}
|
||||
|
||||
using (histogram?.WithLabels("EntityEventBus").NewTimer())
|
||||
@@ -108,6 +111,11 @@ namespace Robust.Shared.GameObjects
|
||||
_eventBus.ProcessEventQueue();
|
||||
}
|
||||
|
||||
using (histogram?.WithLabels("ComponentCull").NewTimer())
|
||||
{
|
||||
_componentManager.CullRemovedComponents();
|
||||
}
|
||||
|
||||
using (histogram?.WithLabels("EntityCull").NewTimer())
|
||||
{
|
||||
CullDeletedEntities();
|
||||
@@ -272,6 +280,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
entity.LifeStage = EntityLifeStage.Deleted;
|
||||
EntityDeleted?.Invoke(this, entity.Uid);
|
||||
EventBus.RaiseEvent(EventSource.Local, new EntityDeletedMessage(entity));
|
||||
}
|
||||
|
||||
public void DeleteEntity(EntityUid uid)
|
||||
@@ -451,6 +460,9 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for generating a new EntityUid for an entity currently being created.
|
||||
/// </summary>
|
||||
protected abstract EntityUid GenerateEntityUid();
|
||||
|
||||
#region Spatial Queries
|
||||
@@ -472,6 +484,17 @@ namespace Robust.Shared.GameObjects
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public void FastEntitiesIntersecting(in MapId mapId, ref Box2 position, EntityQueryCallback callback)
|
||||
{
|
||||
if (mapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
_entityTreesPerMap[mapId]._b2Tree
|
||||
.FastQuery(ref position, (ref IEntity data) => callback(data));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> GetEntitiesIntersecting(MapId mapId, Box2 position, bool approximate = false)
|
||||
{
|
||||
@@ -712,18 +735,8 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public virtual void Update()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The children of this entity are about to be deleted.
|
||||
/// </summary>
|
||||
public class EntityTerminatingEvent : EntityEventArgs { }
|
||||
|
||||
public enum EntityMessageType : byte
|
||||
{
|
||||
Error = 0,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
using System;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
@@ -10,6 +10,8 @@ namespace Robust.Shared.GameObjects
|
||||
public ComponentChanged[]? ComponentChanges { get; }
|
||||
public ComponentState[]? ComponentStates { get; }
|
||||
|
||||
public bool Empty => ComponentChanges is null && ComponentStates is null;
|
||||
|
||||
public EntityState(EntityUid uid, ComponentChanged[]? changedComponents, ComponentState[]? componentStates)
|
||||
{
|
||||
Uid = uid;
|
||||
|
||||
@@ -251,7 +251,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update(float frameTime)
|
||||
public void TickUpdate(float frameTime)
|
||||
{
|
||||
foreach (var updReg in _updateOrder)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public sealed class EntityDeletedMessage : EntityEventArgs
|
||||
{
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// The children of this entity are about to be deleted.
|
||||
/// </summary>
|
||||
public class EntityTerminatingEvent : EntityEventArgs { }
|
||||
}
|
||||
@@ -112,7 +112,8 @@ namespace Robust.Shared.GameObjects
|
||||
bool HasComponent(EntityUid uid, Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the entity has a component with a given network ID.
|
||||
/// Checks if the entity has a component with a given network ID. This does not check
|
||||
/// if the component is deleted.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to check.</param>
|
||||
/// <param name="netId">Network ID to check for.</param>
|
||||
@@ -136,7 +137,8 @@ namespace Robust.Shared.GameObjects
|
||||
IComponent GetComponent(EntityUid uid, Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component with a specific network ID.
|
||||
/// Returns the component with a specific network ID. This does not check
|
||||
/// if the component is deleted.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to look on.</param>
|
||||
/// <param name="netId">Network ID of the component to retrieve.</param>
|
||||
@@ -162,7 +164,8 @@ namespace Robust.Shared.GameObjects
|
||||
bool TryGetComponent(EntityUid uid, Type type, [NotNullWhen(true)] out IComponent? component);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component with a specified network ID.
|
||||
/// Returns the component with a specified network ID. This does not check
|
||||
/// if the component is deleted.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to check.</param>
|
||||
/// <param name="netId">Component Network ID to check for.</param>
|
||||
|
||||
@@ -127,17 +127,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// </exception>
|
||||
IComponent GetComponent(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the component with the specified network ID.
|
||||
/// </summary>
|
||||
/// <param name="netID">The net ID of the component to retrieve.</param>
|
||||
/// <returns>The component with the provided net ID.</returns>
|
||||
/// <seealso cref="IComponent.NetID" />
|
||||
/// <exception cref="Shared.GameObjects.UnknownComponentException">
|
||||
/// Thrown if there is no component with the specified net ID.
|
||||
/// </exception>
|
||||
IComponent GetComponent(uint netID);
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to retrieve the component with specified type,
|
||||
/// writing it to the <paramref name="component" /> out parameter if it was found.
|
||||
@@ -172,23 +161,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>The component, if it was found. Null otherwise.</returns>
|
||||
IComponent? GetComponentOrNull(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to retrieve the component with specified network ID,
|
||||
/// writing it to the <paramref name="component" /> out parameter if it was found.
|
||||
/// </summary>
|
||||
/// <param name="netId">The component net ID to attempt to fetch.</param>
|
||||
/// <param name="component">The component, if it was found. Null otherwise.</param>
|
||||
/// <returns>True if a component with specified net ID was found.</returns>
|
||||
bool TryGetComponent(uint netId, [NotNullWhen(true)] out IComponent? component);
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to retrieve the component with specified network ID,
|
||||
/// returning it if it was found.
|
||||
/// </summary>
|
||||
/// <param name="netId">The component net ID to attempt to fetch.</param>
|
||||
/// <returns>The component, if it was found. Null otherwise.</returns>
|
||||
IComponent? GetComponentOrNull(uint netId);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes this entity.
|
||||
/// </summary>
|
||||
@@ -222,6 +194,9 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="message">Message to send.</param>
|
||||
void SendNetworkMessage(IComponent owner, ComponentMessage message, INetChannel? channel = null);
|
||||
|
||||
/// <summary>
|
||||
/// Marks this entity as dirty so that it will be updated over the network.
|
||||
/// </summary>
|
||||
void Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Robust.Shared.GameObjects
|
||||
void Initialize();
|
||||
void Startup();
|
||||
void Shutdown();
|
||||
void Update(float frameTime, Histogram? histogram=null);
|
||||
void TickUpdate(float frameTime, Histogram? histogram=null);
|
||||
|
||||
/// <summary>
|
||||
/// Client-specific per-render frame updating.
|
||||
@@ -126,6 +126,8 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="box"></param>
|
||||
/// <param name="approximate">If true, will not recalculate precise entity AABBs, resulting in a perf increase. </param>
|
||||
bool AnyEntitiesIntersecting(MapId mapId, Box2 box, bool approximate = false);
|
||||
|
||||
void FastEntitiesIntersecting(in MapId mapId, ref Box2 position, EntityQueryCallback callback);
|
||||
|
||||
/// <summary>
|
||||
/// Gets entities with a bounding box that intersects this box
|
||||
@@ -216,8 +218,5 @@ namespace Robust.Shared.GameObjects
|
||||
bool RemoveFromEntityTree(IEntity entity, MapId mapId);
|
||||
|
||||
#endregion
|
||||
|
||||
void Update();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Sends out queued messages based on current tick.
|
||||
/// </summary>
|
||||
void Update();
|
||||
void TickUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
/// <param name="frameTime">Time since the last frame was rendered.</param>
|
||||
/// <seealso cref="IEntitySystem.Update(float)"/>
|
||||
void Update(float frameTime);
|
||||
void TickUpdate(float frameTime);
|
||||
void FrameUpdate(float frameTime);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Shared.Containers;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Controllers;
|
||||
@@ -260,6 +261,8 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
var mapId = message.Container.Owner.Transform.MapID;
|
||||
|
||||
physicsComponent.LinearVelocity = Vector2.Zero;
|
||||
physicsComponent.AngularVelocity = 0.0f;
|
||||
physicsComponent.ClearJoints();
|
||||
_maps[mapId].RemoveBody(physicsComponent);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace Robust.Shared.IoC
|
||||
|
||||
private readonly Dictionary<Type, DependencyFactoryDelegate<object>> _resolveFactories = new();
|
||||
|
||||
private readonly Queue<Type> _pendingResolves = new();
|
||||
|
||||
// To do injection of common types like components, we make DynamicMethods to do the actual injecting.
|
||||
// This is way faster than reflection and should be allocation free outside setup.
|
||||
private readonly Dictionary<Type, (InjectorDelegate? @delegate, object[]? services)> _injectorCache =
|
||||
@@ -38,9 +40,38 @@ namespace Robust.Shared.IoC
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Register<TInterface, TImplementation>(bool overwrite = false)
|
||||
where TImplementation : class, TInterface, new()
|
||||
where TImplementation : class, TInterface
|
||||
{
|
||||
Register<TInterface, TImplementation>(() => new TImplementation(), overwrite);
|
||||
Register<TInterface, TImplementation>(() =>
|
||||
{
|
||||
var objectType = typeof(TImplementation);
|
||||
var constructors = objectType.GetConstructors();
|
||||
|
||||
if (constructors.Length != 1)
|
||||
throw new InvalidOperationException($"Dependency '{typeof(TImplementation).FullName}' requires exactly one constructor.");
|
||||
|
||||
var constructorParams = constructors[0].GetParameters();
|
||||
var parameters = new object[constructorParams.Length];
|
||||
|
||||
for (var index = 0; index < constructorParams.Length; index++)
|
||||
{
|
||||
var param = constructorParams[index];
|
||||
|
||||
if (_services.TryGetValue(param.ParameterType, out var instance))
|
||||
parameters[index] = instance;
|
||||
else
|
||||
{
|
||||
if (_resolveTypes.ContainsKey(param.ParameterType))
|
||||
{
|
||||
throw new InvalidOperationException($"Dependency '{typeof(TImplementation).FullName}' ctor requires {param.ParameterType.FullName} registered before it.");
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Dependency '{typeof(TImplementation).FullName}' ctor has unknown dependency {param.ParameterType.FullName}");
|
||||
}
|
||||
}
|
||||
|
||||
return (TImplementation) Activator.CreateInstance(objectType, parameters)!;
|
||||
}, overwrite);
|
||||
}
|
||||
|
||||
public void Register<TInterface, TImplementation>(DependencyFactoryDelegate<TImplementation> factory, bool overwrite = false)
|
||||
@@ -51,6 +82,7 @@ namespace Robust.Shared.IoC
|
||||
|
||||
_resolveTypes[interfaceType] = typeof(TImplementation);
|
||||
_resolveFactories[typeof(TImplementation)] = factory;
|
||||
_pendingResolves.Enqueue(interfaceType);
|
||||
}
|
||||
|
||||
[AssertionMethod]
|
||||
@@ -151,8 +183,11 @@ namespace Robust.Shared.IoC
|
||||
|
||||
// First we build every type we have registered but isn't yet built.
|
||||
// This allows us to run this after the content assembly has been loaded.
|
||||
foreach (var (key, value) in _resolveTypes.Where(p => !_services.ContainsKey(p.Key)))
|
||||
while(_pendingResolves.Count > 0)
|
||||
{
|
||||
Type key = _pendingResolves.Dequeue();
|
||||
var value = _resolveTypes[key];
|
||||
|
||||
// Find a potential dupe by checking other registered types that have already been instantiated that have the same instance type.
|
||||
// Can't catch ourselves because we're not instantiated.
|
||||
// Ones that aren't yet instantiated are about to be and will find us instead.
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Robust.Shared.IoC
|
||||
/// or if an already instantiated interface (by <see cref="DependencyCollection.BuildGraph"/>) is attempting to be overwritten.
|
||||
/// </exception>
|
||||
void Register<TInterface, TImplementation>(bool overwrite = false)
|
||||
where TImplementation : class, TInterface, new();
|
||||
where TImplementation : class, TInterface;
|
||||
|
||||
/// <summary>
|
||||
/// Registers an interface to an implementation, to make it accessible to <see cref="DependencyCollection.Resolve{T}"/>
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Robust.Shared.IoC
|
||||
/// or if an already instantiated interface (by <see cref="BuildGraph"/>) is attempting to be overwritten.
|
||||
/// </exception>
|
||||
public static void Register<TInterface, TImplementation>(bool overwrite = false)
|
||||
where TImplementation : class, TInterface, new()
|
||||
where TImplementation : class, TInterface
|
||||
{
|
||||
DebugTools.Assert(_container.IsValueCreated, NoContextAssert);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public struct MapId : IEquatable<MapId>
|
||||
public readonly struct MapId : IEquatable<MapId>
|
||||
{
|
||||
public static readonly MapId Nullspace = new(0);
|
||||
|
||||
|
||||
@@ -177,10 +177,10 @@ namespace Robust.Shared.Network
|
||||
{
|
||||
get
|
||||
{
|
||||
var sentPackets = 0;
|
||||
var sentBytes = 0;
|
||||
var recvPackets = 0;
|
||||
var recvBytes = 0;
|
||||
var sentPackets = 0L;
|
||||
var sentBytes = 0L;
|
||||
var recvPackets = 0L;
|
||||
var recvBytes = 0L;
|
||||
|
||||
foreach (var peer in _netPeers)
|
||||
{
|
||||
@@ -1155,24 +1155,24 @@ namespace Robust.Shared.Network
|
||||
/// <summary>
|
||||
/// Total sent bytes.
|
||||
/// </summary>
|
||||
public readonly int SentBytes;
|
||||
public readonly long SentBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Total received bytes.
|
||||
/// </summary>
|
||||
public readonly int ReceivedBytes;
|
||||
public readonly long ReceivedBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Total sent packets.
|
||||
/// </summary>
|
||||
public readonly int SentPackets;
|
||||
public readonly long SentPackets;
|
||||
|
||||
/// <summary>
|
||||
/// Total received packets.
|
||||
/// </summary>
|
||||
public readonly int ReceivedPackets;
|
||||
public readonly long ReceivedPackets;
|
||||
|
||||
public NetworkStats(int sentBytes, int receivedBytes, int sentPackets, int receivedPackets)
|
||||
public NetworkStats(long sentBytes, long receivedBytes, long sentPackets, long receivedPackets)
|
||||
{
|
||||
SentBytes = sentBytes;
|
||||
ReceivedBytes = receivedBytes;
|
||||
|
||||
@@ -915,6 +915,41 @@ namespace Robust.Shared.Physics
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void FastQueryCallback(ref T userData);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public void FastQuery(ref Box2 aabb, FastQueryCallback callback)
|
||||
{
|
||||
var stack = new GrowableStack<Proxy>(stackalloc Proxy[256]);
|
||||
stack.Push(_root);
|
||||
|
||||
ref var baseRef = ref _nodes[0];
|
||||
while (stack.GetCount() != 0)
|
||||
{
|
||||
var nodeId = stack.Pop();
|
||||
if (nodeId == Proxy.Free)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip bounds check with Unsafe.Add().
|
||||
ref var node = ref Unsafe.Add(ref baseRef, nodeId);
|
||||
ref var nodeAabb = ref node.Aabb;
|
||||
if (nodeAabb.Intersects(aabb))
|
||||
{
|
||||
if (node.IsLeaf)
|
||||
{
|
||||
callback(ref node.UserData);
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.Push(node.Child1);
|
||||
stack.Push(node.Child2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly RayQueryCallback<RayQueryCallback> EasyRayQueryCallback =
|
||||
(ref RayQueryCallback callback, Proxy proxy, in Vector2 hitPos, float distance) => callback(proxy, hitPos, distance);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Initially based on Box2D by Erin Catto, license follows;
|
||||
*
|
||||
* Copyright (c) 2009 Erin Catto http://www.box2d.org
|
||||
@@ -72,7 +72,7 @@ namespace Robust.Shared.Physics
|
||||
|
||||
// avoids "Collection was modified; enumeration operation may not execute."
|
||||
private Dictionary<T, Proxy> _nodeLookup;
|
||||
private readonly B2DynamicTree<T> _b2Tree;
|
||||
public readonly B2DynamicTree<T> _b2Tree;
|
||||
|
||||
public DynamicTree(ExtractAabbDelegate extractAabbFunc, IEqualityComparer<T>? comparer = null, float aabbExtendSize = 1f / 32, int capacity = 256, Func<int, int>? growthFunc = null)
|
||||
{
|
||||
|
||||
@@ -12,12 +12,5 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
bodyA.AddJoint(joint);
|
||||
return joint;
|
||||
}
|
||||
|
||||
public static SlothJoint CreateSlothJoint(this PhysicsComponent bodyA, PhysicsComponent bodyB)
|
||||
{
|
||||
var joint = new SlothJoint(bodyA, bodyB);
|
||||
bodyA.AddJoint(joint);
|
||||
return joint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public class SlothJoint : Joint
|
||||
{
|
||||
// Solver temp
|
||||
[NonSerialized] private int _indexA;
|
||||
[NonSerialized] private int _indexB;
|
||||
[NonSerialized] private Vector2 _rA;
|
||||
[NonSerialized] private Vector2 _rB;
|
||||
[NonSerialized] private Vector2 _localCenterA;
|
||||
[NonSerialized] private Vector2 _localCenterB;
|
||||
[NonSerialized] private float _invMassA;
|
||||
[NonSerialized] private float _invMassB;
|
||||
[NonSerialized] private float _invIA;
|
||||
[NonSerialized] private float _invIB;
|
||||
[NonSerialized] private float _mass;
|
||||
[NonSerialized] private float _currentLength;
|
||||
[NonSerialized] private float _softMass;
|
||||
|
||||
public SlothJoint(PhysicsComponent bodyA, PhysicsComponent bodyB) : base(bodyA, bodyB)
|
||||
{
|
||||
}
|
||||
|
||||
public override JointType JointType => JointType.Distance;
|
||||
|
||||
[field:NonSerialized]
|
||||
public override Vector2 WorldAnchorA { get; set; }
|
||||
[field:NonSerialized]
|
||||
public override Vector2 WorldAnchorB { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MaxLength
|
||||
{
|
||||
get => _maxLength;
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseTo(value, _maxLength)) return;
|
||||
|
||||
_maxLength = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
private float _maxLength;
|
||||
|
||||
public override Vector2 GetReactionForce(float invDt)
|
||||
{
|
||||
// TODO: Need break force
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
public override float GetReactionTorque(float invDt)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
internal override void InitVelocityConstraints(SolverData data)
|
||||
{
|
||||
_indexA = BodyA.IslandIndex;
|
||||
_indexB = BodyB.IslandIndex;
|
||||
_localCenterA = Vector2.Zero; //BodyA->m_sweep.localCenter;
|
||||
_localCenterB = Vector2.Zero; //BodyB->m_sweep.localCenter;
|
||||
_invMassA = BodyA.InvMass;
|
||||
_invMassB = BodyB.InvMass;
|
||||
_invIA = BodyA.InvI;
|
||||
_invIB = BodyB.InvI;
|
||||
|
||||
_currentLength = (data.Positions[_indexA] - data.Positions[_indexB]).Length;
|
||||
|
||||
_softMass = _mass;
|
||||
}
|
||||
|
||||
internal override void SolveVelocityConstraints(SolverData data)
|
||||
{
|
||||
if (_currentLength < _maxLength) return;
|
||||
|
||||
var posA = data.Positions[_indexA];
|
||||
var posB = data.Positions[_indexB];
|
||||
|
||||
var vA = data.LinearVelocities[_indexA];
|
||||
float wA = data.AngularVelocities[_indexA];
|
||||
var vB = data.LinearVelocities[_indexB];
|
||||
float wB = data.AngularVelocities[_indexB];
|
||||
|
||||
var correctionDistance = _maxLength - _currentLength;
|
||||
|
||||
//var P = _u * impulse;
|
||||
//vA -= P * _invMassA;
|
||||
//wA -= _invIA * Vector2.Cross(_rA, P);
|
||||
//vB += P * _invMassB;
|
||||
//wB += _invIB * Vector2.Cross(_rB, P);
|
||||
}
|
||||
|
||||
internal override bool SolvePositionConstraints(SolverData data)
|
||||
{
|
||||
if (_currentLength < _maxLength) return true;
|
||||
|
||||
var posA = data.Positions[_indexA];
|
||||
var posB = data.Positions[_indexB];
|
||||
|
||||
var vA = data.LinearVelocities[_indexA];
|
||||
float wA = data.AngularVelocities[_indexA];
|
||||
var vB = data.LinearVelocities[_indexB];
|
||||
float wB = data.AngularVelocities[_indexB];
|
||||
|
||||
var correctionDistance = _maxLength - _currentLength;
|
||||
|
||||
data.Positions[_indexB] -= correctionDistance;
|
||||
|
||||
//var P = _u * impulse;
|
||||
//vA -= P * _invMassA;
|
||||
//wA -= _invIA * Vector2.Cross(_rA, P);
|
||||
//vB += P * _invMassB;
|
||||
//wB += _invIB * Vector2.Cross(_rB, P);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,14 @@ namespace Robust.Shared.Prototypes
|
||||
/// </exception>
|
||||
IEnumerable<IPrototype> EnumeratePrototypes(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain variant.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the variant of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<IPrototype> EnumeratePrototypes(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Index for a <see cref="IPrototype"/> by ID.
|
||||
/// </summary>
|
||||
@@ -64,8 +72,61 @@ namespace Robust.Shared.Prototypes
|
||||
/// </exception>
|
||||
IPrototype Index(Type type, string id);
|
||||
|
||||
bool HasIndex<T>(string id) where T : IPrototype;
|
||||
bool TryIndex<T>(string id, [NotNullWhen(true)] out T? prototype) where T : IPrototype;
|
||||
/// <summary>
|
||||
/// Returns whether a prototype of type <typeparamref name="T"/> with the specified <param name="id"/> exists.
|
||||
/// </summary>
|
||||
bool HasIndex<T>(string id) where T : class, IPrototype;
|
||||
bool TryIndex<T>(string id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype;
|
||||
bool TryIndex(Type type, string id, [NotNullWhen(true)] out IPrototype? prototype);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a prototype variant <param name="variant"/> exists.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <returns>Whether the prototype variant exists.</returns>
|
||||
bool HasVariant(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Type for a prototype variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <returns>The specified prototype Type.</returns>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown when the specified prototype variant isn't registered or doesn't exist.
|
||||
/// </exception>
|
||||
Type GetVariantType(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the Type for a prototype variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <param name="prototype">The specified prototype Type, or null.</param>
|
||||
/// <returns>Whether the prototype type was found and <see cref="prototype"/> isn't null.</returns>
|
||||
bool TryGetVariantType(string variant, [NotNullWhen(true)] out Type? prototype);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="variant"></param>
|
||||
/// <returns></returns>
|
||||
bool TryGetVariantFrom(Type type, [NotNullWhen(true)] out string? variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="prototype">The prototype in question.</param>
|
||||
/// <param name="variant">Identifier for the prototype variant, or null.</param>
|
||||
/// <returns>Whether the prototype variant was successfully retrieved.</returns>
|
||||
bool TryGetVariantFrom(IPrototype prototype, [NotNullWhen(true)] out string? variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant, or null.</param>
|
||||
/// <typeparam name="T">The prototype in question.</typeparam>
|
||||
/// <returns>Whether the prototype variant was successfully retrieved.</returns>
|
||||
bool TryGetVariantFrom<T>([NotNullWhen(true)] out string? variant) where T : class, IPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// Load prototypes from files in a directory, recursively.
|
||||
@@ -187,6 +248,11 @@ namespace Robust.Shared.Prototypes
|
||||
return prototypes[type].Values;
|
||||
}
|
||||
|
||||
public IEnumerable<IPrototype> EnumeratePrototypes(string variant)
|
||||
{
|
||||
return EnumeratePrototypes(GetVariantType(variant));
|
||||
}
|
||||
|
||||
public T Index<T>(string id) where T : class, IPrototype
|
||||
{
|
||||
if (!_hasEverBeenReloaded)
|
||||
@@ -586,7 +652,7 @@ namespace Robust.Shared.Prototypes
|
||||
return changedPrototypes;
|
||||
}
|
||||
|
||||
public bool HasIndex<T>(string id) where T : IPrototype
|
||||
public bool HasIndex<T>(string id) where T : class, IPrototype
|
||||
{
|
||||
if (!prototypes.TryGetValue(typeof(T), out var index))
|
||||
{
|
||||
@@ -596,16 +662,74 @@ namespace Robust.Shared.Prototypes
|
||||
return index.ContainsKey(id);
|
||||
}
|
||||
|
||||
public bool TryIndex<T>(string id, [NotNullWhen(true)] out T? prototype) where T : IPrototype
|
||||
public bool TryIndex<T>(string id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype
|
||||
{
|
||||
if (!prototypes.TryGetValue(typeof(T), out var index))
|
||||
var returned = TryIndex(typeof(T), id, out var proto);
|
||||
prototype = (proto ?? null) as T;
|
||||
return returned;
|
||||
}
|
||||
|
||||
public bool TryIndex(Type type, string id, [NotNullWhen(true)] out IPrototype? prototype)
|
||||
{
|
||||
if (!prototypes.TryGetValue(type, out var index))
|
||||
{
|
||||
throw new UnknownPrototypeException(id);
|
||||
}
|
||||
|
||||
var returned = index.TryGetValue(id, out var uncast);
|
||||
prototype = (T) uncast!;
|
||||
return returned;
|
||||
return index.TryGetValue(id, out prototype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasVariant(string variant)
|
||||
{
|
||||
return prototypeTypes.ContainsKey(variant);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type GetVariantType(string variant)
|
||||
{
|
||||
return prototypeTypes[variant];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetVariantType(string variant, [NotNullWhen(true)] out Type? prototype)
|
||||
{
|
||||
return prototypeTypes.TryGetValue(variant, out prototype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetVariantFrom(Type type, [NotNullWhen(true)] out string? variant)
|
||||
{
|
||||
variant = null;
|
||||
|
||||
// If the type doesn't implement IPrototype, this fails.
|
||||
if (!(typeof(IPrototype).IsAssignableFrom(type)))
|
||||
return false;
|
||||
|
||||
var attribute = (PrototypeAttribute?) Attribute.GetCustomAttribute(type, typeof(PrototypeAttribute));
|
||||
|
||||
// If the prototype type doesn't have the attribute, this fails.
|
||||
if (attribute == null)
|
||||
return false;
|
||||
|
||||
// If the variant isn't registered, this fails.
|
||||
if (!HasVariant(attribute.Type))
|
||||
return false;
|
||||
|
||||
variant = attribute.Type;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetVariantFrom<T>([NotNullWhen(true)] out string? variant) where T : class, IPrototype
|
||||
{
|
||||
return TryGetVariantFrom(typeof(T), out variant);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetVariantFrom(IPrototype prototype, [NotNullWhen(true)] out string? variant)
|
||||
{
|
||||
return TryGetVariantFrom(prototype.GetType(), out variant);
|
||||
}
|
||||
|
||||
public void RegisterIgnore(string name)
|
||||
|
||||
@@ -46,7 +46,11 @@ namespace Robust.Shared.Serialization.Manager
|
||||
/// </returns>
|
||||
ValidationNode ValidateNode<T>(DataNode node, ISerializationContext? context = null);
|
||||
|
||||
ValidationNode ValidateNodeWithCustomTypeSerializer(Type type, Type typeSerializer, DataNode node, ISerializationContext? context = null);
|
||||
ValidationNode ValidateNodeWith(Type type, Type typeSerializer, DataNode node, ISerializationContext? context = null);
|
||||
|
||||
ValidationNode ValidateNodeWith<TType, TSerializer, TNode>(TNode node, ISerializationContext? context = null)
|
||||
where TSerializer : ITypeValidator<TType, TNode>
|
||||
where TNode : DataNode;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Shared.Serialization.Manager.Result
|
||||
{
|
||||
|
||||
@@ -156,7 +156,7 @@ namespace Robust.Shared.Serialization.Manager
|
||||
|
||||
var keyValidated = serializationManager.ValidateNode(typeof(string), key, context);
|
||||
ValidationNode valValidated = field.Attribute.CustomTypeSerializer != null
|
||||
? serializationManager.ValidateNodeWithCustomTypeSerializer(field.FieldType,
|
||||
? serializationManager.ValidateNodeWith(field.FieldType,
|
||||
field.Attribute.CustomTypeSerializer, val, context)
|
||||
: serializationManager.ValidateNode(field.FieldType, val, context);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Manager.Result;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Serialization.Manager
|
||||
@@ -200,7 +201,7 @@ namespace Robust.Shared.Serialization.Manager
|
||||
return ValidateNode(typeof(T), node, context);
|
||||
}
|
||||
|
||||
public ValidationNode ValidateNodeWithCustomTypeSerializer(Type type, Type typeSerializer, DataNode node,
|
||||
public ValidationNode ValidateNodeWith(Type type, Type typeSerializer, DataNode node,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
var method =
|
||||
@@ -209,6 +210,14 @@ namespace Robust.Shared.Serialization.Manager
|
||||
return (ValidationNode)method.Invoke(this, new object?[] {node, context})!;
|
||||
}
|
||||
|
||||
public ValidationNode ValidateNodeWith<TType, TSerializer, TNode>(TNode node,
|
||||
ISerializationContext? context = null)
|
||||
where TSerializer : ITypeValidator<TType, TNode>
|
||||
where TNode: DataNode
|
||||
{
|
||||
return ValidateNodeWith(typeof(TType), typeof(TSerializer), node, context);
|
||||
}
|
||||
|
||||
public DeserializationResult CreateDataDefinition<T>(DeserializedFieldEntry[] fields, bool skipHook = false)
|
||||
where T : notnull, new()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Result;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List
|
||||
{
|
||||
public partial class PrototypeIdListSerializer<T> :
|
||||
ITypeSerializer<ImmutableList<string>, SequenceDataNode>
|
||||
where T : class, IPrototype
|
||||
{
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, SequenceDataNode node,
|
||||
IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
{
|
||||
return ValidateInternal(serializationManager, node, dependencies, context);
|
||||
}
|
||||
|
||||
public DeserializationResult Read(ISerializationManager serializationManager, SequenceDataNode node,
|
||||
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
|
||||
{
|
||||
var builder = ImmutableList.CreateBuilder<string>();
|
||||
var mappings = new List<DeserializationResult>();
|
||||
|
||||
foreach (var dataNode in node.Sequence)
|
||||
{
|
||||
var result = _prototypeSerializer.Read(
|
||||
serializationManager,
|
||||
(ValueDataNode) dataNode,
|
||||
dependencies,
|
||||
skipHook,
|
||||
context);
|
||||
|
||||
builder.Add((string) result.RawValue!);
|
||||
mappings.Add(result);
|
||||
}
|
||||
|
||||
return new DeserializedCollection<ImmutableList<string>, string>(builder.ToImmutable(), mappings,
|
||||
ImmutableList.CreateRange);
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serializationManager, ImmutableList<string> value, bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
return WriteInternal(serializationManager, value, alwaysWrite, context);
|
||||
}
|
||||
|
||||
public ImmutableList<string> Copy(ISerializationManager serializationManager, ImmutableList<string> source, ImmutableList<string> target,
|
||||
bool skipHook, ISerializationContext? context = null)
|
||||
{
|
||||
return ImmutableList.CreateRange(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Result;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List
|
||||
{
|
||||
public partial class PrototypeIdListSerializer<T> :
|
||||
ITypeSerializer<IReadOnlyCollection<string>, SequenceDataNode>
|
||||
where T : class, IPrototype
|
||||
{
|
||||
ValidationNode ITypeValidator<IReadOnlyCollection<string>, SequenceDataNode>.Validate(
|
||||
ISerializationManager serializationManager,
|
||||
SequenceDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
return ValidateInternal(serializationManager, node, dependencies, context);
|
||||
}
|
||||
|
||||
DeserializationResult ITypeReader<IReadOnlyCollection<string>, SequenceDataNode>.Read(
|
||||
ISerializationManager serializationManager,
|
||||
SequenceDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
bool skipHook,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
var list = new List<string>();
|
||||
var mappings = new List<DeserializationResult>();
|
||||
|
||||
foreach (var dataNode in node.Sequence)
|
||||
{
|
||||
var result = _prototypeSerializer.Read(
|
||||
serializationManager,
|
||||
(ValueDataNode) dataNode,
|
||||
dependencies,
|
||||
skipHook,
|
||||
context);
|
||||
|
||||
list.Add((string) result.RawValue!);
|
||||
mappings.Add(result);
|
||||
}
|
||||
|
||||
return new DeserializedCollection<List<string>, string>(list, mappings,
|
||||
elements => new List<string>(elements));
|
||||
}
|
||||
|
||||
DataNode ITypeWriter<IReadOnlyCollection<string>>.Write(
|
||||
ISerializationManager serializationManager,
|
||||
IReadOnlyCollection<string> value,
|
||||
bool alwaysWrite,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
return WriteInternal(serializationManager, value, alwaysWrite, context);
|
||||
}
|
||||
|
||||
IReadOnlyCollection<string> ITypeCopier<IReadOnlyCollection<string>>.Copy(
|
||||
ISerializationManager serializationManager,
|
||||
IReadOnlyCollection<string> source,
|
||||
IReadOnlyCollection<string> target,
|
||||
bool skipHook,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
return new List<string>(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Result;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List
|
||||
{
|
||||
public partial class PrototypeIdListSerializer<T> : ITypeSerializer<IReadOnlyList<string>, SequenceDataNode>
|
||||
where T : class, IPrototype
|
||||
{
|
||||
DataNode ITypeWriter<IReadOnlyList<string>>.Write(
|
||||
ISerializationManager serializationManager,
|
||||
IReadOnlyList<string> value,
|
||||
bool alwaysWrite,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
return WriteInternal(serializationManager, value, alwaysWrite, context);
|
||||
}
|
||||
|
||||
[MustUseReturnValue]
|
||||
IReadOnlyList<string> ITypeCopier<IReadOnlyList<string>>.Copy(
|
||||
ISerializationManager serializationManager,
|
||||
IReadOnlyList<string> source,
|
||||
IReadOnlyList<string> target,
|
||||
bool skipHook,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
return new List<string>(source);
|
||||
}
|
||||
|
||||
DeserializationResult ITypeReader<IReadOnlyList<string>, SequenceDataNode>.Read(
|
||||
ISerializationManager serializationManager,
|
||||
SequenceDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
bool skipHook,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
var list = new List<string>();
|
||||
var mappings = new List<DeserializationResult>();
|
||||
|
||||
foreach (var dataNode in node.Sequence)
|
||||
{
|
||||
var result = _prototypeSerializer.Read(
|
||||
serializationManager,
|
||||
(ValueDataNode) dataNode,
|
||||
dependencies,
|
||||
skipHook,
|
||||
context);
|
||||
|
||||
list.Add((string) result.RawValue!);
|
||||
mappings.Add(result);
|
||||
}
|
||||
|
||||
return new DeserializedCollection<IReadOnlyList<string>, string>(list, mappings,
|
||||
elements => new List<string>(elements));
|
||||
}
|
||||
|
||||
ValidationNode ITypeValidator<IReadOnlyList<string>, SequenceDataNode>.Validate(
|
||||
ISerializationManager serializationManager,
|
||||
SequenceDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
var list = new List<ValidationNode>();
|
||||
|
||||
foreach (var dataNode in node.Sequence)
|
||||
{
|
||||
if (dataNode is not ValueDataNode value)
|
||||
{
|
||||
list.Add(new ErrorNode(dataNode, $"Cannot cast node {dataNode} to ValueDataNode."));
|
||||
continue;
|
||||
}
|
||||
|
||||
list.Add(_prototypeSerializer.Validate(serializationManager, value, dependencies, context));
|
||||
}
|
||||
|
||||
return new ValidatedSequenceNode(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Result;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List
|
||||
{
|
||||
public partial class PrototypeIdListSerializer<T> : ITypeSerializer<List<string>, SequenceDataNode> where T : class, IPrototype
|
||||
{
|
||||
private readonly PrototypeIdSerializer<T> _prototypeSerializer = new();
|
||||
|
||||
private ValidationNode ValidateInternal(
|
||||
ISerializationManager serializationManager,
|
||||
SequenceDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
var list = new List<ValidationNode>();
|
||||
|
||||
foreach (var dataNode in node.Sequence)
|
||||
{
|
||||
if (dataNode is not ValueDataNode value)
|
||||
{
|
||||
list.Add(new ErrorNode(dataNode, $"Cannot cast node {dataNode} to ValueDataNode."));
|
||||
continue;
|
||||
}
|
||||
|
||||
list.Add(_prototypeSerializer.Validate(serializationManager, value, dependencies, context));
|
||||
}
|
||||
|
||||
return new ValidatedSequenceNode(list);
|
||||
}
|
||||
|
||||
private DataNode WriteInternal(
|
||||
ISerializationManager serializationManager,
|
||||
IEnumerable<string> value,
|
||||
bool alwaysWrite,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
var list = new List<DataNode>();
|
||||
|
||||
foreach (var str in value)
|
||||
{
|
||||
list.Add(_prototypeSerializer.Write(serializationManager, str, alwaysWrite, context));
|
||||
}
|
||||
|
||||
return new SequenceDataNode(list);
|
||||
}
|
||||
|
||||
ValidationNode ITypeValidator<List<string>, SequenceDataNode>.Validate(
|
||||
ISerializationManager serializationManager,
|
||||
SequenceDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
return ValidateInternal(serializationManager, node, dependencies, context);
|
||||
}
|
||||
|
||||
DeserializationResult ITypeReader<List<string>, SequenceDataNode>.Read(
|
||||
ISerializationManager serializationManager,
|
||||
SequenceDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
bool skipHook,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
var list = new List<string>();
|
||||
var mappings = new List<DeserializationResult>();
|
||||
|
||||
foreach (var dataNode in node.Sequence)
|
||||
{
|
||||
var result = _prototypeSerializer.Read(
|
||||
serializationManager,
|
||||
(ValueDataNode) dataNode,
|
||||
dependencies,
|
||||
skipHook,
|
||||
context);
|
||||
|
||||
list.Add((string) result.RawValue!);
|
||||
mappings.Add(result);
|
||||
}
|
||||
|
||||
return new DeserializedCollection<List<string>, string>(list, mappings,
|
||||
elements => new List<string>(elements));
|
||||
}
|
||||
|
||||
DataNode ITypeWriter<List<string>>.Write(
|
||||
ISerializationManager serializationManager,
|
||||
List<string> value,
|
||||
bool alwaysWrite,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
return WriteInternal(serializationManager, value, alwaysWrite, context);
|
||||
}
|
||||
|
||||
List<string> ITypeCopier<List<string>>.Copy(
|
||||
ISerializationManager serializationManager,
|
||||
List<string> source,
|
||||
List<string> target,
|
||||
bool skipHook,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
return new();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,14 @@ using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom
|
||||
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype
|
||||
{
|
||||
public class PrototypeIdSerializer<TPrototype> : ITypeSerializer<string, ValueDataNode> where TPrototype : IPrototype
|
||||
public class PrototypeIdSerializer<TPrototype> : ITypeSerializer<string, ValueDataNode> where TPrototype : class, IPrototype
|
||||
{
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,
|
||||
IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
{
|
||||
return IoCManager.Resolve<IPrototypeManager>().HasIndex<TPrototype>(node.Value)
|
||||
return dependencies.Resolve<IPrototypeManager>().HasIndex<TPrototype>(node.Value)
|
||||
? new ValidatedValueNode(node)
|
||||
: new ErrorNode(node, $"PrototypeID {node.Value} for type {typeof(TPrototype)} not found");
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Result;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set
|
||||
{
|
||||
public class PrototypeIdHashSetSerializer<TPrototype> : ITypeSerializer<HashSet<string>, SequenceDataNode> where TPrototype : class, IPrototype
|
||||
{
|
||||
private readonly PrototypeIdSerializer<TPrototype> _prototypeSerializer = new();
|
||||
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, SequenceDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
{
|
||||
var list = new List<ValidationNode>();
|
||||
|
||||
foreach (var dataNode in node.Sequence)
|
||||
{
|
||||
if (dataNode is not ValueDataNode value)
|
||||
{
|
||||
list.Add(new ErrorNode(dataNode, $"Cannot cast node {dataNode} to ValueDataNode."));
|
||||
continue;
|
||||
}
|
||||
|
||||
list.Add(_prototypeSerializer.Validate(serializationManager, value, dependencies, context));
|
||||
}
|
||||
|
||||
return new ValidatedSequenceNode(list);
|
||||
}
|
||||
|
||||
public DeserializationResult Read(ISerializationManager serializationManager, SequenceDataNode node, IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
|
||||
{
|
||||
var set = new HashSet<string>();
|
||||
var mappings = new List<DeserializationResult>();
|
||||
|
||||
foreach (var dataNode in node.Sequence)
|
||||
{
|
||||
var result = _prototypeSerializer.Read(
|
||||
serializationManager,
|
||||
(ValueDataNode) dataNode,
|
||||
dependencies,
|
||||
skipHook,
|
||||
context);
|
||||
|
||||
set.Add((string) result.RawValue!);
|
||||
mappings.Add(result);
|
||||
}
|
||||
|
||||
return new DeserializedCollection<HashSet<string>, string>(set, mappings,
|
||||
elements => new HashSet<string>(elements));
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serializationManager, HashSet<string> value, bool alwaysWrite = false, ISerializationContext? context = null)
|
||||
{
|
||||
var list = new List<DataNode>();
|
||||
|
||||
foreach (var str in value)
|
||||
{
|
||||
list.Add(_prototypeSerializer.Write(serializationManager, str, alwaysWrite, context));
|
||||
}
|
||||
|
||||
return new SequenceDataNode(list);
|
||||
}
|
||||
|
||||
public HashSet<string> Copy(ISerializationManager serializationManager, HashSet<string> source, HashSet<string> target, bool skipHook, ISerializationContext? context = null)
|
||||
{
|
||||
return new(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,6 +232,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic
|
||||
return list;
|
||||
}
|
||||
|
||||
[MustUseReturnValue]
|
||||
public ImmutableList<T> Copy(ISerializationManager serializationManager, ImmutableList<T> source,
|
||||
ImmutableList<T> target, bool skipHook, ISerializationContext? context = null)
|
||||
{
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
systems.GetEntitySystem<TestSystemC>().Counter = counter;
|
||||
systems.GetEntitySystem<TestSystemD>().Counter = counter;
|
||||
|
||||
systems.Update(1);
|
||||
systems.TickUpdate(1);
|
||||
|
||||
Assert.That(counter.X, Is.EqualTo(4));
|
||||
|
||||
|
||||
@@ -36,6 +36,18 @@ namespace Robust.UnitTesting.Shared.IoC
|
||||
tester.Test();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IoCTestConstructorInjection()
|
||||
{
|
||||
IoCManager.Register<TestFieldInjection, TestFieldInjection>();
|
||||
IoCManager.Register<TestConstructorInjection, TestConstructorInjection>();
|
||||
IoCManager.BuildGraph();
|
||||
|
||||
var tester = IoCManager.Resolve<TestConstructorInjection>();
|
||||
|
||||
Assert.That(tester.FieldInjection, Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IoCTestBasic()
|
||||
{
|
||||
@@ -192,6 +204,16 @@ namespace Robust.UnitTesting.Shared.IoC
|
||||
}
|
||||
}
|
||||
|
||||
public class TestConstructorInjection
|
||||
{
|
||||
public TestFieldInjection FieldInjection { get; }
|
||||
|
||||
public TestConstructorInjection(TestFieldInjection fieldInjection)
|
||||
{
|
||||
FieldInjection = fieldInjection;
|
||||
}
|
||||
}
|
||||
|
||||
public class TestUnregisteredInjection
|
||||
{
|
||||
[Dependency]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -8,7 +8,6 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Timing;
|
||||
using MapGrid = Robust.Shared.Map.MapGrid;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Map
|
||||
@@ -159,7 +158,6 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
private static IMapGridInternal MapGridFactory(GridId id)
|
||||
{
|
||||
var entMan = (ServerEntityManager)IoCManager.Resolve<IEntityManager>();
|
||||
entMan.CullDeletionHistory(GameTick.MaxValue);
|
||||
|
||||
var mapId = new MapId(5);
|
||||
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
// ReSharper disable AccessToStaticMemberViaDerivedType
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers.Custom.Prototype
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(PrototypeIdListSerializer<>))]
|
||||
public class PrototypeIdListSerializerTest : TypeSerializerTest
|
||||
{
|
||||
private static readonly string TestEntityId = $"{nameof(PrototypeIdListSerializerTest)}Dummy";
|
||||
|
||||
private static readonly string TestInvalidEntityId = $"{nameof(PrototypeIdListSerializerTest)}DummyInvalid";
|
||||
|
||||
private static readonly string Prototypes = $@"
|
||||
- type: entity
|
||||
id: {TestEntityId}";
|
||||
|
||||
private static readonly string DataString = $@"
|
||||
entitiesList:
|
||||
- {TestEntityId}
|
||||
entitiesReadOnlyList:
|
||||
- {TestEntityId}
|
||||
entitiesReadOnlyCollection:
|
||||
- {TestEntityId}
|
||||
entitiesImmutableList:
|
||||
- {TestEntityId}";
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetUp()
|
||||
{
|
||||
IoCManager.Resolve<IPrototypeManager>().LoadString(Prototypes);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SerializationTest()
|
||||
{
|
||||
var definition = new PrototypeIdListSerializerTestDataDefinition
|
||||
{
|
||||
EntitiesList = {TestEntityId},
|
||||
EntitiesReadOnlyList = new List<string>() {TestEntityId},
|
||||
EntitiesReadOnlyCollection = new List<string>() {TestEntityId},
|
||||
EntitiesImmutableList = ImmutableList.Create(TestEntityId)
|
||||
};
|
||||
var node = Serialization.WriteValueAs<MappingDataNode>(definition);
|
||||
|
||||
Assert.That(node.Children.Count, Is.EqualTo(4));
|
||||
|
||||
var entities = node.Cast<SequenceDataNode>("entitiesList");
|
||||
Assert.That(entities.Sequence.Count, Is.EqualTo(1));
|
||||
Assert.That(entities.Cast<ValueDataNode>(0).Value, Is.EqualTo(TestEntityId));
|
||||
|
||||
var entitiesReadOnlyList = node.Cast<SequenceDataNode>("entitiesReadOnlyList");
|
||||
Assert.That(entitiesReadOnlyList.Sequence.Count, Is.EqualTo(1));
|
||||
Assert.That(entitiesReadOnlyList.Cast<ValueDataNode>(0).Value, Is.EqualTo(TestEntityId));
|
||||
|
||||
var entitiesReadOnlyCollection = node.Cast<SequenceDataNode>("entitiesReadOnlyCollection");
|
||||
Assert.That(entitiesReadOnlyCollection.Sequence.Count, Is.EqualTo(1));
|
||||
Assert.That(entitiesReadOnlyCollection.Cast<ValueDataNode>(0).Value, Is.EqualTo(TestEntityId));
|
||||
|
||||
var entitiesImmutableList = node.Cast<SequenceDataNode>("entitiesImmutableList");
|
||||
Assert.That(entitiesImmutableList.Sequence.Count, Is.EqualTo(1));
|
||||
Assert.That(entitiesImmutableList.Cast<ValueDataNode>(0).Value, Is.EqualTo(TestEntityId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeserializationTest()
|
||||
{
|
||||
var stream = new YamlStream();
|
||||
stream.Load(new StringReader(DataString));
|
||||
|
||||
var node = stream.Documents[0].RootNode.ToDataNode();
|
||||
var definition = Serialization.ReadValue<PrototypeIdListSerializerTestDataDefinition>(node);
|
||||
|
||||
Assert.NotNull(definition);
|
||||
|
||||
Assert.That(definition!.EntitiesList.Count, Is.EqualTo(1));
|
||||
Assert.That(definition.EntitiesList[0], Is.EqualTo(TestEntityId));
|
||||
|
||||
Assert.That(definition!.EntitiesReadOnlyList.Count, Is.EqualTo(1));
|
||||
Assert.That(definition.EntitiesReadOnlyList[0], Is.EqualTo(TestEntityId));
|
||||
|
||||
Assert.That(definition!.EntitiesReadOnlyCollection.Count, Is.EqualTo(1));
|
||||
Assert.That(definition.EntitiesReadOnlyCollection.Single(), Is.EqualTo(TestEntityId));
|
||||
|
||||
Assert.That(definition!.EntitiesImmutableList.Count, Is.EqualTo(1));
|
||||
Assert.That(definition.EntitiesImmutableList[0], Is.EqualTo(TestEntityId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ValidationValidTest()
|
||||
{
|
||||
var validSequence = new SequenceDataNode(TestEntityId);
|
||||
|
||||
var validations = Serialization.ValidateNodeWith<
|
||||
List<string>,
|
||||
PrototypeIdListSerializer<EntityPrototype>,
|
||||
SequenceDataNode>(validSequence);
|
||||
Assert.True(validations.Valid);
|
||||
|
||||
validations = Serialization.ValidateNodeWith<
|
||||
IReadOnlyList<string>,
|
||||
PrototypeIdListSerializer<EntityPrototype>,
|
||||
SequenceDataNode>(validSequence);
|
||||
Assert.True(validations.Valid);
|
||||
|
||||
validations = Serialization.ValidateNodeWith<
|
||||
IReadOnlyCollection<string>,
|
||||
PrototypeIdListSerializer<EntityPrototype>,
|
||||
SequenceDataNode>(validSequence);
|
||||
Assert.True(validations.Valid);
|
||||
|
||||
validations = Serialization.ValidateNodeWith<
|
||||
ImmutableList<string>,
|
||||
PrototypeIdListSerializer<EntityPrototype>,
|
||||
SequenceDataNode>(validSequence);
|
||||
Assert.True(validations.Valid);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ValidationInvalidTest()
|
||||
{
|
||||
var invalidSequence = new SequenceDataNode(TestInvalidEntityId);
|
||||
|
||||
var validations = Serialization.ValidateNodeWith<
|
||||
List<string>,
|
||||
PrototypeIdListSerializer<EntityPrototype>,
|
||||
SequenceDataNode>(invalidSequence);
|
||||
Assert.False(validations.Valid);
|
||||
|
||||
validations = Serialization.ValidateNodeWith<
|
||||
IReadOnlyList<string>,
|
||||
PrototypeIdListSerializer<EntityPrototype>,
|
||||
SequenceDataNode>(invalidSequence);
|
||||
Assert.False(validations.Valid);
|
||||
|
||||
validations = Serialization.ValidateNodeWith<
|
||||
IReadOnlyCollection<string>,
|
||||
PrototypeIdListSerializer<EntityPrototype>,
|
||||
SequenceDataNode>(invalidSequence);
|
||||
Assert.False(validations.Valid);
|
||||
|
||||
validations = Serialization.ValidateNodeWith<
|
||||
ImmutableList<string>,
|
||||
PrototypeIdListSerializer<EntityPrototype>,
|
||||
SequenceDataNode>(invalidSequence);
|
||||
Assert.False(validations.Valid);
|
||||
}
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public class PrototypeIdListSerializerTestDataDefinition
|
||||
{
|
||||
[field: DataField("entitiesList", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public List<string> EntitiesList = new();
|
||||
|
||||
[field: DataField("entitiesReadOnlyList", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public IReadOnlyList<string> EntitiesReadOnlyList = new List<string>();
|
||||
|
||||
[field: DataField("entitiesReadOnlyCollection", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public IReadOnlyCollection<string> EntitiesReadOnlyCollection = new List<string>();
|
||||
|
||||
[field: DataField("entitiesImmutableList", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public ImmutableList<string> EntitiesImmutableList = ImmutableList<string>.Empty;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -9,13 +9,14 @@ namespace Robust.UnitTesting.Shared.Utility
|
||||
[TestOf(typeof(NullableHelper))]
|
||||
public class NullableHelper_Test
|
||||
{
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetup()
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
//initializing logmanager so it wont error out if nullablehelper logs an error
|
||||
IoCManager.InitThread();
|
||||
IoCManager.Register<ILogManager, LogManager>();
|
||||
IoCManager.BuildGraph();
|
||||
var collection = new DependencyCollection();
|
||||
collection.Register<ILogManager, LogManager>();
|
||||
collection.BuildGraph();
|
||||
IoCManager.InitThread(collection, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
Reference in New Issue
Block a user