Unity Events Rewiring
TL;DR: Unity’s UnityEvent (used by Button.onClick, Slider.onValueChanged, CCK triggers, etc.) stores “persistent calls” as serialized data pointing at a specific Object + method name. At build time, any persistent call whose target is a WasmBehaviour is rewritten so the target becomes the replacement WasmRuntimeBehaviour and the method becomes one of a fixed set of trampolines that dispatch into WASM by method name.
The rewrite map
Section titled “The rewrite map”From WasmBuildProcessor.RewireMap:
Original arg type (m_Mode) | Setter method | Trigger method | Serialized arg field |
|---|---|---|---|
UnityEngine.Object | set__InternalEventObjectArg | TriggerScriptEventObject | m_ObjectArgument |
int | set__InternalEventIntArg | TriggerScriptEventInt | m_IntArgument |
float | set__InternalEventFloatArg | TriggerScriptEventFloat | m_FloatArgument |
string | set__InternalEventStringArg | TriggerScriptEventString | m_StringArgument |
bool | set__InternalEventBoolArg | TriggerScriptEventBool | m_BoolArgument |
m_Mode values (Unity’s PersistentListenerMode):
| Value | Name | Action |
|---|---|---|
| 0 | EventDefined | Use the event’s generic argument type. Rewritten to the matching setter+trigger pair, or to a void TriggerScriptEvent if no argument. |
| 1 | Void | No argument. Rewritten to a single TriggerScriptEvent("MethodName") call. |
| 2 | Object | Rewritten to set__InternalEventObjectArg + TriggerScriptEventObject. |
| 3 | Int | set__InternalEventIntArg + TriggerScriptEventInt. |
| 4 | Float | set__InternalEventFloatArg + TriggerScriptEventFloat. |
| 5 | String | set__InternalEventStringArg + TriggerScriptEventString. |
| 6 | Bool | set__InternalEventBoolArg + TriggerScriptEventBool. |
The two-call pattern
Section titled “The two-call pattern”For any mode that carries an argument, the build processor expands the single serialized call into two consecutive calls:
- Setter —
set__InternalEventXxxArg(value)stores the hard-coded or event-defined argument on the runtime behaviour. - Trigger —
TriggerScriptEventXxx("MethodName")dispatches to the guest, which pulls the stored arg and invokes the guest method.
The setter call’s mode encodes the original typed argument (2–6). The trigger call is always mode 5 (String) because the method-name string is the trigger’s serialized argument.
Hardcoded vs event-defined arguments
Section titled “Hardcoded vs event-defined arguments”- Hardcoded: an argument baked into the serialized persistent call (e.g. you typed
42in the inspector for an int event).isHardcoded = true; the setter receives that same baked value. - Event-defined: the argument comes from the runtime invocation (e.g.
Slider.onValueChanged(float)passes the current value).isHardcoded = false; the setter’s arg field is zeroed at build time — the runtime fills it.
m_TargetAssemblyTypeName
Section titled “m_TargetAssemblyTypeName”All rewritten calls set this to "WasmScripting.WasmRuntimeBehaviour, Assembly-CSharp". This is the fully-qualified name of the runtime behaviour host type; Unity uses it to resolve the method reflectively at runtime even when the original MonoScript no longer exists.
[ExternallyVisible] — required
Section titled “[ExternallyVisible] — required”The attribute WasmScripting.ExternallyVisibleAttribute (CVR.CCK.Wasm/Scripting/Attributes/ExternallyVisibleAttribute.cs) marks a method as callable from outside the WASM module — i.e. by the host via TriggerScriptEvent(methodName). Since Preview.11-WASM it’s the sole route for host-initiated name-based dispatch.
Every method you wire to a UnityEvent persistent listener (Button.onClick, Toggle.onValueChanged, Slider.onValueChanged, animation events, CVRInteractable, etc.) must be public and carry [ExternallyVisible].
using WasmScripting;
public partial class Panel : WasmBehaviour{ [ExternallyVisible] public void OnButtonClicked() { /* wired to Button.onClick in inspector */ }}What needs the attribute
Section titled “What needs the attribute”- Any method referenced by a persistent UnityEvent listener, even if the listener args mode is zero-arg (
UnityEngine.UI.Button.onClick). - Any method the host invokes by name —
CVRInteractablemethod calls,CVRPointerevents routed throughTriggerScriptEvent, animation event callbacks, etc.
What doesn’t
Section titled “What doesn’t”- Unity lifecycle methods (
Start,Update,LateUpdate,FixedUpdate,OnEnable,OnDisable,OnDestroy, theOnTrigger*/OnCollision*family). Found by enum-name scan inWasmBuildProcessor.ScanForUnityEvents. - Game events (
OnPlayerJoined,OnPlayerLeft,OnPlayerRespawned,OnPropSpawned/Despawned,OnPlayerTriggerEnter/Stay/Exit,OnPlayerCollisionEnter/Stay/Exit,OnPropTrigger*,OnPropCollision*,OnPortalCreated/Destroyed,OnInstanceOwnerChange,OnInputReady,OnWorldPermissionsChanged). Found byScanForGameEvents. - Delegate subscriptions (e.g.
Networking.OnReceiveMessage += HandleMessage). The host calls these through the delegate pointer, not by name, so there’s no dispatcher lookup. - Direct same-VM calls between behaviours (
other.RunMethod()). Plain C# — no host boundary crossed.
Failure mode
Section titled “Failure mode”Omitting the attribute on a UnityEvent-targeted method produces no editor error and no build error. The rewire step still replaces the listener with TriggerScriptEvent("YourMethod"), but the guest-side dispatcher generated by the CCK toolchain has no entry for that name, so the call silently no-ops at runtime. Buttons appear wired but do nothing.
Supported signatures
Section titled “Supported signatures”Only single-argument methods are rewireable:
public partial class Panel : WasmBehaviour{ public void OnButtonClicked() { } // Void mode public void OnSliderChanged(float v) { } // Float mode public void OnNameEntered(string name) { } // String mode public void OnToggled(bool on) { } // Bool mode public void OnCountSet(int n) { } // Int mode public void OnObjectPicked(UnityEngine.Object o) { } // Object mode}Multi-argument methods can’t be targeted by Unity’s persistent call system in the first place — call them from a single-arg handler that wraps the payload.
Integration in the build flow
Section titled “Integration in the build flow”Inside WasmBuildProcessor.BuildScripts:
- Replace
WasmBehaviourwithWasmRuntimeBehaviourand remember the instance ID mapping. RemapWasmBehaviourReferences(...)— for any serialized object reference that pointed at the old behaviour, redirect to the new runtime behaviour.RewireUnityEvents(...)— recursively scan every Component forSerializedPropertyentries of typePersistentCall, detect ones targeting a replaced instance ID, and rewrite per the table above.
Debug logging
Section titled “Debug logging”The build processor emits log lines like:
[RerouteUnityEvents] -> Step 1: Panel.set__InternalEventIntArg[RerouteUnityEvents] -> Step 2: Panel.TriggerScriptEventInt("OnCountSet")Check the Unity console after a CCK build if you suspect a persistent call didn’t get rewired.
What to avoid
Section titled “What to avoid”- Dynamic
UnityEvent.AddListener(lambda)— those aren’t persistent calls; they live at C# runtime only. The WASM runtime behaviour has no equivalent. Use persistent calls via the inspector, or implement your own event dispatch inside the guest. - Multi-arg methods from UI — not rewireable.
- Non-serializable argument types (e.g. a user struct) — rewrite table only covers the five Unity-serializable primitives +
Object.
Source
Section titled “Source”CVR.CCK.Wasm/CCK/Editor/WasmBuildProcessor.cs—RewireUnityEvents,RecursiveRewire,RewireCall,RewireVoidTarget,RewireEventDefinedTarget, and theRewireMapdictionary that drives the mode → setter/trigger pair.CVR-GameFiles/WasmScripting/WasmRuntimeBehaviour.cs— the_InternalEventXxxArgsetters andTriggerScriptEvent/TriggerScriptEvent{String,Int,Float,Bool,Object}trampolines the rewire points at.CVR.CCK.Wasm/Scripting/Attributes/ExternallyVisibleAttribute.cs— the attribute the guest-side dispatcher looks for.
Related
Section titled “Related”- Events — canonical Unity/game events the build processor scans for (different mechanism).
- Runtime Lifecycle — the
scripting_call_trigger_script_event*exports that receive these. - Authoring — recommended method signatures on your behaviour.