Jobs & Burst¶
Trecs integrates with Unity's job system and Burst compiler for parallel, high-performance entity processing. You can write job structs by hand — Trecs's source generator handles the scheduling boilerplate (ScheduleParallel, dependency tracking, field auto-wiring). For the simplest cases, the generator emits the entire job struct from a single annotated method.
[WrapAsJob] — the simplest approach¶
Mark a static [ForEachEntity] method with [WrapAsJob] and Trecs generates a Burst-compiled parallel job for you:
public partial class ParticleMoveSystem : ISystem
{
[ForEachEntity(typeof(SampleTags.Particle))]
[WrapAsJob]
static void Execute(
in Velocity velocity,
ref Position position,
in NativeWorldAccessor world)
{
position.Value += world.DeltaTime * velocity.Value;
}
}
Two requirements:
static— the generated Burst job calls your method directly, with no system instance, so it must bestatic.- Use
NativeWorldAccessor, notWorldAccessorfor any world-level operations (DeltaTime, structural changes, set ops) inside the body.
Trecs schedules the job instead of calling your Execute() method directly.
[PassThroughArgument] is supported here too and forwards the data to the generated job.
Manual job structs¶
To define a custom job struct instead of using [WrapAsJob], put [ForEachEntity] on the job's Execute method — Trecs generates a ScheduleParallel for it the same way:
public partial class ParticleJobSystem : ISystem
{
[BurstCompile]
partial struct MoveJob
{
public float DeltaTime;
[ForEachEntity(typeof(SampleTags.Particle))]
public readonly void Execute(in Velocity velocity, ref Position position)
{
position.Value += DeltaTime * velocity.Value;
}
}
public void Execute()
{
new MoveJob { DeltaTime = World.DeltaTime }.ScheduleParallel(World);
}
}
[ForEachEntity] is not required. You can write an entirely hand-rolled IJobFor / IJobParallelFor and still pass Trecs data into it. See Advanced Job Features.
Per-iteration EntityHandle¶
A [ForEachEntity] callback inside a job can take an EntityHandle parameter alongside its components — works in both [WrapAsJob] and manual job structs:
[ForEachEntity(typeof(SampleTags.Particle))]
[WrapAsJob]
static void Cull(ref Lifetime lifetime, EntityHandle handle, in NativeWorldAccessor world)
{
if (lifetime.Remaining <= 0)
handle.Remove(world);
}
The handle is materialized per iteration from a component buffer the source generator wires up automatically. Inside jobs, entity-targeted ops go through EntityHandle taking a NativeWorldAccessor parameter — the same handle.Op(accessor) pattern as on the main thread (where the accessor is a WorldAccessor instead).
NativeWorldAccessor¶
NativeWorldAccessor is the Burst-compatible counterpart to WorldAccessor. Get one with world.ToNative() (or take it as an in NativeWorldAccessor parameter — [WrapAsJob] auto-injects it). World-level operations (entity creation, set membership) live on NativeWorldAccessor directly; entity-targeted operations live on EntityHandle and take the native accessor as an argument:
// In a job:
nativeWorld.AddEntity<GameTags.Bullet>(sortKey: (uint)index)
.Set(new Position(pos));
entityHandle.Remove(nativeWorld);
entityHandle.SetTag<BallTags.Resting>(nativeWorld); // partition transition (turn tag on / switch variant)
entityHandle.UnsetTag<BallTags.Active>(nativeWorld); // partition transition (presence/absence dim only)
Warning
WorldAccessor is main-thread only. Inside jobs always use NativeWorldAccessor and the native read/write types — see Advanced Job Features for how to wire them via [FromWorld].
For deeper coverage — [FromWorld] auto-wiring, cross-template lookup types, native set operations, [GlobalIndex], external job-handle tracking — see Advanced Job Features. For the parallel iteration pattern itself, see Queries & Iteration.