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

TypeDescription
(e: ViroGameLoopUpdateEvent) => voidFires every rendered frame.

onLateUpdate

TypeDescription
(e: ViroGameLoopUpdateEvent) => voidFires after physics simulation.

fixedHz

TypeDescription
numberWhen set, enables the fixed-step accumulator.

onFixedUpdate

TypeDescription
(e: ViroGameLoopFixedEvent) => voidFires 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: dt and elapsed arrive as strings on Android due to New Architecture Fabric constraints and are parsed to number by 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

PlatformSupported
iOS
Android