|
using System; |
|
using System.IO; |
|
using System.Text; |
|
using System.Collections.Generic; |
|
using System.Reflection; |
|
using UnityEngine; |
|
using UnityEditor; |
|
|
|
using UnityObject = UnityEngine.Object; |
|
|
|
public class PersistentCallFinder: EditorWindow |
|
{ |
|
|
|
private string rawContent; |
|
private string[] lines; |
|
private Vector2 scroll; |
|
private UnityObject[] references; |
|
|
|
[MenuItem("Window/Persistent Call Stub Generator")] |
|
public static PersistentCallFinder Open() |
|
{ |
|
return GetWindow<PersistentCallFinder>(); |
|
} |
|
|
|
protected void OnEnable() |
|
{ |
|
titleContent = new GUIContent("Persistent Call Stub Generator"); |
|
} |
|
|
|
protected void OnGUI() |
|
{ |
|
EditorGUILayout.BeginVertical(); |
|
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); |
|
if (GUILayout.Button("(Re)load From Selected Scene Object(s)", EditorStyles.toolbarButton)) |
|
InitStubFile(false); |
|
if (GUILayout.Button("(Re)load From Selected Asset(s)", EditorStyles.toolbarButton)) |
|
InitStubFile(true); |
|
EditorGUI.BeginDisabledGroup(references == null || references.Length == 0); |
|
if (GUILayout.Button("Save Stub File", EditorStyles.toolbarButton)) |
|
{ |
|
var file = EditorUtility.SaveFilePanel("Save Stub File", "", "PersistentCallStub.cs", "cs"); |
|
if (!string.IsNullOrEmpty(file)) |
|
{ |
|
File.WriteAllText(file, rawContent); |
|
AssetDatabase.Refresh(); |
|
} |
|
} |
|
if (GUILayout.Button("Remove All References", EditorStyles.toolbarButton)) |
|
{ |
|
RemoveAllPersistentCalls(references); |
|
references = null; |
|
} |
|
EditorGUI.EndDisabledGroup(); |
|
EditorGUILayout.EndHorizontal(); |
|
scroll = EditorGUILayout.BeginScrollView(scroll); |
|
if (lines != null) |
|
foreach (string c in lines) |
|
GUILayout.Label(c); |
|
EditorGUILayout.EndScrollView(); |
|
EditorGUILayout.EndVertical(); |
|
} |
|
|
|
public static string GetPersistantCallStubFile(UnityObject[] targets) |
|
{ |
|
const string STUB_HEADER = "using System;{0}" + |
|
"using UnityEngine;{0}{0}" + |
|
"#if UNITY_EDITOR{0}" + |
|
"public static class PersistentCallStub{1}{0}" + |
|
"{{{0}" + |
|
"\tpublic static void GetPersistantDelegate(UnityEngine.Object obj){0}" + |
|
"\t{{{0}"; |
|
|
|
const string STUB_FOOTER = "\t}\n" + |
|
"}\n#endif"; |
|
|
|
var nl = Environment.NewLine; |
|
var calls = FindPersistentCall(targets); |
|
var sb = new StringBuilder(); |
|
sb.AppendFormat(STUB_HEADER, nl, DateTime.Now.ToFileTime()); |
|
foreach (var call in calls) |
|
{ |
|
sb.Append($"\t\t// {GetHirechyPath(call.source)}{nl}\t\t(obj as {call.type}).{call.method.Name}("); |
|
bool firstArg = true; |
|
foreach (var arg in call.method.GetParameters()) |
|
{ |
|
if (!firstArg) sb.Append(", "); |
|
sb.Append($"default({arg.ParameterType})"); |
|
firstArg = false; |
|
} |
|
sb.AppendLine($");{nl}"); |
|
} |
|
sb.AppendLine(STUB_FOOTER.Replace("\n", nl)); |
|
return sb.ToString(); |
|
} |
|
|
|
private void InitStubFile(bool assets) |
|
{ |
|
references = assets ? |
|
Selection.GetFiltered<UnityObject>(SelectionMode.DeepAssets) : |
|
Selection.GetFiltered<UnityObject>(SelectionMode.Deep); |
|
rawContent = GetPersistantCallStubFile(references); |
|
lines = rawContent.Split(new[] { Environment.NewLine }, StringSplitOptions.None); |
|
} |
|
|
|
public static PersistentCallInfo[] FindPersistentCall(params UnityObject[] targets) |
|
{ |
|
var result = new List<PersistentCallInfo>(); |
|
foreach (var obj in GetObjectsInTree(targets)) |
|
{ |
|
if (obj == null) continue; |
|
var so = new SerializedObject(obj); |
|
if (so == null) continue; |
|
var prop = so.GetIterator(); |
|
bool goDeeper = false; |
|
do |
|
{ |
|
if (!(goDeeper = prop.propertyType == SerializedPropertyType.Generic)) |
|
continue; |
|
var persistentCalls = prop.FindPropertyRelative("m_PersistentCalls.m_Calls"); |
|
if (persistentCalls == null || persistentCalls.type != "PersistentCall") |
|
continue; |
|
for (int i = 0; i < persistentCalls.arraySize; i++) |
|
{ |
|
var call = persistentCalls.GetArrayElementAtIndex(i); |
|
|
|
var target = call.FindPropertyRelative("m_Target").objectReferenceValue; |
|
if (target == null) |
|
continue; |
|
|
|
var type = target.GetType(); |
|
|
|
var method = type.GetMethod( |
|
call.FindPropertyRelative("m_MethodName").stringValue, |
|
GetTypes(call) |
|
); |
|
if (method == null) |
|
continue; |
|
|
|
result.Add(new PersistentCallInfo |
|
{ |
|
type = type, |
|
method = method, |
|
source = obj, |
|
target = target, |
|
}); |
|
} |
|
goDeeper = false; |
|
} while (prop.Next(goDeeper)); |
|
} |
|
return result.ToArray(); |
|
} |
|
|
|
private static void RemoveAllPersistentCalls(params UnityObject[] targets) |
|
{ |
|
foreach (var obj in GetObjectsInTree(targets)) |
|
{ |
|
if (obj == null) continue; |
|
var so = new SerializedObject(obj); |
|
if (so == null) continue; |
|
var prop = so.GetIterator(); |
|
bool goDeeper = false; |
|
do |
|
{ |
|
if (!(goDeeper = prop.propertyType == SerializedPropertyType.Generic)) |
|
continue; |
|
var persistentCalls = prop.FindPropertyRelative("m_PersistentCalls.m_Calls"); |
|
if (persistentCalls != null && persistentCalls.type == "PersistentCall") |
|
{ |
|
persistentCalls.ClearArray(); |
|
goDeeper = false; |
|
} |
|
} while (prop.Next(goDeeper)); |
|
} |
|
} |
|
|
|
private static ICollection<UnityObject> GetObjectsInTree(UnityObject[] targets) |
|
{ |
|
var scan = new Stack<UnityObject>(targets); |
|
var objs = new HashSet<UnityObject>(); |
|
while (scan.Count > 0) |
|
{ |
|
var target = scan.Pop(); |
|
|
|
if (target is Transform transform) |
|
target = transform.gameObject; |
|
|
|
if (target is GameObject gameObject) |
|
{ |
|
foreach (Transform child in gameObject.transform) |
|
scan.Push(child); |
|
foreach (var mb in gameObject.GetComponents<MonoBehaviour>()) |
|
objs.Add(mb); |
|
} |
|
else if (target is ScriptableObject || target is MonoBehaviour) |
|
objs.Add(target); |
|
} |
|
return objs; |
|
} |
|
|
|
private static Type[] GetTypes(SerializedProperty serializedProperty) |
|
{ |
|
var argsProp = serializedProperty.FindPropertyRelative("m_Arguments"); |
|
var argTypes = new Type[argsProp.arraySize]; |
|
for (int i = 0; i < argTypes.Length; i++) |
|
argTypes[i] = Type.GetType(argsProp |
|
.GetArrayElementAtIndex(i) |
|
.FindPropertyRelative("m_ObjectArgumentAssemblyTypeName") |
|
.stringValue |
|
); |
|
return argTypes; |
|
} |
|
|
|
private static string GetHirechyPath(UnityObject obj) |
|
{ |
|
var path = AssetDatabase.GetAssetOrScenePath(obj); |
|
if (obj is Component component) |
|
obj = component.transform; |
|
else if (obj is GameObject gameObject) |
|
obj = gameObject.transform; |
|
if (obj is Transform transform) |
|
{ |
|
var sb = new StringBuilder(path); |
|
var stack = new Stack<string>(); |
|
do |
|
stack.Push(transform.name); |
|
while ((transform = transform.parent) != null); |
|
while (stack.Count > 0) |
|
{ |
|
sb.Append('/'); |
|
sb.Append(stack.Pop()); |
|
} |
|
path = sb.ToString(); |
|
} |
|
return path; |
|
} |
|
|
|
public struct PersistentCallInfo |
|
{ |
|
public UnityObject source, target; |
|
public Type type; |
|
public MethodInfo method; |
|
} |
|
} |