Options
All
  • Public
  • Public/Protected
  • All
Menu

ajeeb-coroutines

Ajeeb Coroutines

Unity-inspired Coroutines for the browser and nodejs.

Ajeeb Coroutines are a TypeScript implementation of a similar idea from the Unity 3D engine. They use ES6 generators to turn code that reads synchronously into code that runs across multiple frames. They were designed for the Ajeeb Game Engine but have no dependencies and can be used anywhere.

Installation

npm install nasser/ajeeb-coroutines

Or in a browser you can link to the CDN, using either legacy script tags

<script src="https://cdn.jsdelivr.net/gh/nasser/ajeeb-coroutines@master/build/coroutines.iife.js"></script>

or ES6 modules

<script type="module">
import * as coro from "https://cdn.jsdelivr.net/gh/nasser/ajeeb-coroutines@master/build/coroutines.esm.js"
</script>

Usage

An instance of an ES6 generator is treated as a coroutine. An instance of the Schedule class schedules and runs coroutines.

Schedule.add adds a couroutine to the collection. Schedule.tick runs every coroutine in the collection up to their next yield statement. Each coroutine remembers the last yield they reached, and the next time Schedule.tick is called they resume execution from that point.

import { Schedule } from "ajeeb-coroutines"

let sched = new Schedule()

sched.add(function* () {
    console.log("hello")    // prints "hello"
    yield;                  // waits until next tick
    console.log("world")  
    console.log("how")      // prints "world" and "how"
    yield; yield;           // waits for two ticks
    console.log("are you?") // prints "are you?"
})

sched.tick()
// prints "hello"
sched.tick()
// prints "world"
// prints "how"
sched.tick()
// does nothing
sched.tick()
// prints "are you?"
// further calls to sched.tick() will do nothing

Coroutines are designed to run across multiple frames. tick can be scheduled to run regularly setInterval or requestAnimationFrame in a browser. This advances every coroutine in the collection automatically once per frame. When scheduled like this, you can think of yield as a way of "waiting one frame".

import { Schedule } from "ajeeb-coroutines"

let sched = new Schedule()

sched.add(function* () {
    console.log("hello")    // prints "hello" and waits one frame
    yield;
    console.log("world")    // prints "world" and waits two frames
    yield; yield;
    console.log("how")      // prints "how" and waits three frames
    yield; yield; yield;
    console.log("are")      // prints "are" and waits four frames
    yield; yield; yield; yield;
    console.log("you?")     // prints "you?"
})

setInterval(() => sched.tick(), 1000 / 60) // tick 60 times a second

Generators are normal JavaScript functions. They have access to local variables, closures, arguments, and more. yield can appear anywhere.

import { Schedule } from "ajeeb-coroutines"

let sched = new Schedule()

function* hello(repeat) {
    console.log("hello")    // prints "hello" and waits one frame
    yield;
    for(let i=0; i<repeat; i++) { 
        console.log("world")    // prints "world" and wait one frame
        yield;                  // execution will resume from this point 
    }
    console.log("!")     // prints "!"
}

sched.add(hello)

setInterval(() => sched.tick(), 1000 / 60) // tick 60 times a second

This library also exports a number of generically useful coroutines, like wait, that can be combined with your own in powerful ways. A coroutine can be made to wait for another coroutine with the yield* statement. If yield is read as "wait one frame" yield* is read as "wait until this other coroutine completes"

import { Schedule, wait } from "ajeeb-coroutines"

let sched = new Schedule()

// schedule coroutines to tick every frame
// in the browser
function runCoroutines() {
    requestAnimationFrame( runCoroutines )
    sched.tick()
}
runCoroutines()

// in node
setInterval(() => sched.tick(), 1000/60)

// you can add coroutines afterwards
sched.add(function* () {
    console.log("hello")  // prints "hello"
    yield* wait(2)        // wait for 2 seconds
    console.log("world")  // prints "world"
    yield* wait(3)        // waits 3 seconds
    console.log("!!!")    // prints "!!!"
})

// prints "hello" immediately 
// waits 2 seconds
// prints "world" 
// waits 3 seconds
// prints "!!!" 

Coroutines can be treated as normal functions. They take arguments, and can be as complex as needed.

import fs from "fs"
import { Schedule, waitUntil } from "ajeeb-coroutines"

let sched = new Schedule()

function* waitForFile(path) {
    console.log("waiting for", path);
    yield* waitUntil(() => fs.existsSync(path))
    console.log("ok");
}

sched.add(waitForFile("nice.txt"))

// in the browser
function runCoroutines() {
    requestAnimationFrame( runCoroutines )
    sched.tick()
}
runCoroutines()

// in node
setInterval(() => sched.tick(), 1000/60)

Generated using TypeDoc