Create BlobAssetReference in IConvertGameObjectToEntity(A) and use in IConvertGameObjectToEntity(B)

If got a situation where I create a BlobAssetReference in a IConvertGameObjectToEntity and then in a separate IConvertGameObjectToEntity I want to add this to the Additional entities created.

This feels a little risky to me, is there a better way to handle this?

public class AuthoringA : MonoBehaviour, IConvertGameObjectToEntity
{
    public static BlobAssetReference<Data> dataBlob;

    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
       //..
        dataBlob = BuildBlob();
        dstManager.AddComponentData(entity, new BlobComponent {Data = dataBlob});
    }

//...
[UpdateAfter(typeof(AuthoringA))]
public class AuthoringB : MonoBehaviour, IConvertGameObjectToEntity
{


    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
       //..
        var e1 = conversionSystem.CreateAdditionalEntity(this.gameObject, additionalEntities);
        dstManager.AddComponentData(e1, new BlobComponent {Data = AuthoringA.dataBlob});
          //..
        var e2 = conversionSystem.CreateAdditionalEntity(this.gameObject, additionalEntities);
        dstManager.AddComponentData(e2, new BlobComponent {Data = AuthoringA.dataBlob});
        // ..
        var e3 = conversionSystem.CreateAdditionalEntity(this.gameObject, additionalEntities);
        dstManager.AddComponentData(e3, new BlobComponent {Data = AuthoringA.dataBlob});
        //..
    }

//...

I don’t know of the execution order of the conversions I think it’s done in the order the components are displyed on the inspector. I don’t think the update after work on IConvertGameObjectToEntity.

To solve your need I would try either :

  1. make a singleton c# class that job is to make the blob and keep a reference to it until all conversion ended.
    or
  2. make a specific monobehavior component that just do the blob and that is required by both your ecs authoring components. (the blob would need to be create not in a IConvertGameObjectToEntity but in a method that run before Awake maybe ?)

Its weird, i can’t seem to break it, swapping components it still works, trying to forcing the blob to run last with
[UpdateAfter()] it still works , removing [RequireComponent(typeof())] it still works. The only way i get an error is if I remove the component. IM not sure what voodoo this is

EDIT :
wait doesn’t it all sit in the conversion world and them its all assembles in dst world … maybe using the serialize hash

It compiles or it works ?
Does the blob asset reference have the expected value in all cases ? or do you get a “default” instead ?

Yea it seems to have the correct data, usually im pretty good at breaking stuff … pretty disappointed with myself :wink:

Well then I guess you are safe with your way of doing things ;).

Yea I double-checked, I really wish I knew why this is working… the GO is i

I can’t tell you how much that scares me

It could be that the archetype generated for AuthoringA has higher priority than the archetype for AuthoringB. Because of that, AuthoringA would always execute first. I don’t know what rules dictate that though.

For blob generation, I always use a GameObjectConversionSystem and BlobAssetComputationContext. It is a lot of boilerplate, but it handles deduplication and fixes the execution order to something sensible.

Yeah as @DreamingImLatios mentioned - BlobAssetComputationContext is what you need

Never seen BlobAssetComputationContext before. I should also point out that this is in a sub-scene.

https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/ECSSamples/Assets/Advanced/BlobAsset/MeshBBConversionSystem.cs
Some public code that traverses a hierarchy and computes hashes in a job: Latios-Framework/Physics/Physics/Authoring/LatiosColliderConversionSystem.cs at v0.2.1 · Dreaming381/Latios-Framework · GitHub
And here’s a third example from my audio library prototype:

using System.Collections.Generic;
using AudioClip = UnityEngine.AudioClip;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;

namespace Latios.Audio.Authoring
{
    [ConverterVersion("Latios", 1)]
    public class AudioConversionSystem : GameObjectConversionSystem
    {
        struct AudioClipComputationData
        {
            public Hash128                           hash;
            public int                               index;
            public BlobAssetReference<AudioClipBlob> blob;
        }

