< Summary

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

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
SimManager()0%2100%
SimManager()0%2100%
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 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.
 034  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.
 044  private static readonly Color _assetColor = new Color(0.75f, 0.4f, 0f);
 45
 046  public static SimManager Instance { get; private set; }
 47
 48  // Simulation configuration.
 049  public Configs.SimulationConfig SimulationConfig { get; set; }
 50
 51  // Simulator configuration.
 052  public Configs.SimulatorConfig SimulatorConfig { get; set; }
 53
 54  // Simulation time.
 055  public float ElapsedTime { get; private set; } = 0f;
 056  public bool IsPaused { get; private set; } = false;
 57
 58  // If true, the simulation is currently running.
 059  public bool IsRunning { get; private set; } = false;
 60
 61  // If true, automatically restart the simulation.
 062  public bool AutoRestartOnEnd { get; set; } = true;
 63
 064  public string Timestamp { get; private set; } = "";
 65
 66  // Lists of all agents in the simulation.
 067  private List<IAgent> _interceptors = new List<IAgent>();
 068  private List<IAgent> _threats = new List<IAgent>();
 069  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.
 076  public float CostLaunchedInterceptors { get; private set; } = 0f;
 077  public float CostDestroyedThreats { get; private set; } = 0f;
 78
 79  // Track the number of interceptors and threats spawned and terminated.
 080  private int _numInterceptorsSpawned = 0;
 081  private int _numThreatsSpawned = 0;
 082  private int _numThreatsTerminated = 0;
 83
 084  public void StartSimulation() {
 085    IsRunning = true;
 086    OnSimulationStarted?.Invoke();
 087    Debug.Log("Simulation started.");
 088    UIManager.Instance?.LogActionMessage("[SIM] Simulation started.");
 89
 090    InitializeAssets();
 091    InitializeLaunchers();
 092    InitializeThreats();
 093  }
 94
 095  public void EndSimulation() {
 096    IsRunning = false;
 097    OnSimulationEnded?.Invoke();
 098    Debug.Log("Simulation ended.");
 099    UIManager.Instance?.LogActionMessage("[SIM] Simulation ended.");
 100
 101    // Clear existing interceptors and threats.
 0102    foreach (var interceptor in _interceptors) {
 0103      if (interceptor as MonoBehaviour != null) {
 0104        Destroy(interceptor.gameObject);
 0105      }
 0106    }
 0107    foreach (var threat in _threats) {
 0108      if (threat as MonoBehaviour != null) {
 0109        Destroy(threat.gameObject);
 0110      }
 0111    }
 0112    foreach (var dummyAgent in _dummyAgents) {
 0113      if (dummyAgent as MonoBehaviour != null) {
 0114        Destroy(dummyAgent.gameObject);
 0115      }
 0116    }
 117
 0118    _interceptors.Clear();
 0119    _threats.Clear();
 0120    _dummyAgents.Clear();
 0121  }
 122
 0123  public void PostSimulation() {
 0124    if (AutoRestartOnEnd) {
 0125      ResetAndStartSimulation();
 0126    }
 0127  }
 128
 0129  public void PauseSimulation() {
 0130    IsPaused = true;
 0131    SetGameSpeed();
 0132  }
 133
 0134  public void ResumeSimulation() {
 0135    IsPaused = false;
 0136    SetGameSpeed();
 0137  }
 138
 0139  public void QuitSimulation() {
 0140    Application.Quit();
 0141  }
 142
 0143  public void ResetAndStartSimulation() {
 0144    ElapsedTime = 0f;
 0145    CostLaunchedInterceptors = 0f;
 0146    CostDestroyedThreats = 0f;
 147
 0148    _numInterceptorsSpawned = 0;
 0149    _numThreatsSpawned = 0;
 0150    _numThreatsTerminated = 0;
 151
 0152    StartSimulation();
 0153  }
 154
 0155  public void LoadNewSimulationConfig(string simulationConfigFile) {
 0156    if (IsRunning) {
 0157      EndSimulation();
 0158    }
 0159    LoadSimConfigs(simulationConfigFile);
 0160    SetGameSpeed();
 161
 0162    if (SimulationConfig != null) {
 0163      Debug.Log($"Loaded new simulation configuration: {simulationConfigFile}.");
 0164      ResetAndStartSimulation();
 0165    } else {
 0166      Debug.LogError($"Failed to load simulation configuration: {simulationConfigFile}.");
 0167    }
 0168  }
 169
 170  // Create an interceptor based on the provided configuration.
 171  public IInterceptor CreateInterceptor(Configs.AgentConfig config, Simulation.State initialState,
 0172                                        bool ignoreMetrics = false) {
 0173    if (config == null) {
 0174      return null;
 175    }
 176
 177    // Load the static configuration.
 0178    Configs.StaticConfig staticConfig = ConfigLoader.LoadStaticConfig(config.ConfigFile);
 0179    if (staticConfig == null) {
 0180      return null;
 181    }
 182
 0183    GameObject interceptorObject = null;
 0184    if (_agentTypePrefabMap.TryGetValue(staticConfig.AgentType, out var prefab)) {
 0185      interceptorObject = CreateAgent(config, initialState, prefab);
 0186    }
 0187    if (interceptorObject == null) {
 0188      return null;
 189    }
 190
 0191    IInterceptor interceptor = interceptorObject.GetComponent<IInterceptor>();
 0192    interceptor.HierarchicalAgent = new HierarchicalAgent(interceptor);
 0193    interceptor.StaticConfig = staticConfig;
 0194    interceptor.OnTerminated += RegisterInterceptorTerminated;
 0195    _interceptors.Add(interceptor);
 0196    ++_numInterceptorsSpawned;
 197
 198    // Assign a unique and simple ID.
 0199    interceptorObject.name = $"{staticConfig.Name}_Interceptor_{_numInterceptorsSpawned}";
 200
 0201    if (!ignoreMetrics) {
 202      // Add the interceptor's unit cost to the total cost.
 0203      CostLaunchedInterceptors += staticConfig.Cost;
 0204    }
 205
 0206    OnNewInterceptor?.Invoke(interceptor);
 0207    return interceptor;
 0208  }
 209
 210  // Create a threat based on the provided configuration.
 211  // Returns the created threat instance, or null if creation failed.
 0212  public IThreat CreateThreat(Configs.AgentConfig config) {
 0213    if (config == null) {
 0214      return null;
 215    }
 216
 217    // Load the static configuration.
 0218    Configs.StaticConfig staticConfig = ConfigLoader.LoadStaticConfig(config.ConfigFile);
 0219    if (staticConfig == null) {
 0220      return null;
 221    }
 222
 0223    GameObject threatObject = null;
 0224    if (_agentTypePrefabMap.TryGetValue(staticConfig.AgentType, out var prefab)) {
 0225      threatObject = CreateRandomAgent(config, prefab);
 0226    }
 0227    if (threatObject == null) {
 0228      return null;
 229    }
 230
 0231    IThreat threat = threatObject.GetComponent<IThreat>();
 0232    threat.HierarchicalAgent = new HierarchicalAgent(threat);
 0233    threat.StaticConfig = staticConfig;
 0234    threat.OnDestroyed += RegisterThreatDestroyed;
 0235    threat.OnTerminated += RegisterThreatTerminated;
 0236    _threats.Add(threat);
 0237    ++_numThreatsSpawned;
 238
 239    // Assign a unique name.
 0240    threatObject.name = $"{staticConfig.Name}_Threat_{_numThreatsSpawned}";
 241
 0242    OnNewThreat?.Invoke(threat);
 0243    return threat;
 0244  }
 245
 0246  public IAgent CreateDummyAgent(in Vector3 position, in Vector3 velocity) {
 0247    GameObject dummyAgentPrefab = Resources.Load<GameObject>($"Prefabs/DummyAgent");
 0248    GameObject dummyAgentObject = Instantiate(dummyAgentPrefab, position, Quaternion.identity);
 0249    var dummyAgent = dummyAgentObject.GetComponent<IAgent>();
 0250    dummyAgent.Velocity = velocity;
 0251    _dummyAgents.Add(dummyAgent);
 0252    dummyAgent.OnTerminated += (agent) => _dummyAgents.Remove(agent);
 0253    return dummyAgent;
 0254  }
 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,
 0262                                 string prefabName) {
 0263    GameObject prefab = Resources.Load<GameObject>($"Prefabs/{prefabName}");
 0264    if (prefab == null) {
 0265      Debug.LogError($"Prefab {prefabName} not found in Resources/Prefabs directory.");
 0266      return null;
 267    }
 268
 0269    GameObject agentObject = Instantiate(prefab, Coordinates3.FromProto(initialState.Position),
 270                                         prefab.transform.rotation);
 0271    IAgent agent = agentObject.GetComponent<IAgent>();
 0272    agent.AgentConfig = config;
 0273    Vector3 velocity = Coordinates3.FromProto(initialState.Velocity);
 0274    agent.Velocity = velocity;
 275
 276    // Set the rotation to face the initial velocity.
 0277    if (velocity.sqrMagnitude > Mathf.Epsilon) {
 0278      Quaternion targetRotation = Quaternion.LookRotation(velocity, Vector3.up);
 0279      agentObject.transform.rotation = targetRotation;
 0280    }
 0281    return agentObject;
 0282  }
 283
 284  // Create a random agent based on the provided configuration and prefab name.
 0285  private GameObject CreateRandomAgent(Configs.AgentConfig config, string prefabName) {
 286    // Randomize the position and the velocity.
 0287    Vector3 positionNoise = Utilities.GenerateRandomNoise(config.StandardDeviation.Position);
 0288    Vector3 velocityNoise = Utilities.GenerateRandomNoise(config.StandardDeviation.Velocity);
 0289    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    };
 0295    return CreateAgent(config, initialState, prefabName);
 0296  }
 297
 0298  private void Awake() {
 0299    if (Instance != null && Instance != this) {
 0300      Destroy(gameObject);
 0301      return;
 302    }
 0303    Instance = this;
 0304    DontDestroyOnLoad(gameObject);
 305
 0306    if (!RunWorker.IsWorkerMode) {
 0307      LoadSimConfigs(_defaultSimulationConfigFile);
 0308    }
 0309    Timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
 0310  }
 311
 0312  private System.Collections.IEnumerator Start() {
 0313    IsPaused = false;
 314    // Wait one frame so every manager has finished Start() and subscribed to simulation events.
 0315    yield return null;
 316
 0317    if (!RunWorker.IsWorkerMode) {
 0318      StartSimulation();
 0319      ResumeSimulation();
 0320    }
 0321  }
 322
 0323  private void FixedUpdate() {
 0324    if (IsRunning && !IsPaused && ElapsedTime < SimulationConfig.EndTime) {
 0325      ElapsedTime += Time.fixedDeltaTime;
 0326    }
 0327  }
 328
 0329  private void LateUpdate() {
 0330    if (ShouldEndSimulation()) {
 0331      EndSimulation();
 0332      PostSimulation();
 0333    }
 0334  }
 335
 0336  private void InitializeAssets() {
 0337    foreach (var assetConfig in SimulationConfig.AssetConfigs) {
 0338      IInterceptor asset =
 339          CreateInterceptor(assetConfig, assetConfig.InitialState, ignoreMetrics: true);
 0340      if (asset != null) {
 341        // Change the color of the asset to be orange.
 0342        Renderer[] renderers = asset.gameObject.GetComponentsInChildren<Renderer>();
 0343        foreach (var renderer in renderers) {
 0344          var propertyBlock = new MaterialPropertyBlock();
 0345          propertyBlock.SetColor("_BaseColor", _assetColor);
 0346          propertyBlock.SetColor("_Color", _assetColor);
 0347          renderer.SetPropertyBlock(propertyBlock);
 0348        }
 0349        OnNewAsset?.Invoke(asset);
 0350      }
 0351    }
 0352  }
 353
 0354  private void InitializeLaunchers() {
 0355    foreach (var swarmConfig in SimulationConfig.InterceptorSwarmConfigs) {
 0356      IInterceptor launcher = CreateInterceptor(
 357          swarmConfig.AgentConfig, swarmConfig.AgentConfig.InitialState, ignoreMetrics: true);
 0358      if (launcher != null) {
 0359        OnNewLauncher?.Invoke(launcher);
 360        // All launchers are assets.
 0361        OnNewAsset?.Invoke(launcher);
 0362      }
 0363    }
 0364  }
 365
 0366  private void InitializeThreats() {
 0367    foreach (var swarmConfig in SimulationConfig.ThreatSwarmConfigs) {
 0368      for (int i = 0; i < swarmConfig.NumAgents; ++i) {
 0369        CreateThreat(swarmConfig.AgentConfig);
 0370      }
 0371    }
 0372  }
 373
 0374  private void LoadSimConfigs(string simulationConfigFile) {
 0375    SimulatorConfig = ConfigLoader.LoadSimulatorConfig(_defaultSimulatorConfigFile);
 376    // If a worker run is provided, enable telemetry logging and event logging.
 0377    if (RunWorker.IsWorkerMode) {
 0378      SimulatorConfig.EnableTelemetryLogging = true;
 0379      SimulatorConfig.EnableEventLogging = true;
 0380    }
 0381    SimulationConfig = ConfigLoader.LoadSimulationConfig(simulationConfigFile);
 0382  }
 383
 0384  private void SetGameSpeed() {
 0385    if (IsPaused) {
 0386      Time.fixedDeltaTime = 0;
 0387      SetTimeScale(timeScale: 0);
 0388    } else {
 0389      Time.fixedDeltaTime = 1.0f / SimulatorConfig.PhysicsUpdateRate;
 0390      SetTimeScale(SimulationConfig.TimeScale);
 0391    }
 0392  }
 393
 0394  private void SetTimeScale(float timeScale) {
 0395    Time.timeScale = timeScale;
 396    // Time.fixedDeltaTime is derived from the simulator configuration.
 0397    Time.maximumDeltaTime = Time.fixedDeltaTime * 3;
 0398  }
 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
 0413  private bool ShouldEndSimulation() {
 0414    if (IsRunning && ElapsedTime >= SimulationConfig.EndTime) {
 0415      return true;
 416    }
 417    // A worker run can end early once all spawned threats have been terminated.
 0418    if (RunWorker.IsWorkerMode && _numThreatsSpawned > 0 &&
 0419        _numThreatsTerminated >= _numThreatsSpawned) {
 0420      return true;
 421    }
 0422    return false;
 0423  }
 424}