From 7bf869835a9281fd527ca8ef93812103a29aa51a Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Thu, 16 Apr 2026 00:59:17 -0500 Subject: [PATCH 1/2] Add session key, to survive Domain Reloads for testing avatar. --- .../SDKInspector/BasisAvatarSDKInspector.cs | 113 +++++++++++++++--- 1 file changed, 99 insertions(+), 14 deletions(-) diff --git a/Basis/Packages/com.basis.sdk/Scripts/Editor/SDKInspector/BasisAvatarSDKInspector.cs b/Basis/Packages/com.basis.sdk/Scripts/Editor/SDKInspector/BasisAvatarSDKInspector.cs index b9b033800e..2f1a9777c1 100644 --- a/Basis/Packages/com.basis.sdk/Scripts/Editor/SDKInspector/BasisAvatarSDKInspector.cs +++ b/Basis/Packages/com.basis.sdk/Scripts/Editor/SDKInspector/BasisAvatarSDKInspector.cs @@ -15,8 +15,11 @@ [CustomEditor(typeof(BasisAvatar))] public partial class BasisAvatarSDKInspector : Editor { + private const string PendingTestInEditorAvatarIdSessionKey = "BasisAvatarSDKInspector.PendingTestInEditorAvatarId"; + public delegate void BeforeTestInEditorHandler(GameObject clone); public static BeforeTestInEditorHandler OnBeforeTestInEditor; + private static BasisAvatar ScheduledTestInEditorAvatar; public static event Action InspectorGuiCreated; public static event Action ButtonClicked; @@ -32,6 +35,71 @@ public partial class BasisAvatarSDKInspector : Editor private Label resultLabel; // Store the result label for later clearing public string Error; public BasisAvatarValidator BasisAvatarValidator; + + [InitializeOnLoadMethod] + private static void InitializeTestInEditorHooks() + { + EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; + EditorApplication.playModeStateChanged += OnPlayModeStateChanged; + } + + private static void OnPlayModeStateChanged(PlayModeStateChange state) + { + if (state != PlayModeStateChange.EnteredPlayMode || !HasPendingTestInEditorAvatarId()) + { + return; + } + + EditorApplication.delayCall -= TryExecutePendingTestInEditor; + EditorApplication.delayCall += TryExecutePendingTestInEditor; + } + + private static void TryExecutePendingTestInEditor() + { + string pendingAvatarId = GetPendingTestInEditorAvatarId(); + if (string.IsNullOrEmpty(pendingAvatarId)) + { + return; + } + + if (!GlobalObjectId.TryParse(pendingAvatarId, out GlobalObjectId avatarId)) + { + ClearPendingTestInEditorAvatarId(); + return; + } + + BasisAvatar avatar = GlobalObjectId.GlobalObjectIdentifierToObjectSlow(avatarId) as BasisAvatar; + ClearPendingTestInEditorAvatarId(); + if (avatar == null) + { + BasisDebug.LogError("Unable to resolve the pending avatar for Test In Editor.", BasisDebug.LogTag.Editor); + return; + } + + RequestAvatarLoad(avatar); + } + + private static bool HasPendingTestInEditorAvatarId() + { + return SessionState.GetBool(PendingTestInEditorAvatarIdSessionKey + ".Exists", false); + } + + private static string GetPendingTestInEditorAvatarId() + { + return SessionState.GetString(PendingTestInEditorAvatarIdSessionKey, string.Empty); + } + + private static void SetPendingTestInEditorAvatarId(string avatarId) + { + SessionState.SetString(PendingTestInEditorAvatarIdSessionKey, avatarId ?? string.Empty); + SessionState.SetBool(PendingTestInEditorAvatarIdSessionKey + ".Exists", !string.IsNullOrEmpty(avatarId)); + } + + private static void ClearPendingTestInEditorAvatarId() + { + SessionState.EraseString(PendingTestInEditorAvatarIdSessionKey); + SessionState.SetBool(PendingTestInEditorAvatarIdSessionKey + ".Exists", false); + } private void OnEnable() { visualTree = AssetDatabase.LoadAssetAtPath(BasisSDKConstants.AvataruxmlPath); @@ -513,9 +581,10 @@ public void AvatarTestInEditorClickFunction() { if (!Application.isPlaying) { - bool result = EditorUtility.DisplayDialog("Confirmation", "this feature requires the editor to be in playmode. do you want to enter play mode now? once done you will need to press it again! please also make sure you have a floor in your scene!", "yes", "no"); + bool result = EditorUtility.DisplayDialog("Confirmation", "this feature requires the editor to be in playmode. do you want to enter play mode now and run Test In Editor automatically? please also make sure you have a floor in your scene!", "yes", "no"); if (result) { + SetPendingTestInEditorAvatarId(GlobalObjectId.GetGlobalObjectIdSlow(Avatar).ToString()); EditorApplication.EnterPlaymode(); } } @@ -525,34 +594,50 @@ public void AvatarTestInEditorClickFunction() } } public void RequestAvatarLoad() + { + RequestAvatarLoad(Avatar); + } + + private static void RequestAvatarLoad(BasisAvatar avatar) { #if BASIS_FRAMEWORK_EXISTS if (BasisLocalPlayer.PlayerReady) { BasisDebug.Log("Player Ready Loading", BasisDebug.LogTag.Editor); - LoadAvatar(); + LoadAvatar(avatar); } else { - ScheduleCallback = true; + ScheduledTestInEditorAvatar = avatar; BasisDebug.Log("Scheduling Load Avatar", BasisDebug.LogTag.Editor); - BasisLocalPlayer.OnLocalPlayerInitalized += LoadAvatar; + BasisLocalPlayer.OnLocalPlayerInitalized -= LoadScheduledAvatar; + BasisLocalPlayer.OnLocalPlayerInitalized += LoadScheduledAvatar; } #endif } - public bool ScheduleCallback = false; - public async void LoadAvatar() + + private static void LoadScheduledAvatar() { #if BASIS_FRAMEWORK_EXISTS - if (ScheduleCallback) + BasisLocalPlayer.OnLocalPlayerInitalized -= LoadScheduledAvatar; + if (ScheduledTestInEditorAvatar == null) { - BasisLocalPlayer.OnLocalPlayerInitalized -= LoadAvatar; - ScheduleCallback = false; + return; } + + BasisAvatar avatar = ScheduledTestInEditorAvatar; + ScheduledTestInEditorAvatar = null; + LoadAvatar(avatar); +#endif + } + + private static async void LoadAvatar(BasisAvatar avatar) + { +#if BASIS_FRAMEWORK_EXISTS BasisDebug.Log("LoadAvatar Called", BasisDebug.LogTag.Editor); var jigglesToReset = new List(); - foreach (MonoBehaviour jiggle in Avatar.gameObject.GetComponentsInChildren(false)) + foreach (MonoBehaviour jiggle in avatar.gameObject.GetComponentsInChildren(false)) { if (jiggle != null && jiggle.GetType().FullName == "GatorDragonGames.JigglePhysics.JiggleRig" @@ -565,18 +650,18 @@ public async void LoadAvatar() if (jigglesToReset.Count > 0) { BasisDebug.Log("Enabled Jiggles were found when Test in Editor was entered. We will disable the avatar in order to reset the Jiggle transforms.", BasisDebug.LogTag.Editor); - Avatar.gameObject.SetActive(false); + avatar.gameObject.SetActive(false); // It's a bit of a hack, but waiting three frames works. await Awaitable.NextFrameAsync(); await Awaitable.NextFrameAsync(); await Awaitable.NextFrameAsync(); - inSceneItem = GameObject.Instantiate(Avatar.gameObject); - Avatar.gameObject.SetActive(true); + inSceneItem = GameObject.Instantiate(avatar.gameObject); + avatar.gameObject.SetActive(true); inSceneItem.SetActive(true); } else { - inSceneItem = GameObject.Instantiate(Avatar.gameObject); + inSceneItem = GameObject.Instantiate(avatar.gameObject); } BasisAssetBundlePipeline.DestroyEditorOnlyInAvatar(inSceneItem); From 7498361b1d574058095fbc31f38b308f63e9456d Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Thu, 16 Apr 2026 01:00:06 -0500 Subject: [PATCH 2/2] Add runtimeFieldsObjects to prevent cilbox for consuming all field objects in cilbox proxy. * Need to check with cnlohr about improvement or disagreement. --- .../Packages/com.cnlohr.cilbox/CilboxProxy.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Basis/Packages/com.cnlohr.cilbox/CilboxProxy.cs b/Basis/Packages/com.cnlohr.cilbox/CilboxProxy.cs index 69e4540120..9ca1a88060 100644 --- a/Basis/Packages/com.cnlohr.cilbox/CilboxProxy.cs +++ b/Basis/Packages/com.cnlohr.cilbox/CilboxProxy.cs @@ -16,6 +16,7 @@ public class CilboxProxy : MonoBehaviour { public StackElement [] fields; public List< UnityEngine.Object > fieldsObjects; // This is generally only held during saving and loading, not in use. + [NonSerialized] private List< UnityEngine.Object > runtimeFieldsObjects; public CilboxClass cls; public Cilbox box; @@ -219,10 +220,12 @@ public void RuntimeProxyLoad() cls = box.GetClass( className ); + runtimeFieldsObjects = fieldsObjects != null ? new List(fieldsObjects) : new List(); + // First thing: Go through any references that are prohibited. - for( int i = 0; i < fieldsObjects.Count; i++ ) + for( int i = 0; i < runtimeFieldsObjects.Count; i++ ) { - UnityEngine.Object o = fieldsObjects[i]; + UnityEngine.Object o = runtimeFieldsObjects[i]; if (o == null) { // If it's null, there's nothing to safety-check. @@ -232,7 +235,7 @@ public void RuntimeProxyLoad() if(box.GetComponentTypeOverride( t.FullName, out Type overrideType )) { Debug.Log( $"RuntimeProxyLoad: Override {t.FullName} with {overrideType.FullName}" ); t = overrideType; - if(typeof(CilboxShim).IsAssignableFrom(t) && fieldsObjects[i] is Component gameObjectComponent) + if(typeof(CilboxShim).IsAssignableFrom(t) && runtimeFieldsObjects[i] is Component gameObjectComponent) { GameObject gameObject = gameObjectComponent.gameObject; Component component; @@ -242,7 +245,7 @@ public void RuntimeProxyLoad() { component = gameObject.AddComponent(t); } - fieldsObjects[i] = component; + runtimeFieldsObjects[i] = component; } } if( t == typeof( CilboxProxy ) ) @@ -252,7 +255,7 @@ public void RuntimeProxyLoad() else if( !box.CheckTypeAllowed( t.FullName ) ) { Debug.LogWarning( $"Contraband found in script {className} field ID {i}: {o.GetType()}" ); - fieldsObjects[i] = null; + runtimeFieldsObjects[i] = null; } } @@ -341,6 +344,7 @@ public void RuntimeProxyLoad() proxyWasSetup = true; + runtimeFieldsObjects = null; if (verboseLogging) Debug.Log( $"RuntimeProxyLoad complete for class {className}" ); } @@ -349,6 +353,7 @@ public void RuntimeProxyLoad() // Returns: true if is object, otherwise is primitive. private bool LoadObjectFromSerializee( Serializee s, out object oOut, String rootFieldName, Type inType, bool root ) { + List objectSlots = runtimeFieldsObjects ?? fieldsObjects; Dictionary< String, Serializee > dict = s.AsMap(); Serializee setype; @@ -361,7 +366,8 @@ private bool LoadObjectFromSerializee( Serializee s, out object oOut, String roo int iFO; if( dict.TryGetValue( "fo", out seFO ) && Int32.TryParse( seFO.AsString(), out iFO ) && - iFO < fieldsObjects.Count ) + objectSlots != null && + iFO < objectSlots.Count ) { if (dict.TryGetValue("or", out var seOr)) { @@ -373,7 +379,7 @@ private bool LoadObjectFromSerializee( Serializee s, out object oOut, String roo } } - UnityEngine.Object o = fieldsObjects[iFO]; + UnityEngine.Object o = objectSlots[iFO]; //Debug.Log( $"LOADING FIELD: {i} with {o}" ); if( o ) @@ -384,7 +390,7 @@ private bool LoadObjectFromSerializee( Serializee s, out object oOut, String roo oOut = o; // Remove reference out of the fieldsObjects array. - fieldsObjects[iFO] = null; + objectSlots[iFO] = null; return true; } @@ -392,7 +398,8 @@ private bool LoadObjectFromSerializee( Serializee s, out object oOut, String roo } else { - Debug.LogWarning( $"Failure to load object in field id:{rootFieldName} of {className} (slot parse failed or out of range, fieldsObjects count={fieldsObjects.Count})"); + int objectSlotCount = objectSlots != null ? objectSlots.Count : 0; + Debug.LogWarning( $"Failure to load object in field id:{rootFieldName} of {className} (slot parse failed or out of range, fieldsObjects count={objectSlotCount})"); } } else if( sT[0] == 'a' )