浏览代码

refactored scenario lifecycle hooks

/main
Steven Leal 4 年前
当前提交
0104a889
共有 12 个文件被更改,包括 203 次插入207 次删除
  1. 15
      com.unity.perception/Editor/Randomization/Editors/ScenarioBaseEditor.cs
  2. 7
      com.unity.perception/Editor/Randomization/Uxml/ScenarioBaseElement.uxml
  3. 5
      com.unity.perception/Editor/Randomization/VisualElements/Randomizer/AddRandomizerMenu.cs
  4. 8
      com.unity.perception/Editor/Randomization/VisualElements/Randomizer/RandomizerList.cs
  5. 7
      com.unity.perception/Runtime/Randomization/Scenarios/Scenario.cs
  6. 326
      com.unity.perception/Runtime/Randomization/Scenarios/ScenarioBase.cs
  7. 2
      com.unity.perception/Runtime/Randomization/Scenarios/Serialization/ScenarioSerializer.cs
  8. 1
      com.unity.perception/Runtime/Randomization/Scenarios/Serialization/SerializationStructures.cs
  9. 21
      com.unity.perception/Runtime/Randomization/Scenarios/UnitySimulationScenario.cs
  10. 2
      com.unity.perception/Tests/Editor/RandomizerEditorTests.cs
  11. 10
      com.unity.perception/Tests/Runtime/Randomization/RandomizerTests/RandomizerTests.cs
  12. 6
      com.unity.perception/Tests/Runtime/Randomization/ScenarioTests/ScenarioTests.cs

15
com.unity.perception/Editor/Randomization/Editors/ScenarioBaseEditor.cs


CreatePropertyFields();
CheckIfConstantsExist();
var serializeConstantsButton = m_Root.Q<Button>("serialize");
var serializeConstantsButton = m_Root.Q<Button>("generate-json-config");
m_Scenario.SerializeToFile();
var filePath = EditorUtility.SaveFilePanel(
"Generate Scenario JSON Configuration", Application.dataPath, "scenarioConfiguration", "json");
m_Scenario.SerializeToFile(filePath);
var newConfigFileAsset = AssetDatabase.LoadAssetAtPath<Object>(m_Scenario.defaultConfigFileAssetPath);
EditorGUIUtility.PingObject(newConfigFileAsset);
EditorUtility.RevealInFinder(filePath);
var deserializeConstantsButton = m_Root.Q<Button>("deserialize");
var deserializeConstantsButton = m_Root.Q<Button>("import-json-config");
var filePath = EditorUtility.OpenFilePanel(
"Import Scenario JSON Configuration", Application.dataPath, "json");
m_Scenario.DeserializeFromFile(m_Scenario.defaultConfigFilePath);
m_Scenario.DeserializeFromFile(filePath);
};
return m_Root;

7
com.unity.perception/Editor/Randomization/Uxml/ScenarioBaseElement.uxml


<TextElement
class="scenario__info-box"
text="Scenarios control the execution flow of your simulation by applying randomization parameters. Make sure to always have only one scenario active within your scene."/>
<Toggle label="Quit On Complete" tooltip="Quit the application when the scenario completes" binding-path="quitOnComplete" style="margin-top:5px"/>
<Button name="serialize" text="Serialize To Config File" style="flex-grow: 1;"
<Button name="generate-json-config" text="Generate JSON Config" style="flex-grow: 1;"
<Button name="deserialize" text="Deserialize From Config File" style="flex-grow: 1;"
tooltip="Deserializes scenario constants and randomizer settings from a scenario_configuration.json file located in the Assets/StreamingAssets project folder"/>
<Button name="import-json-config" text="Import JSON Config" style="flex-grow: 1;"
tooltip="Imports scenario constants and randomizer settings from a selected JSON file"/>
</VisualElement>
</VisualElement>
</VisualElement>

5
com.unity.perception/Editor/Randomization/VisualElements/Randomizer/AddRandomizerMenu.cs


m_MenuItemsMap.Add(string.Empty, rootList);
var randomizerTypeSet = new HashSet<Type>();
foreach (var randomizer in m_RandomizerList.scenario.m_Randomizers)
foreach (var randomizer in m_RandomizerList.scenario.randomizers)
randomizerTypeSet.Add(randomizer.GetType());
foreach (var randomizerType in StaticData.randomizerTypes)

