I have implemented a job component system that uses generic jobs and components.
All works well in the editor but when I try to do a standalone build, it fails with this error:
Unable to find interface method Unity.Entities.IJobForEach_C1<GenericComponent1<T>>.Execute(ref T0) from type GenericJob1 at Unity.Entities.JobForEachExtensions.JobStruct_Process_C2<GenericJob1<Unity.Transforms.Translation>,GenericComponent1>.ExecuteChunk(ref Unity.Entities.JobForEachExtensions.JobStruct_Process_C2<GenericJob1<Unity.Transforms.Translation>,GenericComponent1<T>> jobData, System.IntPtr bufferRangePatchData, int begin, int end, Unity.Entities.ArchetypeChunk* chunks, int* entityIndices) at Unity.Entities.JobForEachExtensions.JobStruct_Process_C2<GenericJob1<Unity.Transforms.Translation>,GenericComponent1>.Execute(ref Unity.Entities.JobForEachExtensions.JobStruct_Process_C2<GenericJob1<Unity.Transforms.Translation>,GenericComponent`1> jobData, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, ref Unity.Jobs.LowLevel.Unsafe.JobRanges ranges, int jobIndex)
The standalone build succeeds if I remove [BurstCompile].
Build target is Windows x86 Standalone
I have tried both Mono and IL2CPP backends and I am using:
Unity 2019.3.0b8
Burst 1.1.2
DOTS Windows Platform 0.1.6
Entities 0.1.1
This is my code:
using Unity.Entities;
using Unity.Burst;
using Unity.Jobs;
using Unity.Transforms;
[assembly: RegisterGenericComponentType(typeof(GenericJob<Translation>))]
[assembly: RegisterGenericComponentType(typeof(GenericComponent<Translation>))]
public class MySystem : JobComponentSystem
{
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
inputDeps = new GenericJob<Translation>().Schedule(this);
inputDeps.Complete();
return inputDeps;
}
}
public struct GenericComponent<T> : IComponentData
where T : struct, IComponentData
{
public T data;
}
[BurstCompile]
public struct GenericJob<T> : IJobForEach<GenericComponent<T>> where T : struct, IComponentData
{
public void Execute(ref GenericComponent<T> componentData)
{
}
}
I do not believe declaring a IJobForEach like that is currently supported by burst. There are currently some limitations on how generics work, specifically with IJobForEach but I can’t remember precisely.
I should add generics do work on most other job types without issue so while it might be a little more boilerplate you can still get an generic jobs working.
using Unity.Entities;
using Unity.Burst;
using Unity.Jobs;
using Unity.Transforms;
[assembly: RegisterGenericComponentType(typeof(GenericComponent<Translation>))]
public class TranslationMySystem : MySystem<Translation>
{
}
public class MySystem<T> : JobComponentSystem
where T : struct, IComponentData
{
private EntityQuery query;
protected override void OnCreate()
{
// data for testing
this.EntityManager.CreateEntity(typeof(T));
this.query = this.GetEntityQuery(ComponentType.ReadWrite<T>());
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
inputDeps = new GenericJob<T>
{
TType = this.GetArchetypeChunkComponentType<T>(),
}
.Schedule(this.query, inputDeps);
return inputDeps;
}
}
[BurstCompile]
public struct GenericJob<T> : IJobChunk
where T : struct, IComponentData
{
public ArchetypeChunkComponentType<T> TType;
public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
{
var array = chunk.GetNativeArray(this.TType);
for (var i = 0; i < chunk.Count; i++)
{
T componentData = array[i];
}
}
}
public struct GenericComponent<T> : IComponentData
where T : struct, IComponentData
{
public T data;
}
This is not something I have really tried but seemed like a quick solution to throw up but I guess it’s not really a solution then!
However, something like this does work though (and I actually tested it this time.)
using Unity.Entities;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Transforms;
[assembly: RegisterGenericComponentType(typeof(GenericComponent<Translation>))]
public class MySystem : JobComponentSystem
{
private EntityQuery query;
protected override void OnCreate()
{
// data for testing
using (var n = new NativeArray<Entity>(1000000, Allocator.Temp))
{
var a = this.EntityManager.CreateArchetype(typeof(GenericComponent<Translation>));
this.EntityManager.CreateEntity(a, n);
}
this.query = this.GetEntityQuery(ComponentType.ReadWrite<GenericComponent<Translation>>());
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
inputDeps = new GenericJob<GenericComponent<Translation>>
{
TType = this.GetArchetypeChunkComponentType<GenericComponent<Translation>>(),
}
.Schedule(this.query, inputDeps);
return inputDeps;
}
}
[BurstCompile]
public struct GenericJob<T> : IJobChunk
where T : struct, IComponentData
{
public ArchetypeChunkComponentType<T> TType;
public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
{
var array = chunk.GetNativeArray(this.TType);
for (var i = 0; i < chunk.Count; i++)
{
T componentData = array[i];
}
}
}
public struct GenericComponent<T> : IComponentData
where T : struct, IComponentData
{
public T data;
}
In case anyone stumbles on the same problem, I ended up with a slightly modified version of tertle’s code in order to access GenericComponent instead of T in my job:
using Unity.Entities;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Transforms;
[assembly: RegisterGenericComponentType(typeof(GenericComponent<Translation>))]
public class TranslationMySystem : MySystem<Translation>
{
}
public class MySystem<T> : JobComponentSystem where T : struct, IComponentData
{
private EntityQuery query;
protected override void OnCreate()
{
// data for testing
using (var n = new NativeArray<Entity>(1000000, Allocator.Temp))
{
var a = this.EntityManager.CreateArchetype(typeof(GenericComponent<T>));
this.EntityManager.CreateEntity(a, n);
}
this.query = this.GetEntityQuery(ComponentType.ReadWrite<GenericComponent<T>>());
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
inputDeps = new GenericJob<T>
{
TType = this.GetArchetypeChunkComponentType<GenericComponent<T>>(),
}
.Schedule(this.query, inputDeps);
return inputDeps;
}
}
[BurstCompile]
public struct GenericJob<T> : IJobChunk where T : struct, IComponentData
{
public ArchetypeChunkComponentType<GenericComponent<T>> TType;
public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
{
var array = chunk.GetNativeArray(this.TType);
for (var i = 0; i < chunk.Count; i++)
{
var genericComponent = array[i];
genericComponent.metaData++;
array[i] = genericComponent;
}
}
}
public struct GenericComponent<T> : IComponentData where T : struct, IComponentData
{
public T data;
public int metaData;
}
Now I can simply declare a class WhateverSystem : MySystem and metadata on all GenericComponent will automatically be processed in parallel.
@kingstone426 , could you please share your use case ? I ask because I very well understand this must be useful for my procedural generation, I just don’t know how yet, trying to wrap my mind around the data design of my project. Thanks.
My particular use case is interpolation, i.e. blending/tweening/lerping between two values:
public struct InterpolationComponent<T> : IComponentData where T : IComponentData
{
public ulong startTime;
public ulong targetTime;
public T startValue;
public T targetValue;
public float percentage;
}
This way, if an entity has several components that require interpolation, I can add separate interpolation components using generics, e.g., InterpolationComponent InterpolationComponent etc.
I am sure there are many other use cases where you will want to attach some MetaData to an entity with T.