Globally Shared (ReadOnly) Data for Jobs: Best Practice

Hi,

I am quite new to the DOTS part of unity and while the docs and examples helped a lot so far I have one question that bugs me:

What is the proposed best practice to access globally shared (immutable) data within jobs using DOTS?

Assume I have a NativeContainer (e.g. NativeArray) or any other field filled with precomputed information which, after initialized once, will never change. An example could be a network of roads, spawn points, animations or any other static information.

Now I need to access that information within different Jobs, maybe in different systems. What I would do atm is: Create a static class which contains my static data/containers. These would be filled with data during the initialization of the game/scene.

public static class GlobalStatics
{
    public static NativeArray<float3> RoadNodes;
}

Then to read the data in a scheduled Job I have to create a copy of the static container in order to access the data.

protected override void OnUpdate()
{
    var roadNodes= GlobalStatics.RoadNodes;
    //....
}

I have read somewhere that this assignment in the OnUpdate() loop is only copying the pointer and some other vars, not the actual data itself. The overhead should thus be minimal? However, is there some intended way I can access the data directly without disabling burst?
I mean, the content is static, and will not change so there should be no issues regarding race conditions or am I wrong? Is there some other method I have not read about yet which might be even more efficient in such cases?

Thanks for your insight regarding this matter.
Cheers,
Simon

1 Like

For immutable data I would suggest you to take a look at blob asset reference.

https://docs.unity3d.com/Packages/com.unity.entities@0.16/api/Unity.Entities.BlobBuilder.html

https://docs.unity3d.com/Packages/com.unity.entities@0.16/api/Unity.Entities.BlobAssetReference-1.html

1 Like

Hi,
thanks for your response. I did have a look at that one but was under the impression blob assets and references can only live within entities or be accessed if they are assigned to an component on an entity? I’ll revisit that one.

Edit: It works as advertised. So this is considered the best practice for this type of situations? Store the data in a blob asset and include the referencing component in all archetypes which “need” the data?

Using static fields are one of the worst things you can do in DOTS, because there is not really a safe way to ensure proper allocation and deallocation of memory and you run the risk of stuff breaking when using Fast Enter Playmode.

A BlobAssetReference might work, but might also be overkill on each entity. You might consider having the BlobAssetReference or a native container in a class IComponentData on a singleton instead.

That’s a good one. Thanks for your expertise!

The Singleton variation sounds also quite juicy and works nicely. But then I have to call GetSingleton in each OnUpdate() loop don’t I? At least I seem not to be able to cache that query. Isn’t that somehow the same as getting a GameObject in each Update()? Just curious.

Nice work with your Framework BTW. :slight_smile:

So for now I have two good and one bad ways towards Rome:

  • Use a IComponentData component as singleton (containing a BlobAssetReference)

  • Add a ISharedComponentData component to each entity which uses the data (containing the same BlobAssetReference)

  • Use a copy of static NativeArray field as stated in my original post (very evil!)

I don’t think shared component data is a good idea in this case. You should stick with the singleton entity suggested. You should also be able to make a non static property in your initialisation system and use it in the other systems using it to cache it but you’ll have to manage the cash yourself in case you reload/remake the blob asset and it will create a dependancy between the initialisation system and the consuming systems.

Maybe you could use something like,

static public class MyVars
{
static readonly public T myVar0 ;
// Initiate vars.
static MyVars
{
myVar = some value...
}
}

You can access these values directly in job.
Similar to constants.

That depends on how you get your GameObject. The query is cached by SystemBase. I can’t really attest to its performance though as I use a different setup for such things.

As a side note, I am in agreement that shared components are the wrong answer here. Shared components are for grouping entities by something they share in common, not for actually sharing data.

1 Like

Those are very valuable inputs. Thank you all. :beer:

I was not aware of that. Good to know. I assumed it worked like sharing meshes or materials or such.
I so the way to go would be a combination of Singleton and static class (thanks @Antypodish ) to store my query to that singleton If that is at all possible.
And yes, this might be nitpicking. The performance difference might be very small. But I am always curious regarding those things. :slight_smile:

1 Like

I am using Public fields on GameSystems to share fields (basically singletons/static fields) and so far having some success (Disclaimer: I am new to Unity). I wonder if anyone can see issues with this approach as why I shouldn’t be using it? I’m pretty sure it falls under:

But so far, I haven’t had any issues with Play mode (with reload Domain/Scene turned off), but I guess it could be a ticking time bomb?

Code

[UpdateAfter(typeof(MouseInputSystem))]
public class PlayerAttackSystem : SystemBase
{
    private MouseInputSystem _mouseSystem;

    protected override void OnCreate()
    {
        _mouseSystem = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem<MouseInputSystem>();
    }

    protected override void OnUpdate()
    {
        var mouseInput = _mouseSystem.MouseInput;

        if (!mouseInput.IsLeftButtonUp)
        {
            // Debug.Log("Not Spawning Fireball - Mouse Button Is Not Up");
            return;
        }
       
        var terrainHitInfo = _mouseSystem.TerrainHitInfo;

        if (!terrainHitInfo.IsHit)
        {
            // Debug.Log("Not Spawning Fireball - Terrain Not Hit");
            return;
        }       

        // Do work
    }
}
public class MouseInputSystem : SystemBase
{
    public RayHitInfo TerrainHitInfo;
    public MouseInputComponent MouseInput;

    public struct MouseInputComponent
    {
        public bool IsLeftButtonUp;
    }
   
    public struct RayInput : IComponentData
    {
        public float3 Start;
        public float3 End;
    }

    protected override void OnUpdate()
    {
         // Update Fields here using Main Thread
    }
}

Wouldn’t querying an Entity with ICompnentData be slower?

[UpdateAfter(typeof(MouseInputSystem))]
public class PlayerAttackSystem : SystemBase
{
    private EntityQuery _mouseQuery;

    protected override void OnCreate()
    {
        _mouseQuery = EntityManager.CreateEntityQuery(ComponentType.ReadOnly<MouseInputSystem.MouseInputComponent>());
    }
    protected override void OnUpdate()
    {
        var mouseInput = _mouseQuery.GetSingleton<MouseInputComponent>();

        // Rest of work here
    }
}

I am not seeing the benefit/usage of BlobAssetReference<T> for this scenario yet. But I’ll try to find more resources online regarding it.

At the moment my guess is that it could make sense if you have a large struct/list/array where, and you want to copy by reference rather than by value as you will be (I am assuming) passing around some sort of pointer as value, rather than the whole value. But for storing relatively small structs (floats/bools) is it worth using a BlobAssetReference<T>? I am assuming the dereference of BlobAsssetReference will still cause a random memory access read.

Maybe could also make if you have a lot of optional reads, where there is a hot path where you may not need some data, and fetching it up front once would be more costly (Random Memory Access), where as with a BlobAssetReference you can dereference it inside a job in the branch where you actually need it.

Just read the following [Some Questions about the Upcoming ISystemBase Interface - Unity Forum] .

I guess this will be the reason to use Singleton queries rather than my current approach!

While I see fields in your systems, I don’t see static fields in your system. That’s a huge difference, and probably why you haven’t encountered any issues (because non-static fields don’t cause issues with fast play mode). Whether or not you want to couple systems together by referring to other systems in code is up to you. I personally try to avoid it as much as possible but everyone seems to have their own opinion on the matter.

2 Likes