#Preamble
Preamble
#My problem is as follows:
My problem is as follows:
#Code below.
Stack Exchange network consists of 183 Q&A communities including Stack Overflow, the largest, most trusted online community for developers to learn, share their knowledge, and build their careers.
Visit Stack ExchangeStack Internal
Knowledge at work
Bring the best of human thought and AI automation together at your work.
Explore Stack Internal#Preamble
#My problem is as follows:
#Code below.
#Preamble
#My problem is as follows:
#Code below.
I solved this problem 4 years ago, so I know it can be done; but, alas, I do not have access to that code anymore. I'm trying to build a solution this time with ScriptableObject and SerilizableObject, and I have something that almost works.
A custom inspector utility class to edit it with - the bug is in here:
and sample custom inspector that uses that - the bug is in here:
I solved this problem 4 years ago, so I know it can be done; but, alas, I do not have access to that code anymore. I'm trying to build a solution this time with ScriptableObject and SerilizableObject, and I have something that almost works.
A custom inspector utility class to edit it with:
and sample custom inspector that uses that - the bug is in here:
I solved this problem 4 years ago, so I know it can be done; but, I do not have access to that code anymore. I'm trying to build a solution this time with ScriptableObject and SerilizableObject, and I have something that almost works.
A custom inspector utility class to edit it with - the bug is in here:
and sample custom inspector that uses that
[EDIT: based on some feedback, I pruned the example down to the base functionality of the bug we are trying to solve. The description below remains accurate, but the repro has been shortened to:
FINALLY: if anybody knows how to solve this problem without using scriptable or serializeable objects, that would be preferred. Thanks a million!]
I apply my FXTriggerTimer MonoBehavior (which inherits from FXTriggerBase) to a test object, and then hit the blue '+' button to add an AnimEvent and a SFXEvent to the trigger. I then save the scene, and reload the scene, and my test object has an array of two null elements that (obviously) don't draw in the inspector. BUT, if I edit a script, any script (e.g. hit space and save), the script refresh will magically repopulate with the correct two items. So I know that all of the pieces are there under the hood, they just need to be restored on scene load.
//Scripts/FXTriggerBase.cs
using UnityEngine;
using System.Collections;
public enum FXEventType {kNone, kAnim, kSFX,}
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class FXEvent: UnityEngine.ScriptableObject
{
[SerializeField]
public FXEventType type;
[SerializeField]
public GameObject go;
virtual public void Fire() { }
};
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class AnimEvent : FXEvent
{
[SerializeField]
public string animName = "";
public AnimEvent() {type = FXEventType.kAnim;}
public void Fire() {go.animation.Play(animName); }
}
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class SFXEvent: FXEvent
{
[SerializeField]
public AudioClip clip;
public SFXEvent() {type = FXEventType.kSFX;}
public void Fire() {go.audio.PlayOneShot(clip); }
}
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[AddComponentMenu("")] // prevents users from using this directly
public class FXTriggerBase : MonoBehaviour
{
[SerializeField]
public FXEvent[] events = new FXEvent[]{};
public void Fire()
{
for( int e = 0; e < events.Length; e++)
{
events[e].go = gameObject;
events[e].Fire();
}
}
}
//Editor/FXTriggerBaseUtil.cs
using UnityEngine;
using System.Collections;
using UnityEditor;
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
public class FXEditorUtil
{
SerializedObject[] serial;
public bool dirtied = false;
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public void OnSubGUI(this FXEvent fx)
{
//having trouble getting polymorphism to work right in the inspector,
// so this is what works.
if(fx.type == FXEventType.kAnim)
OnSubGUI((AnimEvent)fx);
else if(fx.type == FXEventType.kSFX)
OnSubGUI((SFXEvent)fx);
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public void OnSubGUI(this AnimEvent anim)
{
string newAnimName = GUILayout.TextField(anim.animName);
if(newAnimName != anim.animName)
{
AnimEvent anim = (AnimEvent)fx;
string newAnimName = GUILayout.TextField(anim.animName);
if(newAnimName != anim.animName)
{
anim.animName = newAnimName;
dirtied = true;
}
}
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public void OnSubGUI(this SFXEvent sfx)
{
AudioClip newSFX = (AudioClip)EditorGUILayout.ObjectField(sfx.clip, typeof(AudioClip));
else if(newSFXfx.type !=== sfxFXEventType.clipkSFX)
{
SFXEvent sfx = (SFXEvent)fx;
AudioClip newSFX = (AudioClip)EditorGUILayout.ObjectField(sfx.clip, typeof(AudioClip));
if(newSFX != sfx.clip)
{
sfx.clip =newSFX;
dirtied = true;
}
}
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public FXEvent OnGUI(FXEvent fx)
{
if(fx == null)
return fx;
GUILayout.BeginHorizontal();
FXEvent ret = null;
//ifdisplay the enum changes, create new FXEvent sub type corresponding to the
so //we newcan enum,see andwhat returnwe're itediting
FXEventType newType = (FXEventType) EditorGUILayout.EnumPopup(fx.type, GUILayout.Width(60));
ifLabel(newType != fx.type)
{
switch(newType)
{
case FXEventType.kNone:
ret = ScriptableObject.CreateInstance<FXEvent>();
break;
case FXEventType.kSFX:
ret = ScriptableObject.CreateInstance<SFXEvent>();
break;
case FXEventType.kAnim:
ret = ScriptableObject.CreateInstance<AnimEvent>ToString();
break;
default:
, Debug.LogError("FXEventGUILayout.OnGUIWidth(60): Magic Error");
break;
}
ret.type = newType;
dirtied = true;
}
//handle element GUI, if that applies
if(fx.type != FXEventType.kNone)
{
OnSubGUI(fx);
}
else
GUILayout.Label("");
GUILayout.EndHorizontal();
return ret;
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public void OnGUI(FXTriggerBase trigger)
{
FXTriggerBase triggerBase = trigger as FXTriggerBase;
if(triggerBase == null)
{
Debug.LogError("FXTriggerBaseInspector.OnGUI() : null item to inspect?");
return;
}
//build an array of serialized versions of our array elements, so that they
// save out properly
int numEvents = triggerBase.events.Length;
serial = new SerializedObject[numEvents];
for(int e = 0; e < numEvents; e++)
{
serial[e] = new SerializedObject(triggerBase.events[e]);
}
dirtied = false;
//Do inspector GUI of our array elements
int ret = ArrayGUI("events", trigger.events, trigger.gameObject);
//handle deletion or addition of elements
if (ret >= 0 && ret < trigger.events.Length)
ArrayHandleDelete(ref trigger.events, ret);
else if (ret == trigger.events.Length)
ArrayHandleAdd(ref trigger.events);
//if any changes have been registered, save our serialized versions out
if(dirtied)
{
numEvents = serial.Length;
for(int e = 0; e < numEvents; e++)
{
serial[e].ApplyModifiedProperties();
}
EditorUtility.SetDirty(trigger);
}
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
int ArrayGUI(string label, FXEvent[] arr, GameObject go)
{
int ret = -1;
Color defColor = GUI.color;
GUILayout.BeginVertical(GUILayout.Width(250));
{
GUILayout.Label(label);
//blue plus button that populates intsome numObjectsevents for us
GUILayout.Label("");
GUI.color = Color.cyan;
bool add = GUILayout.Button("+", GUILayout.Width(20));
if (add)
ret = arr.Length;
GUI.color = defColor;
GUILayout.Label(label);
int numObjects = arr.Length;
for (int o = 0; o < numObjects; o++)
{
GUILayout.BeginHorizontal();
{
GUILayout.BeginHorizontal//draw custom GUI for each of the polymorphic fx event types
FXEvent fx = arr[o];
FXEvent newFX = OnGUI(fx);
if(newFX != null)
{
//Draw a red '-' button that will remove this element
GUI.color = Color.red;
bool hit = GUILayout.Button("-", GUILayout.Width(20));
if (hit)
ret = o;
GUI.color = defColor;
//spacer
GUILayout.Label("", GUILayout.Width(10));
//draw custom GUI for each of the polymorphic fx event types
FXEvent fx = arr[o];
FXEvent newFX = OnGUI(fx);
if(newFX != null)
{
arr[o] = newFX;
dirtied = true;
serial[o] = new SerializedObject(arr[o]);
ScriptableObject.DestroyImmediate(fx);
}
}
GUILayout.EndHorizontal();
}
//finally, at the bottom draw a blue plus button that will
GUI.color = Color.cyan;
bool add = GUILayout.Button("+", GUILayout.WidthEndHorizontal(20));
if (add)
ret = arr.Length;
} GUI.color = defColor;
}
GUILayout.EndVertical();
return ret;
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
void ArrayHandleDelete(ref FXEvent[] arr, int index)
{
FXEvent fx = arr[index];
ScriptableObject.DestroyImmediate(fx);
//copy over the element we are deleting, and continue compacting the array
for (int i = index; i < arr.Length - 1; i++)
{
arr[i] = arr[i + 1];
serial[i] = serial[i+1];
}
//remove last element to finalize deletion
System.Array.Resize(ref arr, arr.Length - 1);
System.Array.Resize(ref serial, serial.Length - 1);
dirtied = true;
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
void ArrayHandleAdd(ref FXEvent[] arr)
{
int size = arr.Length;
//add new element to the end
System.Array.Resize(ref arr, size + 12);
System.Array.Resize(ref serial, size + 12);
arr[size] = ScriptableObject.CreateInstance<FXEvent>CreateInstance<AnimEvent>();
serial[size] = new SerializedObject(arr[size]);
arr[size+1] = ScriptableObject.CreateInstance<SFXEvent>();
serial[size+1] = new SerializedObject(arr[size+1]);
dirtied = true;
}
}
and sample custom inspector that uses that - the bug is in here:
I apply my FXTriggerTimer MonoBehavior (which inherits from FXTriggerBase) to a test object, and then add an AnimEvent and a SFXEvent to the trigger. I then save the scene, and reload the scene, and my test object has an array of two null elements that (obviously) don't draw in the inspector. BUT, if I edit a script (e.g. hit space and save), the script refresh will magically repopulate with the correct two items. So I know that all of the pieces are there under the hood, they just need to be restored on scene load.
//Scripts/FXTriggerBase.cs
using UnityEngine;
using System.Collections;
public enum FXEventType {kNone, kAnim, kSFX,}
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class FXEvent: UnityEngine.ScriptableObject
{
[SerializeField]
public FXEventType type;
[SerializeField]
public GameObject go;
virtual public void Fire() { }
};
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class AnimEvent : FXEvent
{
[SerializeField]
public string animName = "";
public void Fire() {go.animation.Play(animName); }
}
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class SFXEvent: FXEvent
{
[SerializeField]
public AudioClip clip;
public void Fire() {go.audio.PlayOneShot(clip); }
}
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[AddComponentMenu("")] // prevents users from using this directly
public class FXTriggerBase : MonoBehaviour
{
[SerializeField]
public FXEvent[] events = new FXEvent[]{};
public void Fire()
{
for( int e = 0; e < events.Length; e++)
{
events[e].go = gameObject;
events[e].Fire();
}
}
}
//Editor/FXTriggerBaseUtil.cs
using UnityEngine;
using System.Collections;
using UnityEditor;
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
public class FXEditorUtil
{
SerializedObject[] serial;
public bool dirtied = false;
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public void OnSubGUI(this FXEvent fx)
{
//having trouble getting polymorphism to work right in the inspector,
// so this is what works.
if(fx.type == FXEventType.kAnim)
OnSubGUI((AnimEvent)fx);
else if(fx.type == FXEventType.kSFX)
OnSubGUI((SFXEvent)fx);
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public void OnSubGUI(this AnimEvent anim)
{
string newAnimName = GUILayout.TextField(anim.animName);
if(newAnimName != anim.animName)
{
anim.animName = newAnimName;
dirtied = true;
}
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public void OnSubGUI(this SFXEvent sfx)
{
AudioClip newSFX = (AudioClip)EditorGUILayout.ObjectField(sfx.clip, typeof(AudioClip));
if(newSFX != sfx.clip)
{
sfx.clip =newSFX;
dirtied = true;
}
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public FXEvent OnGUI(FXEvent fx)
{
if(fx == null)
return fx;
GUILayout.BeginHorizontal();
FXEvent ret = null;
//if the enum changes, create new FXEvent sub type corresponding to the
// new enum, and return it
FXEventType newType = (FXEventType) EditorGUILayout.EnumPopup(fx.type, GUILayout.Width(60));
if(newType != fx.type)
{
switch(newType)
{
case FXEventType.kNone:
ret = ScriptableObject.CreateInstance<FXEvent>();
break;
case FXEventType.kSFX:
ret = ScriptableObject.CreateInstance<SFXEvent>();
break;
case FXEventType.kAnim:
ret = ScriptableObject.CreateInstance<AnimEvent>();
break;
default:
Debug.LogError("FXEvent.OnGUI(): Magic Error");
break;
}
ret.type = newType;
dirtied = true;
}
//handle element GUI, if that applies
if(fx.type != FXEventType.kNone)
{
OnSubGUI(fx);
}
else
GUILayout.Label("");
GUILayout.EndHorizontal();
return ret;
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public void OnGUI(FXTriggerBase trigger)
{
FXTriggerBase triggerBase = trigger as FXTriggerBase;
if(triggerBase == null)
{
Debug.LogError("FXTriggerBaseInspector.OnGUI() : null item to inspect?");
return;
}
//build an array of serialized versions of our array elements, so that they
// save out properly
int numEvents = triggerBase.events.Length;
serial = new SerializedObject[numEvents];
for(int e = 0; e < numEvents; e++)
{
serial[e] = new SerializedObject(triggerBase.events[e]);
}
dirtied = false;
//Do inspector GUI of our array elements
int ret = ArrayGUI("events", trigger.events, trigger.gameObject);
//handle deletion or addition of elements
if(ret >= 0 && ret < trigger.events.Length)
ArrayHandleDelete(ref trigger.events, ret);
else if (ret == trigger.events.Length)
ArrayHandleAdd(ref trigger.events);
//if any changes have been registered, save our serialized versions out
if(dirtied)
{
numEvents = serial.Length;
for(int e = 0; e < numEvents; e++)
{
serial[e].ApplyModifiedProperties();
}
EditorUtility.SetDirty(trigger);
}
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
int ArrayGUI(string label, FXEvent[] arr, GameObject go)
{
int ret = -1;
Color defColor = GUI.color;
GUILayout.BeginVertical(GUILayout.Width(250));
{
GUILayout.Label(label);
int numObjects = arr.Length;
for (int o = 0; o < numObjects; o++)
{
GUILayout.BeginHorizontal();
{
//Draw a red '-' button that will remove this element
GUI.color = Color.red;
bool hit = GUILayout.Button("-", GUILayout.Width(20));
if (hit)
ret = o;
GUI.color = defColor;
//spacer
GUILayout.Label("", GUILayout.Width(10));
//draw custom GUI for each of the polymorphic fx event types
FXEvent fx = arr[o];
FXEvent newFX = OnGUI(fx);
if(newFX != null)
{
arr[o] = newFX;
dirtied = true;
serial[o] = new SerializedObject(arr[o]);
ScriptableObject.DestroyImmediate(fx);
}
}
GUILayout.EndHorizontal();
}
//finally, at the bottom draw a blue plus button that will
GUI.color = Color.cyan;
bool add = GUILayout.Button("+", GUILayout.Width(20));
if (add)
ret = arr.Length;
GUI.color = defColor;
}
GUILayout.EndVertical();
return ret;
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
void ArrayHandleDelete(ref FXEvent[] arr, int index)
{
FXEvent fx = arr[index];
ScriptableObject.DestroyImmediate(fx);
//copy over the element we are deleting, and continue compacting the array
for (int i = index; i < arr.Length - 1; i++)
{
arr[i] = arr[i + 1];
serial[i] = serial[i+1];
}
//remove last element to finalize deletion
System.Array.Resize(ref arr, arr.Length - 1);
System.Array.Resize(ref serial, serial.Length - 1);
dirtied = true;
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
void ArrayHandleAdd(ref FXEvent[] arr)
{
int size = arr.Length;
//add new element to the end
System.Array.Resize(ref arr, size + 1);
System.Array.Resize(ref serial, size + 1);
arr[size] = ScriptableObject.CreateInstance<FXEvent>();
serial[size] = new SerializedObject(arr[size]);
dirtied = true;
}
}
and sample custom inspector that uses that:
[EDIT: based on some feedback, I pruned the example down to the base functionality of the bug we are trying to solve. The description below remains accurate, but the repro has been shortened to:
FINALLY: if anybody knows how to solve this problem without using scriptable or serializeable objects, that would be preferred. Thanks a million!]
I apply my FXTriggerTimer MonoBehavior (which inherits from FXTriggerBase) to a test object, and then hit the blue '+' button to add an AnimEvent and a SFXEvent to the trigger. I then save the scene, and reload the scene, and my test object has an array of two null elements that (obviously) don't draw in the inspector. BUT, if I edit a script, any script (e.g. hit space and save), the script refresh will magically repopulate with the correct items. So I know that all of the pieces are there under the hood, they just need to be restored on scene load.
//Scripts/FXTriggerBase.cs
using UnityEngine;
using System.Collections;
public enum FXEventType {kNone, kAnim, kSFX,}
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class FXEvent: UnityEngine.ScriptableObject
{
[SerializeField] public FXEventType type;
[SerializeField] public GameObject go;
virtual public void Fire() { }
};
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class AnimEvent : FXEvent
{
[SerializeField] public string animName = "";
public AnimEvent() {type = FXEventType.kAnim;}
public void Fire() {go.animation.Play(animName); }
}
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class SFXEvent: FXEvent
{
[SerializeField] public AudioClip clip;
public SFXEvent() {type = FXEventType.kSFX;}
public void Fire() {go.audio.PlayOneShot(clip); }
}
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[AddComponentMenu("")] // prevents users from using this directly
public class FXTriggerBase : MonoBehaviour
{
[SerializeField] public FXEvent[] events = new FXEvent[]{};
public void Fire()
{
for( int e = 0; e < events.Length; e++)
{
events[e].go = gameObject;
events[e].Fire();
}
}
}
//Editor/FXTriggerBaseUtil.cs
using UnityEngine;
using System.Collections;
using UnityEditor;
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
public class FXEditorUtil
{
SerializedObject[] serial;
public bool dirtied = false;
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public void OnSubGUI(this FXEvent fx)
{
//having trouble getting polymorphism to work right in the inspector,
// so this is what works.
if(fx.type == FXEventType.kAnim)
{
AnimEvent anim = (AnimEvent)fx;
string newAnimName = GUILayout.TextField(anim.animName);
if(newAnimName != anim.animName)
{
anim.animName = newAnimName;
dirtied = true;
}
}
else if(fx.type == FXEventType.kSFX)
{
SFXEvent sfx = (SFXEvent)fx;
AudioClip newSFX = (AudioClip)EditorGUILayout.ObjectField(sfx.clip, typeof(AudioClip));
if(newSFX != sfx.clip)
{
sfx.clip =newSFX;
dirtied = true;
}
}
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public FXEvent OnGUI(FXEvent fx)
{
if(fx == null)
return fx;
GUILayout.BeginHorizontal();
FXEvent ret = null;
//display the enum type so we can see what we're editing
GUILayout.Label(fx.type.ToString(), GUILayout.Width(60));
//handle element GUI, if that applies
if(fx.type != FXEventType.kNone)
OnSubGUI(fx);
else
GUILayout.Label("");
GUILayout.EndHorizontal();
return ret;
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public void OnGUI(FXTriggerBase trigger)
{
FXTriggerBase triggerBase = trigger as FXTriggerBase;
if(triggerBase == null)
{
Debug.LogError("FXTriggerBaseInspector.OnGUI() : null item to inspect?");
return;
}
//build an array of serialized versions of our array elements, so that they
// save out properly
int numEvents = triggerBase.events.Length;
serial = new SerializedObject[numEvents];
for(int e = 0; e < numEvents; e++)
{
serial[e] = new SerializedObject(triggerBase.events[e]);
}
dirtied = false;
//Do inspector GUI of our array elements
int ret = ArrayGUI("events", trigger.events, trigger.gameObject);
//handle addition of elements
if (ret >= 0)
ArrayHandleAdd(ref trigger.events);
//if any changes have been registered, save our serialized versions out
if(dirtied)
{
numEvents = serial.Length;
for(int e = 0; e < numEvents; e++)
{
serial[e].ApplyModifiedProperties();
}
EditorUtility.SetDirty(trigger);
}
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
int ArrayGUI(string label, FXEvent[] arr, GameObject go)
{
int ret = -1;
Color defColor = GUI.color;
GUILayout.BeginVertical(GUILayout.Width(250));
//blue plus button that populates some events for us
GUILayout.Label("");
GUI.color = Color.cyan;
bool add = GUILayout.Button("+", GUILayout.Width(20));
if (add)
ret = arr.Length;
GUI.color = defColor;
GUILayout.Label(label);
int numObjects = arr.Length;
for (int o = 0; o < numObjects; o++)
{
GUILayout.BeginHorizontal();
{
//draw custom GUI for each of the polymorphic fx event types
FXEvent fx = arr[o];
FXEvent newFX = OnGUI(fx);
if(newFX != null)
{
arr[o] = newFX;
dirtied = true;
serial[o] = new SerializedObject(arr[o]);
ScriptableObject.DestroyImmediate(fx);
}
}
GUILayout.EndHorizontal();
}
GUILayout.EndVertical();
return ret;
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
void ArrayHandleAdd(ref FXEvent[] arr)
{
int size = arr.Length;
//add new element to the end
System.Array.Resize(ref arr, size + 2);
System.Array.Resize(ref serial, size + 2);
arr[size] = ScriptableObject.CreateInstance<AnimEvent>();
serial[size] = new SerializedObject(arr[size]);
arr[size+1] = ScriptableObject.CreateInstance<SFXEvent>();
serial[size+1] = new SerializedObject(arr[size+1]);
dirtied = true;
}
}
and sample custom inspector that uses that - the bug is in here: