< Summary

Class:IADS
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/IADS/IADS.cs
Covered lines:97
Uncovered lines:184
Coverable lines:281
Total lines:466
Line coverage:34.5% (97 of 281)
Covered branches:0
Total branches:0
Covered methods:17
Total methods:31
Method coverage:54.8% (17 of 31)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
IADS()0%110100%
Awake()0%2.152066.67%
OnDestroy()0%20400%
Start()0%110100%
LateUpdate()0%330100%
RegisterSimulationStarted()0%110100%
LaunchInterceptorsManager()0%3.043083.33%
CheckAndLaunchInterceptors()0%46.458015.62%
ShouldLaunchSubmunitions(...)0%42600%
HasClusterAssignment(...)0%2100%
CleanupDestroyedClusters()0%9.065045.45%
ReleaseInterceptorFromCluster(...)0%2100%
AssignSubmunitionsToThreats(...)0%30500%
RequestAssignInterceptorToThreat(...)0%2100%
AssignInterceptorToThreat(...)0%25.467027.78%
RequestClusterThreat(...)0%110100%
CheckForEscapingThreatsManager()0%3.333066.67%
CheckForEscapingThreats()0%42600%
ClusterThreatsManager()0%3.043083.33%
ClusterThreats()0%5.025090%
RegisterNewThreat(...)0%110100%
RegisterNewInterceptor(...)0%2100%
RegisterInterceptorHit(...)0%30500%
RegisterInterceptorMiss(...)0%20400%
RegisterThreatHit(...)0%12300%
RegisterThreatMiss(...)0%12300%
GetThreatTracks()0%2100%
GetInterceptorTracks()0%2100%
RegisterSimulationEnded()0%110100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections;
 3using System.Collections.Generic;
 4using System.IO;
 5using System.Linq;
 6using UnityEngine;
 7
 8// Integrated Air Defense System.
 9public class IADS : MonoBehaviour {
 210  public static IADS Instance { get; private set; }
 11
 12  private const float LaunchInterceptorsPeriod = 0.4f;
 13  private const float CheckForEscapingThreatsPeriod = 5.0f;
 14  private const float ClusterThreatsPeriod = 2.0f;
 15
 16  // TODO(titan): Choose the CSV file based on the interceptor type.
 117  private ILaunchAnglePlanner _launchAnglePlanner =
 18      new LaunchAngleCsvInterpolator(Path.Combine("Planning", "hydra70_launch_angle.csv"));
 119  private IAssignment _assignmentScheme = new MaxSpeedAssignment();
 20  private Coroutine _launchInterceptorsCoroutine;
 21
 22  [SerializeField]
 123  private List<TrackFileData> _trackFiles = new List<TrackFileData>();
 124  private Dictionary<Agent, TrackFileData> _trackFileMap = new Dictionary<Agent, TrackFileData>();
 25
 126  private List<Interceptor> _assignmentQueue = new List<Interceptor>();
 127  private List<Cluster> _threatClusters = new List<Cluster>();
 128  private Dictionary<Cluster, ThreatClusterData> _threatClusterMap =
 29      new Dictionary<Cluster, ThreatClusterData>();
 130  private Dictionary<Interceptor, Cluster> _interceptorClusterMap =
 31      new Dictionary<Interceptor, Cluster>();
 132  private HashSet<Interceptor> _assignableInterceptors = new HashSet<Interceptor>();
 33
 134  private HashSet<Threat> _threatsToCluster = new HashSet<Threat>();
 35  private Coroutine _checkForEscapingThreatsCoroutine;
 36  private Coroutine _clusterThreatsCoroutine;
 37
 138  private int _trackFileIdTicker = 0;
 39
 140  private void Awake() {
 241    if (Instance == null) {
 142      Instance = this;
 143    } else {
 044      Destroy(gameObject);
 045    }
 146  }
 47
 048  private void OnDestroy() {
 049    if (_launchInterceptorsCoroutine != null) {
 050      StopCoroutine(_launchInterceptorsCoroutine);
 051    }
 052    if (_checkForEscapingThreatsCoroutine != null) {
 053      StopCoroutine(_checkForEscapingThreatsCoroutine);
 054    }
 055    if (_clusterThreatsCoroutine != null) {
 056      StopCoroutine(_clusterThreatsCoroutine);
 057    }
 058  }
 59
 160  public void Start() {
 161    SimManager.Instance.OnSimulationStarted += RegisterSimulationStarted;
 162    SimManager.Instance.OnSimulationEnded += RegisterSimulationEnded;
 163    SimManager.Instance.OnNewThreat += RegisterNewThreat;
 164    SimManager.Instance.OnNewInterceptor += RegisterNewInterceptor;
 165  }
 66
 2767  public void LateUpdate() {
 68    // Detach interceptors assigned to fully-destroyed clusters.
 2769    CleanupDestroyedClusters();
 70
 71    // Update the cluster centroids.
 149172    foreach (var cluster in _threatClusters) {
 47073      cluster.Recenter();
 47074      _threatClusterMap[cluster].UpdateCentroid();
 47075    }
 76
 77    // Assign any interceptors that are no longer assigned to any threat.
 2778    AssignInterceptorToThreat(
 079        _assignableInterceptors.Where(interceptor => !interceptor.IsTerminated()).ToList());
 2780  }
 81
 1082  private void RegisterSimulationStarted() {
 1083    _launchInterceptorsCoroutine =
 84        StartCoroutine(LaunchInterceptorsManager(LaunchInterceptorsPeriod));
 1085    _checkForEscapingThreatsCoroutine =
 86        StartCoroutine(CheckForEscapingThreatsManager(CheckForEscapingThreatsPeriod));
 1087    _clusterThreatsCoroutine = StartCoroutine(ClusterThreatsManager(ClusterThreatsPeriod));
 1088  }
 89
 1090  private IEnumerator LaunchInterceptorsManager(float period) {
 2091    while (true) {
 92      // Check whether an interceptor should be launched at a cluster and launch it.
 1093      CheckAndLaunchInterceptors();
 1094      yield return new WaitForSeconds(period);
 095    }
 96  }
 97
 1098  private void CheckAndLaunchInterceptors() {
 3099    foreach (var cluster in _threatClusters) {
 100      // Check whether an interceptor has already been assigned to the cluster.
 0101      if (_threatClusterMap[cluster].Status != ThreatClusterStatus.UNASSIGNED) {
 0102        continue;
 103      }
 104
 105      // Check whether all threats in the cluster have terminated.
 0106      bool allTerminated = cluster.Threats.All(threat => threat.IsTerminated());
 0107      if (allTerminated) {
 0108        continue;
 109      }
 110
 111      // Create a predictor to track the cluster's centroid.
 0112      IPredictor predictor = new LinearExtrapolator(_threatClusterMap[cluster].Centroid);
 113
 114      // Create a launch planner.
 0115      ILaunchPlanner planner = new IterativeLaunchPlanner(_launchAnglePlanner, predictor);
 0116      LaunchPlan plan = planner.Plan();
 117
 118      // Check whether an interceptor should be launched.
 0119      if (plan.ShouldLaunch) {
 0120        Debug.Log(
 121            $"Launching a carrier interceptor at an elevation of {plan.LaunchAngle} degrees to position {plan.InterceptP
 0122        UIManager.Instance.LogActionMessage(
 123            $"[IADS] Launching a carrier interceptor at an elevation of {plan.LaunchAngle} degrees to position {plan.Int
 124
 125        // Create a new interceptor.
 0126        Configs.AgentConfig config =
 127            SimManager.Instance.SimulationConfig.InterceptorSwarmConfigs.Count > 0
 128                ? SimManager.Instance.SimulationConfig.InterceptorSwarmConfigs[0].AgentConfig
 129                : null;
 0130        Simulation.State initialState = new Simulation.State();
 131
 132        // Set the initial position, which defaults to the origin.
 0133        initialState.Position = Coordinates3.ToProto(Vector3.zero);
 134
 135        // Set the initial velocity to point along the launch vector.
 0136        initialState.Velocity = Coordinates3.ToProto(plan.GetNormalizedLaunchVector() * 1e-3f);
 0137        Interceptor interceptor = SimManager.Instance.CreateInterceptor(config, initialState);
 138
 139        // Assign the interceptor to the cluster.
 0140        _interceptorClusterMap[interceptor] = cluster;
 0141        interceptor.AssignTarget(_threatClusterMap[cluster].Centroid);
 0142        _threatClusterMap[cluster].AssignInterceptor(interceptor);
 143
 144        // Create an interceptor swarm.
 0145        SimManager.Instance.AddInterceptorSwarm(new List<Agent> { interceptor as Agent });
 0146      }
 0147    }
 10148  }
 149
 0150  public bool ShouldLaunchSubmunitions(Interceptor carrier) {
 0151    if (!HasClusterAssignment(carrier)) {
 0152      return false;
 153    }
 154    // The carrier interceptor will spawn submunitions when any target is greater than 30 degrees
 155    // away from the carrier interceptor's current velocity or when any threat is within 500 meters
 156    // of the interceptor.
 157    const float SubmunitionSpawnMaxAngularDeviation = 30.0f;
 158    const float SubmunitionSpawnMinDistanceToThreat = 500.0f;
 159    const float SubmunitionSpawnMaxDistanceToThreat = 2000.0f;
 160    // TODO(titan): The prediction time should be a function of the submunition characteristic, such
 161    // as the boost time.
 162    const float SubmunitionSpawnPredictionTime = 0.6f;
 163
 0164    Cluster cluster = _interceptorClusterMap[carrier];
 0165    List<Threat> threats = cluster.Threats.ToList();
 0166    Vector3 carrierPosition = carrier.GetPosition();
 0167    Vector3 carrierVelocity = carrier.GetVelocity();
 0168    foreach (var threat in threats) {
 0169      IPredictor predictor = new LinearExtrapolator(threat);
 0170      PredictorState predictedState = predictor.Predict(SubmunitionSpawnPredictionTime);
 0171      Vector3 positionToPredictedThreat = predictedState.Position - carrierPosition;
 0172      float predictedDistanceToThreat = positionToPredictedThreat.magnitude;
 173
 174      // Check whether the distance to the threat is less than the minimum distance.
 0175      if (predictedDistanceToThreat < SubmunitionSpawnMinDistanceToThreat) {
 0176        return true;
 177      }
 178
 179      // Check whether the angular deviation exceeds the maximum angular deviation.
 0180      float distanceDeviation =
 181          (Vector3.ProjectOnPlane(positionToPredictedThreat, carrierVelocity)).magnitude;
 0182      float angularDeviation =
 183          Mathf.Asin(distanceDeviation / predictedDistanceToThreat) * Mathf.Rad2Deg;
 0184      if (angularDeviation > SubmunitionSpawnMaxAngularDeviation &&
 0185          predictedDistanceToThreat < SubmunitionSpawnMaxDistanceToThreat) {
 0186        return true;
 187      }
 0188    }
 0189    return false;
 0190  }
 191
 192  // True if an interceptor still has a valid cluster assignment.
 0193  private bool HasClusterAssignment(Interceptor interceptor) {
 0194    return _interceptorClusterMap.ContainsKey(interceptor);
 0195  }
 196
 197  // Releases interceptors from clusters that only contain destroyed threats.
 27198  private void CleanupDestroyedClusters() {
 1491199    foreach (Cluster cluster in _threatClusters) {
 940200      if (!cluster.IsFullyTerminated()) {
 470201        continue;
 202      }
 203
 0204      IReadOnlyList<Interceptor> assigned = _threatClusterMap[cluster].AssignedInterceptors;
 0205      if (assigned.Count == 0) {
 0206        continue;
 207      }
 208
 0209      foreach (Interceptor interceptor in assigned.ToList()) {
 0210        ReleaseInterceptorFromCluster(cluster, interceptor);
 0211      }
 0212    }
 27213  }
 214
 215  // Detach a single interceptor from a cluster and decide its next action.
 216  // All interceptors are queued for reassignment in the hierarchical architecture,
 217  // including carrier interceptors.
 0218  private void ReleaseInterceptorFromCluster(Cluster cluster, Interceptor interceptor) {
 0219    _threatClusterMap[cluster].RemoveInterceptor(interceptor);
 0220    _interceptorClusterMap.Remove(interceptor);
 221    // TODO(titan): During the hierarchical architecture overhaul/refactor,
 222    // we need to properly handle the assignment of carrier interceptors at this point.
 223    // Since they have been unassigned from their cluster, they will need to be queued for
 224    // a new cluster assignment. Right now, they will go for re-assignment but fail
 225    // since they return false for IsAssignable().
 0226    RequestAssignInterceptorToThreat(interceptor);
 0227  }
 228
 0229  public void AssignSubmunitionsToThreats(Interceptor carrier, List<Interceptor> interceptors) {
 230    // Assign threats to the submunitions.
 0231    Cluster cluster = _interceptorClusterMap[carrier];
 0232    List<Threat> threats = cluster.Threats.ToList();
 0233    IEnumerable<IAssignment.AssignmentItem> assignments =
 234        _assignmentScheme.Assign(interceptors, threats);
 235
 236    // Mark the cluster as delegated to submunitions.
 0237    _threatClusterMap[cluster].RemoveInterceptor(carrier, delegated: true);
 238
 239    // Apply the assignments to the submunitions.
 0240    foreach (var assignment in assignments) {
 0241      assignment.Interceptor.AssignTarget(assignment.Threat);
 0242    }
 243
 244    // Check whether any submunitions were not assigned to a threat.
 0245    foreach (var interceptor in interceptors) {
 0246      if (!interceptor.HasAssignedTarget()) {
 0247        RequestAssignInterceptorToThreat(interceptor);
 0248      }
 0249    }
 0250  }
 251
 0252  public void RequestAssignInterceptorToThreat(Interceptor interceptor) {
 0253    interceptor.UnassignTarget();
 0254    _assignableInterceptors.Add(interceptor);
 0255  }
 256
 27257  private void AssignInterceptorToThreat(in IReadOnlyList<Interceptor> interceptors) {
 54258    if (interceptors.Count == 0) {
 27259      return;
 260    }
 261
 262    // The threat originally assigned to the interceptor has been terminated, so assign another
 263    // threat to the interceptor.
 264
 265    // This pulls from all available track files, not from our previously assigned cluster.
 0266    List<Threat> threats = _trackFiles.Where(trackFile => trackFile.Agent is Threat)
 0267                               .Select(trackFile => trackFile.Agent as Threat)
 268                               .ToList();
 0269    if (threats.Count == 0) {
 0270      return;
 271    }
 272
 0273    IEnumerable<IAssignment.AssignmentItem> assignments =
 274        _assignmentScheme.Assign(interceptors, threats);
 275
 276    // Apply the assignments to the submunitions.
 0277    foreach (var assignment in assignments) {
 0278      assignment.Interceptor.AssignTarget(assignment.Threat);
 0279      _assignableInterceptors.Remove(assignment.Interceptor);
 0280    }
 27281  }
 282
 755283  public void RequestClusterThreat(Threat threat) {
 755284    _threatsToCluster.Add(threat);
 755285  }
 286
 10287  private IEnumerator CheckForEscapingThreatsManager(float period) {
 20288    while (true) {
 10289      yield return new WaitForSeconds(period);
 0290      CheckForEscapingThreats();
 0291    }
 292  }
 293
 0294  private void CheckForEscapingThreats() {
 0295    List<Threat> threats = _trackFiles
 0296                               .Where(trackFile => trackFile.Status == TrackStatus.ASSIGNED &&
 297                                                   trackFile.Agent is Threat)
 0298                               .Select(trackFile => trackFile.Agent as Threat)
 299                               .ToList();
 0300    if (threats.Count == 0) {
 0301      return;
 302    }
 303
 304    // Check whether the threats are escaping the pursuing interceptors.
 0305    foreach (var threat in threats) {
 0306      bool isEscaping = threat.AssignedInterceptors.All(interceptor => {
 0307        Vector3 interceptorPosition = interceptor.GetPosition();
 0308        Vector3 threatPosition = threat.GetPosition();
 309
 0310        float threatTimeToHit = (float)(threatPosition.magnitude / threat.GetSpeed());
 0311        float interceptorTimeToHit =
 312            (float)((threatPosition - interceptorPosition).magnitude / interceptor.GetSpeed());
 0313        return interceptorPosition.magnitude > threatPosition.magnitude ||
 314               threatTimeToHit < interceptorTimeToHit;
 0315      });
 0316      if (isEscaping) {
 0317        RequestClusterThreat(threat);
 0318      }
 0319    }
 0320  }
 321
 10322  private IEnumerator ClusterThreatsManager(float period) {
 20323    while (true) {
 10324      ClusterThreats();
 10325      yield return new WaitForSeconds(period);
 0326    }
 327  }
 328
 10329  private void ClusterThreats() {
 330    // Maximum number of threats per cluster.
 331    const int MaxSize = 7;
 332    // Maximum cluster radius in meters.
 333    const float MaxRadius = 500;
 334
 335    // Filter the threats.
 10336    List<Threat> threats =
 337        _threatsToCluster
 755338            .Where(threat => !threat.IsTerminated() && threat.AssignedInterceptors.Count == 0)
 339            .ToList();
 10340    if (threats.Count == 0) {
 0341      return;
 342    }
 343
 344    // Cluster threats.
 10345    IClusterer clusterer = new AgglomerativeClusterer(new List<Agent>(threats), MaxSize, MaxRadius);
 10346    clusterer.Cluster();
 10347    var clusters = clusterer.Clusters;
 10348    Debug.Log($"Clustered {threats.Count} threats into {clusters.Count} clusters.");
 10349    UIManager.Instance.LogActionMessage(
 350        $"[IADS] Clustered {threats.Count} threats into {clusters.Count} clusters.");
 351
 10352    _threatClusters = clusters.ToList();
 561353    foreach (var cluster in clusters) {
 177354      _threatClusterMap.Add(cluster, new ThreatClusterData(cluster));
 177355    }
 356
 10357    _threatsToCluster.Clear();
 10358  }
 359
 755360  public void RegisterNewThreat(Threat threat) {
 755361    string trackID = $"T{1000 + ++_trackFileIdTicker}";
 755362    ThreatData trackFile = new ThreatData(threat, trackID);
 755363    _trackFiles.Add(trackFile);
 755364    _trackFileMap.Add(threat, trackFile);
 755365    RequestClusterThreat(threat);
 366
 755367    threat.OnThreatHit += RegisterThreatHit;
 755368    threat.OnThreatMiss += RegisterThreatMiss;
 755369  }
 370
 0371  public void RegisterNewInterceptor(Interceptor interceptor) {
 0372    string trackID = $"I{2000 + ++_trackFileIdTicker}";
 0373    InterceptorData trackFile = new InterceptorData(interceptor, trackID);
 0374    _trackFiles.Add(trackFile);
 0375    _trackFileMap.Add(interceptor, trackFile);
 376
 0377    interceptor.OnInterceptMiss += RegisterInterceptorMiss;
 0378    interceptor.OnInterceptHit += RegisterInterceptorHit;
 0379  }
 380
 0381  private void RegisterInterceptorHit(Interceptor interceptor, Threat threat) {
 0382    var threatTrack = _trackFileMap[threat] as ThreatData;
 0383    var interceptorTrack = _trackFileMap[interceptor] as InterceptorData;
 384
 0385    if (threatTrack != null) {
 0386      threatTrack.RemoveInterceptor(interceptor);
 0387      threatTrack.MarkDestroyed();
 0388    }
 389
 0390    if (interceptorTrack != null) {
 0391      interceptorTrack.RemoveThreat(threat);
 0392      interceptorTrack.MarkDestroyed();
 0393    }
 394
 395    // Assign the interceptors to other threats.
 0396    foreach (var assignedInterceptor in threat.AssignedInterceptors.ToList()) {
 0397      if (assignedInterceptor is not CarrierInterceptor) {
 0398        RequestAssignInterceptorToThreat(assignedInterceptor as Interceptor);
 0399      }
 0400    }
 0401  }
 402
 0403  private void RegisterInterceptorMiss(Interceptor interceptor, Threat threat) {
 404    // Assign the interceptor to another threat.
 0405    RequestAssignInterceptorToThreat(interceptor);
 406
 0407    var threatTrack = _trackFileMap[threat] as ThreatData;
 0408    var interceptorTrack = _trackFileMap[interceptor] as InterceptorData;
 409
 0410    if (threatTrack != null) {
 0411      threatTrack.RemoveInterceptor(interceptor);
 412
 413      // Check if the threat is being targeted by at least one interceptor.
 0414      if (threatTrack.AssignedInterceptorCount == 0) {
 0415        RequestClusterThreat(threat);
 0416      }
 0417    }
 418
 0419    if (interceptorTrack != null) {
 0420      interceptorTrack.RemoveThreat(threat);
 0421      interceptorTrack.MarkDestroyed();
 0422    }
 0423  }
 424
 0425  private void RegisterThreatHit(Threat threat) {
 0426    var threatTrack = _trackFileMap[threat] as ThreatData;
 0427    if (threatTrack != null) {
 0428      threatTrack.MarkDestroyed();
 0429    }
 430
 431    // Re-assign the assigned interceptors to other threats.
 0432    foreach (var interceptor in threat.AssignedInterceptors.ToList()) {
 0433      RequestAssignInterceptorToThreat(interceptor as Interceptor);
 0434    }
 0435  }
 436
 0437  private void RegisterThreatMiss(Threat threat) {
 438    // The threat missed (e.g., it hit the floor).
 439    // Re-assign the assigned interceptors to other threats.
 0440    foreach (var interceptor in threat.AssignedInterceptors.ToList()) {
 0441      RequestAssignInterceptorToThreat(interceptor as Interceptor);
 0442    }
 443
 0444    var threatTrack = _trackFileMap[threat] as ThreatData;
 0445    if (threatTrack != null) {
 0446      threatTrack.MarkDestroyed();
 0447    }
 0448  }
 449
 0450  public List<ThreatData> GetThreatTracks() => _trackFiles.OfType<ThreatData>().ToList();
 451
 452  public List<InterceptorData> GetInterceptorTracks() =>
 0453      _trackFiles.OfType<InterceptorData>().ToList();
 454
 9455  private void RegisterSimulationEnded() {
 9456    _trackFiles.Clear();
 9457    _trackFileMap.Clear();
 9458    _assignmentQueue.Clear();
 9459    _threatClusters.Clear();
 9460    _threatClusterMap.Clear();
 9461    _interceptorClusterMap.Clear();
 9462    _assignableInterceptors.Clear();
 9463    _threatsToCluster.Clear();
 9464    _trackFileIdTicker = 0;
 9465  }
 466}