/*
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
}
}