From cec071f9073fcf1e5162796646f29418209207a1 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 1 Jun 2023 16:56:36 +1200 Subject: [PATCH] Add ability to reset loaded prototypes. (#4097) --- .../GameController/GameController.cs | 3 +- .../Prototypes/ClientPrototypeManager.cs | 9 +++- Robust.Server/BaseServer.cs | 2 +- .../Prototypes/ServerPrototypeManager.cs | 8 +++ Robust.Shared/Prototypes/IPrototypeManager.cs | 28 ++++++++-- Robust.Shared/Prototypes/PrototypeManager.cs | 51 ++++++++++++++----- .../Server/RobustServerSimulation.cs | 4 +- 7 files changed, 83 insertions(+), 22 deletions(-) diff --git a/Robust.Client/GameController/GameController.cs b/Robust.Client/GameController/GameController.cs index 8207b1dc3..29db4d50e 100644 --- a/Robust.Client/GameController/GameController.cs +++ b/Robust.Client/GameController/GameController.cs @@ -164,8 +164,7 @@ namespace Robust.Client ProgramShared.FinishCheckBadFileExtensions(checkBadExtensions); _prototypeManager.Initialize(); - _prototypeManager.LoadDirectory(new("/EnginePrototypes/")); - _prototypeManager.LoadDirectory(Options.PrototypeDirectory); + _prototypeManager.LoadDefaultPrototypes(); _prototypeManager.ResolveResults(); _userInterfaceManager.Initialize(); _eyeManager.Initialize(); diff --git a/Robust.Client/Prototypes/ClientPrototypeManager.cs b/Robust.Client/Prototypes/ClientPrototypeManager.cs index a41186bd9..79adba3c4 100644 --- a/Robust.Client/Prototypes/ClientPrototypeManager.cs +++ b/Robust.Client/Prototypes/ClientPrototypeManager.cs @@ -8,7 +8,6 @@ using Robust.Client.Graphics; using Robust.Client.Timing; using Robust.Shared; using Robust.Shared.Configuration; -using Robust.Shared.ContentPack; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Network; @@ -25,6 +24,7 @@ namespace Robust.Client.Prototypes [Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly IClientGameTiming _timing = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IGameControllerInternal _controller = default!; private readonly List _watchers = new(); private readonly TimeSpan _reloadDelay = TimeSpan.FromMilliseconds(10); @@ -42,6 +42,13 @@ namespace Robust.Client.Prototypes WatchResources(); } + public override void LoadDefaultPrototypes(Dictionary>? changed = null) + { + LoadDirectory(new("/EnginePrototypes/"), changed: changed); + LoadDirectory(_controller.Options.PrototypeDirectory, changed: changed); + ResolveResults(); + } + private void WindowFocusedChanged(WindowFocusedEventArgs args) { #if TOOLS diff --git a/Robust.Server/BaseServer.cs b/Robust.Server/BaseServer.cs index 50f05bc6b..73b0ca0de 100644 --- a/Robust.Server/BaseServer.cs +++ b/Robust.Server/BaseServer.cs @@ -365,7 +365,7 @@ namespace Robust.Server // because of 'reasons' this has to be called after the last assembly is loaded // otherwise the prototypes will be cleared _prototype.Initialize(); - _prototype.LoadDirectory(Options.PrototypeDirectory); + _prototype.LoadDefaultPrototypes(); _prototype.ResolveResults(); _consoleHost.Initialize(); diff --git a/Robust.Server/Prototypes/ServerPrototypeManager.cs b/Robust.Server/Prototypes/ServerPrototypeManager.cs index cb8150b8b..6db067435 100644 --- a/Robust.Server/Prototypes/ServerPrototypeManager.cs +++ b/Robust.Server/Prototypes/ServerPrototypeManager.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.Diagnostics; using Robust.Server.Console; using Robust.Server.Player; @@ -14,6 +16,7 @@ namespace Robust.Server.Prototypes [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IConGroupController _conGroups = default!; [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly IBaseServerInternal _server = default!; public ServerPrototypeManager() { @@ -46,5 +49,10 @@ namespace Robust.Server.Prototypes #endif } + public override void LoadDefaultPrototypes(Dictionary>? changed = null) + { + LoadDirectory(_server.Options.PrototypeDirectory, changed: changed); + ResolveResults(); + } } } diff --git a/Robust.Shared/Prototypes/IPrototypeManager.cs b/Robust.Shared/Prototypes/IPrototypeManager.cs index f0854bb65..9ce4d2c94 100644 --- a/Robust.Shared/Prototypes/IPrototypeManager.cs +++ b/Robust.Shared/Prototypes/IPrototypeManager.cs @@ -211,16 +211,33 @@ public interface IPrototypeManager /// void Clear(); + /// + /// Calls and then rediscovers all prototype kinds. + /// + void ReloadPrototypeKinds(); + + /// + /// Calls and then loads prototypes from the default directories. + /// + void Reset(); + + /// + /// Loads prototypes from the default directories. + /// + /// Dictionary that will be filled with all the loaded prototypes. + void LoadDefaultPrototypes(Dictionary>? loaded = null); + /// /// Syncs all inter-prototype data. Call this when operations adding new prototypes are done. /// void ResolveResults(); /// - /// Reload the changes from LoadString + /// Invokes with information about the modified prototypes. + /// When built with development tools, this will also push inheritance for reloaded prototypes/ /// - /// Changes from load string - void ReloadPrototypes(Dictionary> prototypes); + void ReloadPrototypes(Dictionary> modified, + Dictionary>? removed = null); /// /// Registers a specific prototype name to be ignored. @@ -245,7 +262,7 @@ public interface IPrototypeManager void RegisterKind(Type kind); /// - /// Fired when prototype are reloaded. The event args contain the modified prototypes. + /// Fired when prototype are reloaded. The event args contain the modified and removed prototypes. /// /// /// This does NOT fire on initial prototype load. @@ -259,7 +276,8 @@ internal interface IPrototypeManagerInternal : IPrototypeManager } public sealed record PrototypesReloadedEventArgs( - IReadOnlyDictionary ByType) + IReadOnlyDictionary ByType, + IReadOnlyDictionary>? Removed = null) { public sealed record PrototypeChangeSet(IReadOnlyDictionary Modified); } diff --git a/Robust.Shared/Prototypes/PrototypeManager.cs b/Robust.Shared/Prototypes/PrototypeManager.cs index 62ee1fcbb..4f1b7da1b 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.cs @@ -7,9 +7,9 @@ using System.Threading.Channels; using System.Threading.Tasks; using Robust.Shared.Asynchronous; using Robust.Shared.ContentPack; -using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.IoC.Exceptions; +using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Random; using Robust.Shared.Reflection; @@ -17,19 +17,18 @@ using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Serialization.Markdown.Value; -using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Robust.Shared.Prototypes { - [Virtual] - public partial class PrototypeManager : IPrototypeManagerInternal + public abstract partial class PrototypeManager : IPrototypeManagerInternal { [Dependency] private readonly IReflectionManager _reflectionManager = default!; [Dependency] protected readonly IResourceManager Resources = default!; [Dependency] protected readonly ITaskManager TaskManager = default!; [Dependency] private readonly ISerializationManager _serializationManager = default!; [Dependency] private readonly ILogManager _logManager = default!; + [Dependency] private readonly ILocalizationManager _locMan = default!; private readonly Dictionary _kindNames = new(); private readonly Dictionary _kindPriorities = new(); @@ -188,6 +187,32 @@ namespace Robust.Shared.Prototypes _kinds.Clear(); } + public void Reset() + { + var removed = _kinds.ToDictionary( + x => x.Key, + x => x.Value.Instances.Keys.ToHashSet()); + + ReloadPrototypeKinds(); + Dictionary> prototypes = new(); + LoadDefaultPrototypes(prototypes); + + foreach (var (kind, ids) in prototypes) + { + if (!removed.TryGetValue(kind, out var removedIds)) + continue; + + removedIds.ExceptWith(ids); + if (removedIds.Count == 0) + removed.Remove(kind); + } + + ReloadPrototypes(prototypes, removed); + _locMan.ReloadLocalizations(); + } + + public abstract void LoadDefaultPrototypes(Dictionary>? changed = null); + private int SortPrototypesByPriority(Type a, Type b) { return _kindPriorities[b].CompareTo(_kindPriorities[a]); @@ -206,10 +231,11 @@ namespace Robust.Shared.Prototypes #endif } - public void ReloadPrototypes(Dictionary> prototypes) + public void ReloadPrototypes(Dictionary> modified, + Dictionary>? removed = null) { #if TOOLS - var prototypeTypeOrder = prototypes.Keys.ToList(); + var prototypeTypeOrder = modified.Keys.ToList(); prototypeTypeOrder.Sort(SortPrototypesByPriority); var pushed = new Dictionary>(); @@ -219,7 +245,7 @@ namespace Robust.Shared.Prototypes var kindData = _kinds[kind]; if (!kind.IsAssignableTo(typeof(IInheritingPrototype))) { - foreach (var id in prototypes[kind]) + foreach (var id in modified[kind]) { var prototype = (IPrototype)_serializationManager.Read(kind, kindData.Results[id])!; kindData.Instances[id] = prototype; @@ -230,7 +256,7 @@ namespace Robust.Shared.Prototypes var tree = kindData.Inheritance!; var processQueue = new Queue(); - foreach (var id in prototypes[kind]) + foreach (var id in modified[kind]) { processQueue.Enqueue(id); } @@ -245,7 +271,7 @@ namespace Robust.Shared.Prototypes foreach (var parent in parents) { //our parent has been reloaded and has not been added to the pushedSet yet - if (prototypes[kind].Contains(parent) && !pushedSet.Contains(parent)) + if (modified[kind].Contains(parent) && !pushedSet.Contains(parent)) { //we re-queue ourselves at the end of the queue processQueue.Enqueue(id); @@ -285,12 +311,13 @@ namespace Robust.Shared.Prototypes //todo paul i hate it but i am not opening that can of worms in this refactor PrototypesReloaded?.Invoke( new PrototypesReloadedEventArgs( - prototypes + modified .ToDictionary( g => g.Key, g => new PrototypesReloadedEventArgs.PrototypeChangeSet( g.Value.Where(x => _kinds[g.Key].Instances.ContainsKey(x)) - .ToDictionary(a => a, a => _kinds[g.Key].Instances[a]))))); + .ToDictionary(a => a, a => _kinds[g.Key].Instances[a]))), + removed)); } /// @@ -506,7 +533,7 @@ namespace Robust.Shared.Prototypes #endregion IPrototypeManager members - private void ReloadPrototypeKinds() + public void ReloadPrototypeKinds() { Clear(); foreach (var type in _reflectionManager.GetAllChildren()) diff --git a/Robust.UnitTesting/Server/RobustServerSimulation.cs b/Robust.UnitTesting/Server/RobustServerSimulation.cs index b202a8933..c0e5de467 100644 --- a/Robust.UnitTesting/Server/RobustServerSimulation.cs +++ b/Robust.UnitTesting/Server/RobustServerSimulation.cs @@ -12,6 +12,7 @@ using Robust.Server.GameObjects; using Robust.Server.GameStates; using Robust.Server.Physics; using Robust.Server.Player; +using Robust.Server.Prototypes; using Robust.Server.Reflection; using Robust.Server.Replays; using Robust.Shared; @@ -210,6 +211,7 @@ namespace Robust.UnitTesting.Server .Setup(x => x.FindAllTypes()) .Returns(() => realReflection.FindAllTypes()); + container.RegisterInstance(new Mock().Object); container.RegisterInstance(reflectionManager.Object); // tests should not be searching for types container.RegisterInstance(new Mock().Object); container.RegisterInstance(new Mock().Object); // no disk access for tests @@ -221,7 +223,7 @@ namespace Robust.UnitTesting.Server container.Register(); container.Register(); container.Register(); - container.Register(); + container.Register(); container.Register(); container.Register(); container.Register();