< Summary

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

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
IADS()0%2100%
Awake()0%6200%
OnDestroy()0%20400%
Start()0%2100%
LateUpdate()0%12300%
RegisterSimulationStarted()0%2100%
LaunchInterceptorsManager()0%12300%
CheckAndLaunchInterceptors()0%42600%
ShouldLaunchSubmunitions(...)0%30500%
AssignSubmunitionsToThreats(...)0%30500%
RequestAssignInterceptorToThreat(...)0%2100%
AssignInterceptorToThreat(...)0%56700%
RequestClusterThreat(...)0%2100%
CheckForEscapingThreatsManager()0%12300%
CheckForEscapingThreats()0%42600%
ClusterThreatsManager()0%12300%
ClusterThreats()0%30500%
RegisterNewThreat(...)0%2100%
RegisterNewInterceptor(...)0%2100%
RegisterInterceptorHit(...)0%30500%
RegisterInterceptorMiss(...)0%20400%
RegisterThreatHit(...)0%12300%
RegisterThreatMiss(...)0%12300%
GetThreatTracks()0%2100%
GetInterceptorTracks()0%2100%
RegisterSimulationEnded()0%2100%

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 {
 010  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.
 017  private ILaunchAnglePlanner _launchAnglePlanner =
 18      new LaunchAngleCsvInterpolator(Path.Combine("Planning", "hydra70_launch_angle.csv"));
 019  private IAssignment _assignmentScheme = new MaxSpeedAssignment();
 20  private Coroutine _launchInterceptorsCoroutine;
 21
 22  [SerializeField]
 023  private List<TrackFileData> _trackFiles = new List<TrackFileData>();
 024  private Dictionary<Agent, TrackFileData> _trackFileMap = new Dictionary<Agent, TrackFileData>();
 25
 026  private List<Interceptor> _assignmentQueue = new List<Interceptor>();
 027  private List<Cluster> _threatClusters = new List<Cluster>();
 028  private Dictionary<Cluster, ThreatClusterData> _threatClusterMap =
 29      new Dictionary<Cluster, ThreatClusterData>();
 030  private Dictionary<Interceptor, Cluster> _interceptorClusterMap =
 31      new Dictionary<Interceptor, Cluster>();
 032  private HashSet<Interceptor> _assignableInterceptors = new HashSet<Interceptor>();
 33
 034  private HashSet<Threat> _threatsToCluster = new HashSet<Threat>();
 35  private Coroutine _checkForEscapingThreatsCoroutine;
 36  private Coroutine _clusterThreatsCoroutine;
 37
 038  private int _trackFileIdTicker = 0;
 39
 040  private void Awake() {
 041    if (Instance == null) {
 042      Instance = this;
 043    } else {
 044      Destroy(gameObject);
 045    }
 046  }
 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
 060  public void Start() {
 061    SimManager.Instance.OnSimulationStarted += RegisterSimulationStarted;
 062    SimManager.Instance.OnSimulationEnded += RegisterSimulationEnded;
 063    SimManager.Instance.OnNewThreat += RegisterNewThreat;
 064    SimManager.Instance.OnNewInterceptor += RegisterNewInterceptor;
 065  }
 66
 067  public void LateUpdate() {
 68    // Update the cluster centroids.
 069    foreach (var cluster in _threatClusters) {
 070      cluster.Recenter();
 071      _threatClusterMap[cluster].UpdateCentroid();
 072    }
 73
 74    // Assign any interceptors that are no longer assigned to any threat.
 075    AssignInterceptorToThreat(
 076        _assignableInterceptors.Where(interceptor => !interceptor.HasTerminated()).ToList());
 077  }
 78
 079  private void RegisterSimulationStarted() {
 080    _launchInterceptorsCoroutine =
 81        StartCoroutine(LaunchInterceptorsManager(LaunchInterceptorsPeriod));
 082    _checkForEscapingThreatsCoroutine =
 83        StartCoroutine(CheckForEscapingThreatsManager(CheckForEscapingThreatsPeriod));
 084    _clusterThreatsCoroutine = StartCoroutine(ClusterThreatsManager(ClusterThreatsPeriod));
 085  }
 86
 087  private IEnumerator LaunchInterceptorsManager(float period) {
 088    while (true) {
 89      // Check whether an interceptor should be launched at a cluster and launch it.
 090      CheckAndLaunchInterceptors();
 091      yield return new WaitForSeconds(period);
 092    }
 93  }
 94
 095  private void CheckAndLaunchInterceptors() {
 096    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    }
 0143  }
 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
 0212  private void AssignInterceptorToThreat(in IReadOnlyList<Interceptor> interceptors) {
 0213    if (interceptors.Count == 0) {
 0214      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    }
 0236  }
 237
 0238  public void RequestClusterThreat(Threat threat) {
 0239    _threatsToCluster.Add(threat);
 0240  }
 241
 0242  private IEnumerator CheckForEscapingThreatsManager(float period) {
 0243    while (true) {
 0244      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
 0277  private IEnumerator ClusterThreatsManager(float period) {
 0278    while (true) {
 0279      ClusterThreats();
 0280      yield return new WaitForSeconds(period);
 0281    }
 282  }
 283
 0284  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.
 0291    List<Threat> threats =
 292        _threatsToCluster
 0293            .Where(threat => !threat.IsTerminated() && threat.AssignedInterceptors.Count == 0)
 294            .ToList();
 0295    if (threats.Count == 0) {
 0296      return;
 297    }
 298
 299    // Cluster threats.
 0300    IClusterer clusterer = new AgglomerativeClusterer(new List<Agent>(threats), MaxSize, MaxRadius);
 0301    clusterer.Cluster();
 0302    var clusters = clusterer.Clusters;
 0303    Debug.Log($"Clustered {threats.Count} threats into {clusters.Count} clusters.");
 0304    UIManager.Instance.LogActionMessage(
 305        $"[IADS] Clustered {threats.Count} threats into {clusters.Count} clusters.");
 306
 0307    _threatClusters = clusters.ToList();
 0308    foreach (var cluster in clusters) {
 0309      _threatClusterMap.Add(cluster, new ThreatClusterData(cluster));
 0310    }
 311
 0312    _threatsToCluster.Clear();
 0313  }
 314
 0315  public void RegisterNewThreat(Threat threat) {
 0316    string trackID = $"T{1000 + ++_trackFileIdTicker}";
 0317    ThreatData trackFile = new ThreatData(threat, trackID);
 0318    _trackFiles.Add(trackFile);
 0319    _trackFileMap.Add(threat, trackFile);
 0320    RequestClusterThreat(threat);
 321
 0322    threat.OnThreatHit += RegisterThreatHit;
 0323    threat.OnThreatMiss += RegisterThreatMiss;
 0324  }
 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
 0410  private void RegisterSimulationEnded() {
 0411    _trackFiles.Clear();
 0412    _trackFileMap.Clear();
 0413    _assignmentQueue.Clear();
 0414    _threatClusters.Clear();
 0415    _threatClusterMap.Clear();
 0416    _interceptorClusterMap.Clear();
 0417    _assignableInterceptors.Clear();
 0418    _threatsToCluster.Clear();
 0419    _trackFileIdTicker = 0;
 0420  }
 421}