< Summary

Class:SimManager
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Managers/SimManager.cs
Covered lines:227
Uncovered lines:47
Coverable lines:274
Total lines:424
Line coverage:82.8% (227 of 274)
Covered branches:0
Total branches:0
Covered methods:44
Total methods:53
Method coverage:83% (44 of 53)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
SimManager()0%110100%
SimManager()0%110100%
StartSimulation()0%330100%
EndSimulation()0%990100%
PostSimulation()0%6200%
PauseSimulation()0%110100%
ResumeSimulation()0%110100%
QuitSimulation()0%2100%
ResetAndStartSimulation()0%110100%
LoadNewSimulationConfig(...)0%3.063081.25%
CreateInterceptor(...)0%8.327070%
CreateThreat(...)0%6.396077.78%
CreateDummyAgent(...)0%110100%
DestroyDummyAgent(...)0%2100%
CreateAgent(...)0%3.043083.33%
CreateRandomAgent(...)0%110100%
Awake()0%4.24076.92%
Start()0%440100%
FixedUpdate()0%440100%
LateUpdate()0%2.752042.86%
InitializeAssets()0%660100%
InitializeLaunchers()0%660100%
InitializeThreats()0%440100%
LoadSimConfigs(...)0%2.352055.56%
SetGameSpeed()0%220100%
SetTimeScale(...)0%110100%
RegisterInterceptorTerminated(...)0%2100%
RegisterThreatDestroyed(...)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 NewAssetEventHandler(IInterceptor asset);
 18  public event NewAssetEventHandler OnNewAsset;
 19  public delegate void NewLauncherEventHandler(IInterceptor launcher);
 20  public event NewLauncherEventHandler OnNewLauncher;
 21
 22  // Threat events.
 23  public delegate void NewThreatEventHandler(IThreat threat);
 24  public event NewThreatEventHandler OnNewThreat;
 25
 26  // Default simulation configuration file.
 27  private const string _defaultSimulationConfigFile = "7_quadcopters.pbtxt";
 28
 29  // Default simulator configuration file.
 30  private const string _defaultSimulatorConfigFile = "simulator.pbtxt";
 31
 32  // Map from the agent type to the prefab class name.
 33  // The prefab class must exist in the Resources/Prefabs directory.
 134  private static readonly Dictionary<Configs.AgentType, string> _agentTypePrefabMap = new() {
 35    { Configs.AgentType.Vessel, "Vessel" },
 36    { Configs.AgentType.ShoreBattery, "ShoreBattery" },
 37    { Configs.AgentType.CarrierInterceptor, "CarrierInterceptor" },
 38    { Configs.AgentType.MissileInterceptor, "MissileInterceptor" },
 39    { Configs.AgentType.FixedWingThreat, "FixedWingThreat" },
 40    { Configs.AgentType.RotaryWingThreat, "RotaryWingThreat" },
 41  };
 42
 43  // Asset color.
 144  private static readonly Color _assetColor = new Color(0.75f, 0.4f, 0f);
 45
 313046  public static SimManager Instance { get; private set; }
 47
 48  // Simulation configuration.
 15649  public Configs.SimulationConfig SimulationConfig { get; set; }
 50
 51  // Simulator configuration.
 105952  public Configs.SimulatorConfig SimulatorConfig { get; set; }
 53
 54  // Simulation time.
 124055  public float ElapsedTime { get; private set; } = 0f;
 10656  public bool IsPaused { get; private set; } = false;
 57
 58  // If true, the simulation is currently running.
 13559  public bool IsRunning { get; private set; } = false;
 60
 61  // If true, automatically restart the simulation.
 262  public bool AutoRestartOnEnd { get; set; } = true;
 63
 1564  public string Timestamp { get; private set; } = "";
 65
 66  // Lists of all agents in the simulation.
 267  private List<IAgent> _interceptors = new List<IAgent>();
 268  private List<IAgent> _threats = new List<IAgent>();
 269  private List<IAgent> _dummyAgents = new List<IAgent>();
 70
 071  public IReadOnlyList<IAgent> Interceptors => _interceptors.AsReadOnly();
 072  public IReadOnlyList<IAgent> Threats => _threats.AsReadOnly();
 073  public IReadOnlyList<IAgent> Agents => Interceptors.Concat(Threats).ToList().AsReadOnly();
 74
 75  // Interceptor and threat costs.
 4276  public float CostLaunchedInterceptors { get; private set; } = 0f;
 4277  public float CostDestroyedThreats { get; private set; } = 0f;
 78
 79  // Track the number of interceptors and threats spawned and terminated.
 280  private int _numInterceptorsSpawned = 0;
 281  private int _numThreatsSpawned = 0;
 282  private int _numThreatsTerminated = 0;
 83
 1284  public void StartSimulation() {
 1285    IsRunning = true;
 1286    OnSimulationStarted?.Invoke();
 1287    Debug.Log("Simulation started.");
 1288    UIManager.Instance?.LogActionMessage("[SIM] Simulation started.");
 89
 1290    InitializeAssets();
 1291    InitializeLaunchers();
 1292    InitializeThreats();
 1293  }
 94
 1195  public void EndSimulation() {
 1196    IsRunning = false;
 1197    OnSimulationEnded?.Invoke();
 1198    Debug.Log("Simulation ended.");
 1199    UIManager.Instance?.LogActionMessage("[SIM] Simulation ended.");
 100
 101    // Clear existing interceptors and threats.
 105102    foreach (var interceptor in _interceptors) {
 48103      if (interceptor as MonoBehaviour != null) {
 24104        Destroy(interceptor.gameObject);
 24105      }
 24106    }
 2877107    foreach (var threat in _threats) {
 1896108      if (threat as MonoBehaviour != null) {
 948109        Destroy(threat.gameObject);
 948110      }
 948111    }
 2877112    foreach (var dummyAgent in _dummyAgents) {
 1896113      if (dummyAgent as MonoBehaviour != null) {
 948114        Destroy(dummyAgent.gameObject);
 948115      }
 948116    }
 117
 11118    _interceptors.Clear();
 11119    _threats.Clear();
 11120    _dummyAgents.Clear();
 11121  }
 122
 0123  public void PostSimulation() {
 0124    if (AutoRestartOnEnd) {
 0125      ResetAndStartSimulation();
 0126    }
 0127  }
 128
 5129  public void PauseSimulation() {
 5130    IsPaused = true;
 5131    SetGameSpeed();
 5132  }
 133
 6134  public void ResumeSimulation() {
 6135    IsPaused = false;
 6136    SetGameSpeed();
 6137  }
 138
 0139  public void QuitSimulation() {
 0140    Application.Quit();
 0141  }
 142
 11143  public void ResetAndStartSimulation() {
 11144    ElapsedTime = 0f;
 11145    CostLaunchedInterceptors = 0f;
 11146    CostDestroyedThreats = 0f;
 147
 11148    _numInterceptorsSpawned = 0;
 11149    _numThreatsSpawned = 0;
 11150    _numThreatsTerminated = 0;
 151
 11152    StartSimulation();
 11153  }
 154
 11155  public void LoadNewSimulationConfig(string simulationConfigFile) {
 22156    if (IsRunning) {
 11157      EndSimulation();
 11158    }
 11159    LoadSimConfigs(simulationConfigFile);
 11160    SetGameSpeed();
 161
 22162    if (SimulationConfig != null) {
 11163      Debug.Log($"Loaded new simulation configuration: {simulationConfigFile}.");
 11164      ResetAndStartSimulation();
 11165    } else {
 0166      Debug.LogError($"Failed to load simulation configuration: {simulationConfigFile}.");
 0167    }
 11168  }
 169
 170  // Create an interceptor based on the provided configuration.
 171  public IInterceptor CreateInterceptor(Configs.AgentConfig config, Simulation.State initialState,
 26172                                        bool ignoreMetrics = false) {
 26173    if (config == null) {
 0174      return null;
 175    }
 176
 177    // Load the static configuration.
 26178    Configs.StaticConfig staticConfig = ConfigLoader.LoadStaticConfig(config.ConfigFile);
 26179    if (staticConfig == null) {
 0180      return null;
 181    }
 182
 26183    GameObject interceptorObject = null;
 52184    if (_agentTypePrefabMap.TryGetValue(staticConfig.AgentType, out var prefab)) {
 26185      interceptorObject = CreateAgent(config, initialState, prefab);
 26186    }
 26187    if (interceptorObject == null) {
 0188      return null;
 189    }
 190
 26191    IInterceptor interceptor = interceptorObject.GetComponent<IInterceptor>();
 26192    interceptor.HierarchicalAgent = new HierarchicalAgent(interceptor);
 26193    interceptor.StaticConfig = staticConfig;
 26194    interceptor.OnTerminated += RegisterInterceptorTerminated;
 26195    _interceptors.Add(interceptor);
 26196    ++_numInterceptorsSpawned;
 197
 198    // Assign a unique and simple ID.
 26199    interceptorObject.name = $"{staticConfig.Name}_Interceptor_{_numInterceptorsSpawned}";
 200
 26201    if (!ignoreMetrics) {
 202      // Add the interceptor's unit cost to the total cost.
 0203      CostLaunchedInterceptors += staticConfig.Cost;
 0204    }
 205
 26206    OnNewInterceptor?.Invoke(interceptor);
 26207    return interceptor;
 26208  }
 209
 210  // Create a threat based on the provided configuration.
 211  // Returns the created threat instance, or null if creation failed.
 955212  public IThreat CreateThreat(Configs.AgentConfig config) {
 955213    if (config == null) {
 0214      return null;
 215    }
 216
 217    // Load the static configuration.
 955218    Configs.StaticConfig staticConfig = ConfigLoader.LoadStaticConfig(config.ConfigFile);
 955219    if (staticConfig == null) {
 0220      return null;
 221    }
 222
 955223    GameObject threatObject = null;
 1910224    if (_agentTypePrefabMap.TryGetValue(staticConfig.AgentType, out var prefab)) {
 955225      threatObject = CreateRandomAgent(config, prefab);
 955226    }
 955227    if (threatObject == null) {
 0228      return null;
 229    }
 230
 955231    IThreat threat = threatObject.GetComponent<IThreat>();
 955232    threat.HierarchicalAgent = new HierarchicalAgent(threat);
 955233    threat.StaticConfig = staticConfig;
 955234    threat.OnDestroyed += RegisterThreatDestroyed;
 955235    threat.OnTerminated += RegisterThreatTerminated;
 955236    _threats.Add(threat);
 955237    ++_numThreatsSpawned;
 238
 239    // Assign a unique name.
 955240    threatObject.name = $"{staticConfig.Name}_Threat_{_numThreatsSpawned}";
 241
 955242    OnNewThreat?.Invoke(threat);
 955243    return threat;
 955244  }
 245
 955246  public IAgent CreateDummyAgent(in Vector3 position, in Vector3 velocity) {
 955247    GameObject dummyAgentPrefab = Resources.Load<GameObject>($"Prefabs/DummyAgent");
 955248    GameObject dummyAgentObject = Instantiate(dummyAgentPrefab, position, Quaternion.identity);
 955249    var dummyAgent = dummyAgentObject.GetComponent<IAgent>();
 955250    dummyAgent.Velocity = velocity;
 955251    _dummyAgents.Add(dummyAgent);
 955252    dummyAgent.OnTerminated += (agent) => _dummyAgents.Remove(agent);
 955253    return dummyAgent;
 955254  }
 255
 0256  public void DestroyDummyAgent(IAgent dummyAgent) {
 0257    dummyAgent.Terminate();
 0258  }
 259
 260  // Create an agent based on the provided configuration and prefab name.
 261  private GameObject CreateAgent(Configs.AgentConfig config, Simulation.State initialState,
 981262                                 string prefabName) {
 981263    GameObject prefab = Resources.Load<GameObject>($"Prefabs/{prefabName}");
 981264    if (prefab == null) {
 0265      Debug.LogError($"Prefab {prefabName} not found in Resources/Prefabs directory.");
 0266      return null;
 267    }
 268
 981269    GameObject agentObject = Instantiate(prefab, Coordinates3.FromProto(initialState.Position),
 270                                         prefab.transform.rotation);
 981271    IAgent agent = agentObject.GetComponent<IAgent>();
 981272    agent.AgentConfig = config;
 981273    Vector3 velocity = Coordinates3.FromProto(initialState.Velocity);
 981274    agent.Velocity = velocity;
 275
 276    // Set the rotation to face the initial velocity.
 1950277    if (velocity.sqrMagnitude > Mathf.Epsilon) {
 969278      Quaternion targetRotation = Quaternion.LookRotation(velocity, Vector3.up);
 969279      agentObject.transform.rotation = targetRotation;
 969280    }
 981281    return agentObject;
 981282  }
 283
 284  // Create a random agent based on the provided configuration and prefab name.
 955285  private GameObject CreateRandomAgent(Configs.AgentConfig config, string prefabName) {
 286    // Randomize the position and the velocity.
 955287    Vector3 positionNoise = Utilities.GenerateRandomNoise(config.StandardDeviation.Position);
 955288    Vector3 velocityNoise = Utilities.GenerateRandomNoise(config.StandardDeviation.Velocity);
 955289    var initialState = new Simulation.State() {
 290      Position = Coordinates3.ToProto(Coordinates3.FromProto(config.InitialState.Position) +
 291                                      positionNoise),
 292      Velocity = Coordinates3.ToProto(Coordinates3.FromProto(config.InitialState.Velocity) +
 293                                      velocityNoise),
 294    };
 955295    return CreateAgent(config, initialState, prefabName);
 955296  }
 297
 1298  private void Awake() {
 1299    if (Instance != null && Instance != this) {
 0300      Destroy(gameObject);
 0301      return;
 302    }
 1303    Instance = this;
 1304    DontDestroyOnLoad(gameObject);
 305
 2306    if (!RunWorker.IsWorkerMode) {
 1307      LoadSimConfigs(_defaultSimulationConfigFile);
 1308    }
 1309    Timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
 1310  }
 311
 1312  private System.Collections.IEnumerator Start() {
 1313    IsPaused = false;
 314    // Wait one frame so every manager has finished Start() and subscribed to simulation events.
 1315    yield return null;
 316
 2317    if (!RunWorker.IsWorkerMode) {
 1318      StartSimulation();
 1319      ResumeSimulation();
 1320    }
 1321  }
 322
 71323  private void FixedUpdate() {
 129324    if (IsRunning && !IsPaused && ElapsedTime < SimulationConfig.EndTime) {
 58325      ElapsedTime += Time.fixedDeltaTime;
 58326    }
 71327  }
 328
 28329  private void LateUpdate() {
 28330    if (ShouldEndSimulation()) {
 0331      EndSimulation();
 0332      PostSimulation();
 0333    }
 28334  }
 335
 12336  private void InitializeAssets() {
 72337    foreach (var assetConfig in SimulationConfig.AssetConfigs) {
 12338      IInterceptor asset =
 339          CreateInterceptor(assetConfig, assetConfig.InitialState, ignoreMetrics: true);
 24340      if (asset != null) {
 341        // Change the color of the asset to be orange.
 12342        Renderer[] renderers = asset.gameObject.GetComponentsInChildren<Renderer>();
 108343        foreach (var renderer in renderers) {
 24344          var propertyBlock = new MaterialPropertyBlock();
 24345          propertyBlock.SetColor("_BaseColor", _assetColor);
 24346          propertyBlock.SetColor("_Color", _assetColor);
 24347          renderer.SetPropertyBlock(propertyBlock);
 24348        }
 12349        OnNewAsset?.Invoke(asset);
 12350      }
 12351    }
 12352  }
 353
 12354  private void InitializeLaunchers() {
 78355    foreach (var swarmConfig in SimulationConfig.InterceptorSwarmConfigs) {
 14356      IInterceptor launcher = CreateInterceptor(
 357          swarmConfig.AgentConfig, swarmConfig.AgentConfig.InitialState, ignoreMetrics: true);
 28358      if (launcher != null) {
 14359        OnNewLauncher?.Invoke(launcher);
 360        // All launchers are assets.
 14361        OnNewAsset?.Invoke(launcher);
 14362      }
 14363    }
 12364  }
 365
 12366  private void InitializeThreats() {
 162367    foreach (var swarmConfig in SimulationConfig.ThreatSwarmConfigs) {
 2949368      for (int i = 0; i < swarmConfig.NumAgents; ++i) {
 955369        CreateThreat(swarmConfig.AgentConfig);
 955370      }
 42371    }
 12372  }
 373
 12374  private void LoadSimConfigs(string simulationConfigFile) {
 12375    SimulatorConfig = ConfigLoader.LoadSimulatorConfig(_defaultSimulatorConfigFile);
 376    // If a worker run is provided, enable telemetry logging and event logging.
 12377    if (RunWorker.IsWorkerMode) {
 0378      SimulatorConfig.EnableTelemetryLogging = true;
 0379      SimulatorConfig.EnableEventLogging = true;
 0380    }
 12381    SimulationConfig = ConfigLoader.LoadSimulationConfig(simulationConfigFile);
 12382  }
 383
 22384  private void SetGameSpeed() {
 32385    if (IsPaused) {
 10386      Time.fixedDeltaTime = 0;
 10387      SetTimeScale(timeScale: 0);
 22388    } else {
 12389      Time.fixedDeltaTime = 1.0f / SimulatorConfig.PhysicsUpdateRate;
 12390      SetTimeScale(SimulationConfig.TimeScale);
 12391    }
 22392  }
 393
 22394  private void SetTimeScale(float timeScale) {
 22395    Time.timeScale = timeScale;
 396    // Time.fixedDeltaTime is derived from the simulator configuration.
 22397    Time.maximumDeltaTime = Time.fixedDeltaTime * 3;
 22398  }
 399
 0400  private void RegisterInterceptorTerminated(IAgent interceptor) {
 0401    _interceptors.Remove(interceptor);
 0402  }
 403
 0404  private void RegisterThreatDestroyed(IThreat threat) {
 0405    CostDestroyedThreats += threat.StaticConfig.Cost;
 0406  }
 407
 0408  private void RegisterThreatTerminated(IAgent threat) {
 0409    _threats.Remove(threat);
 0410    ++_numThreatsTerminated;
 0411  }
 412
 28413  private bool ShouldEndSimulation() {
 28414    if (IsRunning && ElapsedTime >= SimulationConfig.EndTime) {
 0415      return true;
 416    }
 417    // A worker run can end early once all spawned threats have been terminated.
 28418    if (RunWorker.IsWorkerMode && _numThreatsSpawned > 0 &&
 0419        _numThreatsTerminated >= _numThreatsSpawned) {
 0420      return true;
 421    }
 28422    return false;
 28423  }
 424}