What's the best way to emulate coroutines before version 5.0?

I’m trying to lerp a mesh programmatically, from one point to another. Most importantly, I need to be able to chain these lerps, such that movement from B to C starts after A to B has finished.

In Unity, I’d do this with coroutines, as folows.

IEnumerator Lerp(Transform target, Vector3 start, Vector3 end, float duration)
{
    float elapsed = 0f;

    while (elapsed < duration)
    {
        elapsed += Time.deltaTime;
        target.position = Vector3.Lerp(start, end, elapsed / duration);
        yield return null;
    }
}

IEnumerator LerpThroughPositions(Transform target, Vector3[] positions, float totalDuration)
{
    float individualDuration = totalDuration / positions.Count;

    // The code below will chain the individual lerps one after another.
    foreach (var position in positions)
    {
        var lerp = Lerp(target, target.position, position, individualDuration);
        yield return StartCoroutine(lerp);
    }
}

Unfortunately, my version of Babylon—4.2—doesn’t have the spiffy new coroutines yet, so I’m trying to figure out a workaround.

In Unity, there exists an alternative workflow: that of async/await. You replace yield return null with await Task.Yield() and yield return StartCoroutine(Lerp()) with await Lerp(). Given the popularity of async/await in the world of JavaScript, I was hoping for a similar alternative here.

Sadly, I haven’t been able to figure out how to replicate await Task.Yield(). I need each “step” of my lerp function to run in each frame, but I don’t know how to do that. The closest I’ve managed is to register it as a callback for scene.onBeforeRenderObservable:

private static LerpPosition(
  target: IPositionable, // Any object with a position member variable.
  start: Vector3,
  end: Vector3,
  duration: number,
  scene: Scene
) {
  const timer = new Timer(duration); // A custom implementation.
  const lerp = () => {
    const deltaTime = scene.deltaTime;
    if (!deltaTime) return; // I do this check because, sometimes, scene.deltaTime is undefined, and that could lead to NaN propagation.

    timer.Increment(deltaTime);
    target.position = Vector3.Lerp(start, end, timer.elapsed / duration);
  };

  scene.onBeforeRenderObservable.add(lerp);
  timer.onComplete.addOnce(() =>
    scene.onBeforeRenderObservable.removeCallback(lerp)
  );
}

This works fine for a single lerp (probably; I haven’t tested it :sweat_smile:), but I don’t know how to chain multiple ones together. It may be possible to start a new lerp as a result of the timer.onComplete notification, but that’s a recursive solution so clunky it stinks like rotten fish.

Is there not a simpler, more idiomatic way?

You can always add the coroutine implementation programatically to your codebase and get ready for babylon 5.0, or simply start using 5.0.0-beta.7, which is stable and tested.

To add coroutines to your code you will need to add the content of this file to your codebase:

and import from this module when you need to run a coroutine. This won’t add anything to the Observable, so this is also needed (with the right references of the last module) in order to work:

Then everything in this page will work:

CC @syntheticmagus in case he wants to suggest a different approach

** EDIT **

This is by no means a recommendation to do add these files to your code. This is just one way to achieve that in 4.2.1. My recommendation is to upgrade to 5.0.0-beta.X

1 Like

Thanks, Ranaan! That’s definitely an option.

However, I figured out an “in-house” solution (in the sense that it requires adding nothing new or external):

private static async LerpPositions(
    target: IPositionable,
    positions: BABYLON.Vector3[],
    duration: number,
    scene: BABYLON.Scene
) {
    const individualDuration = duration / positions.length;
    for (let position of positions) {
        await this.LerpPosition(target, target.position, position, individualDuration, scene);
    }
}

private static async LerpPosition(
    target: IPositionable,
    start: BABYLON.Vector3,
    end: BABYLON.Vector3,
    duration: number,
    scene: BABYLON.Scene
): Promise<void> {
    return new Promise<void>((resolve, _reject) => {
        const timer = new Timer(duration, false);
        const lerp = () => {
            const deltaTime = scene.deltaTime;
            if (!deltaTime) return;

            timer.Increment(deltaTime);
            target.position = BABYLON.Vector3.Lerp(start, end, timer.elapsed / duration);
        };

        scene.onBeforeRenderObservable.add(lerp);
        timer.onComplete.addOnce(() => {
            scene.onBeforeRenderObservable.removeCallback(lerp);
            resolve();
        })
    });
}

You can see it in action over at the Playground: LerpTestWithoutCoroutines | Babylon.js Playground (babylonjs.com).

Is this the best way to do it? Probably not. I’m no good at JavaScript. I’m sure the code be improved to be cleaner and more elegant. But it works, at least, and to anyone who has something to add to it: please, feel free!


P.S. What’s the recommended route for upgrading to 5.0 in an NPM environment? Do I simply edit my package.json, or should I clone the relevant branch from the GitHub repo?

Yep, just set the @babylonjs/core (and all other babylon dependencies) to the latest 5.0.0 version, which is 5.0.0-beta.7

As long as it’s working :slight_smile:

2 Likes