Architecture
OneJS connects JavaScript to Unity through several layers.
Architecture Overview
┌─────────────────────────────────────────────────┐
│ Your React Code │
├─────────────────────────────────────────────────┤
│ onejs-react Reconciler │
├─────────────────────────────────────────────────┤
│ QuickJS JavaScript Engine │
├─────────────────────────────────────────────────┤
│ QuickJSUIBridge (Events, Scheduling) │
├─────────────────────────────────────────────────┤
│ UI Toolkit Elements │
└─────────────────────────────────────────────────┘The JavaScript Engine
OneJS uses QuickJS, a small embeddable JavaScript engine. It runs your code in an isolated context with:
- Full ES2020 support
- Async/await and Promises
- Modules (ESM)
The React Reconciler
onejs-react is a custom React reconciler. When you write:
<View style={{ padding: 20 }}>
<Label text="Hello" />
</View>The reconciler:
- Creates a
VisualElementfor the View - Creates a
Labelelement - Sets the style properties
- Adds the Label as a child of the View
Component Mapping
| React Component | UI Toolkit Element |
|---|---|
<View> | VisualElement |
<Text> | TextElement |
<Label> | Label |
<Button> | Button |
<TextField> | TextField |
<Toggle> | Toggle |
<Slider> | Slider |
<ScrollView> | ScrollView |
<Image> | Image |
<ListView> | ListView |
| Raw text | TextElement |
<View>Hello</View>) automatically create TextElement instances. Use <Text> for primary text display and <Label> for form labels.
C# Interop
The CS global proxy uses reflection to access C# types:
CS.UnityEngine.Debug.Log("Hello")This resolves to:
- Look up the
UnityEngine.Debugtype - Find the
Logmethod - Convert arguments to C# types
- Invoke the method
- Convert the return value back to JavaScript
Event System
UI Toolkit events are captured and routed to JavaScript:
- C# registers a TrickleDown callback on the root element
- When an event fires, it's serialized to JSON
__dispatchEvent(handle, eventType, data)is called- JavaScript looks up handlers and invokes them
UI Event → C# Capture → JSON Serialize → JS Dispatch → Your HandlerScheduling
OneJS provides familiar timing APIs:
requestAnimationFrame(): Called every Unity UpdatesetTimeout()/setInterval(): Timer queue processed each framequeueMicrotask(): For Promise resolution
Tick() method drives all scheduling from Unity's Update loop.
Memory Management
C# objects referenced from JavaScript are tracked with integer handles:
- C# object → Handle registered in table
- JavaScript proxy wraps the handle
- When JS object is garbage collected → FinalizationRegistry releases handle
- C# object becomes eligible for GC
releaseObject() but rarely needed.
WebGL Differences
On WebGL, JavaScript runs in the browser's V8/SpiderMonkey engine:
- JIT compilation (faster execution)
- Browser's native RAF loop
- No QuickJS overhead