void Start()
    {
        if (getOnStart)
        {
            if (udonBehaviour)
            {
                originalValue = (int)udonBehaviour.GetProgramVariable(variableName);
            }

            value = originalValue;
        }
        else
        {
            originalValue = value;
        }

        if (display)
        {
            display.text = value.ToString();
        }

        if (slider)
        {
            slider.wholeNumbers = true;
            slider.value        = value;
            sliderInitialize    = true;
        }

        if (0 > step)
        {
            step = Mathf.Abs(step);
        }
    }
Example #2
0
        private void ShowVariableEditor(UdonBehaviour udonBehaviour)
        {
            expandVariableEditor_ = EditorGUILayout.Foldout(expandVariableEditor_, "Edit Public Variables", true);

            if (!expandVariableEditor_)
            {
                return;
            }

            var program = udonBehaviour.programSource;

            if (!(program is UdonProgramAsset programAsset))
            {
                return;
            }

            var publicVariables = udonBehaviour.publicVariables;

            foreach (var varName in publicVariables.VariableSymbols)
            {
                publicVariables.TryGetVariableType(varName, out Type varType);
                object   value      = udonBehaviour.GetProgramVariable(varName);
                object[] parameters = { varName, value, varType, false, true };
                var      res        = DrawPropertyMethod.Invoke(programAsset, parameters);

                if ((bool)parameters[3])
                {
                    udonBehaviour.SetProgramVariable(varName, res);
                }
            }
        }
Example #3
0
    private void updatePlayers()
    {
        if (playerObserverUdon == null)
        {
            playerObserverUdon = (UdonBehaviour)playerObserver.GetComponent(typeof(UdonBehaviour));
        }

        var allPlayers = (VRCPlayerApi[])playerObserverUdon.GetProgramVariable("Players");

        if (allPlayers == null)
        {
            return;
        }

        var isModified = false;

        var halfScale = transform.localScale / 2;
        var zMin      = transform.position.z - halfScale.z;
        var zMax      = transform.position.z + halfScale.z;
        var xMin      = transform.position.x - halfScale.x;
        var xMax      = transform.position.x + halfScale.x;

        int k = 0;

        for (int i = 0; i < allPlayers.Length; i++)
        {
            var player = allPlayers[i];
            if (player == null)
            {
                break;
            }
            var ppos     = player.GetPosition();
            var isInArea = zMin < ppos.z && ppos.z < zMax && xMin < ppos.x && ppos.x < xMax;
            if (isInArea)
            {
                if (Players[k] == null || Players[k].playerId != player.playerId)
                {
                    isModified = true;
                }
                Players[k++] = player;
            }
        }

        for (; k < Players.Length; k++)
        {
            if (Players[k] == null)
            {
                break;
            }
            Players[k] = null;
            isModified = true;
        }

        if (isModified)
        {
            updateText();
            emitModifyEvent();
        }
    }
Example #4
0
        private void LateUpdate()
        {
            if (!active)
            {
                return;
            }
            for (int i = 0; i < textVariableNames.Length; i++)
            {
                var val = source.GetProgramVariable(textVariableNames[i]).ToString();
                textVariableTargets[i].text = textReadoutFormats[i].Replace("{0}", val);
            }

            for (int i = 0; i < sliderVariableNames.Length; i++)
            {
                var val = source.GetProgramVariable(sliderVariableNames[i]);
                sliderVariableTargets[i].value = Convert.ToSingle(val) * sliderReadoutMultipliers[i];
            }
        }
        internal static long GetUdonTypeID(UdonBehaviour behaviour)
        {
            object id = behaviour.GetProgramVariable(CompilerConstants.UsbTypeIDHeapKey);

            if (id == null)
            {
                return(0);
            }

            return((long)id);
        }
Example #6
0
        void Update()
        {
            Color[] audioData = (Color[])audioLink.GetProgramVariable("audioData");
            if (audioData.Length != 0)          // check for audioLink initialization
            {
                float amplitude = audioData[_dataIndex].grayscale;

                transform.localPosition    = _initialPosition + (position * amplitude);
                transform.localEulerAngles = _initialRotation + (rotation * amplitude);

                transform.localScale = new Vector3(_initialScale.x * Mathf.Lerp(1f, scale.x, amplitude), _initialScale.y * Mathf.Lerp(1f, scale.y, amplitude), _initialScale.z * Mathf.Lerp(1f, scale.z, amplitude));
            }
        }
 void Update()
 {
     Color[] audioData = (Color[])audioLink.GetProgramVariable("audioData");
     if (audioData.Length != 0)       // check for audioLink initialization
     {
         float amplitude = audioData[_dataIndex].grayscale;
         if (affectIntensity)
         {
             _light.intensity = amplitude * intensityMultiplier;
         }
         _light.color = HueShift(_initialColor, amplitude * hueShift);
     }
 }
