[Feature] Promise based camera movement and concurrent calls handling
Is your feature request related to a problem? Please describe.
I recently started using it to showcase a network, and certain buttons trigger an animation. If we need to perform actions on Animation end, it's unreliable to start timeout with durationMs delay as the cameraPosition() call can get overridden by another call (user pressing another button). Hence we would be performing the promise resolution of the first cameraPosition() call without it being resolved in the end.
Describe the solution you'd like I have fixed this locally by wrapping camera position inside a Promise based framework with a token generated for each call.
- When call occurs to
cameraMovementWrapper, a token is generated and stored in a state and acameraPositioncall occurs. We create a promise, store its reference in the component and return it. - A recursive resolution is called with a loop on animation frames inside the Promise body. If the camera is close to its final position , transition period expired or the associated token does not match the stored token inside the component, the Promise is resolved. We resolve with success if the token matches at Resolution time and reject if token does not match.
- From the user perspective, we get a promise resolution and can call subsequent actions or the promise chain is broken and error can be caught.
Note Let me know if you're interested and accepting contributions. I can submit a PR with the solution you prefer.
Describe alternatives you've considered Modifying this library, we can use a resolution on the Tween completion, and maybe use a token based approach to camera movement requests and break the promise if another request is performed.
Additional context Sample code (I work with force-graph-3d)
function moveCameraWithPromise(graph, pos, lookAt, transitionMs, token, tolerance = 2) {
graph.cameraPosition(pos, lookAt, transitionMs);
const startTime = performance.now();
return new Promise((resolve, reject) => {
const check = () => {
// Invalidate if a new camera action has been requested.
if (token !== cameraAnimationTokenRef.current) {
reject(new Error('Cancelled'));
return;
}
const elapsed = performance.now() - startTime;
const camPos = graph.camera().position;
const dx = camPos.x - pos.x;
const dy = camPos.y - pos.y;
const dz = camPos.z - pos.z;
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (distance < tolerance || elapsed >= transitionMs) {
resolve();
} else {
requestAnimationFrame(check);
}
};
requestAnimationFrame(check);
});
}
@xwkya thanks for reaching out.
In order to avoid adding too much complexity in this module, I would suggest that you can achieve the same thing on your app side. You don't need to call cameraPosition with a timed animation. You can run your own tween and have this promise system all in your app side. At every tick of the tween you simply invoke cameraPosition with 0 transition time (the default), which effectively disables the animation and moves the camera immediately to the final position.