< Summary

Information
Class: Trailblazer.Pathing.TrailblazerGuideService
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/Guide/TrailblazerGuideService.cs
Line coverage
100%
Covered lines: 119
Uncovered lines: 0
Coverable lines: 119
Total lines: 216
Line coverage: 100%
Branch coverage
100%
Covered branches: 40
Total branches: 40
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
TryGetDirectHeading(...)100%1010100%
RequestGuide(...)100%1010100%
RequestFlowField(...)100%1414100%
EnsureUsable()100%22100%
ResolveDirectHeading(...)100%44100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// TrailblazerGuideService.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;
 10
 11namespace Trailblazer.Pathing;
 12
 13/// <summary>
 14/// Context-owned API for actionable A* and flow-field guide acquisition.
 15/// </summary>
 16public sealed class TrailblazerGuideService
 17{
 18    private readonly TrailblazerWorldContext _context;
 98919    internal TrailblazerGuideService(TrailblazerWorldContext context)
 20    {
 98921        _context = context;
 98922    }
 23
 24    /// <summary>Proves one cost-neutral graph-direct surface heading.</summary>
 25    internal NavigationRayStatus TryGetDirectHeading(
 26        PathQuery query,
 27        Vector3d actualFoot,
 28        out Vector3d heading)
 29    {
 57630        EnsureUsable();
 57631        heading = Vector3d.Zero;
 57632        NavigationImmediateRayWorkspace immediate = _context.Pathing.ImmediateRayWorkspace;
 57633        lock (immediate.SyncRoot)
 34        {
 57635            NavigationWorldGraphStore store = _context.Pathing.NavigationGraphStore;
 57636            using NavigationWorldGraphLease? lease = store.TryAcquire();
 57637            if (lease == null)
 238                return NavigationRayStatus.CapacityExceeded;
 57439            NavigationWorldGraph graph = lease.Graph;
 57440            if (!graph.AreaCatalog.TryGet(
 57441                    query.AreaPolicy,
 57442                    out NavigationAreaPolicy areaPolicy))
 43            {
 244                return NavigationRayStatus.Stale;
 45            }
 46
 57247            NavigationRayWork ray = immediate.RayWork;
 57248            ray.Begin(new NavigationRayRequest(
 57249                _context.World,
 57250                store,
 57251                graph,
 57252                query.Agent,
 57253                areaPolicy,
 57254                query.Traversal.StartMedium,
 57255                actualFoot,
 57256                query.End.Position,
 57257                NavigationRayEndpointAllowance.None));
 57258            NavigationWorkMeter meter = immediate.WorkMeter;
 57259            meter.Reset(query.Budget);
 60            NavigationRayStatus status;
 61            NavigationRayResult result;
 62            try
 63            {
 64                do
 65                {
 57266                    status = ray.Advance(meter);
 67                }
 57268                while (status == NavigationRayStatus.Pending);
 57269                result = ray.Result;
 57270            }
 71            finally
 72            {
 57273                ray.Reset();
 57274            }
 57275            if (status != NavigationRayStatus.Success)
 4676                return status;
 52677            if (!result.IsSemanticCostNeutral)
 278                return NavigationRayStatus.Blocked;
 52479            return ResolveDirectHeading(query.End.Position, actualFoot, out heading);
 80        }
 57681    }
 82
 83    /// <summary>
 84    /// Requests one graph-backed A* guide for immutable query intent.
 85    /// </summary>
 86    public NavigationGuideStatus RequestGuide(
 87        PathQuery query,
 88        out NavigationGuideLease? result)
 89    {
 38590        EnsureUsable();
 38391        result = null;
 92
 38393        if (query.Algorithm != PathAlgorithm.AStar)
 94        {
 195            return NavigationGuideStatus.Unsupported;
 96        }
 97
 38298        NavigationAStarAdmissionGate gate = _context.Pathing.NavigationAStarAdmissionGate;
 38299        NavigationAStarQueryStatus beginStatus = gate.Begin(query, out NavigationAStarBatchWork work);
 382100        if (beginStatus != NavigationAStarQueryStatus.Pending)
 1101            return NavigationGuideStatusMapper.ToPublic(beginStatus);
 102
 381103        using (work)
 104        {
 845105            while (!work.IsAdmissionComplete)
 106            {
 464107                work.AdvanceAdmission(
 464108                    query.Budget.MaxLookupProbes,
 464109                    query.Budget.MaxEndpointCandidates);
 110            }
 111
 536112            while (!work.IsReadyToPublish(inputIndex: 0))
 113            {
 155114                work.AdvanceSearch(
 155115                    inputIndex: 0,
 155116                    query.Budget.MaxLookupProbes,
 155117                    nodeStepLimit: int.MaxValue,
 155118                    query.Budget.MaxEvaluatedEdges,
 155119                    query.Budget.MaxConnectionLegs);
 120            }
 121
 381122            work.PublishReadyPrefix(maximumCount: 1);
 381123            NavigationAStarQueryStatus status = work.GetStatus(inputIndex: 0);
 381124            if (status != NavigationAStarQueryStatus.Success)
 24125                return NavigationGuideStatusMapper.ToPublic(status);
 126
 357127            NavigationAStarPayloadLease payloadLease = work.TakeResult(inputIndex: 0);
 357128            return gate.PayloadCache.TryCreatePublicGuide(
 357129                _context.Pathing.NavigationGraphStore,
 357130                payloadLease,
 357131                out result);
 132        }
 381133    }
 134
 135    /// <summary>
 136    /// Requests one graph-backed destination-centric flow field for immutable query intent.
 137    /// </summary>
 138    public NavigationGuideStatus RequestFlowField(
 139        PathQuery query,
 140        out NavigationFlowFieldLease? result)
 141    {
 44142        EnsureUsable();
 44143        result = null;
 144
 44145        if (!query.Agent.IsValid)
 1146            return NavigationGuideStatus.InvalidProfile;
 43147        if (query.Algorithm != PathAlgorithm.FlowField)
 148        {
 1149            return NavigationGuideStatus.Unsupported;
 150        }
 151
 42152        NavigationFlowAdmissionGate gate = _context.Pathing.NavigationFlowAdmissionGate;
 42153        NavigationFlowQueryStatus beginStatus = gate.Begin(query, out NavigationFlowBatchWork work);
 42154        if (beginStatus != NavigationFlowQueryStatus.Pending)
 1155            return NavigationGuideStatusMapper.ToPublic(beginStatus);
 156
 41157        using (work)
 158        {
 109159            while (!work.IsAdmissionComplete)
 160            {
 68161                work.AdvanceAdmission(
 68162                    query.Budget.MaxLookupProbes,
 68163                    query.Budget.MaxEndpointCandidates);
 164            }
 165
 72166            while (!work.IsReadyToPublish(inputIndex: 0))
 167            {
 31168                work.AdvanceSearch(
 31169                    inputIndex: 0,
 31170                    query.Budget.MaxLookupProbes,
 31171                    nodeStepLimit: int.MaxValue,
 31172                    query.Budget.MaxEvaluatedEdges,
 31173                    query.Budget.MaxConnectionLegs);
 174            }
 175
 41176            work.PublishReadyPrefix(maximumCount: 1);
 41177            NavigationFlowQueryStatus status = work.GetStatus(inputIndex: 0);
 41178            if (status != NavigationFlowQueryStatus.Success)
 10179                return NavigationGuideStatusMapper.ToPublic(status);
 180
 31181            using NavigationFlowQueryResult flowResult = work.TakeResult(inputIndex: 0);
 31182            NavigationGuideStatus guideStatus = gate.PayloadCache.TryCreateGuide(
 31183                _context.Pathing.NavigationGraphStore,
 31184                flowResult,
 31185                out NavigationFlowFieldLease guide);
 31186            if (guideStatus != NavigationGuideStatus.Success)
 1187                return guideStatus;
 188
 30189            result = guide;
 30190            return NavigationGuideStatus.Success;
 191        }
 41192    }
 193
 194    private void EnsureUsable()
 195    {
 1005196        if (_context.IsDisposed)
 1197            throw new ObjectDisposedException(nameof(TrailblazerWorldContext));
 1004198        SwiftThrowHelper.ThrowIfTrue(
 1004199            !_context.World.IsActive,
 1004200            message: "TrailblazerGuideService is bound to an inactive GridWorld.");
 1003201    }
 202
 203    internal static NavigationRayStatus ResolveDirectHeading(
 204        Vector3d destination,
 205        Vector3d actualFoot,
 206        out Vector3d heading)
 207    {
 525208        heading = Vector3d.Zero;
 525209        if (!Vector3d.TrySubtract(destination, actualFoot, out Vector3d delta))
 1210            return NavigationRayStatus.CostOverflow;
 524211        if (delta != Vector3d.Zero)
 523212            heading = delta.Normalized;
 524213        return NavigationRayStatus.Success;
 214    }
 215
 216}