Compare commits

...

10 Commits

Author SHA1 Message Date
DrSmugleaf
e2a4dcdff1 Fix comparing by name and not ID for entity prototype updates (#1578) 2021-02-20 02:41:51 +01:00
DrSmugleaf
68b0d7bf2e Fix not clearing the queue after hot reload (#1576) 2021-02-20 01:43:56 +01:00
DrSmugleaf
a9b163992b Fix and add test for PrototypeManager LoadString (#1574) 2021-02-20 01:43:43 +01:00
DrSmugleaf
2409965cf8 Fix build (#1575) 2021-02-20 00:15:43 +01:00
DrSmugleaf
eada37378a Add YAML hot reloading (#1571)
* Implement hot reloading for entity prototypes

* Implement automatic prototype hot-reloading

* Merge fixes

* Add yaml hot reloading and a message to notify the client

* Add reloading only changed files, remove cooldown, add retries and remove IPrototype

* Remove reload command

* Make the client listen for reloads instead and only when focused

* Fix errors

* Only queue a reload when the queue has items in it

* Make fails after 10 retries log instead of throw if reloading

Co-authored-by: Jackson Lewis <inquisitivepenguin@protonmail.com>
2021-02-20 00:02:04 +01:00
DrSmugleaf
0f1da1ba2a Add window focused callback to Clyde (#1573) 2021-02-19 22:10:03 +01:00
Acruid
e0cdcd228e Fixed Timer Namespace in unit tests. 2021-02-18 20:35:34 -08:00
Acruid
fdb5e014b5 PauseManager moved to Shared (#1553)
* Moved IPauseManager from server to shared.

* Moved ITimerManager from Timers to Timing.

* Added missing IConsoleHost to server/client RegisterIoC. Tests work again.
2021-02-18 20:12:26 -08:00
DrSmugleaf
cefcad775b Make addcomp and rmcomp give better feedback and case insensitive (#1570)
* Make addcomp and rmcomp case insensitive

* Fix up names

* Make addcomp and rmcomp give better feedback

* Make addcomp and rmcomp less fail happy
2021-02-18 20:01:14 -08:00
Vera Aguilera Puerto
e40feac1f1 Adds VV autorefresh when right-clicking the refresh button. (#1558)
* Adds VV autorefresh when right-clicking the refresh button.

* cancel token on close

* button tooltip
2021-02-18 00:14:11 -08:00
48 changed files with 964 additions and 424 deletions

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.Audio.Midi;
using Robust.Client.Console;
using Robust.Client.Debugging;
@@ -10,6 +10,7 @@ using Robust.Client.Input;
using Robust.Client.Map;
using Robust.Client.Placement;
using Robust.Client.Player;
using Robust.Client.Prototypes;
using Robust.Client.Reflection;
using Robust.Client.ResourceManagement;
using Robust.Client.State;
@@ -17,6 +18,7 @@ using Robust.Client.UserInterface;
using Robust.Client.Utility;
using Robust.Client.ViewVariables;
using Robust.Shared;
using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -33,7 +35,7 @@ namespace Robust.Client
{
SharedIoC.RegisterIoC();
IoCManager.Register<IPrototypeManager, PrototypeManager>();
IoCManager.Register<IPrototypeManager, ClientPrototypeManager>();
IoCManager.Register<IEntityManager, ClientEntityManager>();
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
@@ -59,6 +61,7 @@ namespace Robust.Client
IoCManager.Register<ILightManager, LightManager>();
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IFontManager, FontManager>();
IoCManager.Register<IFontManagerInternal, FontManager>();
IoCManager.Register<IMidiManager, MidiManager>();

View File

@@ -26,7 +26,6 @@ using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Timers;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -163,6 +162,7 @@ namespace Robust.Client
_serializer.Initialize();
_inputManager.Initialize();
_consoleHost.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
_prototypeManager.Resync();
_mapManager.Initialize();

View File

@@ -1,11 +0,0 @@
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(SharedIgnorePauseComponent))]
public sealed class IgnorePauseComponent : SharedIgnorePauseComponent
{
}
}

View File

@@ -55,6 +55,7 @@ namespace Robust.Client.Graphics.Clyde
private GLFWCallbacks.WindowSizeCallback _windowSizeCallback = default!;
private GLFWCallbacks.WindowContentScaleCallback _windowContentScaleCallback = default!;
private GLFWCallbacks.WindowIconifyCallback _windowIconifyCallback = default!;
private GLFWCallbacks.WindowFocusCallback _windowFocusCallback = default!;
private bool _glfwInitialized;
@@ -62,6 +63,7 @@ namespace Robust.Client.Graphics.Clyde
private Window* _glfwWindow;
private Vector2i _framebufferSize;
private bool _isFocused;
private Vector2i _windowSize;
private Vector2i _prevWindowSize;
private Vector2i _prevWindowPos;
@@ -74,6 +76,7 @@ namespace Robust.Client.Graphics.Clyde
// NOTE: in engine we pretend the framebuffer size is the screen size..
// For practical reasons like UI rendering.
public override Vector2i ScreenSize => _framebufferSize;
public override bool IsFocused => _isFocused;
public Vector2 DefaultWindowScale => _windowScale;
public Vector2 MouseScreenPosition => _lastMousePos;
@@ -231,6 +234,7 @@ namespace Robust.Client.Graphics.Clyde
GLFW.SetMouseButtonCallback(_glfwWindow, _mouseButtonCallback);
GLFW.SetWindowContentScaleCallback(_glfwWindow, _windowContentScaleCallback);
GLFW.SetWindowIconifyCallback(_glfwWindow, _windowIconifyCallback);
GLFW.SetWindowFocusCallback(_glfwWindow, _windowFocusCallback);
GLFW.MakeContextCurrent(_glfwWindow);
@@ -548,6 +552,19 @@ namespace Robust.Client.Graphics.Clyde
}
}
private void OnGlfwWindowFocus(Window* window, bool focused)
{
try
{
_isFocused = focused;
OnWindowFocused?.Invoke(new WindowFocusedEventArgs(focused));
}
catch (Exception e)
{
CatchCallbackException(e);
}
}
private void StoreCallbacks()
{
_errorCallback = OnGlfwError;
@@ -560,6 +577,7 @@ namespace Robust.Client.Graphics.Clyde
_windowSizeCallback = OnGlfwWindowSize;
_windowContentScaleCallback = OnGlfwWindownContentScale;
_windowIconifyCallback = OnGlfwWindowIconify;
_windowFocusCallback = OnGlfwWindowFocus;
}
public override void SetWindowTitle(string title)

View File

@@ -88,7 +88,7 @@ namespace Robust.Client.Graphics.Clyde
public override bool Initialize()
{
base.Initialize();
_configurationManager.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
if (!InitWindowing())
@@ -152,6 +152,8 @@ namespace Robust.Client.Graphics.Clyde
public override event Action<WindowResizedEventArgs>? OnWindowResized;
public override event Action<WindowFocusedEventArgs>? OnWindowFocused;
public void Screenshot(ScreenshotType type, Action<Image<Rgb24>> callback)
{
_queuedScreenshots.Add((type, callback));

View File

@@ -21,6 +21,7 @@ namespace Robust.Client.Graphics.Clyde
public IRenderWindow MainWindowRenderTarget { get; }
public override Vector2i ScreenSize { get; } = (1280, 720);
public Vector2 DefaultWindowScale => (1, 1);
public override bool IsFocused => true;
public ShaderInstance InstanceShader(ClydeHandle handle)
{
@@ -79,6 +80,12 @@ namespace Robust.Client.Graphics.Clyde
remove { }
}
public override event Action<WindowFocusedEventArgs> OnWindowFocused
{
add { }
remove { }
}
public void Render()
{
// Nada.

View File

@@ -25,6 +25,8 @@ namespace Robust.Client.Graphics
protected bool VSync { get; private set; } = true;
public abstract Vector2i ScreenSize { get; }
public abstract bool IsFocused { get; }
public abstract void SetWindowTitle(string title);
public virtual bool Initialize()
@@ -45,6 +47,8 @@ namespace Robust.Client.Graphics
public abstract event Action<WindowResizedEventArgs> OnWindowResized;
public abstract event Action<WindowFocusedEventArgs> OnWindowFocused;
protected virtual void ReadConfig()
{
WindowMode = (WindowMode) _configurationManager.GetCVar(CVars.DisplayWindowMode);

View File

@@ -13,6 +13,8 @@ namespace Robust.Client.Graphics
Vector2i ScreenSize { get; }
bool IsFocused { get; }
/// <summary>
/// The default scale ratio for window contents, given to us by the OS.
/// </summary>
@@ -27,6 +29,8 @@ namespace Robust.Client.Graphics
event Action<WindowResizedEventArgs> OnWindowResized;
event Action<WindowFocusedEventArgs> OnWindowFocused;
Texture LoadTextureFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParams = null);

View File

@@ -12,7 +12,7 @@ using YamlDotNet.RepresentationModel;
namespace Robust.Client.Graphics
{
[Prototype("shader")]
public sealed class ShaderPrototype : IPrototype, IIndexedPrototype
public sealed class ShaderPrototype : IPrototype
{
[Dependency] private readonly IClydeInternal _clyde = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;

View File

@@ -0,0 +1,14 @@
using System;
namespace Robust.Client.Graphics
{
public class WindowFocusedEventArgs : EventArgs
{
public WindowFocusedEventArgs(bool focused)
{
Focused = focused;
}
public bool Focused { get; }
}
}

View File

@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Robust.Client.Graphics;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Client.Prototypes
{
public sealed class ClientPrototypeManager : PrototypeManager
{
[Dependency] private readonly IClyde _clyde = default!;
private readonly List<FileSystemWatcher> _watchers = new();
private readonly TimeSpan _reloadDelay = TimeSpan.FromMilliseconds(10);
private CancellationTokenSource _reloadToken = new();
private readonly HashSet<ResourcePath> _reloadQueue = new();
public override void Initialize()
{
base.Initialize();
NetManager.RegisterNetMessage<MsgReloadPrototypes>(MsgReloadPrototypes.NAME, accept: NetMessageAccept.Server);
_clyde.OnWindowFocused += WindowFocusedChanged;
WatchResources();
}
private void WindowFocusedChanged(WindowFocusedEventArgs args)
{
#if !FULL_RELEASE
if (args.Focused && _reloadQueue.Count > 0)
{
Timer.Spawn(_reloadDelay, ReloadPrototypeQueue, _reloadToken.Token);
}
else
{
_reloadToken.Cancel();
_reloadToken = new CancellationTokenSource();
}
#endif
}
private void ReloadPrototypeQueue()
{
#if !FULL_RELEASE
var then = DateTime.Now;
var msg = NetManager.CreateNetMessage<MsgReloadPrototypes>();
msg.Paths = _reloadQueue.ToArray();
NetManager.ClientSendMessage(msg);
foreach (var path in _reloadQueue)
{
ReloadPrototypes(path);
}
_reloadQueue.Clear();
Logger.Info($"Reloaded prototypes in {(int) (DateTime.Now - then).TotalMilliseconds} ms");
#endif
}
private void WatchResources()
{
#if !FULL_RELEASE
foreach (var path in Resources.GetContentRoots().Select(r => r.ToString())
.Where(r => Directory.Exists(r + "/Prototypes")).Select(p => p + "/Prototypes"))
{
var watcher = new FileSystemWatcher(path, "*.yml")
{
IncludeSubdirectories = true,
NotifyFilter = NotifyFilters.LastWrite
};
watcher.Changed += (_, args) =>
{
switch (args.ChangeType)
{
case WatcherChangeTypes.Renamed:
case WatcherChangeTypes.Deleted:
return;
case WatcherChangeTypes.Created:
// case WatcherChangeTypes.Deleted:
case WatcherChangeTypes.Changed:
case WatcherChangeTypes.All:
break;
default:
throw new ArgumentOutOfRangeException();
}
TaskManager.RunOnMainThread(() =>
{
var file = new ResourcePath(args.FullPath);
foreach (var root in IoCManager.Resolve<IResourceManager>().GetContentRoots())
{
if (!file.TryRelativeTo(root, out var relative))
{
continue;
}
_reloadQueue.Add(relative);
}
});
};
watcher.EnableRaisingEvents = true;
_watchers.Add(watcher);
}
#endif
}
}
}

View File

@@ -5,7 +5,7 @@ using System.Diagnostics.Contracts;
using Robust.Client.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Maths;
using Timer = Robust.Shared.Timers.Timer;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Client.UserInterface.Controls
{

View File

@@ -1,21 +1,27 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.ViewVariables.Traits;
using Robust.Shared.Input;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Client.ViewVariables.Instances
{
internal class ViewVariablesInstanceObject : ViewVariablesInstance
{
private TabContainer _tabs = default!;
private Button _refreshButton = default!;
private int _tabCount;
private readonly List<ViewVariablesTrait> _traits = new();
private CancellationTokenSource _refreshCancelToken = new();
public ViewVariablesRemoteSession? Session { get; private set; }
public object? Object { get; private set; }
@@ -72,9 +78,10 @@ namespace Robust.Client.ViewVariables.Instances
name.SizeFlagsHorizontal = Control.SizeFlags.FillExpand;
headBox.AddChild(name);
var button = new Button {Text = "Refresh"};
button.OnPressed += _ => _refresh();
headBox.AddChild(button);
_refreshButton = new Button {Text = "Refresh", ToolTip = "RMB to toggle auto-refresh."};
_refreshButton.OnPressed += _ => _refresh();
_refreshButton.OnKeyBindDown += OnButtonKeybindDown;
headBox.AddChild(_refreshButton);
vBoxContainer.AddChild(headBox);
}
@@ -82,10 +89,32 @@ namespace Robust.Client.ViewVariables.Instances
vBoxContainer.AddChild(_tabs);
}
private void OnButtonKeybindDown(GUIBoundKeyEventArgs eventArgs)
{
if (eventArgs.Function == EngineKeyFunctions.UIRightClick)
{
_refreshButton.ToggleMode = !_refreshButton.ToggleMode;
_refreshButton.Pressed = !_refreshButton.Pressed;
_refreshCancelToken.Cancel();
if (!_refreshButton.Pressed) return;
_refreshCancelToken = new CancellationTokenSource();
Timer.SpawnRepeating(500, _refresh, _refreshCancelToken.Token);
} else if (eventArgs.Function == EngineKeyFunctions.UIClick)
{
_refreshButton.ToggleMode = false;
}
}
public override void Close()
{
base.Close();
_refreshCancelToken.Cancel();
if (Session != null && !Session.Closed)
{
ViewVariablesManager.CloseSession(Session);

View File

@@ -31,7 +31,6 @@ using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Serialization;
using Robust.Shared.Timers;
using Serilog.Debugging;
using Serilog.Sinks.Loki;
using Stopwatch = Robust.Shared.Timing.Stopwatch;
@@ -273,8 +272,13 @@ namespace Robust.Server
//IoCManager.Resolve<IMapLoader>().LoadedMapData +=
// IoCManager.Resolve<IRobustMappedStringSerializer>().AddStrings;
IoCManager.Resolve<IPrototypeManager>().LoadedData +=
(yaml, name) => _stringSerializer.AddStrings(yaml);
IoCManager.Resolve<IPrototypeManager>().LoadedData += (yaml, name) =>
{
if (!_stringSerializer.Locked)
{
_stringSerializer.AddStrings(yaml);
}
};
// Initialize Tier 2 services
IoCManager.Resolve<IGameTiming>().InSimulation = true;
@@ -297,6 +301,7 @@ namespace Robust.Server
// because of 'reasons' this has to be called after the last assembly is loaded
// otherwise the prototypes will be cleared
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
prototypeManager.Initialize();
prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes"));
prototypeManager.Resync();

View File

@@ -1,64 +0,0 @@
using JetBrains.Annotations;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Server.Console.Commands
{
[UsedImplicitly]
internal sealed class AddCompCommand : IConsoleCommand
{
public string Command => "addcomp";
public string Description => "Adds a component to an entity";
public string Help => "addcomp <uid> <componentName>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2)
{
shell.WriteLine("Wrong number of arguments");
return;
}
var entityUid = EntityUid.Parse(args[0]);
var componentName = args[1];
var compManager = IoCManager.Resolve<IComponentManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
var entityManager = IoCManager.Resolve<IEntityManager>();
var entity = entityManager.GetEntity(entityUid);
var component = (Component) compFactory.GetComponent(componentName);
component.Owner = entity;
compManager.AddComponent(entity, component);
}
}
[UsedImplicitly]
internal sealed class RemoveCompCommand : IConsoleCommand
{
public string Command => "rmcomp";
public string Description => "Removes a component from an entity.";
public string Help => "rmcomp <uid> <componentName>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2)
{
shell.WriteLine("Wrong number of arguments");
return;
}
var entityUid = EntityUid.Parse(args[0]);
var componentName = args[1];
var compManager = IoCManager.Resolve<IComponentManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
var registration = compFactory.GetRegistration(componentName);
compManager.RemoveComponent(entityUid, registration.Type);
}
}
}

View File

@@ -0,0 +1,61 @@
using JetBrains.Annotations;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Server.Console.Commands
{
[UsedImplicitly]
internal sealed class AddComponentCommand : IConsoleCommand
{
public string Command => "addcomp";
public string Description => "Adds a component to an entity";
public string Help => $"{Command} <uid> <componentName>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2)
{
shell.WriteLine($"Invalid amount of arguments.\n{Help}");
return;
}
if (!EntityUid.TryParse(args[0], out var uid))
{
shell.WriteLine($"{uid} is not a valid entity uid.");
return;
}
var entityManager = IoCManager.Resolve<IEntityManager>();
if (!entityManager.TryGetEntity(uid, out var entity))
{
shell.WriteLine($"No entity found with id {uid}.");
return;
}
var componentName = args[1];
var compManager = IoCManager.Resolve<IComponentManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
if (!compFactory.TryGetRegistration(componentName, out var registration, true))
{
shell.WriteLine($"No component found with name {componentName}.");
return;
}
if (entity.HasComponent(registration.Type))
{
shell.WriteLine($"Entity {entity.Name} already has a {componentName} component.");
}
var component = (Component) compFactory.GetComponent(registration.Type);
component.Owner = entity;
compManager.AddComponent(entity, component);
shell.WriteLine($"Added {componentName} component to entity {entity.Name}.");
}
}
}

View File

@@ -1,15 +1,14 @@
using System.Globalization;
using System.Globalization;
using System.Linq;
using System.Text;
using Robust.Server.Maps;
using Robust.Server.Player;
using Robust.Server.Timing;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Robust.Server.Console.Commands
{
@@ -237,81 +236,6 @@ namespace Robust.Server.Console.Commands
}
}
class PauseMapCommand : IConsoleCommand
{
public string Command => "pausemap";
public string Description => "Pauses a map, pausing all simulation processing on it.";
public string Help => "Usage: pausemap <map ID>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteLine(Loc.GetString("Need to supply a valid MapId"));
return;
}
var arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
var pauseManager = IoCManager.Resolve<IPauseManager>();
var mapManager = IoCManager.Resolve<IMapManager>();
if (!mapManager.MapExists(mapId))
{
shell.WriteLine("That map does not exist.");
return;
}
pauseManager.SetMapPaused(mapId, true);
}
}
class UnpauseMapCommand : IConsoleCommand
{
public string Command => "unpausemap";
public string Description => "unpauses a map, resuming all simulation processing on it.";
public string Help => "Usage: unpausemap <map ID>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteLine(Loc.GetString("Need to supply a valid MapId"));
return;
}
var arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
var pauseManager = IoCManager.Resolve<IPauseManager>();
var mapManager = IoCManager.Resolve<IMapManager>();
if (!mapManager.MapExists(mapId))
{
shell.WriteLine("That map does not exist.");
return;
}
pauseManager.SetMapPaused(mapId, false);
}
}
class QueryMapPausedCommand : IConsoleCommand
{
public string Command => "querymappaused";
public string Description => "Check whether a map is paused or not.";
public string Help => "Usage: querymappaused <map ID>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
var pauseManager = IoCManager.Resolve<IPauseManager>();
var mapManager = IoCManager.Resolve<IMapManager>();
if (!mapManager.MapExists(mapId))
{
shell.WriteLine("That map does not exist.");
return;
}
shell.WriteLine(pauseManager.IsMapPaused(mapId).ToString());
}
}
class TpGridCommand : IConsoleCommand
{
public string Command => "tpgrid";

View File

@@ -0,0 +1,59 @@
using JetBrains.Annotations;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Server.Console.Commands
{
[UsedImplicitly]
internal sealed class RemoveComponentCommand : IConsoleCommand
{
public string Command => "rmcomp";
public string Description => "Removes a component from an entity.";
public string Help => $"{Command} <uid> <componentName>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2)
{
shell.WriteLine($"Invalid amount of arguments.\n{Help}.");
return;
}
if (!EntityUid.TryParse(args[0], out var uid))
{
shell.WriteLine($"{uid} is not a valid entity uid.");
return;
}
var entityManager = IoCManager.Resolve<IEntityManager>();
if (!entityManager.TryGetEntity(uid, out var entity))
{
shell.WriteLine($"No entity found with id {uid}.");
return;
}
var componentName = args[1];
var compManager = IoCManager.Resolve<IComponentManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
if (!compFactory.TryGetRegistration(componentName, out var registration, true))
{
shell.WriteLine($"No component found with name {componentName}.");
return;
}
if (!entity.HasComponent(registration.Type))
{
shell.WriteLine($"No {componentName} component found on entity {entity.Name}.");
return;
}
compManager.RemoveComponent(uid, registration.Type);
shell.WriteLine($"Removed {componentName} component from entity {entity.Name}.");
}
}
}

View File

@@ -30,5 +30,10 @@ namespace Robust.Server.Console
{
return Implementation?.CanAdminMenu(session) ?? false;
}
public bool CanAdminReloadPrototypes(IPlayerSession session)
{
return Implementation?.CanAdminReloadPrototypes(session) ?? false;
}
}
}

View File

@@ -9,5 +9,6 @@ namespace Robust.Server.Console
bool CanAdminPlace(IPlayerSession session);
bool CanScript(IPlayerSession session);
bool CanAdminMenu(IPlayerSession session);
bool CanAdminReloadPrototypes(IPlayerSession session);
}
}

