using System; using System.Collections.Generic; using System.Linq; using Robust.Shared.Log; using Robust.Shared.Utility; namespace Robust.Shared.Input.Binding { /// public sealed class CommandBindRegistry : ICommandBindRegistry { // all registered bindings private List _bindings = new(); // handlers in the order they should be resolved for the given key function. // internally we use a graph to construct this but we render it down to a flattened // list so we don't need to do any graph traversal at query time private Dictionary> _bindingsForKey = new(); private bool _graphDirty = false; /// public void Register(CommandBinds commandBinds) { Register(commandBinds, typeof(TOwner)); } /// public void Register(CommandBinds commandBinds, Type owner) { if (_bindings.Any(existing => existing.ForType == owner)) { // feel free to delete this if there's an actual need for registering multiple // bindings for a given type in separate calls to Register() Logger.Warning("Command binds already registered for type {0}, but you are trying" + " to register more. This may " + "be a programming error. Did you register these under the wrong type, or " + "did you forget to unregister these bindings when" + " your system / manager is shutdown?", owner.Name); } foreach (var binding in commandBinds.Bindings) { _bindings.Add(new TypedCommandBind(owner, binding)); } _graphDirty = true; } /// public IEnumerable GetHandlers(BoundKeyFunction function) { if (_graphDirty) RebuildGraph(); if (_bindingsForKey.TryGetValue(function, out var handlers)) { return handlers; } return Enumerable.Empty(); } /// public void Unregister(Type owner) { _bindings.RemoveAll(binding => binding.ForType == owner); _graphDirty = true; } /// public void Unregister() { Unregister(typeof(TOwner)); } internal void RebuildGraph() { _bindingsForKey.Clear(); foreach (var functionBindings in FunctionToBindings()) { _bindingsForKey[functionBindings.Key] = ResolveDependencies(functionBindings.Key, functionBindings.Value); } _graphDirty = false; } private Dictionary> FunctionToBindings() { var functionToBindings = new Dictionary>(); foreach (var typeBinding in _bindings) { if (!functionToBindings.ContainsKey(typeBinding.CommandBind.BoundKeyFunction)) { functionToBindings[typeBinding.CommandBind.BoundKeyFunction] = new List(); } functionToBindings[typeBinding.CommandBind.BoundKeyFunction].Add(typeBinding); } return functionToBindings; } /// /// Determines the order in which the indicated bindings handlers should be resolved for a /// particular bound key function /// private List ResolveDependencies(BoundKeyFunction function, List bindingsForFunction) { //TODO: Probably could be optimized if needed! Generally shouldn't be a big issue since there is a relatively // tiny amount of bindings List> allNodes = new(); Dictionary>> typeToNode = new(); // build the dict for quick lookup on type foreach (var binding in bindingsForFunction) { if (!typeToNode.ContainsKey(binding.ForType)) { typeToNode[binding.ForType] = new List>(); } var newNode = new TopologicalSort.GraphNode(binding); typeToNode[binding.ForType].Add(newNode); allNodes.Add(newNode); } //add the graph edges foreach (var curBinding in allNodes) { foreach (var afterType in curBinding.Value.CommandBind.After) { // curBinding should always fire after bindings associated with this afterType, i.e. // this binding DEPENDS ON afterTypes' bindings if (typeToNode.TryGetValue(afterType, out var afterBindings)) { foreach (var afterBinding in afterBindings) { afterBinding.Dependant.Add(curBinding); } } } foreach (var beforeType in curBinding.Value.CommandBind.Before) { // curBinding should always fire before bindings associated with this beforeType, i.e. // beforeTypes' bindings DEPENDS ON this binding if (typeToNode.TryGetValue(beforeType, out var beforeBindings)) { foreach (var beforeBinding in beforeBindings) { curBinding.Dependant.Add(beforeBinding); } } } } //TODO: Log graph structure for debugging //use toposort to build the final result return TopologicalSort.Sort(allNodes).Select(c => c.CommandBind.Handler).ToList(); } /// /// Command bind which has an associated type. /// The only time a client should need to think about the type for a binding is when they are /// registering a set of bindings, so we don't include this information in CommandBind /// private sealed class TypedCommandBind { public readonly Type ForType; public readonly CommandBind CommandBind; public TypedCommandBind(Type forType, CommandBind commandBind) { ForType = forType; CommandBind = commandBind; } } } }