public sealed class RunicNavigator : IAsyncDisposable, IDisposable Owns the navigation regions and entries of one window. Register it per window scope with RunicNavigationServiceCollectionExtensions.AddRunicNavigation.
Remarks
Each region admits one transition at a time: a later request supersedes earlier ones that have not started committing. Guards, initialize and resume run outside model turns; a commit re-checks the region and applies the new state in one turn. Disposing the navigator cancels in-flight transitions and retires every entry; owned content is disposed outside model turns.
When the model context implements IRunicModelHookScheduler, factories, guards, initialize and resume hooks and the disposal of owned content each run as a separate operation on the model's thread, never inline in the caller's frame or in a commit turn. Otherwise they run outside turns, on the caller's thread until its first await or on the thread pool. The navigator's own work between hooks always continues on the thread pool.
Constructors
RunicNavigator
public RunicNavigator(RunicNavigatorOptions options) Creates a navigator for one window.
Fields
DiagnosticId
public const string DiagnosticId = "RUNICNAV001" The diagnostic ID of the experimental navigation API.
LogCategory
public const string LogCategory = "Runic.Navigation" The category of the navigator's log entries (events 1060-1079).
Properties
IsClosed
public bool IsClosed { get; } Gets whether RunicNavigator.DisposeAsync has started or the model context closed (see IRunicModelContextLifetime). A closed navigator rejects requests as NavigationRejection.Closed, throws ObjectDisposedException from RunicNavigator.CreateRegion and ignores RunicNavigator.AttachPresentation.
ModelContext
public IRunicModelContext ModelContext { get; } Gets the model context whose turns commit this navigator's state. Owned content is bound to it.
Services
public IServiceProvider? Services { get; } Gets the service provider passed to target factories, or null when none was configured.
UnretiredEntryCount
public int UnretiredEntryCount { get; } Gets the entries that have not finished retiring: pending, committed, and removed entries whose cleanup is still running. It is zero after RunicNavigator.DisposeAsync.
Methods
AttachPresentation
public IDisposable AttachPresentation(INavigationPresentation presentation) Attaches a presentation that shows this navigator's content. Presentation integrations, such as the Runic Views runtime, call it; applications do not.
Parameters
presentationThe presentation to attach.
Returns
An IDisposable that detaches the presentation.
Remarks
When owned content retires, after its child regions are closed and before the content is disposed, the navigator calls INavigationPresentation.Forget on each attached presentation, outside model turns. A presentation that throws is logged (event 1064, step Forget) and does not skip the others. A presentation attached after the navigator started closing (RunicNavigator.IsClosed) is ignored, and the returned IDisposable does nothing.
CreateRegion
public NavigationRegion<TContent> CreateRegion<TContent>(object owner, INavigationTarget<TContent>? initial = null, NavigationRegionOptions? options = null) where TContent : class Creates a region owned by owner. A region owned by the content of an owned entry is that entry's child and closes when it retires; other regions close when the navigator is disposed.
Parameters
ownerThe object that owns the region, such as the ViewModel exposing it.
initialContent committed synchronously as the first entry. It must implement neither
INavigationInitializenorINavigationInitialize.optionsThe region's child policy.
Dispose
public void Dispose() Starts RunicNavigator.DisposeAsync and returns without waiting for it, so a synchronous container disposal on the model's thread can't deadlock against a commit that needs that thread. Prefer RunicNavigator.DisposeAsync: after this method returns, owned content may still be disposing in the background.
Remarks
It is idempotent and shares one disposal with RunicNavigator.DisposeAsync. Failures are logged (1073).
DisposeAsync
public ValueTask DisposeAsync() Refuses new requests and ends every open NavigationRegion.PushForResult request as Dismissed (or Completed when its return already committed), so guards awaiting a result unblock. Then cancels in-flight transitions and waits for them up to the close timeout, and closes and retires every entry region by region in creation order. Does not run guards.
Remarks
Each region is cleared in a model turn so the change is serialized with its commit. The wait for those turns is bounded by RunicNavigatorOptions.CloseTimeout for the whole disposal: when a turn stays blocked, that region is cleared outside a turn after the timeout and a warning (1068) is logged, the notifications it owes are posted, and the remaining regions are cleared without waiting. Disposal always completes; content disposal itself is not bounded. Retiring an entry whose initialize hook is running waits for the hook, within the same close timeout, before it disposes the content; once the wait for cancelled transitions timed out the hook is not awaited again and a warning (1069) is logged. An initialize hook never starts on content that retirement already claimed. The worst case of a disposal is about twice RunicNavigatorOptions.CloseTimeout (transition wait plus clearing turns), plus content disposal.
With a model context that implements IRunicModelHookScheduler, owned content is disposed as scheduled operations on the model's thread. Those that haven't started by the clearing turns' deadline run on the thread pool instead, so disposal still completes when the model's thread is blocked. Never block the model's thread on this task (for example with GetAwaiter().GetResult() in a WPF OnExit): content would then be disposed off that thread after the timeout. Call RunicNavigator.Dispose there, which doesn't wait.
WhenIdleAsync
public ValueTask WhenIdleAsync(CancellationToken cancellationToken = default) Completes when no transition is in flight and no retirement is running.