| | | 1 | | using System; |
| | | 2 | | using System.Collections; |
| | | 3 | | using UnityEngine; |
| | | 4 | | |
| | | 5 | | // The run manager handles running the same simulation configuration multiple times. |
| | | 6 | | // It implements the singleton pattern to ensure that only one instance exists. |
| | | 7 | | public class RunManager : MonoBehaviour { |
| | | 8 | | private const string _configFlag = "--config"; |
| | | 9 | | |
| | 0 | 10 | | public static RunManager Instance { get; private set; } |
| | | 11 | | |
| | | 12 | | // Run configuration. |
| | 0 | 13 | | public Configs.RunConfig RunConfig { get; set; } |
| | | 14 | | |
| | | 15 | | // If true, the simulation is currently running. |
| | 0 | 16 | | public bool IsRunning { get; private set; } = false; |
| | | 17 | | |
| | 0 | 18 | | public int RunIndex { get; private set; } = 0; |
| | | 19 | | |
| | 0 | 20 | | public int Seed { get; private set; } = 0; |
| | | 21 | | |
| | 0 | 22 | | public bool HasRunConfig() { |
| | 0 | 23 | | return RunConfig != null; |
| | 0 | 24 | | } |
| | | 25 | | |
| | | 26 | | [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] |
| | 0 | 27 | | private static void OnBeforeSceneLoad() { |
| | 0 | 28 | | string runConfigFile = TryGetCommandLineArg(_configFlag); |
| | 0 | 29 | | if (runConfigFile == null) { |
| | 0 | 30 | | return; |
| | | 31 | | } |
| | 0 | 32 | | Configs.RunConfig runConfig = ConfigLoader.LoadRunConfig(runConfigFile); |
| | 0 | 33 | | if (runConfig == null) { |
| | 0 | 34 | | Debug.LogWarning( |
| | | 35 | | $"Failed to load run configuration from: {runConfigFile}. Application will not start batch mode."); |
| | 0 | 36 | | return; |
| | | 37 | | } |
| | | 38 | | |
| | | 39 | | // Create a game object to run coroutines. |
| | 0 | 40 | | var gameObject = new GameObject("RunManager"); |
| | 0 | 41 | | DontDestroyOnLoad(gameObject); |
| | 0 | 42 | | var runManager = gameObject.AddComponent<RunManager>(); |
| | 0 | 43 | | runManager.InitializeFromRunConfig(runConfig); |
| | 0 | 44 | | } |
| | | 45 | | |
| | 0 | 46 | | private void InitializeFromRunConfig(Configs.RunConfig runConfig) { |
| | 0 | 47 | | RunConfig = runConfig; |
| | 0 | 48 | | IsRunning = false; |
| | 0 | 49 | | RunIndex = 0; |
| | 0 | 50 | | Seed = RunConfig.Seed; |
| | 0 | 51 | | } |
| | | 52 | | |
| | 0 | 53 | | private void Start() { |
| | 0 | 54 | | if (RunConfig != null) { |
| | 0 | 55 | | Application.targetFrameRate = -1; |
| | | 56 | | |
| | | 57 | | // Disable automatically restarting at the end of the simulation. |
| | 0 | 58 | | SimManager.Instance.AutoRestartOnEnd = false; |
| | 0 | 59 | | SimManager.Instance.OnSimulationStarted += RegisterSimulationStarted; |
| | 0 | 60 | | SimManager.Instance.OnSimulationEnded += RegisterSimulationEnded; |
| | | 61 | | |
| | 0 | 62 | | StartCoroutine(Run()); |
| | 0 | 63 | | } |
| | 0 | 64 | | } |
| | | 65 | | |
| | 0 | 66 | | private void Awake() { |
| | 0 | 67 | | if (Instance != null && Instance != this) { |
| | 0 | 68 | | Destroy(gameObject); |
| | 0 | 69 | | return; |
| | | 70 | | } |
| | 0 | 71 | | Instance = this; |
| | 0 | 72 | | DontDestroyOnLoad(gameObject); |
| | 0 | 73 | | } |
| | | 74 | | |
| | 0 | 75 | | private IEnumerator Run() { |
| | 0 | 76 | | if (RunConfig.NumRuns == 0) { |
| | 0 | 77 | | yield break; |
| | | 78 | | } |
| | | 79 | | |
| | | 80 | | // Allow one frame for initialization to finish. |
| | 0 | 81 | | yield return null; |
| | | 82 | | |
| | | 83 | | // Seed the random number generator. |
| | 0 | 84 | | UnityEngine.Random.InitState(Seed); |
| | | 85 | | |
| | 0 | 86 | | Debug.Log( |
| | | 87 | | $"Starting run {RunIndex + 1} with seed {Seed} and simulation config {RunConfig.SimulationConfigFile}."); |
| | 0 | 88 | | SimManager.Instance.LoadNewSimulationConfig(RunConfig.SimulationConfigFile); |
| | 0 | 89 | | } |
| | | 90 | | |
| | 0 | 91 | | private IEnumerator Advance() { |
| | | 92 | | // Allow one frame for cleanup to finish, such as simulation monitoring. |
| | 0 | 93 | | yield return null; |
| | | 94 | | |
| | 0 | 95 | | ++RunIndex; |
| | 0 | 96 | | if (RunIndex >= RunConfig.NumRuns) { |
| | 0 | 97 | | Debug.Log($"Completed run {RunConfig.Name} with {RunConfig.NumRuns} runs."); |
| | 0 | 98 | | SimManager.Instance.QuitSimulation(); |
| | 0 | 99 | | yield break; |
| | | 100 | | } |
| | 0 | 101 | | Seed += RunConfig.SeedStride; |
| | | 102 | | |
| | 0 | 103 | | yield return Run(); |
| | 0 | 104 | | } |
| | | 105 | | |
| | 0 | 106 | | private void RegisterSimulationStarted() { |
| | 0 | 107 | | IsRunning = true; |
| | 0 | 108 | | } |
| | | 109 | | |
| | 0 | 110 | | private void RegisterSimulationEnded() { |
| | 0 | 111 | | if (!IsRunning) { |
| | 0 | 112 | | return; |
| | | 113 | | } |
| | 0 | 114 | | IsRunning = false; |
| | 0 | 115 | | StartCoroutine(Advance()); |
| | 0 | 116 | | } |
| | | 117 | | |
| | 0 | 118 | | private static string TryGetCommandLineArg(string name) { |
| | 0 | 119 | | try { |
| | 0 | 120 | | var args = Environment.GetCommandLineArgs(); |
| | 0 | 121 | | return GetArgValue(args, name); |
| | 0 | 122 | | } catch (Exception e) { |
| | 0 | 123 | | Debug.LogWarning($"Failed to parse command line args: {e.Message}"); |
| | 0 | 124 | | return null; |
| | | 125 | | } |
| | 0 | 126 | | } |
| | | 127 | | |
| | 0 | 128 | | private static string GetArgValue(string[] args, string name) { |
| | 0 | 129 | | for (int i = 0; i < args.Length; ++i) { |
| | 0 | 130 | | if (args[i].Equals(name, StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length) { |
| | 0 | 131 | | return args[i + 1]; |
| | | 132 | | } |
| | 0 | 133 | | if (args[i].StartsWith(name + "=", StringComparison.OrdinalIgnoreCase)) { |
| | 0 | 134 | | return args[i].Substring(name.Length + 1); |
| | | 135 | | } |
| | 0 | 136 | | } |
| | 0 | 137 | | return null; |
| | 0 | 138 | | } |
| | | 139 | | } |