#CustomEditor detect array change

1 messages · Page 1 of 1 (latest)

hallow skiff
#

None of the ways I tried for detecting editor modification detects an array changing when a new item is dragged in.
What I need is to execute code when the array gets modified, and know the original and new state of the array

Full context:
I have a double link situation, one scripts has an array referencing many other scripts. I want to reduce the amount of manual work so I'd like to make the array make the components reference itself automatically.
Which means:

  1. It needs to know that an element is removed from the array (Including when it gets replaced), to remove the reference
  2. It needs to know that an element is added to the array (Including when it replaces another), to create the reference

I can circumvent programmatic modifications quite easily, so that won't be the problem

azure cairn
#

One way is to have 2 copies of the array. The real one and one representing the last state of the array. Then on validate or something check if the arrays are different. If they are, then a change occurred. From that point you can execute whatever code you need.

hallow skiff
#

Does that mean there's no good way to do this?

azure cairn
hallow skiff
#

And is it possible to do it in a custom inspector? Because I would rather not include the cache in the script itself

azure cairn
#

It might be possible with a custom inspector, but it might also detect false-positives when the user messed with the property without actually changing it.

hallow skiff
#

GUI.changed, Begin/EndChangeCheck, DrawDefaultInspector and so on all failed to register me dragging a new element in, they do just fine for everything except from that

hallow skiff
azure cairn
#

Then perhaps just use OnValidate

hallow skiff
#

Yikes... Well, guess it's the only choice besides constantly making a copy of the array to poll for changes unless if someone else knows a working way... Well, either way, thanks for your help.

azure cairn
#

What so bad about having a copy though? It's mostly a memory cost(and the cost of comparing the arrays)

#

it's not like you need to allocate a new array every time.

#

Which it likely is if you're using unity 6+

hallow skiff
# azure cairn I guess, there's also this: <https://docs.unity3d.com/6000.1/Documentation/Scrip...

I've tried all options I could possibly think of (Although it may not mean much since my knowledge in editor ui is shallow), including that one... None safe for one worked- If you use UIToolkit, you can use BindingExtensions.TrackPropertyValue.
It works for all editor interactions, and additionally also will callback if I programmatically modified and applied changes to the property.
So ye, this is the answer to my questio, and anyone who comes looking for a way to solve this problem.

hallow skiff
#

... I don't know why, maybe I got lucky, or maybe the detection is ran on an async code and heavier functions made the detection less likely to catch me dragging something in, but either way, I found out that the callback doesn't always happen... And neither is reordering detected. 🤦‍♂️

azure cairn
#

Did RegisterValueChangeCallback not work btw?

azure cairn
#

That's weird. How are you testing? did you try logging or breakpoints?

hallow skiff
azure cairn
#

Maybe share your code of the custom inspector.

hallow skiff
#

I've tried plenty ways by now, but systematically dragging elements in proves unreliable, and I haven't tested before but I have a suspicion none of the previous solutions would've worked with reordering either

azure cairn
#

Recording should trigger serialization of the object. Or at least marking it as dirty. You should be able to detect that somewhere.

hallow skiff
#

And serializedObject.hasModifiedProperties, although that one for some reason never triggered at the time

#

Most parts have been deleted from this snippet, they don't matter and I can't send the whole thing anyway

[CustomEditor(typeof(SaveableObject))]
public class SaveableObjectDrawer : Editor
{
    public SerializedProperty registryNameProperty;
    public SerializedProperty componentsProperty;
    public SerializedProperty subpartsProperty;
    public SerializedProperty boundsProperty;

    void OnEnable()
    {
        registryNameProperty = serializedObject.FindProperty("<RegistryName>k__BackingField");
        componentsProperty = serializedObject.FindProperty("saveableBehaviours");
        subpartsProperty = serializedObject.FindProperty("subparts");
        boundsProperty = serializedObject.FindProperty("bounds");
    }

    public override VisualElement CreateInspectorGUI()
    {
        VisualElement container = new VisualElement();

        BindingExtensions.TrackPropertyValue(container, componentsProperty, (property) =>
        {
            Debug.Log("Boo");
        });

        container.Add(new PropertyField(registryNameProperty));
        container.Add(new PropertyField(componentsProperty));
        container.Add(new PropertyField(subpartsProperty));
        container.Add(new PropertyField(boundsProperty));

        return container;
    }
}
#

The element assigned is container instead of the componentsField, but that's okay, it only uses that element to watch for changes if a callback is defined, so it doesn't really matter which element I attach it to

azure cairn
#

You need to assign the new PropertyField's to a local variable and subscribe to their RegisterValueChangeCallback before adding them to the container.

hallow skiff
azure cairn
#

Well, I don't know how you tried that.

hallow skiff
#

Exactly the way you suggested

#

How else could I've called PropertyField.RegisterValueChangeCallback?

#

Except from on another element which obviously would've been the wrong way cus there's no indicator which property to watch

#

Okay well ig you might've assumed that I made a newbie mistake and thus why nothing worked... Can't blame ye, just... I really not a newbie anymore (Hopefully), and I really wouldn't have been asking for help if I'm not already out of ideas for where to search

azure cairn
#

Asked chat gpt to provide a working example for detecting reordering:

using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;

[CustomEditor(typeof(UiToolkitListChangeTest))]
public class UiToolkitListChangeTestEditor : Editor
{
    public override VisualElement CreateInspectorGUI()
    {
        var root = new VisualElement();

        var listProp = serializedObject.FindProperty("items");

        var listField = new PropertyField(listProp, "Items");
        root.Add(listField);

        root.RegisterCallback<SerializedPropertyChangeEvent>(evt =>
        {
            Debug.Log($"Property changed: {evt.changedProperty.propertyPath}");
        });

        listField.RegisterCallback<ChangeEvent<string>>(evt =>
        {
            Debug.Log($"List changed: {evt.newValue}");
        });

        return root;
    }
}
using System;
using System.Collections.Generic;
using UnityEngine;

public class UiToolkitListChangeTest : MonoBehaviour
{
    public List<string> items = new()
    {
        "A",
        "B",
        "C"
    };
}
#

It gets called more than once though

hallow skiff
#

Maybe even better than the minimum I required

azure cairn
#

Could probably defer your logic to the next frame so that you don't execute it many times.