refactoring

This commit is contained in:
raiti-chan 2022-12-17 16:51:34 +09:00
parent 0614379590
commit 7367418de8
4 changed files with 149 additions and 220 deletions

View File

@ -3,21 +3,25 @@ using System.Collections.Immutable;
using VRC.SDK3.Avatars.ScriptableObjects;
// ReSharper disable once CheckNamespace
namespace nadena.dev.modular_avatar.core.editor {
namespace nadena.dev.modular_avatar.core.editor
{
public static class ClonedMenuMappings {
private static Dictionary<VRCExpressionsMenu, ImmutableArray<VRCExpressionsMenu>> ClonedMappings =
private static readonly Dictionary<VRCExpressionsMenu, ImmutableArray<VRCExpressionsMenu>> ClonedMappings =
new Dictionary<VRCExpressionsMenu, ImmutableArray<VRCExpressionsMenu>>();
private static Dictionary<VRCExpressionsMenu, VRCExpressionsMenu> OriginalMapping =
private static readonly Dictionary<VRCExpressionsMenu, VRCExpressionsMenu> OriginalMapping =
new Dictionary<VRCExpressionsMenu, VRCExpressionsMenu>();
public static void Clear() {
public static void Clear()
{
ClonedMappings.Clear();
OriginalMapping.Clear();
}
public static void Add(VRCExpressionsMenu original, VRCExpressionsMenu clonedMenu) {
if (!ClonedMappings.TryGetValue(original, out ImmutableArray<VRCExpressionsMenu> clonedMenus)) {
public static void Add(VRCExpressionsMenu original, VRCExpressionsMenu clonedMenu)
{
if (!ClonedMappings.TryGetValue(original, out ImmutableArray<VRCExpressionsMenu> clonedMenus))
{
clonedMenus = ImmutableArray<VRCExpressionsMenu>.Empty;
}
// Usually, one menu is rarely duplicated in multiple menus, so don't bother using a Builder
@ -25,11 +29,13 @@ namespace nadena.dev.modular_avatar.core.editor {
OriginalMapping[clonedMenu] = original;
}
public static bool TryGetClonedMenus(VRCExpressionsMenu original, out ImmutableArray<VRCExpressionsMenu> clonedMenus) {
public static bool TryGetClonedMenus(VRCExpressionsMenu original, out ImmutableArray<VRCExpressionsMenu> clonedMenus)
{
return ClonedMappings.TryGetValue(original, out clonedMenus);
}
public static VRCExpressionsMenu GetOriginal(VRCExpressionsMenu cloned) {
public static VRCExpressionsMenu GetOriginal(VRCExpressionsMenu cloned)
{
return OriginalMapping.TryGetValue(cloned, out VRCExpressionsMenu original) ? original : null;
}
}

View File

@ -122,20 +122,24 @@ namespace nadena.dev.modular_avatar.core.editor
OnDoubleclickSelect.Invoke();
}
protected override TreeViewItem BuildRoot() {
protected override TreeViewItem BuildRoot()
{
this._menuItems.Clear();
this._visitedMenuStack.Clear();
_menuTree = new MenuTree(Avatar);
_menuTree.AvatarsMenuMapping();
foreach (ModularAvatarMenuInstaller installer in this.Avatar.gameObject.GetComponentsInChildren<ModularAvatarMenuInstaller>(true)) {
foreach (ModularAvatarMenuInstaller installer in this.Avatar.gameObject.GetComponentsInChildren<ModularAvatarMenuInstaller>(true))
{
if (installer == Installer) continue;
this._menuTree.MappingMenuInstaller(installer);
}
var root = new TreeViewItem(-1, -1, "<root>");
List<TreeViewItem> treeItems = new List<TreeViewItem> {
new TreeViewItem {
List<TreeViewItem> treeItems = new List<TreeViewItem>
{
new TreeViewItem
{
id = 0,
depth = 0,
displayName = $"{Avatar.gameObject.name} ({(Avatar.expressionsMenu == null ? "None" : Avatar.expressionsMenu.name)})"
@ -149,15 +153,18 @@ namespace nadena.dev.modular_avatar.core.editor
return root;
}
private void TraverseMenu(int depth, List<TreeViewItem> items, VRCExpressionsMenu menu) {
private void TraverseMenu(int depth, List<TreeViewItem> items, VRCExpressionsMenu menu)
{
IEnumerable<MenuTree.ChildElement> children = this._menuTree.GetChildren(menu)
.Where(child => !this._visitedMenuStack.Contains(child.menu));
foreach (MenuTree.ChildElement child in children) {
foreach (MenuTree.ChildElement child in children)
{
string displayName = child.installer == null ?
$"{child.menuName} ({child.menu.name})" :
$"{child.menuName} ({child.menu.name}) InstallerObject : {child.installer.name}";
items.Add(
new TreeViewItem {
new TreeViewItem
{
id = items.Count,
depth = depth,
displayName = displayName
@ -169,53 +176,5 @@ namespace nadena.dev.modular_avatar.core.editor
this._visitedMenuStack.Pop();
}
}
/*
protected override TreeViewItem BuildRoot()
{
_menuItems.Clear();
_visitedMenus.Clear();
if (Avatar.expressionsMenu == null)
{
return new TreeViewItem(0, -1, "No menu");
}
_visitedMenus.Add(Avatar.expressionsMenu);
_menuItems.Add(Avatar.expressionsMenu);
var root = new TreeViewItem {id = -1, depth = -1, displayName = "<root>"};
var treeItems = new List<TreeViewItem>();
treeItems.Add(new TreeViewItem
{id = 0, depth = 0, displayName = $"{Avatar.gameObject.name} ({Avatar.expressionsMenu.name})"});
TraverseMenu(1, treeItems, Avatar.expressionsMenu);
SetupParentsAndChildrenFromDepths(root, treeItems);
return root;
}
private void TraverseMenu(int depth, List<TreeViewItem> items, VRCExpressionsMenu menu)
{
foreach (var control in menu.controls)
{
if (control.type == VRCExpressionsMenu.Control.ControlType.SubMenu
&& control.subMenu != null && !_visitedMenus.Contains(control.subMenu))
{
items.Add(new TreeViewItem
{
id = _menuItems.Count,
depth = depth,
displayName = $"{control.name} ({control.subMenu.name})"
});
_menuItems.Add(control.subMenu);
_visitedMenus.Add(control.subMenu);
TraverseMenu(depth + 1, items, control.subMenu);
}
}
}
*/
}
}

View File

@ -1,5 +1,4 @@
#define NEW
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
@ -22,11 +21,11 @@ namespace nadena.dev.modular_avatar.core.editor
private VRCExpressionsMenu _rootMenu;
#if NEW
private MenuTree _menuTree;
private Stack<ModularAvatarMenuInstaller> _visitedInstallerStack;
public void OnPreprocessAvatar(GameObject avatarRoot) {
public void OnPreprocessAvatar(GameObject avatarRoot)
{
ModularAvatarMenuInstaller[] menuInstallers = avatarRoot.GetComponentsInChildren<ModularAvatarMenuInstaller>(true)
.Where(menuInstaller => menuInstaller.enabled)
.ToArray();
@ -38,7 +37,8 @@ namespace nadena.dev.modular_avatar.core.editor
VRCAvatarDescriptor avatar = avatarRoot.GetComponent<VRCAvatarDescriptor>();
if (avatar.expressionsMenu == null) {
if (avatar.expressionsMenu == null)
{
var menu = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
AssetDatabase.CreateAsset(menu, Util.GenerateAssetPath());
avatar.expressionsMenu = menu;
@ -51,16 +51,18 @@ namespace nadena.dev.modular_avatar.core.editor
avatar.expressionsMenu = CloneMenu(avatar.expressionsMenu);
foreach (ModularAvatarMenuInstaller installer in menuInstallers) {
foreach (ModularAvatarMenuInstaller installer in menuInstallers)
{
_menuTree.MappingMenuInstaller(installer);
}
foreach (MenuTree.ChildElement childElement in _menuTree.GetChildInstallers(null)) {
InstallMenuToAvatarMenu(childElement.installer);
foreach (MenuTree.ChildElement childElement in _menuTree.GetChildInstallers(null))
{
InstallMenu(childElement.installer);
}
}
private void InstallMenuToAvatarMenu(ModularAvatarMenuInstaller installer)
private void InstallMenu(ModularAvatarMenuInstaller installer, VRCExpressionsMenu installTarget = null)
{
if (!installer.enabled) return;
@ -69,29 +71,9 @@ namespace nadena.dev.modular_avatar.core.editor
installer.installTargetMenu = _rootMenu;
}
if (installer.installTargetMenu == null || installer.menuToAppend == null) return;
if (!_clonedMenus.TryGetValue(installer.installTargetMenu, out var targetMenu)) return;
// Clone before appending to sanitize menu icons
targetMenu.controls.AddRange(CloneMenu(installer.menuToAppend).controls);
SplitMenu(installer, targetMenu);
if (_visitedInstallerStack.Contains(installer)) return;
_visitedInstallerStack.Push(installer);
foreach (MenuTree.ChildElement childElement in _menuTree.GetChildInstallers(installer)) {
InstallMenuToInstallerMenu(childElement.parent, childElement.installer);
}
_visitedInstallerStack.Pop();
}
private void InstallMenuToInstallerMenu(VRCExpressionsMenu installTarget, ModularAvatarMenuInstaller installer) {
if (!installer.enabled) return;
if (installer.installTargetMenu == null)
if (installTarget == null)
{
installer.installTargetMenu = _rootMenu;
installTarget = installer.installTargetMenu;
}
if (installer.installTargetMenu == null || installer.menuToAppend == null) return;
@ -101,32 +83,37 @@ namespace nadena.dev.modular_avatar.core.editor
targetMenu.controls.AddRange(CloneMenu(installer.menuToAppend).controls);
SplitMenu(installer, targetMenu);
if (_visitedInstallerStack.Contains(installer)) return;
_visitedInstallerStack.Push(installer);
foreach (MenuTree.ChildElement childElement in _menuTree.GetChildInstallers(installer)) {
InstallMenuToInstallerMenu(childElement.parent, childElement.installer);
foreach (MenuTree.ChildElement childElement in _menuTree.GetChildInstallers(installer))
{
InstallMenu(childElement.installer, childElement.parent);
}
_visitedInstallerStack.Pop();
}
private void SplitMenu(ModularAvatarMenuInstaller installer, VRCExpressionsMenu targetMenu) {
while (targetMenu.controls.Count > VRCExpressionsMenu.MAX_CONTROLS) {
private void SplitMenu(ModularAvatarMenuInstaller installer, VRCExpressionsMenu targetMenu)
{
while (targetMenu.controls.Count > VRCExpressionsMenu.MAX_CONTROLS)
{
// Split target menu
var newMenu = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
AssetDatabase.CreateAsset(newMenu, Util.GenerateAssetPath());
var keepCount = VRCExpressionsMenu.MAX_CONTROLS - 1;
const int keepCount = VRCExpressionsMenu.MAX_CONTROLS - 1;
newMenu.controls.AddRange(targetMenu.controls.Skip(keepCount));
targetMenu.controls.RemoveRange(keepCount,
targetMenu.controls.Count - keepCount
);
targetMenu.controls.Add(new VRCExpressionsMenu.Control() {
targetMenu.controls.Add(new VRCExpressionsMenu.Control
{
name = "More",
type = VRCExpressionsMenu.Control.ControlType.SubMenu,
subMenu = newMenu,
parameter = new VRCExpressionsMenu.Control.Parameter() {
parameter = new VRCExpressionsMenu.Control.Parameter
{
name = ""
},
subParameters = Array.Empty<VRCExpressionsMenu.Control.Parameter>(),
@ -138,86 +125,7 @@ namespace nadena.dev.modular_avatar.core.editor
targetMenu = newMenu;
}
}
#else
private Dictionary<VRCExpressionsMenu, VRCExpressionsMenu> _installTargets;
public void OnPreprocessAvatar(GameObject avatarRoot)
{
var menuInstallers = avatarRoot.GetComponentsInChildren<ModularAvatarMenuInstaller>(true)
.Where(c => c.enabled)
.ToArray();
if (menuInstallers.Length == 0) return;
_clonedMenus = new Dictionary<VRCExpressionsMenu, VRCExpressionsMenu>();
var avatar = avatarRoot.GetComponent<VRCAvatarDescriptor>();
if (avatar.expressionsMenu == null)
{
var menu = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
AssetDatabase.CreateAsset(menu, Util.GenerateAssetPath());
avatar.expressionsMenu = menu;
}
_rootMenu = avatar.expressionsMenu;
avatar.expressionsMenu = CloneMenu(avatar.expressionsMenu);
_installTargets = new Dictionary<VRCExpressionsMenu, VRCExpressionsMenu>(_clonedMenus);
foreach (var install in menuInstallers)
{
InstallMenu(install);
}
}
private void InstallMenu(ModularAvatarMenuInstaller installer)
{
if (!installer.enabled) return;
if (installer.installTargetMenu == null)
{
installer.installTargetMenu = _rootMenu;
}
if (installer.installTargetMenu == null || installer.menuToAppend == null) return;
if (!_installTargets.TryGetValue(installer.installTargetMenu, out var targetMenu)) return;
if (_installTargets.ContainsKey(installer.menuToAppend)) return;
// Clone before appending to sanitize menu icons
targetMenu.controls.AddRange(CloneMenu(installer.menuToAppend).controls);
while (targetMenu.controls.Count > VRCExpressionsMenu.MAX_CONTROLS)
{
// Split target menu
var newMenu = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
AssetDatabase.CreateAsset(newMenu, Util.GenerateAssetPath());
var keepCount = VRCExpressionsMenu.MAX_CONTROLS - 1;
newMenu.controls.AddRange(targetMenu.controls.Skip(keepCount));
targetMenu.controls.RemoveRange(keepCount,
targetMenu.controls.Count - keepCount
);
targetMenu.controls.Add(new VRCExpressionsMenu.Control()
{
name = "More",
type = VRCExpressionsMenu.Control.ControlType.SubMenu,
subMenu = newMenu,
parameter = new VRCExpressionsMenu.Control.Parameter()
{
name = ""
},
subParameters = Array.Empty<VRCExpressionsMenu.Control.Parameter>(),
icon = _moreIcon,
labels = Array.Empty<VRCExpressionsMenu.Control.Label>()
});
_installTargets[installer.installTargetMenu] = newMenu;
targetMenu = newMenu;
}
}
#endif
private VRCExpressionsMenu CloneMenu(VRCExpressionsMenu menu)
{
if (menu == null) return null;

View File

@ -6,10 +6,12 @@ using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
using static VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu.Control;
// ReSharper disable once CheckNamespace
namespace nadena.dev.modular_avatar.core.editor {
public class MenuTree {
public struct ChildElement {
namespace nadena.dev.modular_avatar.core.editor
{
public class MenuTree
{
public struct ChildElement
{
public string menuName;
public VRCExpressionsMenu menu;
public VRCExpressionsMenu parent;
@ -17,61 +19,83 @@ namespace nadena.dev.modular_avatar.core.editor {
public bool isInstallerRoot;
}
private class ImmutableBuilder
{
public ImmutableArray<ChildElement> immutableArray;
public ImmutableArray<ChildElement>.Builder builder;
}
private readonly HashSet<VRCExpressionsMenu> _included;
private readonly VRCExpressionsMenu _rootMenu;
private readonly Dictionary<VRCExpressionsMenu, List<ChildElement>> _menuChildrenMap;
private readonly Dictionary<VRCExpressionsMenu, ImmutableBuilder> _menuChildrenMap;
public MenuTree(VRCAvatarDescriptor descriptor) {
public MenuTree(VRCAvatarDescriptor descriptor)
{
this._rootMenu = descriptor.expressionsMenu;
this._included = new HashSet<VRCExpressionsMenu>();
this._menuChildrenMap = new Dictionary<VRCExpressionsMenu, List<ChildElement>>();
this._menuChildrenMap = new Dictionary<VRCExpressionsMenu, ImmutableBuilder>();
if (this._rootMenu == null) {
if (this._rootMenu == null)
{
this._rootMenu = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
}
this._included.Add(this._rootMenu);
}
public void AvatarsMenuMapping() {
public void AvatarsMenuMapping()
{
if (this._rootMenu == null) return;
this.MappingMenu(this._rootMenu);
}
public void MappingMenuInstaller(ModularAvatarMenuInstaller installer) {
public void MappingMenuInstaller(ModularAvatarMenuInstaller installer)
{
if (!installer.enabled) return;
if (installer.menuToAppend == null) return;
this.MappingMenu(installer);
}
public IEnumerable<ChildElement> GetChildren(VRCExpressionsMenu parent) {
// TODO: ライブラリとするのであれば、複製したリスト or ImmutableArray,を返すのが好ましい
public ImmutableArray<ChildElement> GetChildren(VRCExpressionsMenu parent)
{
if (parent == null) parent = this._rootMenu;
return this._menuChildrenMap.TryGetValue(parent, out List<ChildElement> children)
? children
: Enumerable.Empty<ChildElement>();
if (!this._menuChildrenMap.TryGetValue(parent, out ImmutableBuilder immutableBuilder)) return ImmutableArray<ChildElement>.Empty;
if (immutableBuilder.immutableArray == ImmutableArray<ChildElement>.Empty)
{
immutableBuilder.immutableArray = immutableBuilder.builder.ToImmutable();
}
return immutableBuilder.immutableArray;
}
public IEnumerable<ChildElement> GetChildInstallers(ModularAvatarMenuInstaller parentInstaller) {
public IEnumerable<ChildElement> GetChildInstallers(ModularAvatarMenuInstaller parentInstaller)
{
HashSet<VRCExpressionsMenu> visitedMenus = new HashSet<VRCExpressionsMenu>();
Queue<VRCExpressionsMenu> queue = new Queue<VRCExpressionsMenu>();
if (parentInstaller != null && parentInstaller.menuToAppend == null) yield break;
if (parentInstaller == null) {
if (parentInstaller == null)
{
queue.Enqueue(this._rootMenu);
} else {
}
else
{
if (parentInstaller.menuToAppend == null) yield break;
foreach (KeyValuePair<string,VRCExpressionsMenu> childMenu in GetChildMenus(parentInstaller.menuToAppend)) {
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in GetChildMenus(parentInstaller.menuToAppend))
{
queue.Enqueue(childMenu.Value);
}
}
while (queue.Count > 0) {
while (queue.Count > 0)
{
VRCExpressionsMenu parentMenu = queue.Dequeue();
if (visitedMenus.Contains(parentMenu)) continue;
visitedMenus.Add(parentMenu);
HashSet<ModularAvatarMenuInstaller> returnedInstallers = new HashSet<ModularAvatarMenuInstaller>();
foreach (ChildElement childElement in this.GetChildren(parentMenu)) {
if (!childElement.isInstallerRoot) {
foreach (ChildElement childElement in this.GetChildren(parentMenu))
{
if (!childElement.isInstallerRoot)
{
queue.Enqueue(childElement.menu);
continue;
}
@ -84,51 +108,82 @@ namespace nadena.dev.modular_avatar.core.editor {
}
private void MappingMenu(VRCExpressionsMenu root) {
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in GetChildMenus(root)) {
this.MappingMenu(root, new ChildElement {
private void MappingMenu(VRCExpressionsMenu root)
{
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in GetChildMenus(root))
{
this.MappingMenu(root, new ChildElement
{
menuName = childMenu.Key,
menu = childMenu.Value
});
}
}
private void MappingMenu(ModularAvatarMenuInstaller installer) {
private void MappingMenu(ModularAvatarMenuInstaller installer)
{
IEnumerable<KeyValuePair<string, VRCExpressionsMenu>> childMenus = GetChildMenus(installer.menuToAppend);
IEnumerable<VRCExpressionsMenu> parents = Enumerable.Empty<VRCExpressionsMenu>();
if (installer.installTargetMenu != null &&
ClonedMenuMappings.TryGetClonedMenus(installer.installTargetMenu, out ImmutableArray<VRCExpressionsMenu> parentMenus)) {
ClonedMenuMappings.TryGetClonedMenus(installer.installTargetMenu, out ImmutableArray<VRCExpressionsMenu> parentMenus))
{
parents = parentMenus;
}
VRCExpressionsMenu[] parentsMenus = parents.DefaultIfEmpty(installer.installTargetMenu).ToArray();
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in childMenus) {
ChildElement childElement = new ChildElement {
bool isControlOnlyMenu = true;
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in childMenus)
{
isControlOnlyMenu = false;
ChildElement childElement = new ChildElement
{
menuName = childMenu.Key,
menu = childMenu.Value,
installer = installer,
isInstallerRoot = true
};
foreach (VRCExpressionsMenu parentMenu in parentsMenus) {
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) {
private void MappingMenu(VRCExpressionsMenu parent, ChildElement childElement)
{
if (parent == null) parent = this._rootMenu;
childElement.parent = parent;
if (!this._menuChildrenMap.TryGetValue(parent, out List<ChildElement> children)) {
children = new List<ChildElement>();
if (!this._menuChildrenMap.TryGetValue(parent, out ImmutableBuilder children))
{
children = new ImmutableBuilder
{
builder = ImmutableArray.CreateBuilder<ChildElement>(),
immutableArray = ImmutableArray<ChildElement>.Empty
};
this._menuChildrenMap[parent] = children;
}
children.Add(childElement);
children.builder.Add(childElement);
children.immutableArray = ImmutableArray<ChildElement>.Empty;
if (childElement.menu == null) return;
if (this._included.Contains(childElement.menu)) return;
this._included.Add(childElement.menu);
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in GetChildMenus(childElement.menu)) {
this.MappingMenu(childElement.menu, new ChildElement {
foreach (KeyValuePair<string, VRCExpressionsMenu> childMenu in GetChildMenus(childElement.menu))
{
this.MappingMenu(childElement.menu, new ChildElement
{
menuName = childMenu.Key,
menu = childMenu.Value,
installer = childElement.installer
@ -136,7 +191,8 @@ namespace nadena.dev.modular_avatar.core.editor {
}
}
private static IEnumerable<KeyValuePair<string, VRCExpressionsMenu>> GetChildMenus(VRCExpressionsMenu expressionsMenu) {
private static IEnumerable<KeyValuePair<string, VRCExpressionsMenu>> GetChildMenus(VRCExpressionsMenu expressionsMenu)
{
return expressionsMenu.controls
.Where(control => control.type == ControlType.SubMenu && control.subMenu != null)
.Select(control => new KeyValuePair<string, VRCExpressionsMenu>(control.name, control.subMenu));