var menuAttribute = (AddRandomizerMenuAttribute)Attribute.GetCustomAttribute(randomizerType, typeof(AddRandomizerMenuAttribute));
var menuAttribute = (AddRandomizerMenuAttribute)Attribute.GetCustomAttribute(
randomizerType, typeof(AddRandomizerMenuAttribute));
if (menuAttribute != null)
{
var pathItems = menuAttribute.menuPath.Split('/');

8
com.unity.perception/Editor/Randomization/VisualElements/Randomizer/RandomizerList.cs


public void RemoveRandomizer(RandomizerElement element)
{
Undo.RegisterCompleteObjectUndo(m_Property.serializedObject.targetObject, "Remove Randomizer");
scenario.RemoveRandomizer(element.randomizerType);
scenario.RemoveRandomizerAt(element.parent.IndexOf(element));
m_Property.serializedObject.Update();
RefreshList();
}

if (currentIndex == nextIndex)
return;
if (nextIndex > currentIndex)
nextIndex--;
scenario.ReorderRandomizer(currentIndex, nextIndex);
var randomizer = scenario.GetRandomizer(currentIndex);
scenario.RemoveRandomizerAt(currentIndex);
scenario.InsertRandomizer(nextIndex, randomizer);
m_Property.serializedObject.Update();
RefreshList();
}

7
com.unity.perception/Runtime/Randomization/Scenarios/Scenario.cs


using System;
using UnityEngine.Perception.Randomization.Scenarios.Serialization;
namespace UnityEngine.Perception.Randomization.Scenarios
{

/// <inheritdoc/>
public override ScenarioConstants genericConstants => constants;
/// <inheritdoc/>
public override void DeserializeFromJson(string json)
{
ScenarioSerializer.Deserialize(this, json);
}
}
}

326
com.unity.perception/Runtime/Randomization/Scenarios/ScenarioBase.cs


