< Summary

Information
Class: Trailblazer.Pathing.PathGuideFactory
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/Guide/PathGuideFactory.cs
Line coverage
95%
Covered lines: 315
Uncovered lines: 15
Coverable lines: 330
Total lines: 729
Line coverage: 95.4%
Branch coverage
90%
Covered branches: 176
Total branches: 194
Branch coverage: 90.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_GuideState()100%11100%
get__aStarSurveyor()100%11100%
get__flowFieldSurveyor()100%11100%
get__volumeSurveyor()100%11100%
get__cachedAStarResults()100%11100%
get_TotalAStarGuideCount()100%11100%
get_InUseAStarGuideCount()100%11100%
get__cachedFlowResults()100%11100%
get_TotalFlowGuideCount()100%11100%
get_InUseFlowGuideCount()100%11100%
get__cachedVolumeResults()100%11100%
get_TotalVolumeGuideCount()100%11100%
get_InUseVolumeGuideCount()100%11100%
get__cachedHybridRoutePlans()100%11100%
get__aStarGuides()100%11100%
get__flowFieldGuides()100%11100%
get__volumeGuides()100%11100%
get_TotalHybridRoutePlanCount()100%11100%
get_InUseHybridRoutePlanCount()100%11100%
get_IsPooling()100%66100%
get_AnyInUse()100%66100%
CullExpiredGuides(...)100%22100%
RequestGuide(...)100%44100%
RequestGuide(...)95%2020100%
RequestAStar(...)100%44100%
RequestAStarMiss(...)100%22100%
RequestFlowField(...)90%101083.33%
RequestFlowFieldMiss(...)100%1414100%
RequestVolume(...)100%22100%
RequestVolumeMiss(...)100%22100%
RequestHybrid(...)50%6688.88%
ReturnGuide(...)91.66%1212100%
ThrowIfGuideOwnedByDifferentContext(...)91.66%1212100%
InvalidateCacheFor(...)100%22100%
InvalidateVolumeCache()100%11100%
FlushCache(...)100%44100%
TrySeedAStarCacheForBenchmark(...)100%11100%
TrySeedFlowFieldCacheForBenchmark(...)100%11100%
TrySeedVolumeCacheForBenchmark(...)100%11100%
TrySeedHybridRoutePlanCacheForBenchmark(...)100%11100%
CountIndexedCacheEntriesForBenchmark(...)100%11100%
CreateSeedWaypoints()100%11100%
RentAStarGuide(...)50%3250%
RentFlowFieldGuide(...)50%3250%
TryRentFlowFieldGuide(...)66.66%66100%
RentVolumeGuide(...)50%3250%
ReturnAStarGuide(...)100%44100%
ReturnFlowFieldGuide(...)100%44100%
ReturnVolumeGuide(...)100%22100%
ClearGuidePools()100%11100%
ResolveAStarResult(...)100%66100%
TryBuildTransitionFallbackAStarResult(...)87.5%8892.3%
TryBuildTransitionFallbackFlowGuide(...)75%4477.77%
TryGetTransitionFallbackFlowPlan(...)100%44100%
TryGetCachedTransitionFallbackFlowPlan(...)100%22100%
ResolveTransitionFallbackFlowPlan(...)100%66100%
CollectRoutePlanChartKeys(...)100%1414100%
AddChartKeys(...)75%88100%
AddRequestEndpointChartOwners(...)100%22100%
AddVoxelChartOwners(...)80%1010100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/Guide/PathGuideFactory.cs

