gustavopsantos / reflex Goto Github PK
View Code? Open in Web Editor NEWMinimal dependency injection framework for Unity
License: MIT License
Minimal dependency injection framework for Unity
License: MIT License
Hello! Great framework, but faced the following situation when used scene context with Unity multiscene workflow:
A
that is loaded first. That leads us to the initialization of scene scoped containerB
in the additive mode. Here we can use container from scene A
.B
. Here we are disposing container that was initialized and scoped on scene A
.As a result any objects created later on scene A
cannot be resolved anymore with the old container as it had been already disposed. And any additionally loaded scenes after all of that cannot be resolved too.
I don't know if that was supposed to work in a such way and if Reflex should support multiscene workflow at all. But it seems that with the small fix it could be changed to work with multiscene correctly. In SceneInjector.cs we need to change DisposeScopedContainer
and add the loaded scene to the closure:
internal static void Inject(Scene scene, ContainerStack containerStack)
{
if (scene.TryFindAtRootObjects<Context>(out var sceneContext))
{
var container = containerStack.PushNew();
sceneContext.InstallBindings(container);
container.InstantiateNonLazySingletons();
Scene loadedScene = scene;
void DisposeScopedContainer(Scene unloadingScene)
{
if (unloadingScene == loadedScene)
{
containerStack.Pop().Dispose();
SceneManager.sceneUnloaded -= DisposeScopedContainer;
}
}
SceneManager.sceneUnloaded += DisposeScopedContainer;
}
foreach (var monoBehaviour in GetEveryMonoBehaviourAtScene(scene))
{
MonoInjector.Inject(monoBehaviour, containerStack);
}
}
So, the container will now only be disposed if its scene is unloaded. And any further additionally loaded scenes could still use previously loaded containers.
Container.BindFunction method was hidden from docs, but it existed in a previous version.
Is it time to let this method go?
Sorry if this ticket should be opened somewhere else...
Describe the bug
Receive this error when opening the cloned project for the first time:
Unable to parse file Packages/com.svermeulen.extenject/package.json.meta: [Parser Failure at line 9: Expect ':' between key and value within mapping]
To Reproduce
Steps to reproduce the behavior:
Expected behaviour
No errors and no dependency on extenject?!
Desktop (please complete the following information):
Additional context
Simply checking out the DI framework for the first time.
To expedite the game development process, I always enable the "Enter Play Mode Settings" option under "Project Settings > Editor" for quicker access to Playmode. However, it seems that there may be an issue with Reflex, as indicated by the logs. Typically, such bugs are related to static data within the framework. If feasible, please incorporate support for the Enter Play Mode Options.
Hello!
What would be a suitable scenario for an application that needs to load remote configuration from an API or load configuration from a file? One approach could involve creating an Initialization scene and transitioning to another scene after the configuration is loaded. However, a challenge arises when using the editor mode and pressing 'Play' on a different game scene. In such cases, it becomes necessary to load the Initialization scene. Unfortunately, calling ReflexSceneManager.LoadScene
from the IInstaller
in the Menu scene (whether in Awake
, Start
, or InstallBindings
methods) can lead to freezing issues in the editor. (2022.3.7f1)
How do others handle asynchronous calls with Reflex?
Is it posible inject things for instantiated entites?
The expression trees implementation used for AOT
platforms is an interpreter, so it is pretty slow compared to a JIT
compiler like Mono. We need to check if theres a good way (clean, readable and scalable) to implement a IL Reweaver
.
References:
Hi
Is it possible to inherit scope from the parent scene instead of the root scope?
This would allow me to avoid placing too many singleton objects in the root scope.
Hi.
If i use ProjectInstaller i catn`t resolve dependencies which was binding in SceneInstaller,
i use method BindInstanceAs and try bind object from scene, from [SerializableField].
Is your feature request related to a problem? Please describe.
I'm often unsure if a certain Type has already been bound to a ContainerDescriptor in GitHub, which leads to confusion and potential errors when working with the code.
Describe the solution you'd like
I would like the author to add a new interface to the ContainerDescriptor type called HasBinding, which would allow users to query whether a certain Type T has already been bound to the ContainerDescriptor.
Describe alternatives you've considered
I have considered manually checking whether a Type has been bound, but this is time-consuming and error-prone.
Additional context
N/A
I want to instantiate gameobject from assets when scene injection is completed. I used that before, when Container was the main thing to bind everything. Do I need to make a new way of how to spawn this gameobject? Create a script that will get Container to spawn the gameobject? Or Forget about SceneContext and handle myself?
public class SceneInstaller : MonoBehaviour, IInstaller
{
[SerializeField] private SystemRunner _systemRunner;
public void InstallBindings(ContainerDescriptor descriptor)
{
Container container = descriptor.Build();
container.Instantiate(_systemRunner);
}
}
After installation attmept of Reflex version 2.0.0 on Unity 2020.3.36f1, I got this error:
Assets/Reflex/Scripts/Core/ContainerStack.cs(27,27): error CS1061: 'Stack<Container>' does not contain a definition for 'TryPop' and no accessible extension method 'TryPop' accepting a first argument of type 'Stack<Container>' could be found (are you missing a using directive or an assembly reference?)
MSDN documentation says that TryPop
available only on .NET Standard 2.1 (here). However, .NET Standard 2.1 became available only on Unity 2022.1, whereas repo readme says that Reflex supports Unity 2019+. Is Reflex v2.0.0 supposed to work only on Unity 2022 now?
Describe the bug
Injected attributes in base class do not work in derived class if private. They do work if defined as protected or public.
To Reproduce
Steps to reproduce the behavior:
[Inject] private ServiceA _serviceA
as private member field, in Start() methods just call _serviceA.Execute()
method [Inject] private ServiceB _serviceB
as private member field, in Start() methods just call base.Start()
and _serviceB.Execute()
methodExpected behaviour
Both string should be printed in the console but only the service B works.
If _serviceA is defined as [Inject] protected ServiceA _serviceA
or [Inject] public ServiceA _serviceA
both strings are printed.
public class ClassA : MonoBehaviour
{
[Inject] private ServiceA _serviceA;
protected virtual void Start()
{
if (_serviceA != null)
{
_serviceA?.Execute();
}
else
{
Debug.Log("service A is not injected");
}
}
}
public class ClassB : ClassA
{
[Inject] private ServiceB _serviceB;
protected override void Start()
{
base.Start();
_serviceB?.Execute();
}
}
public class ServiceA
{
public void Execute()
{
Debug.Log("I am service A");
}
}
public class ServiceB
{
public void Execute()
{
Debug.Log("I am service B");
}
}
public class DependencyInstaller : MonoBehaviour, IInstaller
{
public void InstallBindings(ContainerBuilder containerBuilder)
{
containerBuilder.AddSingleton(new ServiceA());
containerBuilder.AddSingleton(new ServiceB());
}
}
Is your feature request related to a problem? Please describe.
When scenes are loaded additively, objects inside them are not getting their dependencies resolved.
Describe the solution you'd like
Maybe add a custom scene loading handler or custom scene manager.
Describe alternatives you've considered
Listen to scene load events and resolve dependencies. Alternatively, exposing SceneInjector
might do the trick.
Some of the injected dependencies have register/unregister mechanisms that fit very well with OnEnable/OnDisable lifecycle from Unity.
At the moment the way the library uses to get notified about scene loading makes the injected dependencies only ready for Start.
Being unable to use the Unity lifecycle of Awale/OnEnable/OnDisable is quite limiting.
Is there a known workaround or a path forward that could be implemented? I am able to volunteer some developer time to implement the feature if is something that makes sense for the project.
Thanks in advance.
Thank you for this library!
Is it possible to extend Reflex to inject VisualElements? More general, can one extend how stuff-to-be-injected is resolved?
Background
Was looking for something like this (more focused than Zenject, but still complete) when I created UniInject.
I will consider to switch to Reflex as it looks more polished.
My project uses UI Toolkit, which is why I added injection of VisualElements to UniInject (see the test for an example).
(was originally asking this question on the Unity forum)
Tried to upgrade Reflex from version 4.4.1 to version 5.0.0 (because it's the only new version that doesn't cause problems like I thought it would), however after upgrading I get the following errors.
It seems that because Styles both there and there does not lie in any namespace it causes a name conflict. I then tried updating Reflex to the latest version, however that didn't solve the problem either.
Describe the bug
When I use AddSingleton
with already created non-mono instance then this instance doesn't get any deps through [Inject]
inside it. At the same time same code works for AddSingleton(typeof(InstanceClass), typeof(IContract))
. To be clear - I install all deps inside same scope and same installer.
On attached screen you can see that there is UIService
installed with 2 calls (which I believe how many times it was resolved) and MainMenuView
with contract IWindow
which injected through [Inject] private readonly IEnumerable<IWindow> _windows;
to UIService
instance.
To Reproduce
Steps to reproduce the behavior:
Scope
(scene one)[inject]
AddSingleton
with provided instance overloadExpected behavior
Instance get dependencies through [Inject]
no matter what method overload was chosen to install it
Additional context
unity 2023.2.15
tested in editor
Reflex installed as readme says
NRE with MonoBehaviour's [Inject] attribute when it's child of DontDestroyOnLoad game object
To Reproduce
Expected behavior
Dependency injected, property is not null.
Find this issue with unity 2022.3.16.
Let's say I want to do something wrong:
public class Greater
{
private readonly DebugConsole _console;
public Greater(DebugConsole console) =>
_console = console;
public void Hello() =>
_console.Notify("Hello, World!");
}
public class DebugConsole
{
private readonly Greater _greater;
public DebugConsole(Greater greater) =>
_greater = greater;
public void Welcome() =>
_greater.Hello();
public void Notify(string message) =>
Debug.Log(message);
}
var builder = new ContainerBuilder();
builder.AddSingleton(typeof(DebugConsole));
builder.AddSingleton(typeof(Greater));
var container = builder.Build();
var console = container.Resolve<DebugConsole>();
console.Welcome();
There is a circular dependency here (Greater
<-> DebugConsole
) and the container, expectedly, can't solve this problem. In runtime, we'll get an exception, when try resolve it, refactor the code, use other approaches to solve it, and solve this problem, making our code better. That's fine. However, I can hack it with enumerations:
public class Greater
{
private readonly DebugConsole _console;
public Greater(DebugConsole console) =>
_console = console;
public void Hello() =>
_console.Notify("Hello, World!");
}
public class DebugConsole
{
private readonly IEnumerable<Greater> _greater;
public DebugConsole(IEnumerable<Greater> greater) =>
_greater = greater;
public void Welcome() =>
_greater.First().Hello();
public void Notify(string message) =>
Debug.Log(message);
}
var builder = new ContainerBuilder();
builder.AddSingleton(typeof(DebugConsole));
builder.AddSingleton(typeof(Greater));
var container = builder.Build();
var console = container.Resolve<DebugConsole>();
console.Welcome(); // now it's work! Print "Hello, World!"!
It happens because enumerations is a lazy evaluation, and the container returns exactly the enumeration when it is resolved.
public IEnumerable<object> All(Type contract)
{
return ResolversByContract.TryGetValue(contract, out var resolvers)
? resolvers.Select(resolver => resolver.Resolve(this))
: Enumerable.Empty<object>();
}
So the previous code works, but the next one fails again:
public class DebugConsole
{
private readonly Greater _greater;
public DebugConsole(IEnumerable<Greater> greater) =>
_greater = greater.First();
public void Welcome() =>
_greater.Hello();
public void Notify(string message) =>
Debug.Log(message);
}
This could be solved by explicitly calling the enumeration at creation time, for example, with .ToArray()
:
public IEnumerable<object> All(Type contract)
{
return ResolversByContract.TryGetValue(contract, out var resolvers)
? resolvers.Select(resolver => resolver.Resolve(this)).ToArray()
: Enumerable.Empty<object>();
}
I don't know if this is a bug or a feature. But in my opinion, it is a hack. Such functionality can be implemented by other means (if necessary), and leaving the enumeration as an enumeration. The containers I've worked with throw an exception here. And this seems to me to be the more expected behavior. What do you think about it?
I am using Unity 6000.0.4f1, and it seems like Unity introduced a new bug for imgui because debugger was fine when I was checking it out at 2022.3.X versions.
A temporary fix I found is either setting wordWrap to false or adding some kind of offset to rect.width in MultiColumnTreeView.cs, I was gonna make a pr but don;t know if there is a better way to fix it so I did not :D
Hi,
Does Reflex support SignalBus and Decorator pattern like Zenject?
I like these two features of Zenject but it seems that Reflex doesn't have these features.
Will Reflex add these features in the future?
Describe the bug
When Deserializing an Object from SQL4Unity Database system the exception
Object must implement IConvertible.
is being thrown
Expected behavior
The object should deserialize without problems
Additional context
I am assuming that this is related to Reflex as without it no errors occur
Stack Trace.
Error Deserializing SQL4Unity.SysTable
SQL For Unity Error
at System.Convert.ChangeType (System.Object value, System.Type conversionType, System.IFormatProvider provider) [0x00000] in <00000000000000000000000000000000>:0
at System.Runtime.Serialization.SerializationInfo.GetValue (System.String name, System.Type type) [0x00000] in <00000000000000000000000000000000>:0
at System.Collections.Generic.Dictionary2[TKey,TValue].OnDeserialization (System.Object sender) [0x00000] in <00000000000000000000000000000000>:0 at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize (System.Runtime.Remoting.Messaging.HeaderHandler handler, System.Runtime.Serialization.Formatters.Binary.__BinaryParser serParser, System.Boolean fCheck) [0x00000] in <00000000000000000000000000000000>:0 at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (System.IO.Stream serializationStream, System.Runtime.Remoting.Messaging.HeaderHandler handler, System.Boolean fCheck) [0x00000] in <00000000000000000000000000000000>:0 at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (System.IO.Stream serializationStream) [0x00000] in <00000000000000000000000000000000>:0 at SQL4Unity.PageManager.PageToObject[T] () [0x00000] in <00000000000000000000000000000000>:0 at SQL4Unity.DatabaseManager.Refresh () [0x00000] in <00000000000000000000000000000000>:0 at SQL4Unity.FileManager.Open (System.String path, System.String name) [0x00000] in <00000000000000000000000000000000>:0 at SQL4Unity.SQLParse.DoExecute (System.String command, SQL4Unity.SQLResult result, SQL4Unity.SQLRow row) [0x00000] in <00000000000000000000000000000000>:0 at SQL4Unity.SQLExecute.Open (System.String name, System.String path) [0x00000] in <00000000000000000000000000000000>:0 at SQL4Unity.SQLExecute.OpenCallback (System.Boolean ok) [0x00000] in <00000000000000000000000000000000>:0 at SQL4Unity.SQLExecute.Open (System.String name) [0x00000] in <00000000000000000000000000000000>:0 at SQL4Unity.SQLExecute.OpenAsync (System.String name, System.Action
1[T] callback) [0x00000] in <00000000000000000000000000000000>:0
at Scripts.RepositoryLayer.CheckOut.ImagesRepository..ctor () [0x00000] in <00000000000000000000000000000000>:0
at I2.Loc.Scripts.GloballInstaller+<>c.b__0_0 () [0x00000] in <00000000000000000000000000000000>:0
at Reflex.Container.Resolve (System.Type contract) [0x00000] in <00000000000000000000000000000000>:0
at Reflex.Injectors.FieldInjector.Inject (System.Reflection.FieldInfo field, System.Object instance, Reflex.Container container) [0x00000] in <00000000000000000000000000000000>:0
at Reflex.Injectors.FieldInjector.InjectMany (System.Collections.Generic.IEnumerable`1[T] fields, System.Object instance, Reflex.Container container) [0x00000] in <00000000000000000000000000000000>:0
at Reflex.Injectors.MonoInjector.Inject (UnityEngine.MonoBehaviour monoBehaviour, Reflex.Container container) [0x00000] in <00000000000000000000000000000000>:0
at Reflex.Injectors.SceneInjector.Inject (UnityEngine.SceneManagement.Scene scene, Reflex.Container container) [0x00000] in <00000000000000000000000000000000>:0
Object must implement IConvertible.
please contact me at [email protected] or via discord (I am the publisher of SQL4Unity)
I'm seeking clarification and an example regarding the behavior of IStartable
components when registered as Transients in the Reflex framework. The documentation mentions that "IStartable also works for Transients", but I can't figure out how to ensure that the Start
method is automatically called upon resolution when using Transients.
Could you please provide an example or further guidance on registering and resolving IStartable
components as Transients while ensuring that the Start
method is automatically invoked?
Any assistance or insights on this topic would be highly appreciated.
Here's my setup:
public class DotInstaller : MonoBehaviour, IInstaller
{
public void InstallBindings(ContainerDescriptor descriptor)
{
descriptor.AddTransient(typeof(Dot));
descriptor.AddSingleton(typeof(DotManager), typeof(IStartable));
}
}
public class DotManager : IStartable
{
public DotManager(Container container)
{
_container = container;
}
public void Start()
{
// As expected, this is being called automatically when the singleton instance is created
var dot1 = _container.Resolve<Dot>(); // Resolve works, but Start method of Dot is not called
var dot2 = _container.Construct<Dot>(); // Same here
}
}
public class Dot : IStartable
{
public void Start()
{
// This is never being called automatically, but why not?
}
}
Describe the bug
When the project contains Unity Burst package, Reflex seems to be unable to decide which dll to take some dependencies from (see screenshot). Conflict happens between Unity.Burst.Cecil and Mono.Cecil namespaces.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
Project to compile with the Burst package installed.
Desktop (please complete the following information):
Hello,
We ran into a problem that all injected MonoBehaviours are stored in the LazyDictionary
(MonoInjectableCache
) cache, and they are not unloaded from memory after scene unloading, because this dictionary stores their references. The situation gets worse when we leave the scene and then enter it again. Moreover, this cache dictionary always creates new entries and never returns a cached value. What was the purpose of this cache and can we do without it?
This is more a question about how this works as it wasn't clear to me. Is there a way to use the Inject attribute in a non-monobehaviour? For example:
public class MyTestClass
{
[Inject] private readonly MyInjectableClass _myClass;
}
MyTestClass will be instantiated repeatedly throughout my app's lifecycle.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.