Skip to content

Commit

Permalink
Merge pull request #26 from DavidPeicho/release/0.0.2
Browse files Browse the repository at this point in the history
RELEASE: v0.0.2
  • Loading branch information
DavidPeicho authored Mar 28, 2021
2 parents ac379dd + 56f3d2f commit 6b15b09
Show file tree
Hide file tree
Showing 28 changed files with 1,326 additions and 114 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
node_modules

dist/
# Ignores generated js files in TS examples
./examples/typescript/**/*.js

17 changes: 17 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,20 @@ import { World } from 'ecstra';
// You can use Ecstra as if it was a npm-installed dependency
const world = new World();
```

## Benchmark

Before being merged, the code is passed into the benchmark test suite to ensure
no performance has been lost.

To run the benchmark locally, you can use:

```sh
npm run benchmark -- -o [PATH_TO_OUTPUT_FILE]
```

If you wish to compare the benchmark with a previous result, you can use:

```sh
npm run benchmark -- -c [PATH_TO_FILE_TO_COMPARE]
```
121 changes: 74 additions & 47 deletions DOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ the other, one group at a time. Running systems can query entities based on the
## Example
```js
import { NumberProp, System } from 'fecs';
import { NumberProp, System } from 'ecstra';

class TransformComponent extends ComponentData { }
TransformComponent.Properties = {
Expand Down Expand Up @@ -226,7 +226,7 @@ Queries can also specify that they want to deal with entities that
**do not** have a given component:
```js
import { Not } from 'fecs';
import { Not } from 'ecstra';

PhysicsSystem.Queries = {
entitiesWithBoxThatArentPlayers: [
Expand Down Expand Up @@ -323,82 +323,109 @@ class TestComponentDecorator extends ComponentData {
}
```
# Advanced
# Pooling
## Custom Properties
The first version of Ecstra had pooling disabled by default. However, when I
started to benchmark the library I quickly realized that pooling was a must have
by default.
You can create your own properties by extending the `Property` class:
By default, every component type and entities have associated pools. If you have
50 different components, Ecstra will then allocates 50 component pools and one
extra pool for entities. This may seem like a waste of memory, but will bring
by ~50% the cost of creating components and entities.
```js
import { Property } from 'property';
## Custom Pool
class MyProperty extends Property {

copy(dest, src) {
// Optional method to implement.
// `dest` should receive the value (for reference type).
// `src` is the input.
return dest;
}
You can derive your own pool implementation by creating a class
matching this interface:
```js
export interface ObjectPool<T> {
destroy?: () => void;
acquire(): T;
release(value: T): void;
expand(count: number): void;
}
```
You can also create a function that setup your property:
You can then use your own default pool for entities / components:
```js
function MyProp(options) {
// Process the `options` argument and create the property.
return new MyProperty(...);
}
const world = new World({
ComponentPoolClass: MySuperFastPoolClass,
EntityPoolClass: MySuperFastPoolClass
});
```
## Pooling
When creating and destroying a lot of entities and components, pooling
can help reduce garbage collection and improve general performance.
Alternatively, you can change the pool on a per-component basis using:
> Note By default, worlds are created in _"manual"_ pooling mode, i.e., no pooling is performed.
### Automatic Pooling
```js
world.registerComponent(MyComponentClass, { pool: MySuperFastPoolClass });
```
It's possible for you to activate pooling with little effort:
or
```js
const world = new World({ useManualPooling: false });
world.setComponentPool(MyComponentClass, MySuperFastPoolClass);
```
Pooling will be enabled for **every** components, as well as for
entities.
## Disable Automatic Pooling
It's possible for you to opt out pooling for the desired components:
If you don't want any default pooling, you can create your `World` using:
```js
// The second argument represents the pool. Setting it to `null`
// disable pooling.
world.setComponentPool(MyComponentClass, null);
const world = new World({
useManualPooling: true
})
```
### Manual Pooling
When the automatic pooling is disabled, `ComponentPoolClass` and
`EntityPoolClass` are unused. However, manually assigning pool using
`world.setComponentPool` is still a possibility.
Instead of using automatic pooling, it's possible to manage pools
one by one. You can assign a pool to a component type:
# Perfomance
## Pooling
Pooling can significantly improve performance, especially if you often add or
remove components. The default pooling scheme should be enough in most cases,
but creating custom pool systems can also help.
## Reduce Componet Addition / Deletion
Even if pooling is used, adding / deleting components always comes at a cost.
The components list is hashed into a string, used to find the new archetype
of the entity.
You can probably enabled / disable some components by using a custom field.
# Advanced
## Custom Properties
You can create your own properties by extending the `Property` class:
```js
import { DefaultPool } from 'ecstra';
import { Property } from 'property';

world.setComponentPool(MyComponentClass, new DefaultPool());
class MyProperty extends Property {

copy(dest, src) {
// Optional method to implement.
// `dest` should receive the value (for reference type).
// `src` is the input.
return dest;
}

}
```
You can also derive your own pool implementation by creating a class
matching this interface:
You can also create a function that setup your property:
```js
export interface ObjectPool<T> {
destroy?: () => void;
acquire(): T;
release(value: T): void;
expand(count: number): void;
function MyProp(options) {
// Process the `options` argument and create the property.
return new MyProperty(...);
}
```
Expand Down
137 changes: 134 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Fast & Flexible EntityComponentSystem (ECS) for JavaScript and Typescript, avail

Get started with:
* The [Documentation](./DOC.md)
* The [Examples](./example)
* The [JavaScript Examples](./example)
* The [TypeScript Examples](./example/typescript)

> 🔍 I am currently looking for people to help me to identify their needs in order to drive the development of this [library further](#stable-version).
Expand All @@ -22,7 +23,7 @@ Get started with:

> Created as 'Flecs', it's been renamed to 'Ecstra' to avoid duplicate
Ecstra (pronounced as "eck-stra") is heavily based on [Ecsy](https://github.com/ecsyjs/ecsy), but mixes concepts from other great ECS. It also share some concepts with
Ecstra (pronounced as "extra") is heavily based on [Ecsy](https://github.com/ecsyjs/ecsy), but mixes concepts from other great ECS. It also share some concepts with
[Hecs](https://github.com/gohyperr/hecs/).

My goals for the library is to keep it:
Expand All @@ -45,6 +46,7 @@ The library will prioritize stability improvements over feature development.
* TypeScript Decorators
* For component properties
* For system ordering and configuration
* No Dependency

## Install

Expand All @@ -66,12 +68,108 @@ The library is distributed as an ES6 module, but also comes with two UMD builds:

## Usage Example

### TypeScript

```ts
import {
ComponentData,
TagComponent,
System,
World,
number,
queries,
ref
} from 'ecstra';

/**
* Components definition.
*/

class Position2D extends ComponentData {
@number()
x!: number;
@number()
y!: number;
}

class FollowTarget extends ComponentData {
@ref()
target!: number;
@number(1.0)
speed!: number;
}

class PlayerTag extends TagComponent {}
class ZombieTag extends TagComponent {}

/**
* Systems definition.
*/

@queries({
// Select entities with all three components `ZombieTag`, `FollowTarget`, and
// `Position2D`.
zombies: [ZombieTag, FollowTarget, Position2D]
})
class ZombieFollowSystem extends System {

execute(delta: number): void {
this.queries.zombies.execute((entity) => {
const { speed, target } = entity.read(FollowTarget);
const position = entity.write(Position2D);
const deltaX = target.x - position.x;
const deltaY = target.y - position.y;
const len = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (len >= 0.00001) {
position.x += speed * delta * (deltaX / len);
position.y += speed * delta * (deltaY / len);
}
});
}

}

const world = new World().register(ZombieFollowSystem);

// Creates a player entity.
const playerEntity = world.create().add(PlayerTag).add(Position2D);
const playerPosition = playerEntity.read();

// Creates 100 zombies at random positions with a `FollowTarget` component that
// will make them follow our player.
for (let i = 0; i < 100; ++i) {
world.create()
.add(ZombieTag)
.add(Position2D, {
x: Math.floor(Math.random() * 50.0) - 100.0,
y: Math.floor(Math.random() * 50.0) - 100.0
})
.add(FollowTarget, { target: playerPosition })
}

// Runs the animation loop and execute all systems every frame.

let lastTime = 0.0;
function loop() {
const currTime = performance.now();
const deltaTime = currTime - lastTime;
lastTime = currTime;
world.execute(deltaTime);
requestAnimationFrame(loop);
}
lastTime = performance.now();
loop();
```

### JavaScript

```js
import {
ComponentData,
TagComponent,
NumberProp,
RefProp,
System,
World
} from 'ecstra';

Expand Down Expand Up @@ -121,7 +219,7 @@ ZombieFollowSystem.Queries = {
zombies: [ZombieTag, FollowTarget, Position2D]
}

const world = new World();
const world = new World().register(ZombieFollowSystem);

// Creates a player entity.
const playerEntity = world.create().add(PlayerTag).add(Position2D);
Expand Down Expand Up @@ -153,6 +251,39 @@ lastTime = performance.now();
loop();
```

## Running Examples

In order to try the examples, you need to build the library using:

```sh
yarn build # Alternatively, `yarn start` to watch the files
```

You can then start the examples web server using:

```sh
yarn example
```

### TS Examples

TypeScript versions of the examples are available [here](.examples/typescript).
If you only want to see the example running, you can run the JS ones as they
are identicial.

If you want to run the TypeScript examples themselves, please build the examples
first:

```sh
yarn example:build # Alternatively, `yarn example:start` to watch the files
```

And then run the examples web server:

```sh
yarn example
```

## Stable Version

The library is brand new and it's the perfect time for me to taylor it to match as much as possible most of the developer needs.
Expand Down
Loading

0 comments on commit 6b15b09

Please sign in to comment.