Added load order restrictions for DependencyAttribute

Abstracted dependency manager away as an interface
This commit is contained in:
Westin Miller
2017-08-18 02:03:53 -07:00
parent 42f58a8649
commit 80d4f62694
13 changed files with 220 additions and 103 deletions

View File

@@ -44,6 +44,9 @@ namespace Torch.API
/// <inheritdoc cref="IPluginManager"/> /// <inheritdoc cref="IPluginManager"/>
IPluginManager Plugins { get; } IPluginManager Plugins { get; }
/// <inheritdoc cref="IDependencyManager"/>
IDependencyManager Managers { get; }
/// <summary> /// <summary>
/// The binary version of the current instance. /// The binary version of the current instance.
/// </summary> /// </summary>
@@ -90,12 +93,6 @@ namespace Torch.API
/// Initialize the Torch instance. /// Initialize the Torch instance.
/// </summary> /// </summary>
void Init(); void Init();
/// <summary>
/// Get an <see cref="IManager"/> that is part of the Torch instance.
/// </summary>
/// <typeparam name="T">Manager type</typeparam>
T GetManager<T>() where T : class, IManager;
} }
/// <summary> /// <summary>

View File

@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.API.Managers
{
/// <summary>
/// Manages a set of <see cref="IManager"/> and the dependencies between them.
/// </summary>
public interface IDependencyManager : IDependencyProvider
{
/// <summary>
/// Registers the given manager into the dependency system.
/// </summary>
/// <remarks>
/// This method only returns false when there is already a manager registered with a type derived from this given manager,
/// or when the given manager is derived from an already existing manager.
/// </remarks>
/// <param name="manager">Manager to register</param>
/// <exception cref="InvalidOperationException">When adding a new manager to an initialized dependency manager</exception>
/// <returns>true if added, false if not</returns>
bool AddManager(IManager manager);
/// <summary>
/// Clears all managers registered with this dependency manager
/// </summary>
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
void ClearManagers();
/// <summary>
/// Removes a single manager from this dependency manager.
/// </summary>
/// <param name="manager">The manager to remove</param>
/// <returns>true if successful, false if the manager wasn't found</returns>
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
bool RemoveManager(IManager manager);
/// <summary>
/// Initializes the dependency manager, and all its registered managers.
/// </summary>
void Init();
/// <summary>
/// Disposes the dependency manager, and all its registered managers.
/// </summary>
void Dispose();
/// <summary>
/// The order that managers should be loaded in. (Dependencies, then dependents)
/// </summary>
/// <exception cref="InvalidOperationException">When trying to determine load order before this dependency manager is initialized</exception>
IEnumerable<IManager> LoadOrder { get; }
/// <summary>
/// The order that managers should be unloaded in. (Dependents, then dependencies)
/// </summary>
/// <exception cref="InvalidOperationException">When trying to determine unload order before this dependency manager is initialized</exception>
IEnumerable<IManager> UnloadOrder { get; }
}
public static class DependencyManagerExtensions
{
/// <summary>
/// Removes a single manager from this dependency manager.
/// </summary>
/// <param name="managerType">The dependency type to remove</param>
/// <returns>The manager that was removed, or null if one wasn't removed</returns>
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
public static IManager RemoveManager(this IDependencyManager depManager, Type managerType)
{
IManager mgr = depManager.GetManager(managerType);
return depManager.RemoveManager(mgr) ? mgr : null;
}
/// <summary>
/// Removes a single manager from this dependency manager.
/// </summary>
/// <typeparam name="T">The dependency type to remove</typeparam>
/// <returns>The manager that was removed, or null if one wasn't removed</returns>
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
public static IManager RemoveManager<T>(this IDependencyManager depManager)
{
return depManager.RemoveManager(typeof(T));
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.API.Managers
{
public interface IDependencyProvider
{
/// <summary>
/// Gets the manager that provides the given type. If there is no such manager, returns null.
/// </summary>
/// <param name="type">Type of manager</param>
/// <returns>manager, or null if none exists</returns>
IManager GetManager(Type type);
}
public static class DependencyProviderExtensions
{
/// <summary>
/// Gets the manager that provides the given type. If there is no such manager, returns null.
/// </summary>
/// <typeparam name="T">Type of manager</typeparam>
/// <returns>manager, or null if none exists</returns>
public static T GetManager<T>(this IDependencyProvider depProvider) where T : class, IManager
{
return (T)depProvider.GetManager(typeof(T));
}
}
}

View File

@@ -26,11 +26,6 @@ namespace Torch.API.Managers
/// </summary> /// </summary>
void UpdatePlugins(); void UpdatePlugins();
/// <summary>
/// Disposes all loaded plugins.
/// </summary>
void DisposePlugins();
/// <summary> /// <summary>
/// Load plugins. /// Load plugins.
/// </summary> /// </summary>

View File

@@ -157,6 +157,8 @@
<Compile Include="ConnectionState.cs" /> <Compile Include="ConnectionState.cs" />
<Compile Include="IChatMessage.cs" /> <Compile Include="IChatMessage.cs" />
<Compile Include="ITorchConfig.cs" /> <Compile Include="ITorchConfig.cs" />
<Compile Include="Managers\IDependencyManager.cs" />
<Compile Include="Managers\IDependencyProvider.cs" />
<Compile Include="Managers\IManager.cs" /> <Compile Include="Managers\IManager.cs" />
<Compile Include="Managers\IMultiplayerManager.cs" /> <Compile Include="Managers\IMultiplayerManager.cs" />
<Compile Include="IPlayer.cs" /> <Compile Include="IPlayer.cs" />

View File

@@ -1,5 +1,6 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using Torch.API.Managers;
using Torch.Server.Managers; using Torch.Server.Managers;
using Torch.Server.ViewModels; using Torch.Server.ViewModels;
@@ -15,7 +16,7 @@ namespace Torch.Server.Views
public ConfigControl() public ConfigControl()
{ {
InitializeComponent(); InitializeComponent();
_instanceManager = TorchBase.Instance.GetManager<InstanceManager>(); _instanceManager = TorchBase.Instance.Managers.GetManager<InstanceManager>();
DataContext = _instanceManager.DedicatedConfig; DataContext = _instanceManager.DedicatedConfig;
} }

View File

@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using System.Timers; using System.Timers;
using Sandbox.ModAPI; using Sandbox.ModAPI;
using Torch; using Torch;
using Torch.API.Managers;
using Torch.Commands.Permissions; using Torch.Commands.Permissions;
using Torch.Managers; using Torch.Managers;
using VRage.Game.ModAPI; using VRage.Game.ModAPI;
@@ -50,7 +51,7 @@ namespace Torch.Commands
[Command("longhelp", "Get verbose help. Will send a long message, check the Comms tab.")] [Command("longhelp", "Get verbose help. Will send a long message, check the Comms tab.")]
public void LongHelp() public void LongHelp()
{ {
var commandManager = Context.Torch.GetManager<CommandManager>(); var commandManager = Context.Torch.Managers.GetManager<CommandManager>();
commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node); commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node);
if (node != null) if (node != null)

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@@ -7,20 +8,22 @@ using Torch.API.Managers;
namespace Torch.Managers namespace Torch.Managers
{ {
public sealed class DependencyManager public sealed class DependencyManager : IDependencyManager
{ {
private static readonly Logger _log = LogManager.GetCurrentClassLogger(); private static readonly Logger _log = LogManager.GetCurrentClassLogger();
private class DependencyInfo private class DependencyInfo
{ {
private readonly Manager.DependencyAttribute _attribute;
internal Type DependencyType => Field.FieldType; internal Type DependencyType => Field.FieldType;
internal FieldInfo Field { get; private set; } internal FieldInfo Field { get; private set; }
internal bool Optional { get; private set; } internal bool Optional => _attribute.Optional;
internal bool Ordered => _attribute.Ordered;
public DependencyInfo(FieldInfo field) public DependencyInfo(FieldInfo field)
{ {
Field = field; Field = field;
Optional = field.GetCustomAttribute<Manager.DependencyAttribute>().Optional; _attribute = field.GetCustomAttribute<Manager.DependencyAttribute>();
} }
} }
@@ -69,18 +72,14 @@ namespace Torch.Managers
private readonly Dictionary<Type, ManagerInstance> _dependencySatisfaction; private readonly Dictionary<Type, ManagerInstance> _dependencySatisfaction;
private readonly List<ManagerInstance> _registeredManagers; private readonly List<ManagerInstance> _registeredManagers;
private readonly List<ManagerInstance> _orderedManagers; private readonly List<ManagerInstance> _orderedManagers;
private readonly DependencyManager _parentManager; private readonly IDependencyProvider[] _parentProviders;
public DependencyManager(DependencyManager parent = null) public DependencyManager(params IDependencyProvider[] parents)
{ {
_dependencySatisfaction = new Dictionary<Type, ManagerInstance>(); _dependencySatisfaction = new Dictionary<Type, ManagerInstance>();
_registeredManagers = new List<ManagerInstance>(); _registeredManagers = new List<ManagerInstance>();
_orderedManagers = new List<ManagerInstance>(); _orderedManagers = new List<ManagerInstance>();
_parentManager = parent; _parentProviders = parents.Distinct().ToArray();
if (parent == null)
return;
foreach (KeyValuePair<Type, ManagerInstance> kv in parent._dependencySatisfaction)
_dependencySatisfaction.Add(kv.Key, kv.Value);
} }
private void AddDependencySatisfaction(ManagerInstance instance) private void AddDependencySatisfaction(ManagerInstance instance)
@@ -100,16 +99,7 @@ namespace Torch.Managers
AddDependencySatisfaction(manager); AddDependencySatisfaction(manager);
} }
/// <summary> /// <inheritdoc/>
/// Registers the given manager into the dependency system.
/// </summary>
/// <remarks>
/// This method only returns false when there is already a manager registered with a type derived from this given manager,
/// or when the given manager is derived from an already existing manager.
/// </remarks>
/// <param name="manager">Manager to register</param>
/// <exception cref="InvalidOperationException">When adding a new manager to an initialized dependency manager</exception>
/// <returns>true if added, false if not</returns>
public bool AddManager(IManager manager) public bool AddManager(IManager manager)
{ {
if (_initialized) if (_initialized)
@@ -122,16 +112,13 @@ namespace Torch.Managers
if (_registeredManagers.Any(x => manager.GetType().IsInstanceOfType(x.Instance))) if (_registeredManagers.Any(x => manager.GetType().IsInstanceOfType(x.Instance)))
return false; return false;
var instance = new ManagerInstance(manager); ManagerInstance instance = new ManagerInstance(manager);
_registeredManagers.Add(instance); _registeredManagers.Add(instance);
AddDependencySatisfaction(instance); AddDependencySatisfaction(instance);
return true; return true;
} }
/// <summary> /// <inheritdoc/>
/// Clears all managers registered with this dependency manager
/// </summary>
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
public void ClearManagers() public void ClearManagers()
{ {
if (_initialized) if (_initialized)
@@ -141,19 +128,14 @@ namespace Torch.Managers
_dependencySatisfaction.Clear(); _dependencySatisfaction.Clear();
} }
/// <summary> /// <inheritdoc/>
/// Removes a single manager from this dependency manager.
/// </summary>
/// <param name="manager">The manager to remove</param>
/// <returns>true if successful, false if the manager wasn't found</returns>
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
public bool RemoveManager(IManager manager) public bool RemoveManager(IManager manager)
{ {
if (_initialized) if (_initialized)
throw new InvalidOperationException("Can't remove managers from an initialized dependency manager"); throw new InvalidOperationException("Can't remove managers from an initialized dependency manager");
if (manager == null) if (manager == null)
return false; return false;
for (var i = 0; i < _registeredManagers.Count; i++) for (int i = 0; i < _registeredManagers.Count; i++)
if (_registeredManagers[i].Instance == manager) if (_registeredManagers[i].Instance == manager)
{ {
_registeredManagers.RemoveAtFast(i); _registeredManagers.RemoveAtFast(i);
@@ -163,29 +145,6 @@ namespace Torch.Managers
return false; return false;
} }
/// <summary>
/// Removes a single manager from this dependency manager.
/// </summary>
/// <param name="type">The dependency type to remove</param>
/// <returns>The manager that was removed, or null if one wasn't removed</returns>
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
public IManager RemoveManager(Type type)
{
IManager mgr = GetManager(type);
return RemoveManager(mgr) ? mgr : null;
}
/// <summary>
/// Removes a single manager from this dependency manager.
/// </summary>
/// <typeparam name="T">The dependency type to remove</typeparam>
/// <returns>The manager that was removed, or null if one wasn't removed</returns>
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
public IManager RemoveManager<T>()
{
return RemoveManager(typeof(T));
}
private void Sort() private void Sort()
{ {
// Resets the previous sort results // Resets the previous sort results
@@ -204,11 +163,14 @@ namespace Torch.Managers
foreach (DependencyInfo dependency in manager.Dependencies) foreach (DependencyInfo dependency in manager.Dependencies)
{ {
if (_dependencySatisfaction.TryGetValue(dependency.DependencyType, out var dependencyInstance)) if (_dependencySatisfaction.TryGetValue(dependency.DependencyType, out var dependencyInstance))
{
if (dependency.Ordered)
{ {
inFactor++; inFactor++;
dependencyInstance.Dependents.Add(manager); dependencyInstance.Dependents.Add(manager);
} }
else if (!dependency.Optional && _parentManager?.GetManager(dependency.DependencyType) == null) }
else if (!dependency.Optional && _parentProviders.All(x => x.GetManager(dependency.DependencyType) == null))
_log.Warn("Unable to satisfy dependency {0} ({1}) of {2}.", dependency.DependencyType.Name, _log.Warn("Unable to satisfy dependency {0} ({1}) of {2}.", dependency.DependencyType.Name,
dependency.Field.Name, manager.Instance.GetType().Name); dependency.Field.Name, manager.Instance.GetType().Name);
} }
@@ -305,28 +267,43 @@ namespace Torch.Managers
_initialized = false; _initialized = false;
} }
/// <inheritdoc/>
public IEnumerable<IManager> LoadOrder
{
get
{
if (!_initialized)
throw new InvalidOperationException("Can't determine dependency load order when uninitialized");
foreach (ManagerInstance k in _orderedManagers)
yield return k.Instance;
}
}
/// <summary> /// <inheritdoc/>
/// Gets the manager that provides the given type. If there is no such manager, returns null. public IEnumerable<IManager> UnloadOrder
/// </summary> {
/// <param name="type">Type of manager</param> get
/// <returns>manager, or null if none exists</returns> {
if (!_initialized)
throw new InvalidOperationException("Can't determine dependency load order when uninitialized");
for (int i = _orderedManagers.Count - 1; i >= 0; i--)
yield return _orderedManagers[i].Instance;
}
}
/// <inheritdoc/>
public IManager GetManager(Type type) public IManager GetManager(Type type)
{ {
// ReSharper disable once ConvertIfStatementToReturnStatement // ReSharper disable once ConvertIfStatementToReturnStatement
if (_dependencySatisfaction.TryGetValue(type, out ManagerInstance mgr)) if (_dependencySatisfaction.TryGetValue(type, out ManagerInstance mgr))
return mgr.Instance; return mgr.Instance;
return _parentManager?.GetManager(type); foreach (IDependencyProvider provider in _parentProviders)
}
/// <summary>
/// Gets the manager that provides the given type. If there is no such manager, returns null.
/// </summary>
/// <typeparam name="T">Type of manager</typeparam>
/// <returns>manager, or null if none exists</returns>
public T GetManager<T>() where T : class, IManager
{ {
return (T)GetManager(typeof(T)); IManager entry = provider.GetManager(type);
if (entry != null)
return entry;
}
return null;
} }
} }
} }

View File

@@ -31,7 +31,29 @@ namespace Torch.Managers
[AttributeUsage(AttributeTargets.Field)] [AttributeUsage(AttributeTargets.Field)]
public class DependencyAttribute : Attribute public class DependencyAttribute : Attribute
{ {
/// <summary>
/// If this dependency isn't required.
/// </summary>
/// <remarks>
/// The tagged field can be null if, and only if, this is true.
/// </remarks>
public bool Optional { get; set; } = false; public bool Optional { get; set; } = false;
/// <summary>
/// Dependency must be loaded before and unloaded after the containing manager.
/// </summary>
/// <example>
/// <code>
/// public class NetworkManager : Manager { }
/// public class ChatManager : Manager {
/// [Dependency(Ordered = true)]
/// private NetworkManager _network;
/// }
/// </code>
/// Load order will be NetworkManager, then ChatManager.
/// Unload order will be ChatManager, then NetworkManager
/// </example>
public bool Ordered { get; set; } = true;
} }
protected ITorchBase Torch { get; } protected ITorchBase Torch { get; }

View File

@@ -48,7 +48,7 @@ namespace Torch.Managers
/// <summary> /// <summary>
/// Unloads all plugins. /// Unloads all plugins.
/// </summary> /// </summary>
public void DisposePlugins() public override void Dispose()
{ {
foreach (var plugin in Plugins) foreach (var plugin in Plugins)
plugin.Dispose(); plugin.Dispose();

View File

@@ -146,7 +146,7 @@ namespace Torch.Managers
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public override void Dispose()
{ {
_updatePollTimer?.Dispose(); _updatePollTimer?.Dispose();
} }

View File

@@ -181,6 +181,7 @@
<Compile Include="TorchBase.cs" /> <Compile Include="TorchBase.cs" />
<Compile Include="SteamService.cs" /> <Compile Include="SteamService.cs" />
<Compile Include="TorchPluginBase.cs" /> <Compile Include="TorchPluginBase.cs" />
<Compile Include="TorchSession.cs" />
<Compile Include="ViewModels\ModViewModel.cs" /> <Compile Include="ViewModels\ModViewModel.cs" />
<Compile Include="Collections\MTObservableCollection.cs" /> <Compile Include="Collections\MTObservableCollection.cs" />
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" /> <Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />

View File

@@ -73,7 +73,8 @@ namespace Torch
/// </summary> /// </summary>
protected static Logger Log { get; } = LogManager.GetLogger("Torch"); protected static Logger Log { get; } = LogManager.GetLogger("Torch");
private readonly DependencyManager _managers; /// <inheritdoc/>
public IDependencyManager Managers { get; }
private bool _init; private bool _init;
@@ -91,7 +92,7 @@ namespace Torch
TorchVersion = Assembly.GetExecutingAssembly().GetName().Version; TorchVersion = Assembly.GetExecutingAssembly().GetName().Version;
RunArgs = new string[0]; RunArgs = new string[0];
_managers = new DependencyManager(); Managers = new DependencyManager();
Plugins = new PluginManager(this); Plugins = new PluginManager(this);
Multiplayer = new MultiplayerManager(this); Multiplayer = new MultiplayerManager(this);
@@ -99,14 +100,14 @@ namespace Torch
Network = new NetworkManager(this); Network = new NetworkManager(this);
Commands = new CommandManager(this); Commands = new CommandManager(this);
_managers.AddManager(new FilesystemManager(this)); Managers.AddManager(new FilesystemManager(this));
_managers.AddManager(new UpdateManager(this)); Managers.AddManager(new UpdateManager(this));
_managers.AddManager(Network); Managers.AddManager(Network);
_managers.AddManager(Commands); Managers.AddManager(Commands);
_managers.AddManager(Plugins); Managers.AddManager(Plugins);
_managers.AddManager(Multiplayer); Managers.AddManager(Multiplayer);
_managers.AddManager(Entities); Managers.AddManager(Entities);
_managers.AddManager(new ChatManager(this)); Managers.AddManager(new ChatManager(this));
TorchAPI.Instance = this; TorchAPI.Instance = this;
@@ -115,13 +116,13 @@ namespace Torch
/// <inheritdoc /> /// <inheritdoc />
public T GetManager<T>() where T : class, IManager public T GetManager<T>() where T : class, IManager
{ {
return _managers.GetManager<T>(); return Managers.GetManager<T>();
} }
/// <inheritdoc /> /// <inheritdoc />
public bool AddManager<T>(T manager) where T : class, IManager public bool AddManager<T>(T manager) where T : class, IManager
{ {
return _managers.AddManager(manager); return Managers.AddManager(manager);
} }
public bool IsOnGameThread() public bool IsOnGameThread()
@@ -249,7 +250,7 @@ namespace Torch
MySession.OnUnloading += OnSessionUnloading; MySession.OnUnloading += OnSessionUnloading;
MySession.OnUnloaded += OnSessionUnloaded; MySession.OnUnloaded += OnSessionUnloaded;
RegisterVRagePlugin(); RegisterVRagePlugin();
_managers.Init(); Managers.Init();
_init = true; _init = true;
} }
@@ -317,7 +318,7 @@ namespace Torch
/// <inheritdoc /> /// <inheritdoc />
public virtual void Dispose() public virtual void Dispose()
{ {
Plugins.DisposePlugins(); Managers.Dispose();
} }
/// <inheritdoc /> /// <inheritdoc />