{
const string k_ScenarioIterationMetricDefinitionId = "DB1B258E-D1D0-41B6-8751-16F601A2E230";
static ScenarioBase s_ActiveScenario;
bool m_FirstScenarioFrame = true;
// ReSharper disable once InconsistentNaming
[SerializeReference] internal List<Randomizer> m_Randomizers = new List<Randomizer>();
bool m_SkipFrame = true;
bool m_WaitingForFinalUploads;
/// <summary>
/// The list of randomizers managed by this scenario
/// </summary>
[SerializeReference] protected List<Randomizer> m_Randomizers = new List<Randomizer>();
/// <summary>
/// On some platforms, the simulation capture package cannot capture the first frame of output,
/// so this is used to track whether the first frame has been skipped yet.
/// </summary>
protected bool m_SkipFrame = true;
/// If true, this scenario will quit the Unity application when it's finished executing
/// Setting this field to true will cause the scenario to enter an idle state. By default, scenarios will enter
/// the idle state after its isScenarioComplete property has returned true.
[HideInInspector]
public bool quitOnComplete = true;
protected bool m_Idle;
IEnumerable<Randomizer> activeRandomizers
/// <summary>
/// Enumerates over all enabled randomizers
/// </summary>
public IEnumerable<Randomizer> activeRandomizers
{
get
{

/// Return the list of randomizers attached to this scenario
/// </summary>
public IReadOnlyList<Randomizer> randomizers => m_Randomizers.AsReadOnly();
/// <summary>
/// The name of the Json file this scenario's constants are serialized to/from.
/// </summary>
public virtual string configFileName => "scenario_configuration";
/// <summary>
/// Returns the active parameter scenario in the scene

}
/// <summary>
/// Returns the asset location of the JSON serialized configuration.
/// This API is used for finding the config file using the AssetDatabase API.
/// </summary>
public string defaultConfigFileAssetPath =>
"Assets/StreamingAssets/" + configFileName + ".json";
/// <summary>
/// Returns the absolute file path of the JSON serialized configuration
/// </summary>
public virtual string defaultConfigFilePath =>
Application.dataPath + "/StreamingAssets/" + configFileName + ".json";
/// <summary>
/// Returns this scenario's non-typed serialized constants
/// </summary>
public abstract ScenarioConstants genericConstants { get; }

public abstract bool isScenarioComplete { get; }
/// <summary>
/// Progresses the current scenario iteration
/// This method selects what the next iteration index will be. By default, the scenario will simply progress to
/// the next iteration, but this behaviour can be overriden.
/// </summary>
protected virtual void IncrementIteration()
{

/// Serializes the scenario's constants and randomizer settings to a JSON file located at the path resolved by
/// the defaultConfigFilePath scenario property
/// </summary>
public virtual void SerializeToFile()
public virtual void SerializeToFile(string filePath)
ScenarioSerializer.SerializeToFile(this, defaultConfigFilePath);
ScenarioSerializer.SerializeToFile(this, filePath);
}
/// <summary>

/// <param name="configFilePath">The file path to the configuration file to deserialize</param>
public virtual void DeserializeFromFile(string configFilePath)
{
if (string.IsNullOrEmpty(configFilePath) || !File.Exists(configFilePath))
Debug.Log($"No configuration file found at {defaultConfigFilePath}");
else
{
if (string.IsNullOrEmpty(configFilePath))
throw new ArgumentException($"{nameof(configFilePath)} is null or empty");
if (!File.Exists(configFilePath))
throw new ArgumentException($"No configuration file found at {configFilePath}");
var jsonText = File.ReadAllText(configFilePath);
DeserializeFromJson(jsonText);
var absolutePath = Path.GetFullPath(configFilePath);
Debug.Log($"Deserialized scenario configuration from <a href=\"file:///${configFilePath}\">{configFilePath}</a>. " +
"Using undo in the editor will revert these changes to your scenario.");
Debug.Log($"Deserialized scenario configuration from {absolutePath}. " +
"Using undo in the editor will revert these changes to your scenario.");
Debug.Log($"Deserialized scenario configuration from <a href=\"file:///${configFilePath}\">{configFilePath}</a>");
Debug.Log($"Deserialized scenario configuration from {absolutePath}");
var jsonText = File.ReadAllText(configFilePath);
DeserializeFromJson(jsonText);
}
}
/// <summary>
/// Resets SamplerState.randomState with a new seed value generated by hashing this Scenario's randomSeed
/// with its currentIteration
/// </summary>
protected virtual void ResetRandomStateOnIteration()
{
SamplerState.randomState = SamplerUtility.IterateSeed((uint)currentIteration, genericConstants.randomSeed);
/// Overwrites this scenario's randomizer settings and scenario constants using a configuration file located at
/// this scenario's defaultConfigFilePath
/// OnAwake is called right after this scenario MonoBehaviour is created or instantiated
/// </summary>
protected virtual void OnAwake() { }
/// <summary>
/// OnStart is called after Awake but before the first Update method call
public void DeserializeFromFile()
protected virtual void OnStart()
DeserializeFromFile(defaultConfigFilePath);
#if !UNITY_EDITOR
var args = Environment.GetCommandLineArgs();
var filePath = string.Empty;
for (var i = 0; i < args.Length; i++)
{
if (args[i] == "--scenario-config-file" && args.Length > i + 1)
{
filePath = args[i + 1];
break;
}
}
if (string.IsNullOrEmpty(filePath))
{
Debug.Log("No --scenario-config-file command line arg specified. " +
"Proceeding with editor assigned scenario configuration values.");
return;
}
try
{
DeserializeFromFile(filePath);
}
catch (Exception exception)
{
Debug.LogException(exception);
Debug.LogError("An exception was caught while attempting to parse a " +
$"scenario configuration file at {filePath}. Cleaning up and exiting simulation.");
m_Idle = true;
}
#endif
/// Resets SamplerState.randomState with a new seed value generated by hashing this Scenario's randomSeed
/// with its currentIteration
/// OnComplete is called when this scenario's isScenarioComplete property
/// returns true during its main update loop
protected virtual void ResetRandomStateOnIteration()
protected virtual void OnComplete()
SamplerState.randomState = SamplerUtility.IterateSeed((uint)currentIteration, genericConstants.randomSeed);
DatasetCapture.ResetSimulation();
m_Idle = true;
/// Awake is called when this scenario MonoBehaviour is created or instantiated
/// OnIdle is called each frame after the scenario has completed
protected virtual void Awake()
protected virtual void OnIdle()
{
Manager.Instance.Shutdown();
if (!Manager.FinalUploadsDone)
return;
#if UNITY_EDITOR
EditorApplication.ExitPlaymode();
#else
Application.Quit();
#endif
}
void Awake()
// Don't skip the first frame if executing on Unity Simulation
if (Configuration.Instance.IsSimulationRunningInCloud())
m_SkipFrame = false;
m_IterationMetricDefinition = DatasetCapture.RegisterMetricDefinition("scenario_iteration", "Iteration information for dataset sequences",
m_IterationMetricDefinition = DatasetCapture.RegisterMetricDefinition(
"scenario_iteration", "Iteration information for dataset sequences",
OnAwake();
}
/// <summary>

activeScenario = null;
}
/// <summary>
/// Start is called after Awake but before the first Update method call
/// </summary>
protected virtual void Start()
void Start()
{
var randomSeedMetricDefinition = DatasetCapture.RegisterMetricDefinition(
"random-seed",

#if !UNITY_EDITOR
DeserializeFromFile();
#endif
OnStart();
/// <summary>
/// Update is called once per frame
/// </summary>
protected virtual void Update()
void Update()
{
// TODO: remove this check when the perception camera can capture the first frame of output
if (m_SkipFrame)

}
// Wait for any final uploads before exiting quitting
if (m_WaitingForFinalUploads && quitOnComplete)
if (m_Idle)
Manager.Instance.Shutdown();
if (!Manager.FinalUploadsDone)
return;
#if UNITY_EDITOR
EditorApplication.ExitPlaymode();
#else
Application.Quit();
#endif
OnIdle();
// Iterate Scenario
if (m_FirstScenarioFrame)
{
m_FirstScenarioFrame = false;
}
else
// Increment iteration and cleanup last iteration
if (isIterationComplete)
currentIterationFrame++;
framesSinceInitialization++;
if (isIterationComplete)
{
IncrementIteration();
currentIterationFrame = 0;
foreach (var randomizer in activeRandomizers)
randomizer.IterationEnd();
}
IncrementIteration();
currentIterationFrame = 0;
foreach (var randomizer in activeRandomizers)
randomizer.IterationEnd();
}
// Quit if scenario is complete

randomizer.ScenarioComplete();
DatasetCapture.ResetSimulation();
m_WaitingForFinalUploads = true;
return;
OnComplete();
}
// Perform new iteration tasks

// Perform new frame tasks
foreach (var randomizer in activeRandomizers)
randomizer.Update();
// Iterate scenario frame count
currentIterationFrame++;
framesSinceInitialization++;
/// Finds and returns a randomizer attached to this scenario of the specified Randomizer type
/// Called by the "Add Randomizer" button in the scenario Inspector
/// <typeparam name="T">The type of randomizer to find</typeparam>
/// <returns>A randomizer of the specified type</returns>
/// <param name="randomizerType">The type of randomizer to create</param>
/// <returns>The newly created randomizer</returns>
public T GetRandomizer<T>() where T : Randomizer
internal Randomizer CreateRandomizer(Type randomizerType)
foreach (var randomizer in m_Randomizers)
if (randomizer is T typedRandomizer)
return typedRandomizer;
throw new ScenarioException($"A Randomizer of type {typeof(T).Name} was not added to this scenario");
if (!randomizerType.IsSubclassOf(typeof(Randomizer)))
throw new ScenarioException(
$"Cannot add non-randomizer type {randomizerType.Name} to randomizer list");
var newRandomizer = (Randomizer)Activator.CreateInstance(randomizerType);
AddRandomizer(newRandomizer);
return newRandomizer;
/// Creates a new randomizer and adds it to this scenario
/// Append a randomizer to the end of the randomizer list
/// <typeparam name="T">The type of randomizer to create</typeparam>
/// <returns>The newly created randomizer</returns>
public T CreateRandomizer<T>() where T : Randomizer, new()
/// <param name="newRandomizer"></param>
public void AddRandomizer(Randomizer newRandomizer)
return (T)CreateRandomizer(typeof(T));
InsertRandomizer(m_Randomizers.Count, newRandomizer);
internal Randomizer CreateRandomizer(Type randomizerType)
/// <summary>
/// Insert a randomizer at a given index within the randomizer list
/// </summary>
/// <param name="index">The index to place the randomizer</param>
/// <param name="newRandomizer">The randomizer to add to the list</param>
/// <exception cref="ScenarioException"></exception>
public void InsertRandomizer(int index, Randomizer newRandomizer)
if (!randomizerType.IsSubclassOf(typeof(Randomizer)))
throw new ScenarioException(
$"Cannot add non-randomizer type {randomizerType.Name} to randomizer list");
if (randomizer.GetType() == randomizerType)
if (randomizer.GetType() == newRandomizer.GetType())
$"Two Randomizers of the same type ({randomizerType.Name}) cannot both be active simultaneously");
var newRandomizer = (Randomizer)Activator.CreateInstance(randomizerType);
m_Randomizers.Add(newRandomizer);
$"Cannot add another randomizer of type ${newRandomizer.GetType()} when " +
$"a scenario of this type is already present in the scenario");
m_Randomizers.Insert(index, newRandomizer);
#if UNITY_EDITOR
if (Application.isPlaying)
newRandomizer.Create();

return newRandomizer;
/// Removes a randomizer of the specified type from this scenario
/// Remove the randomizer present at the given index
/// <typeparam name="T">The type of scenario to remove</typeparam>
public void RemoveRandomizer<T>() where T : Randomizer, new()
/// <param name="index">The index of the randomizer to remove</param>
public void RemoveRandomizerAt(int index)
RemoveRandomizer(typeof(T));
m_Randomizers.RemoveAt(index);
internal void RemoveRandomizer(Type randomizerType)
/// <summary>
/// Returns the randomizer present at the given index
/// </summary>
/// <param name="index">The lookup index</param>
/// <returns>The randomizer present at the given index</returns>
public Randomizer GetRandomizer(int index)
if (!randomizerType.IsSubclassOf(typeof(Randomizer)))
throw new ScenarioException(
$"Cannot remove non-randomizer type {randomizerType.Name} from randomizer list");
var removed = false;
for (var i = 0; i < m_Randomizers.Count; i++)
if (m_Randomizers[i].GetType() == randomizerType)
{
m_Randomizers.RemoveAt(i);
removed = true;
break;
}
if (!removed)
throw new ScenarioException(
$"No active Randomizer of type {randomizerType.Name} could be removed");
return m_Randomizers[index];
/// Returns the execution order index of a randomizer of the given type
/// Finds and returns a randomizer attached to this scenario of the specified Randomizer type
/// <typeparam name="T">The type of randomizer to index</typeparam>
/// <returns>The randomizer index</returns>
/// <typeparam name="T">The type of randomizer to find</typeparam>
/// <returns>A randomizer of the specified type</returns>
public int GetRandomizerIndex<T>() where T : Randomizer, new()
public T GetRandomizer<T>() where T : Randomizer
for (var i = 0; i < m_Randomizers.Count; i++)
{
var randomizer = m_Randomizers[i];
if (randomizer is T)
return i;
}
foreach (var randomizer in m_Randomizers)
if (randomizer is T typedRandomizer)
return typedRandomizer;
}
/// <summary>
/// Moves a randomizer from one index to another
/// </summary>
/// <param name="currentIndex">The index of the randomizer to move</param>
/// <param name="nextIndex">The index to move the randomizer to</param>
public void ReorderRandomizer(int currentIndex, int nextIndex)
{
if (currentIndex == nextIndex)
return;
if (nextIndex > currentIndex)
nextIndex--;
var randomizer = m_Randomizers[currentIndex];
m_Randomizers.RemoveAt(currentIndex);
m_Randomizers.Insert(nextIndex, randomizer);
}
void ValidateParameters()

