diff --git a/Robust.Shared.IntegrationTests/GameObjects/EntitySystemManagerOrderTest.cs b/Robust.Shared.IntegrationTests/GameObjects/EntitySystemManagerOrderTest.cs index 8cf31f528..9762c89b2 100644 --- a/Robust.Shared.IntegrationTests/GameObjects/EntitySystemManagerOrderTest.cs +++ b/Robust.Shared.IntegrationTests/GameObjects/EntitySystemManagerOrderTest.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Moq; using NUnit.Framework; using Robust.Server.Configuration; @@ -88,7 +85,6 @@ namespace Robust.UnitTesting.Shared.GameObjects deps.Register(); deps.RegisterInstance(new Mock().Object); deps.Register(); - deps.RegisterInstance(new Mock().Object); // WHEN WILL THE SUFFERING END deps.RegisterInstance(new Mock().Object); @@ -104,6 +100,15 @@ namespace Robust.UnitTesting.Shared.GameObjects deps.RegisterInstance(reflectionMock.Object); + // Never + var componentFactoryMock = new Mock(); + componentFactoryMock.Setup(p => p.AllRegisteredTypes).Returns(Enumerable.Empty()); + deps.RegisterInstance(componentFactoryMock.Object); + + var entityManagerMock = new Mock(); + entityManagerMock.Setup(p => p.ComponentFactory).Returns(componentFactoryMock.Object); + deps.RegisterInstance(entityManagerMock.Object); + deps.BuildGraph(); IoCManager.InitThread(deps, true); diff --git a/Robust.Shared.IntegrationTests/GameObjects/EntitySystemManager_Tests.cs b/Robust.Shared.IntegrationTests/GameObjects/EntitySystemManager_Tests.cs index 8a9aa869e..23fbf28c6 100644 --- a/Robust.Shared.IntegrationTests/GameObjects/EntitySystemManager_Tests.cs +++ b/Robust.Shared.IntegrationTests/GameObjects/EntitySystemManager_Tests.cs @@ -1,11 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using Robust.Shared.Analyzers; +using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.IoC.Exceptions; +using Robust.Shared.Physics.Components; namespace Robust.UnitTesting.Shared.GameObjects { @@ -39,6 +38,14 @@ namespace Robust.UnitTesting.Shared.GameObjects [Dependency] public readonly ESystemDepA ESystemDepA = default!; } + internal sealed class ESystemDepAll : EntitySystem + { + [Dependency] public readonly ESystemDepA ESystemDepA = default!; + [Dependency] public readonly IConfigurationManager Config = default!; + [Dependency] public readonly EntityQuery TransformQuery = default!; + [Dependency] public readonly EntityQuery PhysicsQuery = default!; + } + /* ESystemBase (Abstract) - ESystemA @@ -58,6 +65,7 @@ namespace Robust.UnitTesting.Shared.GameObjects syssy.LoadExtraSystemType(); syssy.LoadExtraSystemType(); syssy.LoadExtraSystemType(); + syssy.LoadExtraSystemType(); syssy.Initialize(false); } @@ -103,5 +111,16 @@ namespace Robust.UnitTesting.Shared.GameObjects Assert.That(sysB.ESystemDepA, Is.EqualTo(sysA)); } + [Test] + public void DependencyInjectionTest() + { + var esm = IoCManager.Resolve(); + var sys = esm.GetEntitySystem(); + + Assert.That(sys.ESystemDepA, Is.Not.Null); + Assert.That(sys.Config, Is.Not.Null); + Assert.That(sys.TransformQuery, Is.Not.Default); + Assert.That(sys.PhysicsQuery, Is.Not.Default); + } } } diff --git a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs index e55c2aa05..a170c3474 100644 --- a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs +++ b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs @@ -32,7 +32,8 @@ namespace Robust.Shared.GameObjects protected BoundUserInterface(EntityUid owner, Enum uiKey) { - IoCManager.InjectDependencies(this); + IoCManager.Resolve(ref EntMan); + EntMan.EntitySysManager.DependencyCollection.InjectDependencies(this); UiSystem = EntMan.System(); Owner = owner; diff --git a/Robust.Shared/GameObjects/EntitySystemManager.cs b/Robust.Shared/GameObjects/EntitySystemManager.cs index 879ab2be1..3a4d3eddb 100644 --- a/Robust.Shared/GameObjects/EntitySystemManager.cs +++ b/Robust.Shared/GameObjects/EntitySystemManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using Prometheus; using Robust.Shared.IoC; using Robust.Shared.IoC.Exceptions; @@ -192,6 +193,14 @@ namespace Robust.Shared.GameObjects _systemTypes.Remove(baseType); } + var queryMethod = typeof(EntityManager).GetMethod(nameof(EntityManager.GetEntityQuery), 1, [])!; + SystemDependencyCollection.RegisterBaseGenericLazy( + typeof(EntityQuery<>), + (queryType, dep) => queryMethod + .MakeGenericMethod(queryType.GetGenericArguments()[0]) + .Invoke(dep.Resolve(), null)! + ); + SystemDependencyCollection.BuildGraph(); foreach (var systemType in _systemTypes) diff --git a/Robust.Shared/IoC/DependencyCollection.cs b/Robust.Shared/IoC/DependencyCollection.cs index 6a0c3f225..f9eb5a778 100644 --- a/Robust.Shared/IoC/DependencyCollection.cs +++ b/Robust.Shared/IoC/DependencyCollection.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Frozen; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -17,6 +18,11 @@ namespace Robust.Shared.IoC public delegate T DependencyFactoryDelegate() where T : class; + public delegate T DependencyFactoryBaseGenericLazyDelegate( + Type type, + IDependencyCollection services) + where T : class; + /// internal sealed class DependencyCollection : IDependencyCollection { @@ -37,6 +43,12 @@ namespace Robust.Shared.IoC /// private FrozenDictionary _services = FrozenDictionary.Empty; + /// + /// Dictionary that maps the types passed to to their implementation + /// for any types registered through . + /// + private readonly ConcurrentDictionary _lazyServices = new(); + // Start fields used for building new services. /// @@ -48,6 +60,8 @@ namespace Robust.Shared.IoC private readonly Dictionary> _resolveFactories = new(); private readonly Queue _pendingResolves = new(); + private readonly ConcurrentDictionary> _baseGenericLazyFactories = new(); + private readonly object _serviceBuildLock = new(); // End fields for building new services. @@ -79,8 +93,8 @@ namespace Robust.Shared.IoC public IEnumerable GetRegisteredTypes() { return _parentCollection != null - ? _services.Keys.Concat(_parentCollection.GetRegisteredTypes()) - : _services.Keys; + ? _services.Keys.Concat(_lazyServices.Keys).Concat(_parentCollection.GetRegisteredTypes()) + : _services.Keys.Concat(_lazyServices.Keys); } public Type[] GetCachedInjectorTypes() @@ -116,10 +130,7 @@ namespace Robust.Shared.IoC FrozenDictionary services, [MaybeNullWhen(false)] out object instance) { - if (!services.TryGetValue(objectType, out instance)) - return _parentCollection is not null && _parentCollection.TryResolveType(objectType, out instance); - - return true; + return TryResolveType(objectType, (IReadOnlyDictionary) services, out instance); } private bool TryResolveType( @@ -128,7 +139,16 @@ namespace Robust.Shared.IoC [MaybeNullWhen(false)] out object instance) { if (!services.TryGetValue(objectType, out instance)) + { + if (objectType.IsGenericType && + _baseGenericLazyFactories.TryGetValue(objectType.GetGenericTypeDefinition(), out var factory)) + { + instance = _lazyServices.GetOrAdd(objectType, type => factory(type, this)); + return true; + } + return _parentCollection is not null && _parentCollection.TryResolveType(objectType, out instance); + } return true; } @@ -267,7 +287,7 @@ namespace Robust.Shared.IoC _pendingResolves.Enqueue(interfaceType); } } - + private void CheckRegisterInterface(Type interfaceType, Type implementationType, bool overwrite) { lock (_serviceBuildLock) @@ -312,15 +332,24 @@ namespace Robust.Shared.IoC Register(type, implementation.GetType(), () => implementation, overwrite); } + public void RegisterBaseGenericLazy(Type interfaceType, DependencyFactoryBaseGenericLazyDelegate factory) + { + lock (_serviceBuildLock) + { + _baseGenericLazyFactories[interfaceType] = factory; + } + } + /// public void Clear() { - foreach (var service in _services.Values.OfType().Distinct()) + foreach (var service in _services.Values.Concat(_lazyServices.Values).OfType().Distinct()) { service.Dispose(); } _services = FrozenDictionary.Empty; + _lazyServices.Clear(); lock (_serviceBuildLock) { diff --git a/Robust.Shared/IoC/IDependencyCollection.cs b/Robust.Shared/IoC/IDependencyCollection.cs index 866a78c7c..9df466dbd 100644 --- a/Robust.Shared/IoC/IDependencyCollection.cs +++ b/Robust.Shared/IoC/IDependencyCollection.cs @@ -132,6 +132,15 @@ namespace Robust.Shared.IoC /// void RegisterInstance(Type type, object implementation, bool overwrite = false); + /// + /// Adds a callback to be called when attempting to resolve an unresolved type that matches the specified + /// base generic type, making it accessible to . + /// This instance will only be created the first time that it is attempted to be resolved. + /// + /// The base generic type of the type that will be resolvable. + /// The callback to call to get an instance of the implementation for that generic type. + void RegisterBaseGenericLazy(Type genericType, DependencyFactoryBaseGenericLazyDelegate factory); + /// /// Clear all services and types. /// Use this between unit tests and on program shutdown.