using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Localization.SmartFormat.Core.Extensions;
using UnityEngine.Localization.SmartFormat.PersistentVariables;
namespace UnityEngine.Localization.SmartFormat.Extensions
{
///
/// Can be used to provide global or local values that do not need to be passed in as arguments when formatting a string.
/// The smart string should take the format {groupName.variableName}. e.g {global.player-score}.
/// Note: The group name and variable names must not contain any spaces.
///
[Serializable]
public class PersistentVariablesSource : ISource, IDictionary, ISerializationCallbackReceiver
{
[Serializable]
class NameValuePair
{
public string name;
[SerializeReference]
public VariablesGroupAsset group;
}
///
/// Encapsulates a and call.
///
public struct ScopedUpdate : IDisposable
{
///
/// Calls .
///
public void Dispose() => EndUpdating();
}
[SerializeField]
List m_Groups = new List();
Dictionary m_GroupLookup = new Dictionary();
internal static int s_IsUpdating;
///
/// Has been called?
/// This can be used when updating the value of multiple in order to do
/// a single update after the updates instead of 1 per change.
///
public static bool IsUpdating => s_IsUpdating != 0;
///
/// The number of that are used for global variables.
///
public int Count => m_Groups.Count;
///
/// Implmented as part of IDictionary but not used. Will always return .
///
public bool IsReadOnly => false;
///
/// Returns the global variable group names.
///
public ICollection Keys => m_GroupLookup.Keys;
///
/// Returns the global variable groups for this source.
///
public ICollection Values => m_GroupLookup.Values.Select(k => k.group).ToList();
///
/// Returns the global variable group that matches .
///
/// The name of the group to return.
///
public VariablesGroupAsset this[string name]
{
get => m_GroupLookup[name].group;
set => Add(name, value);
}
///
/// Called after the final has been called.
/// This can be used when you wish to respond to value change events but wish to do a
/// single update at the end instead of 1 per change.
/// For example, if you wanted to change the value of multiple global variables
/// that a smart string was using then changing each value would result in a new string
/// being generated, by using begin and end the string generation can be deferred until the
/// final change so that only 1 update is performed.
///
public static event Action EndUpdate;
///
/// Creates a new instance and adds the "." operator to the parser.
///
///
public PersistentVariablesSource(SmartFormatter formatter)
{
formatter.Parser.AddOperators(".");
}
///
/// Indicates that multiple will be changed and should wait for before updating.
/// See and .
/// Note: and can be nested, will only be called after the last .
///
public static void BeginUpdating() => s_IsUpdating++;
///
/// Indicates that updates to have finished and sends the event.
/// Note: and can be nested, will only be called after the last .
///
public static void EndUpdating()
{
s_IsUpdating--;
if (s_IsUpdating == 0)
{
EndUpdate?.Invoke();
}
else if (s_IsUpdating < 0)
{
Debug.LogWarning($"Incorrect number of Begin and End calls to {nameof(PersistentVariablesSource)}. {nameof(BeginUpdating)} must be called before {nameof(EndUpdating)}.");
s_IsUpdating = 0;
}
}
///
/// Can be used to create a and scope.
///
///
public static IDisposable UpdateScope()
{
BeginUpdating();
return new ScopedUpdate();
}
///
/// Returns if a global variable group could be found with a matching name, or if one could not.
///
/// The name of the global variable group to find.
/// The found global variable group or if one could not be found with a matching name.
/// if a group could be found or if one could not.
public bool TryGetValue(string name, out VariablesGroupAsset value)
{
if (m_GroupLookup.TryGetValue(name, out var v))
{
value = v.group;
return true;
}
value = null;
return false;
}
///
/// Add a global variable group to the source.
///
/// The name of the group to add.
/// The group to add.
/// Thrown if is .
/// Thrown if is or empty.
public void Add(string name, VariablesGroupAsset group)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException(nameof(name), "Name must not be null or empty.");
if (group == null)
throw new ArgumentNullException(nameof(group));
var pair = new NameValuePair { name = name, group = group };
name = name.ReplaceWhiteSpaces("-");
m_GroupLookup[name] = pair;
m_Groups.Add(pair);
}
///
public void Add(KeyValuePair item) => Add(item.Key, item.Value);
///
/// Removes the group with the matching name.
///
/// The name of the group to remove.
/// if a group with a matching name was found and removed, or if one was not.
public bool Remove(string name)
{
if (m_GroupLookup.TryGetValue(name, out var v))
{
m_Groups.Remove(v);
m_GroupLookup.Remove(name);
return true;
}
return false;
}
///
public bool Remove(KeyValuePair item) => Remove(item.Key);
///
/// Removes all global variables.
///
public void Clear()
{
m_GroupLookup.Clear();
m_Groups.Clear();
}
///
/// Returns if a global variable group is found with the same name.
///
/// The name of the global variable group to check for.
/// if a group with the name is found or if one is not.
public bool ContainsKey(string name) => m_GroupLookup.ContainsKey(name);
///
public bool Contains(KeyValuePair item) => TryGetValue(item.Key, out var v) && v == item.Value;
///
/// Copy all global variable groups into the provided array starting at .
///
/// The array to copy the global variables into.
/// The index to start copying into.
public void CopyTo(KeyValuePair[] array, int arrayIndex)
{
foreach (var entry in m_GroupLookup)
{
array[arrayIndex++] = new KeyValuePair(entry.Key, entry.Value.group);
}
}
///
/// Returns an enumerator for all the global variables in the source.
///
///
IEnumerator> IEnumerable>.GetEnumerator()
{
foreach (var v in m_GroupLookup)
{
yield return new KeyValuePair(v.Key, v.Value.group);
}
}
///
/// Returns an enumerator for all the global variables in the source.
///
///
public IEnumerator GetEnumerator()
{
foreach (var v in m_GroupLookup)
{
yield return new KeyValuePair(v.Key, v.Value.group);
}
}
///
public bool TryEvaluateSelector(ISelectorInfo selectorInfo)
{
var selector = selectorInfo.SelectorText;
// First we test the current value
if (selectorInfo.CurrentValue is IVariableGroup grp && EvaluateLocalGroup(selectorInfo, grp))
return true;
// If we are at the root we also test the local variables
if (selectorInfo.SelectorOperator == "" && EvaluateLocalGroup(selectorInfo, selectorInfo.FormatDetails.FormatCache?.LocalVariables))
return true;
if (TryGetValue(selector, out var group))
{
selectorInfo.Result = group;
return true;
}
return false;
}
static bool EvaluateLocalGroup(ISelectorInfo selectorInfo, IVariableGroup variablleGroup)
{
if (variablleGroup == null)
return false;
if (variablleGroup != null && variablleGroup.TryGetValue(selectorInfo.SelectorText, out var variable))
{
// Add the variable to the cache
var cache = selectorInfo.FormatDetails.FormatCache;
if (cache != null && variable is IVariableValueChanged valueChanged)
{
if (!cache.VariableTriggers.Contains(valueChanged))
cache.VariableTriggers.Add(valueChanged);
}
selectorInfo.Result = variable.GetSourceValue(selectorInfo);
return true;
}
return false;
}
public void OnBeforeSerialize() {}
public void OnAfterDeserialize()
{
if (m_GroupLookup == null)
m_GroupLookup = new Dictionary();
m_GroupLookup.Clear();
foreach (var v in m_Groups)
{
if (!string.IsNullOrEmpty(v.name))
{
m_GroupLookup[v.name] = v;
}
}
}
}
}