npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@alduino/coroutines

v0.1.0

Published

Generator-based coroutines ala Unity

Downloads

3

Readme

Coroutines

This is a coroutine library built on generators, and inspired by Unity's coroutines implementation.

It's based on the implementations in my experiments repo.

Features

Awaiters

In this library, an 'awaiter' is a value that, when yielded in a coroutine, causes the coroutine to wait until some condition passes. For example, it could be waiting until the user clicks with their mouse, or until some amount of time has passed.

coroutineManager.startCoroutine(function *handleMouse() {
    while (true) {
        yield waitUntil.mousePressed();
        console.log("clicked!");
    }
});

Technically, an awaiter is an object with a shouldContinue method, which is called every tick. Once this method returns {done: true}, control is passed back to the coroutine.

There are a few built-in awaiters that implement common tasks (for example, waitUntil.nextTick). Generally, however, awaiters are implemented by the framework that runs the coroutines, as they are generally framework-dependent.

Nesting

Nesting is very important in making reusable code so it's supported in this library. To nest, simply yield a generator function, which will cause the parent coroutine to wait until the child exits. If you want to have the function defined somewhere else and pass it parameters, create a wrapper that takes the parameters and returns a generator function.

Warning

You must yield a generator function, not the generator iterator.

For example, with function *generator() {}, you must yield generator, instead of yield generator(). The function is called internally.

coroutineManager.startCoroutine(function *handleMouse() {
    const {ctx} = yield waitUntil.mousePressed();
    
    // returns once the sparkles have gone away
    yield showSparkles(ctx.mouseX, ctx.mouseY);
});

function showSparkles(x, y) {
    return function*() {
        // ...
    };
}

Note

You can also yield a Coroutine instance, which allows you to set a custom identifier if needed. See the AwaiterCastable type for an up-to-date list of everything you can yield.

If you need to run multiple nested coroutines at the same time, there are a few helper functions to make this possible:

  • waitUntil.one([...awaiters]): Waits until any one of the awaiters completes, returning when it does. The data property in the result is the index of the awaiter that finished first.
  • waitUntil.all([...awaiters]): Waits until every awaiter has completed.
  • waitUntil.nest({...}): Lower-level nesting method allowing direct access to the result of each awaiter on each tick.

Note

A nested coroutine currently cannot return any data to the parent.

Focus targets

Focus targets are a crude form of focus control built in to this library, that function similar to a mutex. A focus target can be active or inactive, and only one focus target can be active at once. If a focus target is passed to an awaiter, the coroutine will only continue (and the awaiter will only run) either if there's no active focus target, or if the passed target is the currently active one.

Note

Set the require option when creating a focus target to only allow an awaiter to run while the focus target is specifically active, instead of also when there are no focus targets active.

coroutineManager.startCoroutine(function *handleMouse() {
	const focusTarget = coroutineManager.getFocusTargetManager().createFocusTarget();

	let focused = false;
    setInterval(() => {
        if (!focused) focusTarget.focus();
        else focusTarget.blur();
        focused = !focused;
    }, 5000);
    
    while (true) {
        yield waitUntil.mouseMoved({focusTarget});
        
        // will pause for 5s every 5s
        console.log("Mouse moved!");
    }
});

You can also create a focus target that combines multiple others, acting as active if any of the children are active:

const focusTargetManager = coroutineManager.getFocusTargetManager();

const focusTargetA = focusTargetManager.createFocusTarget();
const focusTargetB = focusTargetManager.createFocusTarget();

const combined = focusTargetManager.createCombinedFocusTarget([focusTargetA, focusTargetB]);

focusTargetA.focus(); // Combined counts as focused
focusTargetA.blur(); // Combined is no longer focused
focusTargetB.focus(); // Combined is focused again

Hooks

Some hooks are provided that allow you to do special things in a coroutine. To use a hook, simply call it and yield the result.

  • coroutineManager.hookDispose(callback): Yielding this will queue callback to be run when the coroutine stops.
  • coroutineManager.hookOptions(options): Sets default options for any subsequent awaiters in this coroutine (e.g. setting a focus target). Multiple calls shallow merge the value.

Quick-start

import {CoroutineController, waitUntil as builtInAwaiters} from "@alduino/coroutines";

// This class handles running each coroutine.
// The instance should be kept internal to your framework.
const coroutineController = new CoroutineController<FrameworkContext>();

// Super-duper quick per-frame ticks.
// The tick method goes through and runs all the waiting `shouldContinue` methods.
// The context value is passed as the `ctx` property on yield results.
setInterval(() => coroutineController.tick(getFrameworkContext()), 1000 / 60);

// This value should be exposed to the user so they can create coroutines.
const coroutineManager = coroutineController.getManager();

// Framework-dependent awaiters are also useful to provide for the user.
const waitUntil = {
    ...builtInAwaiters,
    mousePressed() { /* ... */ }
};

License

This library is licensed under the MIT License. See LICENSE for more info.