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 { public class MenuTree { public struct ChildElement { public string menuName; public VRCExpressionsMenu menu; public VRCExpressionsMenu parent; public ModularAvatarMenuInstaller installer; public bool isInstallerRoot; } private class ImmutableBuilder { public ImmutableArray immutableArray; public ImmutableArray.Builder builder; } private readonly HashSet _included; private readonly VRCExpressionsMenu _rootMenu; private readonly Dictionary _menuChildrenMap; public MenuTree(VRCAvatarDescriptor descriptor) { this._rootMenu = descriptor.expressionsMenu; this._included = new HashSet(); this._menuChildrenMap = new Dictionary(); if (this._rootMenu == null) { this._rootMenu = ScriptableObject.CreateInstance(); } this._included.Add(this._rootMenu); } public void AvatarsMenuMapping() { if (this._rootMenu == null) return; this.MappingMenu(this._rootMenu); } public void MappingMenuInstaller(ModularAvatarMenuInstaller installer) { if (!installer.enabled) return; if (installer.menuToAppend == null) return; this.MappingMenu(installer); } public ImmutableArray GetChildren(VRCExpressionsMenu parent) { if (parent == null) parent = this._rootMenu; if (!this._menuChildrenMap.TryGetValue(parent, out ImmutableBuilder immutableBuilder)) return ImmutableArray.Empty; if (immutableBuilder.immutableArray == ImmutableArray.Empty) { immutableBuilder.immutableArray = immutableBuilder.builder.ToImmutable(); } return immutableBuilder.immutableArray; } 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(this._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 this.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 MappingMenu(VRCExpressionsMenu root) { foreach (KeyValuePair childMenu in GetChildMenus(root)) { this.MappingMenu(root, new ChildElement { menuName = childMenu.Key, menu = childMenu.Value }); } } private void MappingMenu(ModularAvatarMenuInstaller installer) { IEnumerable> childMenus = GetChildMenus(installer.menuToAppend); IEnumerable parents = Enumerable.Empty(); if (installer.installTargetMenu != null && ClonedMenuMappings.TryGetClonedMenus(installer.installTargetMenu, out ImmutableArray parentMenus)) { parents = parentMenus; } VRCExpressionsMenu[] parentsMenus = parents.DefaultIfEmpty(installer.installTargetMenu).ToArray(); bool isControlOnlyMenu = true; foreach (KeyValuePair childMenu in childMenus) { isControlOnlyMenu = false; ChildElement childElement = new ChildElement { menuName = childMenu.Key, menu = childMenu.Value, installer = installer, isInstallerRoot = true }; foreach (VRCExpressionsMenu parentMenu in parentsMenus) { this.MappingMenu(parentMenu, childElement); } } if (!isControlOnlyMenu) return; { foreach (VRCExpressionsMenu parentMenu in parentsMenus) { this.MappingMenu(parentMenu, new ChildElement { installer = installer, isInstallerRoot = true }); } } } private void MappingMenu(VRCExpressionsMenu parent, ChildElement childElement) { if (parent == null) parent = this._rootMenu; childElement.parent = parent; if (!this._menuChildrenMap.TryGetValue(parent, out ImmutableBuilder children)) { children = new ImmutableBuilder { builder = ImmutableArray.CreateBuilder(), immutableArray = ImmutableArray.Empty }; this._menuChildrenMap[parent] = children; } children.builder.Add(childElement); children.immutableArray = ImmutableArray.Empty; if (childElement.menu == null) return; if (this._included.Contains(childElement.menu)) return; this._included.Add(childElement.menu); foreach (KeyValuePair childMenu in GetChildMenus(childElement.menu)) { this.MappingMenu(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)); } } }