Runtime guidance
Performance And Threading
Choose clear defaults first, then use invoke variants, ref payloads, coalescing, and main-thread bridges where profiling calls for them.
- Backend guidance
- Invoke variants
- Main-thread rules
- Token lifecycle cost
Azkar EDA is designed to keep normal Unity event and state code clear first, then provide measured options for performance-sensitive paths. Start with the clear path by default, and use faster variants only when profiling shows the path matters.
Deep references:
Backend Guidance
AzEvent
- Normal use
Discrete notification with no payload
- Hot-path note
Cheapest shape and easiest to reason about.
AzEvent<T>
- Normal use
Discrete notification with payload
- Hot-path note
Good default for small payloads and normal gameplay code.
AzEventRef<T>
- Normal use
Large struct payload
- Hot-path note
Avoids copying a large struct to every handler.
AzPriorityEvent
- Normal use
Ordered no-payload notification
- Hot-path note
Use only when order matters.
AzPriorityEvent<T>
- Normal use
Ordered payload notification
- Hot-path note
More structure than plain events; useful for phases.
AzPriorityEventRef<T>
- Normal use
Ordered large struct payload
- Hot-path note
For hot ordered paths with struct payloads.
AzState<T>
- Normal use
Current meaningful value
- Hot-path note
Prefer for values that need initial delivery and later changes.
Invoke Variants
Use the safest, clearest invocation that fits the path.
Invoke
- Best for
Normal gameplay dispatch for events, priority events, and ref events
- Tradeoff
Balanced default.
InvokeSafe
- Best for
Dispatch where one bad listener should not break others
- Tradeoff
Exception isolation costs more than trusted direct dispatch.
InvokeFast
- Best for
Measured
AzPriorityEventhot paths with trusted listeners- Tradeoff
Priority-event fast path with fewer protections.
Example:
using Azkar.Eda;
public static class FrameEvents
{
public static readonly AzEvent<int> FrameSampled = new();
}using UnityEngine;
public sealed class FrameSampler : MonoBehaviour
{
private void Update()
{
FrameEvents.FrameSampled.Invoke(Time.frameCount);
}
}Do not start with InvokeFast. First make the lifecycle correct, then profile, then use priority-event fast invocation only on handlers you trust. Plain AzEvent code should normally choose between Invoke and InvokeSafe.
SourceSet, Scheduling, And Coalescing
AzState<T> is for values with current meaning. In state-heavy systems, SourceSet gives you distinct-until-changed writes and participates in the state's scheduling behavior.
using UnityEngine;
using Azkar.Eda;
public sealed class HealthWriter : MonoBehaviour
{
public void ApplyDamage(int amount)
{
int next = PlayerState.Health.Value - amount;
PlayerState.Health.SourceSet(next);
}
}State guidance:
- Use state when late subscribers need the current value.
- Prefer one owner or a clear set of allowed writers.
- Keep state ownership clear so you can debug who changed a value.
- Expect scheduled state writes to notify according to their scheduling rules, not necessarily at the exact line where the write was requested.
- Avoid writing state in tight loops if you only need the final value. Coalesce and publish once.
Main Thread Rules
Unity has a practical rule: most Unity APIs belong on the main thread.
Keep these on the main thread:
UnityEngine.Objectaccess.- Scene object creation and destruction.
- Transform changes.
- UI updates.
- Most event invocations that reach scene objects.
- Most state writes that update UI or scene listeners.
Move plain data across thread boundaries, then publish on the main thread.
using System.Collections.Concurrent;
using UnityEngine;
using Azkar.Eda;
public sealed class MainThreadBridge : MonoBehaviour
{
private readonly ConcurrentQueue<float> _damageResults = new();
public void EnqueueDamageFromWorker(float amount)
{
_damageResults.Enqueue(amount);
}
private void Update()
{
while (_damageResults.TryDequeue(out float amount))
{
CombatEvents.DamageApplied.Invoke(amount);
}
}
}Off Thread Caveats
When working off thread:
- Use plain data only.
- Avoid captured
MonoBehaviour,GameObject,Transform,ScriptableObject, or UI references. - Do not assume a handler is thread-safe because the event type can be referenced from a worker.
- Be careful with subscription and disposal while another thread is publishing.
- Prefer queueing results back to a main-thread bridge.
Token Lifecycle Cost
Tokens and bags make cleanup explicit. They also make lifecycle choices visible.
Fast and clear:
using UnityEngine;
using Azkar.Eda;
public sealed class MenuView : MonoBehaviour
{
private readonly AzEventTokenBag _tokens = new();
private void OnEnable()
{
MenuEvents.Opened.Subscribe(OnOpened).AddTo(_tokens);
}
private void OnDisable()
{
_tokens.DisposeAll();
}
private void OnOpened() { }
}Guidance:
- Subscribe once per owner lifecycle.
- Dispose the bag at the matching lifecycle boundary.
- Prefer pausing and resuming only when you need temporary silence without losing ownership.
- Dispose when the listener is no longer valid.
- Avoid subscribing every frame.
- Avoid creating short-lived lambdas in hot loops.
- Batch related subscriptions into one bag.
- Trim capacity only after large one-time subscription spikes if the guide for that type supports it.
- Use metrics and profiling before optimizing token operations.
Tracking Cost
Tracking is an editor observability tool. It is valuable while debugging, but captured value samples and high-volume activity can add overhead.
Guidance:
- Enable
CaptureValueswhere supported value samples help debugging. - Avoid capturing noisy payloads on extremely hot events unless needed.
- Use groups and tags so filters stay usable.
- Turn off unnecessary runtime registrations when a temporary debugging session is done.
Practical Performance Checklist
- Use
AzEventfor unordered signals. - Use
AzPriorityEventonly when order matters. - Use
AzState<T>for current values. - Keep subscription setup outside tight loops.
- Dispose bags reliably.
- Use ref payloads for large struct payloads.
- Coalesce repeated state writes when only the final value matters.
- Queue worker results back to main thread.
- Profile before changing priority-event
Invokecalls toInvokeFast.