Example #8
0
 void Interact()
 {
     if (Networking.IsOwner(boneposition.gameObject))
     {
         bool track = (bool)boneposition.GetProgramVariable("track");
         boneposition.SetProgramVariable("track", !track);
     }
     else
     {
         Networking.SetOwner(PlayerApiref, boneposition.gameObject);
         boneposition.SetProgramVariable("localwait", true);
     }
 }
Example #9
0
    void Update()
    {
        Color[] audioData = (Color[])audioLink.GetProgramVariable("audioData");
        if (audioData.Length != 0)      // check for audioLink initialization
        {
            float amplitude = audioData[_dataIndex].grayscale;

            transform.localPosition    = _initialPosition + (position * amplitude);
            transform.localEulerAngles = _initialRotation + (rotation * amplitude);

            //transform.localScale *= scale;
        }
    }
Example #10
0
    private void Start()
    {
        //update display text to current float value if added
        object variable = udonBehaviour.GetProgramVariable(variable_string);

        if (variable != null)
        {
            if (variable.GetType() == typeof(float))
            {
                float num = (float)variable;
                updatevisual(num.ToString("0.00"));
            }
            else
            {
                Debug.LogWarning("float controller: variable not of float type");
            }
        }
        else
        {
            Debug.LogWarning("float controller: get variable returned null");
        }
    }
    public void UpdateSettings()
    {
        // Update labels
        gainLabel.text   = "Gain: " + ((int)Remap(gainSlider.value, 0f, 2f, 0f, 200f)).ToString() + "%";
        trebleLabel.text = "Treble: " + ((int)Remap(trebleSlider.value, 0f, 2f, 0f, 200f)).ToString() + "%";
        bassLabel.text   = "Bass: " + ((int)Remap(bassSlider.value, 0f, 2f, 0f, 200f)).ToString() + "%";

        // Update
        threshold0Slider.transform.localPosition = new Vector3(Remap(x1Slider.value / 2f, 0f, 1f, -349f, 349f), _initThreshold0SliderPosition.y, 0f);
        threshold1Slider.transform.localPosition = new Vector3(Remap((x1Slider.value + x2Slider.value) / 2f, 0f, 1f, -349f, 349f), _initThreshold1SliderPosition.y, 0f);
        threshold2Slider.transform.localPosition = new Vector3(Remap((x2Slider.value + x3Slider.value) / 2f, 0f, 1f, -349f, 349f), _initThreshold2SliderPosition.y, 0f);
        threshold3Slider.transform.localPosition = new Vector3(Remap((x3Slider.value + 1f) / 2f, 0f, 1f, -349f, 349f), _initThreshold3SliderPosition.y, 0f);

        // General settings
        audioLink.SetProgramVariable("gain", gainSlider.value);
        audioLink.SetProgramVariable("treble", trebleSlider.value);
        audioLink.SetProgramVariable("bass", bassSlider.value);
        audioLink.SetProgramVariable("fadeLength", fadeLengthSlider.value);
        audioLink.SetProgramVariable("fadeExpFalloff", fadeExpFalloffSlider.value);
        audioLink.SetProgramVariable("fadeExpFalloff", fadeExpFalloffSlider.value);

        // Crossover settings
        float[] audioBands = (float[])audioLink.GetProgramVariable("audioBands");
        audioBands[1] = x1Slider.value;
        audioBands[2] = x2Slider.value;
        audioBands[3] = x3Slider.value;
        audioLink.SetProgramVariable("audioBands", audioBands);
        audioSpectrumDisplay.SetFloatArray("_AudioBands", audioBands);
        float[] audioThresholds = (float[])audioLink.GetProgramVariable("audioThresholds");
        audioThresholds[0] = threshold0Slider.value;
        audioThresholds[1] = threshold1Slider.value;
        audioThresholds[2] = threshold2Slider.value;
        audioThresholds[3] = threshold3Slider.value;
        audioLink.SetProgramVariable("audioThresholds", audioThresholds);
        audioSpectrumDisplay.SetFloatArray("_AudioThresholds", audioThresholds);

        audioLink.SendCustomEvent("UpdateSettings");
    }
