< Summary

Information
Class: Trailblazer.TrailblazerWorldContext
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Runtime/TrailblazerWorldContext.cs
Line coverage
100%
Covered lines: 114
Uncovered lines: 0
Coverable lines: 114
Total lines: 372
Line coverage: 100%
Branch coverage
100%
Covered branches: 14
Total branches: 14
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
get_IsDisposed()100%11100%
get_FrameRate()100%11100%
get_DeltaTime()100%11100%
get_InvDeltaTime()100%11100%
get_FrameCount()100%11100%
get_TotalTime()100%11100%
get_AccumulatedTime()100%11100%
get_ResetAccumulation()100%11100%
get_ExpectedAccumulation()100%11100%
Attach(...)100%22100%
CreateOwned(...)100%22100%
Simulate()100%11100%
LateSimulate()100%11100%
Visualize()100%11100%
Reset()100%11100%
SetFrameRate(...)100%11100%
GetFrameFromTime(...)100%11100%
RegisterOnSimulate(...)100%11100%
RegisterOnLateSimulate(...)100%11100%
RegisterOnVisualize(...)100%11100%
RegisterOnReset(...)100%11100%
RegisterOnFrameRateChanged(...)100%11100%
Dispose()100%66100%
CreateRegistered(...)100%11100%
ThrowIfWorldOwned(...)100%11100%
ReleaseWorldOwnership(...)100%11100%
ThrowIfUnusable(...)100%44100%
ThrowIfDisposed()100%11100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Runtime/TrailblazerWorldContext.cs

