From 6ba3f95177ee019becb6a863e2664b8b0faac6ba Mon Sep 17 00:00:00 2001 From: bd_ Date: Sat, 4 Mar 2023 13:51:05 +0900 Subject: [PATCH] fixing issues with error reporting and dependent component cleanup --- .../Editor/AvatarProcessor.cs | 160 +++++++++++------- 1 file changed, 102 insertions(+), 58 deletions(-) diff --git a/Packages/nadena.dev.modular-avatar/Editor/AvatarProcessor.cs b/Packages/nadena.dev.modular-avatar/Editor/AvatarProcessor.cs index c288d7bc..e5cde52b 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/AvatarProcessor.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/AvatarProcessor.cs @@ -23,8 +23,10 @@ */ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using nadena.dev.modular_avatar.editor.ErrorReporting; @@ -147,78 +149,120 @@ namespace nadena.dev.modular_avatar.core.editor { try { - AssetDatabase.StartAssetEditing(); - nowProcessing = true; - - ClearEditorOnlyTagComponents(avatarGameObject.transform); - - BoneDatabase.ResetBones(); - PathMappings.Init(vrcAvatarDescriptor.gameObject); - ClonedMenuMappings.Clear(); - - // Sometimes people like to nest one avatar in another, when transplanting clothing. To avoid issues - // with inconsistently determining the avatar root, we'll go ahead and remove the extra sub-avatars - // here. - foreach (Transform directChild in avatarGameObject.transform) + try { - foreach (var component in directChild.GetComponentsInChildren(true)) + AssetDatabase.StartAssetEditing(); + nowProcessing = true; + + ClearEditorOnlyTagComponents(avatarGameObject.transform); + + BoneDatabase.ResetBones(); + PathMappings.Init(vrcAvatarDescriptor.gameObject); + ClonedMenuMappings.Clear(); + + // Sometimes people like to nest one avatar in another, when transplanting clothing. To avoid issues + // with inconsistently determining the avatar root, we'll go ahead and remove the extra sub-avatars + // here. + foreach (Transform directChild in avatarGameObject.transform) { - Object.DestroyImmediate(component); + foreach (var component in directChild.GetComponentsInChildren(true)) + { + Object.DestroyImmediate(component); + } + + foreach (var component in directChild.GetComponentsInChildren(true)) + { + Object.DestroyImmediate(component); + } } - foreach (var component in directChild.GetComponentsInChildren(true)) - { - Object.DestroyImmediate(component); - } + var context = new BuildContext(vrcAvatarDescriptor); + + new ActionGenerator(context).OnPreprocessAvatar(vrcAvatarDescriptor); + new RenameParametersHook().OnPreprocessAvatar(avatarGameObject, context); + new MergeAnimatorProcessor().OnPreprocessAvatar(avatarGameObject, context); + context.AnimationDatabase.Bootstrap(vrcAvatarDescriptor); + + new MenuInstallHook().OnPreprocessAvatar(avatarGameObject, context); + new MergeArmatureHook().OnPreprocessAvatar(context, avatarGameObject); + new BoneProxyProcessor().OnPreprocessAvatar(avatarGameObject); + new VisibleHeadAccessoryProcessor(vrcAvatarDescriptor).Process(context); + new RemapAnimationPass(vrcAvatarDescriptor).Process(context.AnimationDatabase); + new BlendshapeSyncAnimationProcessor().OnPreprocessAvatar(avatarGameObject, context); + PhysboneBlockerPass.Process(avatarGameObject); + + context.AnimationDatabase.Commit(); + + new GCGameObjectsPass(context, avatarGameObject).OnPreprocessAvatar(); + + AfterProcessing?.Invoke(avatarGameObject); } + finally + { + AssetDatabase.StopAssetEditing(); - var context = new BuildContext(vrcAvatarDescriptor); + nowProcessing = false; - new ActionGenerator(context).OnPreprocessAvatar(vrcAvatarDescriptor); - new RenameParametersHook().OnPreprocessAvatar(avatarGameObject, context); - new MergeAnimatorProcessor().OnPreprocessAvatar(avatarGameObject, context); - context.AnimationDatabase.Bootstrap(vrcAvatarDescriptor); + // Ensure that we clean up AvatarTagComponents after failed processing. This ensures we don't re-enter + // processing from the Awake method on the unprocessed AvatarTagComponents + var toDestroy = avatarGameObject.GetComponentsInChildren(true).ToList(); + var retryDestroy = new List(); - new MenuInstallHook().OnPreprocessAvatar(avatarGameObject, context); - new MergeArmatureHook().OnPreprocessAvatar(context, avatarGameObject); - new BoneProxyProcessor().OnPreprocessAvatar(avatarGameObject); - new VisibleHeadAccessoryProcessor(vrcAvatarDescriptor).Process(context); - new RemapAnimationPass(vrcAvatarDescriptor).Process(context.AnimationDatabase); - new BlendshapeSyncAnimationProcessor().OnPreprocessAvatar(avatarGameObject, context); - PhysboneBlockerPass.Process(avatarGameObject); + // Sometimes AvatarTagComponents have interdependencies and need to be deleted in the right order; + // retry until we purge them all. + bool madeProgress = true; + while (toDestroy.Count > 0) + { + if (!madeProgress) + { + throw new Exception("One or more components failed to destroy." + + RuntimeUtil.AvatarRootPath(toDestroy[0].gameObject)); + } - context.AnimationDatabase.Commit(); + foreach (var component in toDestroy) + { + try + { + if (component != null) + { + UnityEngine.Object.DestroyImmediate(component); + madeProgress = true; + } + } + catch (Exception e) + { + retryDestroy.Add(component); + } + } - new GCGameObjectsPass(context, avatarGameObject).OnPreprocessAvatar(); + toDestroy = retryDestroy; + retryDestroy = new List(); + } - AfterProcessing?.Invoke(avatarGameObject); + var activator = avatarGameObject.GetComponent(); + if (activator != null) + { + UnityEngine.Object.DestroyImmediate(activator); + } + + ClonedMenuMappings.Clear(); + + ErrorReportUI.MaybeOpenErrorReportUI(); + + AssetDatabase.SaveAssets(); + + Resources.UnloadUnusedAssets(); + } } - finally + catch (Exception e) { - AssetDatabase.StopAssetEditing(); + BuildReport.LogException(e); + throw; + } - nowProcessing = false; - - // Ensure that we clean up AvatarTagComponents after failed processing. This ensures we don't re-enter - // processing from the Awake method on the unprocessed AvatarTagComponents - foreach (var component in avatarGameObject.GetComponentsInChildren(true)) - { - UnityEngine.Object.DestroyImmediate(component); - } - - var activator = avatarGameObject.GetComponent(); - if (activator != null) - { - UnityEngine.Object.DestroyImmediate(activator); - } - - ClonedMenuMappings.Clear(); - - ErrorReportUI.MaybeOpenErrorReportUI(); - - AssetDatabase.SaveAssets(); - - Resources.UnloadUnusedAssets(); + if (!BuildReport.CurrentReport.CurrentAvatar.successful) + { + throw new Exception("Fatal error reported during avatar processing."); } } }