Example #12
0
#pragma warning restore CS0649

        void TestSetGetProgramVar()
        {
            SetProgramVariable(nameof(_programVar), 5);

            tester.TestAssertion("SetProgramVariable local", _programVar == 5);
            tester.TestAssertion("GetProgramVariable local", (int)GetProgramVariable(nameof(_programVar)) == 5);

            // ReSharper disable once SuspiciousTypeConversion.Global
            UdonBehaviour selfUdonBehaviour = (UdonBehaviour)(Component)this;

            selfUdonBehaviour.SetProgramVariable(nameof(_programVar), 10);

            tester.TestAssertion("UdonBehaviour SetProgramVariable", _programVar == 10);
            tester.TestAssertion("UdonBehaviour GetProgramVariable", (int)selfUdonBehaviour.GetProgramVariable(nameof(_programVar)) == 10);
        }
Example #13
0
    void Start()
    {
        //var camera = audioLink.GetComponent<Camera>();
        //var audioTexture = camera.targetTexture;
        var spectrumBands = (float[])audioLink.GetProgramVariable("spectrumBands");
        var block         = new MaterialPropertyBlock();
        var mesh          = GetComponent <MeshRenderer>();

        block.SetFloat("_Delay", (float)delay);
        block.SetFloat("_Band", (float)band);
        block.SetFloat("_NumBands", spectrumBands.Length);
        block.SetFloat("_AudioHueShift", hueShift);
        block.SetColor("_AudioColor", color);
        //block.SetTexture("_AudioTexture", audioTexture);
        mesh.SetPropertyBlock(block);
    }
Example #14
0
        public void ExecuteTests()
        {
            if (behaviour != null)
            {
                // This just needs to compile
                byte[] data = (byte[])((UdonBehaviour)behaviour.GetProgramVariable("ABC")).GetProgramVariable("DEFG");
            }

            int castFloat = (int)5.4f;

            tester.TestAssertion("Int Cast", castFloat == 5);


            int testCastFloat = (int)(int)(float)(int)floatField;

            tester.TestAssertion("Int Cast 2", testCastFloat == 4);
        }
Example #15
0
    void ProcessReceiver()
    {
        string        receiverName   = (string)EVENT_ARRAY.GetValue(1);
        GameObject    receiverObject = GameObject.Find(receiverName);
        UdonBehaviour receiverUdon   = (UdonBehaviour)receiverObject.GetComponent(typeof(UdonBehaviour));

        if (receiverObject == null)
        {
            Debug.Log("[SE_Emitter] Error: No Receiver found called " + receiverName);
            return;
        }
        UdonBehaviour eventReceiverHandler = (UdonBehaviour)receiverUdon.GetProgramVariable("EventHandler");

        if (eventReceiverHandler == null)
        {
            Debug.Log("[SE_Emitter] Error: No EventHandler found on target " + receiverObject.name);
            return;
        }
        eventReceiverHandler.SetProgramVariable("EventInbox", EVENT);
    }
    void Start()
    {
        if (getOnStart)
        {
            if (udonBehaviour)
            {
                value = (bool)udonBehaviour.GetProgramVariable(variableName);
            }
        }

        if (display)
        {
            display.text = value.ToString();
        }

        if (toggle)
        {
            toggle.isOn      = value;
            toggleInitialize = true;
        }
    }
Example #17
0
        /// <summary>
        /// Triggers Serialization of the manually synced player id.
        /// Does nothing if the caller does not own this behaviour/gameobject.
        /// </summary>
        /// <returns>false if the local player is not the owner or anything goes wrong</returns>
        public bool UpdateForAll()
        {
            var localPlayer = VRC.SDKBase.Networking.LocalPlayer;

            if (!Utilities.IsValid(targetBehaviour) ||
                !Utilities.IsValid(localPlayer) ||
                !localPlayer.IsOwner(gameObject))
            {
                return(false);
            }

            if (!string.IsNullOrEmpty(targetPreSerialization))
            {
                targetBehaviour.SendCustomEvent(targetPreSerialization);
            }

            var value = targetBehaviour.GetProgramVariable(targetVariable);

            if (value == null)
            {
                Debug.LogError(
                    $"SyncedInteger.UpdateForAll: '{targetVariable}' does not exist in '{targetBehaviour.name}'", this);
                return(false);
            }

            // ReSharper disable once OperatorIsCanBeUsed
            if (value.GetType() == typeof(int))
            {
                syncedValue = (int)value;
                UpdateOldValueAndTriggerChangeEvent();
                RequestSerialization();

                return(true);
            }

            Debug.LogError(
                $"SyncedInteger.UpdateForAll: '{targetVariable}' in '{targetBehaviour.name}' is not an integer", this);
            return(false);
        }
