colyseus-events
Advanced tools
Comparing version 2.0.0 to 2.1.0
@@ -1,34 +0,4 @@ | ||
import { ArraySchema, CollectionSchema, MapSchema, Schema, SetSchema } from '@colyseus/schema'; | ||
export declare type Primitive = number | string | boolean | null | undefined; | ||
export declare type Container = Schema | ArraySchema | MapSchema | CollectionSchema | SetSchema; | ||
export declare type Colyseus = Primitive | Container; | ||
export interface Events<E = string> { | ||
emit(eventName: E, value: Event): unknown; | ||
} | ||
export declare type Add = { | ||
op: 'add'; | ||
path: string; | ||
value: Colyseus; | ||
}; | ||
export declare type Replace = { | ||
op: 'replace'; | ||
path: string; | ||
value: Colyseus; | ||
}; | ||
export declare type Remove = { | ||
op: 'remove'; | ||
path: string; | ||
}; | ||
export declare type Event = Add | Remove | Replace; | ||
export declare function add(path: string, value: Colyseus): Add; | ||
export declare function replace(path: string, value: Colyseus): Replace; | ||
export declare function remove(path: string): Remove; | ||
/** | ||
* make every change in Colyseus state trigger an event in the EventEmitter using the provided namespace. | ||
* @param state Colyseus state to track | ||
* @param events EventsEmitter of you choice. has to at least have the method `emit(eventName, value)`. | ||
* @param namespace (optional) Prefix of json pointer | ||
* @returns the provided events emitter (2nd argument) | ||
*/ | ||
export declare function wireEvents<T extends Events>(state: Colyseus, events: T, namespace?: string): T; | ||
export * from './types'; | ||
export * from './wire-events'; | ||
export * from './core-visitors'; | ||
//# sourceMappingURL=index.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.wireEvents = exports.remove = exports.replace = exports.add = void 0; | ||
const schema_1 = require("@colyseus/schema"); | ||
const schemaKeys = Object.keys(new (class extends schema_1.Schema { | ||
})()).concat(['onChange']); // keys that exist on any schema object - are not data fields | ||
function isPrimitive(val) { | ||
return typeof val !== 'object' || !val; | ||
} | ||
function add(path, value) { | ||
return { op: 'add', path, value }; | ||
} | ||
exports.add = add; | ||
function replace(path, value) { | ||
return { op: 'replace', path, value }; | ||
} | ||
exports.replace = replace; | ||
function remove(path) { | ||
return { op: 'remove', path }; | ||
} | ||
exports.remove = remove; | ||
/** | ||
* make every change in Colyseus state trigger an event in the EventEmitter using the provided namespace. | ||
* @param state Colyseus state to track | ||
* @param events EventsEmitter of you choice. has to at least have the method `emit(eventName, value)`. | ||
* @param namespace (optional) Prefix of json pointer | ||
* @returns the provided events emitter (2nd argument) | ||
*/ | ||
function wireEvents(state, events, namespace = '') { | ||
if (isPrimitive(state)) { | ||
return events; | ||
} | ||
if (state instanceof schema_1.Schema) { | ||
state.onChange = (changes) => { | ||
for (const { field, value } of changes) { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(fieldNamespace, replace(fieldNamespace, value)); | ||
wireEvents(value, events, fieldNamespace); | ||
} | ||
}; | ||
for (const field in state) { | ||
if (!schemaKeys.includes(field) && Object.prototype.hasOwnProperty.call(state, field)) { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
const value = state[field]; | ||
wireEvents(value, events, fieldNamespace); | ||
} | ||
} | ||
} | ||
else if (state instanceof schema_1.ArraySchema) { | ||
state.onAdd = (value, field) => { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(namespace, add(fieldNamespace, value)); | ||
wireEvents(value, events, fieldNamespace); | ||
}; | ||
state.onChange = (value, field) => { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(fieldNamespace, replace(fieldNamespace, value)); | ||
wireEvents(value, events, fieldNamespace); | ||
}; | ||
state.onRemove = (_, field) => { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(namespace, remove(fieldNamespace)); | ||
}; | ||
for (const [field, value] of state.entries()) { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
wireEvents(value, events, fieldNamespace); | ||
} | ||
} | ||
else if (state instanceof schema_1.MapSchema) { | ||
state.onAdd = (value, field) => { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(namespace, add(fieldNamespace, value)); | ||
wireEvents(value, events, fieldNamespace); | ||
}; | ||
state.onChange = (value, field) => { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(fieldNamespace, replace(fieldNamespace, value)); | ||
wireEvents(value, events, fieldNamespace); | ||
}; | ||
state.onRemove = (_, field) => { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(namespace, remove(fieldNamespace)); | ||
}; | ||
for (const [field, value] of state.entries()) { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
wireEvents(value, events, fieldNamespace); | ||
} | ||
} | ||
// TODO handle CollectionSchema and andSetSchema | ||
return events; | ||
} | ||
exports.wireEvents = wireEvents; | ||
const tslib_1 = require("tslib"); | ||
tslib_1.__exportStar(require("./types"), exports); | ||
tslib_1.__exportStar(require("./wire-events"), exports); | ||
tslib_1.__exportStar(require("./core-visitors"), exports); | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "colyseus-events", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"description": "generate notification events from colyseus state", | ||
@@ -5,0 +5,0 @@ "repository": "git@github.com:starwards/colyseus-events.git", |
# colyseus-events | ||
Generate json-patch events from colyseus state. | ||
Generate json-patch events from [colyseus](https://www.colyseus.io/) state. | ||
```typescript | ||
@@ -23,3 +23,4 @@ import { wireEvents } from 'colyseus-events'; | ||
## How to use | ||
Import `wireEvents` and call it once when connecting to a room on the client side, | ||
### wireEvents | ||
Import `wireEvents` and call it once when connecting to a room on the client side: | ||
```typescript | ||
@@ -31,4 +32,54 @@ import { wireEvents } from 'colyseus-events'; | ||
``` | ||
then you can wire listeners to `events` using their [JSON-pointer](https://github.com/janl/node-jsonpointer) as event name. | ||
then you can wire listeners to `events` using the [JSON-pointer](https://github.com/janl/node-jsonpointer) of target field as event name. | ||
### customWireEvents | ||
To change the behavior for parts or all of your state, use `customWireEvents` to produce your own version of `wireEvents`: | ||
```typescript | ||
import { customWireEvents, coreVisitors} from 'colyseus-events'; | ||
const special = { | ||
visit: (traverse: Traverse, state: Container, events: Events, jsonPath: string): boolean => { /* see Visitor implementation below*/}, | ||
}; | ||
const wireEvents = customWireEvents([ special, ...coreVisitors]); | ||
const room: Room<GameState> = await client.joinOrCreate("game"); | ||
const events = wireEvents(room.state, new EventEmitter()); | ||
// `events` will emit json-patch events whenever the room state changes | ||
``` | ||
`customWireEvents` accepts a single argument, a collection of `Visitor` objects, and returns afunctyion compatible with the default `wireEvents`. In fact, the default `wireEvents` function is itself the result `customWireEvents` when using `coreVisitors` as the argument. it is defined in [wire-events.ts](src/wire-events.ts#L42) by the following line: | ||
```typescript | ||
export const wireEvents = customWireEvents(coreVisitors); | ||
``` | ||
The order of the visitors is crucial: they are executed as a fallback chain: the first visitor to return `true` will stop the chain and prevent later visitors from wiring the same state. So be sure to order them by specificity: the more specific handlers should first check for their use case before the generic visitors, and `coreVisitors` should be the last visitors. | ||
#### Visitor implementation | ||
A visitor must implement a single method, `visit`. This method should: | ||
1. Check if it is going to handle the state object, and return `false` if not. | ||
2. Call the traverse function for each child member of the state. | ||
3. Hook on the state's [`.onChange`](https://docs.colyseus.io/colyseus/state/schema/#onchange-changes-datachange) and possibly [`.onAdd`](https://docs.colyseus.io/colyseus/state/schema/#onadd-instance-key) and [`.onRemove`](https://docs.colyseus.io/colyseus/state/schema/#onremove-instance-key), or perhaspse the [`.listen`](https://docs.colyseus.io/colyseus/state/schema/#listenprop-callback) mechanism. | ||
4. For every new value in each child member of the state, call the traverse function and emit the events using the event emitter. | ||
Examples can be found in [core-visitors.ts](src/core-visitors.ts). Here is the visitor that handles `MapSchema`: | ||
```typescript | ||
{ | ||
visit: (traverse: Traverse, state: Container, events: Events, namespace: string) => { | ||
// Check if it is going to handle the state object, and return `false` if not. | ||
if (!(state instanceof MapSchema)) { | ||
return false; | ||
} | ||
// Hook on new elements | ||
state.onAdd = (value: Colyseus, field) => { | ||
const fieldNamespace = `${namespace}/${field}`; // path to the new element | ||
events.emit(namespace, Add(fieldNamespace, value)); // emit the add event | ||
traverse(value, events, fieldNamespace); // call the traverse function on the new value | ||
}; | ||
... | ||
// Call the traverse function for each child member of the state. | ||
for (const [field, value] of state.entries()) { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
traverse(value as Colyseus, events, fieldNamespace); | ||
} | ||
// finally return true. this will break the visitors fallback chain and complete the wiring for this object. | ||
return true; | ||
} | ||
} | ||
``` | ||
## Examples | ||
@@ -39,3 +90,4 @@ | ||
export class Inner extends Schema { | ||
@type('uint8') public baz = 0; | ||
@type('uint8') public x = 0; | ||
@type('uint8') public y = 0; | ||
} | ||
@@ -55,3 +107,3 @@ export class GameState extends Schema { | ||
- when the server executes: `room.mapNumbers.set('F00', 1)` (assuming mapNumbers had a previous value at key `F00`) an event named `'/mapNumbers/F00'` will be emitted with value `{ op: 'replace', path: '/mapNumbers/F00', value: 1 }` | ||
- when the server executes: `room.state.bar.baz = 1` an event named `'/bar/baz'` will be emitted with value `{ op: 'replace', path: '/bar/baz', value: 1 }` | ||
- when the server executes: `room.state.bar.x = 1` an event named `'/bar/x'` will be emitted with value `{ op: 'replace', path: '/bar/x', value: 1 }` | ||
- when the server executes: `room.state.bar = new Inner()` an event named `'/bar'` will be emitted with value `{ op: 'replace', path: '/bar', value: {{the actual object in state.bar }} }` | ||
@@ -58,0 +110,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
29538
19
230
152
1