ViroGameLoop
A headless node that fires per-frame JS callbacks from the native render loop. New in v2.56.0.
ViroGameLoop is a headless (zero-size) node that fires per-frame JS callbacks from the native render loop. Mount it anywhere inside a ViroARScene or ViroScene to start the loop; unmount it to stop.
Import
import { ViroGameLoop } from "@reactvision/react-viro";
Props
onUpdate
| Type | Description |
|---|---|
(e: ViroGameLoopUpdateEvent) => void | Fires every rendered frame. |
onLateUpdate
| Type | Description |
|---|---|
(e: ViroGameLoopUpdateEvent) => void | Fires after physics simulation. |
fixedHz
| Type | Description |
|---|---|
number | When set, enables the fixed-step accumulator. |
onFixedUpdate
| Type | Description |
|---|---|
(e: ViroGameLoopFixedEvent) => void | Fires at fixedHz regardless of render rate. Spiral-of-death protection: max 4 ticks per frame. |
Event types
type ViroGameLoopUpdateEvent = {
nativeEvent: { dt: number; elapsed: number };
};
type ViroGameLoopFixedEvent = {
nativeEvent: { dt: number };
};
Note:
dtandelapsedarrive as strings on Android due to New Architecture Fabric constraints and are parsed tonumberby the component wrapper automatically.
Hooks
import { useGameLoop, useLateUpdate, useFixedUpdate } from "@reactvision/react-viro";
useGameLoop((dt: number, elapsed: number) => void): void;
useLateUpdate((dt: number, elapsed: number) => void): void;
useFixedUpdate((dt: number) => void, fixedHz?: number): void;
Utility: bypass the reconciler
For high-frequency position/rotation updates, use these helpers to bypass the React reconciler:
import { ViroGameLoopUtils } from "@reactvision/react-viro";
ViroGameLoopUtils.setPosition(nodeRef, x, y, z);
ViroGameLoopUtils.setRotation(nodeRef, x, y, z);
ViroGameLoopUtils.setScale(nodeRef, x, y, z);
Example
import { ViroARScene, ViroBox, ViroGameLoop } from "@reactvision/react-viro";
import { useRef } from "react";
const boxRef = useRef(null);
<ViroARScene>
<ViroBox ref={boxRef} position={[0, 0, -1]} scale={[0.1, 0.1, 0.1]} />
<ViroGameLoop
fixedHz={30}
onFixedUpdate={({ nativeEvent: { dt } }) => {
// deterministic physics step
}}
onUpdate={({ nativeEvent: { dt } }) => {
// interpolate / render
}}
/>
</ViroARScene>;
Sharing state with the AR scene
ViroARSceneNavigator.initialScene.scene is evaluated once at mount — props passed to it later are ignored. To share state between the React UI (e.g. joystick callbacks) and the scene (game loop), use a module-level object:
// module-level — shared between render calls without React re-renders
const ctrl = { x: 0, y: 0, btn: false };
// Joystick (React UI, outside scene):
<ViroVirtualJoystick
onStickChange={(e) => { ctrl.x = e.nativeEvent.x; ctrl.y = e.nativeEvent.y; }}
/>
// Game loop (inside ViroARScene):
<ViroGameLoop
onUpdate={() => {
// ctrl.x, ctrl.y are always current
}}
/>
Architecture note
ViroGameLoop is registered as a native VROFrameListener. On iOS it is a VRTNode that registers via VROFrameSynchronizer; on Android it uses nativeCreate/nativeDestroy JNI. The fixed-step accumulator runs entirely in C++ — if the game loop is called at 60 fps with fixedHz=30, exactly two fixed ticks fire per frame.
Platform Support
| Platform | Supported |
|---|---|
| iOS | ✅ |
| Android | ✅ |
Updated 3 days ago