#LineLine coverage
 1using FixedMathSharp;
 2using GridForge.Grids;
 3using GridForge.Spatial;
 4using SwiftCollections;
 5using System;
 6using System.Diagnostics.CodeAnalysis;
 7
 8namespace Trailblazer.Pathing;
 9
 10/// <summary>
 11/// Provides context-scoped access to pooled and reusable IGuide instances for the built-in pathing strategies.
 12/// Handles guide request routing, instantiation, and lifecycle management.
 13/// </summary>
 14internal static class PathGuideFactory
 15{
 16    /// <summary>
 17    /// The number of frames after which unused guides are considered stale and eligible for eviction from the pool.
 18    /// This helps prevent memory bloat from guides that are rarely used but still occupy cache space.
 19    /// Adjust this value based on typical pathfinding usage patterns and acceptable memory overhead in your application
 20    /// </summary>
 21    private const int MaxFramesUnused = 600;
 22
 23    /// <summary>
 24    /// A shared cache for A* survey results, keyed by request parameters.
 25    /// This allows for efficient reuse of recently computed paths without needing to re-run the A* algorithm for identi
 26    /// </summary>
 2858727    private static TrailblazerGuideState GuideState => PathManager.ActiveState.GuideState;
 28
 7229    private static AStarSurveyor _aStarSurveyor => GuideState.AStarSurveyor;
 30
 3031    private static FlowFieldSurveyor _flowFieldSurveyor => GuideState.FlowFieldSurveyor;
 32
 2933    private static VolumeSurveyor _volumeSurveyor => GuideState.VolumeSurveyor;
 34
 35    private static ReusableSurveyResultCache<AStarSurveyResult> _cachedAStarResults =>
 649336        GuideState.CachedAStarResults;
 37
 38    /// <summary>
 39    /// Returns the number of active (pooled or in-use) A* results currently tracked.
 40    /// </summary>
 396541    public static int TotalAStarGuideCount => _cachedAStarResults.Count;
 42
 43    /// <summary>
 44    /// Returns only the number of active (in-use) A* results currently tracked.
 45    /// </summary>
 1546    public static int InUseAStarGuideCount => _cachedAStarResults.CountInUse;
 47
 48    /// <summary>
 49    /// A shared cache for FlowField survey results, keyed by request parameters.
 50    /// This allows for efficient reuse of recently computed flow fields without needing to re-run the flow field genera
 51    /// </summary>
 52    private static ReusableSurveyResultCache<FlowFieldSurveyResult> _cachedFlowResults =>
 629953        GuideState.CachedFlowResults;
 54
 55    /// <summary>
 56    /// Returns the number of active (pooled or in-use) FlowField guides currently tracked.
 57    /// </summary>
 390158    public static int TotalFlowGuideCount => _cachedFlowResults.Count;
 59
 60    /// <summary>
 61    /// Returns only the number of active (in-use) FlowField guides currently tracked.
 62    /// </summary>
 663    public static int InUseFlowGuideCount => _cachedFlowResults.CountInUse;
 64
 65    /// <summary>
 66    /// A shared cache for raw-volume survey results, keyed by request parameters.
 67    /// This allows for efficient reuse of recently computed raw-volume data without needing to re-run the volume genera
 68    /// </summary>
 69    private static ReusableSurveyResultCache<VolumeSurveyResult> _cachedVolumeResults =>
 755270        GuideState.CachedVolumeResults;
 71
 72    /// <summary>
 73    /// Returns the number of active raw-volume guides currently tracked.
 74    /// </summary>
 390575    public static int TotalVolumeGuideCount => _cachedVolumeResults.Count;
 76
 77    /// <summary>
 78    /// Returns only the number of active (in-use) raw-volume guides currently tracked.
 79    /// </summary>
 380    public static int InUseVolumeGuideCount => _cachedVolumeResults.CountInUse;
 81
 82    private static ReusableSurveyResultCache<HybridRoutePlanSurveyResult> _cachedHybridRoutePlans =>
 520683        GuideState.CachedHybridRoutePlans;
 84
 118585    private static GuidePool<AStarGuide> _aStarGuides => GuideState.AStarGuides;
 86
 111287    private static GuidePool<FlowFieldGuide> _flowFieldGuides => GuideState.FlowFieldGuides;
 88
 60989    private static GuidePool<VolumeGuide> _volumeGuides => GuideState.VolumeGuides;
 90
 91    /// <summary>
 92    /// Returns the number of cached transition route plans currently tracked.
 93    /// </summary>
 388794    public static int TotalHybridRoutePlanCount => _cachedHybridRoutePlans.Count;
 95
 96    /// <summary>
 97    /// Returns only the number of active (in-use) hybrid route plans currently tracked.
 98    /// </summary>
 399    public static int InUseHybridRoutePlanCount => _cachedHybridRoutePlans.CountInUse;
 100
 101    /// <summary>
 102    /// Indicates whether any pathing guides are currently pooled and available.
 103    /// </summary>
 104    public static bool IsPooling =>
 3935105        TotalAStarGuideCount > 0
 3935106        || TotalFlowGuideCount > 0
 3935107        || TotalVolumeGuideCount > 0
 3935108        || TotalHybridRoutePlanCount > 0;
 109
 110    /// <summary>
 111    /// Indicates whether any guides are currently in use (checked out from the pool and not yet returned).
 112    /// </summary>
 113    public static bool AnyInUse =>
 6114        InUseAStarGuideCount > 0
 6115        || InUseFlowGuideCount > 0
 6116        || InUseVolumeGuideCount > 0
 6117        || InUseHybridRoutePlanCount > 0;
 118
 119    /// <summary>
 120    /// Attempts to remove guides from the pool that haven't been used for a configured number of frames.
 121    /// </summary>
 122    /// <param name="currentFrame">The current frame index used to check guide staleness.</param>
 123    public static void CullExpiredGuides(int currentFrame)
 124    {
 4586125        if (!IsPooling) return;
 126
 14127        _cachedAStarResults.EvictStaleEntries(currentFrame, MaxFramesUnused);
 14128        _cachedFlowResults.EvictStaleEntries(currentFrame, MaxFramesUnused);
 14129        _cachedVolumeResults.EvictStaleEntries(currentFrame, MaxFramesUnused);
 14130        _cachedHybridRoutePlans.EvictStaleEntries(currentFrame, MaxFramesUnused);
 14131    }
 132
 133    /// <summary>
 134    /// Requests a guide of a specific type using an already populated path request.
 135    /// </summary>
 136    /// <typeparam name="T">The concrete guide type to return.</typeparam>
 137    /// <param name="request">The path request with validated parameters.</param>
 138    /// <param name="result">The resolved guide or null if the request was invalid.</param>
 139    /// <returns><c>true</c> if the guide was properly configured, otherwise <c>false</c>.</returns>
 140    public static bool RequestGuide<T>(IPathRequest request, [NotNullWhen(true)] out T? result) where T : class, IGuide
 141    {
 1378142        result = default;
 1378143        if (!RequestGuide(request, out IGuide? guide))
 16144            return false;
 145
 1362146        if (guide is not T typedGuide)
 147        {
 1148            ReturnGuide(guide);
 1149            return false;
 150        }
 151
 1361152        result = typedGuide;
 1361153        return true;
 154    }
 155
 156    /// <summary>
 157    /// Routes the path request to the appropriate guide implementation based on type.
 158    /// </summary>
 159    /// <param name="request">The polymorphic request to resolve.</param>
 160    /// <param name="result">The resolved guide or null if the request was invalid.</param>
 161    /// <returns><c>true</c> if the guide was properly configured, otherwise <c>false</c>.</returns>
 162    public static bool RequestGuide(IPathRequest request, [NotNullWhen(true)] out IGuide? result)
 163    {
 1447164        if (request?.IsValid != true)
 165        {
 2166            TrailblazerLogger.Channel.Warn($"Request is invalid. Create or update the request before requesting a guide.
 2167            result = null;
 2168            return false;
 169        }
 170
 1445171        if (!ReferenceEquals(request.Context, PathManager.ActiveState.Context))
 172        {
 1173            result = null;
 1174            return false;
 175        }
 176
 1444177        if (request is AStarPathRequest unreachableAStar
 1444178            && SolidPartitionReachability.IsProvablyUnreachable(unreachableAStar))
 179        {
 11180            result = null;
 11181            return false;
 182        }
 183
 1433184        result = request switch
 1433185        {
 590186            AStarPathRequest a => RequestAStar(a),
 546187            FlowFieldPathRequest f => RequestFlowField(f),
 291188            VolumePathRequest v => RequestVolume(v),
 3189            HybridPathRequest h => RequestHybrid(h),
 3190            _ => null,
 1433191        };
 1433192        return result != null;
 193    }
 194
 195    /// <summary>
 196    /// Retrieves an A* guide from the pool or creates a new one based on the provided request.
 197    /// </summary>
 198    /// <param name="request">The configured A* pathfinding request.</param>
 199    /// <returns>A valid AStarGuide instance.</returns>
 200    public static AStarGuide? RequestAStar(AStarPathRequest request)
 201    {
 591202        if (SolidPartitionReachability.IsProvablyUnreachable(request))
 1203            return null;
 204
 590205        if (_cachedAStarResults.TryCheckout(request, out AStarSurveyResult cachedResult))
 518206            return RentAStarGuide(cachedResult);
 207
 72208        return RequestAStarMiss(request);
 209    }
 210
 211    private static AStarGuide? RequestAStarMiss(AStarPathRequest request)
 212    {
 72213        bool pathFound = _cachedAStarResults.TryGetOrCreate(request,
 72214            () => ResolveAStarResult(request),
 72215            out AStarSurveyResult result);
 216
 72217        if (!pathFound)
 4218            return null;
 219
 68220        return RentAStarGuide(result);
 221    }
 222
 223    /// <summary>
 224    /// Retrieves a FlowField guide from the pool or creates a new one based on the provided request.
 225    /// </summary>
 226    /// <param name="request">The configured FlowField pathfinding request.</param>
 227    /// <returns>A valid FlowFieldGuide instance.</returns>
 228    public static FlowFieldGuide? RequestFlowField(FlowFieldPathRequest request)
 229    {
 546230        if (request.AllowTraversalTransitions
 546231            && TryGetCachedTransitionFallbackFlowPlan(request, out HybridRoutePlan? cachedRoutePlan))
 232        {
 1233            FlowFieldGuide cachedGuide = _flowFieldGuides.Rent();
 1234            if (cachedGuide.InitializeStaged(cachedRoutePlan))
 1235                return cachedGuide;
 236
 0237            ReturnFlowFieldGuide(cachedGuide, dispose: false);
 0238            return null;
 239        }
 240
 545241        if (_cachedFlowResults.TryCheckout(request, out FlowFieldSurveyResult cachedResult))
 242        {
 515243            if (TryRentFlowFieldGuide(request, cachedResult, out FlowFieldGuide? cachedGuide))
 514244                return cachedGuide;
 245
 1246            _cachedFlowResults.Return(cachedResult, dispose: false);
 247        }
 248
 31249        return RequestFlowFieldMiss(request);
 250    }
 251
 252    private static FlowFieldGuide? RequestFlowFieldMiss(FlowFieldPathRequest request)
 253    {
 31254        bool pathFound = _cachedFlowResults.TryGetOrCreate(request,
 31255            () => _flowFieldSurveyor.FindPath(request),
 31256            out FlowFieldSurveyResult result);
 257
 258        // Make sure the start voxel is within the current fields collection. This dictionary probe is on the warm
 259        // cache-hit path, so GridForge's WorldVoxelIndex hash must stay allocation-free to keep FlowField hits near A*.
 31260        if (pathFound
 31261            && result.Fields != null
 31262            && request.StartNode != null
 31263            && result.Fields.ContainsKey(request.StartNode.WorldIndex))
 264        {
 22265            return RentFlowFieldGuide(result);
 266        }
 267
 9268        if (pathFound)
 1269            _cachedFlowResults.Return(result, dispose: false);
 270
 9271        if (!request.AllowTraversalTransitions)
 2272            return null;
 273
 7274        return TryBuildTransitionFallbackFlowGuide(request, out FlowFieldGuide? fallbackGuide)
 7275            ? fallbackGuide
 7276            : null;
 277    }
 278
 279    /// <summary>
 280    /// Retrieves a raw-volume guide from the pool or creates a new one based on the provided request.
 281    /// </summary>
 282    public static VolumeGuide? RequestVolume(VolumePathRequest request)
 283    {
 291284        if (_cachedVolumeResults.TryCheckout(request, out VolumeSurveyResult cachedResult))
 262285            return RentVolumeGuide(cachedResult);
 286
 29287        return RequestVolumeMiss(request);
 288    }
 289
 290    private static VolumeGuide? RequestVolumeMiss(VolumePathRequest request)
 291    {
 29292        bool pathFound = _cachedVolumeResults.TryGetOrCreate(request,
 29293            () => _volumeSurveyor.FindPath(request),
 29294            out VolumeSurveyResult result);
 295
 29296        if (!pathFound)
 1297            return null;
 298
 28299        return RentVolumeGuide(result);
 300    }
 301
 302    /// <summary>
 303    /// Builds a hybrid guide by composing cached chart and volume segment guides from a planned route request.
 304    /// </summary>
 305    private static HybridGuide? RequestHybrid(HybridPathRequest request)
 306    {
 3307        HybridRoutePlan? routePlan = request.RoutePlan;
 3308        if (routePlan == null
 3309            || !HybridWaypointFlattener.TryBuild(
 3310            routePlan,
 3311            out AStarWaypoint[]? flattened,
 3312            out _))
 313        {
 0314            return null;
 315        }
 316
 3317        HybridGuide guide = new();
 3318        return guide.Initialize(flattened!) ? guide : null;
 319    }
 320
 321    /// <summary>
 322    /// Returns the guide back to its associated pool, optionally disposing it completely.
 323    /// </summary>
 324    /// <param name="guide">The guide to return to the cache.</param>
 325    /// <param name="dispose">Whether to destroy the guide instead of pooling it.</param>
 326    public static void ReturnGuide(IGuide? guide, bool dispose = false)
 327    {
 1380328        if (guide == null) return;
 329
 1370330        ThrowIfGuideOwnedByDifferentContext(guide, PathManager.ActiveState.Context);
 331
 332        switch (guide)
 333        {
 334            case AStarGuide a:
 559335                _cachedAStarResults.Return(a.TrailMap, dispose);
 559336                ReturnAStarGuide(a, dispose);
 559337                break;
 338            case FlowFieldGuide f:
 529339                f.ReleaseStagedResources(dispose);
 529340                if (f.FlowMap != null)
 526341                    _cachedFlowResults.Return(f.FlowMap, dispose);
 529342                ReturnFlowFieldGuide(f, dispose);
 529343                break;
 344            case VolumeGuide v:
 279345                if (v.TrailMap != null)
 279346                    _cachedVolumeResults.Return(v.TrailMap, dispose);
 279347                ReturnVolumeGuide(v, dispose);
 348                break;
 349        }
 279350    }
 351
 352    private static void ThrowIfGuideOwnedByDifferentContext(
 353        IGuide guide,
 354        TrailblazerWorldContext expectedContext)
 355    {
 1370356        TrailblazerWorldContext? ownerContext = guide switch
 1370357        {
 560358            AStarGuide a => a.TrailMap.Context,
 529359            FlowFieldGuide f => f.OwnerContext,
 279360            VolumeGuide v => v.TrailMap?.Context,
 2361            _ => null,
 1370362        };
 363
 1370364        if (ownerContext == null || ReferenceEquals(ownerContext, expectedContext))
 1369365            return;
 366
 1367        throw new InvalidOperationException(
 1368            "Guide belongs to a different owning TrailblazerWorldContext. Return it through the context that created it.
 369    }
 370
 371    /// <summary>
 372    /// Invalidates all cached results associated with the specified chart key.
 373    /// </summary>
 374    /// <remarks>
 375    /// Call this method to ensure that any cached data related to the specified chart is removed and
 376    /// will be recalculated on the next access.
 377    /// This is useful when the underlying chart data has changed and stale cache entries must be cleared.
 378    /// </remarks>
 379    /// <param name="chartKey">The unique key identifying the chart whose cached results should be invalidated. Cannot b
 380    public static void InvalidateCacheFor(string chartKey)
 381    {
 1236382        if (string.IsNullOrEmpty(chartKey)) return;
 383
 1232384        _cachedAStarResults.InvalidateForChart(chartKey);
 1232385        _cachedFlowResults.InvalidateForChart(chartKey);
 1232386        _cachedVolumeResults.InvalidateForChart(chartKey);
 1232387        _cachedHybridRoutePlans.InvalidateForChart(chartKey);
 1232388    }
 389
 390    internal static void InvalidateVolumeCache()
 391    {
 1755392        _cachedVolumeResults.InvalidateAll();
 1755393    }
 394
 395    /// <summary>
 396    /// Removes all cached A*, FlowField, and Volume guides.
 397    /// </summary>
 398    public static void FlushCache(bool force = false)
 399    {
 42400        if (!force && AnyInUse) return;
 40401        _cachedAStarResults.InvalidateAll();
 40402        _cachedFlowResults.InvalidateAll();
 40403        _cachedVolumeResults.InvalidateAll();
 40404        _cachedHybridRoutePlans.InvalidateAll();
 40405        ClearGuidePools();
 40406    }
 407
 408    internal static bool TrySeedAStarCacheForBenchmark(int requestKey, string[] chartKeys, bool checkout)
 409    {
 5410        TrailblazerWorldContext context = PathManager.ActiveState.Context;
 5411        return _cachedAStarResults.TrySeed(
 5412            AStarSurveyResult.Create(context, CreateSeedWaypoints(), chartKeys, requestKey),
 5413            checkout);
 414    }
 415
 416    internal static bool TrySeedFlowFieldCacheForBenchmark(int requestKey, string[] chartKeys, bool checkout)
 417    {
 1418        WorldVoxelIndex index = default;
 1419        var fields = new SwiftDictionary<WorldVoxelIndex, FlowField>(1)
 1420        {
 1421            [index] = new FlowField
 1422            {
 1423                GlobalIndex = index,
 1424                IsGoal = true
 1425            }
 1426        };
 427
 1428        TrailblazerWorldContext context = PathManager.ActiveState.Context;
 1429        return _cachedFlowResults.TrySeed(
 1430            FlowFieldSurveyResult.Create(context, fields, chartKeys, requestKey),
 1431            checkout);
 432    }
 433
 434    internal static bool TrySeedVolumeCacheForBenchmark(int requestKey, string[] chartKeys, bool checkout)
 435    {
 3436        TrailblazerWorldContext context = PathManager.ActiveState.Context;
 3437        return _cachedVolumeResults.TrySeed(
 3438            VolumeSurveyResult.Create(context, CreateSeedWaypoints(), chartKeys, requestKey),
 3439            checkout);
 440    }
 441
 442    internal static bool TrySeedHybridRoutePlanCacheForBenchmark(int requestKey, string[] chartKeys, bool checkout)
 443    {
 1444        TrailblazerWorldContext context = PathManager.ActiveState.Context;
 1445        var routePlan = new HybridRoutePlan(
 1446            new[] { HybridRouteStep.Waypoint(context, Vector3d.Zero) },
 1447            Array.Empty<TraversalTransition>(),
 1448            totalPathCost: 0);
 449
 1450        return _cachedHybridRoutePlans.TrySeed(
 1451            HybridRoutePlanSurveyResult.Create(context, routePlan, chartKeys, requestKey),
 1452            checkout);
 453    }
 454
 455    internal static int CountIndexedCacheEntriesForBenchmark(string chartKey)
 456    {
 1457        return _cachedAStarResults.CountIndexedEntriesForChart(chartKey)
 1458            + _cachedFlowResults.CountIndexedEntriesForChart(chartKey)
 1459            + _cachedVolumeResults.CountIndexedEntriesForChart(chartKey)
 1460            + _cachedHybridRoutePlans.CountIndexedEntriesForChart(chartKey);
 461    }
 462
 463    private static AStarWaypoint[] CreateSeedWaypoints()
 464    {
 8465        return new[]
 8466        {
 8467            new AStarWaypoint
 8468            {
 8469                Position = Vector3d.Zero,
 8470                IsGoal = true
 8471            }
 8472        };
 473    }
 474
 475    private static AStarGuide? RentAStarGuide(AStarSurveyResult result)
 476    {
 586477        AStarGuide guide = _aStarGuides.Rent();
 586478        if (guide.Initialize(result))
 586479            return guide;
 480
 0481        ReturnAStarGuide(guide, dispose: false);
 0482        _cachedAStarResults.Return(result, dispose: false);
 0483        return null;
 484    }
 485
 486    private static FlowFieldGuide? RentFlowFieldGuide(FlowFieldSurveyResult result)
 487    {
 536488        FlowFieldGuide guide = _flowFieldGuides.Rent();
 536489        if (guide.Initialize(result))
 536490            return guide;
 491
 0492        ReturnFlowFieldGuide(guide, dispose: false);
 0493        _cachedFlowResults.Return(result, dispose: false);
 0494        return null;
 495    }
 496
 497    private static bool TryRentFlowFieldGuide(
 498        FlowFieldPathRequest request,
 499        FlowFieldSurveyResult result,
 500        [NotNullWhen(true)] out FlowFieldGuide? guide)
 501    {
 515502        guide = null;
 515503        if (result.Fields == null
 515504            || request.StartNode == null
 515505            || !result.Fields.ContainsKey(request.StartNode.WorldIndex))
 506        {
 1507            return false;
 508        }
 509
 514510        guide = RentFlowFieldGuide(result);
 514511        return guide != null;
 512    }
 513
 514    private static VolumeGuide? RentVolumeGuide(VolumeSurveyResult result)
 515    {
 290516        VolumeGuide guide = _volumeGuides.Rent();
 290517        if (guide.Initialize(result))
 290518            return guide;
 519
 0520        ReturnVolumeGuide(guide, dispose: false);
 0521        _cachedVolumeResults.Return(result, dispose: false);
 0522        return null;
 523    }
 524
 525    private static void ReturnAStarGuide(AStarGuide guide, bool dispose)
 526    {
 559527        if (dispose || guide.GetType() != typeof(AStarGuide))
 3528            _aStarGuides.Destroy(guide);
 529        else
 556530            _aStarGuides.Release(guide);
 556531    }
 532
 533    private static void ReturnFlowFieldGuide(FlowFieldGuide guide, bool dispose)
 534    {
 529535        if (dispose || guide.GetType() != typeof(FlowFieldGuide))
 3536            _flowFieldGuides.Destroy(guide);
 537        else
 526538            _flowFieldGuides.Release(guide);
 526539    }
 540
 541    private static void ReturnVolumeGuide(VolumeGuide guide, bool dispose)
 542    {
 279543        if (dispose)
 2544            _volumeGuides.Destroy(guide);
 545        else
 277546            _volumeGuides.Release(guide);
 277547    }
 548
 549    private static void ClearGuidePools()
 550    {
 40551        _aStarGuides.Clear();
 40552        _flowFieldGuides.Clear();
 40553        _volumeGuides.Clear();
 40554    }
 555
 556    private static AStarSurveyResult ResolveAStarResult(AStarPathRequest request)
 557    {
 72558        AStarSurveyResult directResult = _aStarSurveyor.FindPath(request);
 72559        if (directResult.HasPath || !request.AllowTraversalTransitions)
 54560            return directResult;
 561
 18562        return TryBuildTransitionFallbackAStarResult(request, out AStarSurveyResult fallbackResult)
 18563            ? fallbackResult
 18564            : directResult;
 565    }
 566
 567    private static bool TryBuildTransitionFallbackAStarResult(
 568        AStarPathRequest request,
 569        out AStarSurveyResult result)
 570    {
 18571        result = AStarSurveyResult.Empty;
 572
 18573        HybridPathRequest? hybridRequest = HybridPathRequest.CreateFromAStar(request);
 18574        HybridRoutePlan? routePlan = hybridRequest?.RoutePlan;
 18575        if (routePlan == null
 18576            || routePlan.DirectedTransitions.Length == 0)
 577        {
 1578            return false;
 579        }
 580
 17581        if (!HybridWaypointFlattener.TryBuild(
 17582            routePlan,
 17583            out AStarWaypoint[]? flattenedWaypoints,
 17584            out string[] chartKeys))
 585        {
 0586            return false;
 587        }
 588
 17589        result = AStarSurveyResult.Create(request.Context, flattenedWaypoints!, chartKeys, request.RequestCacheKey);
 17590        return true;
 591    }
 592
 593    private static bool TryBuildTransitionFallbackFlowGuide(
 594        FlowFieldPathRequest request,
 595        [NotNullWhen(true)] out FlowFieldGuide? guide)
 596    {
 7597        guide = null;
 598
 7599        if (!TryGetTransitionFallbackFlowPlan(request, out HybridRoutePlan? routePlan))
 1600            return false;
 601
 6602        FlowFieldGuide stagedGuide = _flowFieldGuides.Rent();
 6603        if (stagedGuide.InitializeStaged(routePlan))
 604        {
 6605            guide = stagedGuide;
 6606            return true;
 607        }
 608
 0609        ReturnFlowFieldGuide(stagedGuide, dispose: false);
 0610        return false;
 611    }
 612
 613    private static bool TryGetTransitionFallbackFlowPlan(
 614        FlowFieldPathRequest request,
 615        [NotNullWhen(true)] out HybridRoutePlan? routePlan)
 616    {
 7617        routePlan = null;
 7618        bool pathFound = _cachedHybridRoutePlans.TryGetOrCreate(
 7619            request,
 7620            () => ResolveTransitionFallbackFlowPlan(request),
 7621            out HybridRoutePlanSurveyResult result);
 622
 7623        if (!pathFound || result.RoutePlan == null)
 1624            return false;
 625
 6626        routePlan = result.RoutePlan;
 6627        _cachedHybridRoutePlans.Return(result, dispose: false);
 6628        return true;
 629    }
 630
 631    private static bool TryGetCachedTransitionFallbackFlowPlan(
 632        FlowFieldPathRequest request,
 633        [NotNullWhen(true)] out HybridRoutePlan? routePlan)
 634    {
 14635        routePlan = null;
 14636        if (!_cachedHybridRoutePlans.TryCheckout(request, out HybridRoutePlanSurveyResult result))
 13637            return false;
 638
 639        try
 640        {
 1641            routePlan = result.RoutePlan;
 1642            return routePlan != null;
 643        }
 644        finally
 645        {
 1646            _cachedHybridRoutePlans.Return(result, dispose: false);
 1647        }
 1648    }
 649
 650    private static HybridRoutePlanSurveyResult ResolveTransitionFallbackFlowPlan(FlowFieldPathRequest request)
 651    {
 7652        HybridPathRequest? hybridRequest = HybridPathRequest.CreateFromFlowField(request);
 7653        HybridRoutePlan? routePlan = hybridRequest?.RoutePlan;
 7654        if (routePlan == null
 7655            || routePlan.DirectedTransitions.Length == 0)
 656        {
 1657            return HybridRoutePlanSurveyResult.Empty;
 658        }
 659
 6660        return HybridRoutePlanSurveyResult.Create(
 6661            request.Context,
 6662            routePlan,
 6663            CollectRoutePlanChartKeys(routePlan),
 6664            request.RequestCacheKey);
 665    }
 666
 667    private static string[] CollectRoutePlanChartKeys(HybridRoutePlan routePlan)
 668    {
 9669        if (routePlan == null || routePlan.Steps.Length == 0)
 1670            return Array.Empty<string>();
 671
 8672        SwiftHashSet<string> chartKeys = new();
 68673        for (int i = 0; i < routePlan.Steps.Length; i++)
 674        {
 26675            HybridRouteStep step = routePlan.Steps[i];
 26676            if (step.Kind != HybridRouteStepKind.PathSegment)
 677                continue;
 678
 14679            if (step.SegmentChartKeys.Length > 0)
 13680                AddChartKeys(chartKeys, step.SegmentChartKeys);
 681            else
 1682                AddRequestEndpointChartOwners(chartKeys, step.SegmentRequest);
 683        }
 684
 8685        if (chartKeys.Count == 0)
 1686            return Array.Empty<string>();
 687
 7688        string[] result = new string[chartKeys.Count];
 7689        int index = 0;
 54690        foreach (string chartKey in chartKeys)
 20691            result[index++] = chartKey;
 692
 7693        return result;
 694    }
 695
 696    private static void AddChartKeys(SwiftHashSet<string> chartKeys, string[] segmentChartKeys)
 697    {
 14698        if (chartKeys == null || segmentChartKeys == null)
 1699            return;
 700
 66701        for (int i = 0; i < segmentChartKeys.Length; i++)
 702        {
 20703            string chartKey = segmentChartKeys[i];
 20704            if (!string.IsNullOrEmpty(chartKey))
 20705                chartKeys.Add(chartKey);
 706        }
 13707    }
 708
 709    private static void AddRequestEndpointChartOwners(SwiftHashSet<string> chartKeys, IPathRequest request)
 710    {
 2711        if (request == null)
 1712            return;
 713
 1714        AddVoxelChartOwners(chartKeys, request.StartNode);
 1715        AddVoxelChartOwners(chartKeys, request.EndNode);
 1716    }
 717
 718    private static void AddVoxelChartOwners(SwiftHashSet<string> chartKeys, Voxel? voxel)
 719    {
 3720        if (voxel == null)
 1721            return;
 722
 2723        if (voxel.TryGetPartition(out SolidChartPartition? solidPartition) && solidPartition != null)
 1724            ChartOwnerUtility.AddOwners(chartKeys, solidPartition.ChartOwners);
 725
 2726        if (voxel.TryGetPartition(out VolumeChartPartition? volumePartition) && volumePartition != null)
 1727            ChartOwnerUtility.AddOwners(chartKeys, volumePartition.ChartOwners);
 2728    }
 729}

Methods/Properties

get_GuideState()
get__aStarSurveyor()
get__flowFieldSurveyor()
get__volumeSurveyor()
get__cachedAStarResults()
get_TotalAStarGuideCount()
get_InUseAStarGuideCount()
get__cachedFlowResults()
get_TotalFlowGuideCount()
get_InUseFlowGuideCount()
get__cachedVolumeResults()
get_TotalVolumeGuideCount()
get_InUseVolumeGuideCount()
get__cachedHybridRoutePlans()
get__aStarGuides()
get__flowFieldGuides()
get__volumeGuides()
get_TotalHybridRoutePlanCount()
get_InUseHybridRoutePlanCount()
get_IsPooling()
get_AnyInUse()
CullExpiredGuides(System.Int32)
RequestGuide(Trailblazer.Pathing.IPathRequest,T&)
RequestGuide(Trailblazer.Pathing.IPathRequest,Trailblazer.Pathing.IGuide&)
RequestAStar(Trailblazer.Pathing.AStarPathRequest)
RequestAStarMiss(Trailblazer.Pathing.AStarPathRequest)
RequestFlowField(Trailblazer.Pathing.FlowFieldPathRequest)
RequestFlowFieldMiss(Trailblazer.Pathing.FlowFieldPathRequest)
RequestVolume(Trailblazer.Pathing.VolumePathRequest)
RequestVolumeMiss(Trailblazer.Pathing.VolumePathRequest)
RequestHybrid(Trailblazer.Pathing.HybridPathRequest)
ReturnGuide(Trailblazer.Pathing.IGuide,System.Boolean)
ThrowIfGuideOwnedByDifferentContext(Trailblazer.Pathing.IGuide,Trailblazer.TrailblazerWorldContext)
InvalidateCacheFor(System.String)
InvalidateVolumeCache()
FlushCache(System.Boolean)
TrySeedAStarCacheForBenchmark(System.Int32,System.String[],System.Boolean)
TrySeedFlowFieldCacheForBenchmark(System.Int32,System.String[],System.Boolean)
TrySeedVolumeCacheForBenchmark(System.Int32,System.String[],System.Boolean)
TrySeedHybridRoutePlanCacheForBenchmark(System.Int32,System.String[],System.Boolean)
CountIndexedCacheEntriesForBenchmark(System.String)
CreateSeedWaypoints()
RentAStarGuide(Trailblazer.Pathing.AStarSurveyResult)
RentFlowFieldGuide(Trailblazer.Pathing.FlowFieldSurveyResult)
TryRentFlowFieldGuide(Trailblazer.Pathing.FlowFieldPathRequest,Trailblazer.Pathing.FlowFieldSurveyResult,Trailblazer.Pathing.FlowFieldGuide&)
RentVolumeGuide(Trailblazer.Pathing.VolumeSurveyResult)
ReturnAStarGuide(Trailblazer.Pathing.AStarGuide,System.Boolean)
ReturnFlowFieldGuide(Trailblazer.Pathing.FlowFieldGuide,System.Boolean)
ReturnVolumeGuide(Trailblazer.Pathing.VolumeGuide,System.Boolean)
ClearGuidePools()
ResolveAStarResult(Trailblazer.Pathing.AStarPathRequest)
TryBuildTransitionFallbackAStarResult(Trailblazer.Pathing.AStarPathRequest,Trailblazer.Pathing.AStarSurveyResult&)
TryBuildTransitionFallbackFlowGuide(Trailblazer.Pathing.FlowFieldPathRequest,Trailblazer.Pathing.FlowFieldGuide&)
TryGetTransitionFallbackFlowPlan(Trailblazer.Pathing.FlowFieldPathRequest,Trailblazer.Pathing.HybridRoutePlan&)
TryGetCachedTransitionFallbackFlowPlan(Trailblazer.Pathing.FlowFieldPathRequest,Trailblazer.Pathing.HybridRoutePlan&)
ResolveTransitionFallbackFlowPlan(Trailblazer.Pathing.FlowFieldPathRequest)
CollectRoutePlanChartKeys(Trailblazer.Pathing.HybridRoutePlan)
AddChartKeys(SwiftCollections.SwiftHashSet`1<System.String>,System.String[])
AddRequestEndpointChartOwners(SwiftCollections.SwiftHashSet`1<System.String>,Trailblazer.Pathing.IPathRequest)
AddVoxelChartOwners(SwiftCollections.SwiftHashSet`1<System.String>,GridForge.Grids.Voxel)