< Summary

Class:RunWorker
Assembly:bamlab.micromissiles
File(s):/github/workspace/Assets/Scripts/Managers/RunWorker.cs
Covered lines:1
Uncovered lines:102
Coverable lines:103
Total lines:166
Line coverage:0.9% (1 of 103)
Covered branches:0
Total branches:0
Covered methods:2
Total methods:22
Method coverage:9% (2 of 22)

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%
OnDestroy()0%12300%
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 OnDestroy() {
 095    if (SimManager.Instance != null) {
 096      SimManager.Instance.OnSimulationEnded -= RegisterSimulationEnded;
 097    }
 098    if (Instance == this) {
 099      Instance = null;
 0100      IsWorkerMode = false;
 0101      SimulationConfigFile = null;
 0102      Seed = 0;
 0103      OutputDirectory = null;
 0104    }
 0105  }
 106
 0107  private void Initialize(string simulationConfigFile, int seed, string outputDirectory) {
 0108    IsWorkerMode = true;
 0109    SimulationConfigFile = simulationConfigFile;
 0110    Seed = seed;
 0111    OutputDirectory = outputDirectory;
 0112  }
 113
 0114  private IEnumerator RunWhenReady() {
 0115    while (SimManager.Instance == null) {
 0116      yield return null;
 0117    }
 118
 0119    SimManager.Instance.AutoRestartOnEnd = false;
 0120    SimManager.Instance.OnSimulationEnded += RegisterSimulationEnded;
 121
 122    // Allow scene initialization and manager subscriptions to finish first.
 0123    yield return null;
 124
 0125    PrepareOutputDirectory();
 0126    UnityEngine.Random.InitState(Seed);
 0127    _hasStartedRun = true;
 0128    Debug.Log($"Starting run with simulation config {SimulationConfigFile} and seed {Seed}.");
 0129    SimManager.Instance.LoadNewSimulationConfig(SimulationConfigFile);
 0130  }
 131
 0132  private void PrepareOutputDirectory() {
 0133    if (Directory.Exists(OutputDirectory)) {
 0134      throw new IOException(
 135          $"Output directory already exists: {OutputDirectory}. Refusing to overwrite.");
 136    }
 0137    Directory.CreateDirectory(OutputDirectory);
 0138  }
 139
 0140  private void RegisterSimulationEnded() {
 0141    if (!_hasStartedRun || _hasScheduledQuit) {
 0142      return;
 143    }
 144
 0145    _hasScheduledQuit = true;
 0146    StartCoroutine(QuitAfterCleanup());
 0147  }
 148
 0149  private IEnumerator QuitAfterCleanup() {
 150    // Allow end-of-run cleanup, such as log flushing, to complete first.
 0151    yield return null;
 0152    SimManager.Instance.QuitSimulation();
 0153  }
 154
 0155  private static string GetArgValue(string[] args, string name) {
 0156    for (int i = 0; i < args.Length; ++i) {
 0157      if (args[i].Equals(name, StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length) {
 0158        return args[i + 1];
 159      }
 0160      if (args[i].StartsWith(name + "=", StringComparison.OrdinalIgnoreCase)) {
 0161        return args[i].Substring(name.Length + 1);
 162      }
 0163    }
 0164    return null;
 0165  }
 166}