< Summary

Class:SimManager
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Managers/SimManager.cs
Covered lines:202
Uncovered lines:42
Coverable lines:244
Total lines:383
Line coverage:82.7% (202 of 244)
Covered branches:0
Total branches:0
Covered methods:43
Total methods:51
Method coverage:84.3% (43 of 51)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
SimManager()0%110100%
SimManager()0%110100%
StartSimulation()0%220100%
EndSimulation()0%880100%
PostSimulation()0%6200%
PauseSimulation()0%110100%
ResumeSimulation()0%110100%
QuitSimulation()0%2100%
ResetAndStartSimulation()0%110100%
LoadNewSimulationConfig(...)0%3.063081.25%
CreateInterceptor(...)0%6.396077.78%
CreateThreat(...)0%6.396077.78%
CreateDummyAgent(...)0%110100%
DestroyDummyAgent(...)0%2100%
CreateAgent(...)0%2.032080%
CreateRandomAgent(...)0%110100%
Awake()0%3.243070%
Start()0%220100%
FixedUpdate()0%440100%
LateUpdate()0%2.752042.86%
InitializeLaunchers()0%440100%
InitializeThreats()0%440100%
LoadSimConfigs(...)0%2.352055.56%
SetGameSpeed()0%220100%
SetTimeScale(...)0%110100%
RegisterThreatMiss(...)0%2100%
RegisterThreatTerminated(...)0%2100%
ShouldEndSimulation()0%9.166055.56%

File(s)

