Skip to content

seikasan/EntitiesEventStream

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Entities Event Stream

license

日本語版READMEはこちら

Requirements

  • Unity 6.0 / 6000.0 or newer
  • Entities 1.4.7 or newer

Installation

Open Package Manager, choose Add package from git URL, and use:

https://github.com/seikasan/EntitiesEventStream.git?path=EntitiesEventStream

Or add the package to Packages/manifest.json:

{
  "dependencies": {
    "com.seikasan.entities-event-stream": "https://github.com/seikasan/EntitiesEventStream.git?path=EntitiesEventStream"
  }
}

A local checkout can also be installed with Add package from disk by selecting EntitiesEventStream/package.json.

Entities Event Stream is a high-throughput event stream package for Unity Entities. It stores unmanaged events in worker-local lanes without a shared atomic append counter, publishes immutable segments without copying event payloads, gives each reader an independent cursor, and grows main-thread storage, segment metadata, and reader capacity on demand.

Registration and lifetime

Register each event type at the assembly level with one retention mode. The source generator registers the required generic singleton component type and creates the lifecycle system.

using EntitiesEventStream;

[assembly: RegisterFrameEvent(typeof(DamageEvent))]
[assembly: RegisterRetainedEvent(typeof(SpawnEvent))]

RegisterFrameEvent keeps published events until the end of their publication frame. Pending frame events that are never published by a reader or an explicit flush are discarded at the generated frame boundary.

RegisterRetainedEvent keeps events for one additional frame. Pending retained events are published at the generated frame boundary, and events published during frame F remain available until the end of frame F+1.

Registering the same event type with both retention modes reports source-generator diagnostic SEES004.

Create only the accessors a system needs. Producers keep an EventStreamWriter<T>, while consumers keep an EventStreamReader<T>. Creating a writer does not allocate a reader slot; each reader receives its own cursor.

Main-thread production

Create the persistent writer binding in OnCreate. For a single event, call Write directly:

private EventStreamWriter<DamageEvent> _writer;

public void OnCreate(ref SystemState state)
{
    _writer = EventStreamAPI.CreateWriter<DamageEvent>(ref state);
}

public void OnUpdate(ref SystemState state)
{
    if (!_writer.Write(ref state, new DamageEvent { Amount = 10 }))
    {
        // Apply the application's retry, telemetry, or loss policy.
    }
}

When one update emits several events, open one short-lived writer and reuse it:

EventWriter<DamageEvent> writer = _writer.Open(ref state);
for (int i = 0; i < count; i++)
{
    writer.Write(new DamageEvent { Amount = i });
}

Open(ref state) refreshes ECS dependencies and returns a writer for the current publication epoch. Do not retain that writer across Flush, a reader operation that publishes pending events, a frame boundary, stream reconfiguration, or stream disposal.

EventStreamWriter<T>.Write(ref state, value) and EventWriter<T>.Write return whether the event was accepted. EventWriter<T>.Write validates the publication epoch even when Unity Collections safety checks are disabled and returns false without modifying the stream when the writer is stale.

WriteUnchecked skips publication-epoch and Unity safety-handle validation. Use it only on a measured hot path whose lifetime guarantees are enforced by the caller. Using it after publication, reconfiguration, a frame boundary, or disposal has undefined behavior.

Parallel production

Set workerCapacityPerThread high enough for the maximum number of events that one Unity worker can emit before the next publication, then pass a short-lived parallel writer to the job:

state.Dependency = new ProduceJob
{
    Writer = _writer.AsParallelWriter(ref state)
}.ScheduleParallel(state.Dependency);

Keep the scheduled job in state.Dependency so later stream operations can complete the producer dependency before publishing or reading the events.

EventParallelWriter<T>.Write validates the publication epoch, worker lane, and per-lane capacity in every build configuration. It returns true after writing. If validation fails, it returns false without writing payload memory, including in Burst player builds.

EventParallelWriter<T>.WriteUnchecked performs no epoch, lane, or capacity validation. It is valid only when the writer belongs to the current publication epoch, the job is scheduled correctly, no publication boundary can run concurrently, and every worker remains below workerCapacityPerThread. Violating these conditions can write outside allocated memory. Start with Write and switch only when profiling shows that the validation cost matters.

Normal consumption

Read(ref state) completes relevant stream dependencies, publishes pending writes, opens a zero-copy window, and returns a scope whose Dispose advances the reader cursor to the end of that window.

private EventStreamReader<DamageEvent> _reader;

public void OnCreate(ref SystemState state)
{
    _reader = EventStreamAPI.CreateReader<DamageEvent>(ref state);
}

public void OnUpdate(ref SystemState state)
{
    using var events = _reader.Read(ref state);
    foreach (ref readonly var damage in events)
    {
        // Consume the event.
    }
}

public void OnDestroy(ref SystemState state)
{
    _reader.Dispose(ref state);
}

The first consumer that runs after a producer normally becomes the same-frame publication boundary, so an ordinary producer-before-consumer setup does not need a separate publication system.

Explicit control

Use Flush(ref state) when the application needs an explicit publication segment or visibility boundary before a consumer reads:

_writer.Flush(ref state);

Use ReadWindow(ref state) and Commit(window) for partial processing, retry semantics, or jobs that consume a window explicitly:

EventReadWindow<DamageEvent> window = _reader.ReadWindow(ref state, maxEvents);
// Process the window, or schedule and complete a job that reads it.
_reader.Commit(window);

A scoped read commits its complete window when disposed, including during exception unwinding. Use the explicit API when failed processing must leave the cursor unchanged.

EventReadWindow<T>.CopyTo and EventReadScope<T>.CopyTo verify destination capacity before copying in every build configuration. TryCopyTo(destination, out written) is suitable for Burst or other non-throwing paths; when the destination is too small, it returns false, sets written to zero, and leaves the destination unchanged.

Publication rotates immutable segments without copying payloads. Main-thread payload storage, segment storage, and reader slots grow when necessary. Parallel worker capacity remains a fixed registration limit after the stream has started publishing, writing, or serving readers.

Performance profiling

Import the API Profiler Benchmark sample from Package Manager to measure the package with your own workload. It provides separate Profiler markers for accessor creation, writer opening, checked and unchecked writes, explicit flushes, scoped reads, iteration, and cursor commits. See performance measurement for guidance on cold and warm runs and on comparing Write with WriteUnchecked.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages