If I want to create a prefab with its own LifetimeScope using RegisterComponentInNewPrefab(), how do I set the parent of that LifetimeScope?
What I want to do
When I create a prefab (filename: MyBehaviour.prefab) with its own LifetimeScope like this:
▼ GameObjectA (with components: MyBehaviour, MyPrefabLifetimeScope)
▼ GameObjectB
GameObjectC
public class MyBehaviour : MonoBehaviour { ... }
public class MyPrefabLifetimeScope : LifetimeScope { ... }
I want to set the parent scope of MyPrefabLifetimeScope to the scope in which I instantiate the prefab.
If only one parent scope exists
public class GameLifetimeScope : LifetimeScope
{
[SerializeField] MyBehaviour prefab; // this referes MyBehaviour.prefab
public override void Configure(IContainerBuilder builder)
{
builder.RegisterComponentInNewPrefab(prefab, Lifetime.Singleton);
}
}
In this case, it is no problem to set GameLifetimeScope as the parent scope of MyPrefabLifetimeScope. I can do so in the inspector of MyPrefabLifetimeScope.
When multiple parent scopes exist
public class GameLifetimeScope : LifetimeScope { /* same as above */ }
// same as GameLifetimeScope
public class TestLifetimeScope : LifetimeScope
{
[SerializeField] MyBehaviour prefab; // this also referes MyBehaviour.prefab
public override void Configure(IContainerBuilder builder)
{
builder.RegisterComponentInNewPrefab(prefab, Lifetime.Singleton);
}
}
I am having trouble finding a way to successfully set the parent scope if I want to use the same MyBehaviour.prefab for TestLifetimeScope in addition to GameLifetimeScope.
Dirty Solution
Currently, I am writing the following code to get by:
- Set
MyPrefabLifetimeScope.parentReferenceto point to the runtime parent scope- In that case,
ParentReference.Type { set; }is private, so I use reflection to force it to be written
- In that case,
public class MyBehaviour : MonoBehaviour
{
[SerializeField] MyPrefabLifetimeScope myPrefabLifetimeScope;
[Inject]
public void Construct(LifetimeScope lifetimeScope)
{
myPrefabLifetimeScope.InitParentReference(lifetimeScope);
}
}
public class MyPrefabLifetimeScope : LifetimeScope
{
public void InitParentReference(LifetimeScope parent)
{
System.Type prentType = parent.GetType();
object pr = new ParentReference { TypeName = parentType.FullName };
SetPrivatePropertyValue(pr, nameof(ParentReference.Type), parentType);
this.parentReference = (ParentReference)pr;
}
static void SetPrivatePropertyValue<T>(object obj, string propName, T val)
{
Type t = obj.GetType();
if (t.GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) == null)
throw new ArgumentOutOfRangeException();
t.InvokeMember(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, obj, new object[] { val });
}
}
What I really want to do
Actually, what I really want to do is calling InjectGameObject() to GameObjectB. However, I want to do the injection before the Awake() of MyBehaviour and GameObjectB.
If there is another way to meet this requirement, that is fine.
In the above example, MyPrefabLifetimeScope.autoInjectGameObjects references to GameObjectB.
Instead of using Container.Instantiate() you should be calling LifetimeScope.CreatChildFromPrefab(yourPrefab)
Here's how you can instantiate a gameobject with a scope on it.
public MyBehaviour prefab;
Protected override void Configure(IContainerBuilder builder)
{
builder.RegisterFactory<MyBehaviour>(c => () =>
{
var childScope = prefab.GetComponent<LifetimeScope>();
var instanceChildScope = CreateChildFromPrefab(childScope);
var instanceMyBehaviour = instanceChildScope.GetComponent<MyBehaviour>();
return instanceMyBehaviour;
}, Lifetime.Scoped);
Thank you SimonNordon4. Your method certainly satisfies "What I really want to do".
Sorry, I forgot to mention my assumption that I want to create a singleton from a prefab. And I want to write "MyBehaviour" as an argument to the constructor of other classes.
I don't think this can be expressed well using a factory.
Does the singleton object have to be a prefab? It would be easier if it existed in the scene:
builder.RegisterComponentInHeirachy<MyBehaviour>();
otherwise I think you would have to use a factory.
builder.RegisterFactory<MyBehaviour>(c => () =>
{
if (MyBehaviour.Singleton = null)
// There should be logic in MyBehaviour to set itself as the Singleton Instance when created.
CreateChildFromPrefab(myBehaviour.GetComponent<LifetimeScope>();
return MyBehaviour.Singleton;
}, Lifetime.Scoped);
Although this is starting to get quite messy.
I prefer to use a prefab because it often changes unexpectedly when placed in a scene.
I think the latter code would need to run a factory method in order to use it, as follows:
class A {
MyBehaviour _MyBehaviour;
[Inject]
public A(Func<MyBehaviour> factory) {
_MyBehaviour = factory();
}
}
But what I want to do is to pass it to the constructor normally as follows. My "Dirty Solution" allows me to do that.
class A {
MyBehaviour _MyBehaviour;
[Inject]
public A(MyBehaviour mb) {
_MyBehaviour = mb;
}
}
Thanks anyway!