< Summary

Class:RunWorker
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Managers/RunWorker.cs
Covered lines:1
Uncovered lines:90
Coverable lines:91
Total lines:153
Line coverage:1% (1 of 91)
Covered branches:0
Total branches:0
Covered methods:2
Total methods:21
Method coverage:9.5% (2 of 21)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
RunWorker()0%2100%
OnBeforeSceneLoad()0%6200%
TryGetWorkerModeArguments(...)0%90900%
Awake()0%12300%
Start()0%6200%
Initialize(...)0%2100%
RunWhenReady()0%42600%
PrepareOutputDirectory()0%6200%
RegisterSimulationEnded()0%20400%
QuitAfterCleanup()0%12300%
GetArgValue(...)0%30500%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections;
 3using System.IO;
 4using UnityEngine;
 5
 6// The run worker executes exactly one seeded simulation run launched externally, e.g., through the
 7// Python batch run launcher. It applies the seed, starts the requested simulation configuration,
 8// writes logs to the assigned output directory, and quits after the simulation finishes.
 9
 10public class RunWorker : MonoBehaviour {
 11  private const string _simulationConfigFlag = "--simulation_config";
 12  private const string _seedFlag = "--seed";
 13  private const string _outputDirFlag = "--output_dir";
 14
 015  public static RunWorker Instance { get; private set; }
 16
 17  // True if the run worker was launched from the CLI, e.g., as part of a batch run, rather than
 18  // from normal interactive mode.
 5419  public static bool IsWorkerMode { get; private set; } = false;
 20
 21  // Simulation configuration to execute.
 022  public static string SimulationConfigFile { get; private set; }
 23
 24  // Simulation run seed.
 025  public static int Seed { get; private set; } = 0;
 26
 27  // Output directory of the simulation run.
 028  public static string OutputDirectory { get; private set; }
 29
 030  private bool _hasStartedRun = false;
 031  private bool _hasScheduledQuit = false;
 32
 33  [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
 034  private static void OnBeforeSceneLoad() {
 035    if (!TryGetWorkerModeArguments(Environment.GetCommandLineArgs(),
 36                                   out string simulationConfigFile, out int seed,
 037                                   out string outputDirectory)) {
 038      return;
 39    }
 40
 041    var gameObject = new GameObject("RunWorker");
 042    DontDestroyOnLoad(gameObject);
 043    var runWorker = gameObject.AddComponent<RunWorker>();
 044    runWorker.Initialize(simulationConfigFile, seed, outputDirectory);
 045  }
 46
 47  public static bool TryGetWorkerModeArguments(string[] args, out string simulationConfigFile,
 048                                               out int seed, out string outputDirectory) {
 049    simulationConfigFile = GetArgValue(args, _simulationConfigFlag);
 050    string seedValue = GetArgValue(args, _seedFlag);
 051    string rawOutputDirectory = GetArgValue(args, _outputDirFlag);
 052    seed = 0;
 053    outputDirectory = null;
 54
 055    if (simulationConfigFile == null && seedValue == null && rawOutputDirectory == null) {
 056      return false;
 57    }
 58
 059    if (string.IsNullOrWhiteSpace(simulationConfigFile) || string.IsNullOrWhiteSpace(seedValue) ||
 060        string.IsNullOrWhiteSpace(rawOutputDirectory)) {
 061      throw new ArgumentException("Worker mode requires simulation config, seed, and output dir.");
 62    }
 063    if (!int.TryParse(seedValue, out seed)) {
 064      throw new ArgumentException($"Failed to parse worker seed: {seedValue}.");
 65    }
 066    if (!Path.IsPathRooted(rawOutputDirectory)) {
 067      throw new ArgumentException(
 68          $"Worker output directory must be absolute: {rawOutputDirectory}.");
 69    }
 70
 071    outputDirectory = Path.GetFullPath(rawOutputDirectory);
 072    return true;
 073  }
 74
 075  private void Awake() {
 076    if (Instance != null && Instance != this) {
 077      Destroy(gameObject);
 078      return;
 79    }
 080    Instance = this;
 081    DontDestroyOnLoad(gameObject);
 082  }
 83
 084  private void Start() {
 085    if (!IsWorkerMode) {
 086      Destroy(gameObject);
 087      return;
 88    }
 89
 090    Application.targetFrameRate = -1;
 091    StartCoroutine(RunWhenReady());
 092  }
 93
 094  private void Initialize(string simulationConfigFile, int seed, string outputDirectory) {
 095    IsWorkerMode = true;
 096    SimulationConfigFile = simulationConfigFile;
 097    Seed = seed;
 098    OutputDirectory = outputDirectory;
 099  }
 100
 0101  private IEnumerator RunWhenReady() {
 0102    while (SimManager.Instance == null) {
 0103      yield return null;
 0104    }
 105
 0106    SimManager.Instance.AutoRestartOnEnd = false;
 0107    SimManager.Instance.OnSimulationEnded += RegisterSimulationEnded;
 108
 109    // Allow scene initialization and manager subscriptions to finish first.
 0110    yield return null;
 111
 0112    PrepareOutputDirectory();
 0113    UnityEngine.Random.InitState(Seed);
 0114    _hasStartedRun = true;
 0115    Debug.Log($"Starting run with simulation config {SimulationConfigFile} and seed {Seed}.");
 0116    SimManager.Instance.LoadNewSimulationConfig(SimulationConfigFile);
 0117  }
 118
 0119  private void PrepareOutputDirectory() {
 0120    if (Directory.Exists(OutputDirectory)) {
 0121      throw new IOException(
 122          $"Output directory already exists: {OutputDirectory}. Refusing to overwrite.");
 123    }
 0124    Directory.CreateDirectory(OutputDirectory);
 0125  }
 126
 0127  private void RegisterSimulationEnded() {
 0128    if (!_hasStartedRun || _hasScheduledQuit) {
 0129      return;
 130    }
 131
 0132    _hasScheduledQuit = true;
 0133    StartCoroutine(QuitAfterCleanup());
 0134  }
 135
 0136  private IEnumerator QuitAfterCleanup() {
 137    // Allow end-of-run cleanup, such as log flushing, to complete first.
 0138    yield return null;
 0139    SimManager.Instance.QuitSimulation();
 0140  }
 141
 0142  private static string GetArgValue(string[] args, string name) {
 0143    for (int i = 0; i < args.Length; ++i) {
 0144      if (args[i].Equals(name, StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length) {
 0145        return args[i + 1];
 146      }
 0147      if (args[i].StartsWith(name + "=", StringComparison.OrdinalIgnoreCase)) {
 0148        return args[i].Substring(name.Length + 1);
 149      }
 0150    }
 0151    return null;
 0152  }
 153}