From fc3ee7a47f31da5958e80b605a6111f52c80954d Mon Sep 17 00:00:00 2001 From: raiti-chan Date: Wed, 14 Dec 2022 00:04:59 +0900 Subject: [PATCH] Changed IsMenuReachable method to verify that AvatarMenu can be reached via multiple Installers --- .../Editor/Inspector/MenuInstallerEditor.cs | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/Packages/nadena.dev.modular-avatar/Editor/Inspector/MenuInstallerEditor.cs b/Packages/nadena.dev.modular-avatar/Editor/Inspector/MenuInstallerEditor.cs index d93b11c1..8bb7dc62 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Inspector/MenuInstallerEditor.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/Inspector/MenuInstallerEditor.cs @@ -26,11 +26,14 @@ namespace nadena.dev.modular_avatar.core.editor private HashSet _avatarMenus; private HashSet _menuFolderCreators; + private Dictionary> _menuInstallersMap; + private void OnEnable() { _installer = (ModularAvatarMenuInstaller) target; FindMenus(); + FindMenuInstallers(); FindMenuFolderCreators(); } @@ -250,6 +253,48 @@ namespace nadena.dev.modular_avatar.core.editor } } + private void FindMenuInstallers() + { + if (targets.Length > 1) + { + _menuInstallersMap = null; + return; + } + + _menuInstallersMap = new Dictionary>(); + var avatar = RuntimeUtil.FindAvatarInParents(((Component)target).transform); + if (avatar == null) return; + var menuInstallers = avatar.GetComponentsInChildren(); + foreach (ModularAvatarMenuInstaller menuInstaller in menuInstallers) + { + if (menuInstaller == target) continue; + if (menuInstaller.menuToAppend == null) continue; + var visitedMenus = new HashSet(); + var queue = new Queue(); + queue.Enqueue(menuInstaller.menuToAppend); + + while (queue.Count > 0) + { + VRCExpressionsMenu parent = queue.Dequeue(); + var controls = parent.controls.Where(control => control.type == VRCExpressionsMenu.Control.ControlType.SubMenu && control.subMenu != null); + foreach (VRCExpressionsMenu.Control control in controls) + { + // Do not filter in LINQ to avoid closure allocation + if (visitedMenus.Contains(control.subMenu)) continue; + if (!_menuInstallersMap.TryGetValue(control.subMenu, out List fromInstallers)) + { + fromInstallers = new List(); + _menuInstallersMap[control.subMenu] = fromInstallers; + } + + fromInstallers.Add(menuInstaller); + visitedMenus.Add(control.subMenu); + queue.Enqueue(control.subMenu); + } + } + } + } + private void FindMenuFolderCreators() { if (this.targets.Length > 1) @@ -267,9 +312,28 @@ namespace nadena.dev.modular_avatar.core.editor } } - private bool IsMenuReachable(VRCAvatarDescriptor avatar, VRCExpressionsMenu menu) + private bool IsMenuReachable(VRCAvatarDescriptor avatar, VRCExpressionsMenu menu, HashSet visitedInstaller = null) { - return _avatarMenus == null || _avatarMenus.Contains(menu); + if (_avatarMenus == null || _avatarMenus.Contains(menu)) return true; + + if (_menuInstallersMap == null) return true; + if (visitedInstaller == null) visitedInstaller = new HashSet { (ModularAvatarMenuInstaller)target }; + + if (!_menuInstallersMap.TryGetValue(menu, out List installers)) return false; + foreach (ModularAvatarMenuInstaller installer in installers) + { + // Root is always reachable if installTargetMenu is null + if (installer.installTargetMenu == null) return true; + // Even in a circular structure, it may be possible to reach root by another path. + if (visitedInstaller.Contains(installer)) continue; + visitedInstaller.Add(installer); + if (IsMenuReachable(avatar, installer.installTargetMenu, visitedInstaller)) + { + return true; + } + } + + return false; } private bool IsMenuReachable(VRCAvatarDescriptor avatar, ModularAvatarSubMenuCreator creator, HashSet session)