Remove the need for an av3emu patch

This commit is contained in:
bd_ 2022-09-11 14:19:05 -07:00 committed by bd_
parent cf82c93e55
commit ab51e9dd82
18 changed files with 96 additions and 208 deletions

View File

@ -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<VRCAvatarDescriptor>(true))
{
if (avatar.GetComponentsInChildren<AvatarTagComponent>(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;
}
}
}

View File

@ -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<VRCAvatarDescriptor>)(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<AvatarTagComponent>(true))
{
UnityEngine.Object.DestroyImmediate(component);
}
}
}
}

View File

@ -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<ModularAvatarBoneProxy>(true);
@ -56,8 +54,6 @@ namespace net.fushizen.modular_avatar.core.editor
}
Object.DestroyImmediate(proxy);
}
return true;
}
}
}

View File

@ -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; }
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: d65a87d13d234177b18f1b0fbbf12360
timeCreated: 1662780435

View File

@ -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;
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 9e6b3680d07242d38d5b2c6b00951ca0
timeCreated: 1661632859

View File

@ -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<AvatarTagComponent>(true))
{
UnityEngine.Object.DestroyImmediate(component);
}
return true;
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 921628a5f4434452bcfa3a926d4ebdac
timeCreated: 1661564171

View File

@ -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<VRCAvatarDescriptor.AnimLayerType, AnimatorController> defaultControllers_ =
new Dictionary<VRCAvatarDescriptor.AnimLayerType, AnimatorController>();
Dictionary<VRCAvatarDescriptor.AnimLayerType, AnimatorCombiner> mergeSessions =
new Dictionary<VRCAvatarDescriptor.AnimLayerType, AnimatorCombiner>();
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(

View File

@ -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<Transform, Transform> BoneRemappings = new Dictionary<Transform, Transform>();
private List<GameObject> ToDelete = new List<GameObject>();
protected override bool OnPreprocessAvatarWrapped(GameObject avatarGameObject)
internal bool OnPreprocessAvatar(GameObject avatarGameObject)
{
BoneRemappings.Clear();
ToDelete.Clear();

View File

@ -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<Transform, bool> IsRetargetable = new Dictionary<Transform, bool>();
@ -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<SkinnedMeshRenderer>(true))
{
@ -131,8 +120,6 @@ namespace net.fushizen.modular_avatar.core.editor
}
}
return true;
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -37,7 +37,17 @@ namespace net.fushizen.modular_avatar.core
// Initialized in Util
public static Action<NullCallback> 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)
{