2
com.unity.perception/Runtime/Randomization/Scenarios/Serialization/ScenarioSerializer.cs


else if (scalar.value is BooleanScalarValue booleanValue)
value = booleanValue.boolean;
else if (scalar.value is DoubleScalarValue doubleValue)
value = doubleValue;
value = doubleValue.num;
else
throw new ArgumentException(
$"Cannot deserialize unsupported scalar type {scalar.value.GetType()}");

1
com.unity.perception/Runtime/Randomization/Scenarios/Serialization/SerializationStructures.cs


class BooleanScalarValue : IScalarValue
{
[JsonProperty("bool")]
public bool boolean;
}
#endregion

21
com.unity.perception/Runtime/Randomization/Scenarios/UnitySimulationScenario.cs


public sealed override bool isScenarioComplete => currentIteration >= constants.totalIterations;
/// <inheritdoc/>
public override string defaultConfigFilePath =>
Configuration.Instance.IsSimulationRunningInCloud()
? new Uri(Configuration.Instance.SimulationConfig.app_param_uri).LocalPath
: base.defaultConfigFilePath;
protected sealed override void IncrementIteration()
{
currentIteration += constants.instanceCount;
}
protected sealed override void IncrementIteration()
protected override void OnAwake()
currentIteration += constants.instanceCount;
// Don't skip the first frame if executing on Unity Simulation
if (Configuration.Instance.IsSimulationRunningInCloud())
m_SkipFrame = false;
protected override void Start()
protected override void OnStart()
base.Start();
if (Configuration.Instance.IsSimulationRunningInCloud())
DeserializeFromFile(new Uri(Configuration.Instance.SimulationConfig.app_param_uri).LocalPath);
else
base.OnStart();
currentIteration = constants.instanceIndex;
}
}

