Looking to dig a little deeper? Want to try building VRChat Udon out of assembly, or even build your own compiler? Here’s some first steps you should look into.
IMPORTANT NOTE: If you’re just getting started with Udon, this probably isn’t what you want! Check out our thread on Getting Started with Udon.
How Udon Works
Udon has many layers: a nodegraph, which compiles down to UAssembly, which assembles down to bytecode, which gets run by the UdonVM.
In addition to building graphs, users can also write in assembly directly.
Writing UAssembly Quickstart
- Create a new scene
- Add a new gameobject to your scene.
- Add an UdonBehaviour component to the gameobject.
- Set the dropdown to “UdonAssemblyProgramAsset”
- Click the “New Program” button on the UdonBehaviour.
- This will create an assembly program in your assets folder, instead of a graph program.
- Type or paste your assembly into the text field, and it’ll automatically compile and load any public variable fields you’ve defined.
- You can also create assembly by dropping a text file with the extension “.uasm” into your assets folder, it’ll automatically get imported as an assembly program.
UAssembly Instruction Set
- Does nothing
- Pushes an address onto the stack
- Pops an address off the stack
- Pops an address off the stack, and gets a bool ‘jumpCondition’ from the heap at that address.
- if(jumpCondition) , sets the program counter to an address
- else , does nothing
- sets the program counter to an address
- Gets a method matching this signature (internally the assembler maps an address to this signature and stores the string value on the heap, which the vm fetches and does some caching on to retrieve the actual method signature)
- Pops off as many addresses from the stack as there are parameters for the method
- Invokes the method with the parameters from the heap
- Does nothing
- Pops an address off the stack, and gets a uint ‘jumpAddress’ from the heap at that address.
- Sets the program counter to jumpAddress
- Pops two addresses off the stack, copies reference value from the first to the second on the heap
Learn X in Y where X = UAssembly
Note: This is incomplete! It is missing more advanced symbols and sync syntax.
# Comments are denoted with a '#' # Everything that occurs after a '#' will be ignored by the assembler's lexer. # Programs typically contain .data_ and .code_ sections .data_start # Section where variables get defined and set .export Target # Set a variable as public, this exposes it # in the udonBehaviour angle_0: %SystemSingle, null # Define variables with this syntax: # name: $TypeString, defaultValue axis_0: %UnityEngineVector3, null # Note: currently, the only # default values you can set from assembly are null (will #set to default(type) if a value type), or `this` instance_0: %UnityEngineTransform, this # Use `this` on #transforms, gameobjects, and udonBehaviours to set the default #reference to the object that your program is attached to Target: %UnityEngineTransform, this axisName_0: %SystemString, null Single_0: %SystemSingle, null .data_end # End your datablock like so .code_start # Section where VM instructions are defined .export _update # Export an event to expose it to the #UdonBehaviour. Events like update and start get called #automatically if named with the _eventName syntax, but you #can also define and call custom events with any name _update: # Start of the instruction block for a given event PUSH, Target # Push a variable onto the stack. You can also use a #raw address pointer PUSH, instance_0 COPY # Copy takes the last two items on the stack, and copies the #reference value from the first to the 2nd PUSH, axisName_0 #Push variables onto the stack for a method call PUSH, Single_0 #in the order that the signature defines # The order is “instance, properties, return type” EXTERN, "UnityEngineInput.__GetAxis__SystemString__SystemSingle" # Extern calls a method from the UdonWrapper with a matching #signature, using the values that were pushed onto the stack PUSH, instance_0 PUSH, axis_0 PUSH, Single_0 EXTERN, "UnityEngineTransform.__Rotate__UnityEngineVector3_SystemSingle__SystemVoid" JUMP, 0xFFFFFF # You can jump to any address, which is how you do #loops and such. It’s convention to jump to max address when #your instruction block is done, to avoid running into #subsequent code blocks .code_end # End your instruction section like so
So you want to write a compiler?
VRChat Udon natively supports custom program types, with hooks for displaying custom UI in the inspector and for loading a program from any valid source.
If you’re interested in building a custom program source for your favorite language, take a look at
UdonBehaviours can load any ScriptableObject that inherits from
AbstractUdonProgramSource, although for most applications, you’ll want to just inherit from
UdonAssemblyProgramAsset, and build upon that.
The simplest way to get going is to override
RefreshProgram, and insert a custom compilation step before
AssembleProgram, just like
FYI, your custom program source will automatically appear as an option in the UdonBehaviour’s “Create New Program” dropdown.