        protected override void OnUpdate()
        {
            Entities.ForEach((MusicTrackHybrid hybrid) =>
            {
                AddHybridComponent(hybrid);
            });

            var        clipList           = new List<AudioClip>();
            using (var computationContext = new BlobAssetComputationContext<AudioClipComputationData, AudioClipBlob>(BlobAssetStore, 128, Allocator.Temp))
            {
                int index = 0;
                Entities.ForEach((LatiosAudioSourceAuthoring authoring) =>
                {
                    var hash = new Hash128((uint)authoring.clip.GetInstanceID(), 0, 0, 0);
                    computationContext.AssociateBlobAssetWithUnityObject(hash, authoring.gameObject);
                    if (computationContext.NeedToComputeBlobAsset(hash))
                    {
                        computationContext.AddBlobAssetToCompute(hash, new AudioClipComputationData { hash = hash, index = index });
                    }
                    index++;
                    clipList.Add(authoring.clip);
                });
                using (var computationDataArray = computationContext.GetSettings(Allocator.TempJob))
                {
                    var     samples       = new NativeList<float>(Allocator.TempJob);
                    var     ranges        = new NativeArray<int2>(computationDataArray.Length, Allocator.TempJob);
                    var     rates         = new NativeArray<int>(computationDataArray.Length, Allocator.TempJob);
                    var     channelCounts = new NativeArray<int>(computationDataArray.Length, Allocator.TempJob);
                    for(int i             = 0; i < computationDataArray.Length; i++)
                    {
                        var clip           = clipList[computationDataArray[i].index];
                        var samplesManaged = new float[clip.samples * clip.channels];
                        clip.GetData(samplesManaged, 0);
                        var samplesUnmanaged = new NativeArray<float>(samplesManaged, Allocator.Temp);
                        ranges[i]            = new int2(samples.Length, samplesUnmanaged.Length);
                        samples.AddRange(samplesUnmanaged);
                        rates[i]         = clip.frequency;
                        channelCounts[i] = clip.channels;
                    }
                    new ComputeAudioClipBlobs
                    {
                        samples              = samples,
                        ranges               = ranges,
                        rates                = rates,
                        channelCounts        = channelCounts,
                        computationDataArray = computationDataArray
                    }.Schedule(ranges.Length, 1).Complete();
                    foreach (var data in computationDataArray)
                    {
                        computationContext.AddComputedBlobAsset(data.hash, data.blob);
                    }
                    samples.Dispose();
                    ranges.Dispose();
                    rates.Dispose();
                    channelCounts.Dispose();

                    Entities.ForEach((LatiosAudioSourceAuthoring authoring) =>
                    {
                        var hash = new Hash128((uint)authoring.clip.GetInstanceID(), 0, 0, 0);
                        computationContext.GetBlobAsset(hash, out var blob);

                        var entity = GetPrimaryEntity(authoring);
                        if (authoring.oneShot)
                        {
                            DstEntityManager.AddComponentData(entity, new AudioSourceOneShot
                            {
                                clip            = blob,
                                range           = authoring.range,
                                rangeFadeMargin = authoring.rangeFadeMargin,
                                volume          = authoring.volume
                            });
                            if (authoring.autoDestroyOnFinish)
                            {
                                DstEntityManager.AddComponent<AudioSourceDestroyOneShotWhenFinished>(entity);
                            }
                        }
                        else
                        {
                            DstEntityManager.AddComponentData(entity, new AudioSourceLooped
                            {
                                clip            = blob,
                                range           = authoring.range,
                                rangeFadeMargin = authoring.rangeFadeMargin,
                                volume          = authoring.volume,
                            });
                        }
                    });
                }
            }
        }

        [BurstCompile]
        struct ComputeAudioClipBlobs : IJobParallelFor
        {
            [ReadOnly] public NativeArray<float>         samples;
            [ReadOnly] public NativeArray<int2>          ranges;
            [ReadOnly] public NativeArray<int>           rates;
            [ReadOnly] public NativeArray<int>           channelCounts;
            public NativeArray<AudioClipComputationData> computationDataArray;

            public void Execute(int index)
            {
                var     builder  = new BlobBuilder(Allocator.Temp);
                ref var root     = ref builder.ConstructRoot<AudioClipBlob>();
                var     blobLeft = builder.Allocate(ref root.samplesLeftOrMono, ranges[index].y);
                if (channelCounts[index] == 2)
                {
                    var blobRight = builder.Allocate(ref root.samplesRight, ranges[index].y);
                    for (int i = 0; i < ranges[index].y; i++)
                    {
                        blobLeft[i / 2] = samples[ranges[index].x + i];
                        i++;
                        blobRight[i / 2] = samples[ranges[index].x + i];
                    }
                }
                else
                {
                    var blobRight = builder.Allocate(ref root.samplesRight, 1);
                    blobRight[0]  = 0f;
                    for (int i = 0; i < ranges[index].y; i += channelCounts[index])
                    {
                        blobLeft[i / channelCounts[index]] = samples[ranges[index].x + i];
                    }
                }
                var offsets                 = builder.Allocate(ref root.loopedOffsets, 3);
                offsets[0]                  = 0;
                offsets[1]                  = blobLeft.Length / 3;
                offsets[2]                  = offsets[1] * 2;
                root.sampleRate             = rates[index];
                var computationData         = computationDataArray[index];
                computationData.blob        = builder.CreateBlobAssetReference<AudioClipBlob>(Allocator.Persistent);
                computationDataArray[index] = computationData;
            }
        }
    }
}
1 Like