DEV Community

Darcy Rayner
Darcy Rayner

Posted on • Edited on

Introducing Colib 🎉

Hey all! I'm writing this post to announce the v1 release of Colib, (short for Command Library), to the wild. Colib is a set of patterns we've been using for years to help build games and interactive media, beginning with a C# Unity Version, and now a new Typescript/Web implementation.

What is Colib?

Colib makes it easy to build complex sequences of timed logic and animations, without ending up in callback hell. At it's core, Colib is a libary of Commands that take time, and a set of tools to schedule those Commands.

An introductory example

Before we define just exactly what a Command is, let's look at an example of what you can do with Colib.

import { globalScheduler, waitForTime, parallel, changeTo, coroutine } from 'colib';

const scheduler = globalScheduler();
const obj = { x: 10, y: 100 };
scheduler.add(
  waitForTime(4.0),
  parallel(
    changeTo(obj, { y: 200 }, 3.0, inOutQuad()),
    sequence(
      waitForTime(1.5), () => {
      console.log('Reached');
    })
  ),
  coroutine(aCoroutine)
);

function *aCoroutine() {
  console.log("Starting Coroutine");
  for (let i = 0; i < 3; ++i) {
    yield waitForTime(0.5);
  }
  console.log("Finished Coroutine");
}

Here we see a couple of Commands used, sequence, parallel, waitForTime, changeTo, and coroutine . Let's break down the whole thing step by step.

Breakdown

scheduler.add(
    //...
)

The global scheduler used here will run the commands. Commands don't do anything until something runs them.

waitForTime(4)

Waits 4 seconds, before execution continues onto the next Command.

parallel(
    //...
)

Executes all its child commands at the same time.

changeTo(obj, {y: 200}, 3.0, inOutQuad())

Tweens the value of obj.y to 200, over a duration of 3 seconds, using the inOutQuad() ease.

sequence(
    //...
)

Executes all its child commands one after the other. Because this sequence is nested inside a parallel command, the entire sequence is executing at the same time as the changeTo command.

waitForTime(1.5)

Waits 1.5 seconds, before execution continues onto the next command.

() => { console.log('Reached') }

A callback command that logs "Reached" to the console.

coroutine(aCoroutine)
//...
function *aCoroutine() {
  console.log("Starting Coroutine");
  for (let i = 0; i < 3; ++i) {
    yield waitForTime(0.5);
  }
  console.log("Finished Coroutine");
}

This launches a coroutine. Basically coroutines are ES6 generators, where you can yield any Command. That command will complete before execution of the function resumes. In this example, aCoroutine logs 'Started Coroutine', waits for 0.5 seconds three times in a row, then logs 'Finished Coroutine'.

Commands

So now we've seen a few commands in action, but what are they? Commands are functions that can consume time, and return whether they are completed or not. Take a look at the following (simplified) function signature of a command:

export type Command = (deltaTime: number) => { deltaTime: number; complete: boolean };

A command takes the maximum amount of time it's allowed to advance by, deltaTime, and returns any remaining unused time and whether it completed or not. Typically commands are implemented as closure functions so they track state internally. This means most commands are impure, (they have side effects).

So, the definition of a Command is simple, but it can used to build some incredibly cool and complex behaviours. As we saw in our example, parallel and sequence allow commands to easily be composed together in different ways. Other common commands worth checking out that can be used for composition include repeat, repeatForever, and chooseRandom.

Other Stuff

Colib is a super flexible set of tools. Check out the guides on different ways to schedule commands, available tweening utilities, and advanced coroutine patterns. There are also a lot of built in commands available.

Feedback Welcome

Feel free to raise bugs of the Github issues page, or suggest features.

Happy Sequencing!

Top comments (0)