OliveRave/UnityProject/Assets/Udon/UdonBehaviour.cs

1641 lines
48 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using JetBrains.Annotations;
using Unity.Profiling;
using UnityEngine;
using VRC.SDK3.Components;
using VRC.SDKBase;
using VRC.Udon.Common;
using VRC.Udon.Common.Attributes;
using VRC.Udon.Common.Enums;
using VRC.Udon.Common.Interfaces;
using VRC.Udon.Serialization.OdinSerializer;
using VRC.Udon.VM;
using Logger = VRC.Core.Logger;
using Object = UnityEngine.Object;
#if UNITY_EDITOR && !VRC_CLIENT
using UnityEditor.SceneManagement;
#endif
namespace VRC.Udon
{
public sealed class UdonBehaviour : AbstractUdonBehaviour, ISerializationCallbackReceiver
{
#region Odin Serialized Fields
[PublicAPI]
public IUdonVariableTable publicVariables = new UdonVariableTable();
#endregion
#region Serialized Public Fields
[Obsolete("Use VRCObjectSync instead")]
[PublicAPI]
// ReSharper disable once InconsistentNaming
public bool SynchronizePosition;
// ReSharper disable once InconsistentNaming
[PublicAPI]
public readonly bool SynchronizeAnimation = false; //We don't support animation sync yet, coming soon.
// ReSharper disable once InconsistentNaming
[Obsolete("Use VRCObjectSync instead")]
[PublicAPI]
public bool AllowCollisionOwnershipTransfer = true;
// ReSharper disable once InconsistentNaming
[HideInInspector, Obsolete("Use SyncMethod instead")]
public bool Reliable = false;
// ReSharper disable once InconsistentNaming
[SerializeField]
private Networking.SyncType _syncMethod = Networking.SyncType.Unknown;
public Networking.SyncType SyncMethod
{
get
{
// Old Scene?
if(_syncMethod == Networking.SyncType.Unknown)
{
#pragma warning disable 618
_syncMethod = Reliable ? Networking.SyncType.Manual : Networking.SyncType.Continuous;
#pragma warning restore 618
}
return _syncMethod;
}
set
{
_syncMethod = value;
if(value == Networking.SyncType.None)
{
return;
}
// All synced UdonBehaviours on one GameObject must use the same sync method.
foreach(UdonBehaviour ub in gameObject.GetComponents<UdonBehaviour>())
{
if(ub != null && ub._syncMethod != Networking.SyncType.None)
{
ub._syncMethod = value;
}
}
}
}
public bool SyncIsContinuous => SyncMethod == Networking.SyncType.Continuous;
public bool SyncIsManual => SyncMethod == Networking.SyncType.Manual;
#endregion
#region Serialized Private Fields
[SerializeField]
private AbstractSerializedUdonProgramAsset serializedProgramAsset;
#if UNITY_EDITOR && !VRC_CLIENT
[SerializeField]
public AbstractUdonProgramSource programSource;
#endif
#endregion
#region Public Fields and Properties
[PublicAPI]
public static Action<UdonBehaviour, IUdonProgram> OnInit { get; set; } = null;
[PublicAPI]
public static Action<UdonBehaviour> RequestSerializationHook { get; set; } = null;
[PublicAPI]
public static Action<UdonBehaviour, NetworkEventTarget, string> SendCustomNetworkEventHook { get; set; } = null;
[PublicAPI]
public override bool DisableInteractive { get; set; }
[PublicAPI]
[ExcludeFromUdonWrapper]
public override bool IsNetworkingSupported
{
get => _isNetworkingSupported;
set
{
if(_initialized)
{
throw new InvalidOperationException(
"IsNetworkingSupported cannot be changed after the UdonBehaviour has been initialized.");
}
_isNetworkingSupported = value;
}
}
public override bool IsInteractive => _hasInteractiveEvents && !DisableInteractive;
// ReSharper disable once InconsistentNaming
public const string ReturnVariableName = "__returnValue";
internal int UpdateOrder => _program?.UpdateOrder ?? 0;
#endregion
#region Private Fields and Properties
private UdonManager _udonManager;
private IUdonProgram _program;
private IUdonVM _udonVM;
private bool _isReady;
private int _debugLevel;
private bool _hasError;
private bool _hasDoneStart;
private bool _initialized;
private bool _isNetworkingSupported = false;
private bool _hasInteractiveEvents;
private bool _hasUpdateEvent;
private bool _hasLateUpdateEvent;
private bool _hasFixedUpdateEvent;
private bool _hasPostLateUpdateEvent;
private readonly Dictionary<string, List<uint>> _eventTable = new Dictionary<string, List<uint>>();
private readonly Dictionary<(string eventName, string symbolName), string> _symbolNameCache =
new Dictionary<(string, string), string>();
private static ProfilerMarker _managedUpdateProfilerMarker =
new ProfilerMarker("UdonBehaviour.ManagedUpdate()");
private static ProfilerMarker _managedLateUpdateProfilerMarker =
new ProfilerMarker("UdonBehaviour.ManagedLateUpdate()");
private static ProfilerMarker _managedFixedUpdateProfilerMarker =
new ProfilerMarker("UdonBehaviour.ManagedFixedUpdate()");
private static ProfilerMarker _postLateUpdateProfilerMarker =
new ProfilerMarker("UdonBehaviour.PostLateUpdate()");
private readonly SortedDictionary<uint, (uint, uint)> _variableToChangeEvent = new SortedDictionary<uint, (uint, uint)>();
private readonly List<AbstractUdonBehaviourEventProxy> _eventProxies = new List<AbstractUdonBehaviourEventProxy>();
#endregion
#region Editor Only
#if UNITY_EDITOR && !VRC_CLIENT
public void RunEditorUpdate(ref bool dirty)
{
if (programSource == null)
{
return;
}
programSource.RunEditorUpdate(this, ref dirty);
if (!dirty)
{
return;
}
EditorSceneManager.MarkSceneDirty(gameObject.scene);
}
#endif
#endregion
#region Private Methods
private bool LoadProgram()
{
if(serializedProgramAsset == null)
{
return false;
}
_program = serializedProgramAsset.RetrieveProgram();
IUdonSymbolTable symbolTable = _program?.SymbolTable;
IUdonHeap heap = _program?.Heap;
if(symbolTable == null || heap == null)
{
return false;
}
foreach(string variableSymbol in publicVariables.VariableSymbols)
{
if(!symbolTable.HasAddressForSymbol(variableSymbol))
{
continue;
}
uint symbolAddress = symbolTable.GetAddressFromSymbol(variableSymbol);
if(!publicVariables.TryGetVariableType(variableSymbol, out Type declaredType))
{
continue;
}
publicVariables.TryGetVariableValue(variableSymbol, out object value);
if(declaredType == typeof(GameObject) || declaredType == typeof(UdonBehaviour) ||
declaredType == typeof(Transform))
{
if(value == null)
{
value = new UdonGameObjectComponentHeapReference(declaredType);
declaredType = typeof(UdonGameObjectComponentHeapReference);
}
}
heap.SetHeapVariable(symbolAddress, value, declaredType);
}
return true;
}
private void RegisterEventProxy<T>() where T : AbstractUdonBehaviourEventProxy
{
// Exit early if we already have a match.
foreach(AbstractUdonBehaviourEventProxy existingProxy in _eventProxies)
{
if(!(existingProxy is T existingProxyAsT))
{
continue;
}
if(existingProxyAsT.EventReceiver.Equals(this))
{
return;
}
}
AbstractUdonBehaviourEventProxy proxy = gameObject.AddComponent<T>();
#if UNITY_EDITOR
proxy.hideFlags = HideFlags.HideInInspector |
HideFlags.DontSaveInEditor |
HideFlags.DontSaveInBuild;
#endif
proxy.EventReceiver = this;
proxy.enabled = enabled;
_eventProxies.Add(proxy);
}
private void ProcessEntryPoints()
{
if(_program.EntryPoints.HasExportedSymbol("_interact"))
{
_hasInteractiveEvents = true;
}
if(_program.EntryPoints.HasExportedSymbol("_update"))
{
_hasUpdateEvent = true;
}
if(_program.EntryPoints.HasExportedSymbol("_lateUpdate"))
{
_hasLateUpdateEvent = true;
}
if(_program.EntryPoints.HasExportedSymbol("_fixedUpdate"))
{
_hasFixedUpdateEvent = true;
}
if(_program.EntryPoints.HasExportedSymbol("_postLateUpdate"))
{
_hasPostLateUpdateEvent = true;
}
DetectExistingProxies();
if(_program.EntryPoints.HasExportedSymbol("_onRenderObject"))
{
RegisterEventProxy<OnRenderObjectProxy>();
}
if(_program.EntryPoints.HasExportedSymbol("_onWillRenderObject"))
{
RegisterEventProxy<OnWillRenderObjectProxy>();
}
if(_program.EntryPoints.HasExportedSymbol("_onTriggerStay") ||
_program.EntryPoints.HasExportedSymbol("_onPlayerTriggerStay"))
{
RegisterEventProxy<OnTriggerStayProxy>();
}
if(_program.EntryPoints.HasExportedSymbol("_onCollisionStay") ||
_program.EntryPoints.HasExportedSymbol("_onPlayerCollisionStay"))
{
RegisterEventProxy<OnCollisionStayProxy>();
}
if(_program.EntryPoints.HasExportedSymbol("_onAnimatorMove"))
{
RegisterEventProxy<OnAnimatorMoveProxy>();
}
if (_program.EntryPoints.HasExportedSymbol("_onAudioFilterRead"))
{
RegisterEventProxy<OnAudioFilterReadProxy>();
}
RegisterUpdate();
_eventTable.Clear();
foreach(string entryPoint in _program.EntryPoints.GetExportedSymbols())
{
uint address = _program.EntryPoints.GetAddressFromSymbol(entryPoint);
if(!_eventTable.ContainsKey(entryPoint))
{
_eventTable.Add(entryPoint, new List<uint>());
}
_eventTable[entryPoint].Add(address);
_udonManager.RegisterInput(this, entryPoint, true);
// check whether this is a variableChangedEvent
if(entryPoint.StartsWith(VariableChangedEvent.EVENT_PREFIX))
{
string variableName = entryPoint.Remove(0, VariableChangedEvent.EVENT_PREFIX.Length);
// ensure the variable with the matching name exists
if(_program.SymbolTable.TryGetAddressFromSymbol(variableName, out uint variableAddress))
{
// the old variable is only added if it's used, so just store default if it's not
_program.SymbolTable.TryGetAddressFromSymbol(string.Concat(VariableChangedEvent.OLD_VALUE_PREFIX, variableName), out uint oldVariableAddress);
// add variable > event address lookup
_variableToChangeEvent.Add(variableAddress, (address, oldVariableAddress));
}
}
}
}
// GameObjects may sometimes already have proxies for this UdonBehaviour.
// For example if the GameObject was cloned from a scene GameObject,
// or the component was for some reason added in the editor (this is not an expected workflow).
// In either case Unity will serialize the reference from the proxy to this UdonBehaviour,
// but will not serialize the contents of the _eventProxies List so we need to build it.
// If we don't do this then we may create a proxy where one already exists and events will run twice.
private void DetectExistingProxies()
{
GetComponents(_eventProxies);
for(int i = _eventProxies.Count - 1; i >= 0; i--)
{
AbstractUdonBehaviourEventProxy proxy = _eventProxies[i];
if(proxy == null)
{
_eventProxies.RemoveAt(i);
continue;
}
UdonBehaviour proxyEventReceiver = proxy.EventReceiver;
// Destroy and remove all copied proxy components which don't have an EventReceiver.
if(proxyEventReceiver == null)
{
Destroy(proxy);
_eventProxies.RemoveAt(i);
continue;
}
// Remove all copied proxy components which aren't for this UdonBehaviour.
if(proxyEventReceiver == this)
{
continue;
}
_eventProxies.RemoveAt(i);
}
}
private bool ResolveUdonHeapReferences(IUdonSymbolTable symbolTable, IUdonHeap heap)
{
bool success = true;
foreach(string symbolName in symbolTable.GetSymbols())
{
uint symbolAddress = symbolTable.GetAddressFromSymbol(symbolName);
object heapValue = heap.GetHeapVariable(symbolAddress);
if(!(heapValue is UdonBaseHeapReference udonBaseHeapReference))
{
continue;
}
if(!ResolveUdonHeapReference(heap, symbolAddress, udonBaseHeapReference))
{
success = false;
}
}
return success;
}
private bool ResolveUdonHeapReference(IUdonHeap heap, uint symbolAddress,
UdonBaseHeapReference udonBaseHeapReference)
{
switch(udonBaseHeapReference)
{
case UdonGameObjectComponentHeapReference udonGameObjectComponentHeapReference:
{
Type referenceType = udonGameObjectComponentHeapReference.type;
if(referenceType == typeof(GameObject))
{
heap.SetHeapVariable(symbolAddress, gameObject);
return true;
}
else if(referenceType == typeof(Transform))
{
heap.SetHeapVariable(symbolAddress, gameObject.transform);
return true;
}
else if(referenceType == typeof(UdonBehaviour))
{
heap.SetHeapVariable(symbolAddress, this);
return true;
}
else if(referenceType == typeof(Object))
{
heap.SetHeapVariable(symbolAddress, this);
return true;
}
else
{
Logger.Log(
$"Unsupported GameObject/Component reference type: {udonBaseHeapReference.GetType().Name}. Only GameObject, Transform, and UdonBehaviour are supported.",
_debugLevel,
this);
return false;
}
}
default:
{
Logger.Log(
$"Unknown heap reference type: {udonBaseHeapReference.GetType().Name}",
_debugLevel,
this);
return false;
}
}
}
#endregion
#region Managed Unity Events
internal void ManagedUpdate()
{
using(_managedUpdateProfilerMarker.Auto())
{
if(!_hasDoneStart && _isReady)
{
_hasDoneStart = true;
RunEvent("_onEnable");
RunEvent("_start");
if(!_hasUpdateEvent)
{
_udonManager.UnregisterUdonBehaviourUpdate(this);
}
}
RunEvent("_update");
}
}
internal void ManagedLateUpdate()
{
using(_managedLateUpdateProfilerMarker.Auto())
{
RunEvent("_lateUpdate");
}
}
internal void ManagedFixedUpdate()
{
using(_managedFixedUpdateProfilerMarker.Auto())
{
RunEvent("_fixedUpdate");
}
}
internal void PostLateUpdate()
{
using(_postLateUpdateProfilerMarker.Auto())
{
RunEvent("_postLateUpdate");
}
}
#endregion
#region Unity Events
public void OnAnimatorIK(int layerIndex)
{
RunEvent("_onAnimatorIK", ("layerIndex", layerIndex));
}
internal void ProxyOnAnimatorMove()
{
RunEvent("_onAnimatorMove");
}
internal void ProxyOnAudioFilterRead(float[] data, int channels)
{
RunEvent("_onAudioFilterRead", ("data", data), ("channels", channels));
}
public void OnBecameInvisible()
{
RunEvent("_onBecameInvisible");
}
public void OnBecameVisible()
{
RunEvent("_onBecameVisible");
}
public void OnCollisionEnter(Collision other)
{
var player = VRCPlayerApi.GetPlayerByGameObject(other.gameObject);
if(player != null)
{
RunEvent("_onPlayerCollisionEnter", ("player", player));
}
else if(!UdonManager.Instance.IsBlacklisted(other))
{
RunEvent("_onCollisionEnter", ("other", other));
}
}
public void OnCollisionEnter2D(Collision2D other)
{
if(!UdonManager.Instance.IsBlacklisted(other))
{
RunEvent("_onCollisionEnter2D", ("other", other));
}
}
public void OnCollisionExit(Collision other)
{
var player = VRCPlayerApi.GetPlayerByGameObject(other.gameObject);
if(player != null)
{
RunEvent("_onPlayerCollisionExit", ("player", player));
}
else if(!UdonManager.Instance.IsBlacklisted(other))
{
RunEvent("_onCollisionExit", ("other", other));
}
}
public void OnCollisionExit2D(Collision2D other)
{
if(!UdonManager.Instance.IsBlacklisted(other))
{
RunEvent("_onCollisionExit2D", ("other", other));
}
}
internal void ProxyOnCollisionStay(Collision other)
{
var player = VRCPlayerApi.GetPlayerByGameObject(other.gameObject);
if(player != null)
{
RunEvent("_onPlayerCollisionStay", ("player", player));
}
else if(!UdonManager.Instance.IsBlacklisted(other))
{
RunEvent("_onCollisionStay", ("other", other));
}
}
public void OnCollisionStay2D(Collision2D other)
{
if(!UdonManager.Instance.IsBlacklisted(other))
{
RunEvent("_onCollisionStay2D", ("other", other));
}
}
public void OnDestroy()
{
if(_program == null)
{
return;
}
foreach(string entryPoint in _program.EntryPoints.GetExportedSymbols())
{
_udonManager.RegisterInput(this, entryPoint, false);
}
RunEvent("_onDestroy");
_udonVM = null;
_program = null;
foreach(AbstractUdonBehaviourEventProxy proxy in _eventProxies)
{
if(proxy)
{
Destroy(proxy);
}
}
}
public void OnDisable()
{
UnregisterUpdate();
RunEvent("_onDisable");
}
public void OnDrawGizmos()
{
RunEvent("_onDrawGizmos");
}
public void OnDrawGizmosSelected()
{
RunEvent("_onDrawGizmosSelected");
}
public void OnEnable()
{
if(_initialized)
{
RegisterUpdate();
}
RunEvent("_onEnable");
}
public void OnJointBreak(float breakForce)
{
RunEvent("_onJointBreak", ("force", breakForce));
}
public void OnJointBreak2D(Joint2D brokenJoint)
{
RunEvent("_onJointBreak2D", ("joint", brokenJoint));
}
public void OnMouseDown()
{
RunEvent("_onMouseDown");
}
public void OnMouseDrag()
{
RunEvent("_onMouseDrag");
}
public void OnMouseEnter()
{
RunEvent("_onMouseEnter");
}
public void OnMouseExit()
{
RunEvent("_onMouseExit");
}
public void OnMouseOver()
{
RunEvent("_onMouseOver");
}
public void OnMouseUp()
{
RunEvent("_onMouseUp");
}
public void OnMouseUpAsButton()
{
RunEvent("_onMouseUpAsButton");
}
public void OnParticleCollision(GameObject other)
{
var player = VRCPlayerApi.GetPlayerByGameObject(other.gameObject);
if(player != null)
{
RunEvent("_onPlayerParticleCollision", ("player", player));
}
else
{
RunEvent("_onParticleCollision", ("other", other));
}
}
public void OnParticleTrigger()
{
RunEvent("_onParticleTrigger");
}
public void OnPostRender()
{
RunEvent("_onPostRender");
}
public void OnPreCull()
{
RunEvent("_onPreCull");
}
public void OnPreRender()
{
RunEvent("_onPreRender");
}
public void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if(!_eventTable.ContainsKey("_onRenderImage") || _eventTable["_onRenderImage"].Count == 0)
{
Graphics.Blit(src, dest);
return;
}
RunEvent("_onRenderImage", ("src", src), ("dest", dest));
}
internal void ProxyOnRenderObject()
{
RunEvent("_onRenderObject");
}
public void OnTransformChildrenChanged()
{
RunEvent("_onTransformChildrenChanged");
}
public void OnTransformParentChanged()
{
RunEvent("_onTransformParentChanged");
}
public void OnTriggerEnter(Collider other)
{
var player = VRCPlayerApi.GetPlayerByGameObject(other.gameObject);
if(player != null)
{
RunEvent("_onPlayerTriggerEnter", ("player", player));
}
else if(!UdonManager.Instance.IsBlacklisted(other))
{
RunEvent("_onTriggerEnter", ("other", other));
}
}
public void OnTriggerEnter2D(Collider2D other)
{
if(!UdonManager.Instance.IsBlacklisted(other))
{
RunEvent("_onTriggerEnter2D", ("other", other));
}
}
public void OnTriggerExit(Collider other)
{
var player = VRCPlayerApi.GetPlayerByGameObject(other.gameObject);
if(player != null)
{
RunEvent("_onPlayerTriggerExit", ("player", player));
}
else if(!UdonManager.Instance.IsBlacklisted(other))
{
RunEvent("_onTriggerExit", ("other", other));
}
}
public void OnTriggerExit2D(Collider2D other)
{
if(!UdonManager.Instance.IsBlacklisted(other))
{
RunEvent("_onTriggerExit2D", ("other", other));
}
}
internal void ProxyOnTriggerStay(Collider other)
{
var player = VRCPlayerApi.GetPlayerByGameObject(other.gameObject);
if(player != null)
{
RunEvent("_onPlayerTriggerStay", ("player", player));
}
else if(!UdonManager.Instance.IsBlacklisted(other))
{
RunEvent("_onTriggerStay", ("other", other));
}
}
public void OnTriggerStay2D(Collider2D other)
{
if(!UdonManager.Instance.IsBlacklisted(other))
{
RunEvent("_onTriggerStay2D", ("other", other));
}
}
public void OnValidate()
{
RunEvent("_onValidate");
}
internal void ProxyOnWillRenderObject()
{
RunEvent("_onWillRenderObject");
}
#endregion
#region VRCSDK Events
#if VRC_CLIENT
[PublicAPI]
private void OnNetworkReady()
{
_isReady = true;
}
#endif
//Called through Interactable interface
public override void Interact()
{
RunEvent("_interact");
}
public override void OnDrop()
{
RunEvent("_onDrop");
}
public override void OnPickup()
{
RunEvent("_onPickup");
}
public override void OnPickupUseDown()
{
RunEvent("_onPickupUseDown");
}
public override void OnPickupUseUp()
{
RunEvent("_onPickupUseUp");
}
//Called via delegate by UdonSync
[PublicAPI]
public void OnPreSerialization()
{
if(_syncMethod == Networking.SyncType.None)
{
return;
}
RunEvent("_onPreSerialization");
}
//Called via delegate by UdonSync
[PublicAPI]
public void OnPostSerialization(SerializationResult result)
{
if(_syncMethod == Networking.SyncType.None)
{
return;
}
RunEvent("_onPostSerialization", ("result", result));
}
//Called via delegate by UdonSync
[PublicAPI]
public void OnDeserialization()
{
if(_syncMethod == Networking.SyncType.None)
{
return;
}
RunEvent("_onDeserialization");
}
#endregion
#region RunProgram Methods
[PublicAPI]
public override void RunProgram(string eventName)
{
if(_program == null)
{
return;
}
if(!_program.EntryPoints.GetExportedSymbols().Contains(eventName))
{
return;
}
uint address = _program.EntryPoints.GetAddressFromSymbol(eventName);
RunProgram(address);
}
private void RunProgram(uint entryPoint)
{
if(_hasError)
{
return;
}
if(_udonVM == null)
{
return;
}
uint originalAddress = _udonVM.GetProgramCounter();
UdonBehaviour originalExecuting = _udonManager.currentlyExecuting;
_udonVM.SetProgramCounter(entryPoint);
_udonManager.currentlyExecuting = this;
_udonVM.DebugLogging = _udonManager.DebugLogging;
try
{
uint result = _udonVM.Interpret();
if(result != 0)
{
Logger.LogError(
$"Udon VM execution errored, this UdonBehaviour will be halted.",
_debugLevel,
this);
_hasError = true;
enabled = false;
}
}
catch(UdonVMException error)
{
Logger.LogError(
$"An exception occurred during Udon execution, this UdonBehaviour will be halted.\n{error}",
_debugLevel,
this);
_hasError = true;
enabled = false;
}
_udonManager.currentlyExecuting = originalExecuting;
if(originalAddress < 0xFFFFFFFC)
{
_udonVM.SetProgramCounter(originalAddress);
}
}
[PublicAPI]
public ImmutableArray<string> GetPrograms()
{
return _program?.EntryPoints.GetExportedSymbols() ?? ImmutableArray<string>.Empty;
}
#endregion
#region Serialization
[SerializeField]
private string serializedPublicVariablesBytesString;
[SerializeField]
private List<Object> publicVariablesUnityEngineObjects;
[SerializeField]
private DataFormat publicVariablesSerializationDataFormat = DataFormat.Binary;
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
DeserializePublicVariables();
}
private void DeserializePublicVariables()
{
byte[] serializedPublicVariablesBytes =
Convert.FromBase64String(serializedPublicVariablesBytesString ?? "");
publicVariables = SerializationUtility.DeserializeValue<IUdonVariableTable>(
serializedPublicVariablesBytes,
publicVariablesSerializationDataFormat,
publicVariablesUnityEngineObjects
) ?? new UdonVariableTable();
// Validate that the type of the value can actually be cast to the declaredType to avoid InvalidCastExceptions later.
foreach(string publicVariableSymbol in publicVariables.VariableSymbols.ToArray())
{
if(!publicVariables.TryGetVariableValue(publicVariableSymbol, out object value))
{
continue;
}
if(value == null)
{
continue;
}
if(!publicVariables.TryGetVariableType(publicVariableSymbol, out Type declaredType))
{
continue;
}
if(declaredType.IsInstanceOfType(value))
{
continue;
}
if(declaredType.IsValueType)
{
publicVariables.TrySetVariableValue(publicVariableSymbol, Activator.CreateInstance(declaredType));
}
else
{
publicVariables.TrySetVariableValue(publicVariableSymbol, null);
}
}
}
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
SerializePublicVariables();
}
private void SerializePublicVariables()
{
byte[] serializedPublicVariablesBytes = SerializationUtility.SerializeValue(
publicVariables,
publicVariablesSerializationDataFormat,
out publicVariablesUnityEngineObjects);
serializedPublicVariablesBytesString = Convert.ToBase64String(serializedPublicVariablesBytes);
}
#endregion
#region IUdonBehaviour Interface
public bool TryToInterrogateUdon(string eventName, out object returnValue, params (string symbolName, object value)[] programVariables)
{
return TryToInterrogateUdon<object>(eventName, out returnValue, programVariables);
}
public bool TryToInterrogateUdon<T>(string eventName, out T returnValue, params (string symbolName, object value)[] programVariables)
{
if(!_initialized || !enabled || !_hasDoneStart)
{
#if VRC_CLIENT || UNITY_EDITOR
if(UdonManager.Instance.DebugLogging)
{
Logger.Log($"{gameObject.name} not ready to respond to {eventName}: initialized={_initialized} enabled={enabled} hasStarted={_hasDoneStart}", _debugLevel);
}
#endif
returnValue = default(T);
return false;
}
if(!_eventTable.ContainsKey(eventName))
{
#if VRC_CLIENT || UNITY_EDITOR
if(UdonManager.Instance.DebugLogging)
{
Logger.Log($"{gameObject.name} will not respond to {eventName}", _debugLevel);
}
#endif
returnValue = default(T);
return false;
}
if(!RunEvent(eventName, programVariables))
{
#if VRC_CLIENT || UNITY_EDITOR
if(UdonManager.Instance.DebugLogging)
{
Logger.LogError($"{gameObject.name} failed to respond to {eventName}", _debugLevel);
}
#endif
returnValue = default(T);
return false;
}
returnValue = GetProgramVariable<T>(ReturnVariableName);
return true;
}
public override bool RunEvent(string eventName, params (string symbolName, object value)[] programVariables)
{
if(!_isReady)
{
return false;
}
if(!_hasDoneStart)
{
return false;
}
if(_hasError)
{
return false;
}
if(_udonVM == null)
{
return false;
}
if(!_eventTable.TryGetValue(eventName, out List<uint> entryPoints))
{
return false;
}
//TODO: Replace with a non-boxing interface before exposing to users
foreach((string symbolName, object value) in programVariables)
{
SetEventVariable(eventName, symbolName, value);
}
foreach(uint entryPoint in entryPoints)
{
RunProgram(entryPoint);
}
foreach((string symbolName, object _) in programVariables)
{
SetProgramVariable(symbolName, null);
}
return true;
}
public override void RunInputEvent(string eventName, UdonInputEventArgs args)
{
if(!_isReady)
{
return;
}
if(!_hasDoneStart)
{
return;
}
if(!_program.EntryPoints.GetExportedSymbols().Contains(eventName))
{
return;
}
// Set value arg
switch(args.eventType)
{
case UdonInputEventType.AXIS:
SetEventVariable(eventName, "floatValue", args.floatValue);
break;
case UdonInputEventType.BUTTON:
SetEventVariable(eventName, "boolValue", args.boolValue);
break;
}
// Set event args
SetEventVariable(eventName, "args", args);
RunProgram(eventName);
}
private void SetEventVariable<T>(string eventName, string symbolName, T value)
{
if(!_symbolNameCache.TryGetValue((eventName, symbolName), out string newSymbolName))
{
newSymbolName = $"{eventName.Substring(1)}{char.ToUpper(symbolName.First())}{symbolName.Substring(1)}";
_symbolNameCache.Add((eventName, symbolName), newSymbolName);
}
SetProgramVariable(newSymbolName, value);
}
public override void InitializeUdonContent()
{
if(_initialized)
{
return;
}
SetupLogging();
_udonManager = UdonManager.Instance;
if(_udonManager == null)
{
enabled = false;
Logger.LogError(
$"Could not find the UdonManager; the UdonBehaviour on '{gameObject.name}' will not run.",
_debugLevel,
this);
return;
}
if(!LoadProgram())
{
enabled = false;
Logger.Log(
$"Could not load the program; the UdonBehaviour on '{gameObject.name}' will not run.",
_debugLevel,
this);
return;
}
// Let UdonManager apply any processing or scans.
_udonManager.ProcessUdonProgram(_program);
IUdonSymbolTable symbolTable = _program?.SymbolTable;
IUdonHeap heap = _program?.Heap;
if(symbolTable == null || heap == null)
{
enabled = false;
Logger.Log(
$"Invalid program; the UdonBehaviour on '{gameObject.name}' will not run.",
_debugLevel,
this);
return;
}
if(!ResolveUdonHeapReferences(symbolTable, heap))
{
enabled = false;
Logger.Log(
$"Failed to resolve a GameObject/Component Reference; the UdonBehaviour on '{gameObject.name}' will not run.",
_debugLevel,
this);
return;
}
_udonVM = _udonManager.ConstructUdonVM();
if(_udonVM == null)
{
enabled = false;
Logger.LogError(
$"No UdonVM; the UdonBehaviour on '{gameObject.name}' will not run.",
_debugLevel,
this);
return;
}
_udonVM.LoadProgram(_program);
ProcessEntryPoints();
#if !VRC_CLIENT
_isReady = true;
#else
if(!_isNetworkingSupported)
{
_isReady = true;
}
#endif
_initialized = true;
RunOnInit();
}
[PublicAPI]
public void RunOnInit()
{
if(OnInit == null)
{
return;
}
try
{
OnInit(this, _program);
}
catch(Exception exception)
{
enabled = false;
Logger.LogError(
$"An exception '{exception.Message}' occurred during initialization; the UdonBehaviour on '{gameObject.name}' will not run. Exception:\n{exception}",
_debugLevel,
this
);
}
}
private void RegisterUpdate()
{
if(_udonManager == null)
{
return;
}
if(!isActiveAndEnabled)
{
return;
}
if(_hasUpdateEvent || !_hasDoneStart)
{
_udonManager.RegisterUdonBehaviourUpdate(this);
}
if(_hasLateUpdateEvent)
{
_udonManager.RegisterUdonBehaviourLateUpdate(this);
}
if(_hasFixedUpdateEvent)
{
_udonManager.RegisterUdonBehaviourFixedUpdate(this);
}
if(_hasPostLateUpdateEvent)
{
_udonManager.RegisterUdonBehaviourPostLateUpdate(this);
}
foreach(AbstractUdonBehaviourEventProxy proxy in _eventProxies)
{
proxy.enabled = true;
}
}
private void UnregisterUpdate()
{
if(_udonManager == null)
{
return;
}
if(_hasUpdateEvent)
{
_udonManager.UnregisterUdonBehaviourUpdate(this);
}
if(_hasLateUpdateEvent)
{
_udonManager.UnregisterUdonBehaviourLateUpdate(this);
}
if(_hasFixedUpdateEvent)
{
_udonManager.UnregisterUdonBehaviourFixedUpdate(this);
}
if(_hasPostLateUpdateEvent)
{
_udonManager.UnregisterUdonBehaviourPostLateUpdate(this);
}
foreach(AbstractUdonBehaviourEventProxy proxy in _eventProxies)
{
proxy.enabled = false;
}
}
#region IUdonEventReceiver and IUdonSyncTarget Interface
#region IUdonEventReceiver Only
public override void SendCustomEvent(string eventName)
{
RunProgram(eventName);
}
public override void SendCustomNetworkEvent(NetworkEventTarget target, string eventName)
{
#if UNITY_EDITOR
SendCustomEvent(eventName);
#else
SendCustomNetworkEventHook?.Invoke(this, target, eventName);
#endif
}
public override void RequestSerialization()
{
RequestSerializationHook?.Invoke(this);
}
public override void SendCustomEventDelayedSeconds(string eventName, float delaySeconds, EventTiming eventTiming = EventTiming.Update)
{
UdonManager.Instance.ScheduleDelayedEvent(this, eventName, delaySeconds, eventTiming);
}
public override void SendCustomEventDelayedFrames(string eventName, int delayFrames, EventTiming eventTiming = EventTiming.Update)
{
UdonManager.Instance.ScheduleDelayedEvent(this, eventName, delayFrames, eventTiming);
}
public override string InteractionText
{
get => interactText;
set => interactText = value;
}
#endregion
#region IUdonSyncTarget
public override IUdonSyncMetadataTable SyncMetadataTable => _program?.SyncMetadataTable;
#endregion
#region Shared
public override Type GetProgramVariableType(string symbolName)
{
if(!_program.SymbolTable.HasAddressForSymbol(symbolName))
{
return null;
}
uint symbolAddress = _program.SymbolTable.GetAddressFromSymbol(symbolName);
return _program.Heap.GetHeapVariableType(symbolAddress);
}
public override void SetProgramVariable<T>(string symbolName, T value)
{
if(_program == null)
{
return;
}
if(!_program.SymbolTable.TryGetAddressFromSymbol(symbolName, out uint symbolAddress))
{
return;
}
SetHeapVariable(symbolAddress, value);
}
public override void SetProgramVariable(string symbolName, object value)
{
if(_program == null)
{
return;
}
if(!_program.SymbolTable.TryGetAddressFromSymbol(symbolName, out uint symbolAddress))
{
return;
}
SetHeapVariable(symbolAddress, value);
}
private void SetHeapVariable<T>(uint symbolAddress, T newValue)
{
if(_variableToChangeEvent.TryGetValue(symbolAddress, out (uint eventAddress, uint oldVariableAddress) data))
{
// cache value before changing
T value = _program.Heap.GetHeapVariable<T>(symbolAddress);
// check for change and trigger event
if(!value?.Equals(newValue) ?? newValue != null)
{
// change the variable on the heap
_program.Heap.SetHeapVariable(symbolAddress, newValue);
// change the old variable on the heap
if(data.oldVariableAddress != uint.MaxValue)
{
_program.Heap.SetHeapVariable(data.oldVariableAddress, value);
}
// trigger the event
RunProgram(data.eventAddress);
}
}
else
{
// just change the variable on the heap
_program.Heap.SetHeapVariable(symbolAddress, newValue);
}
}
public override T GetProgramVariable<T>(string symbolName)
{
if(_program == null)
{
return default;
}
if(!_program.SymbolTable.TryGetAddressFromSymbol(symbolName, out uint symbolAddress))
{
return default;
}
return _program.Heap.GetHeapVariable<T>(symbolAddress);
}
public override object GetProgramVariable(string symbolName)
{
if(_program == null)
{
return null;
}
if(!_program.SymbolTable.TryGetAddressFromSymbol(symbolName, out uint symbolAddress))
{
#if UNITY_EDITOR
Logger.LogError($"Could not find symbol {symbolName}; available: [{string.Join(",", _program.SymbolTable.GetSymbols())}]", _debugLevel);
#endif
return null;
}
return _program.Heap.GetHeapVariable(symbolAddress);
}
public override bool TryGetProgramVariable<T>(string symbolName, out T value)
{
value = default;
if(_program == null)
{
return false;
}
if(!_program.SymbolTable.TryGetAddressFromSymbol(symbolName, out uint symbolAddress))
{
return false;
}
return _program.Heap.TryGetHeapVariable(symbolAddress, out value);
}
public override bool TryGetProgramVariable(string symbolName, out object value)
{
value = null;
if(_program == null)
{
return false;
}
if(!_program.SymbolTable.TryGetAddressFromSymbol(symbolName, out uint symbolAddress))
{
return false;
}
return _program.Heap.TryGetHeapVariable(symbolAddress, out value);
}
#endregion
#endregion
#endregion
#region Logging Methods
private void SetupLogging()
{
_debugLevel = GetType().GetHashCode();
if(Logger.DebugLevelIsDescribed(_debugLevel))
{
return;
}
Logger.DescribeDebugLevel(_debugLevel, "UdonBehaviour");
Logger.AddDebugLevel(_debugLevel);
}
#endregion
#region Manual Initialization Methods
[PublicAPI]
public void AssignProgramAndVariables(AbstractSerializedUdonProgramAsset compiledAsset,
IUdonVariableTable variables)
{
serializedProgramAsset = compiledAsset;
publicVariables = variables;
}
#endregion
}
}