Example #18
0
    public void UpdateSettings()
    {
        gainLabel.text   = "Gain: " + ((int)Remap(gainSlider.value, 0f, 2f, 0f, 200f)).ToString() + "%";
        trebleLabel.text = "Treble: " + ((int)Remap(trebleSlider.value, 0f, 2f, 0f, 200f)).ToString() + "%";
        bassLabel.text   = "Bass: " + ((int)Remap(bassSlider.value, 0f, 2f, 0f, 200f)).ToString() + "%";
        x1Label.text     = "X1: " + ((int)x1Slider.value).ToString() + "hz";
        x2Label.text     = "X2: " + ((int)x2Slider.value).ToString() + "hz";
        x3Label.text     = "X3: " + ((int)x3Slider.value).ToString() + "hz";

        audioLink.SetProgramVariable("gain", gainSlider.value);
        audioLink.SetProgramVariable("treble", trebleSlider.value);
        audioLink.SetProgramVariable("bass", bassSlider.value);
        audioLink.SetProgramVariable("fadeLength", fadeLengthSlider.value);
        audioLink.SetProgramVariable("fadeExpFalloff", fadeExpFalloffSlider.value);
        audioLink.SetProgramVariable("fadeExpFalloff", fadeExpFalloffSlider.value);

        float[] spectrumBands = (float[])audioLink.GetProgramVariable("spectrumBands");
        spectrumBands[1] = x1Slider.value;
        spectrumBands[2] = x2Slider.value;
        spectrumBands[3] = x3Slider.value;
        audioLink.SetProgramVariable("spectrumBands", spectrumBands);
        audioLink.SendCustomEvent("UpdateSettings");
    }
Example #19
0
        private void Add(string displayname)
        {
            Debug.Log("[Thry] [Player-List] Add Player: " + displayname);
            GameObject playerButton = VRCInstantiate(playerPrefab);

            playerButton.transform.position   = Vector3.zero;
            playerButton.transform.localScale = Vector3.one;
            playerButton.name = displayname;
            ((Text)playerButton.GetComponentInChildren(typeof(Text))).text = displayname;

            int addAtIndex = list.childCount;

            for (int i = list.childCount - 1; i >= 0; i--)
            {
                if (System.String.Compare(list.GetChild(i).name, displayname, System.StringComparison.CurrentCultureIgnoreCase) == 1)
                {
                    addAtIndex = i;
                }
            }
            playerButton.transform.SetParent(list, false);
            playerButton.transform.SetSiblingIndex(addAtIndex);
            playerButton.SetActive(true);

            foreach (Component c in playerButton.GetComponentsInChildren(typeof(UdonBehaviour), true))
            {
                if (c.gameObject != playerButton)
                {
                    c.name = displayname;
                }
                UdonBehaviour u = (UdonBehaviour)c;
                if (u.GetProgramVariableType("collapseAfterInstanciate") == typeof(bool) && (bool)u.GetProgramVariable("collapseAfterInstanciate") == true)
                {
                    ((GameObject)u.GetProgramVariable("content")).SetActive(false);
                }
            }
        }
 internal static string GetUdonTypeName(UdonBehaviour behaviour)
 {
     return((string)behaviour.GetProgramVariable(CompilerConstants.UsbTypeNameHeapKey));
 }
Example #21
0
        protected override void DrawProgramSourceGUI(UdonBehaviour udonBehaviour, ref bool dirty)
        {
            if (undoLabelStyle == null ||
                undoArrowDark == null ||
                undoArrowLight == null)
            {
                undoLabelStyle               = new GUIStyle(EditorStyles.label);
                undoLabelStyle.alignment     = TextAnchor.MiddleCenter;
                undoLabelStyle.padding       = new RectOffset(0, 0, 1, 0);
                undoLabelStyle.margin        = new RectOffset(0, 0, 0, 0);
                undoLabelStyle.border        = new RectOffset(0, 0, 0, 0);
                undoLabelStyle.stretchWidth  = false;
                undoLabelStyle.stretchHeight = false;

                undoArrowLight = new GUIContent((Texture)EditorGUIUtility.Load("Assets/UdonSharp/Editor/Resources/UndoArrowLight.png"), "Reset to default value");
                undoArrowDark  = new GUIContent((Texture)EditorGUIUtility.Load("Assets/UdonSharp/Editor/Resources/UndoArrowBlack.png"), "Reset to default value");
            }

            undoArrowContent = EditorGUIUtility.isProSkin ? undoArrowLight : undoArrowDark;

            currentBehaviour = udonBehaviour;

            EditorGUI.BeginChangeCheck();
            MonoScript newSourceCsScript = (MonoScript)EditorGUILayout.ObjectField("Source Script", sourceCsScript, typeof(MonoScript), false);

            if (EditorGUI.EndChangeCheck())
            {
                Undo.RecordObject(this, "Changed source C# script");
                sourceCsScript = newSourceCsScript;
                dirty          = true;
            }

            if (sourceCsScript == null)
            {
                if (DrawCreateScriptButton())
                {
                    dirty = true;
                }
                return;
            }

            object behaviourID           = null;
            bool   shouldUseRuntimeValue = EditorApplication.isPlaying && currentBehaviour != null;

            // UdonBehaviours won't have valid heap values unless they have been enabled once to run their initialization.
            // So we check against a value we know will exist to make sure we can use the heap variables.
            if (shouldUseRuntimeValue)
            {
                behaviourID = currentBehaviour.GetProgramVariable(behaviourIDHeapVarName);
                if (behaviourID == null)
                {
                    shouldUseRuntimeValue = false;
                }
            }

            // Just manually break the disabled scope in the UdonBehaviourEditor default drawing for now
            GUI.enabled            = GUI.enabled || shouldUseRuntimeValue;
            shouldUseRuntimeValue &= GUI.enabled;

            if (currentBehaviour != null && hasInteractEvent)
            {
                EditorGUILayout.Space();
                EditorGUILayout.LabelField("Interact", EditorStyles.boldLabel);
                currentBehaviour.interactText = EditorGUILayout.TextField("Interaction Text", currentBehaviour.interactText);
                currentBehaviour.proximity    = EditorGUILayout.Slider("Proximity", currentBehaviour.proximity, 0f, 100f);

                EditorGUI.BeginDisabledGroup(!EditorApplication.isPlaying);
                if (GUILayout.Button("Trigger Interact"))
                {
                    currentBehaviour.SendCustomEvent("_interact");
                }
                EditorGUI.EndDisabledGroup();
            }

            EditorGUILayout.Space();

            DrawPublicVariables(udonBehaviour, ref dirty);

            if (currentBehaviour != null && !shouldUseRuntimeValue && program != null)
            {
                string[] exportedSymbolNames = program.SymbolTable.GetExportedSymbols();

                foreach (string exportedSymbolName in exportedSymbolNames)
                {
                    bool foundValue = currentBehaviour.publicVariables.TryGetVariableValue(exportedSymbolName, out var variableValue);
                    bool foundType  = currentBehaviour.publicVariables.TryGetVariableType(exportedSymbolName, out var variableType);

                    // Remove this variable from the publicVariable list since UdonBehaviours set all null GameObjects, UdonBehaviours, and Transforms to the current behavior's equivalent object regardless of if it's marked as a `null` heap variable or `this`
                    // This default behavior is not the same as Unity, where the references are just left null. And more importantly, it assumes that the user has interacted with the inspector on that object at some point which cannot be guaranteed.
                    // Specifically, if the user adds some public variable to a class, and multiple objects in the scene reference the program asset,
                    //   the user will need to go through each of the objects' inspectors to make sure each UdonBehavior has its `publicVariables` variable populated by the inspector
                    if (foundValue && foundType &&
                        variableValue == null &&
                        (variableType == typeof(GameObject) || variableType == typeof(UdonBehaviour) || variableType == typeof(Transform)))
                    {
                        currentBehaviour.publicVariables.RemoveVariable(exportedSymbolName);
                    }
                }
            }

            DrawCompileErrorTextArea();
            DrawAssemblyErrorTextArea();

            EditorGUILayout.Space();

            if (GUILayout.Button("Force Compile Script"))
            {
                CompileCsProgram();
            }

            if (GUILayout.Button("Compile All UdonSharp Programs"))
            {
                CompileAllCsPrograms();
            }

            EditorGUILayout.Space();

            showExtraOptions = EditorGUILayout.Foldout(showExtraOptions, "Utilities");
            if (showExtraOptions)
            {
                if (GUILayout.Button("Export to Assembly Asset"))
                {
                    string savePath = EditorUtility.SaveFilePanelInProject("Assembly asset save location", Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(sourceCsScript)), "asset", "Choose a save location for the assembly asset");

                    if (savePath.Length > 0)
                    {
                        UdonSharpEditorUtility.UdonSharpProgramToAssemblyProgram(this, savePath);
                    }
                }
            }

            showProgramUasm = EditorGUILayout.Foldout(showProgramUasm, "Compiled C# Assembly");
            if (showProgramUasm)
            {
                DrawAssemblyTextArea(/*!Application.isPlaying*/ false, ref dirty);

                if (program != null)
                {
                    DrawProgramDisassembly();
                }
            }

            currentBehaviour = null;
        }
