From 32e015554646164e1f6e55d93226680460f7a007 Mon Sep 17 00:00:00 2001 From: bd_ <bd_@nadena.dev> Date: Mon, 29 Jan 2024 19:10:01 +0900 Subject: [PATCH] feat: add non-component-based hook execution deduplication (#142) This is in preparation for coordination with VRCF: https://discord.com/channels/1001423276553285653/1001424777401077830/1201398914574712842 --- CHANGELOG.md | 1 + Editor/VRChat/BuildFrameworkPreprocessHook.cs | 11 ++++- Editor/VRChat/HookDedup.cs | 43 +++++++++++++++++++ Editor/VRChat/HookDedup.cs.meta | 3 ++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 Editor/VRChat/HookDedup.cs create mode 100644 Editor/VRChat/HookDedup.cs.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index 0305ac8..07e5c8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] ### Added +- Added a non-component-based check for double execution of hooks (#142) ### Fixed diff --git a/Editor/VRChat/BuildFrameworkPreprocessHook.cs b/Editor/VRChat/BuildFrameworkPreprocessHook.cs index 71053b4..d3fc84c 100644 --- a/Editor/VRChat/BuildFrameworkPreprocessHook.cs +++ b/Editor/VRChat/BuildFrameworkPreprocessHook.cs @@ -27,8 +27,13 @@ internal class BuildFrameworkPreprocessHook : IVRCSDKPreprocessAvatarCallback public bool OnPreprocessAvatar(GameObject avatarGameObject) { + // Legacy: For VRCF if (avatarGameObject.GetComponent<AlreadyProcessedTag>()?.processingCompleted == true) return true; + var state = HookDedup.RecordAvatar(avatarGameObject); + if (state.ranEarlyHook) return true; + state.ranEarlyHook = true; + try { var holder = avatarGameObject.AddComponent<ContextHolder>(); @@ -53,7 +58,11 @@ internal class BuildFrameworkOptimizeHook : IVRCSDKPreprocessAvatarCallback public bool OnPreprocessAvatar(GameObject avatarGameObject) { if (avatarGameObject.GetComponent<AlreadyProcessedTag>()?.processingCompleted == true) return true; - + + var state = HookDedup.RecordAvatar(avatarGameObject); + if (state.ranOptimization) return true; + state.ranOptimization = true; + var holder = avatarGameObject.GetComponent<ContextHolder>(); if (holder == null) return true; diff --git a/Editor/VRChat/HookDedup.cs b/Editor/VRChat/HookDedup.cs new file mode 100644 index 0000000..f4d59df --- /dev/null +++ b/Editor/VRChat/HookDedup.cs @@ -0,0 +1,43 @@ +#if NDMF_VRCSDK3_AVATARS + +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace nadena.dev.ndmf.VRChat +{ + /// <summary> + /// Ensures that we don't run hooks more than once. This is in preparation for coordinating with VRCFury to have all + /// nondestructive utilities independently execute hooks as part of Apply on Play, while deduplicating to ensure that + /// we don't rerun hooks on the same avatar. + /// </summary> + internal static class HookDedup + { + internal class State + { + internal bool ranEarlyHook; + internal bool ranOptimization; + } + + private static ConditionalWeakTable<GameObject, State> _avatars = new ConditionalWeakTable<GameObject, State>(); + + public static State RecordAvatar(GameObject root) + { + if (_avatars.TryGetValue(root, out var state)) + { + return state; + } + + state = new State(); + _avatars.Add(root, state); + + return state; + } + + public static bool HasAvatar(GameObject root) + { + return _avatars.TryGetValue(root, out _); + } + } +} + +#endif \ No newline at end of file diff --git a/Editor/VRChat/HookDedup.cs.meta b/Editor/VRChat/HookDedup.cs.meta new file mode 100644 index 0000000..c764a08 --- /dev/null +++ b/Editor/VRChat/HookDedup.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a4f36e298dab4f8ba30bcd68940cf3a6 +timeCreated: 1706520401 \ No newline at end of file