Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

0g

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

0g - npm Package Compare versions

Comparing version 0.0.2 to 0.0.3

9

dist-esm/entityManager.d.ts

@@ -7,3 +7,3 @@ /// <reference types="node" />

import { Store, StoreInstanceFor } from './stores';
export declare interface EntityManagerEvents {
export declare interface EntityManager {
on(ev: 'entityAdded', callback: (entity: Entity) => void): this;

@@ -15,8 +15,7 @@ on(ev: 'entityRemoved', callback: (entity: Entity) => void): this;

off(ev: 'entityRemoved', callback: (entity: Entity) => void): this;
off(ev: 'entityStoreAdded', callback: (entity: Entity) => void): this;
off(ev: 'entityStoreRemoved', callback: (entity: Entity) => void): this;
}
export declare class EntityManagerEvents extends EventEmitter {
}
export declare class EntityManager {
export declare class EntityManager extends EventEmitter {
__game: Game;
events: EntityManagerEvents;
pool: ObjectPool<Entity>;

@@ -23,0 +22,0 @@ _destroyList: string[];

@@ -46,30 +46,23 @@ var __extends = (this && this.__extends) || (function () {

import { logger } from './logger';
var EntityManagerEvents = /** @class */ (function (_super) {
__extends(EntityManagerEvents, _super);
function EntityManagerEvents() {
return _super !== null && _super.apply(this, arguments) || this;
}
return EntityManagerEvents;
}(EventEmitter));
export { EntityManagerEvents };
var EntityManager = /** @class */ (function () {
var EntityManager = /** @class */ (function (_super) {
__extends(EntityManager, _super);
function EntityManager() {
var _this = this;
this.__game = null;
this.events = new EntityManagerEvents();
this.pool = new ObjectPool(function () { return new Entity(); });
this._destroyList = new Array();
this.entities = {};
this.executeDestroys = function () {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.__game = null;
_this.pool = new ObjectPool(function () { return new Entity(); });
_this._destroyList = new Array();
_this.entities = {};
_this.executeDestroys = function () {
_this._destroyList.forEach(_this.executeDestroy);
_this._destroyList.length = 0;
};
this.executeDestroy = function (id) {
_this.executeDestroy = function (id) {
var entity = _this.entities[id];
delete _this.entities[id];
_this.pool.release(entity);
_this.events.emit('entityRemoved', entity);
_this.emit('entityRemoved', entity);
_this.__game.queries.onEntityDestroyed(entity);
logger.debug("Destroyed " + id);
};
return _this;
}

@@ -101,3 +94,3 @@ Object.defineProperty(EntityManager.prototype, "ids", {

var registered = this.entities[id];
this.events.emit('entityAdded', registered);
this.emit('entityAdded', registered);
this.__game.queries.onEntityCreated(registered);

@@ -118,3 +111,3 @@ logger.debug("Added " + id);

entity.__data.set(spec, data);
this.events.emit('entityStoreAdded', entity);
this.emit('entityStoreAdded', entity);
this.__game.queries.onEntityStoresChanged(entity);

@@ -127,3 +120,3 @@ return data;

entity.__data.delete(spec);
this.events.emit('entityStoreRemoved', entity);
this.emit('entityStoreRemoved', entity);
this.__game.queries.onEntityStoresChanged(entity);

@@ -160,4 +153,4 @@ return entity;

return EntityManager;
}());
}(EventEmitter));
export { EntityManager };
//# sourceMappingURL=entityManager.js.map

@@ -16,3 +16,3 @@ /// <reference types="node" />

private _entityManager;
private _stores;
private _storeSpecs;
private _systems;

@@ -38,2 +38,3 @@ private _systemInstances;

get storeSpecs(): Record<string, Store>;
get systems(): SystemSpec[];
get playState(): GamePlayState;

@@ -60,2 +61,7 @@ get isPaused(): boolean;

private runFrame;
/**
* Writes all the default values of each kind of Store
* to a static property on the constructor
*/
private initializeStores;
}

@@ -25,2 +25,18 @@ var __extends = (this && this.__extends) || (function () {

};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
import { EventEmitter } from 'events';

@@ -30,2 +46,3 @@ import * as input from './input';

import { QueryManager } from './queryManager';
import { BaseStore } from './stores';
import { StoreManager } from './storeManager';

@@ -72,3 +89,3 @@ var Game = /** @class */ (function (_super) {

var entry = serialized_1_1.value;
var spec = Object.keys(entry.data).map(function (storeKind) { return _this._stores[storeKind]; });
var spec = Object.keys(entry.data).map(function (storeKind) { return _this._storeSpecs[storeKind]; });
var entity = _this.create(entry.id);

@@ -112,6 +129,23 @@ try {

};
/**
* Writes all the default values of each kind of Store
* to a static property on the constructor
*/
_this.initializeStores = function () {
var builtins = BaseStore.builtinKeys;
Object.values(_this._storeSpecs).forEach(function (Store) {
var instance = new Store();
Store.defaultValues = Object.entries(instance).reduce(function (acc, _a) {
var _b = __read(_a, 2), key = _b[0], value = _b[1];
if (!builtins.includes(key)) {
acc[key] = value;
}
return acc;
}, {});
});
};
_this._entityManager.__game = _this;
_this._queryManager.__game = _this;
_this.setMaxListeners(Infinity);
_this._stores = stores;
_this._storeSpecs = stores;
_this._systems = systems;

@@ -122,2 +156,3 @@ _this._systemInstances = systems.map(function (Sys) { return new Sys(_this); });

_this._playState = initialState;
_this.initializeStores();
if (_this._playState === 'running') {

@@ -137,3 +172,3 @@ _this.resume();

get: function () {
return this._stores;
return this._storeSpecs;
},

@@ -143,2 +178,9 @@ enumerable: false,

});
Object.defineProperty(Game.prototype, "systems", {
get: function () {
return this._systems;
},
enumerable: false,
configurable: true
});
Object.defineProperty(Game.prototype, "playState", {

@@ -145,0 +187,0 @@ get: function () {

@@ -1,2 +0,1 @@

export * from './types';
export * from './Game';

@@ -3,0 +2,0 @@ export * from './entity';

@@ -1,2 +0,1 @@

export * from './types';
export * from './Game';

@@ -3,0 +2,0 @@ export * from './entity';

@@ -5,3 +5,3 @@ /// <reference types="node" />

import { Store } from './stores';
export declare interface QueryEvents {
export declare interface Query {
on(event: 'entityAdded', callback: (entity: Entity) => void): this;

@@ -12,4 +12,2 @@ on(event: 'entityRemoved', callback: (entity: Entity) => void): this;

}
export declare class QueryEvents extends EventEmitter {
}
export declare type QueryDef = {

@@ -19,6 +17,5 @@ all?: Store[];

};
export declare class Query<Def extends QueryDef = QueryDef> {
export declare class Query<Def extends QueryDef = QueryDef> extends EventEmitter {
def: Def;
entities: Entity[];
events: QueryEvents;
constructor(def: Def);

@@ -25,0 +22,0 @@ evaluate(entity: Entity): void;

@@ -16,15 +16,9 @@ var __extends = (this && this.__extends) || (function () {

import { logger } from './logger';
var QueryEvents = /** @class */ (function (_super) {
__extends(QueryEvents, _super);
function QueryEvents() {
return _super !== null && _super.apply(this, arguments) || this;
}
return QueryEvents;
}(EventEmitter));
export { QueryEvents };
var Query = /** @class */ (function () {
var Query = /** @class */ (function (_super) {
__extends(Query, _super);
function Query(def) {
this.def = def;
this.entities = new Array();
this.events = new QueryEvents();
var _this = _super.call(this) || this;
_this.def = def;
_this.entities = new Array();
return _this;
}

@@ -51,3 +45,3 @@ Query.prototype.evaluate = function (entity) {

logger.debug("Added " + entity.id + " to " + this.key);
this.events.emit('entityAdded', entity);
this.emit('entityAdded', entity);
};

@@ -60,3 +54,3 @@ Query.prototype.remove = function (entity) {

logger.debug("Removed " + entity.id + " from " + this.key);
this.events.emit('entityRemoved', entity);
this.emit('entityRemoved', entity);
}

@@ -91,4 +85,4 @@ };

return Query;
}());
}(EventEmitter));
export { Query };
//# sourceMappingURL=queries.js.map

@@ -0,8 +1,15 @@

/// <reference types="node" />
import { EventEmitter } from 'events';
import { Poolable } from './internal/objectPool';
import { Constructor } from './types';
declare class BaseStore implements Poolable {
export declare interface BaseStore {
on(event: 'change', callback: () => void): this;
off(event: 'change', callback: () => void): this;
}
export declare class BaseStore extends EventEmitter implements Poolable {
static kind: string;
static defaultValues: any;
static builtinKeys: string[];
__alive: boolean;
__version: number;
___version: number;
get __version(): number;
set<T extends BaseStore>(this: T, values: Partial<T>): void;

@@ -19,4 +26,6 @@ mark(): void;

export declare type StoreInstance = PersistentStore | StateStore;
export declare type Store = Constructor<PersistentStore> | Constructor<StateStore>;
export declare type StoreInstanceFor<S extends Store> = S extends Constructor<infer T> ? T : never;
export {};
export declare type StoreConstructor<T> = {
new (): T;
};
export declare type Store = StoreConstructor<PersistentStore> | StoreConstructor<StateStore>;
export declare type StoreInstanceFor<S extends Store> = S extends StoreConstructor<infer T> ? T : never;

@@ -14,23 +14,37 @@ var __extends = (this && this.__extends) || (function () {

})();
var BaseStore = /** @class */ (function () {
import { EventEmitter } from 'events';
var BaseStore = /** @class */ (function (_super) {
__extends(BaseStore, _super);
function BaseStore() {
var _this = this;
this.__alive = true;
this.__version = 0;
this.reset = function () {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.__alive = true;
_this.___version = 0;
_this.reset = function () {
_this.set(Object.getPrototypeOf(_this).constructor.defaultValues);
_this.__version = 0;
_this.___version = 0;
_this.emit('change');
};
return _this;
}
Object.defineProperty(BaseStore.prototype, "__version", {
get: function () {
return this.___version;
},
enumerable: false,
configurable: true
});
BaseStore.prototype.set = function (values) {
Object.assign(this, values);
this.__version++;
this.mark();
};
BaseStore.prototype.mark = function () {
this.__version++;
this.___version++;
this.emit('change');
};
BaseStore.kind = 'base';
BaseStore.defaultValues = {};
BaseStore.builtinKeys = Object.getOwnPropertyNames(new BaseStore());
return BaseStore;
}());
}(EventEmitter));
export { BaseStore };
var PersistentStore = /** @class */ (function (_super) {

@@ -37,0 +51,0 @@ __extends(PersistentStore, _super);

@@ -29,6 +29,6 @@ var FrameHandle = /** @class */ (function () {

System.prototype.watch = function (query, stores, run) {
var versionCache = new WeakMap();
return new FrameHandle(function () {
// TODO: consider implications of object pooling on weakmap usage - it
// probably makes them irrelevant, but possibly also incorrect?
var versionCache = new WeakMap();
// TODO: optimize this use case within Query

@@ -35,0 +35,0 @@ query.entities.forEach(function (entity) {

@@ -7,3 +7,3 @@ /// <reference types="node" />

import { Store, StoreInstanceFor } from './stores';
export declare interface EntityManagerEvents {
export declare interface EntityManager {
on(ev: 'entityAdded', callback: (entity: Entity) => void): this;

@@ -15,8 +15,7 @@ on(ev: 'entityRemoved', callback: (entity: Entity) => void): this;

off(ev: 'entityRemoved', callback: (entity: Entity) => void): this;
off(ev: 'entityStoreAdded', callback: (entity: Entity) => void): this;
off(ev: 'entityStoreRemoved', callback: (entity: Entity) => void): this;
}
export declare class EntityManagerEvents extends EventEmitter {
}
export declare class EntityManager {
export declare class EntityManager extends EventEmitter {
__game: Game;
events: EntityManagerEvents;
pool: ObjectPool<Entity>;

@@ -23,0 +22,0 @@ _destroyList: string[];

@@ -46,3 +46,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.EntityManager = exports.EntityManagerEvents = void 0;
exports.EntityManager = void 0;
var events_1 = require("events");

@@ -53,30 +53,23 @@ var shortid_1 = __importDefault(require("shortid"));

var logger_1 = require("./logger");
var EntityManagerEvents = /** @class */ (function (_super) {
__extends(EntityManagerEvents, _super);
function EntityManagerEvents() {
return _super !== null && _super.apply(this, arguments) || this;
}
return EntityManagerEvents;
}(events_1.EventEmitter));
exports.EntityManagerEvents = EntityManagerEvents;
var EntityManager = /** @class */ (function () {
var EntityManager = /** @class */ (function (_super) {
__extends(EntityManager, _super);
function EntityManager() {
var _this = this;
this.__game = null;
this.events = new EntityManagerEvents();
this.pool = new objectPool_1.ObjectPool(function () { return new entity_1.Entity(); });
this._destroyList = new Array();
this.entities = {};
this.executeDestroys = function () {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.__game = null;
_this.pool = new objectPool_1.ObjectPool(function () { return new entity_1.Entity(); });
_this._destroyList = new Array();
_this.entities = {};
_this.executeDestroys = function () {
_this._destroyList.forEach(_this.executeDestroy);
_this._destroyList.length = 0;
};
this.executeDestroy = function (id) {
_this.executeDestroy = function (id) {
var entity = _this.entities[id];
delete _this.entities[id];
_this.pool.release(entity);
_this.events.emit('entityRemoved', entity);
_this.emit('entityRemoved', entity);
_this.__game.queries.onEntityDestroyed(entity);
logger_1.logger.debug("Destroyed " + id);
};
return _this;
}

@@ -108,3 +101,3 @@ Object.defineProperty(EntityManager.prototype, "ids", {

var registered = this.entities[id];
this.events.emit('entityAdded', registered);
this.emit('entityAdded', registered);
this.__game.queries.onEntityCreated(registered);

@@ -125,3 +118,3 @@ logger_1.logger.debug("Added " + id);

entity.__data.set(spec, data);
this.events.emit('entityStoreAdded', entity);
this.emit('entityStoreAdded', entity);
this.__game.queries.onEntityStoresChanged(entity);

@@ -134,3 +127,3 @@ return data;

entity.__data.delete(spec);
this.events.emit('entityStoreRemoved', entity);
this.emit('entityStoreRemoved', entity);
this.__game.queries.onEntityStoresChanged(entity);

@@ -167,4 +160,4 @@ return entity;

return EntityManager;
}());
}(events_1.EventEmitter));
exports.EntityManager = EntityManager;
//# sourceMappingURL=entityManager.js.map

@@ -16,3 +16,3 @@ /// <reference types="node" />

private _entityManager;
private _stores;
private _storeSpecs;
private _systems;

@@ -38,2 +38,3 @@ private _systemInstances;

get storeSpecs(): Record<string, Store>;
get systems(): SystemSpec[];
get playState(): GamePlayState;

@@ -60,2 +61,7 @@ get isPaused(): boolean;

private runFrame;
/**
* Writes all the default values of each kind of Store
* to a static property on the constructor
*/
private initializeStores;
}

@@ -45,2 +45,18 @@ "use strict";

};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
Object.defineProperty(exports, "__esModule", { value: true });

@@ -52,2 +68,3 @@ exports.Game = void 0;

var queryManager_1 = require("./queryManager");
var stores_1 = require("./stores");
var storeManager_1 = require("./storeManager");

@@ -94,3 +111,3 @@ var Game = /** @class */ (function (_super) {

var entry = serialized_1_1.value;
var spec = Object.keys(entry.data).map(function (storeKind) { return _this._stores[storeKind]; });
var spec = Object.keys(entry.data).map(function (storeKind) { return _this._storeSpecs[storeKind]; });
var entity = _this.create(entry.id);

@@ -134,6 +151,23 @@ try {

};
/**
* Writes all the default values of each kind of Store
* to a static property on the constructor
*/
_this.initializeStores = function () {
var builtins = stores_1.BaseStore.builtinKeys;
Object.values(_this._storeSpecs).forEach(function (Store) {
var instance = new Store();
Store.defaultValues = Object.entries(instance).reduce(function (acc, _a) {
var _b = __read(_a, 2), key = _b[0], value = _b[1];
if (!builtins.includes(key)) {
acc[key] = value;
}
return acc;
}, {});
});
};
_this._entityManager.__game = _this;
_this._queryManager.__game = _this;
_this.setMaxListeners(Infinity);
_this._stores = stores;
_this._storeSpecs = stores;
_this._systems = systems;

@@ -144,2 +178,3 @@ _this._systemInstances = systems.map(function (Sys) { return new Sys(_this); });

_this._playState = initialState;
_this.initializeStores();
if (_this._playState === 'running') {

@@ -159,3 +194,3 @@ _this.resume();

get: function () {
return this._stores;
return this._storeSpecs;
},

@@ -165,2 +200,9 @@ enumerable: false,

});
Object.defineProperty(Game.prototype, "systems", {
get: function () {
return this._systems;
},
enumerable: false,
configurable: true
});
Object.defineProperty(Game.prototype, "playState", {

@@ -167,0 +209,0 @@ get: function () {

@@ -1,2 +0,1 @@

export * from './types';
export * from './Game';

@@ -3,0 +2,0 @@ export * from './entity';

@@ -13,3 +13,2 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./types"), exports);
__exportStar(require("./Game"), exports);

@@ -16,0 +15,0 @@ __exportStar(require("./entity"), exports);

@@ -5,3 +5,3 @@ /// <reference types="node" />

import { Store } from './stores';
export declare interface QueryEvents {
export declare interface Query {
on(event: 'entityAdded', callback: (entity: Entity) => void): this;

@@ -12,4 +12,2 @@ on(event: 'entityRemoved', callback: (entity: Entity) => void): this;

}
export declare class QueryEvents extends EventEmitter {
}
export declare type QueryDef = {

@@ -19,6 +17,5 @@ all?: Store[];

};
export declare class Query<Def extends QueryDef = QueryDef> {
export declare class Query<Def extends QueryDef = QueryDef> extends EventEmitter {
def: Def;
entities: Entity[];
events: QueryEvents;
constructor(def: Def);

@@ -25,0 +22,0 @@ evaluate(entity: Entity): void;

@@ -16,18 +16,12 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.Query = exports.QueryEvents = void 0;
exports.Query = void 0;
var events_1 = require("events");
var logger_1 = require("./logger");
var QueryEvents = /** @class */ (function (_super) {
__extends(QueryEvents, _super);
function QueryEvents() {
return _super !== null && _super.apply(this, arguments) || this;
}
return QueryEvents;
}(events_1.EventEmitter));
exports.QueryEvents = QueryEvents;
var Query = /** @class */ (function () {
var Query = /** @class */ (function (_super) {
__extends(Query, _super);
function Query(def) {
this.def = def;
this.entities = new Array();
this.events = new QueryEvents();
var _this = _super.call(this) || this;
_this.def = def;
_this.entities = new Array();
return _this;
}

@@ -54,3 +48,3 @@ Query.prototype.evaluate = function (entity) {

logger_1.logger.debug("Added " + entity.id + " to " + this.key);
this.events.emit('entityAdded', entity);
this.emit('entityAdded', entity);
};

@@ -63,3 +57,3 @@ Query.prototype.remove = function (entity) {

logger_1.logger.debug("Removed " + entity.id + " from " + this.key);
this.events.emit('entityRemoved', entity);
this.emit('entityRemoved', entity);
}

@@ -94,4 +88,4 @@ };

return Query;
}());
}(events_1.EventEmitter));
exports.Query = Query;
//# sourceMappingURL=queries.js.map

@@ -0,8 +1,15 @@

/// <reference types="node" />
import { EventEmitter } from 'events';
import { Poolable } from './internal/objectPool';
import { Constructor } from './types';
declare class BaseStore implements Poolable {
export declare interface BaseStore {
on(event: 'change', callback: () => void): this;
off(event: 'change', callback: () => void): this;
}
export declare class BaseStore extends EventEmitter implements Poolable {
static kind: string;
static defaultValues: any;
static builtinKeys: string[];
__alive: boolean;
__version: number;
___version: number;
get __version(): number;
set<T extends BaseStore>(this: T, values: Partial<T>): void;

@@ -19,4 +26,6 @@ mark(): void;

export declare type StoreInstance = PersistentStore | StateStore;
export declare type Store = Constructor<PersistentStore> | Constructor<StateStore>;
export declare type StoreInstanceFor<S extends Store> = S extends Constructor<infer T> ? T : never;
export {};
export declare type StoreConstructor<T> = {
new (): T;
};
export declare type Store = StoreConstructor<PersistentStore> | StoreConstructor<StateStore>;
export declare type StoreInstanceFor<S extends Store> = S extends StoreConstructor<infer T> ? T : never;

@@ -16,24 +16,38 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.StateStore = exports.PersistentStore = void 0;
var BaseStore = /** @class */ (function () {
exports.StateStore = exports.PersistentStore = exports.BaseStore = void 0;
var events_1 = require("events");
var BaseStore = /** @class */ (function (_super) {
__extends(BaseStore, _super);
function BaseStore() {
var _this = this;
this.__alive = true;
this.__version = 0;
this.reset = function () {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.__alive = true;
_this.___version = 0;
_this.reset = function () {
_this.set(Object.getPrototypeOf(_this).constructor.defaultValues);
_this.__version = 0;
_this.___version = 0;
_this.emit('change');
};
return _this;
}
Object.defineProperty(BaseStore.prototype, "__version", {
get: function () {
return this.___version;
},
enumerable: false,
configurable: true
});
BaseStore.prototype.set = function (values) {
Object.assign(this, values);
this.__version++;
this.mark();
};
BaseStore.prototype.mark = function () {
this.__version++;
this.___version++;
this.emit('change');
};
BaseStore.kind = 'base';
BaseStore.defaultValues = {};
BaseStore.builtinKeys = Object.getOwnPropertyNames(new BaseStore());
return BaseStore;
}());
}(events_1.EventEmitter));
exports.BaseStore = BaseStore;
var PersistentStore = /** @class */ (function (_super) {

@@ -40,0 +54,0 @@ __extends(PersistentStore, _super);

@@ -32,6 +32,6 @@ "use strict";

System.prototype.watch = function (query, stores, run) {
var versionCache = new WeakMap();
return new FrameHandle(function () {
// TODO: consider implications of object pooling on weakmap usage - it
// probably makes them irrelevant, but possibly also incorrect?
var versionCache = new WeakMap();
// TODO: optimize this use case within Query

@@ -38,0 +38,0 @@ query.entities.forEach(function (entity) {

{
"name": "0g",
"version": "0.0.2",
"version": "0.0.3",
"description": "",

@@ -22,3 +22,3 @@ "files": [

"prepublishOnly": "yarn build",
"release": "npm publish --access public",
"release": "npm publish --access public --registry https://registry.npmjs.org",
"storybook": "start-storybook -p 6006",

@@ -25,0 +25,0 @@ "storybook:build": "build-storybook -o docs/storybook",

# `0G`
## Entities, Systems, and Stores
The weightless game framework for TypeScript.
### Entities
### TypeScript Focused
Entities describe what it takes to create a type of Entity.
Entities declare a name, which is globally unique and used elsewhere to refer to it.
Entities declare Stores, which determine what kind of Entity it shall be.
Entities declare a Component, which defines how it shows up in the scene.
Entities have an ID which identifies them.
`0G` strikes a balance between flexibility and confidence, supporting seamless TypeScript typing for important data boundaries like Stores and core engine features.
### Systems
### ECS inspired
Systems process logic on Entities each tick.
Systems can define local state by defining `state`.
`0G` tries to take the core ides of Entity-Component-System game frameworks and extend them with greater flexibility and concrete use cases.
```tsx
// TODO: update code example
```
### React Compatible
Multiple systems can modify the same Stores on the same Entity and thereby interact.
Systems may only interact via data.
The data is the boundary.
`0G` has some goodies for React developers like me to integrate familiar patterns into game development use cases. You can utilize React to power your game's UI, or you can hook up [`react-three-fiber`](https://github.com/pmndrs/react-three-fiber) or [`react-pixi`](https://github.com/inlet/react-pixi) to power your whole game.
```tsx
// TODO: update code example
```
Of course, React adds additional overhead and can require a lot of imperative bailouts (i.e. `ref`s) to render realtime visualizations - but you get to decide where to draw the line for your game.
### Stores
For more details, take a look at the [`@0g/react`](https://github.com/a-type/0g/tree/master/packages/react) package after you've read up on the basics of `0G`.
Stores are just shapes of data.
## Show me a Game
```tsx
export const transformStore = 0g.store('transform', {
x: 0,
y: 0,
angle: 0,
});
```
class Transform extends PersistentStore {
x = 0;
y = 0;
randomJump() {
this.set({
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
});
}
}
When an Entity is rendered, its Store data is added to the World.
When an Entity is unmounted, it is removed from the World.
class ButtonTag extends PersistentStore {}
## Saving and Loading
class Button extends StateStore {
element: HTMLButtonElement | null = null;
lastJump: number;
}
Stores of rendered Entities are persisted as part of the save state.
Entities are restored from this snapshot on load.
class Score extends PersistentStore {
points = 0;
increment() {
this.set({ points: this.points + 1 });
}
}
Save files store entities and stores.
class ButtonMover extends System {
// new buttons that don't have a Button store connected yet
newButtons = this.query({
all: [ButtonTag, Transform],
none: [Button],
});
// buttons that have been initialized
buttons = this.query({
all: [ButtonTag, Button, Transform],
});
// reference the player to increment store
players = this.query({
all: [Score],
});
```js
// TODO: finalize snapshot shape
```
setup = this.frame(this.newButtons, (entity) => {
const element = document.createElement('button');
element.innerText = 'Click!';
element.style.position = 'absolute';
element.addEventListener('click', () => {
this.players.forEach((playerEntity) => {
playerEntity.get(Score).increment();
});
entity.get(Transform).randomJump();
entity.get(Button).set({ lastJump: Date.now() });
});
document.bodyElement.appendChild(element);
entity.add(Button, {
element,
});
});
## Finding, Creating, Destroying
run = this.frame(this.buttons, (entity) => {
const buttonStore = entity.get(Button);
if (Date.now() - buttonStore.lastJump > 3000) {
buttonStore.lastJump = Date.now();
entity.get(Transform).randomJump();
}
// update button positions
const transform = entity.get(Transform);
buttonStore.element.style.left = transform.x;
buttonStore.element.style.top = transform.y;
});
}
Each System has access to the World.
The World has a list of all Entities being rendered.
Get any other Entity by ID from the World.
class ScoreRenderer extends System {
players = this.query({
all: [Score],
});
```tsx
type World = {
get(id: string): EntityData;
// more things come from plugins ?
};
```
scoreElement = document.getElementById('scoreDisplay');
To "destroy" an Entity, its parent must stop rendering it.
Usually this involves interacting with that parent's Stores via a System.
For example, a Beehive Entity may have a list of bees it manages.
Use a System to remove one of the bees from the Beehive's bee list Store.
Then the bee will be unmounted and subsequently removed from the World.
update = this.watch(this.players, [Score], (entity) => {
this.scoreElement.innerText = entity.get(Score).points;
});
}
## Entities
const game = new Game({
stores: { Transform, ButtonTag, Button, Score },
systems: [ButtonMover],
});
Entities can reference one another (via Systems).
Entities expose only certain things publicly.
game.create('player').add(Score);
```tsx
type EntityData = {
id: string;
storesData: {
[alias: string]: any;
};
};
game.create('button').add(Transform).add(ButtonTag);
```
## Editor (TODO)
## Docs
Scenes are really defined entirely by saved Entities and their Stores.
So to construct a scene we start with making our Entities.
Each Scene begins with a single Scene entity, which has children.
Once we have a Scene, we can open the editor.
The Editor can create Entities and define their Store data by adding them to Scene or others.
The Editor renders the initial state of all Entities.
But the Editor isn't only for initial states.
We can bring up the Editor any time during gameplay to tweak.
### ECS-based Architecture
## Plugins
#### Stores
Plugins can add behavior to the World.
Physics is a good plugin example.
Plugins have several things:
```tsx
class Transform extends PersistentStore {
x = 0;
y = 0;
angle = 0;
1. _API_: plugins can provide some arbitrary API in the World Context.
2. _Wrappers_: plugins can wrap the World tree in Providers or other things.
3. _Stores_: plugins can add Stores to the game's Stores list.
4. _Systems_: plugins can add their own Systems to manage game behaviors.
5. _Anything else_: a plugin user can import other useful stuff like Components or Hooks directly from the plugin.
get position() {
return {
x: this.x,
y: this.y,
};
}
}
```
### Packaging Stores & Systems as Plugins
To start modeling your game behavior, you'll probably first begin defining Stores.
TODO
"Stores" replace ECS "Components" naming (disambiguating the term from React Components). Stores are where your game state lives. The purpose of the rest of your code is either to group, change, or render Stores.
## Known issues
Stores come in two flavors:
- Stores is a bad name - maybe Aspects? But it's not AOP...
- _Persistent_ : Serializeable and runtime-editable<sup>1</sup>, this data forms the loadable "scene."
- Example: Store the configuration of a physics rigidbody for each character
- _State_: Store any data you want. Usually these stores are derived from persistent stores at initialization time.
- Example: Store the runtime rigidbody created by the physics system at runtime
## Entity Lifecycle
<sup><sup>1</sup> Runtime editing is not yet supported... except in the devtools console.</sup>
There is no generic concept of "children."
However, different Entities may construct specialized Stores which manage some particular concept of hierarchy.
For example, the `Bricks` Entity in Brick Breaker might have a Store which controls its brick pattern.
It then renders child Entities using their Entity components.
#### Entities
```tsx
const Bricks = entity(
'Bricks',
{
config: game.stores.brickConfig({
rows: 2,
columns: 5,
}),
transform: game.stores.transform(),
},
({
stores: {
config: { rows, columns },
transform: { x, y },
},
}) => {
return (
<>
{new Array(columns).fill(null).map((_, h) =>
new Array(rows).fill(null).map((_, v) => (
<Brick
key={`${h}_${v}`}
id={`${h}_${v}`}
initial={{
transform: { x: x + h * 10, y: y + v * 10 },
}}
/>
)),
)}
</>
);
},
);
const transform = entity.get(stores.Transform);
transform.x = 100;
```
Entities can only control their children's initial configuration:
As in classic ECS, Entities are identifiers for groupings of Stores. Each Entity has an ID. In `0G`, an Entity object provides some convenience tools for retrieving, updating, and adding Stores to itself.
- Initial Stores
- ID
#### Queries
Otherwise the child is managed by Systems like the parent.
When an Entity is mounted it is added to the scene with its initial Stores.
When an Entity is mounted it is assigned an ID if it was not given one.
When an Entity is unmounted it is removed from the scene.
If an Entity is mounted and it is already present in the World data, it connects to that data.
Therefore, when a saved scene is loaded and Entities begin rendering their children, those children seamlessly recover their prior state.
Orphaned Entities are cleaned up periodically. (TODO)
To reparent an Entity just render it from a different parent (TODO)
Somehow we enforce an Entity can't render twice (TODO)
The Scene has a special Store which is a generic children container.
Other Entities can use this Store, too, if they just want generic children.
It works like this:
```tsx
const newEntity = {
id: 'player',
prefab: 'Player',
initial: {
transform: { x: 0, y: 0 },
},
};
const scene = world.get('scene');
// TODO: high-level store API for this
game.stores.children.get(scene)[newEntity.id] = newEntity;
```
Then the Scene will render the new Entity.
When rendered the Entity will be stored in World data.
To remove an Entity from Scene you do:
```ts
// TODO: high-level store API for this
delete game.stores.children.get(world.get('scene'))[id];
```
This generic `children` Store is the least specialized concept of children.
It is therefore the most verbose.
For example, if an Entity only renders one kind of child, you could do:
```ts
const bricks = world.get('bricks');
game.stores.brickPositions.get(bricks).push({ x: 10, y: 50 });
```
Supposing that `brickPositions` was a list of locations to place `Brick` Entities.
## The Flow
We start with Stores.
Define all the Stores, pass them to `create`, get a Game.
Then register Prefabs and Systems on Game.
We expose `stores` on Game.
```ts
const Player = entity(
'Player',
{
transform: game.store.transform({ x: 0, y: 10 }),
},
() => {
/* ... */
},
);
```
Systems utilize `game.store` to reference an Entity's Stores by kind.
```ts
const body = game.system({
name: 'body',
run: (e) => {
const transform = game.store.transform.get(e);
},
bodies = this.query({
all: [stores.Body, stores.Transform],
none: [stores.OmitPhysics],
});
```
## Store APIs
It's important, as a game developer, to find Entities in the game and iterate over them. Generally you want to find Entities by certain criteria. In `0G` the primary criteria is which Stores they have associated.
Stores can define higher-level APIs for users to streamline tasks.
But all logic remains pure, because Store APIs are pure.
Queries are managed by the game, and they monitor changes to Entities and select which Entities match your criteria. For example, you could have a Query which selects all Entities that have a `Transform` and `Body` store to do physics calculations.
```ts
game.stores.forces.applyImpulse(entity, { x: 5, y: 0 });
```
#### Systems
A high-level Store method like this can only modify its own Store instance on the Entity.
# New Ideas - ECS + React
Now that ECS is working, how about a React-focused ECS implementation?
```tsx
class PlayerHealth extends PersistentStore {
// required? or constructor.name suffices?
static key = 'PlayerHealth';
// assign main data properties
health = 100;
// getters are allowed
get isDead() {
return this.health === 0;
}
// a static `set` method is available and can be composed
// into new operations
static takeDamage(p: Player, dmg: number) {
Player.set(p, {
health: Math.max(0, p.health - dmg),
});
}
}
// not shown: StateStore (not saved in savefile),
// TagStore (no values), and ValueStore (single value)
const stores = { ...box2d.stores, Player };
// game is analogous to, say, a Redux/Zustand store or a
// gql client. It allows external interaction to the main
// state and plugs seamlessly into React via provider
const game = new Game({ stores });
// systems are React components. They can optionally render
// JSX to control the visuals of the game.
const PlayerMovement = () => {
// useQuery returns a static reference to a Query object
// managed by the game which caches entities that match
// the requirements.
const players = useQuery({
all: [stores.PlayerHealth, stores.Transform],
class DemoMove extends System {
movable = this.query({
all: [stores.Transform],
});
const game = useGame();
// runs every frame and iterates over query items
useFrame(players, (entity) => {
const playerHealth = stores.PlayerHealth.get(entity);
const transform = stores.Transform.get(entity);
// dead folks don't move
if (playerHealth.isDead) return;
if (game.input.keyboard.getKey('ArrowLeft')) {
stores.Transform.set(transform, {
x: transform.x - 5,
});
} else if (game.input.keyboard.getKey('ArrowRight')) {
stores.Transform.set(transform, {
x: transform.x + 5,
});
run = this.frame(this.movable, (entity) => {
const transform = entity.getWritable(stores.Transform);
if (this.game.input.keyboard.getKeyPressed('ArrowLeft')) {
transform.x -= 5;
} else if (this.game.input.keyboard.getKeyPressed('ArrowRight')) {
transform.x += 5;
}
});
}
```
// runs only when a watched store changes
useWatch(players, [stores.Transform], (entity) => {
// this is totally contrived, idk what you'd do here.
});
Systems are where your game logic lives. They utilize Queries to access Entities in the game which meet certain constraints. Using those Queries, they can iterate over matching entities each frame, or monitor specific Stores for changes.
return null;
};
##### Using Systems to Render
const PlayerSprite = React.memo(({ entity }) => {
const ref = useRef();
Systems are also how you render your game. `0G` supports flexible approaches to rendering.
useWatch(entity, [stores.Transform], () => {
if (!ref.current) return;
For Vanilla JS, you might want to use State Stores (non-persistent) to initialize and store a renderable object, like a Pixi `Sprite` or a ThreeJS `Mesh`. The System can update the renderable each frame like any other data.
const transform = stores.Transform.get(entity);
ref.current.x = transform.x;
ref.current.y = transform.y;
});
return <Sprite ref={ref} />;
});
const PlayerSprites = () => {
const players = useQuery({
all: [stores.PlayerHealth, stores.Transform],
});
return players.entities.map((entity) => <PlayerSprite entity={entity} />);
};
const Game = () => {
return (
<World game={game}>
<PlayerMovement />
</World>
);
};
```
If you want to use React to render a game, you can utilize the [`@0g/react`](https://github.com/a-type/0g/tree/master/packages/react) package to actually write Systems as React Components. The package provides the hooks you'll need to accomplish the same behavior as class-based Systems.

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc