Hello all! Apologies in advance if this question is confusing, as I’m pretty tired.
I want to provide an ISharedComponentData RenderMesh which has been created on the Main Thread to an IJobForEachWithEntity struct that I have Schedule()'d to Execute() over a set of Entities selected using an EntityQuery. I want to assign this RenderMesh to some of the Entities depending on the info in their IComponentDatas. Ideally I’d like this to be performed in parallel, and as fast as is possible, since I have many Entities to iterate over.
I am aware of the fact that we are not allowed to access the values of an ISharedComponentData in a threaded Job, because the ISharedComponentData can contain references to Managed memory and this is unsafe to use in a Job thread.
However I have been doing a bit of research on the forums here and reading some of @5argon 's wonderful articles on the topic and I have heard that there is a way to get an “index” to an ISharedComponentData which exists in the ECS system, and is internally used to attach ISharedComponentData to Entity Chunks. I assume the index integer is used internally to allow us to use Entities to retrieve ISharedComponentData values using calls to EntityManager on the main thread (e.g. with GetSharedComponentData(Entity)).
Since I am not interested in actually accessing the values of the RenderMesh, but merely assigning the pre-existing RenderMesh to a given Entity… Is there any way I can provide this index to a call to EntityCommandBuffer.Concurrent.SetSharedComponent(Entity)? All I want to do is attach the pre-created RenderMesh to the Entity, not perform any operations on it’s Managed data or read or write to it. But I cannot simply pass the RenderMesh into the Job struct because of:
Unity.Rendering.RenderMesh used in NativeArray<Unity.Rendering.RenderMesh> must be unmanaged (contain no managed types).
Here’s the stripped-down for readability version of my current Job code:
First the IJobForEachWithEntity itself:
[ExcludeComponent(typeof(BadComponent))]
struct ARenderMeshJob : IJobForEachWithEntity<ComponentOne, ComponentTwo>
[DeallocateOnJobCompletion]
[ReadOnly] public NativeArray<RenderMesh> renderMeshies;
public EntityCommandBuffer.Concurrent context;
public void Execute(Entity entity, int index, ref ComponentOne compA, [ReadOnly] ref ComponentTwo compB) {
int number = compA.numberData; //id no. of desired RenderMesh
...
//Does it already have a RenderMesh?
bool hasRenderMesh = ECSMain.mainEntityManager.HasComponent<RenderMesh>(entity);
//Update/set this Entity's RenderMesh:
RenderMesh meshInstance = renderMeshies[number];
if (hasRenderMesh) {
context.SetSharedComponent<RenderMesh>(index, entity, meshInstance);
} else {
context.AddSharedComponent<RenderMesh>(index, entity, meshInstance);
}
}
}
When the JobComponentSystem starts it runs:
protected override void OnCreate() {
this.Enabled = true;
barrier = World.Active.GetOrCreateSystem<ARenderMeshCommandBufferSystem>();
entityQuery = GetEntityQuery(
ComponentType.ReadWrite<ComponentOne>(),
ComponentType.ReadOnly<ComponentTwo>(),
ComponentType.Exclude<BadComponent>()
);
}
And here’s how the Job is Schedule()'d (on main thread):
protected override JobHandle OnUpdate(JobHandle inputDeps) {
//Get all the unique Shared RenderMeshDataGroups.
var uniqueTypes = new List<RenderMeshDataGroup>(10);
EntityManager.GetAllUniqueSharedComponentData(uniqueTypes);
//Prepare an array for the to-be-scheduled JobHandles.
JobHandle[] depsArray = new JobHandle[uniqueTypes.Count + 1];
depsArray[0] = inputDeps;
//Schedule ARenderMeshJob for each unique type.
for (int sharedIndex = 0; sharedIndex < uniqueTypes.Count; sharedIndex++) {
RenderMeshDataGroup unique = uniqueTypes[sharedIndex];
//Pre-build RenderMeshes for the Job
NativeArray<RenderMesh> meshies = new NativeArray<RenderMesh>(5, Allocator.TempJob);
for (int meshIndex = 0; meshIndex < meshies.Length; meshIndex++) {
meshies[meshIndex] = new RenderMesh
{
mesh = unique.currentMesh,
material = unique.currentMaterial,
subMesh = 0,
castShadows = unique.currentShadowMode,
receiveShadows = unique.receiveShadows
};
}
//Finally schedule the Job.
entityQuery.SetFilter(unique);
ARenderMeshJob job = new ARenderMeshJob()
{
renderMeshies = meshies,
context = barrier.CreateCommandBuffer().ToConcurrent()
};
depsArray[sharedIndex + 1] = job.Schedule<ARenderMeshJob>(entityQuery, inputDeps);
}
//Coalesce scheduled job dependencies.
var scheduledJobs = new NativeArray<JobHandle>(uniqueTypes.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
scheduledJobs.CopyFrom(depsArray);
JobHandle combinedDeps = JobHandle.CombineDependencies(scheduledJobs);
scheduledJobs.Dispose();//be sure to dispose the temporary NativeArray.
barrier.AddJobHandleForProducer(combinedDeps);//ensure EntityCommandBuffer schedules it's buffer playback correctly
return combinedDeps;
}
As you can see, I need the to select the appropriate RenderMesh based on an ID number in the IComponentData for each Entity. However, this code is invalid, and I can’t seem to find a way to get the ISharedComponentData into the Job struct. The RenderMesh contains Meshes and Materials that cannot be simply inserted into the struct. I would really like if I could instead of passing the RenderMesh, pass the index to the ISharedComponentData and attach that since an int can be easily copied. Ideally I’d like to avoid using the low-level Chunk Iteration API but if that’s the only way, I would appreciate some pointers!
Any help would be greatly appreciated! Thank you!