Skip to content

Commit 0c52df3

Browse files
authored
feat: workaround VRC expressions menu inspector hang (#1932)
1 parent 4b7558c commit 0c52df3

File tree

7 files changed

+76
-0
lines changed

7 files changed

+76
-0
lines changed

CHANGELOG-PRERELEASE-jp.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11+
- [#1932] Expressions Menuのインスペクターが固まるVRCSDKのバグを迂回
1112

1213
### Fixed
1314
- [#1926] 日本語版FAQページの改訂

CHANGELOG-PRERELEASE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11+
- [#1932] Patched a VRCSDK bug which caused the expressions menu editor to hang
1112

1213
### Fixed
1314
- [#1926] Revisions to the Japanese FAQ page

CHANGELOG-jp.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Modular Avatarの主な変更点をこのファイルで記録しています。
99
## [Unreleased]
1010

1111
### Added
12+
- [#1932] Expressions Menuのインスペクターが固まるVRCSDKのバグを迂回
1213
- [#1924] メニュー描画時間に時間制限を設けた(一時的なデバッグ処置です)
1314

1415
### Fixed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
## [Unreleased]
1212

1313
### Added
14+
- [#1932] Patched a VRCSDK bug which caused the expressions menu editor to hang
1415
- [#1924] Added a watchdog to limit time spent in menu GUI rendering to help diagnose an issue
1516

1617
### Fixed

Editor/HarmonyPatches/PatchLoader.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ internal class PatchLoader
1818
//HierarchyViewPatches.Patch,
1919
#if MA_VRCSDK3_AVATARS
2020
VRCSDKBuildInitiationHook.Patch,
21+
VRCExpressionMenuHangPatch.Patch,
2122
#endif
2223
TMProPostProcessorDisabler.Patch
2324
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#if MA_VRCSDK3_AVATARS
2+
3+
using System.Collections.Generic;
4+
using HarmonyLib;
5+
using VRC.SDK3.Avatars.ScriptableObjects;
6+
7+
namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
8+
{
9+
// Workaround for https://feedback.vrchat.com/sdk-bug-reports/p/sdk-3101-pathologically-slow-behavior-in-vrcexpressionsmenu-issubmenurecursive-c
10+
//
11+
// VRCExpressionsMenuEditor uses IsSubmenuRecursive to check whether a menu is a submenu of the avatar root menu
12+
// in OnEnable, with the intent of setting the "parameters" field if it is. However, this traversal does not deal
13+
// with having multiple paths to the same menu, and has only very limited protection from loops (in that there is
14+
// an "arbitrary" depth limit of 16). This can result in editor hangs when there are many paths to the same node.
15+
//
16+
// Since there's no point visiting the same node multiple times for the same needle menu, we cache whether we've
17+
// visited the menu before here. It's theoretically possible for this to give a different result, since the second
18+
// visit might have a different depth, but I'll leave it up to VRC to do the "correct" fix, and just prevent the
19+
// editor hang for now.
20+
internal static class VRCExpressionMenuHangPatch
21+
{
22+
private static readonly HashSet<VRCExpressionsMenu> _visited = new();
23+
24+
public static void Patch(Harmony obj)
25+
{
26+
var ty_editor = AccessTools.TypeByName("VRCExpressionsMenuEditor");
27+
if (ty_editor == null) return;
28+
29+
var onEnable = AccessTools.Method(ty_editor, "OnEnable");
30+
var isSubmenuRecursive = AccessTools.Method(ty_editor, "IsSubmenuRecursive");
31+
32+
if (onEnable == null || isSubmenuRecursive == null) return;
33+
34+
obj.Patch(onEnable,
35+
// Clear first to ensure we start with a clean slate
36+
new HarmonyMethod(typeof(VRCExpressionMenuHangPatch), nameof(ClearVisited)),
37+
// Clear after to avoid keeping references to objects longer than necessary (potential memory leak)
38+
new HarmonyMethod(typeof(VRCExpressionMenuHangPatch), nameof(ClearVisited))
39+
);
40+
obj.Patch(isSubmenuRecursive,
41+
new HarmonyMethod(typeof(VRCExpressionMenuHangPatch), nameof(IsSubmenuRecursivePrefix)));
42+
}
43+
44+
private static void ClearVisited()
45+
{
46+
_visited.Clear();
47+
}
48+
49+
private static bool IsSubmenuRecursivePrefix(
50+
VRCExpressionsMenu needle,
51+
VRCExpressionsMenu haystack,
52+
int depth,
53+
ref bool __result
54+
)
55+
{
56+
if (haystack != null && !_visited.Add(haystack))
57+
{
58+
// Already visited
59+
__result = false;
60+
return false;
61+
}
62+
63+
return true;
64+
}
65+
}
66+
}
67+
68+
#endif

Editor/HarmonyPatches/VRCExpressionMenuHangPatch.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)