#LineLine coverage
 1//=======================================================================
 2// TrailblazerWorldContext.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using FixedMathSharp;
 10using GridForge.Grids;
 11using SwiftCollections;
 12using Trailblazer.Heightmaps;
 13using Trailblazer.Navigation;
 14using Trailblazer.Pathing;
 15
 16namespace Trailblazer;
 17
 18/// <summary>
 19/// Owns Trailblazer runtime state for one explicit <see cref="GridWorld"/>.
 20/// </summary>
 21/// <remarks>
 22/// This is the context-first host API for multi-world Trailblazer usage. It owns world lifetime,
 23/// deterministic clock state, pathing state, reachability snapshots, and guide caches.
 24/// </remarks>
 25public sealed class TrailblazerWorldContext : IDisposable
 26{
 127    private static readonly object _worldOwnershipLock = new();
 28
 129    private static readonly SwiftHashSet<GridWorld> _ownedWorlds = new();
 30
 98931    private readonly TrailblazerClock _clock = new();
 32
 98933    private readonly TrailblazerLifecycleHooks _hooks = new();
 34
 35    private readonly bool _ownsWorld;
 36
 37    private bool _disposed;
 38
 98939    private TrailblazerWorldContext(
 98940        GridWorld world,
 98941        bool ownsWorld,
 98942        TrailblazerWorldContextSettings settings)
 43    {
 98944        World = world;
 98945        Settings = settings;
 98946        _ownsWorld = ownsWorld;
 98947        Pathing = new TrailblazerPathingService(this);
 98948        Guides = new TrailblazerGuideService(this);
 98949        Navigation = new TrailblazerNavigationService(this);
 98950        Heightmaps = new TrailblazerHeightmapService(this);
 98951    }
 52
 53    /// <summary>
 54    /// Gets the explicit GridForge world owned or referenced by this context.
 55    /// </summary>
 56    public GridWorld World { get; }
 57
 58    /// <summary>Gets the immutable finite runtime ceilings for this context.</summary>
 59    public TrailblazerWorldContextSettings Settings { get; }
 60
 61    /// <summary>
 62    /// Gets this context's world-local pathing service.
 63    /// </summary>
 64    public TrailblazerPathingService Pathing { get; }
 65
 66    /// <summary>
 67    /// Gets this context's world-local path guide service.
 68    /// </summary>
 69    public TrailblazerGuideService Guides { get; }
 70
 71    internal TrailblazerNavigationService Navigation { get; }
 72
 73    /// <summary>
 74    /// Gets this context's world-local heightmap registry and sampling service.
 75    /// </summary>
 76    public TrailblazerHeightmapService Heightmaps { get; }
 77
 78    /// <summary>
 79    /// Gets whether this context has been disposed.
 80    /// </summary>
 2342181    public bool IsDisposed => _disposed;
 82
 83    /// <summary>
 84    /// Gets this context's fixed simulation frame rate.
 85    /// </summary>
 86    public int FrameRate
 87    {
 88        get
 89        {
 10190            ThrowIfDisposed();
 10091            return _clock.FrameRate;
 92        }
 93    }
 94
 95    /// <summary>
 96    /// Gets this context's fixed simulation time step.
 97    /// </summary>
 98    public Fixed64 DeltaTime
 99    {
 100        get
 101        {
 6411102            ThrowIfDisposed();
 6411103            return _clock.DeltaTime;
 104        }
 105    }
 106
 107    /// <summary>
 108    /// Gets the reciprocal of this context's fixed simulation time step.
 109    /// </summary>
 110    public Fixed64 InvDeltaTime
 111    {
 112        get
 113        {
 4669114            ThrowIfDisposed();
 4669115            return _clock.InvDeltaTime;
 116        }
 117    }
 118
 119    /// <summary>
 120    /// Gets this context's simulated frame count.
 121    /// </summary>
 122    public int FrameCount
 123    {
 124        get
 125        {
 6010126            ThrowIfDisposed();
 6010127            return _clock.FrameCount;
 128        }
 129    }
 130
 131    /// <summary>
 132    /// Gets this context's total simulated time.
 133    /// </summary>
 134    public Fixed64 TotalTime
 135    {
 136        get
 137        {
 56138            ThrowIfDisposed();
 56139            return _clock.TotalTime;
 140        }
 141    }
 142
 143    /// <summary>
 144    /// Gets this context's accumulated visualization time.
 145    /// </summary>
 146    public Fixed64 AccumulatedTime
 147    {
 148        get
 149        {
 3150            ThrowIfDisposed();
 3151            return _clock.AccumulatedTime;
 152        }
 153    }
 154
 155    /// <summary>
 156    /// Gets whether this context's visualization accumulation will reset on the next visualize call.
 157    /// </summary>
 158    public bool ResetAccumulation
 159    {
 160        get
 161        {
 4162            ThrowIfDisposed();
 4163            return _clock.ResetAccumulation;
 164        }
 165    }
 166
 167    /// <summary>
 168    /// Gets this context's visualization accumulation expressed in simulation frames.
 169    /// </summary>
 170    public Fixed64 ExpectedAccumulation
 171    {
 172        get
 173        {
 3174            ThrowIfDisposed();
 3175            return _clock.ExpectedAccumulation;
 176        }
 177    }
 178
 179    /// <summary>
 180    /// Attaches a context to a host-owned <see cref="GridWorld"/>.
 181    /// </summary>
 182    /// <param name="world">The active world to bind.</param>
 183    /// <param name="takeOwnership">True when disposing this context should dispose the supplied world.</param>
 184    /// <param name="settings">Optional immutable context ceilings; defaults to the recommended finite settings.</param>
 185    /// <returns>A context bound to <paramref name="world"/>.</returns>
 186    public static TrailblazerWorldContext Attach(
 187        GridWorld world,
 188        bool takeOwnership = false,
 189        TrailblazerWorldContextSettings? settings = null)
 190    {
 41191        SwiftThrowHelper.ThrowIfNull(world, nameof(world));
 40192        SwiftThrowHelper.ThrowIfTrue(
 40193            !world.IsActive,
 40194            nameof(TrailblazerWorldContext),
 40195            "TrailblazerWorldContext requires an active GridWorld.");
 196
 39197        return CreateRegistered(world, takeOwnership, settings ?? TrailblazerWorldContextSettings.Default);
 198    }
 199
 200    /// <summary>
 201    /// Creates a context with an owned <see cref="GridWorld"/>.
 202    /// </summary>
 203    /// <param name="spatialGridCellSize">Spatial hash cell size for the created world.</param>
 204    /// <param name="settings">Optional immutable context ceilings; defaults to the recommended finite settings.</param>
 205    /// <returns>A context that owns its created world.</returns>
 206    public static TrailblazerWorldContext CreateOwned(
 207        int spatialGridCellSize = GridWorld.DefaultSpatialGridCellSize,
 208        TrailblazerWorldContextSettings? settings = null)
 209    {
 951210        return CreateRegistered(
 951211            new GridWorld(spatialGridCellSize),
 951212            ownsWorld: true,
 951213            settings ?? TrailblazerWorldContextSettings.Default);
 214    }
 215
 216    /// <summary>
 217    /// Advances this context's deterministic simulation clock and ordered simulate hooks.
 218    /// </summary>
 219    public void Simulate()
 220    {
 12186221        ThrowIfDisposed();
 12185222        _clock.Simulate();
 12185223        Pathing.MaintainNavigationGraph(_clock.FrameCount);
 12185224        _hooks.InvokeSimulate();
 12185225    }
 226
 227    /// <summary>
 228    /// Runs this context's late-simulation step.
 229    /// </summary>
 230    public void LateSimulate()
 231    {
 2232        ThrowIfDisposed();
 2233        _clock.LateSimulate();
 2234        _hooks.InvokeLateSimulate();
 2235    }
 236
 237    /// <summary>
 238    /// Runs this context's visualization accumulation step.
 239    /// </summary>
 240    public void Visualize()
 241    {
 5242        ThrowIfDisposed();
 5243        _clock.Visualize();
 5244        _hooks.InvokeVisualize();
 5245    }
 246
 247    /// <summary>
 248    /// Resets this context's deterministic clock and context-local lifecycle hooks.
 249    /// </summary>
 250    public void Reset()
 251    {
 22252        ThrowIfDisposed();
 22253        _clock.Reset();
 22254        Navigation.Reset();
 22255        Heightmaps.Reset();
 22256        Pathing.Reset();
 22257        _hooks.InvokeReset();
 22258    }
 259
 260    /// <summary>
 261    /// Updates this context's fixed simulation frame rate.
 262    /// </summary>
 263    /// <param name="frameRate">The new frame rate. Must be greater than zero.</param>
 264    public void SetFrameRate(int frameRate)
 265    {
 11266        ThrowIfDisposed();
 11267        _clock.SetFrameRate(frameRate);
 9268        _hooks.InvokeFrameRateChanged();
 9269    }
 270
 271    /// <summary>
 272    /// Calculates the frame index containing the specified fixed-point timestamp.
 273    /// </summary>
 274    /// <param name="timestamp">The timestamp to resolve.</param>
 275    /// <returns>The zero-based frame index for the timestamp.</returns>
 276    public int GetFrameFromTime(Fixed64 timestamp)
 277    {
 1278        ThrowIfDisposed();
 1279        return _clock.GetFrameFromTime(timestamp);
 280    }
 281
 282    internal IDisposable RegisterOnSimulate(string owner, int order, Action callback)
 283    {
 10284        ThrowIfDisposed();
 10285        return _hooks.RegisterOnSimulate(owner, order, callback);
 286    }
 287
 288    internal IDisposable RegisterOnLateSimulate(string owner, int order, Action callback)
 289    {
 1290        ThrowIfDisposed();
 1291        return _hooks.RegisterOnLateSimulate(owner, order, callback);
 292    }
 293
 294    internal IDisposable RegisterOnVisualize(string owner, int order, Action callback)
 295    {
 1296        ThrowIfDisposed();
 1297        return _hooks.RegisterOnVisualize(owner, order, callback);
 298    }
 299
 300    internal IDisposable RegisterOnReset(string owner, int order, Action callback)
 301    {
 2302        ThrowIfDisposed();
 2303        return _hooks.RegisterOnReset(owner, order, callback);
 304    }
 305
 306    internal IDisposable RegisterOnFrameRateChanged(string owner, int order, Action callback)
 307    {
 1308        ThrowIfDisposed();
 1309        return _hooks.RegisterOnFrameRateChanged(owner, order, callback);
 310    }
 311
 312    /// <inheritdoc/>
 313    public void Dispose()
 314    {
 992315        lock (_worldOwnershipLock)
 316        {
 992317            if (_disposed)
 4318                return;
 319
 988320            Pathing.Dispose();
 988321            Heightmaps.Dispose();
 988322            _disposed = true;
 988323            ReleaseWorldOwnership(this);
 324
 988325            if (_ownsWorld && World.IsActive)
 977326                World.Dispose();
 988327        }
 992328    }
 329
 330    private static TrailblazerWorldContext CreateRegistered(
 331        GridWorld world,
 332        bool ownsWorld,
 333        TrailblazerWorldContextSettings settings)
 334    {
 990335        lock (_worldOwnershipLock)
 336        {
 990337            ThrowIfWorldOwned(world);
 989338            TrailblazerWorldContext context = new(world, ownsWorld, settings);
 989339            _ownedWorlds.Add(world);
 989340            return context;
 341        }
 989342    }
 343
 344    private static void ThrowIfWorldOwned(GridWorld world)
 345    {
 990346        SwiftThrowHelper.ThrowIfTrue(
 990347            _ownedWorlds.Contains(world),
 990348            message: "GridWorld is already attached to an active TrailblazerWorldContext.");
 989349    }
 350
 351    private static void ReleaseWorldOwnership(TrailblazerWorldContext context) =>
 988352        _ownedWorlds.Remove(context.World);
 353
 354    internal static void ThrowIfUnusable(TrailblazerWorldContext? context)
 355    {
 16861356        if (context == null)
 1357            throw new ArgumentNullException(nameof(context), "An explicit TrailblazerWorldContext is required.");
 16860358        if (context.IsDisposed)
 1359            throw new ObjectDisposedException(nameof(TrailblazerWorldContext));
 16859360        SwiftThrowHelper.ThrowIfTrue(
 16859361            !context.World.IsActive,
 16859362            message: "An active TrailblazerWorldContext is required.");
 16858363    }
 364
 365    private void ThrowIfDisposed()
 366    {
 29499367        SwiftThrowHelper.ThrowIfDisposed(_disposed, nameof(TrailblazerWorldContext));
 29499368        SwiftThrowHelper.ThrowIfTrue(
 29499369            !World.IsActive,
 29499370            message: "TrailblazerWorldContext is bound to an inactive GridWorld.");
 29497371    }
 372}