Comparing version 0.2.20 to 0.2.21
@@ -41,2 +41,4 @@ export type Type = | ||
type QueryModifier = (c: (IComponent | IComponentProp)[]) => (world: IWorld) => IComponent | IComponentProp | ||
type Query = (world: IWorld) => number[] | ||
@@ -56,3 +58,3 @@ | ||
hasComponent: (world: IWorld, component: IComponent, eid: number) => boolean | ||
defineQuery: (components: (IComponent | IComponentProp)[]) => Query | ||
defineQuery: (components: (IComponent | QueryModifier)[]) => Query | ||
Changed: (c: (IComponent | IComponentProp)[]) => (world: IWorld) => IComponent | IComponentProp | ||
@@ -64,4 +66,4 @@ Not: (c: (IComponent | IComponentProp)[]) => (world: IWorld) => IComponent | IComponentProp | ||
defineSystem: (update: (world: IWorld) => void) => System | ||
defineSerializer: (target: IWorld | IComponent | IComponentProp, maxBytes?: number) => (target: IWorld | number[]) => ArrayBuffer | ||
defineDeserializer: (target: IWorld | IComponent | IComponentProp) => (world: IWorld, packet: ArrayBuffer) => void | ||
defineSerializer: (target: IWorld | IComponent | IComponentProp | QueryModifier, maxBytes?: number) => (target: IWorld | number[]) => ArrayBuffer | ||
defineDeserializer: (target: IWorld | IComponent | IComponentProp | QueryModifier) => (world: IWorld, packet: ArrayBuffer) => void | ||
} |
{ | ||
"name": "bitecs", | ||
"version": "0.2.20", | ||
"version": "0.2.21", | ||
"description": "Tiny, data-driven, high performance ECS library written in Javascript", | ||
@@ -28,3 +28,3 @@ "license": "MPL-2.0", | ||
"build": "rollup -c", | ||
"test": "jest", | ||
"test": "mocha ./test", | ||
"docs": "node scripts/docs.js", | ||
@@ -38,4 +38,4 @@ "dist": "npm run test && npm run build && npm run docs" | ||
"globby": "^11.0.1", | ||
"jest": "^26.6.1", | ||
"jsdoc-to-markdown": "^6.0.1", | ||
"mocha": "^8.3.2", | ||
"rollup": "^2.32.1" | ||
@@ -54,8 +54,3 @@ }, | ||
] | ||
}, | ||
"jest": { | ||
"testMatch": [ | ||
"**/__tests__/**/*.test.js" | ||
] | ||
} | ||
} |
306
README.md
# ๐พ bitECS ๐พ | ||
Functional, small, data-oriented, high performance [ECS](https://en.wikipedia.org/wiki/Entity_component_system) library written using JavaScript TypedArrays. | ||
Functional, minimal, data-oriented, ultra-high performance [ECS](https://en.wikipedia.org/wiki/Entity_component_system) library written using JavaScript TypedArrays. | ||
@@ -8,27 +8,22 @@ | ||
๐ฎ Simple & functional API | ||
| | | | ||
| --------------------------------- | ---------------------------------------- | | ||
| ๐ฎ Simple, declarative API | ๐ฅ Blazing fast iteration | | ||
| ๐ Powerful & performant queries | ๐พ Swift serialization | | ||
| ๐ Zero dependencies | ๐ Node or browser | | ||
| ๐ค `~5kb` gzipped | ๐ Unparalleled performance benchmarks | | ||
๐ฅ Blazing fast iteration | ||
#### Benchmarks | ||
๐ Powerful & performant queries | ||
| | | | ||
| --------------------------------------------------------------- | ------------------------------------------------------------------------- | | ||
| [noctjs/ecs-benchmark](https://github.com/noctjs/ecs-benchmark) | [ddmills/js-ecs-benchmarks](https://github.com/ddmills/js-ecs-benchmarks) | | ||
๐พ Swift serialization | ||
๐ Zero dependencies | ||
#### In Development | ||
| | | ||
| ---------------- | | ||
|๐งต Multithreading | | ||
๐ Node or browser | ||
๐ค `~5kb` gzipped | ||
๐ Unparalleled performance | ||
- [noctjs/ecs-benchmark](https://github.com/noctjs/ecs-benchmark) | ||
- [ddmills/js-ecs-benchmarks](https://github.com/ddmills/js-ecs-benchmarks) | ||
### In Development | ||
๐งต Multithreading | ||
## Install | ||
@@ -39,8 +34,7 @@ ``` | ||
## Overview | ||
## Example | ||
This is the entire API: | ||
```js | ||
// this is the entire API | ||
import { | ||
@@ -68,75 +62,114 @@ | ||
pipe, | ||
} from 'bitecs' | ||
``` | ||
## World | ||
/** | ||
* createWorld | ||
* | ||
* Creates a world which represents a set of entities and what components they possess. | ||
* Does NOT store actual component data. | ||
* Create as many worlds as you want. | ||
**/ | ||
A world represents a set of entities and the components that they each possess. | ||
// creating worlds returns empty objects | ||
Worlds do not store actual component data, only the relationships with entities (archetypes). | ||
Any number of worlds can be created. An empty object is returned which you can use as a context. | ||
```js | ||
const world = createWorld() | ||
const world2 = createWorld() | ||
// store whatever you need on the world object | ||
world.time = { delta: 0, elapsed: 0 } | ||
world.name = 'MyWorld' | ||
``` | ||
## Entity | ||
/** | ||
* defineComponent | ||
* | ||
* Returns a SoA (Structure of Arrays). | ||
* Store of component data. | ||
**/ | ||
An entity is an integer, technically a pointer, which components can be associated with. Entities are accessed via queries, components of whom are mutated with systems. | ||
// available types | ||
const { bool, i8, ui8, ui8c, i16, ui16, i32, ui32, f32, f64 } = Types | ||
Add entities to the world: | ||
```js | ||
const eid = addEntity(world) | ||
const eid2 = addEntity(world) | ||
``` | ||
Remove entities from the world: | ||
```js | ||
removeEntity(world, eid2) | ||
``` | ||
// schema for a component | ||
const Vector2 = { x: f32, y: f32 } | ||
## Component | ||
Components are pure data and added to entities to give them state. | ||
// define components, which creates SoA data stores | ||
const Position = defineComponent(Vector2) | ||
const Velocity = defineComponent(Vector2) | ||
const Health = defineComponent({ value: ui16 }) | ||
const Alive = defineComponent() // "tag" component | ||
const Mapping = defineComponent(new Map()) // can use a map to associate regular JS objects with entities | ||
The object returned from `defineComponent` is a SoA (Structure of Arrays). This is what actually stores the component data. | ||
/** | ||
* defineQuery | ||
* | ||
* Returns a query function which returns array of entities from a world that match the given components. | ||
**/ | ||
Define component stores: | ||
```js | ||
const Vector3 = { x: Types.f32, y: Types.f32, z: Types.f32 } | ||
const Position = defineComponent(Vector3) | ||
const Velocity = defineComponent(Vector3) | ||
``` | ||
// define a query using components | ||
Add components to an entity in a world: | ||
```js | ||
addComponent(world, Position, eid) | ||
addComponent(world, Velocity, eid) | ||
``` | ||
Component data accessed directly via `eid`, there are no getters or setters: | ||
* This is how high performance iteration is achieved | ||
```js | ||
Velocity.x[eid] = 1 | ||
Velocity.y[eid] = 1 | ||
``` | ||
## Query | ||
A query is defined with components and is used to obtain a specific set of entities from a world. | ||
Define a query: | ||
```js | ||
const movementQuery = defineQuery([Position, Velocity]) | ||
``` | ||
// use the query on a world | ||
Use the query on a world to obtain an array of entities with those components: | ||
```js | ||
const ents = movementQuery(world) | ||
``` | ||
// wrapping a component with the Not modifier creates a query which | ||
// returns entities who explicitly do not have the component | ||
Wrapping a component with the `Not` modifier defines a query which returns entities who explicitly do not have the component: | ||
```js | ||
const positionWithoutVelocityQuery = defineQuery([ Position, Not(Velocity) ]) | ||
``` | ||
// wrapping a component with the Change modifier creates a query which | ||
// returns entities whose component's state has changed since last call of the function | ||
Wrapping a component with the `Change` modifier creates a query which returns entities whose component's state has changed since last call of the function: | ||
```js | ||
const changedPositionQuery = defineQuery([ Changed(Position) ]) | ||
// enter-query hook, called when an entity's components matches the query | ||
let ents = changedPositionQuery(world) | ||
console.log(ents) // => [] | ||
Position.x[eid]++ | ||
ents = changedPositionQuery(world) | ||
console.log(ents) // => [0] | ||
``` | ||
The enter-query hook is called when an entity's components match the query: | ||
```js | ||
enterQuery(world, movementQuery, eid => {}) | ||
``` | ||
// exit-query hook, called when an entity's components no longer matches the query | ||
The exit-query hook is called when an entity's components no longer match the query: | ||
```js | ||
exitQuery(world, movementQuery, eid => {}) | ||
``` | ||
/** | ||
* defineSystem | ||
* | ||
* Creates a function which can be processed against a given world. | ||
* Use queries to access relevant entities for the system. | ||
**/ | ||
## System | ||
// movement system | ||
Systems are functions and are run against a world to update componenet state of entities, or anything else. | ||
Queries are used inside of systems to obtain a relevant set of entities and perform operations on their component data. | ||
While not required, it is greatly encouraged that you keep all component data mutations inside of systems, and all system-dependent state on the world. | ||
Define a system that moves entity positions based on their velocity: | ||
```js | ||
const movementSystem = defineSystem(world => { | ||
@@ -150,99 +183,90 @@ const ents = movementQuery(world) | ||
}) | ||
``` | ||
// delta time system | ||
let then = performance.now() | ||
Define a system which tracks time: | ||
```js | ||
world.time = { | ||
delta: 0, | ||
elapsed: 0, | ||
then: performance.now() | ||
} | ||
const timeSystem = defineSystem(world => { | ||
const now = performance.now() | ||
const delta = now - then | ||
const delta = now - world.time.then | ||
world.time.delta = delta | ||
world.time.elapsed += delta | ||
then = now | ||
world.time.then = now | ||
}) | ||
``` | ||
Systems are used to update entities of a world: | ||
```js | ||
movementSystem(world) | ||
``` | ||
/** | ||
* addEntity | ||
* | ||
* An entity is a single ID which components can be associated with. | ||
* Entities are accessed via queries, components of whom are mutated with systems. | ||
**/ | ||
// add entities to the world | ||
const eid = addEntity(world) | ||
const eid2 = addEntity(world) | ||
// remove entities from the world | ||
removeEntity(world, eid2) | ||
// add components to the new entities in the world | ||
addComponent(world, Position, eid) | ||
addComponent(world, Velocity, eid) | ||
// remove components from entities in the world | ||
removeComponent(world, Velocity, eid) | ||
// there are no component getters or setters | ||
// data is accessed directly by entity ID | ||
Position.x[eid] = 1 | ||
Position.y[eid] = 2 | ||
/** | ||
* pipe | ||
* | ||
* Creates a sequence of systems which are executed in serial. | ||
**/ | ||
Pipelines of systems should be created with the `pipe` function: | ||
```js | ||
const pipeline = pipe( | ||
movementSystem, | ||
timeSystem, | ||
timeSystem | ||
) | ||
pipeline(world) | ||
``` | ||
/** | ||
* Update worlds with systems or pipelines of systems. | ||
**/ | ||
## Serialization | ||
// execute movement system on world | ||
movementSystem(world) | ||
Performant and highly customizable serialization is built-in. Any subset of data can be targeted and serialized/deserialized with great efficiency and ease. | ||
// executes a pipeline of systems on world | ||
pipeline(world) | ||
Serializers and deserializers need the same configs in order to work properly. Any combination of components and component properties may be used as configs. | ||
Serialization can take a whole world as a config and will serialize all component stores in that world: | ||
```js | ||
const serialize = createSerializer(world) | ||
const deserialize = createDeserializer(world) | ||
``` | ||
/** | ||
* createSerializer / Deserializer | ||
* | ||
* Creates a function which serializes the state of a world or array of entities. | ||
**/ | ||
Serialize all of the world's entities and thier component data: | ||
```js | ||
const packet = serialize(world) | ||
``` | ||
// creates a serializer/deserializer which will serialize all component stores | ||
const serialize = createSerializer() | ||
const deserialize = createDeserializer() | ||
Use the deserializer to apply state onto the same or any other world: | ||
* Note: serialized entities and components are automatically (re)created if they do not exist in the target world | ||
```js | ||
deserialize(world, packet) | ||
``` | ||
// serializes the entire world of entities | ||
let packet = serialize(world) | ||
// deserializes the state back onto the world | ||
// note: creates entities and adds components if they do not exist | ||
Serialize a more specific set of entities using queries: | ||
```js | ||
const ents = movementQuery(world) | ||
const packet = serialize(ents) | ||
deserialize(world, packet) | ||
``` | ||
// creates a serializer/deserializer which will serialize select component stores | ||
const serializePositions = createSerializer([Position]) | ||
const deserializePositions = createDeserializer([Position]) | ||
Serialization for any mixture of components and component properties: | ||
```js | ||
const serializePositions = createSerializer([Position, Velocity.x]) | ||
const deserializePositions = createDeserializer([Position, Velocity.x]) | ||
``` | ||
// serializes the Position data of entities which match the movementQuery | ||
packet = serializePositions(movementQuery(world)) | ||
// deserializes the Position data back onto the world | ||
Serialize Position data for entities matching the movementQuery, defined with pipe: | ||
```js | ||
const serializeMovementQueryPositions = pipe(movementQuery, serializePositions) | ||
const packet = serializeMovementQueryPositions(world) | ||
deserializePositions(world, packet) | ||
``` | ||
// creates a serializer which will serialize select component stores of entities | ||
// whose component state has changed since the last call of the function | ||
Serialization which targets select component stores of entities | ||
whose component state has changed since the last call of the function: | ||
```js | ||
const serializeOnlyChangedPositions = createSerializer([Changed(Position)]) | ||
// serializes the Position data of entities which match the movementQuery | ||
// whose component values have changed since last call of the function | ||
let packet = pipe(movementQuery, serializeOnlyChangedPositions)(world) | ||
console.log(packet) // => undefined | ||
Position.x[eid]++ | ||
packet = serializeOnlyChangedPositions(movementQuery(world)) | ||
``` | ||
console.log(packet.byteLength) // => 13 | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
229727
15
1658
268