colyseus-events
Advanced tools
Comparing version 2.1.1 to 3.0.0
import { Container, Events, Traverse, Visitor } from './types'; | ||
export declare const schemaKeys: readonly string[]; | ||
import { CallbacksCleanup } from './destructors'; | ||
export declare const handleSchema: Readonly<{ | ||
/** | ||
* keys that exist on any schema object - are not data fields | ||
*/ | ||
schemaKeys: readonly string[]; | ||
visit: (traverse: Traverse, state: Container, events: Events, namespace: string) => boolean; | ||
cache: CallbacksCleanup; | ||
visit(traverse: Traverse, state: Container, events: Events, namespace: string): boolean; | ||
}>; | ||
export declare const handleArraySchema: Readonly<{ | ||
visit: (traverse: Traverse, state: Container, events: Events, namespace: string) => boolean; | ||
cache: CallbacksCleanup; | ||
visit(traverse: Traverse, state: Container, events: Events, namespace: string): boolean; | ||
}>; | ||
export declare const handleMapSchema: Readonly<{ | ||
visit: (traverse: Traverse, state: Container, events: Events, namespace: string) => boolean; | ||
cache: CallbacksCleanup; | ||
visit(traverse: Traverse, state: Container, events: Events, namespace: string): boolean; | ||
}>; | ||
export declare const coreVisitors: ReadonlyArray<Visitor>; | ||
//# sourceMappingURL=core-visitors.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.coreVisitors = exports.handleMapSchema = exports.handleArraySchema = exports.handleSchema = exports.schemaKeys = void 0; | ||
exports.coreVisitors = exports.handleMapSchema = exports.handleArraySchema = exports.handleSchema = void 0; | ||
const types_1 = require("./types"); | ||
const schema_1 = require("@colyseus/schema"); | ||
// all objects in this module are frozen, to narrow API surface | ||
exports.schemaKeys = Object.freeze(Object.keys(new (class extends schema_1.Schema { | ||
})()).concat(['onChange', 'onRemove'])); | ||
const destructors_1 = require("./destructors"); | ||
exports.handleSchema = Object.freeze({ | ||
/** | ||
* keys that exist on any schema object - are not data fields | ||
*/ | ||
schemaKeys: exports.schemaKeys, | ||
visit: (traverse, state, events, namespace) => { | ||
cache: new destructors_1.CallbacksCleanup(), | ||
visit(traverse, state, events, namespace) { | ||
if (!(state instanceof schema_1.Schema)) { | ||
return false; | ||
} | ||
state.onChange = (changes) => { | ||
for (const { field, value } of changes) { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
// @ts-ignore: access _definition to get fields list | ||
const fieldsList = Object.values(state._definition.fieldsByIndex); | ||
const destructors = this.cache.resetDestructors(state); | ||
for (const field of fieldsList) { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
const d = state.listen(field, (value, previousValue) => { | ||
if (value === previousValue) | ||
return; | ||
events.emit(fieldNamespace, (0, types_1.Replace)(fieldNamespace, value)); | ||
traverse(value, events, fieldNamespace); | ||
} | ||
}; | ||
for (const field in state) { | ||
if (!exports.schemaKeys.includes(field) && Object.prototype.hasOwnProperty.call(state, field)) { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
const value = state[field]; | ||
traverse(value, events, fieldNamespace); | ||
} | ||
}); | ||
destructors.add(d); | ||
} | ||
this.cache.recheckCallbacks(state); | ||
return true; | ||
@@ -36,24 +31,30 @@ }, | ||
exports.handleArraySchema = Object.freeze({ | ||
visit: (traverse, state, events, namespace) => { | ||
cache: new destructors_1.CallbacksCleanup(), | ||
visit(traverse, state, events, namespace) { | ||
if (!(state instanceof schema_1.ArraySchema)) { | ||
return false; | ||
} | ||
state.onAdd = (value, field) => { | ||
const knownKeys = new Set(); // for ignoring first and last onChange | ||
const destructors = this.cache.resetDestructors(state); | ||
destructors.add(state.onAdd((value, field) => { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(namespace, (0, types_1.Add)(fieldNamespace, value)); | ||
traverse(value, events, fieldNamespace); | ||
}; | ||
state.onChange = (value, field) => { | ||
})); | ||
destructors.add(state.onChange((value, field) => { | ||
if (knownKeys.has(field)) { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(fieldNamespace, (0, types_1.Replace)(fieldNamespace, value)); | ||
traverse(value, events, fieldNamespace); | ||
} | ||
else { | ||
knownKeys.add(field); | ||
} | ||
})); | ||
destructors.add(state.onRemove((_, field) => { | ||
knownKeys.delete(field); | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(fieldNamespace, (0, types_1.Replace)(fieldNamespace, value)); | ||
traverse(value, events, fieldNamespace); | ||
}; | ||
state.onRemove = (_, field) => { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(namespace, (0, types_1.Remove)(fieldNamespace)); | ||
}; | ||
for (const [field, value] of state.entries()) { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
traverse(value, events, fieldNamespace); | ||
} | ||
})); | ||
this.cache.recheckCallbacks(state); | ||
return true; | ||
@@ -63,24 +64,32 @@ }, | ||
exports.handleMapSchema = Object.freeze({ | ||
visit: (traverse, state, events, namespace) => { | ||
cache: new destructors_1.CallbacksCleanup(), | ||
visit(traverse, state, events, namespace) { | ||
// Check if it is going to handle the state object, and return `false` if not. | ||
if (!(state instanceof schema_1.MapSchema)) { | ||
return false; | ||
} | ||
state.onAdd = (value, field) => { | ||
const knownKeys = new Set(); // for ignoring first and last onChange | ||
const destructors = this.cache.resetDestructors(state); | ||
// Hook on new elements and register destructors | ||
destructors.add(state.onAdd((value, field) => { | ||
const fieldNamespace = `${namespace}/${field}`; // path to the new element | ||
events.emit(namespace, (0, types_1.Add)(fieldNamespace, value)); // emit the add event | ||
traverse(value, events, fieldNamespace); // call the traverse function on the new value | ||
})); | ||
destructors.add(state.onChange((value, field) => { | ||
if (knownKeys.has(field)) { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(fieldNamespace, (0, types_1.Replace)(fieldNamespace, value)); | ||
traverse(value, events, fieldNamespace); | ||
} | ||
else { | ||
knownKeys.add(field); | ||
} | ||
})); | ||
destructors.add(state.onRemove((_, field) => { | ||
knownKeys.delete(field); | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(namespace, (0, types_1.Add)(fieldNamespace, value)); | ||
traverse(value, events, fieldNamespace); | ||
}; | ||
state.onChange = (value, field) => { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(fieldNamespace, (0, types_1.Replace)(fieldNamespace, value)); | ||
traverse(value, events, fieldNamespace); | ||
}; | ||
state.onRemove = (_, field) => { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
events.emit(namespace, (0, types_1.Remove)(fieldNamespace)); | ||
}; | ||
for (const [field, value] of state.entries()) { | ||
const fieldNamespace = `${namespace}/${field}`; | ||
traverse(value, events, fieldNamespace); | ||
} | ||
})); | ||
this.cache.recheckCallbacks(state); | ||
return true; | ||
@@ -87,0 +96,0 @@ }, |
export * from './types'; | ||
export * from './wire-events'; | ||
export * from './core-visitors'; | ||
export { CallbacksCleanup } from './destructors'; | ||
//# sourceMappingURL=index.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.CallbacksCleanup = void 0; | ||
const tslib_1 = require("tslib"); | ||
@@ -7,2 +8,4 @@ tslib_1.__exportStar(require("./types"), exports); | ||
tslib_1.__exportStar(require("./core-visitors"), exports); | ||
var destructors_1 = require("./destructors"); | ||
Object.defineProperty(exports, "CallbacksCleanup", { enumerable: true, get: function () { return destructors_1.CallbacksCleanup; } }); | ||
//# sourceMappingURL=index.js.map |
@@ -24,6 +24,7 @@ import { ArraySchema, CollectionSchema, MapSchema, Schema, SetSchema } from '@colyseus/schema'; | ||
export declare type Event = Add | Remove | Replace; | ||
export declare function equalEvents(a: Event, b: Event): boolean; | ||
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; | ||
export declare type Traverse<T extends Events = Events> = (state: Colyseus, events: T, jsonPath: string) => T; | ||
export declare type Traverse<T extends Events = Events> = (state: Colyseus, events: T, jsonPath: string) => unknown; | ||
/** | ||
@@ -30,0 +31,0 @@ * logic to wire events for a single entity type |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Remove = exports.Replace = exports.Add = exports.isPrimitive = void 0; | ||
exports.Remove = exports.Replace = exports.Add = exports.equalEvents = exports.isPrimitive = void 0; | ||
function isPrimitive(val) { | ||
@@ -8,2 +8,12 @@ return typeof val !== 'object' || !val; | ||
exports.isPrimitive = isPrimitive; | ||
function equalEvents(a, b) { | ||
if (a === b) | ||
return true; | ||
if (a.op !== b.op || a.path !== b.path) | ||
return false; | ||
if (a.op === 'remove') | ||
return true; // 'remove' events have no 'value' | ||
return a.value === b.value; | ||
} | ||
exports.equalEvents = equalEvents; | ||
function Add(path, value) { | ||
@@ -10,0 +20,0 @@ return { op: 'add', path, value }; |
@@ -15,3 +15,6 @@ import { Colyseus, Events, Visitor } from './types'; | ||
*/ | ||
export declare function customWireEvents(visitors: Iterable<Visitor>): <T extends Events<string>>(state: Colyseus, events: T, namespace?: string) => T; | ||
export declare function customWireEvents(visitors: Iterable<Visitor>): <T extends Events<string>>(root: Colyseus, userEvents: T, rootNamespace?: string) => { | ||
events: T; | ||
clearCache: () => void; | ||
}; | ||
/** | ||
@@ -25,3 +28,6 @@ * make every change in Colyseus state trigger an event in the EventEmitter using the provided namespace. | ||
*/ | ||
export declare const wireEvents: <T extends Events<string>>(state: Colyseus, events: T, namespace?: string) => T; | ||
export declare const wireEvents: <T extends Events<string>>(root: Colyseus, userEvents: T, rootNamespace?: string) => { | ||
events: T; | ||
clearCache: () => void; | ||
}; | ||
//# sourceMappingURL=wire-events.d.ts.map |
@@ -5,2 +5,4 @@ "use strict"; | ||
const types_1 = require("./types"); | ||
const de_dupe_wrapper_1 = require("./de-dupe-wrapper"); | ||
const weak_set_1 = require("./weak-set"); | ||
const core_visitors_1 = require("./core-visitors"); | ||
@@ -13,12 +15,22 @@ /** | ||
function customWireEvents(visitors) { | ||
return function recursive(state, events, namespace = '') { | ||
if ((0, types_1.isPrimitive)(state)) { | ||
return events; | ||
} | ||
for (const ch of visitors) { | ||
if (ch.visit(recursive, state, events, namespace)) { | ||
return function wireEvents(root, userEvents, rootNamespace = '') { | ||
const wiredContainers = new weak_set_1.SymbolWeakSet(); | ||
function recursive(state, events, namespace) { | ||
if ((0, types_1.isPrimitive)(state)) { | ||
return events; | ||
} | ||
if (wiredContainers.has(state)) { | ||
return events; | ||
} | ||
wiredContainers.add(state); | ||
for (const ch of visitors) { | ||
if (ch.visit(recursive, state, events, namespace)) { | ||
return events; | ||
} | ||
} | ||
return events; | ||
} | ||
return events; | ||
const dedupedEvents = new de_dupe_wrapper_1.DeDupeEmitter(userEvents); | ||
recursive(root, dedupedEvents, rootNamespace); | ||
return { events: userEvents, clearCache: dedupedEvents.clearCache }; | ||
}; | ||
@@ -25,0 +37,0 @@ } |
{ | ||
"name": "colyseus-events", | ||
"version": "2.1.1", | ||
"version": "3.0.0", | ||
"description": "generate notification events from colyseus state", | ||
@@ -32,6 +32,6 @@ "repository": "git@github.com:starwards/colyseus-events.git", | ||
"peerDependencies": { | ||
"@colyseus/schema": "1.x" | ||
"@colyseus/schema": "2.x" | ||
}, | ||
"devDependencies": { | ||
"@colyseus/schema": "^1.0.41", | ||
"@colyseus/schema": "^2.0.27", | ||
"@types/tape": "^4.13.2", | ||
@@ -38,0 +38,0 @@ "@typescript-eslint/eslint-plugin": "^5.29.0", |
@@ -7,3 +7,3 @@ # colyseus-events | ||
const room: Room<GameState> = await client.joinOrCreate("game"); | ||
const events = wireEvents(room.state, new EventEmitter()); | ||
const { events } = wireEvents(room.state, new EventEmitter()); | ||
// `events` will emit json-patch events whenever the room state changes | ||
@@ -14,3 +14,3 @@ ``` | ||
Due to breaking API changes in Colyseus, this version only supports Colyseus 0.14 and above (@colyseus/schema >= 1.0.2) | ||
Due to breaking API changes in Colyseus, this version only supports Colyseus 0.15 and above (@colyseus/schema 2.x) | ||
@@ -30,3 +30,3 @@ ## Pending support | ||
const room: Room<GameState> = await client.joinOrCreate("game"); | ||
const events = wireEvents(room.state, new EventEmitter()); | ||
const { events } = wireEvents(room.state, new EventEmitter()); | ||
// `events` will emit json-patch events whenever the room state changes | ||
@@ -45,3 +45,3 @@ ``` | ||
const room: Room<GameState> = await client.joinOrCreate("game"); | ||
const events = wireEvents(room.state, new EventEmitter()); | ||
const { events } = wireEvents(room.state, new EventEmitter()); | ||
// `events` will emit json-patch events whenever the room state changes | ||
@@ -58,7 +58,8 @@ ``` | ||
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. | ||
3. Hook on the state's [Client-side Callbacks](https://docs.colyseus.io/state/schema-callbacks/#state-sync-client-side-callbacks). Make sure to only hook once per state object. This may become trickey with Proxies, and 'stickey' callbacks. | ||
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`: | ||
Examples can be found in [core-visitors.ts](src/core-visitors.ts). Here is a brief of the visitor that handles `MapSchema`: | ||
```typescript | ||
{ | ||
visit: (traverse: Traverse, state: Container, events: Events, namespace: string) => { | ||
@@ -78,7 +79,2 @@ // Check if it is going to handle the state object, and return `false` if not. | ||
// 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. | ||
@@ -89,2 +85,3 @@ return true; | ||
``` | ||
In addition to the code above, there ais also code to handle duplicate events and keeping only one registration per state object. | ||
## Examples | ||
@@ -91,0 +88,0 @@ |
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
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
43272
35
433
149