< Summary

Class:IADS
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/IADS/IADS.cs
Covered lines:49
Uncovered lines:67
Coverable lines:116
Total lines:192
Line coverage:42.2% (49 of 116)
Covered branches:0
Total branches:0
Covered methods:13
Total methods:20
Method coverage:65% (13 of 20)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
IADS()0%2100%
Awake()0%3.713057.14%
Start()0%110100%
OnDestroy()0%6200%
RegisterSimulationStarted()0%110100%
RegisterSimulationEnded()0%220100%
RegisterNewAsset(...)0%220100%
RegisterNewLauncher(...)0%220100%
RegisterNewThreat(...)0%220100%
RegisterMessageReceived(...)0%12300%
HierarchyManager()0%5.44055.56%
BuildHierarchy()0%30500%
AssignSubInterceptor(...)0%90900%
ReassignTarget(...)0%20400%

File(s)

/github/workspace/Assets/Scripts/IADS/IADS.cs

#LineLine coverage
 1using System.Collections;
 2using System.Collections.Generic;
 3using System.Linq;
 4using UnityEngine;
 5
 6// The Integrated Air Defense System (IADS) manages the air defense strategy.
 7// It implements the singleton pattern to ensure that only one instance exists.
 8public class IADS : MonoBehaviour, ICommsEndpoint {
 9  // Hierarchy parameters.
 10  private const float _hierarchyUpdatePeriod = 5f;
 11  private const float _coverageFactor = 1f;
 12
 13  // List of assets.
 014  private List<IHierarchical> _assets = new List<IHierarchical>();
 15
 16  // The IADS only manages the launchers at the top level of the interceptor hierarchy.
 017  private List<IHierarchical> _launchers = new List<IHierarchical>();
 18
 19  // Coroutine to perform the maintain the agent hierarchy.
 20  private Coroutine _hierarchyCoroutine;
 21
 22  // List of threats waiting to be incorporated into the hierarchy.
 023  private List<IHierarchical> _newThreats = new List<IHierarchical>();
 24
 95725  public static IADS Instance { get; private set; }
 26
 027  public IReadOnlyList<IHierarchical> Assets => _assets.AsReadOnly();
 28
 95529  public IReadOnlyList<IHierarchical> Launchers => _launchers.AsReadOnly();
 30
 1731  public CommsNode CommsNode { get; private set; }
 32
 133  private void Awake() {
 134    if (Instance != null && Instance != this) {
 035      Destroy(gameObject);
 036      return;
 37    }
 138    Instance = this;
 139  }
 40
 141  private void Start() {
 142    SimManager.Instance.OnSimulationStarted += RegisterSimulationStarted;
 143    SimManager.Instance.OnSimulationEnded += RegisterSimulationEnded;
 144    SimManager.Instance.OnNewAsset += RegisterNewAsset;
 145    SimManager.Instance.OnNewLauncher += RegisterNewLauncher;
 146    SimManager.Instance.OnNewThreat += RegisterNewThreat;
 47
 48    // Create a communication node for the IADS.
 149    CommsNode = new CommsNode(Configs.AgentType.Iads);
 150    CommsNode.OnReceived += RegisterMessageReceived;
 151    CommsManager.Instance.AddNode(CommsNode);
 152  }
 53
 054  private void OnDestroy() {
 055    if (_hierarchyCoroutine != null) {
 056      StopCoroutine(_hierarchyCoroutine);
 057      _hierarchyCoroutine = null;
 058    }
 059  }
 60
 1261  private void RegisterSimulationStarted() {
 1262    _hierarchyCoroutine = StartCoroutine(HierarchyManager(_hierarchyUpdatePeriod));
 1263  }
 64
 1165  private void RegisterSimulationEnded() {
 2266    if (_hierarchyCoroutine != null) {
 1167      StopCoroutine(_hierarchyCoroutine);
 1168      _hierarchyCoroutine = null;
 1169    }
 1170    _assets.Clear();
 1171    _launchers.Clear();
 1172    _newThreats.Clear();
 1173  }
 74
 2675  private void RegisterNewAsset(IInterceptor asset) {
 5276    if (asset.HierarchicalAgent != null) {
 2677      _assets.Add(asset.HierarchicalAgent);
 2678    }
 2679  }
 80
 1481  private void RegisterNewLauncher(IInterceptor launcher) {
 2882    if (launcher.HierarchicalAgent != null) {
 1483      launcher.ParentCommsNode = CommsNode;
 1484      _launchers.Add(launcher.HierarchicalAgent);
 1485    }
 1486  }
 87
 95588  private void RegisterNewThreat(IThreat threat) {
 191089    if (threat.HierarchicalAgent != null) {
 95590      _newThreats.Add(threat.HierarchicalAgent);
 95591    }
 95592  }
 93
 094  private void RegisterMessageReceived(Message message) {
 095    switch (message) {
 96      case AssignTargetRequestMessage request:
 097        AssignSubInterceptor(request.PayloadData.SubInterceptor);
 098        break;
 99      case ReassignTargetRequestMessage request:
 0100        ReassignTarget(request.PayloadData.Target);
 0101        break;
 102      default:
 0103        break;
 104    }
 0105  }
 106
 12107  private IEnumerator HierarchyManager(float period) {
 24108    while (true) {
 12109      if (_newThreats.Count != 0) {
 0110        BuildHierarchy();
 0111      }
 12112      yield return new WaitForSeconds(period);
 0113    }
 114  }
 115
 0116  private void BuildHierarchy() {
 0117    if (_newThreats.Count == 0 || _launchers.Count == 0) {
 0118      return;
 119    }
 120
 121    // TODO(titan): The clustering algorithm should be aware of the capacity of the launcher.
 0122    var swarmClusterer = new KMeansClusterer(Mathf.RoundToInt(_launchers.Count / _coverageFactor));
 0123    List<Cluster> swarms = swarmClusterer.Cluster(_newThreats);
 0124    _newThreats.Clear();
 125
 126    // Assign one swarm to each launcher.
 0127    var swarmToLauncherAssignment =
 128        new MinDistanceAssignment(Assignment.Assignment_EvenAssignment_Assign);
 0129    List<AssignmentItem> swarmToLauncherAssignments =
 130        swarmToLauncherAssignment.Assign(_launchers, swarms);
 0131    void AssignTarget(IHierarchical hierarchical, IHierarchical target) {
 0132      hierarchical.Target = target;
 0133      foreach (var subHierarchical in hierarchical.ActiveSubHierarchicals) {
 0134        AssignTarget(subHierarchical, target);
 0135      }
 0136    }
 0137    foreach (var assignment in swarmToLauncherAssignments) {
 138      // Assign the swarm as the target to the launcher.
 0139      assignment.First.Target = assignment.Second;
 140
 141      // Find the asset closest to each swarm and assign it as the target to all threats within the
 142      // swarm. If there are no assets, the threats will target the launcher.
 143      // TODO(titan): Move threat coordination into a separate module as the IADS should only manage
 144      // the defense strategy.
 0145      var closestAsset =
 0146          _assets.OrderBy(asset => Vector3.Distance(assignment.Second.Position, asset.Position))
 147              .FirstOrDefault();
 0148      AssignTarget(assignment.Second, closestAsset ?? assignment.First);
 0149    }
 0150  }
 151
 0152  private void AssignSubInterceptor(IInterceptor subInterceptor) {
 0153    if (subInterceptor == null || subInterceptor.IsTerminated ||
 0154        subInterceptor.CapacityRemaining <= 0) {
 0155      return;
 156    }
 157
 158    // Pass the sub-interceptor through all the launchers in order of increasing distance between
 159    // the sub-interceptor and the launcher's target.
 0160    var sortedLaunchers =
 0161        Launchers.Where(launcher => launcher.Target != null && !launcher.Target.IsTerminated)
 162            .OrderBy(launcher =>
 0163                         Vector3.Distance(subInterceptor.Position, launcher.Target.Position));
 0164    foreach (var launcher in sortedLaunchers) {
 0165      IHierarchical target = launcher.FindNewTarget(subInterceptor.HierarchicalAgent,
 166                                                    subInterceptor.CapacityRemaining);
 0167      if (target != null && !target.IsTerminated) {
 0168        CommsManager.Instance.SendMessage(
 169            new AssignTargetResponseMessage(CommsNode, subInterceptor.CommsNode, target));
 0170        break;
 171      }
 0172    }
 0173  }
 174
 0175  private void ReassignTarget(IHierarchical target) {
 176    // Assign the closest launcher with non-zero remaining capacity to pursue the target.
 0177    var closestLauncher =
 178        Launchers
 0179            .Select(launcher => new {
 180              Hierarchical = launcher,
 181              Interceptor = (launcher as HierarchicalAgent)?.Agent as IInterceptor,
 182            })
 0183            .Where(launcher => launcher.Interceptor?.CapacityPlannedRemaining > 0)
 0184            .OrderBy(launcher => Vector3.Distance(target.Position, launcher.Hierarchical.Position))
 185            .FirstOrDefault();
 0186    if (closestLauncher == null) {
 0187      return;
 188    }
 0189    CommsManager.Instance.SendMessage(
 190        new ReassignTargetRequestMessage(CommsNode, closestLauncher.Interceptor.CommsNode, target));
 0191  }
 192}