2
com.unity.perception/Tests/Editor/RandomizerEditorTests.cs


// if ScenarioBase.CreateRandomizer<>() was coded correctly
Assert.DoesNotThrow(() =>
{
m_Scenario.CreateRandomizer<ErrorsOnCreateTestRandomizer>();
m_Scenario.AddRandomizer(new ErrorsOnCreateTestRandomizer());
});
}
}

10
com.unity.perception/Tests/Runtime/Randomization/RandomizerTests/RandomizerTests.cs


IEnumerator CreateNewScenario(int totalIterations, int framesPerIteration)
{
m_Scenario = m_TestObject.AddComponent<FixedLengthScenario>();
m_Scenario.quitOnComplete = false;
m_Scenario.constants.totalIterations = totalIterations;
m_Scenario.constants.framesPerIteration = framesPerIteration;
yield return null; // Skip first frame

public void OneRandomizerInstancePerTypeTest()
{
m_Scenario = m_TestObject.AddComponent<FixedLengthScenario>();
m_Scenario.quitOnComplete = false;
m_Scenario.CreateRandomizer<ExampleTransformRandomizer>();
Assert.Throws<ScenarioException>(() => m_Scenario.CreateRandomizer<ExampleTransformRandomizer>());
m_Scenario.AddRandomizer(new ExampleTransformRandomizer());
Assert.Throws<ScenarioException>(() => m_Scenario.AddRandomizer(new ExampleTransformRandomizer()));
}
[UnityTest]

m_Scenario.CreateRandomizer<ExampleTransformRandomizer>();
m_Scenario.AddRandomizer(new ExampleTransformRandomizer());
var transform = m_TestObject.transform;
var initialPosition = Vector3.zero;
transform.position = initialPosition;

public IEnumerator OnIterationStartExecutesEveryIteration()
{
yield return CreateNewScenario(10, 2);
m_Scenario.CreateRandomizer<ExampleTransformRandomizer>();
m_Scenario.AddRandomizer(new ExampleTransformRandomizer());
var transform = m_TestObject.transform;
var initialRotation = Quaternion.identity;
transform.rotation = initialRotation;

6
com.unity.perception/Tests/Runtime/Randomization/ScenarioTests/ScenarioTests.cs


using System;
using System.Collections;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityEngine.Perception.Randomization.Randomizers.SampleRandomizers;
using UnityEngine.Perception.Randomization.Samplers;

IEnumerator CreateNewScenario(int totalIterations, int framesPerIteration)
{
m_Scenario = m_TestObject.AddComponent<FixedLengthScenario>();
m_Scenario.quitOnComplete = false;
m_Scenario.constants.totalIterations = totalIterations;
m_Scenario.constants.framesPerIteration = framesPerIteration;
yield return null; // Skip Start() frame

{
m_TestObject = new GameObject();
m_Scenario = m_TestObject.AddComponent<FixedLengthScenario>();
m_Scenario.CreateRandomizer<RotationRandomizer>();
m_Scenario.AddRandomizer(new RotationRandomizer());
string RemoveWhitespace(string str) =>
string.Join("", str.Split(default(string[]), StringSplitOptions.RemoveEmptyEntries));

正在加载...
取消
保存