View File

@@ -4,7 +4,6 @@ using System.Linq;
using System.Threading;
using Prometheus;
using Robust.Server.Player;
using Robust.Server.Timing;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;

View File

@@ -12,9 +12,9 @@ using Robust.Shared.GameObjects;
using System.Globalization;
using System.Linq;
using Robust.Server.GameObjects;
using Robust.Server.Timing;
using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using YamlDotNet.Core;
namespace Robust.Server.Maps

View File

@@ -1,12 +1,50 @@
using System;
using Robust.Server.Console;
using Robust.Server.Player;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Prototypes;
namespace Robust.Server.Prototypes
{
public sealed class ServerPrototypeManager : PrototypeManager
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IConGroupController _conGroups = default!;
public ServerPrototypeManager() : base()
{
RegisterIgnore("shader");
}
public override void Initialize()
{
base.Initialize();
NetManager.RegisterNetMessage<MsgReloadPrototypes>(MsgReloadPrototypes.NAME, HandleReloadPrototypes, NetMessageAccept.Server);
}
private void HandleReloadPrototypes(MsgReloadPrototypes msg)
{
#if !FULL_RELEASE
if (!_playerManager.TryGetSessionByChannel(msg.MsgChannel, out var player) ||
!_conGroups.CanAdminReloadPrototypes(player))
{
return;
}
var then = DateTime.Now;
foreach (var path in msg.Paths)
{
ReloadPrototypes(path);
}
Logger.Info($"Reloaded prototypes in {(int) (DateTime.Now - then).TotalMilliseconds} ms");
#endif
}
}
}

