Creating Concrete Instances of Generic Systems

It looks like when you call World.GetOrCreateSystem() and pass a generic system type as the Type parameter, Unity will create the system, but it won’t be added to the update loop:

public class GenericSystem<T> : JobComponentSystem
{
    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        // do work
    }
}

public class OtherSystem : JobComponentSystem
{
    private GenericSystem<Foo> fooSystem;

    protected override void OnCreate()
    {
        fooSystem = World.GetOrCreateSystem<GenericSystem<Foo>>();
    }

    // system continues...
}

In this case, a new instance of GenericSystem will be created, but it’s OnUpdate() method will never be called. It also won’t show up in the Entity Inspector’s system list. I’m guessing it’s not being added to a ComponentSystemGroup.

I don’t expect Unity to spawn instances of generic systems automatically. But if you’re intentionally creating one with a concrete Type parameter, I would expect Unity to know how to update that system.

What can be done about this? Do I need to create and add a GenericSystem manually, in a custom world via an ICustomBootStrap? Will that even work?

Thanks for any advice.

They get automatically registered for updates if you do this (create manual implementations of the generic system):

public class GenericIntSystem :  GenericSystem<int>
{
    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        // do work
    }
}
1 Like

Thank you, but this is what I’m trying to avoid, if possible.

I think your alternative is to create a custom world Bootstrap. You can take a look at the NetCode samples, which has an example of it

EDIT:

class MyCustomBootStrap : ICustomBootstrap
{
    public bool Initialize(string defaultWorldName)
    {
        Debug.Log("Executing bootstrap");
        var world = new World("Custom world");
        World.DefaultGameObjectInjectionWorld = world;
        var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default);

        DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, systems);
        ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world);
        return true;
    }
}

(and yes I should’ve read your original post more thoroughly before answering, sorry :smile:)

You create your generic systems via code and add them to the “systems” list here. And then do the “DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, systems);” and “ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world);”

1 Like

Thank you very much, sir. :slight_smile:

I will give this a spin.

In that code example, can DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups actually add the systems to Unity’s default world?

I read recently that they may have changed the execution order, such that the default world isn’t available to ICustomBootstraps…Am I recalling that correctly?

hmmm, I’ve never actually used custom bootstrap in practice, so I don’t know

AddSystemsToRootLevelSystemGroups creates and injects the systems and system groups into the InitializationSystemGroup, SimulationSystemGroup, and PresentationSystemGroup. You can use World.GetExistingSystem paired with ComponentSystemGroup.AddSystemToUpdateList to inject a system into a group.

Keep in mind that Burst doesn’t work with generic jobs in a build where the instance of the job is created with a generic parameter (the instance must be concrete at compile time).

1 Like

Thank you for the info!

For future readers: I was concerned this might also be true for job types instantiated from generic systems. But through the Burst Inspector, I confirmed that it’s not. Jobs instantiated from generic systems (even ones which use the system’s generic type arguments) seem to compile with Burst.

Editor is different from build.

1 Like

Can confirm that generics do not work in Build with latest Burst release. However it was working on previous versions. Even though Burst inspector shows a compiled job the job won’t be Burstable in build.

1 Like

Thank you for your replies!

Jobs being burstable in editor but not build sounds like a problem - like a potential bug. At the least, one would expect the editor to inform the dev that the job won’t be burstable in the build. But what you’d really expect is parity across editor and build for a major feature like this (even if that means jobs aren’t burstable in the editor). What is the use case for the way it is now?

Does anyone know a reason why they would intentionally want jobs to be burstable in editor but in builds? Maybe it does inform you, and I’m not looking in the right place?

Many thanks for any insight here.

It isn’t a bug nor a design decision. It ultimately has to do with the fact that in the Editor, Burst jobs are compiled in JIT fashion, whereas in a build, they are compiled AOT. Currently, the method they use to discover jobs to be compiled AOT doesn’t handle generic jobs with generic arguments. That is something they have suggested they will implement in the future (sometime in 2020 was the estimate I got) but it requires a more powerful discovery tool than the one they have for Burst. You can trick the system by having concrete instances of the generic systems declared somewhere in your code that doesn’t get stripped.

1 Like

AOT was working before with explicitly typed jobs somewhere in code. Examlpe. public static readonly IJob Job; that would be visible to burst inspector and compiled in build.

But it does not work anymore.

So all in all, I think the cleanest approach may be to just codegen your concrete classes (as in Post #2 ) if you are dealing with a mass number of them. Certainly will save a lot of headaches down the line! Unless ofc those classes cannot reasonably be determined at compile time (doesn’t seem like your case though). @PublicEnumE

1 Like

Out of curiosity do you have a preferred method to codegen things? I have a ton of things where codegen is the seemingly perfect answer for, but I haven’t figured out a quick and clean way to do codegen without modifying the Entities package.

For me, simply using attributes+reflection to find out what needs codegen, and then writing a simple script that generates a text file usually does the trick. You can also make your own template system by writing a code text file manually and writing like $$$$MY_TYPE_NAME$$$$ in places where your codegen script should be replacing stuff. I try to avoid using third party solutions for as long as I can for this sort of thing. I like keeping things ultra simple.

Out of curiosity, what sort of codegen scenario requires you to modify the Entities package?

I think the same as @PhilSA . Most codegen needs I came across are fairly simple such as generating a list of:

[assembly: RegisterGenericComponentType(typeof(BufferedValue<Translation>))]

or

public class IntSystem : GenericSystem<int>{ }

So for these cases, you don’t really need to use a template engine like T4. You can just process them in
OnPostprocessAllAssets or on-demand in your own editor tooling. Also I may add that even if you need control flow within the “template”, you can just cleanly use ternary operators and $ string interpolations in your logic and still forego a full-fledged template engine.

1 Like

The use case that most closely relates to the this thread’s topic is I have an API that defines new interface types that users can tack onto their custom types. I want to create two tag components for each type the user defines that implements those interfaces. I do not have the ability to depend on the user’s assemblies nor do I want to trash any file the user may define. Currently I am trying to use reflection to add types into the TypeManager at runtime.

However, most of my codegen use cases involve extending the lambda codegen with custom types. Specifically, I want to be able to build EntityQueries and I want to be able to convert a lambda into a callback from inside a job with access to captured variables.

You may use this to add your types.
My code.

        private static int RegisterGenericComponent(Type componentType)
        {
            var buildMethod = typeof(TypeManager).GetMethod("BuildComponentType", BindingFlags.Static |
                BindingFlags.NonPublic, null, CallingConventions.Any, new Type[] { typeof(Type) }, null);

            var addMethod = typeof(TypeManager).GetMethod("AddTypeInfoToTables", BindingFlags.Static |
                BindingFlags.NonPublic, null, CallingConventions.Any, new Type[] { typeof(Type), typeof(TypeManager.TypeInfo) }, null);

            TypeManager.TypeInfo typeInfo = (TypeManager.TypeInfo)
                buildMethod.Invoke(obj: null, parameters: new object[] { componentType });

            addMethod.Invoke(obj: null, parameters: new object[]
            {
                componentType, typeInfo
            });

            return TypeManager.GetTypeIndex(componentType);
        }
3 Likes