Search Unity

Feature Request Can we get constructor arguments in CreateSystem?

Discussion in 'Entity Component System' started by kensct, Nov 30, 2022.

  1. kensct

    kensct

    Joined:
    Jul 3, 2012
    Posts:
    19
    The feature seems to be already made as I can achieve this through reflection, but it would be super useful if i could just do this properly by creating the system the normal way with World.CreateSystem<T>()

    Would be looking for:

    Code (CSharp):
    1. World.CreateSystem<T>( params object[] args)
    2. World.CreateSystemManaged<T>( params object[] args)

    Unless I am missing something and this is already possible some other way?

    Reflection workaround code:
    Code (CSharp):
    1. private static SystemHandle CreateSystem<T>(World world,params object[] args) where T : SystemBase
    2. {
    3.     MethodInfo typeManager = typeof(TypeManager).GetMethod("AddSystemTypeToTables" ,BindingFlags.NonPublic | BindingFlags.Static,null,new []{typeof(Type)},null);
    4.     typeManager.Invoke(null, new object[] { typeof(T) });
    5.  
    6.     SystemBase system = (SystemBase)Activator.CreateInstance(typeof(T),args);
    7.    
    8.     MethodInfo addSystemInternal = typeof(World).GetMethod("AddSystem_Add_Internal" ,BindingFlags.NonPublic | BindingFlags.Instance);
    9.     addSystemInternal.Invoke(world, new object[] {system});    
    10.    
    11.     MethodInfo onCreateSystemInternal = typeof(World).GetMethod("AddSystem_OnCreate_Internal" ,BindingFlags.NonPublic | BindingFlags.Instance);
    12.     onCreateSystemInternal.Invoke(world, new object[] {system});
    13.  
    14.     return system.SystemHandle;
    15. }
    Thanks,

    Ken
     
    Last edited: Nov 30, 2022
  2. Spy-Master

    Spy-Master

    Joined:
    Aug 4, 2022
    Posts:
    639
    Ask Microsoft. Probably for generics without a new() constraint.
    This design pattern is odd. An explanation of why such a thing is desired would be good.
     
  3. kensct

    kensct

    Joined:
    Jul 3, 2012
    Posts:
    19
    Facepalm for some reason I didn't realise i was looking at system and was thinking of dots systems. I can remove that line of redundant code and just do it normally lol.

    The reason I do this is I want to set up my system with some asset references to scriptable objects and materials. As far as I can tell there is no nice way to do this, you can't put the data into a Singleton Entity.

    Now you could use a monobehaviour singleton or some static variables, but this doesn't work if you want multiple worlds and have 2 worlds running the same system.

    Basically i want a nice way to set up a system preferably before On Create runs ( i had another workaround which worked using OnStartRunning but it was also clunky) and have the data unique to that system.

    If I could create a system and set it up at the same time using it's constructor that would save a lot of effort. This also insures the system is set up correctly as setup would be forced by the constructor.
     
    Last edited: Nov 30, 2022
  4. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    There is the World.CreateSystemManaged() method that should work for you.
    Here is roughly how I add generic Systems to my update loop:

    Code (CSharp):
    1. public static SystemBase AddGenericSystem(World world,Type type1,Type type2)
    2. {
    3.    var sysType = typeof(GenericSystemBase<,>);
    4.    var constructedClass = sysType.MakeGenericType(type1, type2);
    5.    var createdSystem = world.CreateSystemManaged(constructedClass);
    6.    var updateGroup = world.GetOrCreateSystemManaged<AnyUpdateGroup>();
    7.    updateGroup.AddSystemToUpdateList(createdSystem);
    8.    var sys = (SystemBase)world.GetExistingSystemManaged(constructedClass);
    9.    return sys;
    10. }
     
    Last edited: Nov 30, 2022
  5. Spy-Master

    Spy-Master

    Joined:
    Aug 4, 2022
    Posts:
    639
    Why not? Managed components exist. You just need to specify a component type that's a class instead of a struct, and then you can treat that as a singleton just like any other. You can drop in your needed UnityEngine.Object there and all the systems that need it can access it. I've done similar things in my project (there's singletons to manage things like per-world managed object pools), it works just fine as long as you set up your queries as RO/RW where needed.
    *ensures
    What does this have to do with anything?

    Addendum: I've not tried it myself, but AddSystemManaged at the very least lets you pass in a managed system instance, so that might be something you could go for. Nothing similar for unmanaged systems, but you probably don't have much reason to use that over SystemBase especially as you're working with non-burstable things like ScriptableObject.
     
  6. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    I have the same use case but i did write it back when SystemEntities were not a thing. I gave each created system a BlobAssetReference to hold settings (converted from ScriptableObjects).
    If i would do it now i would create a Singleton Entity or use the SystemEntity (haven't done it yet so not sure its allowed)
    to hold the settings.

    You can use Class based IComponentData to hold these.


    Sorry I might have misunderstood his question but i was under the impression he wants to know how to avoid getting the Method via Reflection. I show an example of how i avoid it.
     
  7. Spy-Master

    Spy-Master

    Joined:
    Aug 4, 2022
    Posts:
    639
    The problem is parameterizing the creation of a system by constructor parameters, not by generic type parameters. Former is pretty much an anti-pattern, systems are meant to be drop-in and work only if necessary data is available.
     
  8. kensct

    kensct

    Joined:
    Jul 3, 2012
    Posts:
    19
    Managed components seem like an ok solution. I was not aware I could do that so thats great to learn thanks. But as far as i can tell you can't use it as a singleton, it gives error saying it must be unmanaged type. I could treat it like a singleton however this method is not as tidy as I need to search for the Entity.

    Yes this data doesn't need burst or to be fast it's a once and done deal for systems setup in OnCreate.

    For me constructor is still slightly nicer ( and presumably marginally faster not that this matters here) but using OnCreate to grab the managed components as a sudo constructor also works just means I can't make the data readonly.
     
  9. Spy-Master

    Spy-Master

    Joined:
    Aug 4, 2022
    Posts:
    639
    You can use them as singletons. There's managed extensions that facilitate this.
    https://docs.unity3d.com/Packages/c...es.EntityQueryManagedComponentExtensions.html
    https://docs.unity3d.com/Packages/c.../api/Unity.Entities.SystemAPI.ManagedAPI.html
    https://docs.unity3d.com/Packages/c...nentSystemBaseManagedComponentExtensions.html

    *pseudo
    That level of control is up to you. I wouldn't be too worried. Depending on the project structure, you could just mark the single type as internal and only touch it in the system's bootstrapping code and in the system itself.
     
  10. kensct

    kensct

    Joined:
    Jul 3, 2012
    Posts:
    19
    Forgive me but I cant get that to work as I expect.

    GetSingleton<MyComponent>() gives me an error if MyComponent is a class.

    The class

    Code (CSharp):
    1. public class MyComponent : IComponentData
    2. {
    3. }
    What am i missing?
     
  11. Spy-Master

    Spy-Master

    Joined:
    Aug 4, 2022
    Posts:
    639
    This gives literally no context. What are you calling it on?
    The full code block where you're calling it, please.
     
  12. kensct

    kensct

    Joined:
    Jul 3, 2012
    Posts:
    19
    Calling it on a system
    Code (CSharp):
    1. protected override void OnCreate()
    2. {
    3.             var myComponent = GetSingleton<MyComponent>();
    4. }
    The error:

    The type 'MyComponent' must be valid unmanaged type (simple numeric, 'bool', 'char', 'void', enumeration type or struct type with all fields of unmanaged types at any level of nesting) in order to use it as a type argument for 'T' parameter
     
  13. Spy-Master

    Spy-Master

    Joined:
    Aug 4, 2022
    Posts:
    639
    You need to invoke that as "this.GetSingleton" because it's an extension method.
     
    kensct likes this.
  14. kensct

    kensct

    Joined:
    Jul 3, 2012
    Posts:
    19
    Ahhh that's what i was missing.

    Thank you
     
  15. kensct

    kensct

    Joined:
    Jul 3, 2012
    Posts:
    19
    This still somehow feels better for me

    Code (CSharp):
    1.  
    2. private static SystemHandle CreateSystem<T>(World world,params object[] args) where T : SystemBase
    3. {
    4.     MethodInfo typeManager = typeof(TypeManager).GetMethod("AddSystemTypeToTables" ,BindingFlags.NonPublic | BindingFlags.Static,null,new []{typeof(Type)},null);
    5.     typeManager.Invoke(null, new object[] { typeof(T) });
    6.  
    7.     SystemBase system = (SystemBase)Activator.CreateInstance(typeof(T),args);
    8.    
    9.     MethodInfo addSystemInternal = typeof(World).GetMethod("AddSystem_Add_Internal" ,BindingFlags.NonPublic | BindingFlags.Instance);
    10.     addSystemInternal.Invoke(world, new object[] {system});    
    11.    
    12.     MethodInfo onCreateSystemInternal = typeof(World).GetMethod("AddSystem_OnCreate_Internal" ,BindingFlags.NonPublic | BindingFlags.Instance);
    13.     onCreateSystemInternal.Invoke(world, new object[] {system});
    14.  
    15.     return system.SystemHandle;
    16. }
    17.  
    As then i can just do this:

    Code (CSharp):
    1. m_GameTickSystems = new  SystemHandle[]
    2.             {
    3.                 m_World.CreateSystem<TickUpdateSystem>(),
    4.                 m_World.CreateSystem<PlayerLocationSystem>(),
    5.                 m_World.CreateSystem<ChunkOpenSystem>(),
    6.                 m_World.CreateSystem<ChunkLoadSystem>(),
    7.                 m_World.CreateSystem<ChunkProceduralSystem>(),
    8.                 CreateSystem<StructureBuildSystem>(m_World,m_StructureLibrary),
    9.                 m_World.CreateSystem<GenerationStateSystem>(),
    10.                 m_World.CreateSystem<VoxelInterfaceSystem>(),
    11.                 m_World.CreateSystem<ChunkUpdateSystem>(),
    12.                 m_World.CreateSystem<ChunkSaveSystem>(),
    13.                 CreateSystem<ChunkMeshSystem>(m_World,m_WorldOpaqueMaterial,m_WorldTransparentMaterial),
    14.                 m_World.CreateSystem<PhysicsSystem>(),
    15.                 m_World.CreateSystem<VoxelSpaceSystem>(),
    16.             };
    17.  
    This was I don't need the extra data helper entities meaning I have to write an extra class, then create it and read from it. I can just do it direct.

    Also you have to make sure you have created the other entity first an easy mistake to make.

    So if that was just added to CreateSystem instead it would be tidy.