Export/Import of ECS DLLs for a DLC System

I’m trying to create a DLC-like system, by packaging my code into a DLL, and loading it in at runtime.

This all works, except for my ECS IComponentTypes, I get an error saying these need to be known at compile time (which they obviously can’t be if I want to load content dynamically. Is there any way around this?

When importing my DLC, I’ve tried shutting down the type manager, AND the world, creating a new type manager, manually registering all my IComponentData types, and then recreating a fresh world… but still no joy

Code for this below…

public static class DynamicWorldManager
{
    private static Unity.Entities.World currentWorld;

    //Calling this after registering the my imported DLLs/Assemblies 
    public static void InitializeWorld(string name, Assembly ass)
    {
        Debug.Log("Init new world");

        // Ensure TypeManager is properly shut down first if necessary
        TypeManager.Shutdown();
        Debug.Log("Old TypeManager shut down");

        // Dispose of any existing world
        if (currentWorld != null)
        {
            Debug.Log("Dispose old world");
            currentWorld.Dispose();
        }

        // Create a new ECS world (after shutting down old TypeManager)
        currentWorld = new Unity.Entities.World(name);
        Debug.Log("World created");

        // Load dynamic types into TypeManager
        LoadTypesIntoTypeManager(ass);
        Debug.Log("Types loaded into TypeManager");

        // Initialize TypeManager after registering types
        TypeManager.Initialize();
        Debug.Log("Types loaded into manager");

        // Set up systems (after initializing the TypeManager)
        DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(currentWorld, new SortedSet<Type>());
        Debug.Log("Systems added");

        // Set the default world for GameObjectInjection
        Unity.Entities.World.DefaultGameObjectInjectionWorld = currentWorld;

        Debug.Log($"World '{name}' initialized with dynamically loaded types.");
    }

    // Load types into TypeManager and ensure they're registered
    private static void LoadTypesIntoTypeManager(Assembly assembly)
    {
        foreach (var type in assembly.GetTypes())
        {
            if (typeof(IComponentData).IsAssignableFrom(type) && type.IsValueType)
            {
                Debug.Log($"<color=green>Loading ECS Component into TypeManager: {type.FullName}</color>"); //This gets logged: Loading ECS Component into TypeManager: boids.BoidDebug
                try
                {
                    // Register the type with TypeManager by forcing its index to be resolved
                    _ = TypeManager.GetTypeIndex(type); //ERROR HERE Failed to register type boids.BoidDebug: Unknown Type:boids.BoidDebug All ComponentType must be known at compile time. For generic components, each concrete type must be registered with [RegisterGenericComponentType].
                }
                catch (Exception ex)
                {
                    Debug.LogError($"Failed to register type {type.FullName}: {ex.Message}");
                }
            }
        }
    }
}

The component types I’m working with are simple structs implementing IComponentData, they’re not generics.

Looks like there used to be support for this in the old entities packages, but now I can’t find a way to load new types into the TypeManager at runtime.

Does anyone have any ideas here? This is currently a total showstopper. I can’t precompile the DLC along with the build as the DLC is community-created

Cheers,
Fred

I’ve settled on the fact that loading in new components at runtime just isn’t doable, but I should at least be able to load systems, right?

Right???

Well, I can’t for the life of me figure out how. I had hoped that just having the system be part of the DLL that gets loaded at runtime would be enough to get that system added to the world automatically, no such luck, I’ve tried doing it myself, from both the main program, and inside the DLC assembly itself, where I know what the concrete types are, e.g

var boidSpawnSystem = (BoidSpawnSystem)Activator.CreateInstance(typeof(BoidSpawnSystem));
world.AddSystemManaged(boidSpawnSystem);

var boidSimulateSystem = (BoidSimulateSystem)Activator.CreateInstance(typeof(BoidSimulateSystem));
world.AddSystemManaged(boidSimulateSystem);

world.Update();

That code all runs through fine, but the system still doesn’t actually seem to appear anywhere

Anyone have any ideas??

You’ve created a new world which initializes TypeManager (via EntityManager), so you’re loading your new components/systems after TypeManager has already been initialized so of course it’s not working :sweat_smile:

You need to load your types before TypeManager Initializes (i.e. before you create your world.)

Tried that too, just got another error can’t remember off the top of my head if type manager was null, or if it was sone explicit error telling me i couldn’t register types before type manager had been initialized, but it definitely didn’t work, I’m tried that code every which way and didn’t get anywhere

I don’t recall trying to do this after shutting down the TypeManager and Initializing it again, but I’ve done this successfully doing it before TypeManager is ever Initialized - that’s basically how my modding library handles it anyway.

Ok that’s something I can try, how do you delay init if the type manager? I tried that scripting define symbol that delays init of the ecs world, but guess that’s a different thing.

Really, I need to be able to load and unload multiple ECS DLCs on the fly as the player goes between levels, so ideally I’d be able to add new types after the manager has been init the first time

Honestly though at this point even just getting it working for one single ECS DLC would be a win

I don’t delay initialisation, I just initialise faster.

I just require all dlls for modding to exist before app starts running, if someone wants to change what’s loaded requires app restart.

Ok, that’s pretty far from ideal, but I can live with it as a compromise until Unity makes whatever change is needed to enable better functionality

I still don’t have my systems working though, I really don’t want to have to register systems on boot, components are small, but to register a system means I have to load in the entire DLC assembly, that’s really not scalable, if the game has e.g 10 ecs dlcs, it has to register all 10 of those assemblies from the get go, even if the player isn’t actually going to be using any of them

Have you even been able to register a system into the world at runtime (post-boot) in ECS 1.0+?

Cracked it


            var boidSpawnSystem = (BoidSpawnSystem)Activator.CreateInstance(typeof(BoidSpawnSystem));
            world.AddSystemManaged(boidSpawnSystem);

            var boidSimulateSystem = (BoidSimulateSystem)Activator.CreateInstance(typeof(BoidSimulateSystem));
            world.AddSystemManaged(boidSimulateSystem);

            var sharkSpawnSystem = (SharkSpawnSystem)Activator.CreateInstance(typeof(SharkSpawnSystem));
            world.AddSystemManaged(sharkSpawnSystem);

            var sharkSimulateSystem = (SharkSimulateSystem)Activator.CreateInstance(typeof(SharkSimulateSystem));
            world.AddSystemManaged(sharkSimulateSystem);

            Debug.Log("Systems setup successfully - about to add systems list to groups");

            DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, new List<Type> { 
                typeof(BoidSpawnSystem),
                typeof(BoidSimulateSystem),
                typeof(SharkSpawnSystem),
                typeof(SharkSimulateSystem),
            });

It was the call to DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups that I was missing.

The caveat of this I’ve so far only got it running from inside the DLC assembly itself, rather than the main program