< Summary

Information
Class: Trailblazer.Pathing.HybridPathRequest
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/Hybrid/HybridPathRequest.cs
Line coverage
96%
Covered lines: 154
Uncovered lines: 5
Coverable lines: 159
Total lines: 373
Line coverage: 96.8%
Branch coverage
85%
Covered branches: 68
Total branches: 80
Branch coverage: 85%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_HasOrigin()100%11100%
get_HasDestination()100%11100%
get_HasValidEndpoints()100%22100%
get_IsValid()100%44100%
get_HasZeroDisplacement()83.33%66100%
get_RequestCacheKey()100%11100%
.ctor()100%11100%
TryCreate(...)100%11100%
Create(...)80%101096.42%
CreateFromAStar(...)100%66100%
CreateFromFlowField(...)100%66100%
UpdateRequest(...)75%88100%
TrySetOrigin(...)83.33%6693.33%
TrySetDestination(...)66.66%6686.66%
TrySetUnitSize(...)100%22100%
Equals(...)100%22100%
Equals(...)100%22100%
GetHashCode()62.5%88100%
RebuildPlan()91.66%121294.11%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/Hybrid/HybridPathRequest.cs

#LineLine coverage
 1using FixedMathSharp;
 2using GridForge.Grids;
 3using System;
 4using System.Diagnostics.CodeAnalysis;
 5using System.Runtime.CompilerServices;
 6
 7namespace Trailblazer.Pathing;
 8
 9/// <summary>
 10/// Internal adapter request used to build staged transition-aware routes from normal chart-backed request intent.
 11/// </summary>
 12/// <remarks>
 13/// The current implementation supports:
 14/// chart -> chart direct paths,
 15/// chart -> transition -> chart,
 16/// chart -> transition -> volume -> transition -> chart.
 17/// </remarks>
 18internal sealed class HybridPathRequest : IPathRequest, IEquatable<HybridPathRequest>
 19{
 20    #region Properties
 21
 22    /// <inheritdoc/>
 23    public TrailblazerWorldContext Context { get; private set; } = null!;
 24
 25    /// <inheritdoc/>
 26    public Vector3d Origin { get; private set; }
 27
 28    /// <inheritdoc/>
 29    public Voxel? StartNode { get; private set; }
 30
 31    /// <inheritdoc/>
 32    public Vector3d TargetPosition { get; private set; }
 33
 34    /// <inheritdoc/>
 35    public Voxel? EndNode { get; private set; }
 36
 37    /// <inheritdoc/>
 38    public Fixed64 UnitSize { get; private set; }
 39
 40    /// <inheritdoc/>
 41    public bool AllowUnwalkableEndpoints { get; set; }
 42
 43    /// <inheritdoc/>
 44    public int MaxPathSearchRange { get; set; }
 45
 46    public HeuristicMethod Heuristic { get; set; }
 47
 48    public Fixed64 MaxClimbHeight { get; set; }
 49
 50    public int ExtraFloodRange { get; set; }
 51
 52    /// <inheritdoc/>
 21553    public bool HasOrigin => StartNode != null;
 54
 55    /// <inheritdoc/>
 21456    public bool HasDestination => EndNode != null;
 57
 58    /// <inheritdoc/>
 21559    public bool HasValidEndpoints => HasOrigin && HasDestination;
 60
 61    /// <inheritdoc/>
 1062    public bool IsValid => HasValidEndpoints && MaxPathSearchRange > 0 && RoutePlan != null;
 63
 64    /// <inheritdoc/>
 65    public bool HasZeroDisplacement =>
 366        !IsValid
 367        || (StartNode == EndNode
 368            && (RoutePlan?.DirectedTransitions.Length ?? 0) == 0);
 69
 70    /// <inheritdoc/>
 471    public int RequestCacheKey => GetHashCode();
 72
 73    internal HybridRoutePlan? RoutePlan { get; private set; }
 74
 75    internal HybridChartRequestKind ChartRequestKind { get; private set; }
 76
 77    #endregion
 78
 79    #region Construction and Initialization
 80
 81    /// <summary>
 82    /// Private constructor to enforce the use of factory methods for creating instances of HybridPathRequest.
 83    /// </summary>
 19684    private HybridPathRequest() { }
 85
 86    /// <summary>
 87    /// Attempts to create a new context-bound HybridPathRequest with the specified parameters.
 88    /// </summary>
 89    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 90    public static bool TryCreate(
 91        TrailblazerWorldContext context,
 92        Vector3d origin,
 93        Vector3d destination,
 94        Fixed64 unitSize,
 95        [NotNullWhen(true)] out HybridPathRequest? request,
 96        HeuristicMethod heuristic = HeuristicMethod.Manhattan,
 97        Fixed64? maxClimbHeight = null,
 98        bool allowUnwalkableEndpoints = false)
 99    {
 13100        request = Create(context, origin, destination, unitSize, heuristic, maxClimbHeight, allowUnwalkableEndpoints);
 13101        return request != null;
 102    }
 103
 104    /// <summary>
 105    /// Creates a new context-bound HybridPathRequest with the specified parameters.
 106    /// </summary>
 107    public static HybridPathRequest? Create(
 108        TrailblazerWorldContext context,
 109        Vector3d origin,
 110        Vector3d destination,
 111        Fixed64 unitSize,
 112        HeuristicMethod heuristic = HeuristicMethod.Manhattan,
 113        Fixed64? maxClimbHeight = null,
 114        bool allowUnwalkableEndpoints = false)
 115    {
 21116        PathRequestContextResolver.ThrowIfUnusable(context);
 21117        if (!SolidVoxelFinder.TryGetPathEdgeVoxels(
 21118            context,
 21119            origin,
 21120            destination,
 21121            out Voxel? startNode,
 21122            out Voxel? endNode,
 21123            unitSize,
 21124            allowUnwalkableEndpoints))
 125        {
 2126            return null;
 127        }
 128
 19129        if (startNode == null || endNode == null)
 0130            return null;
 131
 19132        var request = new HybridPathRequest
 19133        {
 19134            Context = context,
 19135            Origin = origin,
 19136            StartNode = startNode,
 19137            TargetPosition = destination,
 19138            EndNode = endNode,
 19139            UnitSize = unitSize,
 19140            ChartRequestKind = HybridChartRequestKind.AStar,
 19141            Heuristic = heuristic,
 19142            AllowUnwalkableEndpoints = allowUnwalkableEndpoints,
 19143            MaxClimbHeight = maxClimbHeight ?? context.VoxelSize
 19144        };
 145
 19146        if (!request.RebuildPlan())
 1147            return null;
 148
 18149        return request;
 150    }
 151
 152    /// <summary>
 153    /// Creates a new HybridPathRequest based on an existing AStarPathRequest.
 154    /// This factory method is used to convert a standard A* path request into a hybrid request that can be processed by
 155    ///  The method checks the validity of the input request and attempts to build a corresponding route plan for the hy
 156    /// </summary>
 157    /// <param name="request">The AStarPathRequest to convert into a HybridPathRequest.</param>
 158    /// <returns>The created HybridPathRequest if successful; otherwise, null.</returns>
 159    internal static HybridPathRequest? CreateFromAStar(AStarPathRequest request)
 160    {
 61161        if (request == null || !request.HasValidEndpoints)
 1162            return null;
 163
 60164        var hybridRequest = new HybridPathRequest
 60165        {
 60166            Context = request.Context,
 60167            Origin = request.Origin,
 60168            StartNode = request.StartNode,
 60169            TargetPosition = request.TargetPosition,
 60170            EndNode = request.EndNode,
 60171            UnitSize = request.UnitSize,
 60172            ChartRequestKind = HybridChartRequestKind.AStar,
 60173            Heuristic = request.Heuristic,
 60174            AllowUnwalkableEndpoints = request.AllowUnwalkableEndpoints,
 60175            MaxClimbHeight = request.MaxClimbHeight
 60176        };
 177
 60178        return hybridRequest.RebuildPlan() ? hybridRequest : null;
 179    }
 180
 181    /// <summary>
 182    /// Creates a new HybridPathRequest based on an existing FlowFieldPathRequest.
 183    /// This factory method is used to convert a standard flow field path request into a hybrid request that can be proc
 184    /// The method checks the validity of the input request and attempts to build a corresponding route plan for the hyb
 185    /// </summary>
 186    /// <param name="request">The FlowFieldPathRequest to convert into a HybridPathRequest.</param>
 187    /// <returns>The created HybridPathRequest if successful; otherwise, null.</returns>
 188    internal static HybridPathRequest? CreateFromFlowField(FlowFieldPathRequest request)
 189    {
 20190        if (request == null || !request.HasValidEndpoints)
 1191            return null;
 192
 19193        var hybridRequest = new HybridPathRequest
 19194        {
 19195            Context = request.Context,
 19196            Origin = request.Origin,
 19197            StartNode = request.StartNode,
 19198            TargetPosition = request.TargetPosition,
 19199            EndNode = request.EndNode,
 19200            UnitSize = request.UnitSize,
 19201            ChartRequestKind = HybridChartRequestKind.FlowField,
 19202            AllowUnwalkableEndpoints = request.AllowUnwalkableEndpoints,
 19203            MaxClimbHeight = request.MaxClimbHeight,
 19204            ExtraFloodRange = request.ExtraFloodRange
 19205        };
 206
 19207        return hybridRequest.RebuildPlan() ? hybridRequest : null;
 208    }
 209
 210    #endregion
 211
 212    /// <inheritdoc/>
 213    public bool UpdateRequest(
 214        Vector3d origin,
 215        Vector3d destination,
 216        Fixed64? unitSize)
 217    {
 2218        Fixed64 resolvedUnitSize = unitSize ?? Context.VoxelSize;
 2219        bool success = SolidVoxelFinder.TryGetPathEdgeVoxels(
 2220            Context,
 2221            origin,
 2222            destination,
 2223            out Voxel? startVoxel,
 2224            out Voxel? endVoxel,
 2225            resolvedUnitSize,
 2226            AllowUnwalkableEndpoints);
 227
 2228        Origin = origin;
 2229        TargetPosition = destination;
 2230        StartNode = startVoxel;
 2231        EndNode = endVoxel;
 2232        UnitSize = resolvedUnitSize;
 233
 2234        if (!success || startVoxel == null || endVoxel == null)
 235        {
 1236            RoutePlan = null;
 1237            MaxPathSearchRange = 0;
 1238            return false;
 239        }
 240
 1241        return RebuildPlan();
 242    }
 243
 244    /// <inheritdoc/>
 245    public bool TrySetOrigin(Vector3d origin, bool resetSearchRange = false)
 246    {
 3247        if (EndNode == null)
 1248            return false;
 249
 2250        if (!SolidVoxelFinder.GetStartVoxel(
 2251            Context,
 2252            origin,
 2253            TargetPosition,
 2254            out Voxel? startNode,
 2255            AllowUnwalkableEndpoints,
 2256            UnitSize))
 257        {
 1258            return false;
 259        }
 260
 1261        if (startNode == null)
 0262            return false;
 263
 1264        Origin = origin;
 1265        StartNode = startNode;
 1266        return RebuildPlan();
 267    }
 268
 269    /// <inheritdoc/>
 270    public bool TrySetDestination(Vector3d destination, bool resetSearchRange = false)
 271    {
 2272        if (StartNode == null)
 1273            return false;
 274
 1275        if (!SolidVoxelFinder.GetEndVoxel(
 1276            Context,
 1277            Origin,
 1278            destination,
 1279            out Voxel? endNode,
 1280            AllowUnwalkableEndpoints,
 1281            UnitSize))
 282        {
 0283            return false;
 284        }
 285
 1286        if (endNode == null)
 0287            return false;
 288
 1289        TargetPosition = destination;
 1290        EndNode = endNode;
 1291        return RebuildPlan();
 292    }
 293
 294    /// <inheritdoc/>
 295    public bool TrySetUnitSize(Fixed64 unitSize)
 296    {
 2297        if (UnitSize == unitSize)
 1298            return false;
 299
 1300        return UpdateRequest(Origin, TargetPosition, unitSize);
 301    }
 302
 303    /// <inheritdoc/>
 304    public override bool Equals(object? obj) =>
 2305        obj is HybridPathRequest other && Equals(other);
 306
 307    /// <inheritdoc/>
 308    public bool Equals(HybridPathRequest? other) =>
 3309        other != null
 3310        && RequestCacheKey == other.RequestCacheKey;
 311
 312    /// <summary>
 313    /// Generates a hash code for the current path request based on its properties and route plan.
 314    /// This hash code is used for caching and guide pooling, allowing for efficient retrieval of guides based on reques
 315    /// </summary>
 316    /// <returns>A hash code representing the current path request.</returns>
 317    public override int GetHashCode()
 318    {
 6319        PathRequestHashBuilder transitionHash = PathRequestHashBuilder.Create();
 6320        if (RoutePlan != null)
 321        {
 20322            for (int i = 0; i < RoutePlan.DirectedTransitions.Length; i++)
 4323                transitionHash.AddOrdinalString(RoutePlan.DirectedTransitions[i].Id);
 324        }
 325
 6326        PathRequestHashBuilder hash = PathRequestHashBuilder.Create();
 6327        hash.Add(StartNode?.SpawnToken ?? 0);
 6328        hash.Add(EndNode?.SpawnToken ?? 0);
 6329        hash.Add(UnitSize.GetHashCode());
 6330        hash.Add((int)ChartRequestKind);
 6331        hash.Add(AllowUnwalkableEndpoints);
 6332        hash.Add((int)Heuristic);
 6333        hash.Add(MaxClimbHeight.GetHashCode());
 6334        hash.Add(ExtraFloodRange);
 6335        hash.Add(MaxPathSearchRange);
 6336        hash.Add(transitionHash.ToHashCode());
 6337        return hash.ToHashCode();
 338    }
 339
 340    /// <summary>
 341    /// Rebuilds the route plan for the current request using the HybridRoutePlanner.
 342    /// This method is called whenever the request parameters are updated (e.g. origin, destination, unit size) to ensur
 343    /// </summary>
 344    /// <returns>True if the route plan was successfully rebuilt; otherwise, false.</returns>
 345    internal bool RebuildPlan()
 346    {
 102347        RoutePlan = null;
 102348        MaxPathSearchRange = 0;
 349
 102350        if (!HasValidEndpoints)
 0351            return false;
 352
 353        HybridRoutePlan? plan;
 102354        using (PathManager.EnterState(Context.Pathing.State))
 355        {
 102356            if (!HybridRoutePlanner.TryPlan(this, out plan)
 102357                || plan == null)
 5358                return false;
 97359        }
 360
 97361        RoutePlan = plan;
 362
 97363        int totalSearchRange = 0;
 804364        for (int i = 0; i < plan.Steps.Length; i++)
 365        {
 305366            if (plan.Steps[i].Kind == HybridRouteStepKind.PathSegment)
 69367                totalSearchRange += plan.Steps[i].SegmentRequest.MaxPathSearchRange;
 368        }
 369
 97370        MaxPathSearchRange = totalSearchRange > 0 ? totalSearchRange : 1;
 97371        return true;
 5372    }
 373}