-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Add custom package settings #5027
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
adcb61c
a1b9fce
f63be9d
87e3a11
4771f68
f6d6003
28e561a
19193f4
5fe5365
d04a5d4
6effd7b
38cbadb
df9aaed
525a5d4
87ed122
3a2a46b
1857a44
2a4c25d
73339fa
5deba49
e1da84f
430e253
53d59fe
52b2297
4a2a5b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
using System.Linq; | ||
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(MLAgentsManager.Settings)) | ||
{ | ||
m_SettingsAddedToPreloadedAssets = MLAgentsManager.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() | ||
{ | ||
#if UNITY_2019_OR_NEWER | ||
var settings = Resources.FindObjectsOfTypeAll<PlayerSettings>(); | ||
if (settings != null && settings.Length > 0) | ||
return EditorUtility.IsDirty(settings[0]); | ||
return false; | ||
#else | ||
dongruoping marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return false; | ||
#endif | ||
} | ||
|
||
private static void ClearPlayerSettingsDirtyFlag() | ||
{ | ||
#if UNITY_2019_OR_NEWER | ||
var settings = Resources.FindObjectsOfTypeAll<PlayerSettings>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PlayerSettings? I thought this would be an MLAgentsSettings. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]); | ||
#endif | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
using System; | ||
using System.Linq; | ||
using System.IO; | ||
using UnityEngine; | ||
using UnityEditor; | ||
#if UNITY_2019_4_OR_NEWER | ||
using UnityEngine.UIElements; | ||
#else | ||
using UnityEngine.Experimental.UIElements; | ||
surfnerd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#endif | ||
|
||
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); | ||
MLAgentsManager.OnSettingsChange += Reinitialize; | ||
} | ||
|
||
public override void OnDeactivate() | ||
{ | ||
base.OnDeactivate(); | ||
MLAgentsManager.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++) | ||
dongruoping marked this conversation as resolved.
Show resolved
Hide resolved
|
||
menu.AddItem(ExtractDisplayName(m_AvailableSettingsAssets[i]), m_CurrentSelectedSettingsAsset == i, (path) => | ||
{ | ||
MLAgentsManager.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 - ".settings".Length); | ||
|
||
// Ugly hack: GenericMenu iterprets "/" as a submenu path. But luckily, "/" is not the only slash we have in Unicode. | ||
dongruoping marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
MLAgentsManager.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.PropertyField(m_SettingsObject.FindProperty("m_ConnectTrainer"), new GUIContent("Connect to Trainer")); | ||
EditorGUILayout.PropertyField(m_SettingsObject.FindProperty("m_EditorPort"), new GUIContent("Editor Training Port")); | ||
if (EditorGUI.EndChangeCheck()) | ||
m_SettingsObject.ApplyModifiedProperties(); | ||
} | ||
} | ||
|
||
internal void InitializeWithCurrentSettings() | ||
{ | ||
m_AvailableSettingsAssets = FindSettingsInProject(); | ||
|
||
m_Settings = MLAgentsManager.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]); | ||
MLAgentsManager.Settings = m_Settings; | ||
} | ||
} | ||
else | ||
{ | ||
var settingsList = m_AvailableSettingsAssets.ToList(); | ||
m_CurrentSelectedSettingsAsset = settingsList.IndexOf(currentSettingsPath); | ||
|
||
EditorBuildSettings.AddConfigObject(MLAgentsManager.EditorBuildSettingsConfigKey, m_Settings, true); | ||
} | ||
|
||
m_SettingsObject = new SerializedObject(m_Settings); | ||
dongruoping marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
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 && MLAgentsManager.Settings != m_Settings) | ||
{ | ||
InitializeWithCurrentSettings(); | ||
} | ||
Repaint(); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
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?)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?)