> For the complete documentation index, see [llms.txt](https://docs.parsue.io/aceland-unity-packages/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.parsue.io/aceland-unity-packages/core-packages/lifecycle.md).

# Lifecycle

Deterministic, dependency-ordered global initialization and a unified quit pipeline for Unity.

[![Open Runtime, Paid Editor Tools](https://img.shields.io/badge/Open%20Runtime,%20Paid%20Editor%20Tools-C24B42)](https://docs.parsue.io/aceland-unity-packages/open-and-paid/open-core-and-paid-editor-tools) ![Burst](https://img.shields.io/badge/Burst-Ready-FF6C37) ![CoreCLR Ready](https://img.shields.io/badge/CoreCLR-Ready-4A5BC2)

[![Sponsor](https://img.shields.io/badge/Sponsor-%E2%9D%A4-db61a2?logo=githubsponsors\&logoColor=white)](https://github.com/sponsors/parsue) [![Discord](https://img.shields.io/badge/Discord-Join-5865F2?logo=discord\&logoColor=white)](https://discord.gg/XsCYGnYzuc) [![Github](https://img.shields.io/badge/github-repo-blue?logo=github)](https://github.com/parsue/com.aceland.lifecycle)

## AceLand Lifecycle

Deterministic, dependency-ordered global initialization and a unified quit pipeline for Unity.

### In One Line

Your app lives and dies on my schedule.

### Overview

`AceLand.Lifecycle` replaces the scattered mix of `[RuntimeInitializeOnLoadMethod]`, `Awake` and `Start` that most projects use for bootstrapping. Each module simply declares **which phase it belongs to** and **what it depends on**; a topological sorter then decides the exact, deterministic execution order.

It also unifies application shutdown into a single **quit pipeline**: busy systems can hold the quit, async wrap-up handlers run in a predictable order, every module is shut down in reverse, and only then does the application actually exit — with timeouts that guarantee it never deadlocks.

A set of Editor windows visualise the Initialization Graph, the Initialization Timeline and the Quit Pipeline Graph, so the whole lifecycle is inspectable rather than guessed at.

{% hint style="info" %}
**This package follows the Open Core & Paid Editor Tools model.** The runtime container, Source Generator, and Abstractions are **free & open source forever** — your game builds and runs without any license. Only the development-time Editor tools (dependency graph, validation, diagnostics) require a paid **AceLand Injection** license (subscription *or* one-time purchase).

Since **v1.0.1** the licensing layer (`com.aceland.licensing`) is an **optional** dependency, not a hard one — it is **not** installed with the package. The first time you open a paid Editor tool without it, Injection offers a one-click install; your runtime and build are never affected either way.

See [Open Core & Paid Editor Tools](/aceland-unity-packages/open-and-paid/open-core-and-paid-editor-tools.md) for what is free vs. paid, activation, seats, Discord support, and billing.
{% endhint %}

### Package Info

<table data-header-hidden data-search="false"><thead><tr><th width="176.60003662109375"></th><th></th></tr></thead><tbody><tr><td>display name</td><td>AceLand Lifecycle</td></tr><tr><td>package name</td><td>com.aceland.lifecycle</td></tr><tr><td>latest version</td><td>1.0.5</td></tr><tr><td>namespace</td><td>AceLand.Lifecycle</td></tr><tr><td>git repository</td><td><a href="https://github.com/parsue/com.aceland.lifecycle.git">https://github.com/parsue/com.aceland.lifecycle.git</a></td></tr><tr><td>unity</td><td>2022.3 or newer</td></tr><tr><td>dependencies</td><td>none (runtime)<br> · com.aceland.licensing 1.0.1 is an optional Editor-only add-on</td></tr></tbody></table>

***

### Why Use It

* **Deterministic order** — the same dependency graph always produces the same boot order, on every machine and every run.
* **Declarative** — an attribute states intent; you never hand-wire call order or juggle `Awake` timing.
* **Dependencies stay honest** — you point at real types with `typeof(...)`, so a code reference, the asmdef reference and the package dependency all stay consistent automatically.
* **Async without chaos** — async modules are awaited in order, and opt-in parallelism lets independent modules warm up concurrently.
* **A real shutdown story** — one quit entry point handles the Editor and builds alike: wait for busy systems, run wrap-up handlers, shut everything down, then exit. Timeouts mean it never hangs.
* **Inspectable** — Editor windows show the Initialization Graph, the Initialization Timeline and the Quit Pipeline Graph.
* **Safe by design** — every static resets cleanly, so it behaves correctly whether or not Domain Reload is disabled.

***

### How It Works

The lifecycle runs in four strictly ordered phases. A phase begins only after the previous one has fully finished.

<table><thead><tr><th width="107.20001220703125">Phase</th><th width="219.4000244140625">Unity timing</th><th>Use for</th></tr></thead><tbody><tr><td><code>Core</code></td><td>AfterAssembliesLoaded</td><td>Pure C#; no UnityEngine objects</td></tr><tr><td><code>Runtime</code></td><td>BeforeSceneLoad</td><td>UnityEngine APIs available, no scene objects yet</td></tr><tr><td><code>Scene</code></td><td>AfterSceneLoad</td><td>Needs an existing scene / creates GameObjects</td></tr><tr><td><code>Late</code></td><td>after AfterSceneLoad</td><td>Final wrap-up: analytics, warm-up, intro flow</td></tr></tbody></table>

Within a phase, the order is decided by dependencies (via `DependsOn`), with `Order` as a tie-break. The attribute only **registers** the module — it never decides order by itself.

```mermaid
flowchart TD
    A[Driver runs at each Unity phase] --> B[Auto-scanner registers modules]
    B --> C[Sort phase by dependencies]
    C --> D[Initialize each module]
    D --> E[Await InitializeAsync if async]
    E --> F[Mark Ready and fire callbacks]
    F --> G[Next phase or InitializationCompleted]
```

Only assemblies marked with `[assembly: LifecycleAssembly]` are scanned, so startup never reflects over the whole project.

{% hint style="warning" %}
The phases run on their own fire-and-forget async chain, **independent** of Unity's `Awake` / `Start`. A scene `Start()` can run before your async modules finish, so never call `Get<T>()` for an async module from a MonoBehaviour — use `WhenReady<T>` / `WhenReadyAsync<T>` instead. See MonoBehaviours Are Not Part of the Phase Chain.
{% endhint %}

***

### Quick Start

#### 1. Opt the assembly in

Add this once per assembly that contains modules (for example in an `AssemblyInfo.cs`):

```csharp
using AceLand.Lifecycle;

[assembly: LifecycleAssembly]
```

{% hint style="warning" %}
Forget this and your modules simply never run — startup only scans assemblies that carry `[assembly: LifecycleAssembly]`. If a module seems to be ignored, check this marker first.
{% endhint %}

#### 2. Write a module

A synchronous module derives from `ModuleBase` and declares its phase.

{% tabs %}
{% tab title="Sync module" %}

```csharp
using AceLand.Lifecycle;
using UnityEngine;

[LifecycleModule(ModulePhase.Core, Order = -5000)]
public sealed class GameSettings : ModuleBase
{
    public string GameName { get; private set; }

    public override void Initialize()
    {
        GameName = "My Game";
        Debug.Log("GameSettings ready");
    }

    public override void Shutdown()
    {
        GameName = null;
    }
}
```

{% endtab %}

{% tab title="Async module" %}

```csharp
using System.Threading;
using System.Threading.Tasks;
using AceLand.Lifecycle;

// Depends on GameSettings, so it always runs after it.
[LifecycleModule(ModulePhase.Core, DependsOn = new[] { typeof(GameSettings) })]
public sealed class RemoteConfigModule : AsyncModuleBase
{
    public override async Task InitializeAsync(CancellationToken cancellationToken)
    {
        var settings = await ModuleRegistry.WhenReadyAsync<GameSettings>(cancellationToken);
        // ... load remote config for settings.GameName ...
        await Task.Delay(500, cancellationToken);
    }
}
```

{% endtab %}
{% endtabs %}

That is all — the module is discovered and run automatically in the correct order. No manual registration needed.

#### 3. Consume a module from anywhere

```csharp
// Fire a callback the moment the module is ready (runs immediately if already ready).
ModuleRegistry.WhenReady<GameSettings>(settings =>
{
    Debug.Log($"Using {settings.GameName}");
});

// Or await it.
var settings = await ModuleRegistry.WhenReadyAsync<GameSettings>();
```

***

### Learn More

* [Defining and Running Modules](/aceland-unity-packages/core-packages/lifecycle/defining-and-running-modules.md) — phases, dependencies, async, parallelism and querying.
* [The Quit Pipeline](/aceland-unity-packages/core-packages/lifecycle/quit-pipeline.md) — quit handlers, blockers and the lifecycle tokens.
* [Frame Scheduling](/aceland-unity-packages/core-packages/lifecycle/frame-scheduling.md) — run work on the player loop without a MonoBehaviour.
* [Player Loop](/aceland-unity-packages/core-packages/lifecycle/player-loop.md) — the eight injection points and how ticks are installed into Unity's loop.
* [Cancellation](/aceland-unity-packages/core-packages/lifecycle/cancellation.md) — the lifecycle tokens, linked sources and frame-scoped waits.
* [Editor Tools and Profiling](/aceland-unity-packages/core-packages/lifecycle/editor-tools-and-profiling.md) — the Initialization Graph, Initialization Timeline and export **(paid)**.

***

### Best Practices

* Keep `Initialize()` lightweight — register services and set fields; push heavy work into `InitializeAsync`.
* Make `Shutdown()` re-entrant; it may be called more than once and must never throw on a second call.
* Depend on the earliest phase that still satisfies your needs — do not push everything into `Late`.
* Never let a synchronous module depend on an async module (the Validator will flag it); make the dependent async too.
* Mark every module-bearing assembly with `[assembly: LifecycleAssembly]`.
* Use `WhenReady<T>` / `WhenReadyAsync<T>` for late subscribers instead of the one-shot events.

***
