Search code examples
animationswiftuirealitykitvisionos

RealityKit scene doesn't call System update() unless I move the volume


Okay, I'm missing something fundamental here. I want to create a scene for visionOS with a continuously rotating cube. I set up my scene and register a System to do the rotation updates.

When I run on the simulator or headset, the first 20 updates happen pretty quickly, then slow to a stop, and the cube stops rotating. If I grab the volume handle and move the volume around, the scene updates and the cube rotates as long as I'm moving it.

How do I get the cube to rotate continuously without interacting with the volume?

import SwiftUI
import RealityKit

struct ContentView: View {
    var body: some View {
        VStack {
            RealityView { content in
                // Create a shiny red cube entity
                let cube = MeshResource.generateBox(size: [0.3, 0.3, 0.3])
                let matl = SimpleMaterial(color: .red, isMetallic: false)
                let cubeEntity = ModelEntity(mesh: cube, materials: [matl])
                cubeEntity.name = "cube"
                content.add(cubeEntity)

                // Register the "SpinSystem" system to rotate it
                SpinSystem.registerSystem()
            }
        }
    }
}

// A System to incrementally spin the cube every frame
class SpinSystem : System {
    var theta: Float = 0
    var count: Int = 0
    
    required init(scene: RealityFoundation.Scene) {}

    func update(context: SceneUpdateContext) {
        count += 1
        print("update \(count)")
        if let cubeEntity = context.scene.findEntity(named: "cube") {
            theta += 0.01
            cubeEntity.transform.rotation = simd_quatf(angle: theta, axis: simd_float3(1, 0, 0))
        }
    }
}

Screenshot of the Rotating Cube in VisionPro simulator

update 1
update 2
cannot add handler to 0 from 1 - dropping
update 3
update 4
update 5
update 6
update 7
cannot add handler to 0 from 1 - dropping
update 8
update 9
...
update 21
update 22
update 23

Solution

  • Here’s some insight from various back channels:

    The problem is that registering a System to get called on every update is not enough to make sure those updates ever happen.

    It's interesting that this is not a problem for a RealityView on macOS or an ARView on iOS - only visionOS does not continually update.

    Something else is necessary to force updates to run indefinitely.

    One option is to create an animation that runs forever. Inserting this code at the end of the RealityView code block (after the SpinSystem.registerSystem() call in the example above) will do it. It adds an animation that sets the cubeEntity transform to the identity every frame forever. (Note that that transform gets set to something else in the SpinSystem.update() every frame, so the cube does still rotate.)

    let animDef = FromToByAnimation(to: Transform.identity,
                                    duration: .infinity,
                                    bindTarget: .transform)
    let animRes = try! AnimationResource.generate(with: animDef)
    cubeEntity.playAnimation(animRes)
    

    Another approach is to subscribe to the Update event in the RealityView content in the RealityView code block. It’s not actually necessary to do anything in the handler, just subscribing seems to be enough.

    _ = content.subscribe(to: SceneEvents.Update.self) { _ in }