< Summary

Class:HierarchicalBase
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Hierarchical/HierarchicalBase.cs
Covered lines:20
Uncovered lines:116
Coverable lines:136
Total lines:231
Line coverage:14.7% (20 of 136)
Covered branches:0
Total branches:0
Covered methods:8
Total methods:26
Method coverage:30.7% (8 of 26)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
HierarchicalBase()0%110100%
AddSubHierarchical(...)0%6200%
RemoveSubHierarchical(...)0%2100%
ClearSubHierarchicals()0%2100%
LeafHierarchicals(...)0%22.899044.44%
AddPursuer(...)0%220100%
RemovePursuer(...)0%2100%
AddLaunchedHierarchical(...)0%6200%
RemoveTargetHierarchical(...)0%20400%
RecursiveCluster(...)0%72800%
AssignNewTarget(...)0%6200%
GetMean(...)0%20400%
FindBestHierarchicalTarget(...)0%12300%
FindBestLeafHierarchicalTarget(...)0%42600%

File(s)

/github/workspace/Assets/Scripts/Hierarchical/HierarchicalBase.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using UnityEngine;
 5
 6// Base implementation of a hierarchical object.
 7//
 8// The position and velocity of a hierarchical object is defined as the mean of the positions and
 9// velocities of the sub-hierarchical objects.
 10[Serializable]
 11public class HierarchicalBase : IHierarchical {
 12  // Soft maximum number of sub-hierarchical objects. This is used for recursive clustering.
 13  private const int _maxNumSubHierarchicals = 10;
 14
 15  // Maximum cluster radius in meters.
 16  private const float _clusterMaxRadius = 1000f;
 17
 18  // List of hierarchical objects in the hierarchy level below.
 19  [SerializeReference]
 96920  protected List<IHierarchical> _subHierarchicals = new List<IHierarchical>();
 21
 22  // List of hierarchical objects pursuing this hierarchical object.
 23  [SerializeReference]
 96924  private List<IHierarchical> _pursuers = new List<IHierarchical>();
 25
 26  // Target hierarchical object.
 27  [SerializeReference]
 28  private IHierarchical _target;
 29
 30  // List of launched hierarchical objects.
 31  [SerializeReference]
 96932  private List<IHierarchical> _launchedHierarchicals = new List<IHierarchical>();
 33
 14634  public IReadOnlyList<IHierarchical> SubHierarchicals => _subHierarchicals.AsReadOnly();
 35
 36  // Return a list of active sub-hierarchical objects.
 37  public IEnumerable<IHierarchical> ActiveSubHierarchicals =>
 038      _subHierarchicals.Where(s => !s.IsTerminated);
 39
 40  public virtual IHierarchical Target {
 2006941    get => _target;
 286542    set { _target = value; }
 43  }
 44
 699545  public IReadOnlyList<IHierarchical> Pursuers => _pursuers.AsReadOnly();
 46  public IEnumerable<IHierarchical> ActivePursuers =>
 699547      Pursuers.Where(pursuer => !pursuer.IsTerminated);
 48
 049  public IReadOnlyList<IHierarchical> LaunchedHierarchicals => _launchedHierarchicals.AsReadOnly();
 50
 051  public virtual Vector3 Position => GetMean(s => s.Position);
 052  public virtual Vector3 Velocity => GetMean(s => s.Velocity);
 053  public float Speed => Velocity.magnitude;
 054  public virtual Vector3 Acceleration => GetMean(s => s.Acceleration);
 055  public virtual bool IsTerminated => !ActiveSubHierarchicals.Any();
 56
 057  public void AddSubHierarchical(IHierarchical subHierarchical) {
 058    if (!_subHierarchicals.Contains(subHierarchical)) {
 059      _subHierarchicals.Add(subHierarchical);
 060    }
 061  }
 62
 063  public void RemoveSubHierarchical(IHierarchical subHierarchical) {
 064    _subHierarchicals.Remove(subHierarchical);
 065  }
 66
 067  public void ClearSubHierarchicals() {
 068    _subHierarchicals.Clear();
 069  }
 70
 14671  public List<IHierarchical> LeafHierarchicals(bool activeOnly, bool withTargetOnly) {
 14672    var subHierarchicals = (activeOnly ? ActiveSubHierarchicals : SubHierarchicals).ToList();
 14673    if (subHierarchicals.Count > 0) {
 074      var leafHierarchicals = new List<IHierarchical>();
 075      foreach (var subHierarchical in subHierarchicals) {
 076        leafHierarchicals.AddRange(subHierarchical.LeafHierarchicals(activeOnly, withTargetOnly));
 077      }
 078      return leafHierarchicals;
 79    }
 80
 16081    if (withTargetOnly && (Target == null || Target.IsTerminated)) {
 1482      return new List<IHierarchical>();
 83    }
 13284    return new List<IHierarchical> { this };
 14685  }
 86
 95587  public void AddPursuer(IHierarchical pursuer) {
 191088    if (!_pursuers.Contains(pursuer)) {
 95589      _pursuers.Add(pursuer);
 95590    }
 95591  }
 92
 093  public void RemovePursuer(IHierarchical pursuer) {
 094    _pursuers.Remove(pursuer);
 095  }
 96
 097  public void AddLaunchedHierarchical(IHierarchical hierarchical) {
 098    if (!_launchedHierarchicals.Contains(hierarchical)) {
 099      _launchedHierarchicals.Add(hierarchical);
 0100    }
 0101  }
 102
 0103  public void RemoveTargetHierarchical(IHierarchical target) {
 0104    Target?.RemoveSubHierarchical(target);
 0105    foreach (var subHierarchical in SubHierarchicals) {
 0106      subHierarchical.RemoveTargetHierarchical(target);
 0107    }
 0108  }
 109
 0110  public void RecursiveCluster(int maxClusterSize) {
 0111    if (SubHierarchicals.Count > 0) {
 0112      foreach (var subHierarchical in SubHierarchicals) {
 0113        subHierarchical.RecursiveCluster(maxClusterSize);
 0114      }
 0115      return;
 116    }
 0117    if (Target == null) {
 0118      return;
 119    }
 0120    int numActiveSubHierarchicals = Target.ActiveSubHierarchicals.Count();
 0121    if (numActiveSubHierarchicals <= maxClusterSize) {
 0122      return;
 123    }
 124
 125    // Perform clustering on the assigned targets.
 126    // TODO(titan): Define a better heuristic for choosing the clustering algorithm to minimize the
 127    // size and radius of each cluster without generating too many clusters.
 0128    IClusterer clusterer = null;
 0129    if (numActiveSubHierarchicals >= _maxNumSubHierarchicals * Mathf.Max(maxClusterSize / 2, 1)) {
 0130      clusterer = new KMeansClusterer(_maxNumSubHierarchicals);
 0131    } else {
 0132      clusterer = new AgglomerativeClusterer(maxClusterSize, _clusterMaxRadius);
 0133    }
 0134    List<Cluster> clusters = clusterer.Cluster(Target.ActiveSubHierarchicals);
 135
 136    // Generate sub-hierarchical objects to manage the target clusters.
 0137    foreach (var cluster in clusters) {
 0138      var subHierarchical = new HierarchicalBase { Target = cluster };
 0139      AddSubHierarchical(subHierarchical);
 0140      subHierarchical.RecursiveCluster(maxClusterSize);
 0141    }
 0142  }
 143
 0144  public bool AssignNewTarget(IHierarchical hierarchical, int capacity) {
 145    // TODO(titan): Abstract the target picking strategy to its own interface and class.
 146    // TODO(titan): Consider whether the OnAssignSubInterceptor and OnReassignTarget events should
 147    // be modified to refer to the new parent interceptor.
 0148    hierarchical.Target = FindBestHierarchicalTarget(hierarchical, capacity) ??
 149                          FindBestLeafHierarchicalTarget(hierarchical, capacity);
 0150    return hierarchical.Target != null;
 0151  }
 152
 0153  private Vector3 GetMean(System.Func<IHierarchical, Vector3> selector) {
 0154    Vector3 sum = Vector3.zero;
 0155    int count = 0;
 0156    foreach (var subHierarchical in ActiveSubHierarchicals) {
 0157      sum += selector(subHierarchical);
 0158      ++count;
 0159    }
 0160    if (count == 0) {
 0161      return Vector3.zero;
 162    }
 0163    return sum / count;
 0164  }
 165
 0166  private IHierarchical FindBestHierarchicalTarget(IHierarchical hierarchical, int capacity) {
 167    // Find all sub-hierarchical objects that have at least one active target but no more than the
 168    // interceptor capacity.
 0169    List<IHierarchical> FindPossibleHierarchicalTargets(IHierarchical hierarchical) {
 0170      if (hierarchical.Target == null) {
 0171        return new List<IHierarchical>();
 172      }
 0173      int numActiveTargets = hierarchical.Target.ActiveSubHierarchicals.Count();
 0174      if (numActiveTargets > 0 && numActiveTargets <= capacity) {
 0175        return new List<IHierarchical> { hierarchical.Target };
 176      }
 177
 0178      var possibleTargets = new List<IHierarchical>();
 0179      foreach (var subHierarchical in hierarchical.SubHierarchicals) {
 0180        possibleTargets.AddRange(FindPossibleHierarchicalTargets(subHierarchical));
 0181      }
 0182      return possibleTargets;
 0183    }
 0184    List<IHierarchical> possibleTargets = FindPossibleHierarchicalTargets(this);
 0185    if (possibleTargets.Count == 0) {
 0186      return null;
 187    }
 188
 189    // Use a maximum speed assignment to select the target resulting in the maximum intercept speed.
 0190    IAssignment targetAssignment =
 191        new MaxSpeedAssignment(Assignment.Assignment_EvenAssignment_Assign);
 0192    List<AssignmentItem> assignments =
 193        targetAssignment.Assign(new List<IHierarchical> { hierarchical }, possibleTargets);
 0194    if (assignments.Count != 1) {
 0195      return null;
 196    }
 0197    return assignments[0].Second;
 0198  }
 199
 0200  private IHierarchical FindBestLeafHierarchicalTarget(IHierarchical hierarchical, int capacity) {
 0201    List<IHierarchical> leafHierarchicalTargets =
 202        LeafHierarchicals(activeOnly: true, withTargetOnly: true)
 0203            .Select(hierarchical => hierarchical.Target)
 204            .ToList();
 0205    if (leafHierarchicalTargets.Count == 0) {
 0206      return null;
 207    }
 208
 209    // Use a maximum speed assignment to select the target resulting in the maximum intercept speed.
 0210    IAssignment targetAssignment =
 211        new MaxSpeedAssignment(Assignment.Assignment_EvenAssignment_Assign);
 0212    List<AssignmentItem> assignments =
 213        targetAssignment.Assign(new List<IHierarchical> { hierarchical }, leafHierarchicalTargets);
 0214    if (assignments.Count != 1) {
 0215      return null;
 216    }
 217
 218    // Remove as many target sub-hierarchical objects until the interceptor capacity.
 0219    var targetSubHierarchicals = assignments[0].Second.ActiveSubHierarchicals.ToList();
 0220    var filteredSubHierarchicals =
 221        targetSubHierarchicals
 222            .OrderBy(subHierarchical =>
 0223                         Vector3.Distance(subHierarchical.Position, assignments[0].Second.Position))
 224            .Take(capacity);
 0225    var targetHierarchical = new HierarchicalBase();
 0226    foreach (var subHierarchical in filteredSubHierarchicals) {
 0227      targetHierarchical.AddSubHierarchical(subHierarchical);
 0228    }
 0229    return targetHierarchical;
 0230  }
 231}