Unity version: 2017.3.1f1
I'm trying to work with a HashSet
in the Unity inspector. Specifically, I need some MonoBehaviour
s to have HashSet
fields which can have their contents modified via the inspector.
To achieve this goal, I've created a concrete class that subclasses HashSet
, and uses a List
internally for (de)serialisation, in a very similar manner to the Dictionary in this guide:
However I'm encountering an issue where the list displays in the inspector, but I cannot set more than 1 value within it. If I set the size of the list to 2 or greater, it immediately is set back to 1.
In an attempt to debug the problem, I found that the OnBeforeSerialize
(and not OnAfterDeserialize
) was being executed every frame, continuously resetting the value. I'm not sure why it was setting it to 1 though.
Note that if I entered a string into the 1 available slot, it would not be reset. So this approach is currently "functional" for a HashSet
of 0 or 1 strings, but not more. Also, the outcome does not change if I use a HashSet
field instead of inheriting from it (like what was done in the above link).
Here is a minimal example:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TEST : MonoBehaviour
{
public StringHashSet test;
}
[System.Serializable]
public class SerializableHashSet<T> : HashSet<T>, ISerializationCallbackReceiver
{
public List<T> values = new List<T> ();
public void OnBeforeSerialize ()
{
values.Clear ();
foreach (T val in this) {
values.Add (val);
}
}
public void OnAfterDeserialize ()
{
this.Clear ();
foreach (T val in values) {
this.Add (val);
}
}
}
[System.Serializable]
public class StringHashSet : SerializableHashSet<string>
{
}
HashSet
)?OnBeforeSerialize
being executed every frame, even if no changes are being made in the inspector?More information
I've figured out it's because when the size of the list is changed, all the new elements in the list by default have the same value as the previous element, which will therefore all be squished to 1 value in the HashSet
.
Thus while question 2 remains from above, question 1 has evolved to ask for a workaround for this while maintaining the desired HashSet
functionality.
Here's how I've solved this problem considering the discoveries added to the More information section in the question:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class SerializableHashSet<T> : HashSet<T>, ISerializationCallbackReceiver
{
public List<T> values = new List<T> ();
public void OnBeforeSerialize ()
{
var cur = new HashSet<T> (values);
foreach (var val in this) {
if (!cur.Contains (val)) {
values.Add (val);
}
}
}
public void OnAfterDeserialize ()
{
this.Clear ();
foreach (var val in values) {
this.Add (val);
}
}
}
[System.Serializable]
public class StringHashSet : SerializableHashSet<string>
{
}
OnAfterDeserialize
is identical.
OnBeforeSerialize
no longer clears the values
list. Instead, it adds new values from the HashSet
to the List
-- specifically, values that exist in the HashSet
but not in the List
.
This allows functionality to continue when new elements are added to the list in the inspector: duplicate and blank entries will work fine, because the list won't be cleared at any point, only added to.
Regarding the second question, I found this information regarding why OnBeforeSerialize
was being executed so frequently: http://answers.unity.com/answers/796853/view.html
The relevant information:
Called every frame if the inspector for the object is open in the editor