Added Unity project files
This commit is contained in:
579
Packages/com.vrchat.worlds/Editor/Udon/UdonEditorManager.cs
Normal file
579
Packages/com.vrchat.worlds/Editor/Udon/UdonEditorManager.cs
Normal file
@ -0,0 +1,579 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using VRC.Udon.Common.Interfaces;
|
||||
using VRC.Udon.Compiler.Compilers;
|
||||
using VRC.Udon.EditorBindings;
|
||||
using VRC.Udon.EditorBindings.Interfaces;
|
||||
using VRC.Udon.Graph;
|
||||
using VRC.Udon.Graph.Interfaces;
|
||||
using VRC.Udon.UAssembly.Interfaces;
|
||||
|
||||
namespace VRC.Udon.Editor
|
||||
{
|
||||
public class UdonEditorManager : IUdonEditorInterface
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
private static UdonEditorManager _instance;
|
||||
public static UdonEditorManager Instance => _instance ?? (_instance = new UdonEditorManager());
|
||||
|
||||
#endregion
|
||||
|
||||
#region Build Preprocessor Class
|
||||
|
||||
private class UdonBuildPreprocessor : IProcessSceneWithReport
|
||||
{
|
||||
public int callbackOrder => 0;
|
||||
|
||||
public void OnProcessScene(Scene scene, BuildReport report)
|
||||
{
|
||||
PopulateSceneSerializedProgramAssetReferences(scene);
|
||||
PopulateAssetDependenciesPrefabSerializedProgramAssetReferences(scene.path);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Events
|
||||
|
||||
public event Action WantRepaint;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Constants
|
||||
|
||||
private const double REFRESH_QUEUE_WAIT_PERIOD = 5.0;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private Lazy<UdonEditorInterface> _udonEditorInterface;
|
||||
|
||||
private UdonEditorInterface UdonEditorInterface => _udonEditorInterface.Value;
|
||||
|
||||
private readonly HashSet<AbstractUdonProgramSource> _programSourceRefreshQueue = new HashSet<AbstractUdonProgramSource>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
private static void Initialize()
|
||||
{
|
||||
_instance = new UdonEditorManager();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
private UdonEditorManager()
|
||||
{
|
||||
_udonEditorInterface = new Lazy<UdonEditorInterface>(() =>
|
||||
{
|
||||
var editorInterface = new UdonEditorInterface();
|
||||
editorInterface.AddTypeResolver(new UdonBehaviourTypeResolver());
|
||||
|
||||
return editorInterface;
|
||||
});
|
||||
|
||||
// Async init the editor interface to avoid 1+ second delay added to assembly reload.
|
||||
Task.Run(() =>
|
||||
{
|
||||
var _ = _udonEditorInterface.Value;
|
||||
});
|
||||
|
||||
EditorSceneManager.sceneOpened += OnSceneOpened;
|
||||
EditorSceneManager.sceneSaving += OnSceneSaving;
|
||||
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UdonBehaviour and ProgramSource Refresh
|
||||
|
||||
public void QueueAndRefreshProgram(AbstractUdonProgramSource programSource)
|
||||
{
|
||||
QueueProgramSourceRefresh(programSource);
|
||||
RefreshQueuedProgramSources();
|
||||
}
|
||||
|
||||
public void RefreshQueuedProgramSources()
|
||||
{
|
||||
foreach(AbstractUdonProgramSource programSource in _programSourceRefreshQueue)
|
||||
{
|
||||
if(programSource == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
programSource.RefreshProgram();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"Failed to refresh program '{programSource.name}' due to exception '{e}'.");
|
||||
}
|
||||
}
|
||||
|
||||
_programSourceRefreshQueue.Clear();
|
||||
|
||||
WantRepaint?.Invoke();
|
||||
}
|
||||
|
||||
public bool IsProgramSourceRefreshQueued(AbstractUdonProgramSource programSource)
|
||||
{
|
||||
if(_programSourceRefreshQueue.Count <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!_programSourceRefreshQueue.Contains(programSource))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void QueueProgramSourceRefresh(AbstractUdonProgramSource programSource)
|
||||
{
|
||||
if(Application.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(programSource == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(IsProgramSourceRefreshQueued(programSource))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_programSourceRefreshQueue.Add(programSource);
|
||||
}
|
||||
|
||||
public void CancelQueuedProgramSourceRefresh(AbstractUdonProgramSource programSource)
|
||||
{
|
||||
if(programSource == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(_programSourceRefreshQueue.Contains(programSource))
|
||||
{
|
||||
_programSourceRefreshQueue.Remove(programSource);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("VRChat SDK/Utilities/Re-compile All Program Sources")]
|
||||
public static void RecompileAllProgramSources()
|
||||
{
|
||||
string[] programSourceGUIDs = AssetDatabase.FindAssets("t:AbstractUdonProgramSource");
|
||||
foreach(string guid in programSourceGUIDs)
|
||||
{
|
||||
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
|
||||
AbstractUdonProgramSource programSource = AssetDatabase.LoadAssetAtPath<AbstractUdonProgramSource>(assetPath);
|
||||
if(programSource == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
programSource.RefreshProgram();
|
||||
}
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
PopulateAllPrefabSerializedProgramAssetReferences();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static void PopulateAllPrefabSerializedProgramAssetReferences()
|
||||
{
|
||||
foreach(string prefabPath in GetAllPrefabAssetPaths())
|
||||
{
|
||||
PopulatePrefabSerializedProgramAssetReferences(prefabPath);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<UdonBehaviour> prefabBehavioursTempList = new List<UdonBehaviour>();
|
||||
|
||||
[PublicAPI]
|
||||
public static void PopulateAssetDependenciesPrefabSerializedProgramAssetReferences(string assetPath)
|
||||
{
|
||||
IEnumerable<string> prefabDependencyPaths = AssetDatabase.GetDependencies(assetPath, true)
|
||||
.Where(path => path.EndsWith(".prefab"))
|
||||
.Where(path => path.StartsWith("Assets"));
|
||||
|
||||
foreach(string prefabPath in prefabDependencyPaths)
|
||||
{
|
||||
if(!(AssetDatabase.LoadMainAssetAtPath(prefabPath) is GameObject prefab))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
prefab.GetComponentsInChildren<UdonBehaviour>(prefabBehavioursTempList);
|
||||
if(prefabBehavioursTempList.Count < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PopulatePrefabSerializedProgramAssetReferences(prefabPath);
|
||||
}
|
||||
}
|
||||
|
||||
private static void PopulatePrefabSerializedProgramAssetReferences(string prefabPath)
|
||||
{
|
||||
using(EditPrefabAssetScope editScope = new EditPrefabAssetScope(prefabPath))
|
||||
{
|
||||
if(!editScope.IsEditable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
editScope.PrefabRoot.GetComponentsInChildren(prefabBehavioursTempList);
|
||||
if(prefabBehavioursTempList.Count < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool dirty = false;
|
||||
foreach(UdonBehaviour udonBehaviour in prefabBehavioursTempList)
|
||||
{
|
||||
if(PopulateSerializedProgramAssetReference(udonBehaviour))
|
||||
{
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(dirty)
|
||||
{
|
||||
editScope.MarkDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scene Manager Callbacks
|
||||
|
||||
private void OnSceneOpened(Scene scene, OpenSceneMode mode)
|
||||
{
|
||||
RefreshQueuedProgramSources();
|
||||
PopulateSceneSerializedProgramAssetReferences(scene);
|
||||
}
|
||||
|
||||
private void OnSceneSaving(Scene scene, string _)
|
||||
{
|
||||
RefreshQueuedProgramSources();
|
||||
PopulateSceneSerializedProgramAssetReferences(scene);
|
||||
}
|
||||
|
||||
private static void PopulateSceneSerializedProgramAssetReferences(Scene scene)
|
||||
{
|
||||
if (!scene.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(GameObject sceneGameObject in scene.GetRootGameObjects())
|
||||
{
|
||||
foreach(UdonBehaviour udonBehaviour in sceneGameObject.GetComponentsInChildren<UdonBehaviour>(true))
|
||||
{
|
||||
PopulateSerializedProgramAssetReference(udonBehaviour);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the serializedProgramProperty was changed, false otherwise.
|
||||
private static bool PopulateSerializedProgramAssetReference(UdonBehaviour udonBehaviour)
|
||||
{
|
||||
SerializedObject serializedUdonBehaviour = new SerializedObject(udonBehaviour);
|
||||
SerializedProperty programSourceSerializedProperty = serializedUdonBehaviour.FindProperty("programSource");
|
||||
SerializedProperty serializedProgramAssetSerializedProperty = serializedUdonBehaviour.FindProperty("serializedProgramAsset");
|
||||
|
||||
if(!(programSourceSerializedProperty.objectReferenceValue is AbstractUdonProgramSource abstractUdonProgramSource))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(abstractUdonProgramSource == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(serializedProgramAssetSerializedProperty.objectReferenceValue == abstractUdonProgramSource.SerializedProgramAsset)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
serializedProgramAssetSerializedProperty.objectReferenceValue = abstractUdonProgramSource.SerializedProgramAsset;
|
||||
serializedUdonBehaviour.ApplyModifiedPropertiesWithoutUndo();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PlayMode Callback
|
||||
|
||||
private void OnPlayModeStateChanged(PlayModeStateChange playModeStateChange)
|
||||
{
|
||||
if(playModeStateChange != PlayModeStateChange.ExitingEditMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(int index = 0; index < SceneManager.sceneCount; index++)
|
||||
{
|
||||
PopulateSceneSerializedProgramAssetReferences(SceneManager.GetSceneAt(index));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IUdonEditorInterface Methods
|
||||
|
||||
public IUdonVM ConstructUdonVM()
|
||||
{
|
||||
return UdonEditorInterface.ConstructUdonVM();
|
||||
}
|
||||
|
||||
public IUdonProgram Assemble(string assembly)
|
||||
{
|
||||
return UdonEditorInterface.Assemble(assembly);
|
||||
}
|
||||
|
||||
public IUdonWrapper GetWrapper()
|
||||
{
|
||||
return UdonEditorInterface.GetWrapper();
|
||||
}
|
||||
|
||||
public IUdonHeap ConstructUdonHeap()
|
||||
{
|
||||
return UdonEditorInterface.ConstructUdonHeap();
|
||||
}
|
||||
|
||||
public IUdonHeap ConstructUdonHeap(uint heapSize)
|
||||
{
|
||||
return UdonEditorInterface.ConstructUdonHeap(heapSize);
|
||||
}
|
||||
|
||||
public string CompileGraph(
|
||||
IUdonCompilableGraph graph, INodeRegistry nodeRegistry,
|
||||
out Dictionary<string, (string uid, string fullName, int index)> linkedSymbols,
|
||||
out Dictionary<string, (object value, Type type)> heapDefaultValues,
|
||||
out IReadOnlyList<UdonGraphCompiler.NetworkEventMetadataContainer> networkEventMetadata)
|
||||
{
|
||||
return UdonEditorInterface.CompileGraph(graph, nodeRegistry, out linkedSymbols, out heapDefaultValues, out networkEventMetadata);
|
||||
}
|
||||
|
||||
public Type GetTypeFromTypeString(string typeString)
|
||||
{
|
||||
return UdonEditorInterface.GetTypeFromTypeString(typeString);
|
||||
}
|
||||
|
||||
public void AddTypeResolver(IUAssemblyTypeResolver typeResolver)
|
||||
{
|
||||
UdonEditorInterface.AddTypeResolver(typeResolver);
|
||||
}
|
||||
|
||||
public string[] DisassembleProgram(IUdonProgram program)
|
||||
{
|
||||
return UdonEditorInterface.DisassembleProgram(program);
|
||||
}
|
||||
|
||||
public string DisassembleInstruction(IUdonProgram program, ref uint offset)
|
||||
{
|
||||
return UdonEditorInterface.DisassembleInstruction(program, ref offset);
|
||||
}
|
||||
|
||||
public UdonNodeDefinition GetNodeDefinition(string identifier)
|
||||
{
|
||||
return UdonEditorInterface.GetNodeDefinition(identifier);
|
||||
}
|
||||
|
||||
public IEnumerable<UdonNodeDefinition> GetNodeDefinitions()
|
||||
{
|
||||
return UdonEditorInterface.GetNodeDefinitions();
|
||||
}
|
||||
|
||||
public Dictionary<string, INodeRegistry> GetNodeRegistries()
|
||||
{
|
||||
return UdonEditorInterface.GetNodeRegistries();
|
||||
}
|
||||
|
||||
private IReadOnlyDictionary<string, ReadOnlyCollection<KeyValuePair<string, INodeRegistry>>> _topRegistries;
|
||||
|
||||
public IReadOnlyDictionary<string, ReadOnlyCollection<KeyValuePair<string, INodeRegistry>>> GetTopRegistries()
|
||||
{
|
||||
if (_topRegistries != null) return _topRegistries;
|
||||
|
||||
var topRegistries = new Dictionary<string, List<KeyValuePair<string, INodeRegistry>>>()
|
||||
{
|
||||
{"System", new List<KeyValuePair<string, INodeRegistry>>()},
|
||||
{"Udon", new List<KeyValuePair<string, INodeRegistry>>()},
|
||||
{"VRC", new List<KeyValuePair<string, INodeRegistry>>()},
|
||||
{"UnityEngine", new List<KeyValuePair<string, INodeRegistry>>()},
|
||||
};
|
||||
|
||||
// Go through each node registry and put it in the right parent registry
|
||||
foreach (KeyValuePair<string, INodeRegistry> nodeRegistry in GetNodeRegistries())
|
||||
{
|
||||
if (nodeRegistry.Key.StartsWith("System"))
|
||||
{
|
||||
topRegistries["System"].Add(nodeRegistry);
|
||||
}
|
||||
else if (nodeRegistry.Key.StartsWith("Udon"))
|
||||
{
|
||||
topRegistries["Udon"].Add(nodeRegistry);
|
||||
}
|
||||
else if (nodeRegistry.Key.StartsWith("VRC"))
|
||||
{
|
||||
topRegistries["VRC"].Add(nodeRegistry);
|
||||
}
|
||||
else if (nodeRegistry.Key.StartsWith("UnityEngine"))
|
||||
{
|
||||
topRegistries["UnityEngine"].Add(nodeRegistry);
|
||||
}
|
||||
else if (nodeRegistry.Key.StartsWith("Cinemachine"))
|
||||
{
|
||||
topRegistries["UnityEngine"].Add(nodeRegistry);
|
||||
}
|
||||
else if (nodeRegistry.Key.StartsWith("TMPro"))
|
||||
{
|
||||
topRegistries["UnityEngine"].Add(nodeRegistry);
|
||||
}
|
||||
else if (nodeRegistry.Key.StartsWith("UnityAINavigation"))
|
||||
{
|
||||
topRegistries["UnityEngine"].Add(nodeRegistry);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Todo: note and handle these
|
||||
UnityEngine.Debug.Log($"The Registry {nodeRegistry.Key} needs to be Added Somewhere");
|
||||
}
|
||||
}
|
||||
|
||||
// Save result as cached variable
|
||||
_topRegistries = new ReadOnlyDictionary<string, ReadOnlyCollection<KeyValuePair<string, INodeRegistry>>>
|
||||
(
|
||||
topRegistries.ToDictionary(entry => entry.Key, entry => entry.Value.AsReadOnly())
|
||||
);
|
||||
|
||||
// return cached version
|
||||
return _topRegistries;
|
||||
}
|
||||
|
||||
private Dictionary<string, INodeRegistry> _registryLookup;
|
||||
|
||||
private void CacheRegistryLookup()
|
||||
{
|
||||
_registryLookup = new Dictionary<string, INodeRegistry>();
|
||||
|
||||
foreach (KeyValuePair<string, INodeRegistry> topRegistry in GetNodeRegistries())
|
||||
{
|
||||
// save top-level registry. do we need to do this? probably not
|
||||
_registryLookup.Add(topRegistry.Key, topRegistry.Value);
|
||||
|
||||
foreach (KeyValuePair<string, INodeRegistry> registry in topRegistry.Value.GetNodeRegistries())
|
||||
{
|
||||
_registryLookup.Add(registry.Key, registry.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetRegistry(string name, out INodeRegistry registry)
|
||||
{
|
||||
if(_registryLookup == null)
|
||||
{
|
||||
CacheRegistryLookup();
|
||||
}
|
||||
return _registryLookup.TryGetValue(name, out registry);
|
||||
}
|
||||
|
||||
public IEnumerable<UdonNodeDefinition> GetNodeDefinitions(string baseIdentifier)
|
||||
{
|
||||
return UdonEditorInterface.GetNodeDefinitions(baseIdentifier);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Prefab Utilities
|
||||
|
||||
private static IEnumerable<string> GetAllPrefabAssetPaths()
|
||||
{
|
||||
return AssetDatabase.GetAllAssetPaths()
|
||||
.Where(path => path.EndsWith(".prefab"))
|
||||
.Where(path => path.StartsWith("Assets"));
|
||||
}
|
||||
|
||||
private class EditPrefabAssetScope : IDisposable
|
||||
{
|
||||
private readonly string _assetPath;
|
||||
private readonly GameObject _prefabRoot;
|
||||
public GameObject PrefabRoot => _disposed ? null : _prefabRoot;
|
||||
|
||||
private readonly bool _isEditable;
|
||||
public bool IsEditable => !_disposed && _isEditable;
|
||||
|
||||
private bool _dirty = false;
|
||||
private bool _disposed;
|
||||
|
||||
public EditPrefabAssetScope(string assetPath)
|
||||
{
|
||||
_assetPath = assetPath;
|
||||
_prefabRoot = PrefabUtility.LoadPrefabContents(_assetPath);
|
||||
_isEditable = !PrefabUtility.IsPartOfImmutablePrefab(_prefabRoot);
|
||||
}
|
||||
|
||||
public void MarkDirty()
|
||||
{
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if(_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
if(_dirty)
|
||||
{
|
||||
try
|
||||
{
|
||||
PrefabUtility.SaveAsPrefabAsset(_prefabRoot, _assetPath);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"Failed to save changes to prefab at '{_assetPath}' due to exception '{e}'.");
|
||||
}
|
||||
}
|
||||
|
||||
PrefabUtility.UnloadPrefabContents(_prefabRoot);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user