View File

@@ -10,9 +10,9 @@ using Robust.Server.Prototypes;
using Robust.Server.Reflection;
using Robust.Server.Scripting;
using Robust.Server.ServerStatus;
using Robust.Server.Timing;
using Robust.Server.ViewVariables;
using Robust.Shared;
using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -20,6 +20,7 @@ using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Timing;
namespace Robust.Server
{
@@ -37,11 +38,11 @@ namespace Robust.Server
IoCManager.Register<IComponentFactory, ServerComponentFactory>();
IoCManager.Register<IConGroupController, ConGroupController>();
IoCManager.Register<IServerConsoleHost, ServerConsoleHost>();
IoCManager.Register<IConsoleHost, ServerConsoleHost>();
IoCManager.Register<IEntityManager, ServerEntityManager>();
IoCManager.Register<IEntityNetworkManager, ServerEntityNetworkManager>();
IoCManager.Register<IServerEntityNetworkManager, ServerEntityNetworkManager>();
IoCManager.Register<IMapLoader, MapLoader>();
IoCManager.Register<IPauseManager, PauseManager>();
IoCManager.Register<IPlacementManager, PlacementManager>();
IoCManager.Register<IPlayerManager, PlayerManager>();
IoCManager.Register<IPrototypeManager, ServerPrototypeManager>();

View File

@@ -1,98 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.ViewVariables;
namespace Robust.Server.Timing
{
internal sealed class PauseManager : IPauseManager, IPostInjectInit
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[ViewVariables] private readonly HashSet<MapId> _pausedMaps = new();
[ViewVariables] private readonly HashSet<MapId> _unInitializedMaps = new();
public void SetMapPaused(MapId mapId, bool paused)
{
if (paused)
{
_pausedMaps.Add(mapId);
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
entity.Paused = true;
}
}
else
{
_pausedMaps.Remove(mapId);
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
entity.Paused = false;
}
}
}
public void DoMapInitialize(MapId mapId)
{
if (IsMapInitialized(mapId))
{
throw new ArgumentException("That map is already initialized.");
}
_unInitializedMaps.Remove(mapId);
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
entity.RunMapInit();
entity.Paused = false;
}
}
public void DoGridMapInitialize(IMapGrid grid) => DoGridMapInitialize(grid.Index);
public void DoGridMapInitialize(GridId gridId)
{
var mapId = _mapManager.GetGrid(gridId).ParentMapId;
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
if (entity.Transform.GridID != gridId)
continue;
entity.RunMapInit();
entity.Paused = false;
}
}
public void AddUninitializedMap(MapId mapId)
{
_unInitializedMaps.Add(mapId);
}
public bool IsMapPaused(MapId mapId) => _pausedMaps.Contains(mapId) || _unInitializedMaps.Contains(mapId);
public bool IsGridPaused(IMapGrid grid) => IsMapPaused(grid.ParentMapId);
public bool IsGridPaused(GridId gridId)
{
var grid = _mapManager.GetGrid(gridId);
return IsGridPaused(grid);
}
public bool IsMapInitialized(MapId mapId)
{
return !_unInitializedMaps.Contains(mapId);
}
public void PostInject()
{
_mapManager.MapDestroyed += (sender, args) =>
{
_pausedMaps.Remove(args.Map);
_unInitializedMaps.Add(args.Map);
};
}
}
}

