Coder Social home page Coder Social logo

gustavopsantos / reflex Goto Github PK

View Code? Open in Web Editor NEW
648.0 11.0 48.0 35.29 MB

Minimal dependency injection framework for Unity

License: MIT License

C# 100.00%
csharp unity dependency-injection ioc-container game-dev unity3d ioc injection container di

reflex's Introduction

Hi there 👋, I'm Gustavo Santos


  • 🔭 I’m currently working on Reflex
  • 🌱 I’m currently learning IL Reweaving and P2P over Steam
  • 📫 How to reach me: LinkedIn
  • ⚡ Fun fact: I used to be a diesel mechanic

⚡ Languages ⚡

Bash Bash Bash Bash Bash Bash Bash Bash Bash

⚡ VCS ⚡

Git Git Git Git Git Git

⚡ Platforms ⚡

Windows Windows Windows Windows Windows Windows

⚡ SDKs ⚡

Steam Steam Steam Bash Steam Steam Steam Bash

⚡ APIs ⚡

Bash Bash Bash Bash Bash

⚡ Other Skills ⚡

Bash Bash Bash Bash Bash Bash Bash Bash Bash Bash Bash Bash Bash Bash Bash Bash Bash Bash

⚡ What I Like ⚡

Bash Bash Bash Bash Bash Bash Bash Bash

⚡ Softwares ⚡

Bash Bash Bash Bash Bash Bash Bash Bash Bash

reflex's People

Contributors

braty-xd avatar danyalmazloum avatar gabrielbigardi avatar gs256 avatar gustavopsantos avatar nilgawa avatar olegmrzv avatar phields avatar xchesh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reflex's Issues

Disposing container depending of its scene

Hello! Great framework, but faced the following situation when used scene context with Unity multiscene workflow:

  1. Place scene context on scene A that is loaded first. That leads us to the initialization of scene scoped container
  2. Load scene B in the additive mode. Here we can use container from scene A.
  3. Unload only scene 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.

Is BindFunction missing?

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...

Received error when opening the project for first time

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:

  1. Cloned project.
  2. Opened with Unity 2021.3.22f1

Expected behaviour
No errors and no dependency on extenject?!

Desktop (please complete the following information):

  • OS: Windows 11

Additional context
Simply checking out the DI framework for the first time.

Add support for Enter Play Mode Options

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.

image
image

Question: Async data

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?

Need HasBinding<T>for Container

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

Can't Instantiate object from IInstaller

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);
    }
}

Benchmark comparison

Screenshot 2024-06-01 104656

I wanted to check out benchmarks locally, and reflex sample literally takes 4 seconds for each iteration and freezes unity. Is it an old sample? According to documentation Reflex should be both faster and less garbage allocated.

error CS1061: 'Stack<Container>' does not contain a definition for 'TryPop'

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?

Injected attributes in base class do not work in derived class if private

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:

  1. Create two monobehaviour class: ClassA and ClassB, ClassB inherit from base class ClassA.
  2. Create two services (not monobehaviour) ServiceA and ServiceB with a simple method Execute which just print a string to console.
  3. In ClassA define [Inject] private ServiceA _serviceA as private member field, in Start() methods just call _serviceA.Execute() method
  4. In Class B define [Inject] private ServiceB _serviceB as private member field, in Start() methods just call base.Start() and _serviceB.Execute() method
  5. Create Scene scope installer for both services.

Expected 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());
    }
}

Feat: Multi scene injection support

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.

Access dependencies before Start

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.

Inject VisualElements

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)

Styles class in DebuggingWindow causes conflicts with Dialogue System from Pixel Crushers

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.

image

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.

when non-mono class installed through `AddSingleton(object, Type)` it doesn't get dependencies through `[Inject]`

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:

  1. Create simple project with Reflex installed
  2. Prepare simple configuration with use of one Scope (scene one)
  3. Create non-mono class to obtain some deps through [inject]
  4. Create class to be injected into previous one
  5. Install both at the same scope using AddSingleton with provided instance overload
  6. You should get null for injected field

Expected behavior
Instance get dependencies through [Inject] no matter what method overload was chosen to install it

Screenshots
image

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

NRE with MonoBehaviour's [Inject] attribute when it's child of DontDestroyOnLoad game object

To Reproduce

  1. Create a scene and add empty object (A). Add script that makes object DontDestroyOnLoad.
  2. Create a child of (A) game object. Add some script with field/property (B) with [Inject] attribute. In this script add reference to (B ) property.
  3. Add project context, bind as singletone with same type as B property.

Expected behavior
Dependency injected, property is not null.

Find this issue with unity 2022.3.16.

изображение
изображение
изображение
изображение
изображение
изображение

IEnumerable allow circular dependencies

Let's say I want to do something wrong:

Example 1. Not working.
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:

Example 2. Working.
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.

Code from Reflex Container
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:

Example 3. Not working.
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():

Added ToArray to Reflex Container
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?

Debugger Layout Wrapping

Screenshot 2024-06-03 141230

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.

Screenshot 2024-06-03 141257
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

Deserialization problem with 3rd Part Products

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

  • OS: Android
  • On Windows works as expected

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.Action1[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)

How does IStartable work with Transients?

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?
    }
}

Unable to compile when Burst package is installed

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:

  1. Create an empty project with 2D (URP) template.
  2. Import Reflex v4.0.0

Expected behavior
Project to compile with the Burst package installed.

Screenshots
image

Desktop (please complete the following information):

  • Editor: Unity 2021.3.16f1
  • OS: Windows 10

MonoInjectableCache leaks

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?

Inject in non-MB

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.