/* Yarn Spinner is licensed to you under the terms found in the file LICENSE.md. */ using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using UnityEngine; #if USE_TMP using TMPro; #else using TMP_Text = Yarn.Unity.TMPShim; #endif #nullable enable namespace Yarn.Unity { /// /// A simple implementation of VariableStorageBehaviour. /// /// /// This class stores variables in memory, and is erased when the game /// exits. /// /// This class also has basic serialization and save/load example functions. /// /// You can also enumerate over the variables by using a foreach /// loop: /// /// /// // 'storage' is an InMemoryVariableStorage /// foreach (var variable in storage) { /// string name = variable.Key; /// System.Object value = variable.Value; /// } /// /// [HelpURL("https://docs.yarnspinner.dev/using-yarnspinner-with-unity/components/variable-storage")] public class InMemoryVariableStorage : VariableStorageBehaviour, IEnumerable> { /// /// Where we're actually keeping our variables /// private Dictionary variables = new Dictionary(); private Dictionary variableTypes = new Dictionary(); // needed for serialization [Header("Optional debugging tools")] [HideInInspector] public bool showDebug; /// /// A that can show the current list /// of all variables in-game. Optional. /// [SerializeField, Tooltip("(optional) output list of variables and values to Text UI in-game")] internal TMP_Text? debugTextView = null; internal void Update() { // If we have a debug view, show the list of all variables in it if (debugTextView != null) { debugTextView.text = GetDebugList(); debugTextView.SetAllDirty(); } } public string GetDebugList() { var stringBuilder = new System.Text.StringBuilder(); foreach (KeyValuePair item in variables) { // .Name gets type name without namespace prefix stringBuilder.AppendLine(string.Format("{0} = {1} ({2})", item.Key, item.Value.ToString(), variableTypes[item.Key].Name)); } return stringBuilder.ToString(); } #region Setters /// /// Throws a if is not a valid Yarn Spinner variable name. /// /// The variable name to test. /// Thrown when is not a valid variable name. private void ValidateVariableName(string variableName) { if (variableName.StartsWith("$") == false) { throw new System.ArgumentException($"{variableName} is not a valid variable name: Variable names must start with a '$'. (Did you mean to use '${variableName}'?)"); } } public override void SetValue(string variableName, string stringValue) { ValidateVariableName(variableName); variables[variableName] = stringValue; variableTypes[variableName] = typeof(string); NotifyVariableChanged(variableName, stringValue); } public override void SetValue(string variableName, float floatValue) { ValidateVariableName(variableName); variables[variableName] = floatValue; variableTypes[variableName] = typeof(float); NotifyVariableChanged(variableName, floatValue); } public override void SetValue(string variableName, bool boolValue) { ValidateVariableName(variableName); variables[variableName] = boolValue; variableTypes[variableName] = typeof(bool); NotifyVariableChanged(variableName, boolValue); } private static bool TryGetAsType(Dictionary dictionary, string key, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out T result) { if (dictionary.TryGetValue(key, out var objectResult) == true && typeof(T).IsAssignableFrom(objectResult.GetType())) { result = (T)objectResult; return true; } result = default!; return false; } /// public override bool TryGetValue(string variableName, [NotNullWhen(true)] out T result) { // Ensure that the variable name is valid. ValidateVariableName(variableName); switch (GetVariableKind(variableName)) { case VariableKind.Stored: // This is a stored value. First, attempt to fetch it from // the variable storage. // Try to get the value from the dictionary, and check to // see that it's the if (TryGetAsType(variables, variableName, out result)) { // We successfully fetched it from storage. return true; } else { if (this.Program is null) { throw new System.InvalidOperationException($"Can't get initial value for variable {variableName}, because {nameof(Program)} is not set"); } return this.Program.TryGetInitialValue(variableName, out result); } case VariableKind.Smart: // The variable is a smart variable. Find the node that // implements it, and use that to get the variable's current // value. // Update the VM's settings, since ours might have changed // since we created the VM. if (this.SmartVariableEvaluator is null) { throw new System.InvalidOperationException($"Can't get value for smart variable {variableName}, because {nameof(SmartVariableEvaluator)} is not set"); } return this.SmartVariableEvaluator.TryGetSmartVariable(variableName, out result); case VariableKind.Unknown: default: // The variable is not known. result = default!; return false; } } /// /// Removes all variables from storage. /// public override void Clear() { variables.Clear(); variableTypes.Clear(); } #endregion /// /// returns a boolean value representing if the particular variable is /// inside the variable storage /// public override bool Contains(string variableName) { return variables.ContainsKey(variableName); } /// /// Returns an that iterates over all /// variables in this object. /// /// An iterator over the variables. IEnumerator> IEnumerable>.GetEnumerator() { return ((IEnumerable>)variables).GetEnumerator(); } /// /// Returns an that iterates over all /// variables in this object. /// /// An iterator over the variables. IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable>)variables).GetEnumerator(); } #region Save/Load public override (Dictionary, Dictionary, Dictionary) GetAllVariables() { Dictionary floatDict = new Dictionary(); Dictionary stringDict = new Dictionary(); Dictionary boolDict = new Dictionary(); foreach (var variable in variables) { var type = variableTypes[variable.Key]; if (type == typeof(float)) { float value = System.Convert.ToSingle(variable.Value); floatDict.Add(variable.Key, value); } else if (type == typeof(string)) { string value = System.Convert.ToString(variable.Value); stringDict.Add(variable.Key, value); } else if (type == typeof(bool)) { bool value = System.Convert.ToBoolean(variable.Value); boolDict.Add(variable.Key, value); } else { Debug.Log($"{variable.Key} is not a valid type"); } } return (floatDict, stringDict, boolDict); } public override void SetAllVariables(Dictionary floats, Dictionary strings, Dictionary bools, bool clear = true) { if (clear) { variables.Clear(); variableTypes.Clear(); } foreach (var value in floats) { SetValue(value.Key, value.Value); } foreach (var value in strings) { SetValue(value.Key, value.Value); } foreach (var value in bools) { SetValue(value.Key, value.Value); } Debug.Log($"bulk loaded {floats.Count} floats, {strings.Count} strings, {bools.Count} bools"); } #endregion } }