Loading non-Editor Data to use in Systems

Is there a common pattern for loading non-Unity data (like a text file) at runtime and using it in the creation of systems? For example, loading a config file to turn into a NativeHashMap shared by multiple Systems as they update.

In the MonoBehaviour approach I would have a loading scene that did this work and then stored the data in a DontDestroyOnLoad singleton that other MonoBehaviours could access in the game scene. However, there doesn’t seem to be much control over when a system’s OnCreate method happens relative to scene loading and MonoBehavior Start/Awake functions. I’ve looked at RequireSingletonForUpdate and GetSingleton, but these require the component to be a struct, and wouldn’t be good for large collections of data tables and other managed assets (e.g. procedurally generated meshes, materials, etc.).

Any advice on how to do this kind of runtime data loading at startup before the ECS system consumes it?

There are managed versions of GetSingleton for class based components.

    public static unsafe class ComponentSystemBaseManagedComponentExtensions
    {
        /// <summary>
        /// Checks whether a singleton component of the specified type exists.
        /// </summary>
        /// <typeparam name="T">The <see cref="IComponentData"/> subtype of the singleton component.</typeparam>
        /// <returns>True, if a singleton of the specified type exists in the current <see cref="World"/>.</returns>
        public static bool HasSingleton<T>(this ComponentSystemBase sys) where T : class, IComponentData
        {
            var type = ComponentType.ReadOnly<T>();
            var query = sys.GetSingletonEntityQueryInternal(type);
            return query.CalculateEntityCount() == 1;
        }

        /// <summary>
        /// Gets the value of a singleton component.
        /// </summary>
        /// <typeparam name="T">The <see cref="IComponentData"/> subtype of the singleton component.</typeparam>
        /// <returns>The component.</returns>
        /// <seealso cref="EntityQuery.GetSingleton{T}"/>
        public static T GetSingleton<T>(this ComponentSystemBase sys) where T : class, IComponentData
        {
            var type = ComponentType.ReadOnly<T>();
            var query = sys.GetSingletonEntityQueryInternal(type);
            return query.GetSingleton<T>();
        }

        /// <summary>
        /// Sets the value of a singleton component.
        /// </summary>
        /// <param name="value">A component containing the value to assign to the singleton.</param>
        /// <typeparam name="T">The <see cref="IComponentData"/> subtype of the singleton component.</typeparam>
        /// <seealso cref="EntityQuery.SetSingleton{T}"/>
        public static void SetSingleton<T>(this ComponentSystemBase sys, T value) where T : class, IComponentData
        {
            var type = ComponentType.ReadWrite<T>();
            var query = sys.GetSingletonEntityQueryInternal(type);
            query.SetSingleton(value);
        }
    }

As they’re extension methods so you have to prefix the call with this.

this.GetSingleton();

2 Likes

Are singletons actually the best way to go about this though? I’m stuck on when I would be setting the singleton.

The file is loaded when the game starts, but that’s likely after the systems are created. There doesn’t seem to be a good way, at least that I’m aware of, to defer system creation after post-launch resources are loaded into the game.

Also, these singletons are entities. I just want a lookup table. It seems wasteful to create an entire entity (and have to do entity lookup) just to retrieve readonly data sitting in memory somewhere, especially every pass.

While I don’t use any managed components anymore, I load all my settings into the world before the systems are created. My basic setup is

  1. Create the World/s (client/server etc)
  2. Create entities/components for settings
  3. Create systems

This way systems can read settings in OnCreate
I don’t like systems being coupled to anything except data.

Also you don’t have to do this every pass. If you’re settings don’t change you can cache it in the system or if the settings can change, can cache the entity and do a direct lookup instead of a query (but instead of using GetSingleton which will leave a query in your system, do the query through entity manager in OnCreate)

But anyway, this is basically how I’ve decided I like handling settings and systems over the last few years of developing with DOTS. I do not claim this is some universal best practice.

1 Like

Yeah, reading more into it this is what I’m leaning towards – taking control over system creation, waiting until the data they need is ready and feeding it directly to them on construction. Do you do this in a MonoBehaviour, or through bootstrapping, or what?

My current setup is something like this.
Bootstrap

  • my bootstrap is very basic, creates the Default world and adds only one system, “GameSystem”

GameSystem

  • Starts the initialization sequence - sets up the platform (certain console needs to setup storage etc), loads/downloads the settings etc
  • Once initialization is done, it then adds all the other required systems to the default world (mostly debugging systems, but also asset management etc).

Starting game

  • Create World
  • If loading from a save de-serialize the entity data onto the world (to save and load I just serialize out the majority of the entity world, as I avoid managed components this is super easy.)
  • Otherwise if starting new, populate the world with settings.
  • Add all the world specific systems

Off we go

1 Like

*Refactors immediately.