diff --git a/Packages/net.fushizen.modular-avatar/Editor/ApplyOnPlay.cs b/Packages/net.fushizen.modular-avatar/Editor/ApplyOnPlay.cs index 1a47d935..43e0bebf 100644 --- a/Packages/net.fushizen.modular-avatar/Editor/ApplyOnPlay.cs +++ b/Packages/net.fushizen.modular-avatar/Editor/ApplyOnPlay.cs @@ -34,13 +34,34 @@ namespace net.fushizen.modular_avatar.core.editor public static class ApplyOnPlay { private const string MENU_NAME = "Tools/Modular Avatar/Apply on Play"; - + + /** + * We need to process avatars before lyuma's av3 emulator wakes up and processes avatars; it does this in Awake, + * so we have to do our processing in Awake as well. This seems to work fine when first entering play mode, but + * if you subsequently enable an initially-disabled avatar, processing from within Awake causes an editor crash. + * + * To workaround this, we initially process in awake; then, after OnPlayModeStateChanged is invoked (ie, after + * all initially-enabled components have Awake called), we switch to processing from Start instead. + */ + private static RuntimeUtil.OnDemandSource armedSource = RuntimeUtil.OnDemandSource.Awake; + static ApplyOnPlay() { EditorApplication.playModeStateChanged += OnPlayModeStateChanged; + RuntimeUtil.OnDemandProcessAvatar = MaybeProcessAvatar; Menu.SetChecked(MENU_NAME, ModularAvatarSettings.applyOnPlay); } + private static void MaybeProcessAvatar(RuntimeUtil.OnDemandSource source, AvatarTagComponent component) + { + if (ModularAvatarSettings.applyOnPlay && source == armedSource && component != null) + { + var avatar = RuntimeUtil.FindAvatarInParents(component.transform); + if (avatar == null) return; + AvatarProcessor.ProcessAvatar(avatar.gameObject); + } + } + [MenuItem(MENU_NAME)] private static void ToggleApplyOnPlay() { @@ -50,35 +71,9 @@ namespace net.fushizen.modular_avatar.core.editor private static void OnPlayModeStateChanged(PlayModeStateChange obj) { - if (obj == PlayModeStateChange.EnteredPlayMode && ModularAvatarSettings.applyOnPlay) + if (obj == PlayModeStateChange.EnteredPlayMode) { - // TODO - only apply modular avatar changes? - foreach (var root in SceneManager.GetActiveScene().GetRootGameObjects()) - { - foreach (var avatar in root.GetComponentsInChildren(true)) - { - if (avatar.GetComponentsInChildren(true).Length > 0) - { - UnpackPrefabsCompletely(avatar.gameObject); - VRCBuildPipelineCallbacks.OnPreprocessAvatar(avatar.gameObject); - } - } - } - } - } - - private static void UnpackPrefabsCompletely(GameObject obj) - { - if (PrefabUtility.IsAnyPrefabInstanceRoot(obj)) - { - PrefabUtility.UnpackPrefabInstance(obj, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction); - } - else - { - foreach (Transform child in obj.transform) - { - UnpackPrefabsCompletely(child.gameObject); - } + armedSource = RuntimeUtil.OnDemandSource.Start; } } } diff --git a/Packages/net.fushizen.modular-avatar/Editor/Av3EmuHook.cs b/Packages/net.fushizen.modular-avatar/Editor/AvatarProcessor.cs similarity index 57% rename from Packages/net.fushizen.modular-avatar/Editor/Av3EmuHook.cs rename to Packages/net.fushizen.modular-avatar/Editor/AvatarProcessor.cs index 9d315afb..1a2f2d02 100644 --- a/Packages/net.fushizen.modular-avatar/Editor/Av3EmuHook.cs +++ b/Packages/net.fushizen.modular-avatar/Editor/AvatarProcessor.cs @@ -25,35 +25,17 @@ using System; using System.Reflection; using UnityEditor; +using UnityEngine; using VRC.SDK3.Avatars.Components; using VRC.SDKBase.Editor.BuildPipeline; namespace net.fushizen.modular_avatar.core.editor { [InitializeOnLoad] - internal class Av3EmuHook + internal class AvatarProcessor : IVRCSDKPreprocessAvatarCallback, IVRCSDKPostprocessAvatarCallback { - static Av3EmuHook() + static AvatarProcessor() { - if (EditorApplication.isPlayingOrWillChangePlaymode) - { - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - { - var runtime = assembly.GetType("LyumaAv3Runtime"); - if (runtime == null) continue; - - var addHook = runtime.GetMethod("AddInitAvatarHook", BindingFlags.Static | BindingFlags.Public); - if (addHook == null) continue; - - addHook.Invoke(null, new object[] - { - -999999, - (Action)(av => VRCBuildPipelineCallbacks.OnPreprocessAvatar(av.gameObject)) - }); - - break; - } - } EditorApplication.playModeStateChanged += OnPlayModeStateChanged; } @@ -64,5 +46,42 @@ namespace net.fushizen.modular_avatar.core.editor Util.DeleteTemporaryAssets(); } } + + public int callbackOrder => -9000; + + public void OnPostprocessAvatar() + { + Util.DeleteTemporaryAssets(); + } + + public bool OnPreprocessAvatar(GameObject avatarGameObject) + { + try + { + ProcessAvatar(avatarGameObject); + return true; + } + catch (Exception e) + { + Debug.LogError(e); + return false; + } + } + + public static void ProcessAvatar(GameObject avatarGameObject) + { + BoneDatabase.ResetBones(); + PathMappings.Clear(); + + new MergeArmatureHook().OnPreprocessAvatar(avatarGameObject); + new RetargetMeshes().OnPreprocessAvatar(avatarGameObject); + new BoneProxyProcessor().OnPreprocessAvatar(avatarGameObject); + new MergeAnimatorProcessor().OnPreprocessAvatar(avatarGameObject); + + foreach (var component in avatarGameObject.GetComponentsInChildren(true)) + { + UnityEngine.Object.DestroyImmediate(component); + } + } } } \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Editor/Av3EmuHook.cs.meta b/Packages/net.fushizen.modular-avatar/Editor/AvatarProcessor.cs.meta similarity index 100% rename from Packages/net.fushizen.modular-avatar/Editor/Av3EmuHook.cs.meta rename to Packages/net.fushizen.modular-avatar/Editor/AvatarProcessor.cs.meta diff --git a/Packages/net.fushizen.modular-avatar/Editor/BoneProxyHook.cs b/Packages/net.fushizen.modular-avatar/Editor/BoneProxyProcessor.cs similarity index 90% rename from Packages/net.fushizen.modular-avatar/Editor/BoneProxyHook.cs rename to Packages/net.fushizen.modular-avatar/Editor/BoneProxyProcessor.cs index 20a103af..8fd8031a 100644 --- a/Packages/net.fushizen.modular-avatar/Editor/BoneProxyHook.cs +++ b/Packages/net.fushizen.modular-avatar/Editor/BoneProxyProcessor.cs @@ -28,11 +28,9 @@ using VRC.SDKBase.Editor.BuildPipeline; namespace net.fushizen.modular_avatar.core.editor { - public class BoneProxyHook : HookBase + internal class BoneProxyProcessor { - public override int callbackOrder => HookSequence.SEQ_BONE_PROXY; - - protected override bool OnPreprocessAvatarWrapped(GameObject avatarGameObject) + internal void OnPreprocessAvatar(GameObject avatarGameObject) { var boneProxies = avatarGameObject.GetComponentsInChildren(true); @@ -56,8 +54,6 @@ namespace net.fushizen.modular_avatar.core.editor } Object.DestroyImmediate(proxy); } - - return true; } } } \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Editor/BoneProxyHook.cs.meta b/Packages/net.fushizen.modular-avatar/Editor/BoneProxyProcessor.cs.meta similarity index 100% rename from Packages/net.fushizen.modular-avatar/Editor/BoneProxyHook.cs.meta rename to Packages/net.fushizen.modular-avatar/Editor/BoneProxyProcessor.cs.meta diff --git a/Packages/net.fushizen.modular-avatar/Editor/HookBase.cs b/Packages/net.fushizen.modular-avatar/Editor/HookBase.cs deleted file mode 100644 index a464886c..00000000 --- a/Packages/net.fushizen.modular-avatar/Editor/HookBase.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using UnityEngine; -using VRC.SDKBase.Editor.BuildPipeline; - -namespace net.fushizen.modular_avatar.core.editor -{ - public abstract class HookBase : IVRCSDKPreprocessAvatarCallback - { - public bool OnPreprocessAvatar(GameObject avatarGameObject) - { - try - { - return OnPreprocessAvatarWrapped(avatarGameObject); - } - catch (Exception e) - { - Debug.LogError(e); - return false; - } - } - - protected abstract bool OnPreprocessAvatarWrapped(GameObject avatarGameObject); - public abstract int callbackOrder { get; } - } -} \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Editor/HookBase.cs.meta b/Packages/net.fushizen.modular-avatar/Editor/HookBase.cs.meta deleted file mode 100644 index d87eaf01..00000000 --- a/Packages/net.fushizen.modular-avatar/Editor/HookBase.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: d65a87d13d234177b18f1b0fbbf12360 -timeCreated: 1662780435 \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Editor/HookSequence.cs b/Packages/net.fushizen.modular-avatar/Editor/HookSequence.cs deleted file mode 100644 index 7386e315..00000000 --- a/Packages/net.fushizen.modular-avatar/Editor/HookSequence.cs +++ /dev/null @@ -1,35 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2022 bd_ - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace net.fushizen.modular_avatar.core.editor -{ - internal static class HookSequence - { - public const int SEQ_RESETTERS = -90000; - public const int SEQ_MERGE_ARMATURE = SEQ_RESETTERS + 1; - public const int SEQ_RETARGET_MESH = SEQ_MERGE_ARMATURE + 1; - public const int SEQ_BONE_PROXY = SEQ_RETARGET_MESH + 1; - public const int SEQ_MERGE_ANIMATORS = SEQ_BONE_PROXY + 1; - } -} \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Editor/HookSequence.cs.meta b/Packages/net.fushizen.modular-avatar/Editor/HookSequence.cs.meta deleted file mode 100644 index ec3e754e..00000000 --- a/Packages/net.fushizen.modular-avatar/Editor/HookSequence.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 9e6b3680d07242d38d5b2c6b00951ca0 -timeCreated: 1661632859 \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Editor/LastResortTagComponentCleaner.cs b/Packages/net.fushizen.modular-avatar/Editor/LastResortTagComponentCleaner.cs deleted file mode 100644 index d87723aa..00000000 --- a/Packages/net.fushizen.modular-avatar/Editor/LastResortTagComponentCleaner.cs +++ /dev/null @@ -1,47 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2022 bd_ - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -using UnityEngine; -using VRC.SDKBase.Editor.BuildPipeline; - -namespace net.fushizen.modular_avatar.core.editor -{ - /** - * Ensure that any AvatarTagComponents are purged just before upload. - */ - public class LastResortTagComponentCleaner : HookBase - { - public override int callbackOrder => 0; - - protected override bool OnPreprocessAvatarWrapped(GameObject avatarGameObject) - { - foreach (var component in avatarGameObject.GetComponentsInChildren(true)) - { - UnityEngine.Object.DestroyImmediate(component); - } - - return true; - } - } -} \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Editor/LastResortTagComponentCleaner.cs.meta b/Packages/net.fushizen.modular-avatar/Editor/LastResortTagComponentCleaner.cs.meta deleted file mode 100644 index a21f5ae0..00000000 --- a/Packages/net.fushizen.modular-avatar/Editor/LastResortTagComponentCleaner.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 921628a5f4434452bcfa3a926d4ebdac -timeCreated: 1661564171 \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Editor/MergeAnimatorHook.cs b/Packages/net.fushizen.modular-avatar/Editor/MergeAnimatorProcessor.cs similarity index 96% rename from Packages/net.fushizen.modular-avatar/Editor/MergeAnimatorHook.cs rename to Packages/net.fushizen.modular-avatar/Editor/MergeAnimatorProcessor.cs index 2e7cd8ac..ee727795 100644 --- a/Packages/net.fushizen.modular-avatar/Editor/MergeAnimatorHook.cs +++ b/Packages/net.fushizen.modular-avatar/Editor/MergeAnimatorProcessor.cs @@ -31,20 +31,18 @@ using VRC.SDKBase.Editor.BuildPipeline; namespace net.fushizen.modular_avatar.core.editor { - public class MergeAnimatorHook : HookBase + internal class MergeAnimatorProcessor { private const string SAMPLE_PATH_PACKAGE = "Packages/com.vrchat.avatars/Samples/AV3 Demo Assets/Animation/Controllers"; private const string SAMPLE_PATH_LEGACY = "Assets/VRCSDK/Examples3/Animation/Controllers"; - public override int callbackOrder => HookSequence.SEQ_MERGE_ANIMATORS; - private Dictionary defaultControllers_ = new Dictionary(); Dictionary mergeSessions = new Dictionary(); - protected override bool OnPreprocessAvatarWrapped(GameObject avatarGameObject) + internal void OnPreprocessAvatar(GameObject avatarGameObject) { defaultControllers_.Clear(); mergeSessions.Clear(); @@ -84,8 +82,6 @@ namespace net.fushizen.modular_avatar.core.editor descriptor.baseAnimationLayers = FinishSessions(descriptor.baseAnimationLayers); descriptor.specialAnimationLayers = FinishSessions(descriptor.specialAnimationLayers); - - return true; } private VRCAvatarDescriptor.CustomAnimLayer[] FinishSessions( diff --git a/Packages/net.fushizen.modular-avatar/Editor/MergeAnimatorHook.cs.meta b/Packages/net.fushizen.modular-avatar/Editor/MergeAnimatorProcessor.cs.meta similarity index 100% rename from Packages/net.fushizen.modular-avatar/Editor/MergeAnimatorHook.cs.meta rename to Packages/net.fushizen.modular-avatar/Editor/MergeAnimatorProcessor.cs.meta diff --git a/Packages/net.fushizen.modular-avatar/Editor/MergeArmatureHook.cs b/Packages/net.fushizen.modular-avatar/Editor/MergeArmatureHook.cs index a48b5e1b..a24e8643 100644 --- a/Packages/net.fushizen.modular-avatar/Editor/MergeArmatureHook.cs +++ b/Packages/net.fushizen.modular-avatar/Editor/MergeArmatureHook.cs @@ -34,14 +34,12 @@ using Vector3 = UnityEngine.Vector3; namespace net.fushizen.modular_avatar.core.editor { - public class MergeArmatureHook : HookBase + public class MergeArmatureHook { - public override int callbackOrder => HookSequence.SEQ_MERGE_ARMATURE; - private Dictionary BoneRemappings = new Dictionary(); private List ToDelete = new List(); - protected override bool OnPreprocessAvatarWrapped(GameObject avatarGameObject) + internal bool OnPreprocessAvatar(GameObject avatarGameObject) { BoneRemappings.Clear(); ToDelete.Clear(); diff --git a/Packages/net.fushizen.modular-avatar/Editor/MeshRetargeter.cs b/Packages/net.fushizen.modular-avatar/Editor/MeshRetargeter.cs index 1dc6281e..abf83aee 100644 --- a/Packages/net.fushizen.modular-avatar/Editor/MeshRetargeter.cs +++ b/Packages/net.fushizen.modular-avatar/Editor/MeshRetargeter.cs @@ -30,16 +30,6 @@ using VRC.SDKBase.Editor.BuildPipeline; namespace net.fushizen.modular_avatar.core.editor { - internal class MeshRetargeterResetHook : HookBase - { - public override int callbackOrder => HookSequence.SEQ_RESETTERS; - protected override bool OnPreprocessAvatarWrapped(GameObject avatarGameObject) - { - BoneDatabase.ResetBones(); - return true; - } - } - internal static class BoneDatabase { private static Dictionary IsRetargetable = new Dictionary(); @@ -84,10 +74,9 @@ namespace net.fushizen.modular_avatar.core.editor } } - internal class RetargetMeshes : HookBase + internal class RetargetMeshes { - public override int callbackOrder => HookSequence.SEQ_RETARGET_MESH; - protected override bool OnPreprocessAvatarWrapped(GameObject avatarGameObject) + internal void OnPreprocessAvatar(GameObject avatarGameObject) { foreach (var renderer in avatarGameObject.GetComponentsInChildren(true)) { @@ -131,8 +120,6 @@ namespace net.fushizen.modular_avatar.core.editor } } - - return true; } } diff --git a/Packages/net.fushizen.modular-avatar/Editor/PathMappings.cs b/Packages/net.fushizen.modular-avatar/Editor/PathMappings.cs index 5875cfeb..d6cd956d 100644 --- a/Packages/net.fushizen.modular-avatar/Editor/PathMappings.cs +++ b/Packages/net.fushizen.modular-avatar/Editor/PathMappings.cs @@ -73,14 +73,4 @@ namespace net.fushizen.modular_avatar.core.editor return path; } } - - internal class ClearPathMappings : HookBase - { - public override int callbackOrder => HookSequence.SEQ_RESETTERS; - protected override bool OnPreprocessAvatarWrapped(GameObject avatarGameObject) - { - PathMappings.Clear(); - return true; - } - } } \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Runtime/AvatarTagComponent.cs b/Packages/net.fushizen.modular-avatar/Runtime/AvatarTagComponent.cs index fd09d595..1255c5f2 100644 --- a/Packages/net.fushizen.modular-avatar/Runtime/AvatarTagComponent.cs +++ b/Packages/net.fushizen.modular-avatar/Runtime/AvatarTagComponent.cs @@ -22,6 +22,7 @@ * SOFTWARE. */ +using System; using UnityEngine; namespace net.fushizen.modular_avatar.core @@ -29,7 +30,19 @@ namespace net.fushizen.modular_avatar.core /** * This abstract base class is injected into the VRCSDK avatar component allowlist to avoid */ + [DefaultExecutionOrder(-9999)] // run before av3emu public abstract class AvatarTagComponent : MonoBehaviour { + private void Awake() + { + if (!RuntimeUtil.isPlaying || this == null) return; + RuntimeUtil.OnDemandProcessAvatar(RuntimeUtil.OnDemandSource.Awake, this); + } + + private void Start() + { + if (!RuntimeUtil.isPlaying || this == null) return; + RuntimeUtil.OnDemandProcessAvatar(RuntimeUtil.OnDemandSource.Start, this); + } } } \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Runtime/RuntimeUtil.cs b/Packages/net.fushizen.modular-avatar/Runtime/RuntimeUtil.cs index 3c3d24c1..ea21ff6e 100644 --- a/Packages/net.fushizen.modular-avatar/Runtime/RuntimeUtil.cs +++ b/Packages/net.fushizen.modular-avatar/Runtime/RuntimeUtil.cs @@ -37,7 +37,17 @@ namespace net.fushizen.modular_avatar.core // Initialized in Util public static Action delayCall = (_) => { }; - + + public enum OnDemandSource + { + Awake, + Start + } + + public delegate void OnDemandProcessAvatarDelegate(OnDemandSource source, AvatarTagComponent component); + + public static OnDemandProcessAvatarDelegate OnDemandProcessAvatar = (_m, _c) => { }; + [CanBeNull] public static string RelativePath(GameObject root, GameObject child) {