< Summary

Class:IADS
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/IADS/IADS.cs
Covered lines:91
Uncovered lines:167
Coverable lines:258
Total lines:421
Line coverage:35.2% (91 of 258)
Covered branches:0
Total branches:0
Covered methods:16
Total methods:28
Method coverage:57.1% (16 of 28)

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%27.636015.62%
ShouldLaunchSubmunitions(...)0%30500%
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    // Update the cluster centroids.
 145569    foreach (var cluster in _threatClusters) {
 45870      cluster.Recenter();
 45871      _threatClusterMap[cluster].UpdateCentroid();
 45872    }
 73
 74    // Assign any interceptors that are no longer assigned to any threat.
 2775    AssignInterceptorToThreat(
 076        _assignableInterceptors.Where(interceptor => !interceptor.HasTerminated()).ToList());
 2777  }
 78
 1079  private void RegisterSimulationStarted() {
 1080    _launchInterceptorsCoroutine =
 81        StartCoroutine(LaunchInterceptorsManager(LaunchInterceptorsPeriod));
 1082    _checkForEscapingThreatsCoroutine =
 83        StartCoroutine(CheckForEscapingThreatsManager(CheckForEscapingThreatsPeriod));
 1084    _clusterThreatsCoroutine = StartCoroutine(ClusterThreatsManager(ClusterThreatsPeriod));
 1085  }
 86
 1087  private IEnumerator LaunchInterceptorsManager(float period) {
 2088    while (true) {
 89      // Check whether an interceptor should be launched at a cluster and launch it.
 1090      CheckAndLaunchInterceptors();
 1091      yield return new WaitForSeconds(period);
 092    }
 93  }
 94
 1095  private void CheckAndLaunchInterceptors() {
 3096    foreach (var cluster in _threatClusters) {
 97      // Check whether an interceptor has already been assigned to the cluster.
 098      if (_threatClusterMap[cluster].Status != ThreatClusterStatus.UNASSIGNED) {
 099        continue;
 100      }
 101
 102      // Check whether all threats in the cluster have terminated.
 0103      bool allTerminated = cluster.Threats.All(threat => threat.IsTerminated());
 0104      if (allTerminated) {
 0105        continue;
 106      }
 107
 108      // Create a predictor to track the cluster's centroid.
 0109      IPredictor predictor = new LinearExtrapolator(_threatClusterMap[cluster].Centroid);
 110
 111      // Create a launch planner.
 0112      ILaunchPlanner planner = new IterativeLaunchPlanner(_launchAnglePlanner, predictor);
 0113      LaunchPlan plan = planner.Plan();
 114
 115      // Check whether an interceptor should be launched.
 0116      if (plan.ShouldLaunch) {
 0117        Debug.Log(
 118            $"Launching a carrier interceptor at an elevation of {plan.LaunchAngle} degrees to position {plan.InterceptP
 0119        UIManager.Instance.LogActionMessage(
 120            $"[IADS] Launching a carrier interceptor at an elevation of {plan.LaunchAngle} degrees to position {plan.Int
 121
 122        // Create a new interceptor.
 0123        DynamicAgentConfig config =
 124            SimManager.Instance.SimulationConfig.interceptor_swarm_configs[0].dynamic_agent_config;
 0125        InitialState initialState = new InitialState();
 126
 127        // Set the initial position, which defaults to the origin.
 0128        initialState.position = Vector3.zero;
 129
 130        // Set the initial velocity to point along the launch vector.
 0131        initialState.velocity = plan.GetNormalizedLaunchVector() * 1e-3f;
 0132        Interceptor interceptor = SimManager.Instance.CreateInterceptor(config, initialState);
 133
 134        // Assign the interceptor to the cluster.
 0135        _interceptorClusterMap[interceptor] = cluster;
 0136        interceptor.AssignTarget(_threatClusterMap[cluster].Centroid);
 0137        _threatClusterMap[cluster].AssignInterceptor(interceptor);
 138
 139        // Create an interceptor swarm.
 0140        SimManager.Instance.AddInterceptorSwarm(new List<Agent> { interceptor as Agent });
 0141      }
 0142    }
 10143  }
 144
 0145  public bool ShouldLaunchSubmunitions(Interceptor carrier) {
 146    // The carrier interceptor will spawn submunitions when any target is greater than 30 degrees
 147    // away from the carrier interceptor's current velocity or when any threat is within 500 meters
 148    // of the interceptor.
 149    const float SubmunitionSpawnMaxAngularDeviation = 30.0f;
 150    const float SubmunitionSpawnMinDistanceToThreat = 500.0f;
 151    const float SubmunitionSpawnMaxDistanceToThreat = 2000.0f;
 152    // TODO(titan): The prediction time should be a function of the submunition characteristic, such
 153    // as the boost time.
 154    const float SubmunitionSpawnPredictionTime = 0.6f;
 155
 0156    Cluster cluster = _interceptorClusterMap[carrier];
 0157    List<Threat> threats = cluster.Threats.ToList();
 0158    Vector3 carrierPosition = carrier.GetPosition();
 0159    Vector3 carrierVelocity = carrier.GetVelocity();
 0160    foreach (var threat in threats) {
 0161      IPredictor predictor = new LinearExtrapolator(threat);
 0162      PredictorState predictedState = predictor.Predict(SubmunitionSpawnPredictionTime);
 0163      Vector3 positionToPredictedThreat = predictedState.Position - carrierPosition;
 0164      float predictedDistanceToThreat = positionToPredictedThreat.magnitude;
 165
 166      // Check whether the distance to the threat is less than the minimum distance.
 0167      if (predictedDistanceToThreat < SubmunitionSpawnMinDistanceToThreat) {
 0168        return true;
 169      }
 170
 171      // Check whether the angular deviation exceeds the maximum angular deviation.
 0172      float distanceDeviation =
 173          (Vector3.ProjectOnPlane(positionToPredictedThreat, carrierVelocity)).magnitude;
 0174      float angularDeviation =
 175          Mathf.Asin(distanceDeviation / predictedDistanceToThreat) * Mathf.Rad2Deg;
 0176      if (angularDeviation > SubmunitionSpawnMaxAngularDeviation &&
 0177          predictedDistanceToThreat < SubmunitionSpawnMaxDistanceToThreat) {
 0178        return true;
 179      }
 0180    }
 0181    return false;
 0182  }
 183
 0184  public void AssignSubmunitionsToThreats(Interceptor carrier, List<Interceptor> interceptors) {
 185    // Assign threats to the submunitions.
 0186    Cluster cluster = _interceptorClusterMap[carrier];
 0187    List<Threat> threats = cluster.Threats.ToList();
 0188    IEnumerable<IAssignment.AssignmentItem> assignments =
 189        _assignmentScheme.Assign(interceptors, threats);
 190
 191    // Mark the cluster as delegated to submunitions.
 0192    _threatClusterMap[cluster].RemoveInterceptor(carrier, delegated: true);
 193
 194    // Apply the assignments to the submunitions.
 0195    foreach (var assignment in assignments) {
 0196      assignment.Interceptor.AssignTarget(assignment.Threat);
 0197    }
 198
 199    // Check whether any submunitions were not assigned to a threat.
 0200    foreach (var interceptor in interceptors) {
 0201      if (!interceptor.HasAssignedTarget()) {
 0202        RequestAssignInterceptorToThreat(interceptor);
 0203      }
 0204    }
 0205  }
 206
 0207  public void RequestAssignInterceptorToThreat(Interceptor interceptor) {
 0208    interceptor.UnassignTarget();
 0209    _assignableInterceptors.Add(interceptor);
 0210  }
 211
 27212  private void AssignInterceptorToThreat(in IReadOnlyList<Interceptor> interceptors) {
 54213    if (interceptors.Count == 0) {
 27214      return;
 215    }
 216
 217    // The threat originally assigned to the interceptor has been terminated, so assign another
 218    // threat to the interceptor.
 219
 220    // This pulls from all available track files, not from our previously assigned cluster.
 0221    List<Threat> threats = _trackFiles.Where(trackFile => trackFile.Agent is Threat)
 0222                               .Select(trackFile => trackFile.Agent as Threat)
 223                               .ToList();
 0224    if (threats.Count == 0) {
 0225      return;
 226    }
 227
 0228    IEnumerable<IAssignment.AssignmentItem> assignments =
 229        _assignmentScheme.Assign(interceptors, threats);
 230
 231    // Apply the assignments to the submunitions.
 0232    foreach (var assignment in assignments) {
 0233      assignment.Interceptor.AssignTarget(assignment.Threat);
 0234      _assignableInterceptors.Remove(assignment.Interceptor);
 0235    }
 27236  }
 237
 755238  public void RequestClusterThreat(Threat threat) {
 755239    _threatsToCluster.Add(threat);
 755240  }
 241
 10242  private IEnumerator CheckForEscapingThreatsManager(float period) {
 20243    while (true) {
 10244      yield return new WaitForSeconds(period);
 0245      CheckForEscapingThreats();
 0246    }
 247  }
 248
 0249  private void CheckForEscapingThreats() {
 0250    List<Threat> threats = _trackFiles
 0251                               .Where(trackFile => trackFile.Status == TrackStatus.ASSIGNED &&
 252                                                   trackFile.Agent is Threat)
 0253                               .Select(trackFile => trackFile.Agent as Threat)
 254                               .ToList();
 0255    if (threats.Count == 0) {
 0256      return;
 257    }
 258
 259    // Check whether the threats are escaping the pursuing interceptors.
 0260    foreach (var threat in threats) {
 0261      bool isEscaping = threat.AssignedInterceptors.All(interceptor => {
 0262        Vector3 interceptorPosition = interceptor.GetPosition();
 0263        Vector3 threatPosition = threat.GetPosition();
 264
 0265        float threatTimeToHit = (float)(threatPosition.magnitude / threat.GetSpeed());
 0266        float interceptorTimeToHit =
 267            (float)((threatPosition - interceptorPosition).magnitude / interceptor.GetSpeed());
 0268        return interceptorPosition.magnitude > threatPosition.magnitude ||
 269               threatTimeToHit < interceptorTimeToHit;
 0270      });
 0271      if (isEscaping) {
 0272        RequestClusterThreat(threat);
 0273      }
 0274    }
 0275  }
 276
 10277  private IEnumerator ClusterThreatsManager(float period) {
 20278    while (true) {
 10279      ClusterThreats();
 10280      yield return new WaitForSeconds(period);
 0281    }
 282  }
 283
 10284  private void ClusterThreats() {
 285    // Maximum number of threats per cluster.
 286    const int MaxSize = 7;
 287    // Maximum cluster radius in meters.
 288    const float MaxRadius = 500;
 289
 290    // Filter the threats.
 10291    List<Threat> threats =
 292        _threatsToCluster
 755293            .Where(threat => !threat.IsTerminated() && threat.AssignedInterceptors.Count == 0)
 294            .ToList();
 10295    if (threats.Count == 0) {
 0296      return;
 297    }
 298
 299    // Cluster threats.
 10300    IClusterer clusterer = new AgglomerativeClusterer(new List<Agent>(threats), MaxSize, MaxRadius);
 10301    clusterer.Cluster();
 10302    var clusters = clusterer.Clusters;
 10303    Debug.Log($"Clustered {threats.Count} threats into {clusters.Count} clusters.");
 10304    UIManager.Instance.LogActionMessage(
 305        $"[IADS] Clustered {threats.Count} threats into {clusters.Count} clusters.");
 306
 10307    _threatClusters = clusters.ToList();
 546308    foreach (var cluster in clusters) {
 172309      _threatClusterMap.Add(cluster, new ThreatClusterData(cluster));
 172310    }
 311
 10312    _threatsToCluster.Clear();
 10313  }
 314
 755315  public void RegisterNewThreat(Threat threat) {
 755316    string trackID = $"T{1000 + ++_trackFileIdTicker}";
 755317    ThreatData trackFile = new ThreatData(threat, trackID);
 755318    _trackFiles.Add(trackFile);
 755319    _trackFileMap.Add(threat, trackFile);
 755320    RequestClusterThreat(threat);
 321
 755322    threat.OnThreatHit += RegisterThreatHit;
 755323    threat.OnThreatMiss += RegisterThreatMiss;
 755324  }
 325
 0326  public void RegisterNewInterceptor(Interceptor interceptor) {
 0327    string trackID = $"I{2000 + ++_trackFileIdTicker}";
 0328    InterceptorData trackFile = new InterceptorData(interceptor, trackID);
 0329    _trackFiles.Add(trackFile);
 0330    _trackFileMap.Add(interceptor, trackFile);
 331
 0332    interceptor.OnInterceptMiss += RegisterInterceptorMiss;
 0333    interceptor.OnInterceptHit += RegisterInterceptorHit;
 0334  }
 335
 0336  private void RegisterInterceptorHit(Interceptor interceptor, Threat threat) {
 0337    var threatTrack = _trackFileMap[threat] as ThreatData;
 0338    var interceptorTrack = _trackFileMap[interceptor] as InterceptorData;
 339
 0340    if (threatTrack != null) {
 0341      threatTrack.RemoveInterceptor(interceptor);
 0342      threatTrack.MarkDestroyed();
 0343    }
 344
 0345    if (interceptorTrack != null) {
 0346      interceptorTrack.RemoveThreat(threat);
 0347      interceptorTrack.MarkDestroyed();
 0348    }
 349
 350    // Assign the interceptors to other threats.
 0351    foreach (var assignedInterceptor in threat.AssignedInterceptors.ToList()) {
 0352      if (assignedInterceptor is not CarrierInterceptor) {
 0353        RequestAssignInterceptorToThreat(assignedInterceptor as Interceptor);
 0354      }
 0355    }
 0356  }
 357
 0358  private void RegisterInterceptorMiss(Interceptor interceptor, Threat threat) {
 359    // Assign the interceptor to another threat.
 0360    RequestAssignInterceptorToThreat(interceptor);
 361
 0362    var threatTrack = _trackFileMap[threat] as ThreatData;
 0363    var interceptorTrack = _trackFileMap[interceptor] as InterceptorData;
 364
 0365    if (threatTrack != null) {
 0366      threatTrack.RemoveInterceptor(interceptor);
 367
 368      // Check if the threat is being targeted by at least one interceptor.
 0369      if (threatTrack.AssignedInterceptorCount == 0) {
 0370        RequestClusterThreat(threat);
 0371      }
 0372    }
 373
 0374    if (interceptorTrack != null) {
 0375      interceptorTrack.RemoveThreat(threat);
 0376      interceptorTrack.MarkDestroyed();
 0377    }
 0378  }
 379
 0380  private void RegisterThreatHit(Threat threat) {
 0381    var threatTrack = _trackFileMap[threat] as ThreatData;
 0382    if (threatTrack != null) {
 0383      threatTrack.MarkDestroyed();
 0384    }
 385
 386    // Re-assign the assigned interceptors to other threats.
 0387    foreach (var interceptor in threat.AssignedInterceptors.ToList()) {
 0388      RequestAssignInterceptorToThreat(interceptor as Interceptor);
 0389    }
 0390  }
 391
 0392  private void RegisterThreatMiss(Threat threat) {
 393    // The threat missed (e.g., it hit the floor).
 394    // Re-assign the assigned interceptors to other threats.
 0395    foreach (var interceptor in threat.AssignedInterceptors.ToList()) {
 0396      RequestAssignInterceptorToThreat(interceptor as Interceptor);
 0397    }
 398
 0399    var threatTrack = _trackFileMap[threat] as ThreatData;
 0400    if (threatTrack != null) {
 0401      threatTrack.MarkDestroyed();
 0402    }
 0403  }
 404
 0405  public List<ThreatData> GetThreatTracks() => _trackFiles.OfType<ThreatData>().ToList();
 406
 407  public List<InterceptorData> GetInterceptorTracks() =>
 0408      _trackFiles.OfType<InterceptorData>().ToList();
 409
 9410  private void RegisterSimulationEnded() {
 9411    _trackFiles.Clear();
 9412    _trackFileMap.Clear();
 9413    _assignmentQueue.Clear();
 9414    _threatClusters.Clear();
 9415    _threatClusterMap.Clear();
 9416    _interceptorClusterMap.Clear();
 9417    _assignableInterceptors.Clear();
 9418    _threatsToCluster.Clear();
 9419    _trackFileIdTicker = 0;
 9420  }
 421}