using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using UnityEngine; using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.ScriptableObjects; using static VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu.Control; namespace nadena.dev.modular_avatar.core.editor { internal class MenuTree { public struct ChildElement { public string menuName; public VRCExpressionsMenu menu; public VRCExpressionsMenu parent; public ModularAvatarMenuInstaller installer; public bool isInstallerRoot; } private readonly HashSet _included; private readonly VRCExpressionsMenu _rootMenu; private readonly Dictionary> _menuChildrenMap; public MenuTree(VRCAvatarDescriptor descriptor) { _rootMenu = descriptor.expressionsMenu; _included = new HashSet(); _menuChildrenMap = new Dictionary>(); if (_rootMenu == null) { _rootMenu = ScriptableObject.CreateInstance(); } _included.Add(_rootMenu); } public void TraverseAvatarMenu() { if (_rootMenu == null) return; TraverseMenu(_rootMenu); } public void TraverseMenuInstaller(ModularAvatarMenuInstaller installer) { if (!installer.enabled) return; if (installer.menuToAppend == null) return; TraverseMenu(installer); } public ImmutableList GetChildren(VRCExpressionsMenu parent) { if (parent == null) parent = _rootMenu; return !_menuChildrenMap.TryGetValue(parent, out ImmutableList immutableList) ? ImmutableList.Empty : immutableList; } public IEnumerable GetChildInstallers(ModularAvatarMenuInstaller parentInstaller) { HashSet visitedMenus = new HashSet(); Queue queue = new Queue(); if (parentInstaller != null && parentInstaller.menuToAppend == null) yield break; if (parentInstaller == null) { queue.Enqueue(_rootMenu); } else { if (parentInstaller.menuToAppend == null) yield break; foreach (KeyValuePair childMenu in GetChildMenus(parentInstaller.menuToAppend)) { queue.Enqueue(childMenu.Value); } } while (queue.Count > 0) { VRCExpressionsMenu parentMenu = queue.Dequeue(); if (visitedMenus.Contains(parentMenu)) continue; visitedMenus.Add(parentMenu); HashSet returnedInstallers = new HashSet(); foreach (ChildElement childElement in GetChildren(parentMenu)) { if (!childElement.isInstallerRoot) { queue.Enqueue(childElement.menu); continue; } if (returnedInstallers.Contains(childElement.installer)) continue; returnedInstallers.Add(childElement.installer); yield return childElement; } } } private void TraverseMenu(VRCExpressionsMenu root) { foreach (KeyValuePair childMenu in GetChildMenus(root)) { TraverseMenu(root, new ChildElement { menuName = childMenu.Key, menu = childMenu.Value }); } } private void TraverseMenu(ModularAvatarMenuInstaller installer) { IEnumerable> childMenus = GetChildMenus(installer.menuToAppend); IEnumerable parents = Enumerable.Empty(); if (installer.installTargetMenu != null && ClonedMenuMappings.TryGetClonedMenus(installer.installTargetMenu, out ImmutableList parentMenus)) { parents = parentMenus; } VRCExpressionsMenu[] parentsMenus = parents.DefaultIfEmpty(installer.installTargetMenu).ToArray(); bool hasChildMenu = false; foreach (KeyValuePair childMenu in childMenus) { hasChildMenu = true; ChildElement childElement = new ChildElement { menuName = childMenu.Key, menu = childMenu.Value, installer = installer, isInstallerRoot = true }; foreach (VRCExpressionsMenu parentMenu in parentsMenus) { TraverseMenu(parentMenu, childElement); } } if (hasChildMenu) return; foreach (VRCExpressionsMenu parentMenu in parentsMenus) { TraverseMenu(parentMenu, new ChildElement { installer = installer, isInstallerRoot = true }); } } private void TraverseMenu(VRCExpressionsMenu parent, ChildElement childElement) { if (parent == null) parent = _rootMenu; childElement.parent = parent; if (!_menuChildrenMap.TryGetValue(parent, out ImmutableList children)) { children = ImmutableList.Empty; _menuChildrenMap[parent] = children; } _menuChildrenMap[parent] = children.Add(childElement); if (childElement.menu == null) return; if (_included.Contains(childElement.menu)) return; _included.Add(childElement.menu); foreach (KeyValuePair childMenu in GetChildMenus(childElement.menu)) { TraverseMenu(childElement.menu, new ChildElement { menuName = childMenu.Key, menu = childMenu.Value, installer = childElement.installer }); } } private static IEnumerable> GetChildMenus(VRCExpressionsMenu expressionsMenu) { return expressionsMenu.controls .Where(control => control.type == ControlType.SubMenu && control.subMenu != null) .Select(control => new KeyValuePair(control.name, control.subMenu)); } } }