3mcd / thyseus

An archetypal Entity Component System, built entirely in Typescript

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Thyseus

npm version license: mit pull requests: welcome code style: prettier

Thyseus is a multi-threadable, DX-focused, and highly performant archetypal Entity Component System (ECS) written in Typescript. It provides a simple, expressive, and type-driven API, and includes many features out of the box, including:

  • Hassle-free multithreading. Don't worry about scheduling, Mutexes, or workers - just write your systems and let Thyseus take care of the rest.
  • A safety-first approach! No eval, new Function(), or creating workers from blobs - Thyseus leverages recent additions to the language and a little bit of ✨ magic ✨ to do what it needs to, and will never use unsafe code.
  • Archetypal storage for lean memory use and cache-friendly iteration.
  • Dynamically sized types with strings.
  • Complex queries with Optional, With, Without, And, and Or filters.

Check out the documentation here!

Please note: Thyseus is in early development and is not yet feature-complete or nearly as performant as it could be. Pre-1.0.0 releases may have frequent breaking changes.

Installation

# pnpm
pnpm add thyseus

# yarn
yarn add thyseus

# npm
npm i thyseus

Contributing

If you're interested in contributing, please have a look at the code of conduct and the contributing guide first.

Quick API Example:

To get started, define a component:

import { struct, initStruct } from 'thyseus';

@struct
class Vec2 {
	@struct.f64 declare x: number;
	@struct.f64 declare y: number;

	constructor(x = 0, y = 0) {
		initStruct(this);
		this.x = x;
		this.y = y;
	}

	add(other: Vec2) {
		this.x += other.x;
		this.y += other.y;
	}

	addScaled(other: Vec2, scalar: number) {
		this.x += other.x * scalar;
		this.y += other.y * scalar;
	}
}
class Position extends Vec2 {}
class Velocity extends Vec2 {}

Let's add a resource to track the time:

import { struct } from 'thyseus';

@struct
class Time {
	@struct.f64 declare current: number;
	@struct.f64 declare previous: number;
	@struct.f64 declare delta: number;
}

And then a couple systems:

import { defineSystem } from 'thyseus';
import { Time, Position, Velocity } from './someModule';

const updateTime = defineSystem(
	({ Res, Mut }) => [Res(Mut(Time))],
	function updateTimeSystem(time) {
		time.previous = time.current;
		time.current = Date.now();
		time.delta = (time.current - time.previous) / 1000;
	}
);
const mover = defineSystem(
	({ Query, Mut, Res }) => [Query([Mut(Position), Velocity]), Res(Time)],
	function moverSystem(query, time) {
		for (const [pos, vel] of query) {
			pos.addScaled(vel, time.delta);
		}
	},
);

Sweet! Now let's make a world with these systems and get it started.

import { World } from 'thyseus';

// Note that the .build() method returns a promise.
// Top-level await is a convenient way to handle this,
// but it's not a requirement.
export const myWorld = await World.new()
	.addSystem(updateTime)
	.addSystem(mover)
	.build();

And then run it!

import { myWorld } from './someOtherModule';

async function loop() {
	await myWorld.update(); // This also returns a promise!
	requestAnimationFrame(loop);
}
loop();

If you'd like to run your systems on multiple threads:

// Will spawn one worker thread (default threads count is 1 - no worker threads)
export const myWorld = await World.new({ threads: 2 }, import.meta.url)
	.addSystem(...)
	...

A full explanation of the few caveats for multithreading will be provided when documentation is completed. Multithreading relies on SharedArrayBuffer and module workers (not yet implemented in Firefox).

About

An archetypal Entity Component System, built entirely in Typescript

License:MIT License


Languages

Language:TypeScript 100.0%