/github/workspace/Assets/Scripts/Managers/SimManager.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using UnityEngine;
 5
 6// The simulation manager handles the creation of all agents.
 7// It implements the singleton pattern to ensure that only one instance exists.
 8public class SimManager : MonoBehaviour {
 9  // Simulation events.
 10  public delegate void SimulationEventHandler();
 11  public event SimulationEventHandler OnSimulationStarted;
 12  public event SimulationEventHandler OnSimulationEnded;
 13
 14  // Interceptor events.
 15  public delegate void NewInterceptorEventHandler(IInterceptor interceptor);
 16  public event NewInterceptorEventHandler OnNewInterceptor;
 17  public delegate void NewLauncherEventHandler(IInterceptor launcher);
 18  public event NewLauncherEventHandler OnNewLauncher;
 19
 20  // Threat events.
 21  public delegate void NewThreatEventHandler(IThreat threat);
 22  public event NewThreatEventHandler OnNewThreat;
 23
 24  // Default simulation configuration file.
 25  private const string _defaultSimulationConfigFile = "7_quadcopters.pbtxt";
 26
 27  // Default simulator configuration file.
 28  private const string _defaultSimulatorConfigFile = "simulator.pbtxt";
 29
 30  // Map from the agent type to the prefab class name.
 31  // The prefab class must exist in the Resources/Prefabs directory.
 132  private static readonly Dictionary<Configs.AgentType, string> _agentTypePrefabMap = new() {
 33    { Configs.AgentType.Vessel, "Vessel" },
 34    { Configs.AgentType.ShoreBattery, "ShoreBattery" },
 35    { Configs.AgentType.CarrierInterceptor, "CarrierInterceptor" },
 36    { Configs.AgentType.MissileInterceptor, "MissileInterceptor" },
 37    { Configs.AgentType.FixedWingThreat, "FixedWingThreat" },
 38    { Configs.AgentType.RotaryWingThreat, "RotaryWingThreat" },
 39  };
 40
 311341  public static SimManager Instance { get; private set; }
 42
 43  // Simulation configuration.
 17644  public Configs.SimulationConfig SimulationConfig { get; set; }
 45
 46  // Simulator configuration.
 104747  public Configs.SimulatorConfig SimulatorConfig { get; set; }
 48
 49  // Simulation time.
 131850  public float ElapsedTime { get; private set; } = 0f;
 14051  public bool IsPaused { get; private set; } = false;
 52
 53  // If true, the simulation is currently running.
 17254  public bool IsRunning { get; private set; } = false;
 55
 56  // If true, automatically restart the simulation.
 257  public bool AutoRestartOnEnd { get; set; } = true;
 58
 1559  public string Timestamp { get; private set; } = "";
 60
 61  // Lists of all agents in the simulation.
 262  private List<IAgent> _interceptors = new List<IAgent>();
 263  private List<IAgent> _threats = new List<IAgent>();
 264  private List<IAgent> _dummyAgents = new List<IAgent>();
 65
 066  public IReadOnlyList<IAgent> Interceptors => _interceptors.AsReadOnly();
 067  public IReadOnlyList<IAgent> Threats => _threats.AsReadOnly();
 068  public IReadOnlyList<IAgent> Agents => Interceptors.Concat(Threats).ToList().AsReadOnly();
 69
 70  // Interceptor and threat costs.
 7471  public float CostLaunchedInterceptors { get; private set; } = 0f;
 4672  public float CostDestroyedThreats { get; private set; } = 0f;
 73
 74  // Track the number of interceptors and threats spawned and terminated.
 275  private int _numInterceptorsSpawned = 0;
 276  private int _numThreatsSpawned = 0;
 277  private int _numThreatsTerminated = 0;
 78
 1279  public void StartSimulation() {
 1280    IsRunning = true;
 1281    OnSimulationStarted?.Invoke();
 1282    Debug.Log("Simulation started.");
 1283    UIManager.Instance.LogActionMessage("[SIM] Simulation started.");
 84
 1285    InitializeLaunchers();
 1286    InitializeThreats();
 1287  }
 88
 1189  public void EndSimulation() {
 1190    IsRunning = false;
 1191    OnSimulationEnded?.Invoke();
 1192    Debug.Log("Simulation ended.");
 1193    UIManager.Instance.LogActionMessage("[SIM] Simulation ended.");
 94
 95    // Clear existing interceptors and threats.
 7296    foreach (var interceptor in _interceptors) {
 2697      if (interceptor as MonoBehaviour != null) {
 1398        Destroy(interceptor.gameObject);
 1399      }
 13100    }
 2877101    foreach (var threat in _threats) {
 1896102      if (threat as MonoBehaviour != null) {
 948103        Destroy(threat.gameObject);
 948104      }
 948105    }
 2877106    foreach (var dummyAgent in _dummyAgents) {
 1896107      if (dummyAgent as MonoBehaviour != null) {
 948108        Destroy(dummyAgent.gameObject);
 948109      }
 948110    }
 111
 11112    _interceptors.Clear();
 11113    _threats.Clear();
 11114    _dummyAgents.Clear();
 11115  }
 116
 0117  public void PostSimulation() {
 0118    if (AutoRestartOnEnd) {
 0119      ResetAndStartSimulation();
 0120    }
 0121  }
 122
 5123  public void PauseSimulation() {
 5124    IsPaused = true;
 5125    SetGameSpeed();
 5126  }
 127
 6128  public void ResumeSimulation() {
 6129    IsPaused = false;
 6130    SetGameSpeed();
 6131  }
 132
 0133  public void QuitSimulation() {
 0134    Application.Quit();
 0135  }
 136
 11137  public void ResetAndStartSimulation() {
 11138    ElapsedTime = 0f;
 11139    CostLaunchedInterceptors = 0f;
 11140    CostDestroyedThreats = 0f;
 141
 11142    _numInterceptorsSpawned = 0;
 11143    _numThreatsSpawned = 0;
 11144    _numThreatsTerminated = 0;
 145
 11146    StartSimulation();
 11147  }
 148
 11149  public void LoadNewSimulationConfig(string simulationConfigFile) {
 22150    if (IsRunning) {
 11151      EndSimulation();
 11152    }
 11153    LoadSimConfigs(simulationConfigFile);
 11154    SetGameSpeed();
 155
 22156    if (SimulationConfig != null) {
 11157      Debug.Log($"Loaded new simulation configuration: {simulationConfigFile}.");
 11158      ResetAndStartSimulation();
 11159    } else {
 0160      Debug.LogError($"Failed to load simulation configuration: {simulationConfigFile}.");
 0161    }
 11162  }
 163
 164  // Create an interceptor based on the provided configuration.
 14165  public IInterceptor CreateInterceptor(Configs.AgentConfig config, Simulation.State initialState) {
 14166    if (config == null) {
 0167      return null;
 168    }
 169
 170    // Load the static configuration.
 14171    Configs.StaticConfig staticConfig = ConfigLoader.LoadStaticConfig(config.ConfigFile);
 14172    if (staticConfig == null) {
 0173      return null;
 174    }
 175
 14176    GameObject interceptorObject = null;
 28177    if (_agentTypePrefabMap.TryGetValue(staticConfig.AgentType, out var prefab)) {
 14178      interceptorObject = CreateAgent(config, initialState, prefab);
 14179    }
 14180    if (interceptorObject == null) {
 0181      return null;
 182    }
 183
 14184    IInterceptor interceptor = interceptorObject.GetComponent<IInterceptor>();
 14185    interceptor.HierarchicalAgent = new HierarchicalAgent(interceptor);
 14186    interceptor.StaticConfig = staticConfig;
 14187    interceptor.OnTerminated += (interceptor) => _interceptors.Remove(interceptor);
 14188    _interceptors.Add(interceptor);
 14189    ++_numInterceptorsSpawned;
 190
 191    // Assign a unique and simple ID.
 14192    interceptorObject.name = $"{staticConfig.Name}_Interceptor_{_numInterceptorsSpawned}";
 193
 194    // Add the interceptor's unit cost to the total cost.
 14195    CostLaunchedInterceptors += staticConfig.Cost;
 196
 14197    OnNewInterceptor?.Invoke(interceptor);
 14198    return interceptor;
 14199  }
 200
 201  // Create a threat based on the provided configuration.
 202  // Returns the created threat instance, or null if creation failed.
 955203  public IThreat CreateThreat(Configs.AgentConfig config) {
 955204    if (config == null) {
 0205      return null;
 206    }
 207
 208    // Load the static configuration.
 955209    Configs.StaticConfig staticConfig = ConfigLoader.LoadStaticConfig(config.ConfigFile);
 955210    if (staticConfig == null) {
 0211      return null;
 212    }
 213
 955214    GameObject threatObject = null;
 1910215    if (_agentTypePrefabMap.TryGetValue(staticConfig.AgentType, out var prefab)) {
 955216      threatObject = CreateRandomAgent(config, prefab);
 955217    }
 955218    if (threatObject == null) {
 0219      return null;
 220    }
 221
 955222    IThreat threat = threatObject.GetComponent<IThreat>();
 955223    threat.HierarchicalAgent = new HierarchicalAgent(threat);
 955224    threat.StaticConfig = staticConfig;
 955225    threat.OnMiss += RegisterThreatMiss;
 955226    threat.OnTerminated += RegisterThreatTerminated;
 955227    _threats.Add(threat);
 955228    ++_numThreatsSpawned;
 229
 230    // Assign a unique name.
 955231    threatObject.name = $"{staticConfig.Name}_Threat_{_numThreatsSpawned}";
 232
 955233    OnNewThreat?.Invoke(threat);
 955234    return threat;
 955235  }
 236
 955237  public IAgent CreateDummyAgent(in Vector3 position, in Vector3 velocity) {
 955238    GameObject dummyAgentPrefab = Resources.Load<GameObject>($"Prefabs/DummyAgent");
 955239    GameObject dummyAgentObject = Instantiate(dummyAgentPrefab, position, Quaternion.identity);
 955240    var dummyAgent = dummyAgentObject.GetComponent<IAgent>();
 955241    dummyAgent.Velocity = velocity;
 955242    _dummyAgents.Add(dummyAgent);
 955243    dummyAgent.OnTerminated += (agent) => _dummyAgents.Remove(agent);
 955244    return dummyAgent;
 955245  }
 246
 0247  public void DestroyDummyAgent(IAgent dummyAgent) {
 0248    dummyAgent.Terminate();
 0249  }
 250
 251  // Create an agent based on the provided configuration and prefab name.
 252  private GameObject CreateAgent(Configs.AgentConfig config, Simulation.State initialState,
 969253                                 string prefabName) {
 969254    GameObject prefab = Resources.Load<GameObject>($"Prefabs/{prefabName}");
 969255    if (prefab == null) {
 0256      Debug.LogError($"Prefab {prefabName} not found in Resources/Prefabs directory.");
 0257      return null;
 258    }
 259
 969260    GameObject agentObject =
 261        Instantiate(prefab, Coordinates3.FromProto(initialState.Position), Quaternion.identity);
 969262    IAgent agent = agentObject.GetComponent<IAgent>();
 969263    agent.AgentConfig = config;
 969264    Vector3 velocity = Coordinates3.FromProto(initialState.Velocity);
 969265    agent.Velocity = velocity;
 266
 267    // Set the rotation to face the initial velocity.
 969268    Quaternion targetRotation = Quaternion.LookRotation(velocity, Vector3.up);
 969269    agentObject.transform.rotation = targetRotation;
 969270    return agentObject;
 969271  }
 272
 273  // Create a random agent based on the provided configuration and prefab name.
 955274  private GameObject CreateRandomAgent(Configs.AgentConfig config, string prefabName) {
 275    // Randomize the position and the velocity.
 955276    Vector3 positionNoise = Utilities.GenerateRandomNoise(config.StandardDeviation.Position);
 955277    Vector3 velocityNoise = Utilities.GenerateRandomNoise(config.StandardDeviation.Velocity);
 955278    var initialState = new Simulation.State() {
 279      Position = Coordinates3.ToProto(Coordinates3.FromProto(config.InitialState.Position) +
 280                                      positionNoise),
 281      Velocity = Coordinates3.ToProto(Coordinates3.FromProto(config.InitialState.Velocity) +
 282                                      velocityNoise),
 283    };
 955284    return CreateAgent(config, initialState, prefabName);
 955285  }
 286
 1287  private void Awake() {
 1288    if (Instance != null && Instance != this) {
 0289      Destroy(gameObject);
 0290      return;
 291    }
 1292    Instance = this;
 1293    DontDestroyOnLoad(gameObject);
 294
 1295    LoadSimConfigs(_defaultSimulationConfigFile);
 1296    Timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
 1297  }
 298
 1299  private void Start() {
 1300    IsPaused = false;
 2301    if (!RunManager.Instance.HasRunConfig()) {
 1302      StartSimulation();
 1303      ResumeSimulation();
 1304    }
 1305  }
 306
 104307  private void FixedUpdate() {
 189308    if (IsRunning && !IsPaused && ElapsedTime < SimulationConfig.EndTime) {
 85309      ElapsedTime += Time.fixedDeltaTime;
 85310    }
 104311  }
 312
 32313  private void LateUpdate() {
 32314    if (ShouldEndSimulation()) {
 0315      EndSimulation();
 0316      PostSimulation();
 0317    }
 32318  }
 319
 12320  private void InitializeLaunchers() {
 78321    foreach (var swarmConfig in SimulationConfig.InterceptorSwarmConfigs) {
 14322      IInterceptor launcher =
 323          CreateInterceptor(swarmConfig.AgentConfig, swarmConfig.AgentConfig.InitialState);
 14324      OnNewLauncher?.Invoke(launcher);
 14325    }
 12326  }
 327
 12328  private void InitializeThreats() {
 162329    foreach (var swarmConfig in SimulationConfig.ThreatSwarmConfigs) {
 2949330      for (int i = 0; i < swarmConfig.NumAgents; ++i) {
 955331        CreateThreat(swarmConfig.AgentConfig);
 955332      }
 42333    }
 12334  }
 335
 12336  private void LoadSimConfigs(string simulationConfigFile) {
 12337    SimulatorConfig = ConfigLoader.LoadSimulatorConfig(_defaultSimulatorConfigFile);
 338    // If a run configuration is provided, enable telemetry logging and event logging.
 12339    if (RunManager.Instance.HasRunConfig()) {
 0340      SimulatorConfig.EnableTelemetryLogging = true;
 0341      SimulatorConfig.EnableEventLogging = true;
 0342    }
 12343    SimulationConfig = ConfigLoader.LoadSimulationConfig(simulationConfigFile);
 12344  }
 345
 22346  private void SetGameSpeed() {
 32347    if (IsPaused) {
 10348      Time.fixedDeltaTime = 0;
 10349      SetTimeScale(timeScale: 0);
 22350    } else {
 12351      Time.fixedDeltaTime = 1.0f / SimulatorConfig.PhysicsUpdateRate;
 12352      SetTimeScale(SimulationConfig.TimeScale);
 12353    }
 22354  }
 355
 22356  private void SetTimeScale(float timeScale) {
 22357    Time.timeScale = timeScale;
 358    // Time.fixedDeltaTime is derived from the simulator configuration.
 22359    Time.maximumDeltaTime = Time.fixedDeltaTime * 3;
 22360  }
 361
 0362  private void RegisterThreatMiss(IThreat threat) {
 0363    CostDestroyedThreats += threat.StaticConfig.Cost;
 0364  }
 365
 0366  private void RegisterThreatTerminated(IAgent threat) {
 0367    _threats.Remove(threat);
 0368    ++_numThreatsTerminated;
 0369  }
 370
 32371  private bool ShouldEndSimulation() {
 32372    if (IsRunning && ElapsedTime >= SimulationConfig.EndTime) {
 0373      return true;
 374    }
 375    // The simulation can be ended before the actual end time if there is a run configuration and
 376    // all spawned threats have been terminated.
 32377    if (RunManager.Instance.HasRunConfig() && _numThreatsSpawned > 0 &&
 0378        _numThreatsTerminated >= _numThreatsSpawned) {
 0379      return true;
 380    }
 32381    return false;
 32382  }
 383}