using System; using System.Collections.Generic; using UdonSharp.Compiler; using UnityEngine; using VRC.Udon; using VRC.Udon.Common; using VRC.Udon.Common.Interfaces; namespace UdonSharp.Serialization { public class UdonVariableStorageInterface : IHeapStorage { class VariableValueStorage : ValueStorage { public string elementKey; public UdonBehaviour behaviour; public VariableValueStorage(string elementKey, UdonBehaviour behaviour) { this.elementKey = elementKey; this.behaviour = behaviour; } public override T Value { get { return GetVariable(behaviour, elementKey); } set { SetVariable(behaviour, elementKey, value); } } } private static void SetVarInternal(UdonBehaviour behaviour, string variableKey, T value) { if (!behaviour.publicVariables.TrySetVariableValue(variableKey, value)) { UdonVariable varVal = new UdonVariable(variableKey, value); if (!behaviour.publicVariables.TryAddVariable(varVal)) { if (!behaviour.publicVariables.RemoveVariable(variableKey) || !behaviour.publicVariables.TryAddVariable(varVal)) // Fallback in case the value already exists for some reason Debug.LogError($"Could not write variable '{variableKey}' to public variables on UdonBehaviour"); else Debug.LogWarning($"Storage for variable '{variableKey}' of type '{typeof(T)}' did not match, updated storage type"); } } } private static void SetVariable(UdonBehaviour behaviour, string variableKey, T value) { System.Type type = typeof(T); bool isNull = false; if ((value is UnityEngine.Object unityEngineObject && unityEngineObject == null) || value == null) isNull = true; if (isNull) { bool isRemoveType = (type == typeof(GameObject) || type == typeof(Transform) || type == typeof(UdonBehaviour)); if (isRemoveType) behaviour.publicVariables.RemoveVariable(variableKey); else SetVarInternal(behaviour, variableKey, value); } else { SetVarInternal(behaviour, variableKey, value); } } private static T GetVariable(UdonBehaviour behaviour, string variableKey) { T output; if (behaviour.publicVariables.TryGetVariableValue(variableKey, out output)) return output; // The type no longer matches exactly, but is trivially convertible // This will usually flow into a reassignment of the public variable type in SetVarInternal() when the value gets copied back to Udon if (behaviour.publicVariables.TryGetVariableValue(variableKey, out object outputObj) && !outputObj.IsUnityObjectNull() && outputObj is T) return (T)outputObj; // Try to get the default value if there's no custom value specified if (behaviour.programSource != null && behaviour.programSource is UdonSharpProgramAsset udonSharpProgramAsset) { udonSharpProgramAsset.UpdateProgram(); IUdonProgram program = udonSharpProgramAsset.GetRealProgram(); uint varAddress; if (program.SymbolTable.TryGetAddressFromSymbol(variableKey, out varAddress)) { if (program.Heap.TryGetHeapVariable(varAddress, out output)) return output; } } return default; } UdonBehaviour udonBehaviour; static Dictionary> variableTypeLookup = new Dictionary>(); private System.Type GetElementType(string elementKey) { UdonSharpProgramAsset programAsset = (UdonSharpProgramAsset)udonBehaviour.programSource; Dictionary programTypeLookup; if (!variableTypeLookup.TryGetValue(programAsset, out programTypeLookup)) { programTypeLookup = new Dictionary(); foreach (FieldDefinition def in programAsset.fieldDefinitions.Values) { if (def.fieldSymbol.declarationType.HasFlag(SymbolDeclTypeFlags.Public) || def.fieldSymbol.declarationType.HasFlag(SymbolDeclTypeFlags.Private)) programTypeLookup.Add(def.fieldSymbol.symbolOriginalName, def.fieldSymbol.symbolCsType); } variableTypeLookup.Add(programAsset, programTypeLookup); } System.Type fieldType; if (!programTypeLookup.TryGetValue(elementKey, out fieldType)) return null; return fieldType; } public UdonVariableStorageInterface(UdonBehaviour udonBehaviour) { this.udonBehaviour = udonBehaviour; } public IValueStorage GetElementStorage(string elementKey) { System.Type elementType = GetElementType(elementKey); if (elementType == null) return null; return (IValueStorage)System.Activator.CreateInstance(typeof(VariableValueStorage<>).MakeGenericType(elementType), elementKey, udonBehaviour); } public object GetElementValueWeak(string elementKey) { object valueOut; udonBehaviour.publicVariables.TryGetVariableValue(elementKey, out valueOut); return valueOut; } public T GetElementValue(string elementKey) { T variableVal; if (udonBehaviour.publicVariables.TryGetVariableValue(elementKey, out variableVal)) return variableVal; return default; } public void SetElementValueWeak(string elementKey, object value) { udonBehaviour.publicVariables.TrySetVariableValue(elementKey, value); } public void SetElementValue(string elementKey, T value) { udonBehaviour.publicVariables.TrySetVariableValue(elementKey, value); } } }