同步
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Yarn.Utility;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Yarn.Unity
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface that works with <see
|
||||
/// cref="GeneratedVariableStorageExtensions"/> to add helper methods used
|
||||
/// by generated variable storage classes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="IVariableStorage"/> classes that implement this interface are
|
||||
/// extended with the methods found in <see
|
||||
/// cref="GeneratedVariableStorageExtensions"/>. This interface is intended
|
||||
/// to be used only with classes in source code that was generated by Yarn
|
||||
/// Spinner.
|
||||
/// </remarks>
|
||||
public interface IGeneratedVariableStorage : Yarn.IVariableStorage
|
||||
{
|
||||
}
|
||||
|
||||
public static class GeneratedVariableStorageExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value for the variable <paramref name="variableName"/> from
|
||||
/// <paramref name="storage"/>, or else returns the default value of
|
||||
/// <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the parameter to get a value
|
||||
/// for.</typeparam>
|
||||
/// <param name="storage">The generated variable storage class to get
|
||||
/// the value from.</param>
|
||||
/// <param name="variableName">The name of the variable to get a value
|
||||
/// for.</param>
|
||||
/// <returns>The value of <paramref name="variableName"/>, or the
|
||||
/// default value of
|
||||
/// <typeparamref name="T"/>.</returns>
|
||||
public static T? GetValueOrDefault<T>(this IGeneratedVariableStorage storage, string variableName) where T : IConvertible
|
||||
{
|
||||
if (storage.TryGetValue<T>(variableName, out T? result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.Log($"Failed to get a value of type {typeof(T)} for variable {variableName}.");
|
||||
return default(T);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetValue<T>(this IGeneratedVariableStorage storage, string v, T value) where T : IConvertible
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case string stringValue:
|
||||
|
||||
storage.SetValue(v, stringValue);
|
||||
break;
|
||||
case float floatValue:
|
||||
storage.SetValue(v, floatValue);
|
||||
break;
|
||||
case bool boolValue:
|
||||
storage.SetValue(v, boolValue);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("Unhandled value type " + value.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
public static T? GetEnumValueOrDefault<T>(this IGeneratedVariableStorage storage, string variableName) where T : System.Enum
|
||||
{
|
||||
if (!storage.TryGetValue(variableName, out object? result))
|
||||
{
|
||||
UnityEngine.Debug.LogError($"Failed to get a value of type {typeof(T).Name} for variable {variableName}.");
|
||||
return default;
|
||||
}
|
||||
|
||||
int caseValue;
|
||||
|
||||
if (result is string stringResult)
|
||||
{
|
||||
// Convert the string value to a hash
|
||||
caseValue = (int)CRC32.GetChecksum(stringResult);
|
||||
}
|
||||
else if (result is float floatResult)
|
||||
{
|
||||
caseValue = (int)floatResult;
|
||||
}
|
||||
else if (result is int intResult)
|
||||
{
|
||||
caseValue = intResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"Failed to get a value of type {typeof(T).Name} for variable {variableName}: received an unexpected variable type {result.GetType()} from variable storage");
|
||||
return default;
|
||||
}
|
||||
|
||||
if (Enum.IsDefined(typeof(T), caseValue))
|
||||
{
|
||||
return (T)Enum.ToObject(typeof(T), caseValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"Failed to get a value of type {typeof(T)} for variable {variableName}: ${caseValue} is not a valid case value.");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11ad53a51081a4d71af38da358e06007
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple implementation of VariableStorageBehaviour.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This class stores variables in memory, and is erased when the game
|
||||
/// exits.</para>
|
||||
///
|
||||
/// <para>This class also has basic serialization and save/load example functions.</para>
|
||||
///
|
||||
/// <para>You can also enumerate over the variables by using a <c>foreach</c>
|
||||
/// loop:</para>
|
||||
///
|
||||
/// <code lang="csharp">
|
||||
/// // 'storage' is an InMemoryVariableStorage
|
||||
/// foreach (var variable in storage) {
|
||||
/// string name = variable.Key;
|
||||
/// System.Object value = variable.Value;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
[HelpURL("https://docs.yarnspinner.dev/using-yarnspinner-with-unity/components/variable-storage")]
|
||||
public class InMemoryVariableStorage : VariableStorageBehaviour, IEnumerable<KeyValuePair<string, object>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Where we're actually keeping our variables
|
||||
/// </summary>
|
||||
private Dictionary<string, object> variables = new Dictionary<string, object>();
|
||||
private Dictionary<string, System.Type> variableTypes = new Dictionary<string, System.Type>(); // needed for serialization
|
||||
|
||||
[Header("Optional debugging tools")]
|
||||
[HideInInspector] public bool showDebug;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="TMPro.TMP_Text"/> that can show the current list
|
||||
/// of all variables in-game. Optional.
|
||||
/// </summary>
|
||||
[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<string, object> 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
|
||||
|
||||
/// <summary>
|
||||
/// Throws a <see cref="System.ArgumentException"/> if <paramref
|
||||
/// name="variableName"/> is not a valid Yarn Spinner variable name.
|
||||
/// </summary>
|
||||
/// <param name="variableName">The variable name to test.</param>
|
||||
/// <exception cref="System.ArgumentException">Thrown when <paramref
|
||||
/// name="variableName"/> is not a valid variable name.</exception>
|
||||
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<T>(Dictionary<string, object> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool TryGetValue<T>(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<T>(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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all variables from storage.
|
||||
/// </summary>
|
||||
public override void Clear()
|
||||
{
|
||||
variables.Clear();
|
||||
variableTypes.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// returns a boolean value representing if the particular variable is
|
||||
/// inside the variable storage
|
||||
/// </summary>
|
||||
public override bool Contains(string variableName)
|
||||
{
|
||||
return variables.ContainsKey(variableName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an <see cref="IEnumerator{T}"/> that iterates over all
|
||||
/// variables in this object.
|
||||
/// </summary>
|
||||
/// <returns>An iterator over the variables.</returns>
|
||||
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<KeyValuePair<string, object>>)variables).GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an <see cref="IEnumerator"/> that iterates over all
|
||||
/// variables in this object.
|
||||
/// </summary>
|
||||
/// <returns>An iterator over the variables.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<KeyValuePair<string, object>>)variables).GetEnumerator();
|
||||
}
|
||||
|
||||
#region Save/Load
|
||||
public override (Dictionary<string, float>, Dictionary<string, string>, Dictionary<string, bool>) GetAllVariables()
|
||||
{
|
||||
Dictionary<string, float> floatDict = new Dictionary<string, float>();
|
||||
Dictionary<string, string> stringDict = new Dictionary<string, string>();
|
||||
Dictionary<string, bool> boolDict = new Dictionary<string, bool>();
|
||||
|
||||
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<string, float> floats, Dictionary<string, string> strings, Dictionary<string, bool> 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 610c541718d694070a21327d1ae62e75
|
||||
timeCreated: 1443787032
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using UnityEngine;
|
||||
using BoolDictionary = System.Collections.Generic.Dictionary<string, bool>;
|
||||
using FloatDictionary = System.Collections.Generic.Dictionary<string, float>;
|
||||
using StringDictionary = System.Collections.Generic.Dictionary<string, string>;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Yarn.Unity
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="MonoBehaviour"/> that a <see cref="DialogueRunner"/> uses
|
||||
/// to store and retrieve variables.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This abstract class inherits from <see cref="MonoBehaviour"/>, which
|
||||
/// means that subclasses of this class can be attached to <see
|
||||
/// cref="GameObject"/>s.
|
||||
/// </remarks>
|
||||
public abstract class VariableStorageBehaviour : MonoBehaviour, Yarn.IVariableStorage
|
||||
{
|
||||
public Program? Program { get; set; }
|
||||
|
||||
public ISmartVariableEvaluator? SmartVariableEvaluator { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract bool TryGetValue<T>(string variableName, [NotNullWhen(true)] out T? result);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract void SetValue(string variableName, string stringValue);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract void SetValue(string variableName, float floatValue);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract void SetValue(string variableName, bool boolValue);
|
||||
|
||||
/// <summary>
|
||||
/// Notifies all currently registered change listeners that the variable
|
||||
/// named <paramref name="variableName"/> has changed value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the variable.</typeparam>
|
||||
/// <param name="variableName">The name of the variable that changed
|
||||
/// value.</param>
|
||||
/// <param name="newValue">The new value of the variable.</param>
|
||||
protected void NotifyVariableChanged<T>(string variableName, T newValue)
|
||||
{
|
||||
if (changeListeners.TryGetValue(variableName, out var delegates))
|
||||
{
|
||||
foreach (var listener in delegates)
|
||||
{
|
||||
listener.DynamicInvoke(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var listener in globalChangeListeners)
|
||||
{
|
||||
listener.DynamicInvoke(variableName, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a boolean value representing if a particular variable is
|
||||
/// inside the variable storage.
|
||||
/// </summary>
|
||||
/// <param name="variableName">The name of the variable to check
|
||||
/// for.</param>
|
||||
/// <returns><see langword="true"/> if this variable storage contains a
|
||||
/// value for the variable named <paramref name="variableName"/>; <see
|
||||
/// langword="false"/> otherwise.</returns>
|
||||
public abstract bool Contains(string variableName);
|
||||
|
||||
/// <summary>
|
||||
/// Provides a unified interface for loading many variables all at once.
|
||||
/// Will override anything already in the variable storage.
|
||||
/// </summary>
|
||||
/// <param name="clear">Should the load also wipe the storage. Defaults
|
||||
/// to true so all existing variables will be cleared.
|
||||
/// </param>
|
||||
public abstract void SetAllVariables(FloatDictionary floats, StringDictionary strings, BoolDictionary bools, bool clear = true);
|
||||
|
||||
/// <summary>
|
||||
/// Provides a unified interface for exporting all variables. Intended
|
||||
/// to be a point for custom saving, editors, etc.
|
||||
/// </summary>
|
||||
public abstract (FloatDictionary FloatVariables, StringDictionary StringVariables, BoolDictionary BoolVariables) GetAllVariables();
|
||||
|
||||
public VariableKind GetVariableKind(string name)
|
||||
{
|
||||
if (this.Contains(name))
|
||||
{
|
||||
return VariableKind.Stored;
|
||||
}
|
||||
else if (this.Program != null)
|
||||
{
|
||||
return Program.GetVariableKind(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"Unable to determine kind of variable {name}: it is not stored in this variable storage, and {nameof(Program)} is not set");
|
||||
return VariableKind.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
private struct ChangeListenerDisposable : IDisposable
|
||||
{
|
||||
|
||||
private Delegate listener;
|
||||
|
||||
private readonly List<Delegate> listeners;
|
||||
|
||||
public ChangeListenerDisposable(List<Delegate> listeners, Delegate listener)
|
||||
{
|
||||
this.listeners = listeners;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
listeners.Remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, List<Delegate>> changeListeners = new();
|
||||
private List<Delegate> globalChangeListeners = new();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a delegate that will be called when the variable <paramref
|
||||
/// name="variableName"/> is modified.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the variable.</typeparam>
|
||||
/// <param name="variableName">The name of the variable to watch for
|
||||
/// changes to. This variable must be of type <typeparamref name="T"/>,
|
||||
/// and it must not be a smart variable.</param>
|
||||
/// <param name="onChange">The delegate to run when the variable changes
|
||||
/// value.</param>
|
||||
/// <returns>An <see cref="IDisposable"/> that removes the registration
|
||||
/// when its <see cref="IDisposable.Dispose"/> method is
|
||||
/// called.</returns>
|
||||
/// <exception cref="InvalidOperationException">Called when <see
|
||||
/// cref="Program"/> is not set.</exception>
|
||||
/// <exception cref="ArgumentException">Called when <paramref
|
||||
/// name="variableName"/> is not the name of a valid variable, or if
|
||||
/// <typeparamref name="T"/> does not match the type of the
|
||||
/// variable.</exception>
|
||||
public IDisposable AddChangeListener<T>(string variableName, Action<T> onChange)
|
||||
{
|
||||
if (Program == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Can't add a change listener for {variableName}: {nameof(Program)} is not set");
|
||||
}
|
||||
|
||||
var kind = this.GetVariableKind(variableName);
|
||||
if (kind == VariableKind.Smart)
|
||||
{
|
||||
throw new ArgumentException($"Can't add a change listener for {variableName}: change listeners cannot be added for {VariableKind.Smart} variables.");
|
||||
}
|
||||
|
||||
if (changeListeners.TryGetValue(variableName, out var list) == false)
|
||||
{
|
||||
list = new List<Delegate>();
|
||||
changeListeners[variableName] = list;
|
||||
}
|
||||
|
||||
if (kind == VariableKind.Stored)
|
||||
{
|
||||
var type = this.Program.InitialValues[variableName].ValueCase;
|
||||
|
||||
if (type == Operand.ValueOneofCase.BoolValue && typeof(T) != typeof(bool))
|
||||
{
|
||||
throw new ArgumentException($"Can't add a {typeof(T)} change listener for {variableName}: must be a {typeof(bool)}");
|
||||
}
|
||||
if (type == Operand.ValueOneofCase.StringValue && typeof(T) != typeof(string))
|
||||
{
|
||||
throw new ArgumentException($"Can't add a {typeof(T)} change listener for {variableName}: must be a {typeof(string)}");
|
||||
}
|
||||
if (type == Operand.ValueOneofCase.FloatValue && typeof(float).IsAssignableFrom(typeof(T)) == false)
|
||||
{
|
||||
throw new ArgumentException($"Can't add a {typeof(T)} change listener for {variableName}: must be a number");
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(onChange);
|
||||
|
||||
return new ChangeListenerDisposable(list, onChange);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Registers a delegate that will be called when any variable is modified.
|
||||
/// </summary>
|
||||
/// <param name="onChange">The delegate to run when the variable changes
|
||||
/// value.</param>
|
||||
/// <returns>An <see cref="IDisposable"/> that removes the registration
|
||||
/// when its <see cref="IDisposable.Dispose"/> method is
|
||||
/// called.</returns>
|
||||
public IDisposable AddChangeListener(System.Action<string, object> onChange)
|
||||
{
|
||||
globalChangeListeners ??= new();
|
||||
|
||||
globalChangeListeners.Add(onChange);
|
||||
|
||||
return new ChangeListenerDisposable(globalChangeListeners, onChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fdcaa867a4b764397a6978e025044583
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user