< Summary

Class:InterceptorBase
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Interceptors/InterceptorBase.cs
Covered lines:66
Uncovered lines:157
Coverable lines:223
Total lines:410
Line coverage:29.5% (66 of 223)
Covered branches:0
Total branches:0
Covered methods:19
Total methods:36
Method coverage:52.7% (19 of 36)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
InterceptorBase()0%110100%
Start()0%110100%
FixedUpdate()0%25.3710046.43%
OnDestroy()0%220100%
UpdateAgentConfig()0%69.7619048%
OnDrawGizmos()0%20400%
OnTriggerEnter(...)0%41.4410032%
RegisterMiss(...)0%2100%
RegisterDestroyed(...)0%2100%
RegisterMessageReceived(...)0%20400%
AssignSubInterceptor(...)0%42600%
EvaluateReassignedTarget(...)0%42600%
ReassignTarget(...)0%6200%
RequestTargetReassignment(...)0%20400%
RequestReassignment(...)0%2.52050%
UnassignedTargetsManager()0%119.441209.3%
SendAssignTargetRequest(...)0%2100%
SendAssignTargetResponse(...)0%2100%
SendReassignTargetRequest(...)0%2100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections;
 3using System.Collections.Generic;
 4using System.Linq;
 5using UnityEngine;
 6
 7// Base implementation of an interceptor.
 8public abstract class InterceptorBase : AgentBase, IInterceptor {
 9  public event Action<IInterceptor> OnHit;
 10  public event Action<IInterceptor> OnMiss;
 11  public event Action<IInterceptor> OnDestroyed;
 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
 20319  public IEscapeDetector EscapeDetector { get; set; }
 20
 1421  public CommsNode ParentCommsNode { get; set; }
 22
 23  // Maximum number of threats that this interceptor can target.
 24  [SerializeField]
 25  private int _capacity;
 26
 27  // Capacity of each sub-interceptor.
 28  [SerializeField]
 29  private int _capacityPerSubInterceptor;
 30
 31  // Number of sub-interceptors.
 32  [SerializeField]
 33  private int _numSubInterceptors;
 34
 35  // Number of sub-interceptors remaining that can be planned to launch.
 36  [SerializeField]
 37  private int _numSubInterceptorsPlannedRemaining;
 38
 39  // Number of sub-interceptors remaining.
 40  [SerializeField]
 41  private int _numSubInterceptorsRemaining;
 42
 43  public int Capacity {
 044    get => _capacity;
 45  protected
 7846    set { _capacity = value; }
 47  }
 48  public int CapacityPerSubInterceptor {
 049    get => _capacityPerSubInterceptor;
 50  protected
 7851    set { _capacityPerSubInterceptor = value; }
 52  }
 053  public virtual int CapacityPlannedRemaining => CapacityPerSubInterceptor *
 54                                                 NumSubInterceptorsPlannedRemaining;
 055  public virtual int CapacityRemaining => CapacityPerSubInterceptor * NumSubInterceptorsRemaining;
 56  public int NumSubInterceptors {
 22957    get => _numSubInterceptors;
 58  protected
 7859    set { _numSubInterceptors = value; }
 60  }
 61  public int NumSubInterceptorsPlannedRemaining {
 062    get => _numSubInterceptorsPlannedRemaining;
 63  protected
 60964    set { _numSubInterceptorsPlannedRemaining = value; }
 65  }
 66  public int NumSubInterceptorsRemaining {
 23167    get => _numSubInterceptorsRemaining;
 68  protected
 12069    set { _numSubInterceptorsRemaining = value; }
 70  }
 71
 72  // If true, the interceptor can be reassigned to other targets.
 073  public virtual bool IsReassignable => true;
 74
 75  // Set of unassigned targets for which an additional sub-interceptor should be launched.
 2876  private HashSet<IHierarchical> _unassignedTargets = new HashSet<IHierarchical>();
 77
 78  // Coroutine for handling unassigned targets.
 79  private Coroutine _unassignedTargetsCoroutine;
 80
 2681  protected override void Start() {
 2682    base.Start();
 2683    _unassignedTargetsCoroutine =
 84        StartCoroutine(UnassignedTargetsManager(_unassignedTargetsLaunchPeriod));
 2685    OnMiss += RegisterMiss;
 2686    OnDestroyed += RegisterDestroyed;
 2687    CommsNode.OnReceived += RegisterMessageReceived;
 2688  }
 89
 17790  protected override void FixedUpdate() {
 17791    base.FixedUpdate();
 92
 93    // Check whether the interceptor has a target. If not, request a new target from the parent
 94    // interceptor.
 95    // TODO(Joseph0120): Prevent duplicate re-assignment requests while waiting for a response.
 35496    if (HierarchicalAgent.Target == null || HierarchicalAgent.Target.IsTerminated) {
 17797      RequestReassignment(this);
 17798    }
 99
 100    // Check whether any targets are escaping from the interceptor.
 177101    if (EscapeDetector != null && HierarchicalAgent.Target != null &&
 0102        !HierarchicalAgent.Target.IsTerminated) {
 0103      List<IHierarchical> targetHierarchicals =
 104          HierarchicalAgent.Target.LeafHierarchicals(activeOnly: true, withTargetOnly: false);
 0105      List<IHierarchical> escapingTargets =
 106          targetHierarchicals.Where(EscapeDetector.IsEscaping).ToList();
 0107      foreach (var target in escapingTargets) {
 0108        SendReassignTargetRequest(target);
 0109      }
 0110      if (escapingTargets.Count == targetHierarchicals.Count) {
 0111        RequestReassignment(this);
 0112      }
 0113    }
 114
 115    // Update the planned number of sub-interceptors remaining.
 116    // TODO(titan): Update the planned number of sub-interceptors remaining when the number of leaf
 117    // hierarchical objects changes, such as when a new target is added.
 177118    List<IHierarchical> leafHierarchicals =
 119        HierarchicalAgent.LeafHierarchicals(activeOnly: false, withTargetOnly: false);
 177120    NumSubInterceptorsPlannedRemaining =
 121        Mathf.Min(NumSubInterceptorsRemaining, NumSubInterceptors - leafHierarchicals.Count);
 122
 123    // Navigate towards the target.
 177124    AccelerationInput = Controller?.Plan() ?? Vector3.zero;
 177125    Acceleration = Movement?.Act(AccelerationInput) ?? Vector3.zero;
 177126    _rigidbody.AddForce(Acceleration, ForceMode.Acceleration);
 177127  }
 128
 24129  protected override void OnDestroy() {
 24130    base.OnDestroy();
 131
 48132    if (_unassignedTargetsCoroutine != null) {
 24133      StopCoroutine(_unassignedTargetsCoroutine);
 24134      _unassignedTargetsCoroutine = null;
 24135    }
 24136  }
 137
 26138  protected override void UpdateAgentConfig() {
 26139    base.UpdateAgentConfig();
 140
 141    // Calculate the capacity.
 94142    int NumAgents(Configs.AgentConfig config) {
 146143      if (config == null || config.SubAgentConfig == null) {
 52144        return 1;
 145      }
 42146      return (int)config.SubAgentConfig.NumSubAgents * NumAgents(config.SubAgentConfig.AgentConfig);
 94147    }
 26148    Capacity = NumAgents(AgentConfig);
 26149    CapacityPerSubInterceptor = NumAgents(AgentConfig.SubAgentConfig?.AgentConfig);
 26150    NumSubInterceptors = (int)(AgentConfig.SubAgentConfig?.NumSubAgents ?? 0);
 26151    NumSubInterceptorsPlannedRemaining = NumSubInterceptors;
 26152    NumSubInterceptorsRemaining = NumSubInterceptors;
 153
 154    // Set the controller.
 26155    switch (AgentConfig.DynamicConfig?.GuidanceConfig?.ControllerType) {
 26156      case Configs.ControllerType.Static: {
 26157        Controller = new StaticController(this);
 26158        break;
 159      }
 0160      case Configs.ControllerType.ProportionalNavigation: {
 0161        Controller = new PnController(this, _proportionalNavigationGain);
 0162        break;
 163      }
 0164      case Configs.ControllerType.AugmentedProportionalNavigation: {
 0165        Controller = new ApnController(this, _proportionalNavigationGain);
 0166        break;
 167      }
 0168      case Configs.ControllerType.Waypoint: {
 0169        Controller = new WaypointController(this);
 0170        break;
 171      }
 0172      default: {
 0173        Debug.LogWarning(
 174            $"Controller type {AgentConfig.DynamicConfig?.GuidanceConfig?.ControllerType} not found.");
 0175        Controller = null;
 0176        break;
 177      }
 178    }
 26179  }
 180
 0181  protected override void OnDrawGizmos() {
 182    const float axisLength = 10f;
 183
 0184    base.OnDrawGizmos();
 185
 0186    if (Application.isPlaying) {
 187      // Target.
 0188      if (HierarchicalAgent.Target != null && !HierarchicalAgent.Target.IsTerminated) {
 0189        Gizmos.color = new Color(1, 1, 1, 0.15f);
 0190        Gizmos.DrawLine(Position, HierarchicalAgent.Target.Position);
 0191      }
 192
 193      // Up direction.
 0194      Gizmos.color = Color.yellow;
 0195      Gizmos.DrawRay(Position, Up * axisLength);
 196
 197      // Forward direction.
 0198      Gizmos.color = Color.blue;
 0199      Gizmos.DrawRay(Position, Forward * axisLength);
 200
 201      // Right direction.
 0202      Gizmos.color = Color.red;
 0203      Gizmos.DrawRay(Position, Right * axisLength);
 0204    }
 0205  }
 206
 207  // If the interceptor collides with the ground or another agent, it will be terminated. It is
 208  // possible for an interceptor to collide with another interceptor or with a non-target threat.
 209  // The interceptor records a hit only if it collides with a threat and destroys it with the
 210  // threat's kill probability.
 1803211  private void OnTriggerEnter(Collider other) {
 1803212    if (CheckGroundCollision(other)) {
 0213      OnDestroyed?.Invoke(this);
 0214      Terminate();
 0215    }
 216
 1803217    IAgent otherAgent = other.gameObject.GetComponentInParent<IAgent>();
 3586218    if (ShouldIgnoreCollision(otherAgent)) {
 1783219      return;
 220    }
 221    // Check if the collision is with a threat.
 20222    if (otherAgent is IThreat threat) {
 223      // Check the kill probability.
 0224      float killProbability = threat.StaticConfig.HitConfig?.KillProbability ?? 1;
 0225      bool isHit = UnityEngine.Random.value <= killProbability;
 0226      if (isHit) {
 0227        threat.HandleIntercept();
 0228        OnHit?.Invoke(this);
 0229        Terminate();
 0230      } else {
 0231        OnMiss?.Invoke(this);
 0232      }
 0233    }
 1803234  }
 235
 0236  private void RegisterMiss(IInterceptor interceptor) {
 0237    RequestTargetReassignment(interceptor);
 238
 239    // Request a new target from the parent interceptor.
 0240  }
 241
 0242  private void RegisterDestroyed(IInterceptor interceptor) {
 0243    RequestTargetReassignment(interceptor);
 0244  }
 245
 0246  private void RegisterMessageReceived(Message message) {
 0247    switch (message) {
 248      case AssignTargetRequestMessage request:
 0249        AssignSubInterceptor(request.PayloadData.SubInterceptor);
 0250        break;
 251      case AssignTargetResponseMessage response:
 252        // If the re-assigned target was not accepted, the fixed update loop will request another
 253        // target.
 0254        EvaluateReassignedTarget(response.PayloadData.Target);
 0255        break;
 256      case ReassignTargetRequestMessage request:
 0257        ReassignTarget(request.PayloadData.Target);
 0258        break;
 259      default:
 0260        Debug.LogWarning($"Message type {message.Type} is not valid.");
 0261        break;
 262    }
 0263  }
 264
 0265  private void AssignSubInterceptor(IInterceptor subInterceptor) {
 0266    if (subInterceptor == null || subInterceptor.IsTerminated ||
 0267        subInterceptor.CapacityRemaining <= 0) {
 0268      return;
 269    }
 270
 271    // Find a new target for the sub-interceptor within the parent interceptor's assigned targets.
 0272    IHierarchical target = HierarchicalAgent.FindNewTarget(subInterceptor.HierarchicalAgent,
 273                                                           subInterceptor.CapacityRemaining);
 0274    if (target != null && !target.IsTerminated) {
 0275      SendAssignTargetResponse(subInterceptor, target);
 0276      return;
 277    }
 278
 279    // Propagate the sub-interceptor target assignment to the parent interceptor above.
 0280    SendAssignTargetRequest(subInterceptor);
 0281  }
 282
 283  // Evaluate whether the interceptor should be reassigned to the new target.
 0284  private void EvaluateReassignedTarget(IHierarchical target) {
 0285    if (target == null || target.IsTerminated) {
 0286      return;
 287    }
 288
 289    // If the interceptor has no target, always accept the new target.
 0290    if (HierarchicalAgent.Target == null || HierarchicalAgent.Target.IsTerminated) {
 0291      HierarchicalAgent.Target = target;
 0292      return;
 293    }
 294
 295    // Accept the new target if the intercept speed is higher.
 0296    float currentFractionalSpeed =
 297        FractionalSpeed.Calculate(this, HierarchicalAgent.Target.Position);
 0298    float newFractionalSpeed = FractionalSpeed.Calculate(this, target.Position);
 0299    if (newFractionalSpeed > currentFractionalSpeed) {
 0300      HierarchicalAgent.Target = target;
 0301    }
 0302  }
 303
 0304  private void ReassignTarget(IHierarchical target) {
 305    // If a target needs to be re-assigned, the interceptor should in the following order:
 306    //  1. Queue up the unassigned targets in preparation of launching an additional
 307    //  sub-interceptor.
 308    //  2. If no existing sub-interceptor has been assigned to pursue the queued target(s), launch
 309    //  another sub-interceptor(s) to pursue the target(s).
 310    //  3. Propagate the target re-assignment to the parent interceptor above.
 0311    if (CapacityPlannedRemaining <= 0) {
 0312      SendReassignTargetRequest(target);
 0313      return;
 314    }
 315
 0316    _unassignedTargets.Add(target);
 0317  }
 318
 0319  private void RequestTargetReassignment(IInterceptor interceptor) {
 320    // Request the parent interceptor to re-assign the target to another interceptor if there are no
 321    // other pursuers.
 0322    IHierarchical target = interceptor.HierarchicalAgent.Target;
 0323    if (target == null || target.IsTerminated) {
 0324      return;
 325    }
 0326    List<IHierarchical> targetHierarchicals =
 327        target.LeafHierarchicals(activeOnly: true, withTargetOnly: false);
 0328    foreach (var targetHierarchical in targetHierarchicals) {
 0329      SendReassignTargetRequest(targetHierarchical);
 0330    }
 331
 0332    RequestReassignment(interceptor);
 0333  }
 334
 177335  private void RequestReassignment(IInterceptor interceptor) {
 177336    if (interceptor.IsReassignable) {
 337      // Request a new target from the parent interceptor.
 0338      SendAssignTargetRequest(interceptor);
 0339    }
 177340  }
 341
 26342  private IEnumerator UnassignedTargetsManager(float period) {
 52343    while (true) {
 109344      yield return new WaitUntil(() => _unassignedTargets.Count > 0);
 0345      yield return new WaitForSeconds(period);
 346
 0347      IEnumerable<IHierarchical> unassignedTargets = _unassignedTargets.ToList();
 0348      _unassignedTargets.Clear();
 349
 350      // Check whether the unassigned targets are still unassigned or are escaping the assigned
 351      // pursuers.
 0352      var filteredTargets =
 353          unassignedTargets
 0354              .Where(target => !target.IsTerminated && target.ActivePursuers.All(pursuer => {
 0355                var pursuerAgent = pursuer as HierarchicalAgent;
 0356                var interceptor = pursuerAgent?.Agent as IInterceptor;
 0357                return interceptor == null || interceptor.CapacityRemaining == 0 ||
 358                       (interceptor.EscapeDetector?.IsEscaping(target) ?? true);
 0359              }))
 360              .ToList();
 0361      if (filteredTargets.Count > CapacityPlannedRemaining) {
 362        // If there are more unassigned targets than the capacity remaining, propagate the target
 363        // re-assignment to the parent interceptor for the excess targets.
 0364        var orderedTargets =
 0365            filteredTargets.OrderBy(target => Vector3.Distance(Position, target.Position));
 0366        var excessTargets = orderedTargets.Skip(CapacityPlannedRemaining);
 0367        foreach (var target in excessTargets) {
 0368          SendReassignTargetRequest(target);
 0369        }
 0370        unassignedTargets = orderedTargets.Take(CapacityPlannedRemaining);
 0371      } else {
 0372        unassignedTargets = filteredTargets;
 0373      }
 0374      if (!unassignedTargets.Any()) {
 0375        continue;
 376      }
 377
 378      // Create a new hierarchical object with the cluster of unassigned targets as the target.
 0379      var newTargetSubHierarchical = new HierarchicalBase();
 0380      int numUnassignedTargets = 0;
 0381      foreach (var target in unassignedTargets) {
 0382        newTargetSubHierarchical.AddSubHierarchical(target);
 0383        ++numUnassignedTargets;
 0384      }
 0385      var newSubHierarchical = new HierarchicalBase { Target = newTargetSubHierarchical };
 0386      HierarchicalAgent.AddSubHierarchical(newSubHierarchical);
 0387      Debug.Log($"Reclustered {numUnassignedTargets} target(s) into a new cluster for {this}.");
 0388      UIManager.Instance.LogActionMessage(
 389          $"[IADS] Reclustered {numUnassignedTargets} target(s) into a new cluster for {this}.");
 390
 391      // Recursively cluster the newly assigned targets.
 0392      newSubHierarchical.RecursiveCluster(maxClusterSize: CapacityPerSubInterceptor);
 0393    }
 394  }
 395
 0396  private void SendAssignTargetRequest(IInterceptor subInterceptor) {
 0397    CommsManager.Instance.SendMessage(
 398        new AssignTargetRequestMessage(CommsNode, ParentCommsNode, subInterceptor));
 0399  }
 400
 0401  private void SendAssignTargetResponse(IInterceptor subInterceptor, IHierarchical target) {
 0402    CommsManager.Instance.SendMessage(
 403        new AssignTargetResponseMessage(CommsNode, subInterceptor.CommsNode, target));
 0404  }
 405
 0406  private void SendReassignTargetRequest(IHierarchical target) {
 0407    CommsManager.Instance.SendMessage(
 408        new ReassignTargetRequestMessage(CommsNode, ParentCommsNode, target));
 0409  }
 410}