< Summary

Class:SimManager
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Managers/SimManager.cs
Covered lines:17
Uncovered lines:257
Coverable lines:274
Total lines:419
Line coverage:6.2% (17 of 274)
Covered branches:0
Total branches:0
Covered methods:20
Total methods:53
Method coverage:37.7% (20 of 53)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
SimManager()0%110100%
SimManager()0%110100%
StartSimulation()0%12300%
EndSimulation()0%90900%
PostSimulation()0%6200%
PauseSimulation()0%2100%
ResumeSimulation()0%2100%
QuitSimulation()0%2100%
ResetAndStartSimulation()0%2100%
LoadNewSimulationConfig(...)0%12300%
CreateInterceptor(...)0%56700%
CreateThreat(...)0%42600%
CreateDummyAgent(...)0%2100%
DestroyDummyAgent(...)0%2100%
CreateAgent(...)0%12300%
CreateRandomAgent(...)0%2100%
Awake()0%20400%
Start()0%20400%
FixedUpdate()0%20400%
LateUpdate()0%6200%
InitializeAssets()0%42600%
InitializeLaunchers()0%42600%
InitializeThreats()0%20400%
LoadSimConfigs(...)0%6200%
SetGameSpeed()0%6200%
SetTimeScale(...)0%2100%
RegisterInterceptorTerminated(...)0%2100%
RegisterThreatDestroyed(...)0%2100%
RegisterThreatTerminated(...)0%2100%
ShouldEndSimulation()0%42600%

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.
 129  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.
 139  private static readonly Color _assetColor = new Color(0.75f, 0.4f, 0f);
 40
 741  public static SimManager Instance { get; private set; }
 42
 43  // Simulation configuration.
 344  public Configs.SimulationConfig SimulationConfig { get; set; }
 45
 46  // Simulator configuration.
 047  public Configs.SimulatorConfig SimulatorConfig { get; set; }
 48
 49  // Simulation time.
 850  public float ElapsedTime { get; private set; } = 0f;
 251  public bool IsPaused { get; private set; } = false;
 52
 53  // If true, the simulation is currently running.
 454  public bool IsRunning { get; private set; } = false;
 55
 56  // If true, automatically restart the simulation.
 257  public bool AutoRestartOnEnd { get; set; } = true;
 58
 259  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.
 271  public float CostLaunchedInterceptors { get; private set; } = 0f;
 272  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
 079  public void StartSimulation() {
 080    IsRunning = true;
 081    OnSimulationStarted?.Invoke();
 082    Debug.Log("Simulation started.");
 083    UIManager.Instance?.LogActionMessage("[SIM] Simulation started.");
 84
 085    InitializeAssets();
 086    InitializeLaunchers();
 087    InitializeThreats();
 088  }
 89
 090  public void EndSimulation() {
 091    IsRunning = false;
 092    OnSimulationEnded?.Invoke();
 093    Debug.Log("Simulation ended.");
 094    UIManager.Instance?.LogActionMessage("[SIM] Simulation ended.");
 95
 96    // Clear existing interceptors and threats.
 097    foreach (var interceptor in _interceptors) {
 098      if (interceptor as MonoBehaviour != null) {
 099        Destroy(interceptor.gameObject);
 0100      }
 0101    }
 0102    foreach (var threat in _threats) {
 0103      if (threat as MonoBehaviour != null) {
 0104        Destroy(threat.gameObject);
 0105      }
 0106    }
 0107    foreach (var dummyAgent in _dummyAgents) {
 0108      if (dummyAgent as MonoBehaviour != null) {
 0109        Destroy(dummyAgent.gameObject);
 0110      }
 0111    }
 112
 0113    _interceptors.Clear();
 0114    _threats.Clear();
 0115    _dummyAgents.Clear();
 0116  }
 117
 0118  public void PostSimulation() {
 0119    if (AutoRestartOnEnd) {
 0120      ResetAndStartSimulation();
 0121    }
 0122  }
 123
 0124  public void PauseSimulation() {
 0125    IsPaused = true;
 0126    SetGameSpeed();
 0127  }
 128
 0129  public void ResumeSimulation() {
 0130    IsPaused = false;
 0131    SetGameSpeed();
 0132  }
 133
 0134  public void QuitSimulation() {
 0135    Application.Quit();
 0136  }
 137
 0138  public void ResetAndStartSimulation() {
 0139    ElapsedTime = 0f;
 0140    CostLaunchedInterceptors = 0f;
 0141    CostDestroyedThreats = 0f;
 142
 0143    _numInterceptorsSpawned = 0;
 0144    _numThreatsSpawned = 0;
 0145    _numThreatsTerminated = 0;
 146
 0147    StartSimulation();
 0148  }
 149
 0150  public void LoadNewSimulationConfig(string simulationConfigFile) {
 0151    if (IsRunning) {
 0152      EndSimulation();
 0153    }
 0154    LoadSimConfigs(simulationConfigFile);
 0155    SetGameSpeed();
 156
 0157    if (SimulationConfig != null) {
 0158      Debug.Log($"Loaded new simulation configuration: {simulationConfigFile}.");
 0159      ResetAndStartSimulation();
 0160    } else {
 0161      Debug.LogError($"Failed to load simulation configuration: {simulationConfigFile}.");
 0162    }
 0163  }
 164
 165  // Create an interceptor based on the provided configuration.
 166  public IInterceptor CreateInterceptor(Configs.AgentConfig config, Simulation.State initialState,
 0167                                        bool ignoreMetrics = false) {
 0168    if (config == null) {
 0169      return null;
 170    }
 171
 172    // Load the static configuration.
 0173    Configs.StaticConfig staticConfig = ConfigLoader.LoadStaticConfig(config.ConfigFile);
 0174    if (staticConfig == null) {
 0175      return null;
 176    }
 177
 0178    GameObject interceptorObject = null;
 0179    if (_agentTypePrefabMap.TryGetValue(staticConfig.AgentType, out var prefab)) {
 0180      interceptorObject = CreateAgent(config, initialState, prefab);
 0181    }
 0182    if (interceptorObject == null) {
 0183      return null;
 184    }
 185
 0186    IInterceptor interceptor = interceptorObject.GetComponent<IInterceptor>();
 0187    interceptor.HierarchicalAgent = new HierarchicalAgent(interceptor);
 0188    interceptor.StaticConfig = staticConfig;
 0189    interceptor.OnTerminated += RegisterInterceptorTerminated;
 0190    _interceptors.Add(interceptor);
 0191    ++_numInterceptorsSpawned;
 192
 193    // Assign a unique and simple ID.
 0194    interceptorObject.name = $"{staticConfig.Name}_Interceptor_{_numInterceptorsSpawned}";
 195
 0196    if (!ignoreMetrics) {
 197      // Add the interceptor's unit cost to the total cost.
 0198      CostLaunchedInterceptors += staticConfig.Cost;
 0199    }
 200
 0201    OnNewInterceptor?.Invoke(interceptor);
 0202    return interceptor;
 0203  }
 204
 205  // Create a threat based on the provided configuration.
 206  // Returns the created threat instance, or null if creation failed.
 0207  public IThreat CreateThreat(Configs.AgentConfig config) {
 0208    if (config == null) {
 0209      return null;
 210    }
 211
 212    // Load the static configuration.
 0213    Configs.StaticConfig staticConfig = ConfigLoader.LoadStaticConfig(config.ConfigFile);
 0214    if (staticConfig == null) {
 0215      return null;
 216    }
 217
 0218    GameObject threatObject = null;
 0219    if (_agentTypePrefabMap.TryGetValue(staticConfig.AgentType, out var prefab)) {
 0220      threatObject = CreateRandomAgent(config, prefab);
 0221    }
 0222    if (threatObject == null) {
 0223      return null;
 224    }
 225
 0226    IThreat threat = threatObject.GetComponent<IThreat>();
 0227    threat.HierarchicalAgent = new HierarchicalAgent(threat);
 0228    threat.StaticConfig = staticConfig;
 0229    threat.OnDestroyed += RegisterThreatDestroyed;
 0230    threat.OnTerminated += RegisterThreatTerminated;
 0231    _threats.Add(threat);
 0232    ++_numThreatsSpawned;
 233
 234    // Assign a unique name.
 0235    threatObject.name = $"{staticConfig.Name}_Threat_{_numThreatsSpawned}";
 236
 0237    OnNewThreat?.Invoke(threat);
 0238    return threat;
 0239  }
 240
 0241  public IAgent CreateDummyAgent(in Vector3 position, in Vector3 velocity) {
 0242    GameObject dummyAgentPrefab = Resources.Load<GameObject>($"Prefabs/DummyAgent");
 0243    GameObject dummyAgentObject = Instantiate(dummyAgentPrefab, position, Quaternion.identity);
 0244    var dummyAgent = dummyAgentObject.GetComponent<IAgent>();
 0245    dummyAgent.Velocity = velocity;
 0246    _dummyAgents.Add(dummyAgent);
 0247    dummyAgent.OnTerminated += (agent) => _dummyAgents.Remove(agent);
 0248    return dummyAgent;
 0249  }
 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,
 0257                                 string prefabName) {
 0258    GameObject prefab = Resources.Load<GameObject>($"Prefabs/{prefabName}");
 0259    if (prefab == null) {
 0260      Debug.LogError($"Prefab {prefabName} not found in Resources/Prefabs directory.");
 0261      return null;
 262    }
 263
 0264    GameObject agentObject = Instantiate(prefab, Coordinates3.FromProto(initialState.Position),
 265                                         prefab.transform.rotation);
 0266    IAgent agent = agentObject.GetComponent<IAgent>();
 0267    agent.AgentConfig = config;
 0268    Vector3 velocity = Coordinates3.FromProto(initialState.Velocity);
 0269    agent.Velocity = velocity;
 270
 271    // Set the rotation to face the initial velocity.
 0272    if (velocity.sqrMagnitude > Mathf.Epsilon) {
 0273      Quaternion targetRotation = Quaternion.LookRotation(velocity, Vector3.up);
 0274      agentObject.transform.rotation = targetRotation;
 0275    }
 0276    return agentObject;
 0277  }
 278
 279  // Create a random agent based on the provided configuration and prefab name.
 0280  private GameObject CreateRandomAgent(Configs.AgentConfig config, string prefabName) {
 281    // Randomize the position and the velocity.
 0282    Vector3 positionNoise = Utilities.GenerateRandomNoise(config.StandardDeviation.Position);
 0283    Vector3 velocityNoise = Utilities.GenerateRandomNoise(config.StandardDeviation.Velocity);
 0284    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    };
 0290    return CreateAgent(config, initialState, prefabName);
 0291  }
 292
 0293  private void Awake() {
 0294    if (Instance != null && Instance != this) {
 0295      Destroy(gameObject);
 0296      return;
 297    }
 0298    Instance = this;
 0299    DontDestroyOnLoad(gameObject);
 300
 0301    if (!RunWorker.IsWorkerMode) {
 0302      LoadSimConfigs(_defaultSimulationConfigFile);
 0303    }
 0304    Timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
 0305  }
 306
 0307  private System.Collections.IEnumerator Start() {
 0308    IsPaused = false;
 309    // Wait one frame so every manager has finished Start() and subscribed to simulation events.
 0310    yield return null;
 311
 0312    if (!RunWorker.IsWorkerMode) {
 0313      StartSimulation();
 0314      ResumeSimulation();
 0315    }
 0316  }
 317
 0318  private void FixedUpdate() {
 0319    if (IsRunning && !IsPaused && ElapsedTime < SimulationConfig.EndTime) {
 0320      ElapsedTime += Time.fixedDeltaTime;
 0321    }
 0322  }
 323
 0324  private void LateUpdate() {
 0325    if (ShouldEndSimulation()) {
 0326      EndSimulation();
 0327      PostSimulation();
 0328    }
 0329  }
 330
 0331  private void InitializeAssets() {
 0332    foreach (var assetConfig in SimulationConfig.AssetConfigs) {
 0333      IInterceptor asset =
 334          CreateInterceptor(assetConfig, assetConfig.InitialState, ignoreMetrics: true);
 0335      if (asset != null) {
 336        // Change the color of the asset to be orange.
 0337        Renderer[] renderers = asset.gameObject.GetComponentsInChildren<Renderer>();
 0338        foreach (var renderer in renderers) {
 0339          var propertyBlock = new MaterialPropertyBlock();
 0340          propertyBlock.SetColor("_BaseColor", _assetColor);
 0341          propertyBlock.SetColor("_Color", _assetColor);
 0342          renderer.SetPropertyBlock(propertyBlock);
 0343        }
 0344        OnNewAsset?.Invoke(asset);
 0345      }
 0346    }
 0347  }
 348
 0349  private void InitializeLaunchers() {
 0350    foreach (var swarmConfig in SimulationConfig.InterceptorSwarmConfigs) {
 0351      IInterceptor launcher = CreateInterceptor(
 352          swarmConfig.AgentConfig, swarmConfig.AgentConfig.InitialState, ignoreMetrics: true);
 0353      if (launcher != null) {
 0354        OnNewLauncher?.Invoke(launcher);
 355        // All launchers are assets.
 0356        OnNewAsset?.Invoke(launcher);
 0357      }
 0358    }
 0359  }
 360
 0361  private void InitializeThreats() {
 0362    foreach (var swarmConfig in SimulationConfig.ThreatSwarmConfigs) {
 0363      for (int i = 0; i < swarmConfig.NumAgents; ++i) {
 0364        CreateThreat(swarmConfig.AgentConfig);
 0365      }
 0366    }
 0367  }
 368
 0369  private void LoadSimConfigs(string simulationConfigFile) {
 0370    SimulatorConfig = ConfigLoader.LoadSimulatorConfig(_defaultSimulatorConfigFile);
 371    // If a worker run is provided, enable telemetry logging and event logging.
 0372    if (RunWorker.IsWorkerMode) {
 0373      SimulatorConfig.EnableTelemetryLogging = true;
 0374      SimulatorConfig.EnableEventLogging = true;
 0375    }
 0376    SimulationConfig = ConfigLoader.LoadSimulationConfig(simulationConfigFile);
 0377  }
 378
 0379  private void SetGameSpeed() {
 0380    if (IsPaused) {
 0381      Time.fixedDeltaTime = 0;
 0382      SetTimeScale(timeScale: 0);
 0383    } else {
 0384      Time.fixedDeltaTime = 1.0f / SimulatorConfig.PhysicsUpdateRate;
 0385      SetTimeScale(SimulationConfig.TimeScale);
 0386    }
 0387  }
 388
 0389  private void SetTimeScale(float timeScale) {
 0390    Time.timeScale = timeScale;
 391    // Time.fixedDeltaTime is derived from the simulator configuration.
 0392    Time.maximumDeltaTime = Time.fixedDeltaTime * 3;
 0393  }
 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
 0408  private bool ShouldEndSimulation() {
 0409    if (IsRunning && ElapsedTime >= SimulationConfig.EndTime) {
 0410      return true;
 411    }
 412    // A worker run can end early once all spawned threats have been terminated.
 0413    if (RunWorker.IsWorkerMode && _numThreatsSpawned > 0 &&
 0414        _numThreatsTerminated >= _numThreatsSpawned) {
 0415      return true;
 416    }
 0417    return false;
 0418  }
 419}