A high performance solution for safely creating events between systems in Unity ECS.
- High performance
- Same frame producing then consuming
- Share events across worlds
- Any system order
- Support different update rates. e.g. produce in fixed update, consume in update
- Any number of producers and consumers
- Fully threaded
- No sync points
- No garbage
- Easy dependency management
- Safety checks
- Support streams of data
Provides convenience at the cost of being nondeterministic.
Entities.ForEach
var writer = this.eventSystem.CreateEventWriter<YourEvent>();
this.Entities.ForEach((Entity entity) =>
{
writer.Write(new YourEvent { Entity = entity });
})
.ScheduleParallel();
this.eventSystem.AddJobHandleForProducer<YourEvent>(this.Dependency);
Provides determinism by manually handling indexing.
Entities.ForEach
var writer = this.eventSystem.CreateEventWriter<YourEvent>(this.query.CalculateEntityCount());
this.Entities.ForEach((Entity entity, int entityInQueryIndex) =>
{
writer.BeginForEachIndex(entityInQueryIndex);
writer.Write(new YourEvent { Entity = entity });
writer.EndForEachIndex();
})
.WithStoreEntityQueryInField(ref this.query)
.ScheduleParallel();
this.eventSystem.AddJobHandleForProducer<YourEvent>(this.Dependency);
IJobChunk
[BurstCompile]
private struct ProducerJob : IJobChunk
{
[ReadOnly]
public ArchetypeChunkEntityType EntityTypes;
public NativeEventStream.IndexWriter Writer;
public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
{
var entities = chunk.GetNativeArray(this.EntityTypes);
this.Writer.BeginForEachIndex(chunkIndex); // chunkIndex
for (var i = 0; i < chunk.Count; i++)
{
var entity = entities[i];
this.Writer.Write(new YourEvent { Entity = entity });
}
this.Writer.EndForEachIndex();
}
}
var writer = this.eventSystem.CreateEventWriter<YourEvent>(this.query.CalculateChunkCount()); // ChunkCount
this.Dependency = new ProducerJob
{
Writer = writer,
EntityTypes = this.GetArchetypeChunkEntityType(),
}
.ScheduleParallel(this.query, this.Dependency);
this.eventSystem.AddJobHandleForProducer<YourEvent>(this.Dependency);
Reader is the same regardless of writing mode
IJobEvent is the easiest way to read back events when no extra data is being streamed. To create a job just implement the IJobEvent interface.
[BurstCompile]
private struct EventJob : IJobEvent<YourEvent>
{
public NativeQueue<int>.ParallelWriter Counter;
public void Execute(YourEvent e)
{
// do what you want
}
}
var eventSystem = this.World.GetOrCreateSystem<EventSystem>();
this.Dependency = new EventJob
{
// assign job fields
}
.Schedule<EventJob, YourEvent>(eventSystem);
Each reader will be schedule to be read one after the other. Each foreach index is read in parallel.
Sometimes you need a bit more control over reading as the event system allows streaming of any type of data in your events. IJobEventReaderForEach gives you direct access to the reader allowing you to read it back in whatever format you desire.
[BurstCompile]
private struct EventStreamJob : IJobEventReaderForEach<YourEvent>
{
public void Execute(NativeEventStream.Reader stream, int foreachIndex)
{
var count = stream.BeginForEachIndex(foreachIndex);
for (var i = 0; i < count; i += 4)
{
var e = stream.Read<YourEvent>();
var d1 = stream.Read<YourData1>();
var d2 = stream.Read<YourData2>();
var d3 = stream.Read<YourData3>();
}
}
}
var eventSystem = this.World.GetOrCreateSystem<EventSystem>();
this.Dependency = new EventStreamJob
{
// assign job fields
}
.ScheduleParallel<EventStreamJob, YourEvent>(eventSystem);
If you need single thread access to the entire reader, you can instead use IJobEventReader.
[BurstCompile]
private struct EventStreamJob : IJobEventReader<YourEvent>
{
public void Execute(NativeEventStream.Reader stream, int readerIndex)
{
for (var foreachIndex = 0; foreachIndex < reader.ForEachCount; foreachIndex++)
{
var count = reader.BeginForEachIndex(foreachIndex);
for (var i = 0; i < count; i++)
{
var e = reader.Read<YourEvent>();
}
}
}
}
var eventSystem = this.World.GetOrCreateSystem<EventSystem>();
this.Dependency = new EventStreamJob
{
// assign job fields
}
.Schedule<EventStreamJob, YourEvent>(eventSystem);
If you need to read your events on the main thread you can use ConsumeSingleEventSystemBase.
public class MyEventSystem : ConsumeSingleEventSystemBase<MyEvent>
{
protected override void OnEvent(MyEvent e)
{
}
}
Again if you need a bit more control over reading your events, you can implement ConsumeEventSystemBase instead.
public class MyEventSystem : ConsumeEventSystemBase<MyEvent>
{
protected override void OnEventStream(ref NativeEventStream.Reader reader, int eventCount)
{
}
}
// TODO
this.Dependency = this.eventSystem.GetEventReaders<T>(this.Dependency, out IReadOnlyList<NativeEventStream.Reader> readers);
this.eventSystem.AddJobHandleForConsumer<T>(this.Dependency);
For convenience, a number of common functions and patterns have been included.
To access, use var extensions = this.World.GetOrCreateSystem<TestEventSystem>().Ex<YourEvent>();
/// <summary>
/// Ensure a NativeHashMap{TKey,TValue} has the capacity to be filled with all events of a specific type.
/// </summary>
public JobHandle EnsureHashMapCapacity<TKey, TValue>(JobHandle handle,NativeHashMap<TKey, TValue> hashMap)
/// <summary> Get the total number of events of a specific type. </summary>
public JobHandle GetEventCount(JobHandle handle, NativeArray<int> count)
// TODO
- Custom Event Systems
- Cross world events
// TODO