Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
43 changes: 43 additions & 0 deletions RuntimeUnityEditor.Core/Features/ContextMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Reflection;
using HarmonyLib;
using RuntimeUnityEditor.Core.Breakpoints;
using RuntimeUnityEditor.Core.ChangeHistory;
using RuntimeUnityEditor.Core.Inspector.Entries;
using RuntimeUnityEditor.Core.ObjectTree;
Expand Down Expand Up @@ -51,6 +52,7 @@ public override bool Enabled
/// <inheritdoc />
protected override void Initialize(InitSettings initSettings)
{
// TODO This mess needs a rewrite with a sane API
MenuContents.AddRange(new[]
{
new MenuEntry("! Destroyed unity Object !", obj => obj is UnityEngine.Object uobj && !uobj, null),
Expand All @@ -75,7 +77,12 @@ protected override void Initialize(InitSettings initSettings)
new MenuEntry("Send to REPL", o => o != null && REPL.ReplWindow.Initialized, o => REPL.ReplWindow.Instance.IngestObject(o)),

new MenuEntry(),
});

AddBreakpointControls(MenuContents);

MenuContents.AddRange(new[]
{
new MenuEntry("Copy to clipboard", o => o != null && Clipboard.ClipboardWindow.Initialized, o =>
{
if (Clipboard.ClipboardWindow.Contents.LastOrDefault() != o)
Expand Down Expand Up @@ -167,6 +174,42 @@ o is Sprite ||
DisplayType = FeatureDisplayType.Hidden;
}

private void AddBreakpointControls(List<MenuEntry> menuContents)
{
menuContents.AddRange(AddGroup("call", (o, info) => info as MethodBase));
menuContents.AddRange(AddGroup("getter", (o, info) => info is PropertyInfo pi ? pi.GetGetMethod(true) : null));
menuContents.AddRange(AddGroup("setter", (o, info) => info is PropertyInfo pi ? pi.GetSetMethod(true) : null));
menuContents.Add(new MenuEntry());
return;

IEnumerable<MenuEntry> AddGroup(string name, Func<object, MemberInfo, MethodBase> getMethod)
{
yield return new MenuEntry("Attach " + name + " breakpoint (this instance)", o =>
{
if (o == null) return false;
var target = getMethod(o, _objMemberInfo);
return target != null && !Breakpoints.Breakpoints.IsAttached(target, o);
}, o => Breakpoints.Breakpoints.AttachBreakpoint(getMethod(o, _objMemberInfo), o));
yield return new MenuEntry("Detach " + name + " breakpoint (this instance)", o =>
{
if (o == null) return false;
var target = getMethod(o, _objMemberInfo);
return target != null && Breakpoints.Breakpoints.IsAttached(target, o);
}, o => Breakpoints.Breakpoints.DetachBreakpoint(getMethod(o, _objMemberInfo), o));

yield return new MenuEntry("Attach " + name + " breakpoint (all instances)", o =>
{
var target = getMethod(o, _objMemberInfo);
return target != null && !Breakpoints.Breakpoints.IsAttached(target, null);
}, o => Breakpoints.Breakpoints.AttachBreakpoint(getMethod(o, _objMemberInfo), null));
yield return new MenuEntry("Detach " + name + " breakpoint (all instances)", o =>
{
var target = getMethod(o, _objMemberInfo);
return target != null && Breakpoints.Breakpoints.IsAttached(target, null);
}, o => Breakpoints.Breakpoints.DetachBreakpoint(getMethod(o, _objMemberInfo), null));
}
}

/// <summary>
/// Show the context menu at current cursor position.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions RuntimeUnityEditor.Core/RuntimeUnityEditor.Core.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@
<Compile Include="$(MSBuildThisFileDirectory)Utils\UI\InterfaceMaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)WindowBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)WindowManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Windows\Breakpoints\BreakpointHit.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Windows\Breakpoints\BreakpointHitException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Windows\Breakpoints\BreakpointPatchInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Windows\Breakpoints\Breakpoints.cs" />
<Compile Include="$(MSBuildThisFileDirectory)windows\breakpoints\BreakpointsWindow.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Windows\Breakpoints\DebuggerBreakType.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Windows\ChangeHistory\Change.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Windows\ChangeHistory\ChangeAction.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Windows\ChangeHistory\ChangeAssignment.cs" />
Expand Down
42 changes: 42 additions & 0 deletions RuntimeUnityEditor.Core/Windows/Breakpoints/BreakpointHit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Diagnostics;
using System.Linq;

namespace RuntimeUnityEditor.Core.Breakpoints
{
public sealed class BreakpointHit
{
public BreakpointHit(BreakpointPatchInfo origin, object instance, object[] args, object result, StackTrace trace)
{
Origin = origin;
Instance = instance;
Args = args;
Result = result;
Trace = trace;
TraceString = trace.ToString();
Time = DateTime.UtcNow;
}

public readonly BreakpointPatchInfo Origin;
public readonly object Instance;
public readonly object[] Args;
public readonly object Result;
public readonly StackTrace Trace;
internal readonly string TraceString;
public readonly DateTime Time;

private string _toStr, _searchStr;
public string GetSearchableString()
{
if (_searchStr == null)
_searchStr = $"{Origin.Target.DeclaringType?.FullName}.{Origin.Target.Name}\t{Result}\t{string.Join("\t", Args.Select(x => x?.ToString() ?? "").ToArray())}";
return _searchStr;
}
public override string ToString()
{
if (_toStr == null)
_toStr = $"{Origin.Target.DeclaringType?.FullName ?? "???"}.{Origin.Target.Name} |Result> {Result?.ToString() ?? "NULL"} |Args> {string.Join(" | ", Args.Select(x => x?.ToString() ?? "NULL").ToArray())}";
return _toStr;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace RuntimeUnityEditor.Core.Breakpoints
{
internal sealed class BreakpointHitException : Exception
{
public BreakpointHitException(string message) : base(message) { }
}
}
36 changes: 36 additions & 0 deletions RuntimeUnityEditor.Core/Windows/Breakpoints/BreakpointPatchInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace RuntimeUnityEditor.Core.Breakpoints
{
public sealed class BreakpointPatchInfo
{
public MethodBase Target { get; }
public MethodInfo Patch { get; }
public List<object> InstanceFilters { get; } = new List<object>();

public BreakpointPatchInfo(MethodBase target, MethodInfo patch, object instanceFilter)
{
Target = target;
Patch = patch;
if (instanceFilter != null)
InstanceFilters.Add(instanceFilter);
}

private string _toStr, _searchStr;

internal string GetSearchableString()
{
if (_searchStr == null)
_searchStr = $"{Target.DeclaringType?.FullName}.{Target.Name}\t{string.Join("\t", InstanceFilters.Select(x => x?.ToString()).ToArray())}";
return _searchStr;
}
public override string ToString()
{
if (_toStr == null)
_toStr = $"{Target.DeclaringType?.FullName ?? "???"}.{Target.Name} |Instances> {string.Join(" | ", InstanceFilters.Select(x => x?.ToString() ?? "NULL").ToArray())}";
return _toStr;
}
}
}
114 changes: 114 additions & 0 deletions RuntimeUnityEditor.Core/Windows/Breakpoints/Breakpoints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using HarmonyLib;

namespace RuntimeUnityEditor.Core.Breakpoints
{
public static class Breakpoints
{
private static readonly Harmony _harmony = new Harmony("RuntimeUnityEditor.Core.Breakpoints");
private static readonly HarmonyMethod _handlerMethodRet = new HarmonyMethod(typeof(Hooks), nameof(Hooks.BreakpointHandlerReturn));
private static readonly HarmonyMethod _handlerMethodNoRet = new HarmonyMethod(typeof(Hooks), nameof(Hooks.BreakpointHandlerNoReturn));
private static readonly Dictionary<MethodBase, BreakpointPatchInfo> _appliedPatches = new Dictionary<MethodBase, BreakpointPatchInfo>();
public static ICollection<BreakpointPatchInfo> AppliedPatches => _appliedPatches.Values;

public static bool Enabled { get; set; } = true;
public static DebuggerBreakType DebuggerBreaking { get; set; }

public static event Action<BreakpointHit> OnBreakpointHit;

public static bool AttachBreakpoint(MethodBase target, object instance)
{
if (_appliedPatches.TryGetValue(target, out var pi))
{
if (instance != null)
pi.InstanceFilters.Add(instance);
else
pi.InstanceFilters.Clear();
return true;
}

var hasReturn = target is MethodInfo mi && mi.ReturnType != typeof(void);
var patch = _harmony.Patch(target, postfix: hasReturn ? _handlerMethodRet : _handlerMethodNoRet);
if (patch != null)
{
_appliedPatches[target] = new BreakpointPatchInfo(target, patch, instance);
return true;
}

return false;
}

public static bool DetachBreakpoint(MethodBase target, object instance)
{
if (_appliedPatches.TryGetValue(target, out var pi))
{
if (instance == null)
pi.InstanceFilters.Clear();
else
pi.InstanceFilters.Remove(instance);

if (pi.InstanceFilters.Count == 0)
{
_harmony.Unpatch(target, pi.Patch);
_appliedPatches.Remove(target);
return true;
}
}

return false;
}

public static bool IsAttached(MethodBase target, object instance)
{
if (_appliedPatches.TryGetValue(target, out var pi))
{
return instance == null && pi.InstanceFilters.Count == 0 || pi.InstanceFilters.Contains(instance);
}

return false;
}

public static void DetachAll()
{
_harmony.UnpatchSelf();
_appliedPatches.Clear();
}

private static void AddHit(object __instance, MethodBase __originalMethod, object[] __args, object __result)
{
if (!Enabled) return;

if (!_appliedPatches.TryGetValue(__originalMethod, out var pi)) return;

if (pi.InstanceFilters.Count > 0 && !pi.InstanceFilters.Contains(__instance)) return;

if (DebuggerBreaking == DebuggerBreakType.ThrowCatch)
{
try { throw new BreakpointHitException(pi.Target.Name); }
catch (BreakpointHitException) { }
}
else if (DebuggerBreaking == DebuggerBreakType.DebuggerBreak)
{
Debugger.Break();
}

OnBreakpointHit?.Invoke(new BreakpointHit(pi, __instance, __args, __result, new StackTrace(2, true)));
}

private static class Hooks
{
public static void BreakpointHandlerReturn(object __instance, MethodBase __originalMethod, object[] __args, object __result)
{
AddHit(__instance, __originalMethod, __args, __result);
}

public static void BreakpointHandlerNoReturn(object __instance, MethodBase __originalMethod, object[] __args)
{
AddHit(__instance, __originalMethod, __args, null);
}
}
}
}
Loading