TailPatchApplier.cs 7.5 KB
using System;
using System.Collections.Generic;
using System.Linq;
using Rcs.Application.Services.PathFind.Models;
using Rcs.Application.Services.PathFind.Realtime;
using Rcs.Domain.Entities;
using Rcs.Domain.Enums;

namespace Rcs.Infrastructure.PathFinding.Realtime;

/// <summary>
/// 在保留已发送分段的前提下,将尾段补丁应用到未发送缓存区域。
/// </summary>
public sealed class TailPatchApplier : ITailPatchApplier
{
    /// <summary>
    /// 在缓存中应用尾段补丁,并推进路径版本号。
    /// </summary>
    /// <param name="cache">目标路径缓存。</param>
    /// <param name="patch">待应用补丁。</param>
    /// <param name="failureReason">失败原因。</param>
    /// <returns>应用是否成功。</returns>
    public bool TryApplyPatch(VdaSegmentedPathCache cache, TailPatch patch, out string failureReason)
    {
        failureReason = string.Empty;

        if (cache.PlanVersion != patch.ExpectedPlanVersion)
        {
            failureReason = $"plan version mismatch, expected={patch.ExpectedPlanVersion}, actual={cache.PlanVersion}";
            return false;
        }

        if (patch.NewTail.Count == 0)
        {
            failureReason = "patch tail is empty";
            return false;
        }

        if (patch.FromJunctionIndex < 0 || patch.FromJunctionIndex > cache.JunctionSegments.Count)
        {
            failureReason = $"invalid from junction index: {patch.FromJunctionIndex}";
            return false;
        }

        var rebuilt = BuildPatchedJunctions(cache, patch, out failureReason);
        if (rebuilt == null)
        {
            return false;
        }

        cache.JunctionSegments = rebuilt;
        cache.CurrentJunctionIndex = patch.FromJunctionIndex;
        cache.CurrentResourceIndex = patch.FromResourceIndex;
        cache.ActivePatchId = patch.PatchId;
        cache.LastDecisionCode = $"PatchApplied:{patch.StrategyCode}";
        cache.RouteMode = RouteModeCodes.Rejoining;
        cache.ConsecutiveConflictFailCount = 0;
        cache.BlockedReason = null;
        cache.PlanVersion += 1;

        return true;
    }

    /// <summary>
    /// 构建补丁应用后的路口分段列表。
    /// </summary>
    private static List<VdaJunctionSegmentCache>? BuildPatchedJunctions(
        VdaSegmentedPathCache cache,
        TailPatch patch,
        out string failureReason)
    {
        failureReason = string.Empty;
        var result = new List<VdaJunctionSegmentCache>();

        for (var j = 0; j < patch.FromJunctionIndex && j < cache.JunctionSegments.Count; j++)
        {
            result.Add(CloneJunction(cache.JunctionSegments[j]));
        }

        var hasCurrentJunction = patch.FromJunctionIndex < cache.JunctionSegments.Count;
        List<VdaSegmentCacheItem> keptResources = new();

        if (hasCurrentJunction)
        {
            var current = cache.JunctionSegments[patch.FromJunctionIndex];
            if (patch.FromResourceIndex < 0 || patch.FromResourceIndex > current.ResourceSegments.Count)
            {
                failureReason = $"invalid from resource index: {patch.FromResourceIndex}";
                return null;
            }

            keptResources = current.ResourceSegments
                .Take(patch.FromResourceIndex)
                .Select(CloneResource)
                .ToList();
        }

        var patchJunctions = ConvertPatchTailToCache(patch.NewTail, patch.StrategyCode);
        if (patchJunctions.Count == 0)
        {
            failureReason = "converted patch junction list is empty";
            return null;
        }

        if (keptResources.Count > 0)
        {
            var first = patchJunctions[0];
            var merged = new VdaJunctionSegmentCache
            {
                ResourceSegments = new List<VdaSegmentCacheItem>()
            };

            merged.ResourceSegments.AddRange(keptResources);
            merged.ResourceSegments.AddRange(first.ResourceSegments);
            result.Add(merged);

            result.AddRange(patchJunctions.Skip(1));
        }
        else
        {
            result.AddRange(patchJunctions);
        }

        return result;
    }

    /// <summary>
    /// 将补丁尾段结构转换为缓存结构。
    /// </summary>
    private static List<VdaJunctionSegmentCache> ConvertPatchTailToCache(
        List<List<List<PathSegmentWithCode>>> newTail,
        string strategyCode)
    {
        var result = new List<VdaJunctionSegmentCache>();
        var segmentOrigin = string.Equals(strategyCode, "TrimProgressAlign", StringComparison.Ordinal)
            ? "Merged"
            : "Avoidance";

        foreach (var junction in newTail)
        {
            var cacheJunction = new VdaJunctionSegmentCache
            {
                ResourceSegments = new List<VdaSegmentCacheItem>()
            };

            foreach (var resource in junction)
            {
                if (resource.Count == 0) continue;

                cacheJunction.ResourceSegments.Add(new VdaSegmentCacheItem
                {
                    IsSent = false,
                    SegmentOrigin = segmentOrigin,
                    Segments = resource.Select(CloneSegment).ToList()
                });
            }

            if (cacheJunction.ResourceSegments.Count > 0)
            {
                result.Add(cacheJunction);
            }
        }

        return result;
    }

    /// <summary>
    /// 深拷贝路口分段。
    /// </summary>
    private static VdaJunctionSegmentCache CloneJunction(VdaJunctionSegmentCache junction)
    {
        return new VdaJunctionSegmentCache
        {
            ResourceSegments = junction.ResourceSegments.Select(CloneResource).ToList()
        };
    }

    /// <summary>
    /// 深拷贝资源分段。
    /// </summary>
    private static VdaSegmentCacheItem CloneResource(VdaSegmentCacheItem resource)
    {
        return new VdaSegmentCacheItem
        {
            IsSent = resource.IsSent,
            SegmentOrigin = resource.SegmentOrigin,
            Segments = resource.Segments.Select(CloneSegment).ToList(),
            EndNodeNetActionContextIds = new List<string>(resource.EndNodeNetActionContextIds),
            StartNodeNetActionContextIds = new List<string>(resource.StartNodeNetActionContextIds),
            IsEndNodeNetActionExecuted = resource.IsEndNodeNetActionExecuted,
            IsStartNodeNetActionExecuted = resource.IsStartNodeNetActionExecuted
        };
    }

    /// <summary>
    /// 深拷贝原子路径段。
    /// </summary>
    private static PathSegmentWithCode CloneSegment(PathSegmentWithCode segment)
    {
        return new PathSegmentWithCode
        {
            EdgeId = segment.EdgeId,
            FromNodeId = segment.FromNodeId,
            ToNodeId = segment.ToNodeId,
            Angle = segment.Angle,
            Length = segment.Length,
            MaxSpeed = segment.MaxSpeed,
            RequiredAngle = segment.RequiredAngle,
            StartTheta = segment.StartTheta,
            EndTheta = segment.EndTheta,
            EdgeCode = segment.EdgeCode,
            FromNodeCode = segment.FromNodeCode,
            ToNodeCode = segment.ToNodeCode,
            ForkAngleOffset = segment.ForkAngleOffset,
            StartNodeActions = new List<StepAction>(segment.StartNodeActions),
            StartNodeNetActionTypes = new Dictionary<Guid, ActionType>(segment.StartNodeNetActionTypes),
            EndNodeActions = new List<StepAction>(segment.EndNodeActions),
            EndNodeNetActionTypes = new Dictionary<Guid, ActionType>(segment.EndNodeNetActionTypes)
        };
    }
}