using System; using System.Collections.Generic; using System.Linq; using Unity.Simulation; using UnityEngine; using UnityEngine.Perception.GroundTruth.Consumers; using UnityEngine.Perception.GroundTruth.DataModel; using UnityEngine.Rendering; #pragma warning disable 649 namespace UnityEngine.Perception.GroundTruth { /// /// Global manager for frame scheduling and output capture for simulations. /// Data capture follows the schema defined in *TODO: Expose schema publicly* /// public class DatasetCapture { static DatasetCapture s_Instance; SimulationState m_ActiveSimulation; List m_ShuttingDownSimulations = new List(); public static DatasetCapture Instance => s_Instance ?? (s_Instance = new DatasetCapture()); //SimulationState m_SimulationState = null; //List m_ShuttingDownSimulationStates = new List(); Type m_ActiveConsumerType = typeof(SoloConsumer); public bool CanBeShutdown() { Debug.Log($"DC::CanBeShutdown, ready: {m_ReadyToShutdown}, active: {m_ActiveSimulation.IsNotRunning()}, shutting down #: {m_ShuttingDownSimulations.Count}, shutting down ready: {m_ShuttingDownSimulations.All(s => s.IsNotRunning())}"); return m_ReadyToShutdown && m_ActiveSimulation.IsNotRunning() && m_ShuttingDownSimulations.All(s => s.IsNotRunning()); } public class WaitUntilComplete : CustomYieldInstruction { public override bool keepWaiting => !Instance.CanBeShutdown(); } class AllCapturesCompleteShutdownCondition : ICondition { public bool HasConditionBeenMet() { if (Instance.m_ReadyToShutdown) Debug.Log("In here, but I doubt it"); return Instance.CanBeShutdown(); } } bool m_automaticShutdown = true; public bool automaticShutdown { get => m_automaticShutdown; set { switch (value) { case true when !m_automaticShutdown: Manager.Instance.ShutdownCondition = new AllCapturesCompleteShutdownCondition(); break; case false when m_automaticShutdown: Manager.Instance.ShutdownCondition = null; break; } m_automaticShutdown = value; } } DatasetCapture() { Debug.Log("Dataset Capture NEW!!!"); if (automaticShutdown) Manager.Instance.ShutdownCondition = new AllCapturesCompleteShutdownCondition(); Manager.Instance.ShutdownNotification += OnApplicationShutdown; } internal SimulationState currentSimulation { get => m_ActiveSimulation ?? (m_ActiveSimulation = CreateSimulationData()); } #if false internal SimulationState simulationState { get { if (!m_Simulations.Any()) m_Simulations.Add(CreateSimulationData()); return m_Simulations.Last(); } private set => m_Simulations.Add(value); } #endif /// /// The json metadata schema version the DatasetCapture's output conforms to. /// public static string SchemaVersion => "0.0.1"; public static string PerceptionVersion => "0.8.0-preview.4"; /// /// Called when the simulation ends. The simulation ends on playmode exit, application exit, or when is called. /// public event Action SimulationEnding; public SensorHandle RegisterSensor(SensorDefinition sensor) { return currentSimulation.AddSensor(sensor, sensor.simulationDeltaTime); } public void RegisterMetric(MetricDefinition metricDefinition) { currentSimulation.RegisterMetric(metricDefinition); } public void RegisterAnnotationDefinition(AnnotationDefinition definition) { currentSimulation.RegisterAnnotationDefinition(definition); } /// /// Starts a new sequence in the capture. /// public void StartNewSequence() => currentSimulation.StartNewSequence(); internal bool IsValid(string id) => currentSimulation.Contains(id); static ConsumerEndpoint s_Endpoint; static Type s_EndpointType = typeof(SoloConsumer); public static void SetEndpoint(ConsumerEndpoint endpoint) { // TODO I think that we need to do some checking to make sure we're not running s_Endpoint = endpoint; Instance.currentSimulation.consumerEndpoint = endpoint; } public static bool SetEndpointType(Type inType) { if (inType.IsSubclassOf(typeof(ConsumerEndpoint))) { Debug.Log("Setting endpoint type"); s_EndpointType = inType; return true; } Debug.Log("Not setting endpoint type"); return false; } static SimulationState CreateSimulationData() { if (s_Endpoint != null) return new SimulationState(s_Endpoint.GetType()); if (s_EndpointType != null) return new SimulationState(s_EndpointType); throw new InvalidOperationException("Dataset capture cannot create a new simulation state without either a valid consumer endpoint or an endpoint type to instantiate."); } [RuntimeInitializeOnLoadMethod] void OnInitializeOnLoad() { Manager.Instance.ShutdownNotification += OnApplicationShutdown; } bool m_ReadyToShutdown = false; void OnApplicationShutdown() { ResetSimulation(); m_ReadyToShutdown = true; } public void Update() { currentSimulation.Update(); foreach (var simulation in m_ShuttingDownSimulations) { simulation.Update(); } m_ShuttingDownSimulations.RemoveAll(sim => sim.ExecutionState == SimulationState.ExecutionStateType.Complete); } public void ResetSimulation() { SimulationEnding?.Invoke(); if (m_ActiveSimulation.IsRunning()) { var old = m_ActiveSimulation; m_ShuttingDownSimulations.Add(old); old.End(); m_ReadyToShutdown = true; } m_ActiveSimulation = CreateSimulationData(); } } public enum FutureType { Sensor, Annotation, Metric } public interface IAsyncFuture where T : SimulationState.IPendingId { T GetId(); FutureType GetFutureType(); bool IsValid(); bool IsPending(); } public struct AsyncSensorFuture : IAsyncFuture { public AsyncSensorFuture(SimulationState.PendingSensorId id, SimulationState simulationState) { m_Id = id; m_SimulationState = simulationState; } SimulationState.PendingSensorId m_Id; SimulationState m_SimulationState; public SimulationState.PendingSensorId GetId() { return m_Id; } public FutureType GetFutureType() { return FutureType.Sensor; } public bool IsValid() { return m_SimulationState.IsRunning(); } public bool IsPending() { return m_SimulationState.IsPending(this); } public void Report(Sensor sensor) { m_SimulationState.ReportAsyncResult(this, sensor); } } public struct AsyncAnnotationFuture : IAsyncFuture { public AsyncAnnotationFuture(SimulationState.PendingCaptureId id, SimulationState simulationState) { m_Id = id; m_SimulationState = simulationState; } SimulationState.PendingCaptureId m_Id; SimulationState m_SimulationState; public SimulationState.PendingCaptureId GetId() { return m_Id; } public FutureType GetFutureType() { return FutureType.Annotation; } public bool IsValid() { return m_SimulationState.IsRunning(); } public bool IsPending() { return m_SimulationState.IsPending(this); } public void Report(Annotation annotation) { m_SimulationState.ReportAsyncResult(this, annotation); } } public struct AsyncMetricFuture : IAsyncFuture { public AsyncMetricFuture(SimulationState.PendingCaptureId id, SimulationState simulationState) { m_Id = id; m_SimulationState = simulationState; } SimulationState.PendingCaptureId m_Id; SimulationState m_SimulationState; public SimulationState.PendingCaptureId GetId() { return m_Id; } public FutureType GetFutureType() { return FutureType.Metric; } public bool IsValid() { return m_SimulationState.IsRunning(); } public bool IsPending() { return m_SimulationState.IsPending(this); } public void Report(Metric metric) { m_SimulationState.ReportAsyncResult(this, metric); } } /// /// A handle to a sensor managed by the . It can be used to check whether the sensor /// is expected to capture this frame and report captures, annotations, and metrics regarding the sensor. /// public struct SensorHandle : IDisposable, IEquatable { public string Id { get; internal set; } internal SensorHandle(string id) { Id = id ?? string.Empty; } /// /// Whether the sensor is currently enabled. When disabled, the DatasetCapture will no longer schedule frames for running captures on this sensor. /// public bool Enabled { get => DatasetCapture.Instance.currentSimulation.IsEnabled(this); set { CheckValid(); DatasetCapture.Instance.currentSimulation.SetEnabled(this, value); } } public void ReportAnnotation(AnnotationDefinition definition, Annotation annotation) { if (!ShouldCaptureThisFrame) throw new InvalidOperationException("Annotation reported on SensorHandle in frame when its ShouldCaptureThisFrame is false."); if (!definition.IsValid()) throw new ArgumentException("The given annotationDefinition is invalid", nameof(definition)); DatasetCapture.Instance.currentSimulation.ReportAnnotation(this, definition, annotation); } /// /// Creates an async annotation for reporting the values for an annotation during a future frame. /// /// The AnnotationDefinition of this annotation. /// Returns a handle to the , which can be used to report annotation data during a subsequent frame. /// Thrown if this method is called during a frame where is false. /// Thrown if the given AnnotationDefinition is invalid. public AsyncAnnotationFuture ReportAnnotationAsync(AnnotationDefinition annotationDefinition) { if (!ShouldCaptureThisFrame) throw new InvalidOperationException("Annotation reported on SensorHandle in frame when its ShouldCaptureThisFrame is false."); if (!annotationDefinition.IsValid()) throw new ArgumentException("The given annotationDefinition is invalid", nameof(annotationDefinition)); return DatasetCapture.Instance.currentSimulation.ReportAnnotationAsync(annotationDefinition, this); } public AsyncSensorFuture ReportSensorAsync() { if (!ShouldCaptureThisFrame) throw new InvalidOperationException("Annotation reported on SensorHandle in frame when its ShouldCaptureThisFrame is false."); if (!IsValid) throw new ArgumentException($"The given annotationDefinition is invalid {Id}"); return DatasetCapture.Instance.currentSimulation.ReportSensorAsync(this); } public void ReportSensor(Sensor sensor) { if (!ShouldCaptureThisFrame) throw new InvalidOperationException("Annotation reported on SensorHandle in frame when its ShouldCaptureThisFrame is false."); if (!IsValid) throw new ArgumentException("The given annotationDefinition is invalid", Id); DatasetCapture.Instance.currentSimulation.ReportSensor(this, sensor); } /// /// Whether the sensor should capture this frame. Sensors are expected to call this method each frame to determine whether /// they should capture during the frame. Captures should only be reported when this is true. /// public bool ShouldCaptureThisFrame => DatasetCapture.Instance.currentSimulation.ShouldCaptureThisFrame(this); /// /// Requests a capture from this sensor on the next rendered frame. Can only be used with manual capture mode (). /// public void RequestCapture() { DatasetCapture.Instance.currentSimulation.SetNextCaptureTimeToNowForSensor(this); } public void ReportMetric(MetricDefinition definition, Metric metric) { if (!ShouldCaptureThisFrame) throw new InvalidOperationException("Annotation reported on SensorHandle in frame when its ShouldCaptureThisFrame is false."); if (!IsValid) throw new ArgumentException("The given annotationDefinition is invalid", Id); DatasetCapture.Instance.currentSimulation.ReportMetric(this, definition, metric); } #if false public MetricHandle ReportMetric(MetricDefinition definition, Metric metric) { if (metric == null) throw new ArgumentNullException(nameof(metric)); if (!ShouldCaptureThisFrame) throw new InvalidOperationException($"Sensor-based metrics may only be reported when SensorHandle.ShouldCaptureThisFrame is true"); return DatasetCapture.Instance.simulationState.ReportMetric(this, definition, metric, default); } #endif /// /// Start an async metric for reporting metric values for this frame in a subsequent frame. /// /// The of the metric /// Thrown if is false /// An which should be used to report the metric values, potentially in a later frame public AsyncMetricFuture ReportMetricAsync(MetricDefinition metricDefinition) { if (!ShouldCaptureThisFrame) throw new InvalidOperationException($"Sensor-based metrics may only be reported when SensorHandle.ShouldCaptureThisFrame is true"); if (!metricDefinition.IsValid()) throw new ArgumentException("The passed in metric definition is invalid", nameof(metricDefinition)); return DatasetCapture.Instance.currentSimulation.CreateAsyncMetric(metricDefinition, this); } /// /// Dispose this SensorHandle. /// public void Dispose() { this.Enabled = false; } /// /// Returns whether this SensorHandle is valid in the current simulation. Nil SensorHandles are never valid. /// public bool IsValid => DatasetCapture.Instance.IsValid(this.Id); /// /// Returns true if this SensorHandle was default-instantiated. /// public bool IsNil => this == default; void CheckValid() { if (!DatasetCapture.Instance.IsValid(this.Id)) throw new InvalidOperationException("SensorHandle has been disposed or its simulation has ended"); } /// public bool Equals(SensorHandle other) { switch (Id) { case null when other.Id == null: return true; case null: return false; default: return Id.Equals(other.Id); } } /// public override bool Equals(object obj) { return obj is SensorHandle other && Equals(other); } /// public override int GetHashCode() { return Id.GetHashCode(); } /// /// Compares two instances for equality. /// /// The first SensorHandle. /// The second SensorHandle. /// Returns true if the two SensorHandles refer to the same sensor. public static bool operator==(SensorHandle left, SensorHandle right) { return left.Equals(right); } /// /// Compares two instances for inequality. /// /// The first SensorHandle. /// The second SensorHandle. /// Returns false if the two SensorHandles refer to the same sensor. public static bool operator!=(SensorHandle left, SensorHandle right) { return !left.Equals(right); } } /// /// A handle to an annotation. Can be used to report metrics on the annotation. /// public readonly struct AnnotationHandle : IEquatable { readonly AnnotationDefinition m_Definition; /// /// The ID of the annotation which will be used in the json metadata. /// public string Id => m_Definition.id; /// /// The SensorHandle on which the annotation was reported /// public readonly SensorHandle SensorHandle; internal AnnotationHandle(SensorHandle sensorHandle, AnnotationDefinition definition, int sequence, int step) { m_Definition = definition; SensorHandle = sensorHandle; } /// /// Returns true if the annotation is nil (created using default instantiation). /// public bool IsNil => Id == string.Empty; /// public bool Equals(AnnotationHandle other) { return SensorHandle.Equals(other.SensorHandle) && m_Definition.Equals(other.m_Definition); } /// public override bool Equals(object obj) { return obj is AnnotationHandle other && Equals(other); } /// public override int GetHashCode() { var hash = (Id != null ? StringComparer.InvariantCulture.GetHashCode(Id) : 0); return hash; } } }