Collection as PersistentVariables

Is there a pre-defined PersistentVariables variable for collection (list, array, anything serializable) of objects?

Specifically I need an array of LocalizedString passed to other LocalizedString with refresh event calling when any children LocalizedString triggers change for any reason.

No but you can create one. You need to implement [IVariable](http://implement Interface IVariable | Localization | 1.2.1) and IVariableGroup.

It took a little playing around but I managed to get something that works:
You can add this to a group or as a local variable.

[DisplayName("LocalizedString Array")]
public class LocalizedStringArray : IVariableGroup, IVariable
{
    public List<LocalizedString> localizedStrings = new List<LocalizedString>();

    public object GetSourceValue(ISelectorInfo selector) => this;

    public bool TryGetValue(string key, out IVariable value)
    {
        value = localizedStrings[int.Parse(key)];
        return true;
    }
}

Usage is like this
7976004--1023594--upload_2022-3-18_22-51-29.png

7976004--1023597--upload_2022-3-18_22-51-49.png

7976004--1023600--upload_2022-3-18_22-52-2.png

1 Like

This is nice! I will try it later on, but I may have a problem here - will the smart formatter recognize such structure as array (ie, can I use list formatter on such data?)

Ah no you won’t be able to use the list formatter.
You could return a list of strings in GetSourceValue. You would only need the IVariable Part then, no need for group. I dont think it would trigger the update if the child was updated though.
I’ll.try and take a closer look Monday to see how we could make it work with a list.

1 Like

Thank you, let us check this at Monday then :slight_smile:

Okay, I managed to workarounded this by facading list:

Here is entire component, to be put into ListVariable.cs . It is not meant as roboust solution, more like temporary workaround :slight_smile:

(License is either PublicDomain or DoWTFyou want if your country does not support PublicDomain)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.SmartFormat.Core.Extensions;
using UnityEngine.Localization.SmartFormat.PersistentVariables;

[Serializable]
public abstract class ListVariable<T> : IList<T>, IReadOnlyList<T>, IVariable, IVariableValueChanged where T : IVariableValueChanged, IVariable
{
    public event Action<IVariable> ValueChanged;

    [SerializeField]
    private List<T> _listField;

    private List<T> _list => _listField ?? (_listField = new List<T>());

    public object GetSourceValue(ISelectorInfo selector)
    {
        return _list.Select(x => x.GetSourceValue(selector));
    }

    public int Count => _list.Count;

    public bool IsReadOnly => false;

    public bool IsFixedSize => false;

    public bool IsSynchronized => false;

    public object SyncRoot => _list;

    public T this[int index]
    {
        get => _list[index];
        set
        {
            _list[index] = value;
            ValueChanged?.Invoke(this);
        }
    }
    private void Item_ValueChanged(IVariable obj)
    {
        ValueChanged?.Invoke(this);
    }


    public int IndexOf(T item)
    {
        return _list.IndexOf(item);
    }

    public void Insert(int index, T item)
    {
        item.ValueChanged += Item_ValueChanged;
        _list.Insert(index, item);
        ValueChanged?.Invoke(this);
    }

    public void RemoveAt(int index)
    {
        _list[index].ValueChanged -= Item_ValueChanged;
        _list.RemoveAt(index);
        ValueChanged?.Invoke(this);
    }

    public void Add(T item)
    {
        item.ValueChanged += Item_ValueChanged;
        _list.Add(item);
        ValueChanged?.Invoke(this);
    }

    public void Clear()
    {
        foreach (var item in _list)
        {
            item.ValueChanged -= Item_ValueChanged;
        }
        _list.Clear();
        ValueChanged?.Invoke(this);
    }

    public bool Contains(T item)
    {
        return _list.Contains(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        _list.CopyTo(array, arrayIndex);
    }

    public bool Remove(T item)
    {
        item.ValueChanged -= Item_ValueChanged;
        var ret = _list.Remove(item);
        if (ret)
            ValueChanged?.Invoke(this);
        return ret;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _list.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _list.GetEnumerator();
    }
}

[DisplayName("Localized List")]
[Serializable]
public sealed class LocalizedListString : ListVariable<LocalizedString>
{

}

And here is a dirty test:

public class Example : MonoBehaviour
{
    public LocalizedString _entry;

    private LocalizedListString _list;
    public LocalizedString _item;

    private IntVariable[] _variable;

    public TMPro.TextMeshProUGUI _target;

    // Start is called before the first frame update
    void Start()
    {
        _variable = new IntVariable[3];
        _list = new LocalizedListString();

        for (int i = 0; i < 3; i++)
        {
            var item = new LocalizedString(_item.TableReference, _item.TableEntryReference);
            item.Add("value", _variable[i] = new IntVariable { Value = 0 });

            _list.Add(item);
        }
        _entry.Add("list", _list);
        _entry.StringChanged += _entry_StringChanged;
    }

    private void _entry_StringChanged(string value)
    {
        _target.text = value;
    }

    // Update is called once per frame
    void Update()
    {
        _variable[1].Value = Mathf.RoundToInt(Time.time); // Notice that I change only variable inside collection entry
    }
}

7977216--1023780--Unity_HQ5IxCBjSO.gif

1 Like

Ah very nice