Skip to content

Instantly share code, notes, and snippets.

@JLChnToZ
Last active September 20, 2021 11:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JLChnToZ/145adc96a5feecc228928a5c133b37b5 to your computer and use it in GitHub Desktop.
Save JLChnToZ/145adc96a5feecc228928a5c133b37b5 to your computer and use it in GitHub Desktop.
Unity Inspector Extension to display/manipulate UdonBehaviours internal state within editor.
using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEditor;
using VRC.Udon;
using VRC.Udon.Common.Interfaces;
namespace UInspectorPlus.Udon {
public class UdonInspectorDrawer: InspectorDrawer {
static readonly FieldInfo programField = typeof(UdonBehaviour).GetField("_program", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
readonly Dictionary<string, MethodPropertyDrawer> symbolDrawers = new Dictionary<string, MethodPropertyDrawer>();
readonly HashSet<string> exportedSymbols = new HashSet<string>();
readonly HashSet<string> syncedSymbols = new HashSet<string>();
string[] entryPoints;
string[] filteredEntryPoints;
int entryPointIndex = -1;
bool foldSymbols, foldUndely;
static UdonInspectorDrawer() => RegisterCustomInspectorDrawer<UdonInspectorDrawer>(typeof(UdonBehaviour));
IUdonProgram program;
string lastSearch;
public UdonInspectorDrawer(object target, Type targetType, bool shown, bool showProps, bool showPrivateFields, bool showObsolete, bool showMethods) :
base(target, targetType, shown, showProps, showPrivateFields, showObsolete, showMethods) {
lastSearch = searchText;
}
protected override void Draw(bool readOnly) {
var ub = target as UdonBehaviour;
Color color = GUI.color;
if (UpdateProgram(programField.GetValue(target) as IUdonProgram) || searchText != lastSearch) {
lastSearch = searchText;
UpdateSearch();
}
if (program != null) {
if (symbolDrawers.Count > 0) {
if (foldSymbols = EditorGUILayout.Foldout(foldSymbols, "Udon Variables")) {
foreach (var kv in symbolDrawers) {
if (!string.IsNullOrEmpty(searchText) && !kv.Key.Contains(searchText))
continue;
if (!exportedSymbols.Contains(kv.Key)) {
if (allowPrivate) GUI.color = new Color(color.r, color.g, color.b, 0.5F);
else continue;
} else
GUI.color = color;
var value = ub.GetProgramVariable(kv.Key);
kv.Value.Draw(readOnly);
if (kv.Value.Changed)
ub.SetProgramVariable(kv.Key, kv.Value.Value);
else
kv.Value.Value = value;
}
}
GUI.color = color;
} else
EditorGUILayout.HelpBox("The udon program does not contains any variables.", MessageType.Info);
if (filteredEntryPoints != null) {
EditorGUILayout.BeginHorizontal();
entryPointIndex = EditorGUILayout.Popup("Udon Events", entryPointIndex, filteredEntryPoints);
if (GUILayout.Button("Execute", EditorStyles.miniButton, GUILayout.ExpandWidth(false)) &&
!string.IsNullOrWhiteSpace(filteredEntryPoints[entryPointIndex]))
ub.RunProgram(filteredEntryPoints[entryPointIndex]);
EditorGUILayout.EndHorizontal();
}
} else {
entryPoints = null;
EditorGUILayout.HelpBox("There is no program runtime atteched to this Udon behaviour at the moment.", MessageType.Info);
}
GUI.color = color;
EditorGUILayout.Space();
if (foldUndely = EditorGUILayout.Foldout(foldUndely, "Show Advanced (Undely UdonBehaviour Stuffs)"))
base.Draw(readOnly);
}
bool UpdateProgram(IUdonProgram program) {
if (this.program == program) return false;
this.program = program;
exportedSymbols.Clear();
syncedSymbols.Clear();
if (program == null) {
entryPoints = null;
CleanDrawers();
symbolDrawers.Clear();
return true;
}
var symbolTable = program.SymbolTable;
var symbols = symbolTable.GetSymbols();
exportedSymbols.UnionWith(symbolTable.GetExportedSymbols());
syncedSymbols.UnionWith(program.SyncMetadataTable.GetAllSyncMetadata().Select(meta => meta.Name));
foreach (var symbol in symbols) {
var symbolType = symbolTable.GetSymbolType(symbol);
if (symbolType == null) continue;
if (!symbolDrawers.TryGetValue(symbol, out var drawer) || drawer.requiredType != symbolType) {
drawer?.Dispose();
symbolDrawers[symbol] = new MethodPropertyDrawer(
symbolType,
syncedSymbols.Contains(symbol) ? $"(S) {symbol}" : symbol,
null, allowPrivate, false
);
}
}
symbols = program.EntryPoints.GetSymbols();
if (entryPoints == null || entryPoints.Length != symbols.Length)
entryPoints = new string[symbols.Length + 1];
entryPoints[0] = " ";
symbols.CopyTo(entryPoints, 1);
entryPointIndex = 0;
UpdateSearch();
return true;
}
void UpdateSearch() {
filteredEntryPoints = entryPoints == null ? null :
string.IsNullOrWhiteSpace(searchText) ? entryPoints :
entryPoints.Where(p => string.IsNullOrWhiteSpace(p) || p.Contains(searchText)).ToArray();
}
void CleanDrawers() {
foreach (var drawer in symbolDrawers.Values)
drawer.Dispose();
}
public override void Dispose() {
base.Dispose();
CleanDrawers();
}
}
}
@JLChnToZ
Copy link
Author

This script is intend to be used as an extension for Unity Inspector+ for debugging Udon Behaviours for VRChat SDK3 worlds. Inspector+ is a low level plugin for Unity which uses reflection to let you edit properties and/or methods on the fly during the game is running within editor. This extension extends this functionality to udon powered scripts/graphs.

To use this script, you will need to import Unity Script Tester and drop this script to editor directory inside your project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment