|
|
|
|
|
|
using System.IO.Abstractions; |
|
|
|
using System.Text.RegularExpressions; |
|
|
|
using UnityEngine; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.IO; |
|
|
|
|
|
|
|
namespace MLAgents |
|
|
|
{ |
|
|
|
|
|
|
[AddComponentMenu("ML Agents/Demonstration Recorder", (int)MenuGroup.Default)] |
|
|
|
public class DemonstrationRecorder : MonoBehaviour |
|
|
|
{ |
|
|
|
[Tooltip("Whether or not to record demonstrations.")] |
|
|
|
|
|
|
|
[Tooltip("Base demonstration file name. Will have numbers appended to make unique.")] |
|
|
|
string m_FilePath; |
|
|
|
|
|
|
|
[Tooltip("Base directory to write the demo files. If null, will use {Application.dataPath}/Demonstrations.")] |
|
|
|
public string demonstrationDirectory; |
|
|
|
|
|
|
|
public const int MaxNameLength = 16; |
|
|
|
internal const int MaxNameLength = 16; |
|
|
|
|
|
|
|
const string k_ExtensionType = ".demo"; |
|
|
|
IFileSystem m_FileSystem; |
|
|
|
|
|
|
|
Agent m_Agent; |
|
|
|
void Start() |
|
|
|
void OnEnable() |
|
|
|
if (Application.isEditor && record) |
|
|
|
{ |
|
|
|
InitializeDemoStore(); |
|
|
|
} |
|
|
|
m_Agent = GetComponent<Agent>(); |
|
|
|
if (Application.isEditor && record && m_DemoStore == null) |
|
|
|
if (record) |
|
|
|
InitializeDemoStore(); |
|
|
|
LazyInitialize(); |
|
|
|
/// Has no effect if the demonstration store was already created.
|
|
|
|
public void InitializeDemoStore(IFileSystem fileSystem = null) |
|
|
|
internal DemonstrationStore LazyInitialize(IFileSystem fileSystem = null) |
|
|
|
m_DemoStore = new DemonstrationStore(fileSystem); |
|
|
|
if (m_DemoStore != null) |
|
|
|
{ |
|
|
|
return m_DemoStore; |
|
|
|
} |
|
|
|
|
|
|
|
if (m_Agent == null) |
|
|
|
{ |
|
|
|
m_Agent = GetComponent<Agent>(); |
|
|
|
} |
|
|
|
|
|
|
|
m_FileSystem = fileSystem ?? new FileSystem(); |
|
|
|
if (string.IsNullOrEmpty(demonstrationName)) |
|
|
|
{ |
|
|
|
demonstrationName = behaviorParams.behaviorName; |
|
|
|
} |
|
|
|
if (string.IsNullOrEmpty(demonstrationDirectory)) |
|
|
|
{ |
|
|
|
demonstrationDirectory = Path.Combine(Application.dataPath, "Demonstrations"); |
|
|
|
} |
|
|
|
|
|
|
|
var filePath = MakeDemonstrationFilePath(m_FileSystem, demonstrationDirectory, demonstrationName); |
|
|
|
var stream = m_FileSystem.File.Create(filePath); |
|
|
|
m_DemoStore = new DemonstrationStore(stream); |
|
|
|
|
|
|
|
behaviorParams.fullyQualifiedBehaviorName); |
|
|
|
behaviorParams.fullyQualifiedBehaviorName |
|
|
|
); |
|
|
|
|
|
|
|
AddDemonstrationStoreToAgent(m_DemoStore); |
|
|
|
|
|
|
|
return m_DemoStore; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
public static string SanitizeName(string demoName, int maxNameLength) |
|
|
|
internal static string SanitizeName(string demoName, int maxNameLength) |
|
|
|
{ |
|
|
|
var rgx = new Regex("[^a-zA-Z0-9 -]"); |
|
|
|
demoName = rgx.Replace(demoName, ""); |
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Forwards AgentInfo to Demonstration Store.
|
|
|
|
/// Gets a unique path for the demonstrationName in the demonstrationDirectory.
|
|
|
|
public void WriteExperience(AgentInfo info, List<ISensor> sensors) |
|
|
|
/// <param name="fileSystem"></param>
|
|
|
|
/// <param name="demonstrationDirectory"></param>
|
|
|
|
/// <param name="demonstrationName"></param>
|
|
|
|
/// <returns></returns>
|
|
|
|
internal static string MakeDemonstrationFilePath( |
|
|
|
IFileSystem fileSystem, string demonstrationDirectory, string demonstrationName |
|
|
|
) |
|
|
|
m_DemoStore?.Record(info, sensors); |
|
|
|
// Create the directory if it doesn't already exist
|
|
|
|
if (!fileSystem.Directory.Exists(demonstrationDirectory)) |
|
|
|
{ |
|
|
|
fileSystem.Directory.CreateDirectory(demonstrationDirectory); |
|
|
|
} |
|
|
|
|
|
|
|
var literalName = demonstrationName; |
|
|
|
var filePath = Path.Combine(demonstrationDirectory, literalName + k_ExtensionType); |
|
|
|
var uniqueNameCounter = 0; |
|
|
|
while (fileSystem.File.Exists(filePath)) |
|
|
|
{ |
|
|
|
// TODO should we use a timestamp instead of a counter here? This loops an increasing number of times
|
|
|
|
// as the number of demos increases.
|
|
|
|
literalName = demonstrationName + "_" + uniqueNameCounter; |
|
|
|
filePath = Path.Combine(demonstrationDirectory, literalName + k_ExtensionType); |
|
|
|
uniqueNameCounter++; |
|
|
|
} |
|
|
|
|
|
|
|
return filePath; |
|
|
|
/// <summary>
|
|
|
|
/// Close the DemonstrationStore and remove it from the Agent.
|
|
|
|
/// Has no effect if the DemonstrationStore is already closed (or wasn't opened)
|
|
|
|
/// </summary>
|
|
|
|
RemoveDemonstrationStoreFromAgent(m_DemoStore); |
|
|
|
|
|
|
|
m_DemoStore.Close(); |
|
|
|
m_DemoStore = null; |
|
|
|
} |
|
|
|
|
|
|
/// Closes Demonstration store.
|
|
|
|
/// Clean up the DemonstrationStore when shutting down or destroying the Agent.
|
|
|
|
void OnApplicationQuit() |
|
|
|
void OnDestroy() |
|
|
|
if (Application.isEditor && record) |
|
|
|
{ |
|
|
|
Close(); |
|
|
|
} |
|
|
|
Close(); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Add additional DemonstrationStore to the Agent. It is still up to the user to Close this
|
|
|
|
/// DemonstrationStores when recording is done.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="demoStore"></param>
|
|
|
|
public void AddDemonstrationStoreToAgent(DemonstrationStore demoStore) |
|
|
|
{ |
|
|
|
m_Agent.DemonstrationStores.Add(demoStore); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Remove additional DemonstrationStore to the Agent. It is still up to the user to Close this
|
|
|
|
/// DemonstrationStores when recording is done.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="demoStore"></param>
|
|
|
|
public void RemoveDemonstrationStoreFromAgent(DemonstrationStore demoStore) |
|
|
|
{ |
|
|
|
m_Agent.DemonstrationStores.Remove(demoStore); |
|
|
|
} |
|
|
|
} |
|
|
|
} |