Skip to content

Serialization

TL;DR: A WasmBehaviour’s state is serialized during build (for baking initial values set in the Unity inspector) and deserialized into the WASM guest at instantiation. Public fields of supported types serialize by default. Use [WasmSerialized] to include private fields or custom classes, [NonWasmSerialized] to exclude a public field.

Without any attribute, a field is serialized if all of the following are true:

  • It is a public instance field on a WasmBehaviour subclass (or a chain of classes that are themselves serialization-eligible).
  • Its type is one of the built-in supported shapes below.

Supported types (from the WasmSerializedAttribute doc comment):

CategoryTypes
C# primitivesbool, byte, sbyte, short, ushort, int, uint, long, ulong, float, double, decimal, char, string
Unity objectsUnityEngine.Object subclasses (GameObject, Component, ScriptableObject, assets)
ContainersT[], IList<T>, IDictionary<K,V> where T/K/V are themselves serializable

Unity-specific structs (Vector3, Quaternion, Color, Bounds, …) are supported via the Unity type path as long as the type is bound into the WASM surface — which the standard ones are.

Source: CVR.CCK.Wasm/Scripting/Attributes/SerializationAttributes.cs.

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class)]
public class WasmSerializedAttribute : Attribute {} // include
[AttributeUsage(AttributeTargets.Field)]
public class NonWasmSerializedAttribute : Attribute {} // exclude

Opts a non-public field into serialization.

public partial class LapState : WasmBehaviour
{
[WasmSerialized] private int bestLapMs = int.MaxValue;
}

Required for any custom (non-Unity) class whose fields you want serialized. Without it, the outer serializer visits the reference but skips its contents entirely.

[WasmSerialized]
public class Checkpoint
{
public int index;
public Vector3 position;
}
public partial class Track : WasmBehaviour
{
public Checkpoint[] checkpoints; // array of a WasmSerialized class -> deep serialized
}

Suppresses serialization for transient UI/debug state.

public partial class Hud : WasmBehaviour
{
public int score;
[NonWasmSerialized] public float debugAnimatedValue; // not baked
}
  1. Build timeWasmBuildProcessor.BuildScripts calls WasmSerializer.SerializeBehaviour(component, eventContext) on each in-scene WasmBehaviour, capturing the inspector state as a WasmSerializedObject (references[] + a blob). This is stored on the replacement WasmRuntimeBehaviour.serializationData.
  2. Runtime — on VM setup, for each behaviour:
    • storeData.AccessManager.ToWrapped(behaviour, ...) → a WASM handle.
    • MarshalSerializedObject(storeData) copies the serialized blob into guest memory and returns a pointer.
    • _deserializeInstance(handle, ptr) asks the guest to reconstruct its field state.

Because the blob also carries references[] (an array of Unity object references), GameObject/Component fields stay live across the build→runtime boundary.

  • Fields of types not in the supported list and without [WasmSerialized] — always null after deserialization.
  • Local variables in methods — they only exist at runtime.
  • Static fields — not per-instance, treated as global guest state (persist across behaviours but not across VM restarts).
  • Delegates / function pointers / raw native pointers.

Persistent calls added via the Unity inspector (e.g. Button.onClick) are rewritten at build time to call WasmRuntimeBehaviour.TriggerScriptEvent*(...). The original m_Arguments/m_Mode serialized state is preserved — the runtime just reroutes the target. See Unity Events Rewiring.

The marker interface IWasmSerialized (in both CVR.CCK.Wasm/Scripting/IWasmSerialized.cs and CVR-GameFiles/WasmScripting/IWasmSerialized.cs) lets the serializer identify participants. WasmBehaviour implements it implicitly.

  • Put anything you want to show in the inspector on a public field. It both persists and edits cleanly.
  • Private helpers you need to keep across play sessions: [WasmSerialized].
  • Caches / UI state you rebuild in Start or OnEnable: [NonWasmSerialized] on public, or just keep private without [WasmSerialized].
  • Custom data types: mark the class with [WasmSerialized]. Fields follow the normal public/attribute rule.
  • Authoring — where these attributes fit in the normal workflow.
  • Runtime Lifecycle_deserializeInstance call path.
  • EventsOnBeforeSerialize / OnAfterDeserialize hooks if you need to transform state.