Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intended behaviour of RegisterComponentInHierarchy() #727

Open
JanikHelbig-NB opened this issue Nov 28, 2024 · 3 comments
Open

Intended behaviour of RegisterComponentInHierarchy() #727

JanikHelbig-NB opened this issue Nov 28, 2024 · 3 comments

Comments

@JanikHelbig-NB
Copy link

JanikHelbig-NB commented Nov 28, 2024

Hi,
recently I was debugging an issue in our project related to multiple repeated injections into a MonoBehaviour, let's call it InstancingSystem. This system is registered in the SceneLifetimeScope using RegisterComponentInHierarchy<InstancingSystem>(). Agents can use this system to get instances of pooled objects, for example projectiles. More complex agents, such as enemies, have their own nested EnemyLifetimeScope.

The problem is the following: InstancingSystem is injected with a IObjectResolver, initially the SceneLifetimeScope's. Now, when an enemy is spawned, the IObjectResolver is re-injected, this time with the EnemyLifetimeScope's. When the enemy is destroyed, the IObjectResolver is destroyed with it, resulting in NullReferenceExceptions when InstancingSystem is used again.

The root of the issue is, at least in my opinion, the fact that RegisterComponentInHierarchy() uses Lifetime.Scoped. This does not make sense to me, because in my understanding Scoped is supposed to ensure that a new instance is created for each scope. This is obviously not the case for Components and a automatic re-injection should also always be avoided anyway IMO.

In general I can't really think of a use case where the behavior of Lifetime.Scoped is preferable to Lifetime.Singleton when using RegisterComponentInHierarchy().

I'm also not the only one who has run into issues with this behavior, see #709 and #704 for examples.
Yes, I can just use RegisterComponent(FindFirstObjectOfType<InstancingSystem>()) to work around this, but then what is the point of RegisterComponentInHierarchy()?

@hadashiA
Copy link
Owner

hadashiA commented Dec 9, 2024

InstancingSystem is injected with a IObjectResolver, initially the SceneLifetimeScope's. Now, when an enemy is spawned, the IObjectResolver is re-injected, this time with the EnemyLifetimeScope's.

I didn't really understand why this was happening.
Is it possible to provide a simple sample?

RegisterComponentInHierarchy is for searching for MonoBehaviours that have already been placed in the scene.
Apart from that, if you are creating instances at runtime, you should not use RegisterComponentInHierarchy.

More complex agents, such as enemies, have their own nested EnemyLifetimeScope.

From the EnemyLifetimeScope name,
I wonder if a LifetimeScope is created each time an Enemy is spawned.
Some Zenject users seem to be assuming this kind of usage, but I don't really recommend that scopes are divided into such a fine-grained unit.
If you want to spawn enemies dynamically, all you need is a factory. If the same type is injected into each enemy using DI, then I think that's an abuse of DI, making the DI container do something that should be done in the application design.

@JanikHelbig-NB
Copy link
Author

Here is a small sample project that hopefully demonstrates the issue.
I don't think RegisterComponentInHierarchy() is misused here.

Some Zenject users seem to be assuming this kind of usage, but I don't really recommend that scopes are divided into such a fine-grained unit.
If you want to spawn enemies dynamically, all you need is a factory.

I'm finding that using scopes like that has advantages above a certain complexity of prefab.
Our enemies for example have quite a few components. (state, behaviour, pathfinding, receiving and dealing damage, etc.)
Being able to loosely couple all of these and check for certain components from outside of the scope, without caring about what the hierarchy looks like, is pretty convenient. You have to do a lot less manual "piping" of references that way.

@hadashiA
Copy link
Owner

Ah oK. I understand the problem.
When you Resolve the parent MonoBehaviiour from the child scope, it gets injected again, right?
Yes, that's unexpected.

I'll think about how to deal with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants