Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
# Environemnt logfile
*Project.log

# Custom settings asset
*.settings.asset*

# Visual Studio 2015 cache directory
/Project/.vs/

Expand Down
1 change: 1 addition & 0 deletions com.unity.ml-agents/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ details. (#5060)
#### com.unity.ml-agents / com.unity.ml-agents.extensions (C#)
- The `.onnx` models input names have changed. All input placeholders will now use the prefix `obs_` removing the distinction between visual and vector observations. Models created with this version will not be usable with previous versions of the package (#5080)
- The `.onnx` models discrete action output now contains the discrete actions values and not the logits. Models created with this version will not be usable with previous versions of the package (#5080)
- Added ML-Agents package settings. (#5027)
#### ml-agents / ml-agents-envs / gym-unity (Python)

### Bug Fixes
Expand Down
69 changes: 69 additions & 0 deletions com.unity.ml-agents/Editor/MLAgentsSettingsBuildProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Linq;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a dumb question, but why do we have a BuildSettingsProvider if none of the settings are used in a build? (They are all Editor only settings?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they are used in builds, if you don't want to train or even try to connect to a trainer in a game that's being shipped you'd want to know that in your build.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are used in builds. If you don't want to train or even try to connect to a trainer in a game that's being shipped, you'd want to know that in your build.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean the use case it to remove the command line argument listeners on the build? Because a build will not try to listen to a trainer unless a specific command line argument is passed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. Originally I made a player training port but that doesn't seem to make much sense so I removed that.
But I still think there's chance that we'll add settings that is used in player (analytics? academy stepping?)

using UnityEngine;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;


namespace Unity.MLAgents.Editor
{
internal class MLAgentsSettingsBuildProvider : IPreprocessBuildWithReport, IPostprocessBuildWithReport
{
private MLAgentsSettings m_SettingsAddedToPreloadedAssets;

public int callbackOrder => 0;

public void OnPreprocessBuild(BuildReport report)
{
var wasDirty = IsPlayerSettingsDirty();
m_SettingsAddedToPreloadedAssets = null;

var preloadedAssets = PlayerSettings.GetPreloadedAssets().ToList();
if (!preloadedAssets.Contains(MLAgentsSettingsManager.Settings))
{
m_SettingsAddedToPreloadedAssets = MLAgentsSettingsManager.Settings;
preloadedAssets.Add(m_SettingsAddedToPreloadedAssets);
PlayerSettings.SetPreloadedAssets(preloadedAssets.ToArray());
}

if (!wasDirty)
ClearPlayerSettingsDirtyFlag();
}

public void OnPostprocessBuild(BuildReport report)
{
if (m_SettingsAddedToPreloadedAssets == null)
return;

var wasDirty = IsPlayerSettingsDirty();

var preloadedAssets = PlayerSettings.GetPreloadedAssets().ToList();
if (preloadedAssets.Contains(m_SettingsAddedToPreloadedAssets))
{
preloadedAssets.Remove(m_SettingsAddedToPreloadedAssets);
PlayerSettings.SetPreloadedAssets(preloadedAssets.ToArray());
}

m_SettingsAddedToPreloadedAssets = null;

if (!wasDirty)
ClearPlayerSettingsDirtyFlag();
}


private static bool IsPlayerSettingsDirty()
{
var settings = Resources.FindObjectsOfTypeAll<PlayerSettings>();
if (settings != null && settings.Length > 0)
return EditorUtility.IsDirty(settings[0]);
return false;
}

private static void ClearPlayerSettingsDirtyFlag()
{
var settings = Resources.FindObjectsOfTypeAll<PlayerSettings>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PlayerSettings? I thought this would be an MLAgentsSettings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're injecting our settings into the preload assets in PlayerSettings and these method is to detect/clear the dirty flag of PlayerSettings before/after we put in our settings. So PlayerSettings here is correct.

if (settings != null && settings.Length > 0)
EditorUtility.ClearDirty(settings[0]);
}
}
}
11 changes: 11 additions & 0 deletions com.unity.ml-agents/Editor/MLAgentsSettingsBuildProvider.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

192 changes: 192 additions & 0 deletions com.unity.ml-agents/Editor/MLAgentsSettingsProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
using System;
using System.Linq;
using System.IO;
using UnityEngine;
using UnityEditor;
using UnityEngine.UIElements;

namespace Unity.MLAgents.Editor
{
internal class MLAgentsSettingsProvider : SettingsProvider, IDisposable
{
const string k_SettingsPath = "Project/ML-Agents";
private static MLAgentsSettingsProvider s_Instance;
private string[] m_AvailableSettingsAssets;
private int m_CurrentSelectedSettingsAsset;
private SerializedObject m_SettingsObject;
[SerializeField]
private MLAgentsSettings m_Settings;


private MLAgentsSettingsProvider(string path, SettingsScope scope = SettingsScope.Project)
: base(path, scope)
{
s_Instance = this;
}

[SettingsProvider]
public static SettingsProvider CreateMLAgentsSettingsProvider()
{
return new MLAgentsSettingsProvider(k_SettingsPath, SettingsScope.Project);
}

public override void OnActivate(string searchContext, VisualElement rootElement)
{
base.OnActivate(searchContext, rootElement);
MLAgentsSettingsManager.OnSettingsChange += Reinitialize;
}

public override void OnDeactivate()
{
base.OnDeactivate();
MLAgentsSettingsManager.OnSettingsChange -= Reinitialize;
}

public void Dispose()
{
m_SettingsObject?.Dispose();
}

public override void OnTitleBarGUI()
{
if (EditorGUILayout.DropdownButton(EditorGUIUtility.IconContent("_Popup"), FocusType.Passive, EditorStyles.label))
{
var menu = new GenericMenu();
for (var i = 0; i < m_AvailableSettingsAssets.Length; i++)
{
menu.AddItem(ExtractDisplayName(m_AvailableSettingsAssets[i]), m_CurrentSelectedSettingsAsset == i, (path) =>
{
MLAgentsSettingsManager.Settings = AssetDatabase.LoadAssetAtPath<MLAgentsSettings>((string)path);
}, m_AvailableSettingsAssets[i]);
}
menu.AddSeparator("");
menu.AddItem(new GUIContent("New Settings Asset…"), false, CreateNewSettingsAsset);
menu.ShowAsContext();
Event.current.Use();
}
}

private GUIContent ExtractDisplayName(string name)
{
if (name.StartsWith("Assets/"))
name = name.Substring("Assets/".Length);
if (name.EndsWith(".asset"))
name = name.Substring(0, name.Length - ".asset".Length);
if (name.EndsWith(".mlagents.settings"))
name = name.Substring(0, name.Length - ".mlagents.settings".Length);

// Ugly hack: GenericMenu interprets "/" as a submenu path. But luckily, "/" is not the only slash we have in Unicode.
return new GUIContent(name.Replace("/", "\u29f8"));
}

private void CreateNewSettingsAsset()
{
// Asset database always use forward slashes. Use forward slashes for all the paths.
var projectName = PlayerSettings.productName;
var path = EditorUtility.SaveFilePanel("Create ML-Agents Settings File", "Assets",
projectName + ".mlagents.settings", "asset");
if (string.IsNullOrEmpty(path))
{
return;
}

path = path.Replace("\\", "/"); // Make sure we only get '/' separators.
var assetPath = Application.dataPath + "/";
if (!path.StartsWith(assetPath, StringComparison.CurrentCultureIgnoreCase))
{
Debug.LogError(string.Format(
"Settings must be stored in Assets folder of the project (got: '{0}')", path));
return;
}

var extension = Path.GetExtension(path);
if (string.Compare(extension, ".asset", StringComparison.InvariantCultureIgnoreCase) != 0)
{
path += ".asset";
}
var relativePath = "Assets/" + path.Substring(assetPath.Length);
CreateNewSettingsAsset(relativePath);
}

private static void CreateNewSettingsAsset(string relativePath)
{
var settings = ScriptableObject.CreateInstance<MLAgentsSettings>();
AssetDatabase.CreateAsset(settings, relativePath);
EditorGUIUtility.PingObject(settings);
// Install the settings. This will lead to an MLAgentsManager.OnSettingsChange event
// which in turn will cause this Provider to reinitialize
MLAgentsSettingsManager.Settings = settings;
}

public override void OnGUI(string searchContext)
{
if (m_Settings == null)
{
InitializeWithCurrentSettings();
}

if (m_AvailableSettingsAssets.Length == 0)
{
EditorGUILayout.HelpBox(
"Click the button below to create a settings asset you can edit.",
MessageType.Info);
if (GUILayout.Button("Create settings asset", GUILayout.Height(30)))
CreateNewSettingsAsset();
GUILayout.Space(20);
}

using (new EditorGUI.DisabledScope(m_AvailableSettingsAssets.Length == 0))
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.LabelField("Trainer Settings", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(m_SettingsObject.FindProperty("m_ConnectTrainer"), new GUIContent("Connect to Trainer"));
EditorGUILayout.PropertyField(m_SettingsObject.FindProperty("m_EditorPort"), new GUIContent("Editor Training Port"));
EditorGUI.indentLevel--;
if (EditorGUI.EndChangeCheck())
m_SettingsObject.ApplyModifiedProperties();
}
}

internal void InitializeWithCurrentSettings()
{
m_AvailableSettingsAssets = FindSettingsInProject();

m_Settings = MLAgentsSettingsManager.Settings;
var currentSettingsPath = AssetDatabase.GetAssetPath(m_Settings);
if (string.IsNullOrEmpty(currentSettingsPath))
{
if (m_AvailableSettingsAssets.Length > 0)
{
m_CurrentSelectedSettingsAsset = 0;
m_Settings = AssetDatabase.LoadAssetAtPath<MLAgentsSettings>(m_AvailableSettingsAssets[0]);
MLAgentsSettingsManager.Settings = m_Settings;
}
}
else
{
var settingsList = m_AvailableSettingsAssets.ToList();
m_CurrentSelectedSettingsAsset = settingsList.IndexOf(currentSettingsPath);

EditorBuildSettings.AddConfigObject(MLAgentsSettingsManager.EditorBuildSettingsConfigKey, m_Settings, true);
}

m_SettingsObject = new SerializedObject(m_Settings);
}

private static string[] FindSettingsInProject()
{
var guids = AssetDatabase.FindAssets("t:MLAgentsSettings");
return guids.Select(guid => AssetDatabase.GUIDToAssetPath(guid)).ToArray();
}

private void Reinitialize()
{
if (m_Settings != null && MLAgentsSettingsManager.Settings != m_Settings)
{
InitializeWithCurrentSettings();
}
Repaint();
}
}
}
11 changes: 11 additions & 0 deletions com.unity.ml-agents/Editor/MLAgentsSettingsProvider.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion com.unity.ml-agents/Runtime/Academy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ static int ReadPortFromArgs()
// No arg passed, or malformed port number.
#if UNITY_EDITOR
// Try connecting on the default editor port
return k_EditorTrainingPort;
return MLAgentsSettingsManager.Settings.ConnectTrainer ? MLAgentsSettingsManager.Settings.EditorPort : -1;
#else
// This is an executable, so we don't try to connect.
return -1;
Expand Down
38 changes: 38 additions & 0 deletions com.unity.ml-agents/Runtime/MLAgentsSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using UnityEngine;

namespace Unity.MLAgents
{
class MLAgentsSettings : ScriptableObject
{
[SerializeField]
private bool m_ConnectTrainer = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were other settings that we talked about adding later - should we group/nest these now so that we don't have to go through hoops to move them around in the future?

Copy link
Contributor Author

@dongruoping dongruoping Mar 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can group them into Training Port or Trainer Settings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chriselion by group/nest these do you mean nesting them in the code (like a subclass in MLAgentsSettings) or just grouping in editor?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way - I think using a subclass in MLAgentsSettings makes the grouping in the editor more automatic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess if we always need to manually layout the fields in MLAgentsSettingsProvider.OnGUI, it's less important. But it might let us just add the "top level" settings (like TrainerSettings) and not have to worry about new fields when we add them.

[SerializeField]
private int m_EditorPort = 5004;

public bool ConnectTrainer
{
get { return m_ConnectTrainer; }
set
{
m_ConnectTrainer = value;
OnChange();
}
}

public int EditorPort
{
get { return m_EditorPort; }
set
{
m_EditorPort = value;
OnChange();
}
}

internal void OnChange()
{
if (MLAgentsSettingsManager.Settings == this)
MLAgentsSettingsManager.ApplySettings();
}
}
}
11 changes: 11 additions & 0 deletions com.unity.ml-agents/Runtime/MLAgentsSettings.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading