< Summary

Class:SimManager
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Managers/SimManager.cs
Covered lines:218
Uncovered lines:56
Coverable lines:274
Total lines:419
Line coverage:79.5% (218 of 274)
Covered branches:0
Total branches:0
Covered methods:41
Total methods:53
Method coverage:77.3% (41 of 53)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
SimManager()0%2100%
SimManager()0%2100%
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 event Action OnSimulationStarted;
 11  public event Action OnSimulationEnded;
 12
 13  // Interceptor events.
 14  public event Action<IInterceptor> OnNewInterceptor;
 15  public event Action<IInterceptor> OnNewAsset;
 16  public event Action<IInterceptor> OnNewLauncher;
 17
 18  // Threat events.
 19  public event Action<IThreat> OnNewThreat;
 20
 21  // Default simulation configuration file.
 22  private const string _defaultSimulationConfigFile = "7_quadcopters.pbtxt";
 23
 24  // Default simulator configuration file.
 25  private const string _defaultSimulatorConfigFile = "simulator.pbtxt";
 26
 27  // Map from the agent type to the prefab class name.
 28  // The prefab class must exist in the Resources/Prefabs directory.
 029  private static readonly Dictionary<Configs.AgentType, string> _agentTypePrefabMap = new() {
 30    { Configs.AgentType.Vessel, "Vessel" },
 31    { Configs.AgentType.ShoreBattery, "ShoreBattery" },
 32    { Configs.AgentType.CarrierInterceptor, "CarrierInterceptor" },
 33    { Configs.AgentType.MissileInterceptor, "MissileInterceptor" },
 34    { Configs.AgentType.FixedWingThreat, "FixedWingThreat" },
 35    { Configs.AgentType.RotaryWingThreat, "RotaryWingThreat" },
 36  };
 37
 38  // Asset color.
 039  private static readonly Color _assetColor = new Color(0.75f, 0.4f, 0f);
 40
 313441  public static SimManager Instance { get; private set; }
 42
 43  // Simulation configuration.
 15844  public Configs.SimulationConfig SimulationConfig { get; set; }
 45
 46  // Simulator configuration.
 105947  public Configs.SimulatorConfig SimulatorConfig { get; set; }
 48
 49  // Simulation time.
 124450  public float ElapsedTime { get; private set; } = 0f;
 11051  public bool IsPaused { get; private set; } = false;
 52
 53  // If true, the simulation is currently running.
 13954  public bool IsRunning { get; private set; } = false;
 55
 56  // If true, automatically restart the simulation.
 057  public bool AutoRestartOnEnd { get; set; } = true;
 58
 1359  public string Timestamp { get; private set; } = "";
 60
 61  // Lists of all agents in the simulation.
 062  private List<IAgent> _interceptors = new List<IAgent>();
 063  private List<IAgent> _threats = new List<IAgent>();
 064  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.
 4071  public float CostLaunchedInterceptors { get; private set; } = 0f;
 4072  public float CostDestroyedThreats { get; private set; } = 0f;
 73
 74  // Track the number of interceptors and threats spawned and terminated.
 075  private int _numInterceptorsSpawned = 0;
 076  private int _numThreatsSpawned = 0;
 077  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    InitializeAssets();
 1286    InitializeLaunchers();
 1287    InitializeThreats();
 1288  }
 89
 1190  public void EndSimulation() {
 1191    IsRunning = false;
 1192    OnSimulationEnded?.Invoke();
 1193    Debug.Log("Simulation ended.");
 1194    UIManager.Instance?.LogActionMessage("[SIM] Simulation ended.");
 95
 96    // Clear existing interceptors and threats.
 10597    foreach (var interceptor in _interceptors) {
 4898      if (interceptor as MonoBehaviour != null) {
 2499        Destroy(interceptor.gameObject);
 24100      }
 24101    }
 2877102    foreach (var threat in _threats) {
 1896103      if (threat as MonoBehaviour != null) {
 948104        Destroy(threat.gameObject);
 948105      }
 948106    }
 2877107    foreach (var dummyAgent in _dummyAgents) {
 1896108      if (dummyAgent as MonoBehaviour != null) {
 948109        Destroy(dummyAgent.gameObject);
 948110      }
 948111    }
 112
 11113    _interceptors.Clear();
 11114    _threats.Clear();
 11115    _dummyAgents.Clear();
 11116  }
 117
 0118  public void PostSimulation() {
 0119    if (AutoRestartOnEnd) {
 0120      ResetAndStartSimulation();
 0121    }
 0122  }
 123
 5124  public void PauseSimulation() {
 5125    IsPaused = true;
 5126    SetGameSpeed();
 5127  }
 128
 6129  public void ResumeSimulation() {
 6130    IsPaused = false;
 6131    SetGameSpeed();
 6132  }
 133
 0134  public void QuitSimulation() {
 0135    Application.Quit();
 0136  }
 137
 11138  public void ResetAndStartSimulation() {
 11139    ElapsedTime = 0f;
 11140    CostLaunchedInterceptors = 0f;
 11141    CostDestroyedThreats = 0f;
 142
 11143    _numInterceptorsSpawned = 0;
 11144    _numThreatsSpawned = 0;
 11145    _numThreatsTerminated = 0;
 146
 11147    StartSimulation();
 11148  }
 149
 11150  public void LoadNewSimulationConfig(string simulationConfigFile) {
 22151    if (IsRunning) {
 11152      EndSimulation();
 11153    }
 11154    LoadSimConfigs(simulationConfigFile);
 11155    SetGameSpeed();
 156
 22157    if (SimulationConfig != null) {
 11158      Debug.Log($"Loaded new simulation configuration: {simulationConfigFile}.");
 11159      ResetAndStartSimulation();
 11160    } else {
 0161      Debug.LogError($"Failed to load simulation configuration: {simulationConfigFile}.");
 0162    }
 11163  }
 164
 165  // Create an interceptor based on the provided configuration.
 166  public IInterceptor CreateInterceptor(Configs.AgentConfig config, Simulation.State initialState,
 26167                                        bool ignoreMetrics = false) {
 26168    if (config == null) {
 0169      return null;
 170    }
 171
 172    // Load the static configuration.
 26173    Configs.StaticConfig staticConfig = ConfigLoader.LoadStaticConfig(config.ConfigFile);
 26174    if (staticConfig == null) {
 0175      return null;
 176    }
 177
 26178    GameObject interceptorObject = null;
 52179    if (_agentTypePrefabMap.TryGetValue(staticConfig.AgentType, out var prefab)) {
 26180      interceptorObject = CreateAgent(config, initialState, prefab);
 26181    }
 26182    if (interceptorObject == null) {
 0183      return null;
 184    }
 185
 26186    IInterceptor interceptor = interceptorObject.GetComponent<IInterceptor>();
 26187    interceptor.HierarchicalAgent = new HierarchicalAgent(interceptor);
 26188    interceptor.StaticConfig = staticConfig;
 26189    interceptor.OnTerminated += RegisterInterceptorTerminated;
 26190    _interceptors.Add(interceptor);
 26191    ++_numInterceptorsSpawned;
 192
 193    // Assign a unique and simple ID.
 26194    interceptorObject.name = $"{staticConfig.Name}_Interceptor_{_numInterceptorsSpawned}";
 195
 26196    if (!ignoreMetrics) {
 197      // Add the interceptor's unit cost to the total cost.
 0198      CostLaunchedInterceptors += staticConfig.Cost;
 0199    }
 200
 26201    OnNewInterceptor?.Invoke(interceptor);
 26202    return interceptor;
 26203  }
 204
 205  // Create a threat based on the provided configuration.
 206  // Returns the created threat instance, or null if creation failed.
 955207  public IThreat CreateThreat(Configs.AgentConfig config) {
 955208    if (config == null) {
 0209      return null;
 210    }
 211
 212    // Load the static configuration.
 955213    Configs.StaticConfig staticConfig = ConfigLoader.LoadStaticConfig(config.ConfigFile);
 955214    if (staticConfig == null) {
 0215      return null;
 216    }
 217
 955218    GameObject threatObject = null;
 1910219    if (_agentTypePrefabMap.TryGetValue(staticConfig.AgentType, out var prefab)) {
 955220      threatObject = CreateRandomAgent(config, prefab);
 955221    }
 955222    if (threatObject == null) {
 0223      return null;
 224    }
 225
 955226    IThreat threat = threatObject.GetComponent<IThreat>();
 955227    threat.HierarchicalAgent = new HierarchicalAgent(threat);
 955228    threat.StaticConfig = staticConfig;
 955229    threat.OnDestroyed += RegisterThreatDestroyed;
 955230    threat.OnTerminated += RegisterThreatTerminated;
 955231    _threats.Add(threat);
 955232    ++_numThreatsSpawned;
 233
 234    // Assign a unique name.
 955235    threatObject.name = $"{staticConfig.Name}_Threat_{_numThreatsSpawned}";
 236
 955237    OnNewThreat?.Invoke(threat);
 955238    return threat;
 955239  }
 240
 955241  public IAgent CreateDummyAgent(in Vector3 position, in Vector3 velocity) {
 955242    GameObject dummyAgentPrefab = Resources.Load<GameObject>($"Prefabs/DummyAgent");
 955243    GameObject dummyAgentObject = Instantiate(dummyAgentPrefab, position, Quaternion.identity);
 955244    var dummyAgent = dummyAgentObject.GetComponent<IAgent>();
 955245    dummyAgent.Velocity = velocity;
 955246    _dummyAgents.Add(dummyAgent);
 955247    dummyAgent.OnTerminated += (agent) => _dummyAgents.Remove(agent);
 955248    return dummyAgent;
 955249  }
 250
 0251  public void DestroyDummyAgent(IAgent dummyAgent) {
 0252    dummyAgent.Terminate();
 0253  }
 254
 255  // Create an agent based on the provided configuration and prefab name.
 256  private GameObject CreateAgent(Configs.AgentConfig config, Simulation.State initialState,
 981257                                 string prefabName) {
 981258    GameObject prefab = Resources.Load<GameObject>($"Prefabs/{prefabName}");
 981259    if (prefab == null) {
 0260      Debug.LogError($"Prefab {prefabName} not found in Resources/Prefabs directory.");
 0261      return null;
 262    }
 263
 981264    GameObject agentObject = Instantiate(prefab, Coordinates3.FromProto(initialState.Position),
 265                                         prefab.transform.rotation);
 981266    IAgent agent = agentObject.GetComponent<IAgent>();
 981267    agent.AgentConfig = config;
 981268    Vector3 velocity = Coordinates3.FromProto(initialState.Velocity);
 981269    agent.Velocity = velocity;
 270
 271    // Set the rotation to face the initial velocity.
 1950272    if (velocity.sqrMagnitude > Mathf.Epsilon) {
 969273      Quaternion targetRotation = Quaternion.LookRotation(velocity, Vector3.up);
 969274      agentObject.transform.rotation = targetRotation;
 969275    }
 981276    return agentObject;
 981277  }
 278
 279  // Create a random agent based on the provided configuration and prefab name.
 955280  private GameObject CreateRandomAgent(Configs.AgentConfig config, string prefabName) {
 281    // Randomize the position and the velocity.
 955282    Vector3 positionNoise = Utilities.GenerateRandomNoise(config.StandardDeviation.Position);
 955283    Vector3 velocityNoise = Utilities.GenerateRandomNoise(config.StandardDeviation.Velocity);
 955284    var initialState = new Simulation.State() {
 285      Position = Coordinates3.ToProto(Coordinates3.FromProto(config.InitialState.Position) +
 286                                      positionNoise),
 287      Velocity = Coordinates3.ToProto(Coordinates3.FromProto(config.InitialState.Velocity) +
 288                                      velocityNoise),
 289    };
 955290    return CreateAgent(config, initialState, prefabName);
 955291  }
 292
 1293  private void Awake() {
 1294    if (Instance != null && Instance != this) {
 0295      Destroy(gameObject);
 0296      return;
 297    }
 1298    Instance = this;
 1299    DontDestroyOnLoad(gameObject);
 300
 2301    if (!RunWorker.IsWorkerMode) {
 1302      LoadSimConfigs(_defaultSimulationConfigFile);
 1303    }
 1304    Timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
 1305  }
 306
 1307  private System.Collections.IEnumerator Start() {
 1308    IsPaused = false;
 309    // Wait one frame so every manager has finished Start() and subscribed to simulation events.
 1310    yield return null;
 311
 2312    if (!RunWorker.IsWorkerMode) {
 1313      StartSimulation();
 1314      ResumeSimulation();
 1315    }
 1316  }
 317
 77318  private void FixedUpdate() {
 137319    if (IsRunning && !IsPaused && ElapsedTime < SimulationConfig.EndTime) {
 60320      ElapsedTime += Time.fixedDeltaTime;
 60321    }
 77322  }
 323
 28324  private void LateUpdate() {
 28325    if (ShouldEndSimulation()) {
 0326      EndSimulation();
 0327      PostSimulation();
 0328    }
 28329  }
 330
 12331  private void InitializeAssets() {
 72332    foreach (var assetConfig in SimulationConfig.AssetConfigs) {
 12333      IInterceptor asset =
 334          CreateInterceptor(assetConfig, assetConfig.InitialState, ignoreMetrics: true);
 24335      if (asset != null) {
 336        // Change the color of the asset to be orange.
 12337        Renderer[] renderers = asset.gameObject.GetComponentsInChildren<Renderer>();
 108338        foreach (var renderer in renderers) {
 24339          var propertyBlock = new MaterialPropertyBlock();
 24340          propertyBlock.SetColor("_BaseColor", _assetColor);
 24341          propertyBlock.SetColor("_Color", _assetColor);
 24342          renderer.SetPropertyBlock(propertyBlock);
 24343        }
 12344        OnNewAsset?.Invoke(asset);
 12345      }
 12346    }
 12347  }
 348
 12349  private void InitializeLaunchers() {
 78350    foreach (var swarmConfig in SimulationConfig.InterceptorSwarmConfigs) {
 14351      IInterceptor launcher = CreateInterceptor(
 352          swarmConfig.AgentConfig, swarmConfig.AgentConfig.InitialState, ignoreMetrics: true);
 28353      if (launcher != null) {
 14354        OnNewLauncher?.Invoke(launcher);
 355        // All launchers are assets.
 14356        OnNewAsset?.Invoke(launcher);
 14357      }
 14358    }
 12359  }
 360
 12361  private void InitializeThreats() {
 162362    foreach (var swarmConfig in SimulationConfig.ThreatSwarmConfigs) {
 2949363      for (int i = 0; i < swarmConfig.NumAgents; ++i) {
 955364        CreateThreat(swarmConfig.AgentConfig);
 955365      }
 42366    }
 12367  }
 368
 12369  private void LoadSimConfigs(string simulationConfigFile) {
 12370    SimulatorConfig = ConfigLoader.LoadSimulatorConfig(_defaultSimulatorConfigFile);
 371    // If a worker run is provided, enable telemetry logging and event logging.
 12372    if (RunWorker.IsWorkerMode) {
 0373      SimulatorConfig.EnableTelemetryLogging = true;
 0374      SimulatorConfig.EnableEventLogging = true;
 0375    }
 12376    SimulationConfig = ConfigLoader.LoadSimulationConfig(simulationConfigFile);
 12377  }
 378
 22379  private void SetGameSpeed() {
 32380    if (IsPaused) {
 10381      Time.fixedDeltaTime = 0;
 10382      SetTimeScale(timeScale: 0);
 22383    } else {
 12384      Time.fixedDeltaTime = 1.0f / SimulatorConfig.PhysicsUpdateRate;
 12385      SetTimeScale(SimulationConfig.TimeScale);
 12386    }
 22387  }
 388
 22389  private void SetTimeScale(float timeScale) {
 22390    Time.timeScale = timeScale;
 391    // Time.fixedDeltaTime is derived from the simulator configuration.
 22392    Time.maximumDeltaTime = Time.fixedDeltaTime * 3;
 22393  }
 394
 0395  private void RegisterInterceptorTerminated(IAgent interceptor) {
 0396    _interceptors.Remove(interceptor);
 0397  }
 398
 0399  private void RegisterThreatDestroyed(IThreat threat) {
 0400    CostDestroyedThreats += threat.StaticConfig.Cost;
 0401  }
 402
 0403  private void RegisterThreatTerminated(IAgent threat) {
 0404    _threats.Remove(threat);
 0405    ++_numThreatsTerminated;
 0406  }
 407
 28408  private bool ShouldEndSimulation() {
 28409    if (IsRunning && ElapsedTime >= SimulationConfig.EndTime) {
 0410      return true;
 411    }
 412    // A worker run can end early once all spawned threats have been terminated.
 28413    if (RunWorker.IsWorkerMode && _numThreatsSpawned > 0 &&
 0414        _numThreatsTerminated >= _numThreatsSpawned) {
 0415      return true;
 416    }
 28417    return false;
 28418  }
 419}