Adoption
Migration Guide
Move from C# events, manual ordered phases, property-change pairs, and manual cleanup into Azkar EDA patterns gradually.
- C# event migration
- Priority phase migration
- State migration
- Cleanup migration
This guide shows how to move common Unity patterns into Azkar EDA without changing the shape of your game all at once.
Deep references:
C# Events To AzEvent
Before:
using System;
using UnityEngine;
public static class ScoreEvents
{
public static event Action<int> ScoreChanged;
public static void RaiseScoreChanged(int score)
{
ScoreChanged?.Invoke(score);
}
}After:
using Azkar.Eda;
public static class ScoreEvents
{
public static readonly AzEvent<int> ScoreChanged = new();
}using UnityEngine;
using Azkar.Eda;
public sealed class ScoreWriter : MonoBehaviour
{
public void Publish(int score)
{
ScoreEvents.ScoreChanged.Invoke(score);
}
}Listener migration:
using UnityEngine;
using Azkar.Eda;
public sealed class ScoreListener : MonoBehaviour
{
private readonly AzEventTokenBag _tokens = new();
private void OnEnable()
{
ScoreEvents.ScoreChanged.Subscribe(OnScoreChanged).AddTo(_tokens);
}
private void OnDisable()
{
_tokens.DisposeAll();
}
private void OnScoreChanged(int score) { }
}The important change is ownership. Instead of relying on -= with the same delegate instance, keep the returned token in a bag.
Ordered Manual Phases To AzPriorityEvent
Before:
using System.Collections.Generic;
using UnityEngine;
public sealed class ManualTick : MonoBehaviour
{
private readonly List<System.Action> _input = new();
private readonly List<System.Action> _simulation = new();
private readonly List<System.Action> _presentation = new();
private void Update()
{
foreach (var action in _input) action();
foreach (var action in _simulation) action();
foreach (var action in _presentation) action();
}
}After:
using Azkar.Eda;
public static class TickBus
{
public static readonly AzPriorityEvent Tick = new();
}
public static class TickPriority
{
public const byte Input = 1;
public const byte Simulation = 5;
public const byte Presentation = 9;
}using UnityEngine;
using Azkar.Eda;
public sealed class TickDriver : MonoBehaviour
{
private void Update()
{
TickBus.Tick.Invoke();
}
}using UnityEngine;
using Azkar.Eda;
public sealed class TickSubscriber : MonoBehaviour
{
private readonly AzEventTokenBag _tokens = new();
private void OnEnable()
{
TickBus.Tick.Subscribe(ReadInput, TickPriority.Input).AddTo(_tokens);
TickBus.Tick.Subscribe(Simulate, TickPriority.Simulation).AddTo(_tokens);
TickBus.Tick.Subscribe(Present, TickPriority.Presentation).AddTo(_tokens);
}
private void OnDisable()
{
_tokens.DisposeAll();
}
private void ReadInput() { }
private void Simulate() { }
private void Present() { }
}Use priority slots 1 through 9 as named phases. Lower slots run first. If same-slot order matters, split the slot into separate named priorities.
Property Plus Event To AzState
Before:
using System;
public sealed class PlayerModel
{
public event Action<int> HealthChanged;
public int Health { get; private set; } = 100;
public void SetHealth(int health)
{
if (Health == health)
{
return;
}
Health = health;
HealthChanged?.Invoke(Health);
}
}After:
using Azkar.Eda;
public sealed class PlayerModel
{
public readonly AzState<int> Health = new(100);
public void SetHealth(int health)
{
Health.SourceSet(health);
}
}Listener:
using UnityEngine;
using UnityEngine.UIElements;
using Azkar.Eda;
public sealed class HealthHud : MonoBehaviour
{
[SerializeField] private UIDocument document;
[SerializeField] private PlayerModel model;
private readonly AzEventTokenBag _tokens = new();
private Label _label;
private void OnEnable()
{
_label = document.rootVisualElement.Q<Label>("healthLabel");
model.Health.Subscribe(OnHealthChanged).AddTo(_tokens);
}
private void OnDisable()
{
_tokens.DisposeAll();
}
private void OnHealthChanged(int health)
{
_label.text = health.ToString();
}
}Use state when subscribers need the current value immediately, not just future changes.
Cleanup Migration
Before:
using UnityEngine;
public sealed class OldListener : MonoBehaviour
{
private void OnEnable()
{
ScoreEvents.ScoreChanged += OnScoreChanged;
}
private void OnDisable()
{
ScoreEvents.ScoreChanged -= OnScoreChanged;
}
private void OnScoreChanged(int score) { }
}After:
using UnityEngine;
using Azkar.Eda;
public sealed class NewListener : MonoBehaviour
{
private readonly AzEventTokenBag _tokens = new();
private void OnEnable()
{
ScoreEvents.ScoreChanged.Subscribe(OnScoreChanged).AddTo(_tokens);
}
private void OnDisable()
{
_tokens.DisposeAll();
}
private void OnScoreChanged(int score) { }
}This is especially helpful when old code used lambdas:
using UnityEngine;
using Azkar.Eda;
public sealed class LambdaListener : MonoBehaviour
{
private readonly AzEventTokenBag _tokens = new();
private void OnEnable()
{
ScoreEvents.ScoreChanged
.Subscribe(score => Debug.Log($"Score: {score}"))
.AddTo(_tokens);
}
private void OnDisable()
{
_tokens.DisposeAll();
}
}With tokens, you do not need to recreate the exact lambda to remove it.
Recommended Migration Order
- Convert one small C# event to
AzEvent<T>. - Update its listeners to use
AzEventTokenBag. - Add tracking attributes to the bus and one handler.
- Convert one property plus change event to
AzState<T>. - Convert one manual ordered workflow to
AzPriorityEvent. - Run the scene and inspect the tracking window.
- Only then migrate broader systems.
