Skip to content

Commit e35d8a0

Browse files
authored
Fix the Test Avatar Hook. (#747)
* Add session key, to survive Domain Reloads for testing avatar. * Add runtimeFieldsObjects to prevent cilbox for consuming all field objects in cilbox proxy. * Need to check with cnlohr about improvement or disagreement.
1 parent 53f5f22 commit e35d8a0

2 files changed

Lines changed: 115 additions & 23 deletions

File tree

Basis/Packages/com.basis.sdk/Scripts/Editor/SDKInspector/BasisAvatarSDKInspector.cs

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
[CustomEditor(typeof(BasisAvatar))]
1616
public partial class BasisAvatarSDKInspector : Editor
1717
{
18+
private const string PendingTestInEditorAvatarIdSessionKey = "BasisAvatarSDKInspector.PendingTestInEditorAvatarId";
19+
1820
public delegate void BeforeTestInEditorHandler(GameObject clone);
1921
public static BeforeTestInEditorHandler OnBeforeTestInEditor;
22+
private static BasisAvatar ScheduledTestInEditorAvatar;
2023

2124
public static event Action<BasisAvatarSDKInspector> InspectorGuiCreated;
2225
public static event Action ButtonClicked;
@@ -32,6 +35,71 @@ public partial class BasisAvatarSDKInspector : Editor
3235
private Label resultLabel; // Store the result label for later clearing
3336
public string Error;
3437
public BasisAvatarValidator BasisAvatarValidator;
38+
39+
[InitializeOnLoadMethod]
40+
private static void InitializeTestInEditorHooks()
41+
{
42+
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
43+
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
44+
}
45+
46+
private static void OnPlayModeStateChanged(PlayModeStateChange state)
47+
{
48+
if (state != PlayModeStateChange.EnteredPlayMode || !HasPendingTestInEditorAvatarId())
49+
{
50+
return;
51+
}
52+
53+
EditorApplication.delayCall -= TryExecutePendingTestInEditor;
54+
EditorApplication.delayCall += TryExecutePendingTestInEditor;
55+
}
56+
57+
private static void TryExecutePendingTestInEditor()
58+
{
59+
string pendingAvatarId = GetPendingTestInEditorAvatarId();
60+
if (string.IsNullOrEmpty(pendingAvatarId))
61+
{
62+
return;
63+
}
64+
65+
if (!GlobalObjectId.TryParse(pendingAvatarId, out GlobalObjectId avatarId))
66+
{
67+
ClearPendingTestInEditorAvatarId();
68+
return;
69+
}
70+
71+
BasisAvatar avatar = GlobalObjectId.GlobalObjectIdentifierToObjectSlow(avatarId) as BasisAvatar;
72+
ClearPendingTestInEditorAvatarId();
73+
if (avatar == null)
74+
{
75+
BasisDebug.LogError("Unable to resolve the pending avatar for Test In Editor.", BasisDebug.LogTag.Editor);
76+
return;
77+
}
78+
79+
RequestAvatarLoad(avatar);
80+
}
81+
82+
private static bool HasPendingTestInEditorAvatarId()
83+
{
84+
return SessionState.GetBool(PendingTestInEditorAvatarIdSessionKey + ".Exists", false);
85+
}
86+
87+
private static string GetPendingTestInEditorAvatarId()
88+
{
89+
return SessionState.GetString(PendingTestInEditorAvatarIdSessionKey, string.Empty);
90+
}
91+
92+
private static void SetPendingTestInEditorAvatarId(string avatarId)
93+
{
94+
SessionState.SetString(PendingTestInEditorAvatarIdSessionKey, avatarId ?? string.Empty);
95+
SessionState.SetBool(PendingTestInEditorAvatarIdSessionKey + ".Exists", !string.IsNullOrEmpty(avatarId));
96+
}
97+
98+
private static void ClearPendingTestInEditorAvatarId()
99+
{
100+
SessionState.EraseString(PendingTestInEditorAvatarIdSessionKey);
101+
SessionState.SetBool(PendingTestInEditorAvatarIdSessionKey + ".Exists", false);
102+
}
35103
private void OnEnable()
36104
{
37105
visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(BasisSDKConstants.AvataruxmlPath);
@@ -513,9 +581,10 @@ public void AvatarTestInEditorClickFunction()
513581
{
514582
if (!Application.isPlaying)
515583
{
516-
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");
584+
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");
517585
if (result)
518586
{
587+
SetPendingTestInEditorAvatarId(GlobalObjectId.GetGlobalObjectIdSlow(Avatar).ToString());
519588
EditorApplication.EnterPlaymode();
520589
}
521590
}
@@ -525,34 +594,50 @@ public void AvatarTestInEditorClickFunction()
525594
}
526595
}
527596
public void RequestAvatarLoad()
597+
{
598+
RequestAvatarLoad(Avatar);
599+
}
600+
601+
private static void RequestAvatarLoad(BasisAvatar avatar)
528602
{
529603
#if BASIS_FRAMEWORK_EXISTS
530604
if (BasisLocalPlayer.PlayerReady)
531605
{
532606
BasisDebug.Log("Player Ready Loading", BasisDebug.LogTag.Editor);
533-
LoadAvatar();
607+
LoadAvatar(avatar);
534608
}
535609
else
536610
{
537-
ScheduleCallback = true;
611+
ScheduledTestInEditorAvatar = avatar;
538612
BasisDebug.Log("Scheduling Load Avatar", BasisDebug.LogTag.Editor);
539-
BasisLocalPlayer.OnLocalPlayerInitalized += LoadAvatar;
613+
BasisLocalPlayer.OnLocalPlayerInitalized -= LoadScheduledAvatar;
614+
BasisLocalPlayer.OnLocalPlayerInitalized += LoadScheduledAvatar;
540615
}
541616
#endif
542617
}
543-
public bool ScheduleCallback = false;
544-
public async void LoadAvatar()
618+
619+
private static void LoadScheduledAvatar()
545620
{
546621
#if BASIS_FRAMEWORK_EXISTS
547-
if (ScheduleCallback)
622+
BasisLocalPlayer.OnLocalPlayerInitalized -= LoadScheduledAvatar;
623+
if (ScheduledTestInEditorAvatar == null)
548624
{
549-
BasisLocalPlayer.OnLocalPlayerInitalized -= LoadAvatar;
550-
ScheduleCallback = false;
625+
return;
551626
}
627+
628+
BasisAvatar avatar = ScheduledTestInEditorAvatar;
629+
ScheduledTestInEditorAvatar = null;
630+
LoadAvatar(avatar);
631+
#endif
632+
}
633+
634+
private static async void LoadAvatar(BasisAvatar avatar)
635+
{
636+
#if BASIS_FRAMEWORK_EXISTS
552637
BasisDebug.Log("LoadAvatar Called", BasisDebug.LogTag.Editor);
553638

554639
var jigglesToReset = new List<MonoBehaviour>();
555-
foreach (MonoBehaviour jiggle in Avatar.gameObject.GetComponentsInChildren<MonoBehaviour>(false))
640+
foreach (MonoBehaviour jiggle in avatar.gameObject.GetComponentsInChildren<MonoBehaviour>(false))
556641
{
557642
if (jiggle != null
558643
&& jiggle.GetType().FullName == "GatorDragonGames.JigglePhysics.JiggleRig"
@@ -565,18 +650,18 @@ public async void LoadAvatar()
565650
if (jigglesToReset.Count > 0)
566651
{
567652
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);
568-
Avatar.gameObject.SetActive(false);
653+
avatar.gameObject.SetActive(false);
569654
// It's a bit of a hack, but waiting three frames works.
570655
await Awaitable.NextFrameAsync();
571656
await Awaitable.NextFrameAsync();
572657
await Awaitable.NextFrameAsync();
573-
inSceneItem = GameObject.Instantiate(Avatar.gameObject);
574-
Avatar.gameObject.SetActive(true);
658+
inSceneItem = GameObject.Instantiate(avatar.gameObject);
659+
avatar.gameObject.SetActive(true);
575660
inSceneItem.SetActive(true);
576661
}
577662
else
578663
{
579-
inSceneItem = GameObject.Instantiate(Avatar.gameObject);
664+
inSceneItem = GameObject.Instantiate(avatar.gameObject);
580665
}
581666

582667
BasisAssetBundlePipeline.DestroyEditorOnlyInAvatar(inSceneItem);

Basis/Packages/com.cnlohr.cilbox/CilboxProxy.cs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class CilboxProxy : MonoBehaviour
1616
{
1717
public StackElement [] fields;
1818
public List< UnityEngine.Object > fieldsObjects; // This is generally only held during saving and loading, not in use.
19+
[NonSerialized] private List< UnityEngine.Object > runtimeFieldsObjects;
1920

2021
public CilboxClass cls;
2122
public Cilbox box;
@@ -219,10 +220,12 @@ public void RuntimeProxyLoad()
219220

220221
cls = box.GetClass( className );
221222

223+
runtimeFieldsObjects = fieldsObjects != null ? new List<UnityEngine.Object>(fieldsObjects) : new List<UnityEngine.Object>();
224+
222225
// First thing: Go through any references that are prohibited.
223-
for( int i = 0; i < fieldsObjects.Count; i++ )
226+
for( int i = 0; i < runtimeFieldsObjects.Count; i++ )
224227
{
225-
UnityEngine.Object o = fieldsObjects[i];
228+
UnityEngine.Object o = runtimeFieldsObjects[i];
226229
if (o == null)
227230
{
228231
// If it's null, there's nothing to safety-check.
@@ -232,7 +235,7 @@ public void RuntimeProxyLoad()
232235
if(box.GetTypeOverride( t.FullName, out Type overrideType )) {
233236
Debug.Log( $"RuntimeProxyLoad: Override {t.FullName} with {overrideType.FullName}" );
234237
t = overrideType;
235-
if(typeof(CilboxShim).IsAssignableFrom(t) && fieldsObjects[i] is Component gameObjectComponent)
238+
if(typeof(CilboxShim).IsAssignableFrom(t) && runtimeFieldsObjects[i] is Component gameObjectComponent)
236239
{
237240
GameObject gameObject = gameObjectComponent.gameObject;
238241
Component component;
@@ -242,7 +245,7 @@ public void RuntimeProxyLoad()
242245
{
243246
component = gameObject.AddComponent(t);
244247
}
245-
fieldsObjects[i] = component;
248+
runtimeFieldsObjects[i] = component;
246249
}
247250
}
248251
if( t == typeof( CilboxProxy ) )
@@ -252,7 +255,7 @@ public void RuntimeProxyLoad()
252255
else if( !box.CheckTypeAllowed( t.FullName ) )
253256
{
254257
Debug.LogWarning( $"Contraband found in script {className} field ID {i}: {o.GetType()}" );
255-
fieldsObjects[i] = null;
258+
runtimeFieldsObjects[i] = null;
256259
}
257260
}
258261

@@ -341,6 +344,7 @@ public void RuntimeProxyLoad()
341344

342345

343346
proxyWasSetup = true;
347+
runtimeFieldsObjects = null;
344348
if (verboseLogging)
345349
Debug.Log( $"RuntimeProxyLoad complete for class {className}" );
346350
}
@@ -349,6 +353,7 @@ public void RuntimeProxyLoad()
349353
// Returns: true if is object, otherwise is primitive.
350354
private bool LoadObjectFromSerializee( Serializee s, out object oOut, String rootFieldName, Type inType, bool root )
351355
{
356+
List<UnityEngine.Object> objectSlots = runtimeFieldsObjects ?? fieldsObjects;
352357
Dictionary< String, Serializee > dict = s.AsMap();
353358

354359
Serializee setype;
@@ -361,7 +366,8 @@ private bool LoadObjectFromSerializee( Serializee s, out object oOut, String roo
361366
int iFO;
362367
if( dict.TryGetValue( "fo", out seFO ) &&
363368
Int32.TryParse( seFO.AsString(), out iFO ) &&
364-
iFO < fieldsObjects.Count )
369+
objectSlots != null &&
370+
iFO < objectSlots.Count )
365371
{
366372
if (dict.TryGetValue("or", out var seOr))
367373
{
@@ -373,7 +379,7 @@ private bool LoadObjectFromSerializee( Serializee s, out object oOut, String roo
373379
}
374380
}
375381

376-
UnityEngine.Object o = fieldsObjects[iFO];
382+
UnityEngine.Object o = objectSlots[iFO];
377383

378384
//Debug.Log( $"LOADING FIELD: {i} with {o}" );
379385
if( o )
@@ -384,15 +390,16 @@ private bool LoadObjectFromSerializee( Serializee s, out object oOut, String roo
384390
oOut = o;
385391

386392
// Remove reference out of the fieldsObjects array.
387-
fieldsObjects[iFO] = null;
393+
objectSlots[iFO] = null;
388394

389395
return true;
390396
}
391397
Debug.LogWarning( $"[CilboxProxy:{gameObject.name}] Object reference slot {iFO} for field {rootFieldName} is null/missing at load time." );
392398
}
393399
else
394400
{
395-
Debug.LogWarning( $"Failure to load object in field id:{rootFieldName} of {className} (slot parse failed or out of range, fieldsObjects count={fieldsObjects.Count})");
401+
int objectSlotCount = objectSlots != null ? objectSlots.Count : 0;
402+
Debug.LogWarning( $"Failure to load object in field id:{rootFieldName} of {className} (slot parse failed or out of range, fieldsObjects count={objectSlotCount})");
396403
}
397404
}
398405
else if( sT[0] == 'a' )

0 commit comments

Comments
 (0)