View File

@@ -1,16 +0,0 @@
using System;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Server.Timing
{
public static class PauseManagerExt
{
[Pure]
[Obsolete("Use IEntity.Paused directly")]
public static bool IsEntityPaused(this IPauseManager manager, IEntity entity)
{
return entity.Paused;
}
}
}

View File

@@ -116,6 +116,12 @@ namespace Robust.Shared.ContentPack
/// <exception cref="ArgumentNullException">Thrown if <paramref name="path"/> is null.</exception>
IEnumerable<ResourcePath> ContentFindFiles(string path);
/// <summary>
/// Returns a list of paths to all top-level content directories
/// </summary>
/// <returns></returns>
IEnumerable<ResourcePath> GetContentRoots();
/// <summary>
/// Read a file from the mounted content paths to a string.
/// </summary>
@@ -168,4 +174,4 @@ namespace Robust.Shared.ContentPack
return new StreamReader(stream, encoding);
}
}
}
}

View File

@@ -305,6 +305,17 @@ namespace Robust.Shared.ContentPack
AddRoot(ResourcePath.Root, loader);
}
public IEnumerable<ResourcePath> GetContentRoots()
{
foreach (var (_, root) in _contentRoots)
{
if (root is DirLoader loader)
{
yield return new ResourcePath(loader.GetPath(new ResourcePath(@"/")));
}
}
}
internal static bool IsPathValid(ResourcePath path)
{
var asString = path.ToString();

View File

@@ -1,13 +1,13 @@
using Robust.Server.Timing;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
namespace Robust.Server.GameObjects
namespace Robust.Shared.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(SharedIgnorePauseComponent))]
public sealed class IgnorePauseComponent : SharedIgnorePauseComponent
public class IgnorePauseComponent : Component
{
public override string Name => "IgnorePause";
public override void OnAdd()
{
base.OnAdd();

View File

@@ -1,7 +0,0 @@
namespace Robust.Shared.GameObjects
{
public abstract class SharedIgnorePauseComponent : Component
{
public override string Name => "IgnorePause";
}
}

View File

@@ -4,7 +4,7 @@ using System.Threading;
using System.Threading.Tasks;
using Robust.Shared.Exceptions;
using Robust.Shared.IoC;
using Timer = Robust.Shared.Timers.Timer;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Shared.GameObjects
{

View File

@@ -1,7 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Timer = Robust.Shared.Timers.Timer;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Shared.GameObjects
{

View File

@@ -79,7 +79,7 @@ namespace Robust.Shared.GameObjects
get => _paused;
set
{
if (_paused == value || value && HasComponent<SharedIgnorePauseComponent>())
if (_paused == value || value && HasComponent<IgnorePauseComponent>())
return;
_paused = value;

View File

@@ -1,11 +1,9 @@
using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
namespace Robust.Shared.GameObjects
{
/// <summary>
/// Defines a component that has "map initialization" behavior.
/// Basically irreversible behavior that moves the map from "map editor" to playable,
/// like spawning preset objects.
/// Defines a component that has "map initialization" behavior.
/// Basically irreversible behavior that moves the map from "map editor" to playable,
/// like spawning preset objects.
/// </summary>
public interface IMapInit
{

View File

@@ -0,0 +1,42 @@
using Lidgren.Network;
using Robust.Shared.Utility;
namespace Robust.Shared.Network.Messages
{
public class MsgReloadPrototypes : NetMessage
{
#region REQUIRED
public const MsgGroups GROUP = MsgGroups.Command;
public const string NAME = nameof(MsgReloadPrototypes);
public MsgReloadPrototypes(INetChannel channel) : base(NAME, GROUP)
{
}
#endregion
public ResourcePath[] Paths = default!;
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
var count = buffer.ReadInt32();
Paths = new ResourcePath[count];
for (var i = 0; i < count; i++)
{
Paths[i] = new ResourcePath(buffer.ReadString());
}
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
buffer.Write(Paths.Length);
foreach (var path in Paths)
{
buffer.Write(path.ToString());
}
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -6,6 +6,7 @@ using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
@@ -18,7 +19,7 @@ namespace Robust.Shared.Prototypes
/// Prototype that represents game entities.
/// </summary>
[Prototype("entity")]
public class EntityPrototype : IPrototype, IIndexedPrototype, ISyncingPrototype
public class EntityPrototype : IPrototype, ISyncingPrototype
{
[Dependency] private readonly IComponentFactory _componentFactory = default!;
@@ -257,6 +258,11 @@ namespace Robust.Shared.Prototypes
}
}
public void Reset()
{
Children.Clear();
}
// Resolve inheritance.
public bool Sync(IPrototypeManager manager, int stage)
{
@@ -424,6 +430,69 @@ namespace Robust.Shared.Prototypes
}
}
public void UpdateEntity(Entity entity)
{
bool HasBeenModified(string name, YamlMappingNode data, EntityPrototype prototype, IComponent currentComponent, IComponentFactory factory)
{
var component = factory.GetComponent(name);
prototype.CurrentDeserializingComponent = name;
ObjectSerializer ser = YamlObjectSerializer.NewReader(data, new PrototypeSerializationContext(prototype));
component.ExposeData(ser);
return component == (Component) currentComponent;
}
if (ID != entity.Prototype?.ID)
{
Logger.Error($"Reloaded prototype used to update entity did not match entity's existing prototype: Expected '{ID}', got '{entity.Prototype?.ID}'");
return;
}
var factory = IoCManager.Resolve<IComponentFactory>();
var componentManager = IoCManager.Resolve<IComponentManager>();
var oldPrototype = entity.Prototype;
var oldPrototypeComponents = oldPrototype.Components.Keys
.Where(n => n != "Transform" && n != "MetaData")
.Select(name => (name, factory.GetRegistration(name).Type))
.ToList();
var newPrototypeComponents = Components.Keys
.Where(n => n != "Transform" && n != "MetaData")
.Select(name => (name, factory.GetRegistration(name).Type))
.ToList();
var ignoredComponents = new List<string>();
// Find components to be removed, and remove them
foreach (var (name, type) in oldPrototypeComponents.Except(newPrototypeComponents))
{
if (!HasBeenModified(name, oldPrototype.Components[name], oldPrototype, entity.GetComponent(type),
factory) && Components.Keys.Contains(name))
{
ignoredComponents.Add(name);
continue;
}
componentManager.RemoveComponent(entity.Uid, type);
}
componentManager.CullRemovedComponents();
// Add new components
foreach (var (name, type) in newPrototypeComponents.Where(t => !ignoredComponents.Contains(t.name)).Except(oldPrototypeComponents))
{
var data = Components[name];
var component = (Component) factory.GetComponent(name);
ObjectSerializer ser = YamlObjectSerializer.NewReader(data, new PrototypeSerializationContext(this));
CurrentDeserializingComponent = name;
component.Owner = entity;
component.ExposeData(ser);
entity.AddComponent(component);
}
// Update entity metadata
entity.MetaData.EntityPrototype = this;
}
internal static void LoadEntity(EntityPrototype? prototype, Entity entity, IComponentFactory factory, IEntityLoadContext? context)
{
YamlObjectSerializer.Context? defaultContext = null;

View File

@@ -3,30 +3,24 @@
namespace Robust.Shared.Prototypes
{
/// <summary>
/// An IPrototype is a prototype that can be loaded from the global YAML prototypes.
/// An IPrototype is a prototype that can be loaded from the global YAML prototypes.
/// </summary>
/// <remarks>
/// To use this, the prototype must be accessible through IoC with <see cref="IoCTargetAttribute"/>
/// and it must have a <see cref="PrototypeAttribute"/> to give it a type string.
/// To use this, the prototype must be accessible through IoC with <see cref="IoCTargetAttribute"/>
/// and it must have a <see cref="PrototypeAttribute"/> to give it a type string.
/// </remarks>
public interface IPrototype
{
/// <summary>
/// Load data from the YAML mappings in the prototype files.
/// </summary>
void LoadFrom(YamlMappingNode mapping);
}
/// <summary>
/// Extension on <see cref="IPrototype"/> that allows it to be "indexed" by a string ID.
/// </summary>
public interface IIndexedPrototype
{
/// <summary>
/// An ID for this prototype instance.
/// If this is a duplicate, an error will be thrown.
/// </summary>
string ID { get; }
/// <summary>
/// Load data from the YAML mappings in the prototype files.
/// </summary>
void LoadFrom(YamlMappingNode mapping);
}
/// <summary>
@@ -35,6 +29,8 @@ namespace Robust.Shared.Prototypes
/// </summary>
public interface ISyncingPrototype
{
void Reset();
/// <summary>
/// Sync and update cross-referencing data.
/// Syncing works in stages, each time it will be called with the stage it's currently on.

View File

@@ -4,11 +4,16 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Robust.Shared.Asynchronous;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.IoC.Exceptions;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using YamlDotNet.Core;
@@ -21,6 +26,8 @@ namespace Robust.Shared.Prototypes
/// </summary>
public interface IPrototypeManager
{
void Initialize();
/// <summary>
/// Return an IEnumerable to iterate all prototypes of a certain type.
/// </summary>
@@ -28,6 +35,7 @@ namespace Robust.Shared.Prototypes
/// Thrown if the type of prototype is not registered.
/// </exception>
IEnumerable<T> EnumeratePrototypes<T>() where T : class, IPrototype;
/// <summary>
/// Return an IEnumerable to iterate all prototypes of a certain type.
/// </summary>
@@ -35,32 +43,44 @@ namespace Robust.Shared.Prototypes
/// Thrown if the type of prototype is not registered.
/// </exception>
IEnumerable<IPrototype> EnumeratePrototypes(Type type);
/// <summary>
/// Index for a <see cref="IIndexedPrototype"/> by ID.
/// Index for a <see cref="IPrototype"/> by ID.
/// </summary>
/// <exception cref="KeyNotFoundException">
/// Thrown if the type of prototype is not registered.
/// </exception>
T Index<T>(string id) where T : class, IIndexedPrototype;
T Index<T>(string id) where T : class, IPrototype;
/// <summary>
/// Index for a <see cref="IIndexedPrototype"/> by ID.
/// Index for a <see cref="IPrototype"/> by ID.
/// </summary>
/// <exception cref="KeyNotFoundException">
/// Thrown if the ID does not exist or the type of prototype is not registered.
/// </exception>
IIndexedPrototype Index(Type type, string id);
bool HasIndex<T>(string id) where T : IIndexedPrototype;
bool TryIndex<T>(string id, out T prototype) where T : IIndexedPrototype;
IPrototype Index(Type type, string id);
bool HasIndex<T>(string id) where T : IPrototype;
bool TryIndex<T>(string id, out T prototype) where T : IPrototype;
/// <summary>
/// Load prototypes from files in a directory, recursively.
/// </summary>
void LoadDirectory(ResourcePath path);
void LoadFromStream(TextReader stream);
void LoadString(string str);
Task<List<IPrototype>> LoadDirectory(ResourcePath path);
List<IPrototype> LoadFromStream(TextReader stream);
List<IPrototype> LoadString(string str);
/// <summary>
/// Clear out all prototypes and reset to a blank slate.
/// </summary>
void Clear();
/// <summary>
/// Performs a reload on all prototypes, updating the game state accordingly
/// </summary>
void ReloadPrototypes(ResourcePath file);
/// <summary>
/// Syncs all inter-prototype data. Call this when operations adding new prototypes are done.
/// </summary>
@@ -79,7 +99,6 @@ namespace Robust.Shared.Prototypes
void RegisterType(Type protoClass);
event Action<YamlStream, string>? LoadedData;
}
/// <summary>
@@ -103,25 +122,40 @@ namespace Robust.Shared.Prototypes
{
[Dependency] private readonly IReflectionManager ReflectionManager = default!;
[Dependency] private readonly IDynamicTypeFactoryInternal _dynamicTypeFactory = default!;
[Dependency] private readonly IResourceManager _resources = default!;
[Dependency] public readonly IResourceManager Resources = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] public readonly ITaskManager TaskManager = default!;
[Dependency] public readonly INetManager NetManager = default!;
private readonly Dictionary<string, Type> prototypeTypes = new();
private bool _initialized;
private bool _hasEverBeenReloaded;
private bool _hasEverResynced;
#region IPrototypeManager members
private readonly Dictionary<Type, List<IPrototype>> prototypes = new();
private readonly Dictionary<Type, Dictionary<string, IIndexedPrototype>> indexedPrototypes = new();
private readonly Dictionary<Type, Dictionary<string, IPrototype>> prototypes = new();
private readonly HashSet<string> IgnoredPrototypeTypes = new();
public virtual void Initialize()
{
if (_initialized)
{
throw new InvalidOperationException($"{nameof(PrototypeManager)} has already been initialized.");
}
_initialized = true;
}
public IEnumerable<T> EnumeratePrototypes<T>() where T : class, IPrototype
{
if (!_hasEverBeenReloaded)
{
throw new InvalidOperationException("No prototypes have been loaded yet.");
}
return prototypes[typeof(T)].Select((IPrototype p) => (T)p);
return prototypes[typeof(T)].Values.Select(p => (T) p);
}
public IEnumerable<IPrototype> EnumeratePrototypes(Type type)
@@ -130,10 +164,11 @@ namespace Robust.Shared.Prototypes
{
throw new InvalidOperationException("No prototypes have been loaded yet.");
}
return prototypes[type];
return prototypes[type].Values;
}
public T Index<T>(string id) where T : class, IIndexedPrototype
public T Index<T>(string id) where T : class, IPrototype
{
if (!_hasEverBeenReloaded)
{
@@ -141,7 +176,7 @@ namespace Robust.Shared.Prototypes
}
try
{
return (T)indexedPrototypes[typeof(T)][id];
return (T)prototypes[typeof(T)][id];
}
catch (KeyNotFoundException)
{
@@ -149,24 +184,60 @@ namespace Robust.Shared.Prototypes
}
}
public IIndexedPrototype Index(Type type, string id)
public IPrototype Index(Type type, string id)
{
if (!_hasEverBeenReloaded)
{
throw new InvalidOperationException("No prototypes have been loaded yet.");
}
return indexedPrototypes[type][id];
return prototypes[type][id];
}
public void Clear()
{
prototypes.Clear();
prototypeTypes.Clear();
indexedPrototypes.Clear();
prototypes.Clear();
}
public virtual async void ReloadPrototypes(ResourcePath file)
{
#if !FULL_RELEASE
var changed = await LoadFile(file.ToRootedPath(), true);
Resync();
foreach (var prototype in changed)
{
if (prototype is not EntityPrototype entityPrototype)
{
continue;
}
foreach (var entity in _entityManager.GetEntities(new PredicateEntityQuery(e => e.Prototype != null && e.Prototype.ID == entityPrototype.ID)))
{
entityPrototype.UpdateEntity((Entity) entity);
}
}
#endif
}
public void Resync()
{
// TODO Make this smarter and only resync changed prototypes
if (_hasEverResynced)
{
foreach (var prototypeList in prototypes.Values)
{
foreach (var prototype in prototypeList.Values)
{
if (prototype is ISyncingPrototype syncing)
{
syncing.Reset();
}
}
}
}
foreach (Type type in prototypeTypes.Values.Where(t => typeof(ISyncingPrototype).IsAssignableFrom(t)))
{
// This list is the list of prototypes we're syncing.
@@ -178,17 +249,18 @@ namespace Robust.Shared.Prototypes
// When we get to the end, do the whole thing again!
// Yes this is ridiculously overengineered BUT IT PERFORMS WELL.
// I hope.
List<ISyncingPrototype> currentRun = prototypes[type].Select(p => (ISyncingPrototype)p).ToList();
int stage = 0;
List<ISyncingPrototype> currentRun = prototypes[type].Values.Select(p => (ISyncingPrototype) p).ToList();
var stage = 0;
// Outer loop to iterate stages.
while (currentRun.Count > 0)
{
// Increase positions to iterate over list.
// If we need to stick, i gets reduced down below.
for (int i = 0; i < currentRun.Count; i++)
for (var i = 0; i < currentRun.Count; i++)
{
ISyncingPrototype prototype = currentRun[i];
bool result = prototype.Sync(this, stage);
var result = prototype.Sync(this, stage);
// Keep prototype and move on to next one if it returns true.
// Thus it stays in the list for next stage.
if (result)
@@ -205,85 +277,135 @@ namespace Robust.Shared.Prototypes
stage++;
}
}
_hasEverResynced = true;
}
/// <inheritdoc />
public void LoadDirectory(ResourcePath path)
public async Task<List<IPrototype>> LoadDirectory(ResourcePath path)
{
var sawmill = Logger.GetSawmill("eng");
var changedPrototypes = new List<IPrototype>();
_hasEverBeenReloaded = true;
var yamlStreams = _resources.ContentFindFiles(path).ToList().AsParallel()
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."))
.Select(filePath =>
{
try
{
using var reader = new StreamReader(_resources.ContentFileRead(filePath), EncodingHelpers.UTF8);
var yamlStream = new YamlStream();
yamlStream.Load(reader);
var streams = Resources.ContentFindFiles(path).ToList().AsParallel()
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."));
var result = ((YamlStream? yamlStream, ResourcePath?))(yamlStream, filePath);
LoadedData?.Invoke(yamlStream, filePath.ToString());
return result;
}
catch (YamlException e)
{
sawmill.Error("YamlException whilst loading prototypes from {0}: {1}", filePath, e.Message);
return (null, null);
}
})
.Where(p => p.yamlStream != null) // Filter out loading errors.
.ToList();
foreach (var (stream, filePath) in yamlStreams)
foreach (var resourcePath in streams)
{
for (var i = 0; i < stream!.Documents.Count; i++)
var filePrototypes = await LoadFile(resourcePath);
changedPrototypes.AddRange(filePrototypes);
}
return changedPrototypes;
}
private Task<StreamReader?> ReadFile(ResourcePath file, bool @throw = true)
{
var retries = 0;
// This might be shit-code, but its pjb-responded-idk-when-asked shit-code.
while (true)
{
try
{
try
var reader = new StreamReader(Resources.ContentFileRead(file), EncodingHelpers.UTF8);
return Task.FromResult<StreamReader?>(reader);
}
catch (IOException e)
{
if (retries > 10)
{
LoadFromDocument(stream.Documents[i]);
}
catch (Exception e)
{
Logger.ErrorS("eng", $"Exception whilst loading prototypes from {filePath}#{i}:\n{e}");
if (@throw)
{
throw;
}
Logger.Error($"Error reloading prototypes in file {file}.", e);
return Task.FromResult<StreamReader?>(null);
}
retries++;
Thread.Sleep(10);
}
}
}
public void LoadFromStream(TextReader stream)
public async Task<List<IPrototype>> LoadFile(ResourcePath file, bool overwrite = false)
{
var changedPrototypes = new List<IPrototype>();
try
{
using var reader = await ReadFile(file, !overwrite);
if (reader == null)
{
return changedPrototypes;
}
var yamlStream = new YamlStream();
yamlStream.Load(reader);
LoadedData?.Invoke(yamlStream, file.ToString());
for (var i = 0; i < yamlStream.Documents.Count; i++)
{
try
{
var documentPrototypes = LoadFromDocument(yamlStream.Documents[i], overwrite);
changedPrototypes.AddRange(documentPrototypes);
}
catch (Exception e)
{
Logger.ErrorS("eng", $"Exception whilst loading prototypes from {file}#{i}:\n{e}");
}
}
}
catch (YamlException e)
{
var sawmill = Logger.GetSawmill("eng");
sawmill.Error("YamlException whilst loading prototypes from {0}: {1}", file, e.Message);
}
return changedPrototypes;
}
public List<IPrototype> LoadFromStream(TextReader stream)
{
var changedPrototypes = new List<IPrototype>();
_hasEverBeenReloaded = true;
var yaml = new YamlStream();
yaml.Load(stream);
for (int i = 0; i < yaml.Documents.Count; i++)
for (var i = 0; i < yaml.Documents.Count; i++)
{
try
{
LoadFromDocument(yaml.Documents[i]);
var documentPrototypes = LoadFromDocument(yaml.Documents[i]);
changedPrototypes.AddRange(documentPrototypes);
}
catch (Exception e)
{
throw new PrototypeLoadException(string.Format("Failed to load prototypes from document#{0}", i), e);
throw new PrototypeLoadException($"Failed to load prototypes from document#{i}", e);
}
}
LoadedData?.Invoke(yaml, "anonymous prototypes YAML stream");
return changedPrototypes;
}
public void LoadString(string str)
public List<IPrototype> LoadString(string str)
{
LoadFromStream(new StreamReader(str));
return LoadFromStream(new StringReader(str));
}
#endregion IPrototypeManager members
public void PostInject()
{
ReflectionManager.OnAssemblyAdded += (_, __) => ReloadPrototypeTypes();
ReflectionManager.OnAssemblyAdded += (_, _) => ReloadPrototypeTypes();
ReloadPrototypeTypes();
}
@@ -296,9 +418,11 @@ namespace Robust.Shared.Prototypes
}
}
private void LoadFromDocument(YamlDocument document)
private List<IPrototype> LoadFromDocument(YamlDocument document, bool overwrite = false)
{
var rootNode = (YamlSequenceNode)document.RootNode;
var changedPrototypes = new List<IPrototype>();
var rootNode = (YamlSequenceNode) document.RootNode;
foreach (YamlMappingNode node in rootNode.Cast<YamlMappingNode>())
{
var type = node.GetNode("type").AsString();
@@ -308,38 +432,41 @@ namespace Robust.Shared.Prototypes
{
continue;
}
throw new PrototypeLoadException(string.Format("Unknown prototype type: '{0}'", type));
throw new PrototypeLoadException($"Unknown prototype type: '{type}'");
}
var prototypeType = prototypeTypes[type];
var prototype = _dynamicTypeFactory.CreateInstanceUnchecked<IPrototype>(prototypeType);
prototype.LoadFrom(node);
prototypes[prototypeType].Add(prototype);
var indexedPrototype = prototype as IIndexedPrototype;
if (indexedPrototype != null)
changedPrototypes.Add(prototype);
var id = prototype.ID;
if (!overwrite && prototypes[prototypeType].ContainsKey(id))
{
var id = indexedPrototype.ID;
if (indexedPrototypes[prototypeType].ContainsKey(id))
{
throw new PrototypeLoadException(string.Format("Duplicate ID: '{0}'", id));
}
indexedPrototypes[prototypeType][id] = (IIndexedPrototype)prototype;
throw new PrototypeLoadException($"Duplicate ID: '{id}'");
}
prototypes[prototypeType][id] = prototype;
}
return changedPrototypes;
}
public bool HasIndex<T>(string id) where T : IIndexedPrototype
public bool HasIndex<T>(string id) where T : IPrototype
{
if (!indexedPrototypes.TryGetValue(typeof(T), out var index))
if (!prototypes.TryGetValue(typeof(T), out var index))
{
throw new UnknownPrototypeException(id);
}
return index.ContainsKey(id);
}
public bool TryIndex<T>(string id, [MaybeNullWhen(false)] out T prototype) where T : IIndexedPrototype
public bool TryIndex<T>(string id, [MaybeNullWhen(false)] out T prototype) where T : IPrototype
{
if (!indexedPrototypes.TryGetValue(typeof(T), out var index))
if (!prototypes.TryGetValue(typeof(T), out var index))
{
throw new UnknownPrototypeException(id);
}
@@ -376,11 +503,10 @@ namespace Robust.Shared.Prototypes
}
prototypeTypes[attribute.Type] = type;
prototypes[type] = new List<IPrototype>();
if (typeof(IIndexedPrototype).IsAssignableFrom(type))
if (typeof(IPrototype).IsAssignableFrom(type))
{
indexedPrototypes[type] = new Dictionary<string, IIndexedPrototype>();
prototypes[type] = new Dictionary<string, IPrototype>();
}
}
@@ -404,11 +530,6 @@ namespace Robust.Shared.Prototypes
public PrototypeLoadException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
}
}
[Serializable]

View File

@@ -14,6 +14,8 @@ namespace Robust.Shared.Serialization
[PublicAPI]
internal interface IRobustMappedStringSerializer
{
bool Locked { get; }
/// <summary>
/// The type serializer to register with NetSerializer.
/// </summary>

View File

@@ -133,6 +133,7 @@ namespace Robust.Shared.Serialization
| RegexOptions.IgnorePatternWhitespace
);
public bool Locked => _dict.Locked;
public ITypeSerializer TypeSerializer => this;

View File

@@ -13,7 +13,6 @@ using Robust.Shared.Physics;
using Robust.Shared.Random;
using Robust.Shared.Sandboxing;
using Robust.Shared.Serialization;
using Robust.Shared.Timers;
using Robust.Shared.Timing;
namespace Robust.Shared
@@ -35,6 +34,7 @@ namespace Robust.Shared
IoCManager.Register<ILogManager, LogManager>();
IoCManager.Register<IMapManager, MapManager>();
IoCManager.Register<IMapManagerInternal, MapManager>();
IoCManager.Register<IPauseManager, PauseManager>();
IoCManager.Register<IModLoader, ModLoader>();
IoCManager.Register<IModLoaderInternal, ModLoader>();
IoCManager.Register<INetManager, NetManager>();

View File

@@ -1,7 +1,7 @@
using JetBrains.Annotations;
using JetBrains.Annotations;
using Robust.Shared.Map;
namespace Robust.Server.Timing
namespace Robust.Shared.Timing
{
public interface IPauseManager
{
@@ -26,4 +26,4 @@ namespace Robust.Server.Timing
[Pure]
bool IsMapInitialized(MapId mapId);
}
}
}

View File

@@ -1,7 +1,6 @@
using System.Threading;
using Robust.Shared.Timing;
namespace Robust.Shared.Timers
namespace Robust.Shared.Timing
{
public interface ITimerManager
{

View File

@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Timing
{
internal sealed class PauseManager : IPauseManager, IPostInjectInit
{
[Dependency] private readonly IConsoleHost _conhost = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[ViewVariables] private readonly HashSet<MapId> _pausedMaps = new();
[ViewVariables] private readonly HashSet<MapId> _unInitializedMaps = new();
public void SetMapPaused(MapId mapId, bool paused)
{
if (paused)
{
_pausedMaps.Add(mapId);
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
entity.Paused = true;
}
}
else
{
_pausedMaps.Remove(mapId);
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
entity.Paused = false;
}
}
}
public void DoMapInitialize(MapId mapId)
{
if (IsMapInitialized(mapId))
throw new ArgumentException("That map is already initialized.");
_unInitializedMaps.Remove(mapId);
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
entity.RunMapInit();
entity.Paused = false;
}
}
public void DoGridMapInitialize(IMapGrid grid)
{
DoGridMapInitialize(grid.Index);
}
public void DoGridMapInitialize(GridId gridId)
{
var mapId = _mapManager.GetGrid(gridId).ParentMapId;
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
if (entity.Transform.GridID != gridId)
continue;
entity.RunMapInit();
entity.Paused = false;
}
}
public void AddUninitializedMap(MapId mapId)
{
_unInitializedMaps.Add(mapId);
}
public bool IsMapPaused(MapId mapId)
{
return _pausedMaps.Contains(mapId) || _unInitializedMaps.Contains(mapId);
}
public bool IsGridPaused(IMapGrid grid)
{
return IsMapPaused(grid.ParentMapId);
}
public bool IsGridPaused(GridId gridId)
{
var grid = _mapManager.GetGrid(gridId);
return IsGridPaused(grid);
}
public bool IsMapInitialized(MapId mapId)
{
return !_unInitializedMaps.Contains(mapId);
}
/// <inheritdoc />
public void PostInject()
{
_mapManager.MapDestroyed += (_, args) =>
{
_pausedMaps.Remove(args.Map);
_unInitializedMaps.Add(args.Map);
};
if(_conhost.IsClient)
return;
_conhost.RegisterCommand("pausemap",
"Pauses a map, pausing all simulation processing on it.",
"pausemap <map ID>",
(shell, _, args) =>
{
if (args.Length != 1)
{
shell.WriteError("Need to supply a valid MapId");
return;
}
string? arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
if (!_mapManager.MapExists(mapId))
{
shell.WriteError("That map does not exist.");
return;
}
SetMapPaused(mapId, true);
});
_conhost.RegisterCommand("querymappaused",
"Check whether a map is paused or not.",
"querymappaused <map ID>",
(shell, _, args) =>
{
string? arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
if (!_mapManager.MapExists(mapId))
{
shell.WriteError("That map does not exist.");
return;
}
shell.WriteLine(IsMapPaused(mapId).ToString());
});
_conhost.RegisterCommand("unpausemap",
"unpauses a map, resuming all simulation processing on it.",
"Usage: unpausemap <map ID>",
(shell, _, args) =>
{
if (args.Length != 1)
{
shell.WriteLine(Loc.GetString("Need to supply a valid MapId"));
return;
}
string? arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
if (!_mapManager.MapExists(mapId))
{
shell.WriteLine("That map does not exist.");
return;
}
SetMapPaused(mapId, false);
});
}
}
}

View File

@@ -4,7 +4,7 @@ using System.Threading.Tasks;
using Robust.Shared.Exceptions;
using Robust.Shared.IoC;
namespace Robust.Shared.Timers
namespace Robust.Shared.Timing
{
public class Timer
{

View File

@@ -2,9 +2,8 @@
using System.Threading;
using Robust.Shared.Exceptions;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
namespace Robust.Shared.Timers
namespace Robust.Shared.Timing
{
internal sealed class TimerManager : ITimerManager
{

View File

@@ -12,7 +12,9 @@ namespace Robust.UnitTesting.Shared.Prototypes
[TestFixture]
public class PrototypeManager_Test : RobustUnitTest
{
private const string LoadStringTestDummyId = "LoadStringTestDummy";
private IPrototypeManager manager = default!;
[OneTimeSetUp]
public void Setup()
{
@@ -97,6 +99,16 @@ namespace Robust.UnitTesting.Shared.Prototypes
Assert.That(prototype.PlacementMode, Is.EqualTo("SnapgridCenter"));
}
[Test]
public void TestLoadString()
{
manager.LoadString(LoadStringDocument);
var prototype = manager.Index<EntityPrototype>(LoadStringTestDummyId);
Assert.That(prototype.Name, Is.EqualTo(LoadStringTestDummyId));
}
private enum YamlTestEnum : byte
{
Foo,
@@ -159,6 +171,11 @@ namespace Robust.UnitTesting.Shared.Prototypes
placement:
mode: SnapgridCenter
";
private static readonly string LoadStringDocument = $@"
- type: entity
id: {LoadStringTestDummyId}
name: {LoadStringTestDummyId}";
}
public class TestBasicPrototypeComponent : Component

View File

@@ -4,11 +4,10 @@ using NUnit.Framework;
using Robust.Shared.Asynchronous;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Timers;
using Robust.Shared.Timing;
using Timer = Robust.Shared.Timers.Timer;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.UnitTesting.Shared.Timers
namespace Robust.UnitTesting.Shared.Timing
{
[TestFixture]
[TestOf(typeof(Timer))]