Example #22
0
        protected override void DrawProgramSourceGUI(UdonBehaviour udonBehaviour, ref bool dirty)
        {
            currentBehaviour = udonBehaviour;

            if (!udonBehaviour)
            {
                EditorGUI.BeginChangeCheck();
                MonoScript newSourceCsScript = (MonoScript)EditorGUILayout.ObjectField("Source Script", sourceCsScript, typeof(MonoScript), false);
                if (EditorGUI.EndChangeCheck())
                {
                    bool shouldReplace = true;

                    if (sourceCsScript != null)
                    {
                        shouldReplace = EditorUtility.DisplayDialog("Modifying script on program asset", "If you modify a script on a program asset while it is being used by objects in a scene it can cause issues. Are you sure you want to change the source script?", "Ok", "Cancel");
                    }

                    if (shouldReplace)
                    {
                        Undo.RecordObject(this, "Changed source C# script");
                        sourceCsScript = newSourceCsScript;
                        dirty          = true;
                    }
                }

                EditorGUI.BeginDisabledGroup(true);
                EditorGUILayout.ObjectField("Serialized Udon Program Asset", serializedUdonProgramAsset, typeof(AbstractSerializedUdonProgramAsset), false);
                EditorGUI.EndDisabledGroup();

                if (sourceCsScript == null)
                {
                    if (UdonSharpGUI.DrawCreateScriptButton(this))
                    {
                        dirty = true;
                    }
                    return;
                }
            }

            object behaviourID           = null;
            bool   shouldUseRuntimeValue = EditorApplication.isPlaying && currentBehaviour != null;

            // UdonBehaviours won't have valid heap values unless they have been enabled once to run their initialization.
            // So we check against a value we know will exist to make sure we can use the heap variables.
            if (shouldUseRuntimeValue)
            {
                behaviourID = currentBehaviour.GetProgramVariable(behaviourIDHeapVarName);
                if (behaviourID == null)
                {
                    shouldUseRuntimeValue = false;
                }
            }

            // Just manually break the disabled scope in the UdonBehaviourEditor default drawing for now
            GUI.enabled            = GUI.enabled || shouldUseRuntimeValue;
            shouldUseRuntimeValue &= GUI.enabled;

            DrawPublicVariables(udonBehaviour, ref dirty);

            if (currentBehaviour != null && !shouldUseRuntimeValue && program != null)
            {
                ImmutableArray <string> exportedSymbolNames = program.SymbolTable.GetExportedSymbols();

                foreach (string exportedSymbolName in exportedSymbolNames)
                {
                    bool foundValue = currentBehaviour.publicVariables.TryGetVariableValue(exportedSymbolName, out var variableValue);
                    bool foundType  = currentBehaviour.publicVariables.TryGetVariableType(exportedSymbolName, out var variableType);

                    // Remove this variable from the publicVariable list since UdonBehaviours set all null GameObjects, UdonBehaviours, and Transforms to the current behavior's equivalent object regardless of if it's marked as a `null` heap variable or `this`
                    // This default behavior is not the same as Unity, where the references are just left null. And more importantly, it assumes that the user has interacted with the inspector on that object at some point which cannot be guaranteed.
                    // Specifically, if the user adds some public variable to a class, and multiple objects in the scene reference the program asset,
                    //   the user will need to go through each of the objects' inspectors to make sure each UdonBehavior has its `publicVariables` variable populated by the inspector
                    if (foundValue && foundType &&
                        variableValue == null &&
                        (variableType == typeof(GameObject) || variableType == typeof(UdonBehaviour) || variableType == typeof(Transform)))
                    {
                        currentBehaviour.publicVariables.RemoveVariable(exportedSymbolName);
                    }
                }
            }

            DrawErrorTextAreas();
            UdonSharpGUI.DrawUtilities(udonBehaviour, this);

            currentBehaviour = null;
        }
        protected override void DrawProgramSourceGUI(UdonBehaviour udonBehaviour, ref bool dirty)
        {
            currentBehaviour = udonBehaviour;

            EditorGUI.BeginChangeCheck();
            MonoScript newSourceCsScript = (MonoScript)EditorGUILayout.ObjectField("Source Script", sourceCsScript, typeof(MonoScript), false);

            if (EditorGUI.EndChangeCheck())
            {
                Undo.RecordObject(this, "Changed source C# script");
                sourceCsScript = newSourceCsScript;
                dirty          = true;
            }

            if (sourceCsScript == null)
            {
                DrawCreateScriptButton();
                return;
            }

            object behaviourID           = null;
            bool   shouldUseRuntimeValue = EditorApplication.isPlaying && currentBehaviour != null;

            // UdonBehaviours won't have valid heap values unless they have been enabled once to run their initialization.
            // So we check against a value we know will exist to make sure we can use the heap variables.
            if (shouldUseRuntimeValue)
            {
                behaviourID = currentBehaviour.GetProgramVariable(behaviourIDHeapVarName);
                if (behaviourID == null)
                {
                    shouldUseRuntimeValue = false;
                }
            }

            // Just manually break the disabled scope in the UdonBehaviourEditor default drawing for now
            GUI.enabled            = GUI.enabled || shouldUseRuntimeValue;
            shouldUseRuntimeValue &= GUI.enabled;

            DrawPublicVariables(udonBehaviour, ref dirty);

            if (currentBehaviour != null && !shouldUseRuntimeValue && program != null)
            {
                string[] exportedSymbolNames = program.SymbolTable.GetExportedSymbols();

                foreach (string exportedSymbolName in exportedSymbolNames)
                {
                    bool foundValue = currentBehaviour.publicVariables.TryGetVariableValue(exportedSymbolName, out var variableValue);
                    bool foundType  = currentBehaviour.publicVariables.TryGetVariableType(exportedSymbolName, out var variableType);

                    // Remove this variable from the publicVariable list since UdonBehaviours set all null GameObjects, UdonBehaviours, and Transforms to the current behavior's equivalent object regardless of if it's marked as a `null` heap variable or `this`
                    // This default behavior is not the same as Unity, where the references are just left null. And more importantly, it assumes that the user has interacted with the inspector on that object at some point which cannot be guaranteed.
                    // Specifically, if the user adds some public variable to a class, and multiple objects in the scene reference the program asset,
                    //   the user will need to go through each of the objects' inspectors to make sure each UdonBehavior has its `publicVariables` variable populated by the inspector
                    if (foundValue && foundType &&
                        variableValue == null &&
                        (variableType == typeof(GameObject) || variableType == typeof(UdonBehaviour) || variableType == typeof(Transform)))
                    {
                        currentBehaviour.publicVariables.RemoveVariable(exportedSymbolName);
                    }
                }
            }

            DrawCompileErrorTextArea();
            DrawAssemblyErrorTextArea();

            EditorGUILayout.Space();

            if (GUILayout.Button("Force Compile Script"))
            {
                CompileCsProgram();
            }

            if (GUILayout.Button("Compile All UdonSharp Programs"))
            {
                CompileAllCsPrograms();
            }

            EditorGUILayout.Space();

            showProgramUasm = EditorGUILayout.Foldout(showProgramUasm, "Compiled C# Assembly");
            //EditorGUI.indentLevel++;
            if (showProgramUasm)
            {
                DrawAssemblyTextArea(/*!Application.isPlaying*/ false, ref dirty);

                if (program != null)
                {
                    DrawProgramDisassembly();
                }
            }
            //EditorGUI.indentLevel--;

            //base.RunProgramSourceEditor(publicVariables, ref dirty);

            currentBehaviour = null;
        }