< Summary

Class:InterceptorBase
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Interceptors/InterceptorBase.cs
Covered lines:0
Uncovered lines:168
Coverable lines:168
Total lines:318
Line coverage:0% (0 of 168)
Covered branches:0
Total branches:0
Covered methods:0
Total methods:25
Method coverage:0% (0 of 25)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
InterceptorBase()0%2100%
AssignSubInterceptor(...)0%20400%
ReassignTarget(...)0%12300%
Start()0%2100%
FixedUpdate()0%1821300%
OnDestroy()0%6200%
UpdateAgentConfig()0%2721600%
OnDrawGizmos()0%20400%
OnTriggerEnter(...)0%1101000%
RegisterMiss(...)0%42600%
UnassignedTargetsManager()0%1821300%

File(s)

/github/workspace/Assets/Scripts/Interceptors/InterceptorBase.cs

#LineLine coverage
 1using System.Collections;
 2using System.Collections.Generic;
 3using System.Linq;
 4using UnityEngine;
 5
 6// Base implementation of an interceptor.
 7public abstract class InterceptorBase : AgentBase, IInterceptor {
 8  public event InterceptHitMissEventHandler OnHit;
 9  public event InterceptHitMissEventHandler OnMiss;
 10  public event InterceptorAssignEventHandler OnAssignSubInterceptor;
 11  public event TargetReassignEventHandler OnReassignTarget;
 12
 13  // Default proportional navigation controller gain.
 14  private const float _proportionalNavigationGain = 5f;
 15
 16  // Time to accumulate unassigned targets before launching additional sub-interceptors.
 17  private const float _unassignedTargetsLaunchPeriod = 2.5f;
 18
 019  public IEscapeDetector EscapeDetector { get; set; }
 20
 21  // Maximum number of threats that this interceptor can target.
 22  [SerializeField]
 23  private int _capacity;
 24
 25  // Capacity of each sub-interceptor.
 26  [SerializeField]
 27  private int _capacityPerSubInterceptor;
 28
 29  // Number of sub-interceptors.
 30  [SerializeField]
 31  private int _numSubInterceptors;
 32
 33  // Number of sub-interceptors remaining that can be planned to launch.
 34  [SerializeField]
 35  private int _numSubInterceptorsPlannedRemaining;
 36
 37  // Number of sub-interceptors remaining.
 38  [SerializeField]
 39  private int _numSubInterceptorsRemaining;
 40
 41  public int Capacity {
 042    get => _capacity;
 43  protected
 044    set { _capacity = value; }
 45  }
 46  public int CapacityPerSubInterceptor {
 047    get => _capacityPerSubInterceptor;
 48  protected
 049    set { _capacityPerSubInterceptor = value; }
 50  }
 051  public virtual int CapacityPlannedRemaining => CapacityPerSubInterceptor *
 52                                                 NumSubInterceptorsPlannedRemaining;
 053  public virtual int CapacityRemaining => CapacityPerSubInterceptor * NumSubInterceptorsRemaining;
 54  public int NumSubInterceptors {
 055    get => _numSubInterceptors;
 56  protected
 057    set { _numSubInterceptors = value; }
 58  }
 59  public int NumSubInterceptorsPlannedRemaining {
 060    get => _numSubInterceptorsPlannedRemaining;
 61  protected
 062    set { _numSubInterceptorsPlannedRemaining = value; }
 63  }
 64  public int NumSubInterceptorsRemaining {
 065    get => _numSubInterceptorsRemaining;
 66  protected
 067    set { _numSubInterceptorsRemaining = value; }
 68  }
 69
 70  // Set of unassigned targets for which an additional sub-interceptor should be launched.
 071  private HashSet<IHierarchical> _unassignedTargets = new HashSet<IHierarchical>();
 72
 73  // Coroutine for handling unassigned targets.
 74  private Coroutine _unassignedTargetsCoroutine;
 75
 076  public void AssignSubInterceptor(IInterceptor subInterceptor) {
 077    if (subInterceptor.CapacityRemaining <= 0) {
 078      return;
 79    }
 80
 81    // Assign a new target to the sub-interceptor within the parent interceptor's assigned targets.
 082    if (!HierarchicalAgent.AssignNewTarget(subInterceptor.HierarchicalAgent,
 083                                           subInterceptor.CapacityRemaining)) {
 84      // Propagate the sub-interceptor target assignment to the parent interceptor above.
 085      OnAssignSubInterceptor?.Invoke(subInterceptor);
 086    }
 087  }
 88
 089  public void ReassignTarget(IHierarchical target) {
 90    // If a target needs to be re-assigned, the interceptor should in the following order:
 91    //  1. Queue up the unassigned targets in preparation of launching an additional
 92    //  sub-interceptor.
 93    //  2. If no existing sub-interceptor has been assigned to pursue the queued target(s), launch
 94    //  another sub-interceptor(s) to pursue the target(s).
 95    //  3. Propagate the target re-assignment to the parent interceptor above.
 096    if (CapacityPlannedRemaining <= 0) {
 097      OnReassignTarget?.Invoke(target);
 098      return;
 99    }
 100
 0101    _unassignedTargets.Add(target);
 0102  }
 103
 0104  protected override void Start() {
 0105    base.Start();
 0106    _unassignedTargetsCoroutine =
 107        StartCoroutine(UnassignedTargetsManager(_unassignedTargetsLaunchPeriod));
 0108    OnMiss += RegisterMiss;
 0109  }
 110
 0111  protected override void FixedUpdate() {
 0112    base.FixedUpdate();
 113
 114    // Check whether the interceptor has a target. If not, request a new target from the parent
 115    // interceptor.
 0116    if (HierarchicalAgent.Target == null || HierarchicalAgent.Target.IsTerminated) {
 0117      OnAssignSubInterceptor?.Invoke(this);
 0118    }
 119
 120    // Check whether any targets are escaping from the interceptor.
 0121    if (EscapeDetector != null && HierarchicalAgent.Target != null &&
 0122        !HierarchicalAgent.Target.IsTerminated) {
 0123      List<IHierarchical> targetHierarchicals =
 124          HierarchicalAgent.Target.LeafHierarchicals(activeOnly: true, withTargetOnly: false);
 0125      List<IHierarchical> escapingTargets =
 126          targetHierarchicals.Where(EscapeDetector.IsEscaping).ToList();
 0127      foreach (var target in escapingTargets) {
 0128        OnReassignTarget?.Invoke(target);
 0129      }
 0130      if (escapingTargets.Count == targetHierarchicals.Count) {
 0131        OnAssignSubInterceptor?.Invoke(this);
 0132      }
 0133    }
 134
 135    // Update the planned number of sub-interceptors remaining.
 136    // TODO(titan): Update the planned number of sub-interceptors remaining when the number of leaf
 137    // hierarchical objects changes, such as when a new target is added.
 0138    List<IHierarchical> leafHierarchicals =
 139        HierarchicalAgent.LeafHierarchicals(activeOnly: false, withTargetOnly: false);
 0140    NumSubInterceptorsPlannedRemaining =
 141        Mathf.Min(NumSubInterceptorsRemaining, NumSubInterceptors - leafHierarchicals.Count);
 142
 143    // Navigate towards the target.
 0144    AccelerationInput = Controller?.Plan() ?? Vector3.zero;
 0145    Acceleration = Movement?.Act(AccelerationInput) ?? Vector3.zero;
 0146    _rigidbody.AddForce(Acceleration, ForceMode.Acceleration);
 0147  }
 148
 0149  protected override void OnDestroy() {
 0150    base.OnDestroy();
 151
 0152    if (_unassignedTargetsCoroutine != null) {
 0153      StopCoroutine(_unassignedTargetsCoroutine);
 0154      _unassignedTargetsCoroutine = null;
 0155    }
 0156  }
 157
 0158  protected override void UpdateAgentConfig() {
 0159    base.UpdateAgentConfig();
 160
 161    // Calculate the capacity.
 0162    int NumAgents(Configs.AgentConfig config) {
 0163      if (config == null || config.SubAgentConfig == null) {
 0164        return 1;
 165      }
 0166      return (int)config.SubAgentConfig.NumSubAgents * NumAgents(config.SubAgentConfig.AgentConfig);
 0167    }
 0168    Capacity = NumAgents(AgentConfig);
 0169    CapacityPerSubInterceptor = NumAgents(AgentConfig.SubAgentConfig?.AgentConfig);
 0170    NumSubInterceptors = (int)(AgentConfig.SubAgentConfig?.NumSubAgents ?? 0);
 0171    NumSubInterceptorsPlannedRemaining = NumSubInterceptors;
 0172    NumSubInterceptorsRemaining = NumSubInterceptors;
 173
 174    // Set the controller.
 0175    switch (AgentConfig.DynamicConfig?.FlightConfig?.ControllerType) {
 0176      case Configs.ControllerType.ProportionalNavigation: {
 0177        Controller = new PnController(this, _proportionalNavigationGain);
 0178        break;
 179      }
 0180      case Configs.ControllerType.AugmentedProportionalNavigation: {
 0181        Controller = new ApnController(this, _proportionalNavigationGain);
 0182        break;
 183      }
 0184      default: {
 0185        Debug.LogWarning(
 186            $"Controller type {AgentConfig.DynamicConfig?.FlightConfig?.ControllerType} not found.");
 0187        Controller = null;
 0188        break;
 189      }
 190    }
 0191  }
 192
 0193  protected override void OnDrawGizmos() {
 194    const float axisLength = 10f;
 195
 0196    base.OnDrawGizmos();
 197
 0198    if (Application.isPlaying) {
 199      // Target.
 0200      if (HierarchicalAgent.Target != null && !HierarchicalAgent.Target.IsTerminated) {
 0201        Gizmos.color = new Color(1, 1, 1, 0.15f);
 0202        Gizmos.DrawLine(Position, HierarchicalAgent.Target.Position);
 0203      }
 204
 205      // Up direction.
 0206      Gizmos.color = Color.yellow;
 0207      Gizmos.DrawRay(Position, Up * axisLength);
 208
 209      // Forward direction.
 0210      Gizmos.color = Color.blue;
 0211      Gizmos.DrawRay(Position, Forward * axisLength);
 212
 213      // Right direction.
 0214      Gizmos.color = Color.red;
 0215      Gizmos.DrawRay(Position, Right * axisLength);
 0216    }
 0217  }
 218
 219  // If the interceptor collides with the ground or another agent, it will be terminated. It is
 220  // possible for an interceptor to collide with another interceptor or with a non-target threat.
 221  // The interceptor records a hit only if it collides with a threat and destroys it with the
 222  // threat's kill probability.
 0223  private void OnTriggerEnter(Collider other) {
 0224    if (CheckFloorCollision(other)) {
 0225      OnMiss?.Invoke(this);
 0226      Terminate();
 0227    }
 228
 0229    IAgent otherAgent = other.gameObject.GetComponentInParent<IAgent>();
 0230    if (ShouldIgnoreCollision(otherAgent)) {
 0231      return;
 232    }
 233    // Check if the collision is with a threat.
 0234    if (otherAgent is IThreat threat) {
 235      // Check the kill probability.
 0236      float killProbability = threat.StaticConfig.HitConfig?.KillProbability ?? 1;
 0237      bool isHit = Random.value <= killProbability;
 0238      if (isHit) {
 0239        threat.HandleIntercept();
 0240        OnHit?.Invoke(this);
 0241        Terminate();
 0242      } else {
 0243        OnMiss?.Invoke(this);
 0244      }
 0245    }
 0246  }
 247
 0248  private void RegisterMiss(IInterceptor interceptor) {
 249    // Request the parent interceptor to re-assign the target to another interceptor if there are no
 250    // other pursuers.
 0251    IHierarchical target = interceptor.HierarchicalAgent.Target;
 0252    if (target == null || target.IsTerminated) {
 0253      return;
 254    }
 0255    List<IHierarchical> targetHierarchicals =
 256        target.LeafHierarchicals(activeOnly: true, withTargetOnly: false);
 0257    foreach (var targetHierarchical in targetHierarchicals) {
 0258      OnReassignTarget?.Invoke(targetHierarchical);
 0259    }
 260
 261    // Request a new target from the parent interceptor.
 0262    OnAssignSubInterceptor?.Invoke(interceptor);
 0263  }
 264
 0265  private IEnumerator UnassignedTargetsManager(float period) {
 0266    while (true) {
 0267      yield return new WaitUntil(() => _unassignedTargets.Count > 0);
 0268      yield return new WaitForSeconds(period);
 269
 0270      IEnumerable<IHierarchical> unassignedTargets = _unassignedTargets.ToList();
 0271      _unassignedTargets.Clear();
 272
 273      // Check whether the unassigned targets are still unassigned or are escaping the assigned
 274      // pursuers.
 0275      var filteredTargets =
 276          unassignedTargets
 0277              .Where(target => !target.IsTerminated && target.ActivePursuers.All(pursuer => {
 0278                var pursuerAgent = pursuer as HierarchicalAgent;
 0279                var interceptor = pursuerAgent?.Agent as IInterceptor;
 0280                return interceptor == null || interceptor.CapacityRemaining == 0 ||
 281                       (interceptor.EscapeDetector?.IsEscaping(target) ?? true);
 0282              }))
 283              .ToList();
 0284      if (filteredTargets.Count > CapacityPlannedRemaining) {
 285        // If there are more unassigned targets than the capacity remaining, propagate the target
 286        // re-assignment to the parent interceptor for the excess targets.
 0287        var orderedTargets =
 0288            filteredTargets.OrderBy(target => Vector3.Distance(Position, target.Position));
 0289        var excessTargets = orderedTargets.Skip(CapacityPlannedRemaining);
 0290        foreach (var target in excessTargets) {
 0291          OnReassignTarget?.Invoke(target);
 0292        }
 0293        unassignedTargets = orderedTargets.Take(CapacityPlannedRemaining);
 0294      } else {
 0295        unassignedTargets = filteredTargets;
 0296      }
 0297      if (!unassignedTargets.Any()) {
 0298        continue;
 299      }
 300
 301      // Create a new hierarchical object with the cluster of unassigned targets as the target.
 0302      var newTargetSubHierarchical = new HierarchicalBase();
 0303      int numUnassignedTargets = 0;
 0304      foreach (var target in unassignedTargets) {
 0305        newTargetSubHierarchical.AddSubHierarchical(target);
 0306        ++numUnassignedTargets;
 0307      }
 0308      var newSubHierarchical = new HierarchicalBase { Target = newTargetSubHierarchical };
 0309      HierarchicalAgent.AddSubHierarchical(newSubHierarchical);
 0310      Debug.Log($"Reclustered {numUnassignedTargets} target(s) into a new cluster for {this}.");
 0311      UIManager.Instance.LogActionMessage(
 312          $"[IADS] Reclustered {numUnassignedTargets} target(s) into a new cluster for {this}.");
 313
 314      // Recursively cluster the newly assigned targets.
 0315      newSubHierarchical.RecursiveCluster(maxClusterSize: CapacityPerSubInterceptor);
 0316    }
 317  }
 318}