@dcl/sdk 7.22.6-25007982108.commit-83012ab → 7.22.6-25321038582.commit-63ddb3f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/atom.d.ts +19 -0
- package/atom.js +83 -0
- package/future.d.ts +8 -0
- package/future.js +26 -0
- package/index.js +1 -5
- package/internal/transports/rendererTransport.d.ts +0 -4
- package/internal/transports/rendererTransport.js +1 -8
- package/network/binary-message-bus.d.ts +6 -3
- package/network/binary-message-bus.js +9 -5
- package/network/chunking.d.ts +5 -0
- package/network/chunking.js +38 -0
- package/network/entities.js +11 -3
- package/network/events/implementation.d.ts +93 -0
- package/network/events/implementation.js +221 -0
- package/network/events/index.d.ts +42 -0
- package/network/events/index.js +43 -0
- package/network/events/protocol.d.ts +27 -0
- package/network/events/protocol.js +66 -0
- package/network/events/registry.d.ts +8 -0
- package/network/events/registry.js +3 -0
- package/network/index.d.ts +8 -2
- package/network/index.js +16 -3
- package/network/message-bus-sync.d.ts +14 -1
- package/network/message-bus-sync.js +161 -103
- package/network/server/index.d.ts +14 -0
- package/network/server/index.js +219 -0
- package/network/server/utils.d.ts +18 -0
- package/network/server/utils.js +135 -0
- package/network/state.js +8 -7
- package/package.json +6 -6
- package/platform/index.d.ts +5 -0
- package/platform/index.js +29 -0
- package/players/index.d.ts +0 -1
- package/players/index.js +1 -2
- package/server/env-var.d.ts +15 -0
- package/server/env-var.js +31 -0
- package/server/index.d.ts +2 -0
- package/server/index.js +3 -0
- package/server/storage/constants.d.ts +23 -0
- package/server/storage/constants.js +2 -0
- package/server/storage/index.d.ts +22 -0
- package/server/storage/index.js +29 -0
- package/server/storage/player.d.ts +43 -0
- package/server/storage/player.js +92 -0
- package/server/storage/scene.d.ts +38 -0
- package/server/storage/scene.js +90 -0
- package/server/storage-url.d.ts +10 -0
- package/server/storage-url.js +29 -0
- package/server/utils.d.ts +35 -0
- package/server/utils.js +56 -0
- package/src/atom.ts +98 -0
- package/src/future.ts +38 -0
- package/src/index.ts +1 -5
- package/src/internal/transports/rendererTransport.ts +0 -13
- package/src/network/binary-message-bus.ts +9 -4
- package/src/network/chunking.ts +45 -0
- package/src/network/entities.ts +10 -2
- package/src/network/events/implementation.ts +271 -0
- package/src/network/events/index.ts +48 -0
- package/src/network/events/protocol.ts +94 -0
- package/src/network/events/registry.ts +18 -0
- package/src/network/index.ts +40 -3
- package/src/network/message-bus-sync.ts +174 -110
- package/src/network/server/index.ts +301 -0
- package/src/network/server/utils.ts +189 -0
- package/src/network/state.ts +9 -5
- package/src/platform/index.ts +35 -0
- package/src/players/index.ts +0 -2
- package/src/server/env-var.ts +36 -0
- package/src/server/index.ts +2 -0
- package/src/server/storage/constants.ts +22 -0
- package/src/server/storage/index.ts +44 -0
- package/src/server/storage/player.ts +156 -0
- package/src/server/storage/scene.ts +149 -0
- package/src/server/storage-url.ts +34 -0
- package/src/server/utils.ts +73 -0
- package/src/testing/runtime.ts +3 -3
- package/testing/runtime.js +4 -4
package/server/utils.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { signedFetch } from '~system/SignedFetch';
|
|
2
|
+
import { isServer } from '../network';
|
|
3
|
+
/**
|
|
4
|
+
* Validates that the code is running on a server-side scene.
|
|
5
|
+
* Throws an error if called from a client-side context.
|
|
6
|
+
*
|
|
7
|
+
* @param moduleName - The name of the module for the error message
|
|
8
|
+
* @throws Error if not running on a server-side scene
|
|
9
|
+
*/
|
|
10
|
+
export function assertIsServer(moduleName) {
|
|
11
|
+
if (!isServer()) {
|
|
12
|
+
throw new Error(`${moduleName} is only available on server-side scenes`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Wraps a promise to catch errors and return a Result tuple.
|
|
17
|
+
* This allows for cleaner error handling without try-catch blocks.
|
|
18
|
+
*
|
|
19
|
+
* @param promise - The promise to wrap
|
|
20
|
+
* @returns A tuple of [error, null] on failure or [null, data] on success
|
|
21
|
+
*/
|
|
22
|
+
export async function tryCatch(promise) {
|
|
23
|
+
try {
|
|
24
|
+
const data = await promise;
|
|
25
|
+
return [null, data];
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return [error, null];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Wraps signedFetch with automatic error handling and JSON parsing.
|
|
33
|
+
* Returns a FetchResult tuple with parsed JSON data or error message and status code.
|
|
34
|
+
*
|
|
35
|
+
* @param signedFetchBody - The signedFetch request configuration
|
|
36
|
+
* @returns A tuple of [error, null, statusCode?] on failure or [null, data, statusCode] on success
|
|
37
|
+
*/
|
|
38
|
+
export async function wrapSignedFetch(signedFetchBody) {
|
|
39
|
+
const [error, response] = await tryCatch(signedFetch(signedFetchBody));
|
|
40
|
+
if (error) {
|
|
41
|
+
console.error(`Error in ${signedFetchBody.url} endpoint`, { error });
|
|
42
|
+
return [error.message, null, undefined];
|
|
43
|
+
}
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const errorMessage = `${response.status} ${response.statusText}`;
|
|
46
|
+
console.error(`Error in ${signedFetchBody.url} endpoint`, { response });
|
|
47
|
+
return [errorMessage, null, response.status];
|
|
48
|
+
}
|
|
49
|
+
const [parseError, body] = await tryCatch(JSON.parse(response.body || '{}'));
|
|
50
|
+
if (parseError) {
|
|
51
|
+
console.error(`Failed to parse response from ${signedFetchBody.url}`);
|
|
52
|
+
return ['Failed to parse response', null, response.status];
|
|
53
|
+
}
|
|
54
|
+
return [null, (body ?? {}), response.status];
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc2VydmVyL3V0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxXQUFXLEVBQXNCLE1BQU0scUJBQXFCLENBQUE7QUFDckUsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLFlBQVksQ0FBQTtBQUVyQzs7Ozs7O0dBTUc7QUFDSCxNQUFNLFVBQVUsY0FBYyxDQUFDLFVBQWtCO0lBQy9DLElBQUksQ0FBQyxRQUFRLEVBQUUsRUFBRTtRQUNmLE1BQU0sSUFBSSxLQUFLLENBQUMsR0FBRyxVQUFVLDBDQUEwQyxDQUFDLENBQUE7S0FDekU7QUFDSCxDQUFDO0FBYUQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxRQUFRLENBQWUsT0FBbUI7SUFDOUQsSUFBSTtRQUNGLE1BQU0sSUFBSSxHQUFHLE1BQU0sT0FBTyxDQUFBO1FBQzFCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUE7S0FDcEI7SUFBQyxPQUFPLEtBQUssRUFBRTtRQUNkLE9BQU8sQ0FBQyxLQUFVLEVBQUUsSUFBSSxDQUFDLENBQUE7S0FDMUI7QUFDSCxDQUFDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxlQUFlLENBQWMsZUFBbUM7SUFDcEYsTUFBTSxDQUFDLEtBQUssRUFBRSxRQUFRLENBQUMsR0FBRyxNQUFNLFFBQVEsQ0FBQyxXQUFXLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQTtJQUV0RSxJQUFJLEtBQUssRUFBRTtRQUNULE9BQU8sQ0FBQyxLQUFLLENBQUMsWUFBWSxlQUFlLENBQUMsR0FBRyxXQUFXLEVBQUUsRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFBO1FBQ3BFLE9BQU8sQ0FBQyxLQUFLLENBQUMsT0FBTyxFQUFFLElBQUksRUFBRSxTQUFTLENBQUMsQ0FBQTtLQUN4QztJQUVELElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxFQUFFO1FBQ2hCLE1BQU0sWUFBWSxHQUFHLEdBQUcsUUFBUSxDQUFDLE1BQU0sSUFBSSxRQUFRLENBQUMsVUFBVSxFQUFFLENBQUE7UUFDaEUsT0FBTyxDQUFDLEtBQUssQ0FBQyxZQUFZLGVBQWUsQ0FBQyxHQUFHLFdBQVcsRUFBRSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUE7UUFDdkUsT0FBTyxDQUFDLFlBQVksRUFBRSxJQUFJLEVBQUUsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFBO0tBQzdDO0lBRUQsTUFBTSxDQUFDLFVBQVUsRUFBRSxJQUFJLENBQUMsR0FBRyxNQUFNLFFBQVEsQ0FBSSxJQUFJLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxJQUFJLElBQUksSUFBSSxDQUFDLENBQUMsQ0FBQTtJQUUvRSxJQUFJLFVBQVUsRUFBRTtRQUNkLE9BQU8sQ0FBQyxLQUFLLENBQUMsaUNBQWlDLGVBQWUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFBO1FBQ3JFLE9BQU8sQ0FBQywwQkFBMEIsRUFBRSxJQUFJLEVBQUUsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFBO0tBQzNEO0lBRUQsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDLElBQUksSUFBSSxFQUFFLENBQU0sRUFBRSxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUE7QUFDbkQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHNpZ25lZEZldGNoLCBTaWduZWRGZXRjaFJlcXVlc3QgfSBmcm9tICd+c3lzdGVtL1NpZ25lZEZldGNoJ1xuaW1wb3J0IHsgaXNTZXJ2ZXIgfSBmcm9tICcuLi9uZXR3b3JrJ1xuXG4vKipcbiAqIFZhbGlkYXRlcyB0aGF0IHRoZSBjb2RlIGlzIHJ1bm5pbmcgb24gYSBzZXJ2ZXItc2lkZSBzY2VuZS5cbiAqIFRocm93cyBhbiBlcnJvciBpZiBjYWxsZWQgZnJvbSBhIGNsaWVudC1zaWRlIGNvbnRleHQuXG4gKlxuICogQHBhcmFtIG1vZHVsZU5hbWUgLSBUaGUgbmFtZSBvZiB0aGUgbW9kdWxlIGZvciB0aGUgZXJyb3IgbWVzc2FnZVxuICogQHRocm93cyBFcnJvciBpZiBub3QgcnVubmluZyBvbiBhIHNlcnZlci1zaWRlIHNjZW5lXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhc3NlcnRJc1NlcnZlcihtb2R1bGVOYW1lOiBzdHJpbmcpOiB2b2lkIHtcbiAgaWYgKCFpc1NlcnZlcigpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGAke21vZHVsZU5hbWV9IGlzIG9ubHkgYXZhaWxhYmxlIG9uIHNlcnZlci1zaWRlIHNjZW5lc2ApXG4gIH1cbn1cblxuLyoqXG4gKiBSZXN1bHQgdHlwZSBmb3Igb3BlcmF0aW9ucyB0aGF0IGNhbiBmYWlsLlxuICogUmV0dXJucyBhIHR1cGxlIG9mIFtlcnJvciwgbnVsbF0gb24gZmFpbHVyZSBvciBbbnVsbCwgZGF0YV0gb24gc3VjY2Vzcy5cbiAqL1xuZXhwb3J0IHR5cGUgUmVzdWx0PFQsIEUgPSBzdHJpbmc+ID0gW0UsIG51bGxdIHwgW251bGwsIFRdXG5cbi8qKlxuICogRXh0ZW5kZWQgcmVzdWx0IHR5cGUgdGhhdCBpbmNsdWRlcyBIVFRQIHN0YXR1cyBjb2RlIGluZm9ybWF0aW9uLlxuICovXG5leHBvcnQgdHlwZSBGZXRjaFJlc3VsdDxUPiA9IFtzdHJpbmcsIG51bGwsIG51bWJlcj9dIHwgW251bGwsIFQsIG51bWJlcl1cblxuLyoqXG4gKiBXcmFwcyBhIHByb21pc2UgdG8gY2F0Y2ggZXJyb3JzIGFuZCByZXR1cm4gYSBSZXN1bHQgdHVwbGUuXG4gKiBUaGlzIGFsbG93cyBmb3IgY2xlYW5lciBlcnJvciBoYW5kbGluZyB3aXRob3V0IHRyeS1jYXRjaCBibG9ja3MuXG4gKlxuICogQHBhcmFtIHByb21pc2UgLSBUaGUgcHJvbWlzZSB0byB3cmFwXG4gKiBAcmV0dXJucyBBIHR1cGxlIG9mIFtlcnJvciwgbnVsbF0gb24gZmFpbHVyZSBvciBbbnVsbCwgZGF0YV0gb24gc3VjY2Vzc1xuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gdHJ5Q2F0Y2g8VCwgRSA9IEVycm9yPihwcm9taXNlOiBQcm9taXNlPFQ+KTogUHJvbWlzZTxSZXN1bHQ8VCwgRT4+IHtcbiAgdHJ5IHtcbiAgICBjb25zdCBkYXRhID0gYXdhaXQgcHJvbWlzZVxuICAgIHJldHVybiBbbnVsbCwgZGF0YV1cbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICByZXR1cm4gW2Vycm9yIGFzIEUsIG51bGxdXG4gIH1cbn1cblxuLyoqXG4gKiBXcmFwcyBzaWduZWRGZXRjaCB3aXRoIGF1dG9tYXRpYyBlcnJvciBoYW5kbGluZyBhbmQgSlNPTiBwYXJzaW5nLlxuICogUmV0dXJucyBhIEZldGNoUmVzdWx0IHR1cGxlIHdpdGggcGFyc2VkIEpTT04gZGF0YSBvciBlcnJvciBtZXNzYWdlIGFuZCBzdGF0dXMgY29kZS5cbiAqXG4gKiBAcGFyYW0gc2lnbmVkRmV0Y2hCb2R5IC0gVGhlIHNpZ25lZEZldGNoIHJlcXVlc3QgY29uZmlndXJhdGlvblxuICogQHJldHVybnMgQSB0dXBsZSBvZiBbZXJyb3IsIG51bGwsIHN0YXR1c0NvZGU/XSBvbiBmYWlsdXJlIG9yIFtudWxsLCBkYXRhLCBzdGF0dXNDb2RlXSBvbiBzdWNjZXNzXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiB3cmFwU2lnbmVkRmV0Y2g8VCA9IHVua25vd24+KHNpZ25lZEZldGNoQm9keTogU2lnbmVkRmV0Y2hSZXF1ZXN0KTogUHJvbWlzZTxGZXRjaFJlc3VsdDxUPj4ge1xuICBjb25zdCBbZXJyb3IsIHJlc3BvbnNlXSA9IGF3YWl0IHRyeUNhdGNoKHNpZ25lZEZldGNoKHNpZ25lZEZldGNoQm9keSkpXG5cbiAgaWYgKGVycm9yKSB7XG4gICAgY29uc29sZS5lcnJvcihgRXJyb3IgaW4gJHtzaWduZWRGZXRjaEJvZHkudXJsfSBlbmRwb2ludGAsIHsgZXJyb3IgfSlcbiAgICByZXR1cm4gW2Vycm9yLm1lc3NhZ2UsIG51bGwsIHVuZGVmaW5lZF1cbiAgfVxuXG4gIGlmICghcmVzcG9uc2Uub2spIHtcbiAgICBjb25zdCBlcnJvck1lc3NhZ2UgPSBgJHtyZXNwb25zZS5zdGF0dXN9ICR7cmVzcG9uc2Uuc3RhdHVzVGV4dH1gXG4gICAgY29uc29sZS5lcnJvcihgRXJyb3IgaW4gJHtzaWduZWRGZXRjaEJvZHkudXJsfSBlbmRwb2ludGAsIHsgcmVzcG9uc2UgfSlcbiAgICByZXR1cm4gW2Vycm9yTWVzc2FnZSwgbnVsbCwgcmVzcG9uc2Uuc3RhdHVzXVxuICB9XG5cbiAgY29uc3QgW3BhcnNlRXJyb3IsIGJvZHldID0gYXdhaXQgdHJ5Q2F0Y2g8VD4oSlNPTi5wYXJzZShyZXNwb25zZS5ib2R5IHx8ICd7fScpKVxuXG4gIGlmIChwYXJzZUVycm9yKSB7XG4gICAgY29uc29sZS5lcnJvcihgRmFpbGVkIHRvIHBhcnNlIHJlc3BvbnNlIGZyb20gJHtzaWduZWRGZXRjaEJvZHkudXJsfWApXG4gICAgcmV0dXJuIFsnRmFpbGVkIHRvIHBhcnNlIHJlc3BvbnNlJywgbnVsbCwgcmVzcG9uc2Uuc3RhdHVzXVxuICB9XG5cbiAgcmV0dXJuIFtudWxsLCAoYm9keSA/PyB7fSkgYXMgVCwgcmVzcG9uc2Uuc3RhdHVzXVxufVxuIl19
|
package/src/atom.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import future, { IFuture } from './future'
|
|
2
|
+
|
|
3
|
+
// atom value wrapper like in clojure
|
|
4
|
+
|
|
5
|
+
// Simple Observable implementation to replace Babylon.js dependency
|
|
6
|
+
class SimpleObservable<T> {
|
|
7
|
+
private observers: ((value: T) => void)[] = []
|
|
8
|
+
private onceObservers: ((value: T) => void)[] = []
|
|
9
|
+
|
|
10
|
+
add(callback: (value: T) => void): (value: T) => void {
|
|
11
|
+
this.observers.push(callback)
|
|
12
|
+
return callback
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
addOnce(callback: (value: T) => void): void {
|
|
16
|
+
this.onceObservers.push(callback)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
remove(callback: (value: T) => void): void {
|
|
20
|
+
const index = this.observers.indexOf(callback)
|
|
21
|
+
if (index > -1) {
|
|
22
|
+
this.observers.splice(index, 1)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
notifyObservers(value: T): void {
|
|
27
|
+
this.observers.forEach((observer) => observer(value))
|
|
28
|
+
|
|
29
|
+
if (this.onceObservers.length > 0) {
|
|
30
|
+
this.onceObservers.forEach((observer) => observer(value))
|
|
31
|
+
this.onceObservers.length = 0
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const EMPTY = Symbol('empty')
|
|
37
|
+
type EMPTY = typeof EMPTY
|
|
38
|
+
|
|
39
|
+
export type Atom<T> = {
|
|
40
|
+
deref(): Promise<T>
|
|
41
|
+
getOrNull(): T | null
|
|
42
|
+
observable: SimpleObservable<T>
|
|
43
|
+
swap(value: T): T | void
|
|
44
|
+
pipe(fn: (value: T) => void | Promise<void>): Promise<void>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function Atom<T>(initialValue: T | EMPTY = EMPTY): Atom<T> {
|
|
48
|
+
const observable = new SimpleObservable<T>()
|
|
49
|
+
let value: T | EMPTY = initialValue
|
|
50
|
+
const valueFutures: IFuture<T>[] = []
|
|
51
|
+
|
|
52
|
+
observable.addOnce((value) => {
|
|
53
|
+
valueFutures.forEach(($) => $.resolve(value))
|
|
54
|
+
valueFutures.length = 0
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
async pipe(fn) {
|
|
59
|
+
observable.add(async (t) => {
|
|
60
|
+
try {
|
|
61
|
+
await fn(t)
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error(err)
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
if (value !== EMPTY) {
|
|
67
|
+
try {
|
|
68
|
+
await fn(value)
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error(err)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
deref() {
|
|
75
|
+
if (value === EMPTY) {
|
|
76
|
+
const ret = future<T>()
|
|
77
|
+
valueFutures.push(ret)
|
|
78
|
+
return ret
|
|
79
|
+
}
|
|
80
|
+
return Promise.resolve(value)
|
|
81
|
+
},
|
|
82
|
+
getOrNull() {
|
|
83
|
+
if (value === EMPTY) {
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
return value
|
|
87
|
+
},
|
|
88
|
+
observable,
|
|
89
|
+
swap(newValue) {
|
|
90
|
+
const oldValue = value
|
|
91
|
+
if (newValue !== value) {
|
|
92
|
+
value = newValue
|
|
93
|
+
observable.notifyObservers(value)
|
|
94
|
+
}
|
|
95
|
+
return oldValue === EMPTY ? undefined : oldValue
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/future.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type IFuture<T> = Promise<T> & {
|
|
2
|
+
resolve: (x: T) => void
|
|
3
|
+
reject: (x: Error) => void
|
|
4
|
+
finally: (fn: () => void) => void
|
|
5
|
+
isPending: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function future<T = any>(): IFuture<T> {
|
|
9
|
+
let resolver: (x: T) => void
|
|
10
|
+
let rejecter: (x: Error) => void
|
|
11
|
+
|
|
12
|
+
const promise: any = new Promise((ok, err) => {
|
|
13
|
+
resolver = (x: T) => {
|
|
14
|
+
ok(x)
|
|
15
|
+
promise.isPending = false
|
|
16
|
+
}
|
|
17
|
+
rejecter = (x: Error) => {
|
|
18
|
+
err(x)
|
|
19
|
+
promise.isPending = false
|
|
20
|
+
}
|
|
21
|
+
}).catch((e) => Promise.reject(e))
|
|
22
|
+
|
|
23
|
+
promise.resolve = resolver!
|
|
24
|
+
promise.reject = rejecter!
|
|
25
|
+
|
|
26
|
+
if (!('finally' in promise)) {
|
|
27
|
+
promise.finally = (fn: any) => {
|
|
28
|
+
promise.then(fn)
|
|
29
|
+
promise.catch(fn)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
promise.isPending = true
|
|
34
|
+
|
|
35
|
+
return promise as IFuture<T>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default future
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @alpha THIS FILE INITIALIZES THE DECENTRALAND RUNTIME. WILL CHANGE SOON */
|
|
2
2
|
import { Composite, engine } from '@dcl/ecs'
|
|
3
3
|
import { crdtGetState, crdtSendToRenderer, sendBatch } from '~system/EngineApi'
|
|
4
|
-
import { createRendererTransport
|
|
4
|
+
import { createRendererTransport } from './internal/transports/rendererTransport'
|
|
5
5
|
import { pollEvents } from './observables'
|
|
6
6
|
import { compositeProvider } from './composite-provider'
|
|
7
7
|
|
|
@@ -21,7 +21,6 @@ export async function onUpdate(deltaTime: number) {
|
|
|
21
21
|
* Function that is called before the first update and after the evaluation of the code.
|
|
22
22
|
*/
|
|
23
23
|
export async function onStart() {
|
|
24
|
-
const rendererMessageInspector: EngineApiRendererInspector = (globalThis as any).rendererMessageInspector
|
|
25
24
|
const response = await crdtGetState({ data: new Uint8Array() })
|
|
26
25
|
|
|
27
26
|
// when this condition is true something like `main.crdt` was pre-loaded from the runtime, we don't need to instance the main.composite
|
|
@@ -39,9 +38,6 @@ export async function onStart() {
|
|
|
39
38
|
|
|
40
39
|
if (!!rendererTransport.onmessage) {
|
|
41
40
|
if (response && response.data && response.data.length) {
|
|
42
|
-
if (rendererMessageInspector) {
|
|
43
|
-
rendererMessageInspector({ message: response.data, type: 'first-receive' })
|
|
44
|
-
}
|
|
45
41
|
for (const byteArray of response.data) {
|
|
46
42
|
rendererTransport.onmessage(byteArray)
|
|
47
43
|
}
|
|
@@ -2,29 +2,16 @@ import { Transport, TransportMessage } from '@dcl/ecs'
|
|
|
2
2
|
import { MAX_STATIC_COMPONENT } from '@dcl/ecs/dist/components/component-number'
|
|
3
3
|
import type { CrdtSendToRendererRequest, CrdtSendToResponse } from '~system/EngineApi'
|
|
4
4
|
|
|
5
|
-
export type EngineApiRendererInspector =
|
|
6
|
-
| ((data: { message: Uint8Array[]; type: 'send' | 'receive' | 'first-receive' }) => void)
|
|
7
|
-
| undefined
|
|
8
|
-
|
|
9
5
|
export type EngineApiForTransport = {
|
|
10
6
|
crdtSendToRenderer(body: CrdtSendToRendererRequest): Promise<CrdtSendToResponse>
|
|
11
7
|
}
|
|
12
8
|
|
|
13
9
|
export function createRendererTransport(engineApi: EngineApiForTransport): Transport {
|
|
14
10
|
async function sendToRenderer(message: Uint8Array) {
|
|
15
|
-
const rendererMessageInspector: EngineApiRendererInspector = (globalThis as any).rendererMessageInspector
|
|
16
|
-
|
|
17
|
-
if (rendererMessageInspector) {
|
|
18
|
-
rendererMessageInspector({ message: [message], type: 'send' })
|
|
19
|
-
}
|
|
20
11
|
const response = await engineApi.crdtSendToRenderer({
|
|
21
12
|
data: new Uint8Array(message)
|
|
22
13
|
})
|
|
23
14
|
if (response && response.data && response.data.length) {
|
|
24
|
-
if (rendererMessageInspector) {
|
|
25
|
-
rendererMessageInspector({ message: response.data, type: 'receive' })
|
|
26
|
-
}
|
|
27
|
-
|
|
28
15
|
if (rendererTransport.onmessage) {
|
|
29
16
|
for (const byteArray of response.data) {
|
|
30
17
|
rendererTransport.onmessage(byteArray)
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { ReadWriteByteBuffer } from '@dcl/ecs/dist/serialization/ByteBuffer'
|
|
2
2
|
|
|
3
3
|
export enum CommsMessage {
|
|
4
|
-
CRDT =
|
|
5
|
-
REQ_CRDT_STATE =
|
|
6
|
-
RES_CRDT_STATE =
|
|
4
|
+
CRDT = 7,
|
|
5
|
+
REQ_CRDT_STATE = 8,
|
|
6
|
+
RES_CRDT_STATE = 9,
|
|
7
|
+
CRDT_SERVER = 4,
|
|
8
|
+
CRDT_AUTHORITATIVE = 5,
|
|
9
|
+
CUSTOM_EVENT = 6
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
export function BinaryMessageBus<T extends CommsMessage>(
|
|
@@ -20,7 +23,9 @@ export function BinaryMessageBus<T extends CommsMessage>(
|
|
|
20
23
|
__processMessages: (messages: Uint8Array[]) => {
|
|
21
24
|
for (const message of messages) {
|
|
22
25
|
const commsMsg = decodeCommsMessage<T>(message)
|
|
23
|
-
if (!commsMsg)
|
|
26
|
+
if (!commsMsg) {
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
24
29
|
const { sender, messageType, data } = commsMsg
|
|
25
30
|
const fn = mapping.get(messageType)
|
|
26
31
|
if (fn) fn(data, sender)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ReadWriteByteBuffer } from '@dcl/ecs/dist/serialization/ByteBuffer'
|
|
2
|
+
import { readMessages } from './server/utils'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Chunks CRDT messages from a Uint8Array buffer, respecting message boundaries
|
|
6
|
+
* Uses the comprehensive readMessages function that handles all message types
|
|
7
|
+
*/
|
|
8
|
+
export function chunkCrdtMessages(data: Uint8Array, maxSizeKB: number = 12): Uint8Array[] {
|
|
9
|
+
if (data.length === 0) {
|
|
10
|
+
return []
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const networkBuffer = new ReadWriteByteBuffer()
|
|
14
|
+
const chunks: Uint8Array[] = []
|
|
15
|
+
|
|
16
|
+
for (const message of readMessages(data)) {
|
|
17
|
+
// Check if adding this message would exceed the size limit
|
|
18
|
+
const currentBufferSize = networkBuffer.toBinary().byteLength
|
|
19
|
+
const messageSize = message.messageBuffer.byteLength
|
|
20
|
+
|
|
21
|
+
if ((currentBufferSize + messageSize) / 1024 > maxSizeKB) {
|
|
22
|
+
// If the current buffer has content, save it as a chunk
|
|
23
|
+
if (currentBufferSize > 0) {
|
|
24
|
+
chunks.push(networkBuffer.toCopiedBinary())
|
|
25
|
+
networkBuffer.resetBuffer()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// If the message itself is larger than the limit, skip it
|
|
29
|
+
if (messageSize / 1024 > maxSizeKB) {
|
|
30
|
+
console.error(`Message too large (${messageSize} bytes), skipping CRDT message`)
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Add message to current buffer
|
|
36
|
+
networkBuffer.writeBuffer(message.messageBuffer, false)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Add any remaining data as the final chunk
|
|
40
|
+
if (networkBuffer.currentWriteOffset() > 0) {
|
|
41
|
+
chunks.push(networkBuffer.toBinary())
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return chunks
|
|
45
|
+
}
|
package/src/network/entities.ts
CHANGED
|
@@ -112,8 +112,16 @@ export function entityUtils(engine: IEngine, profile: IProfile) {
|
|
|
112
112
|
if (!Transform.getOrNull(entity)) {
|
|
113
113
|
Transform.create(entity)
|
|
114
114
|
} else {
|
|
115
|
-
//
|
|
116
|
-
|
|
115
|
+
// Force a tick update of the transform so the renderer receives the NEW parent.
|
|
116
|
+
// createOrReplace bypasses the CRDT unchanged-data suppression optimization,
|
|
117
|
+
// ensuring the renderer transport can inject the network parent into the message.
|
|
118
|
+
const t = Transform.get(entity)
|
|
119
|
+
Transform.createOrReplace(entity, {
|
|
120
|
+
position: { x: t.position.x, y: t.position.y, z: t.position.z },
|
|
121
|
+
rotation: { x: t.rotation.x, y: t.rotation.y, z: t.rotation.z, w: t.rotation.w },
|
|
122
|
+
scale: { x: t.scale.x, y: t.scale.y, z: t.scale.z },
|
|
123
|
+
parent: t.parent
|
|
124
|
+
})
|
|
117
125
|
}
|
|
118
126
|
}
|
|
119
127
|
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { IEngine } from '@dcl/ecs'
|
|
2
|
+
import { CommsMessage } from '../binary-message-bus'
|
|
3
|
+
import { AUTH_SERVER_PEER_ID } from '../message-bus-sync'
|
|
4
|
+
import { EventTypes, EventSchemaRegistry } from './registry'
|
|
5
|
+
import { encodeEvent, decodeEvent } from './protocol'
|
|
6
|
+
import { Atom } from '../../atom'
|
|
7
|
+
import { future, IFuture } from '../../future'
|
|
8
|
+
|
|
9
|
+
// Context provided to server-side event handlers
|
|
10
|
+
export type EventContext = {
|
|
11
|
+
from: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Event callback type - server gets context, client doesn't
|
|
15
|
+
export type EventCallback<T> = (data: T, context?: EventContext) => void
|
|
16
|
+
|
|
17
|
+
// Options for sending events
|
|
18
|
+
export type SendOptions = {
|
|
19
|
+
to?: string[] // Target specific peers (server only)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Room provides type-safe communication between clients and server
|
|
24
|
+
* Uses binary serialization with Schema definitions for efficiency
|
|
25
|
+
*/
|
|
26
|
+
type QueuedMessage<T extends EventSchemaRegistry = EventSchemaRegistry> = {
|
|
27
|
+
[K in keyof T]: {
|
|
28
|
+
eventType: K
|
|
29
|
+
data: EventTypes<T>[K]
|
|
30
|
+
options?: SendOptions
|
|
31
|
+
}
|
|
32
|
+
}[keyof T]
|
|
33
|
+
|
|
34
|
+
export class Room<T extends EventSchemaRegistry = EventSchemaRegistry> {
|
|
35
|
+
private listeners = new Map<keyof T, Set<EventCallback<any>>>()
|
|
36
|
+
private binaryMessageBus: any
|
|
37
|
+
private isServerFuture: IFuture<boolean> = future()
|
|
38
|
+
private isRoomReadyAtom: Atom<boolean>
|
|
39
|
+
private messageQueue: QueuedMessage<T>[] = []
|
|
40
|
+
private isProcessingQueue = false
|
|
41
|
+
|
|
42
|
+
constructor(_engine: IEngine, binaryMessageBus: any, isServerFn: Atom<boolean>, isRoomReadyAtom: Atom<boolean>) {
|
|
43
|
+
void isServerFn.deref().then(($) => this.isServerFuture.resolve($))
|
|
44
|
+
|
|
45
|
+
this.binaryMessageBus = binaryMessageBus
|
|
46
|
+
this.isRoomReadyAtom = isRoomReadyAtom
|
|
47
|
+
|
|
48
|
+
// Subscribe to room readiness changes to flush queue
|
|
49
|
+
this.isRoomReadyAtom.observable.add((isReady) => {
|
|
50
|
+
if (isReady && this.messageQueue.length > 0) {
|
|
51
|
+
void this.flushMessageQueue()
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
// Listen for CUSTOM_EVENT messages
|
|
55
|
+
binaryMessageBus.on(CommsMessage.CUSTOM_EVENT, (data: Uint8Array, sender: string) => {
|
|
56
|
+
try {
|
|
57
|
+
const { eventType, payload } = decodeEvent(data, globalEventRegistry)
|
|
58
|
+
const callbacks = this.listeners.get(eventType)
|
|
59
|
+
|
|
60
|
+
if (callbacks) {
|
|
61
|
+
callbacks.forEach(async (cb) => {
|
|
62
|
+
if (await this.isServerFuture) {
|
|
63
|
+
// Server handlers receive sender context
|
|
64
|
+
cb(payload, { from: sender })
|
|
65
|
+
} else if (sender === AUTH_SERVER_PEER_ID) {
|
|
66
|
+
// Client only processes events from authoritative server
|
|
67
|
+
cb(payload)
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('[EventBus] Failed to decode event:', error)
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Flush queued messages when room becomes ready
|
|
79
|
+
*/
|
|
80
|
+
private async flushMessageQueue(): Promise<void> {
|
|
81
|
+
if (this.isProcessingQueue || this.messageQueue.length === 0) return
|
|
82
|
+
|
|
83
|
+
this.isProcessingQueue = true
|
|
84
|
+
|
|
85
|
+
// Copy and clear the queue to avoid mutation during iteration
|
|
86
|
+
const messages = [...this.messageQueue]
|
|
87
|
+
this.messageQueue.length = 0
|
|
88
|
+
|
|
89
|
+
// Re-send all queued messages
|
|
90
|
+
for (const message of messages) {
|
|
91
|
+
await this.send(message.eventType, message.data, message.options)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.isProcessingQueue = false
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Send an event
|
|
99
|
+
* @param eventType - The type of event from the registry
|
|
100
|
+
* @param data - The event data matching the schema
|
|
101
|
+
* @param options - Optional send options (server only)
|
|
102
|
+
*
|
|
103
|
+
* Messages are automatically queued if the room is not ready and sent once connected.
|
|
104
|
+
*/
|
|
105
|
+
async send<K extends keyof T>(eventType: K, data: EventTypes<T>[K], options?: SendOptions): Promise<void> {
|
|
106
|
+
try {
|
|
107
|
+
const isRoomReady = this.isRoomReadyAtom.getOrNull() ?? false
|
|
108
|
+
|
|
109
|
+
// If room is not ready, queue the message with original params
|
|
110
|
+
if (!isRoomReady) {
|
|
111
|
+
this.messageQueue.push({
|
|
112
|
+
eventType,
|
|
113
|
+
data,
|
|
114
|
+
options
|
|
115
|
+
})
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Room is ready, send immediately
|
|
120
|
+
const buffer = encodeEvent(eventType as string, data, globalEventRegistry)
|
|
121
|
+
|
|
122
|
+
if (await this.isServerFuture) {
|
|
123
|
+
// Server can send to specific clients or broadcast
|
|
124
|
+
this.binaryMessageBus.emit(CommsMessage.CUSTOM_EVENT, buffer, options?.to)
|
|
125
|
+
} else {
|
|
126
|
+
// Client always sends to authoritative server
|
|
127
|
+
this.binaryMessageBus.emit(CommsMessage.CUSTOM_EVENT, buffer)
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error(`[EventBus] Failed to send event '${String(eventType)}':`, error)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Listen for an event
|
|
136
|
+
* @param eventType - The type of event to listen for
|
|
137
|
+
* @param callback - Callback to handle the event
|
|
138
|
+
* @returns Unsubscribe function
|
|
139
|
+
*/
|
|
140
|
+
onMessage<K extends keyof T>(eventType: K, callback: EventCallback<EventTypes<T>[K]>): () => void {
|
|
141
|
+
if (!this.listeners.has(eventType)) {
|
|
142
|
+
this.listeners.set(eventType, new Set())
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const callbacks = this.listeners.get(eventType)!
|
|
146
|
+
callbacks.add(callback)
|
|
147
|
+
|
|
148
|
+
// Return unsubscribe function
|
|
149
|
+
return () => {
|
|
150
|
+
callbacks.delete(callback)
|
|
151
|
+
if (callbacks.size === 0) {
|
|
152
|
+
this.listeners.delete(eventType)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Remove all listeners for a specific event type
|
|
159
|
+
* @param eventType - The type of event to clear listeners for
|
|
160
|
+
*/
|
|
161
|
+
clear<K extends keyof T>(eventType?: K): void {
|
|
162
|
+
if (eventType) {
|
|
163
|
+
this.listeners.delete(eventType)
|
|
164
|
+
} else {
|
|
165
|
+
this.listeners.clear()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get the number of listeners for an event type
|
|
171
|
+
* @param eventType - The type of event to check
|
|
172
|
+
* @returns Number of registered listeners
|
|
173
|
+
*/
|
|
174
|
+
listenerCount<K extends keyof T>(eventType: K): number {
|
|
175
|
+
return this.listeners.get(eventType)?.size ?? 0
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Check if the room is ready to send messages
|
|
180
|
+
* @returns true if messages will be sent immediately, false if they will be queued
|
|
181
|
+
*/
|
|
182
|
+
isReady(): boolean {
|
|
183
|
+
return this.isRoomReadyAtom.getOrNull() ?? false
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Subscribe to room readiness changes
|
|
188
|
+
* @param callback - Called when room becomes ready or disconnected
|
|
189
|
+
* @returns Unsubscribe function
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```ts
|
|
193
|
+
* const unsubscribe = room.onReady((isReady) => {
|
|
194
|
+
* if (isReady) {
|
|
195
|
+
* console.log('Room connected!')
|
|
196
|
+
* } else {
|
|
197
|
+
* console.log('Room disconnected')
|
|
198
|
+
* }
|
|
199
|
+
* })
|
|
200
|
+
*
|
|
201
|
+
* // Later: unsubscribe()
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
onReady(callback: (isReady: boolean) => void): () => void {
|
|
205
|
+
const observer = this.isRoomReadyAtom.observable.add((isReady) => {
|
|
206
|
+
callback(isReady)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
return () => {
|
|
210
|
+
if (observer) {
|
|
211
|
+
this.isRoomReadyAtom.observable.remove(observer)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Global registry for user-defined events
|
|
218
|
+
const globalEventRegistry: EventSchemaRegistry = {}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get the global event registry (internal use)
|
|
222
|
+
* @internal
|
|
223
|
+
*/
|
|
224
|
+
export function getEventRegistry(): EventSchemaRegistry {
|
|
225
|
+
return globalEventRegistry
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Global room instance (created by addSyncTransport)
|
|
229
|
+
let globalRoom: Room | null = null
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Set the global room instance (internal use)
|
|
233
|
+
* @internal
|
|
234
|
+
*/
|
|
235
|
+
export function setGlobalRoom(roomInstance: Room): void {
|
|
236
|
+
globalRoom = roomInstance
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Register message schemas for use with the room
|
|
241
|
+
* Call this before main() to define your custom messages
|
|
242
|
+
* @param messages - Object containing your message schemas
|
|
243
|
+
* @returns Typed room instance for your registered messages
|
|
244
|
+
*/
|
|
245
|
+
export function registerMessages<T extends EventSchemaRegistry>(messages: T): Room<T> {
|
|
246
|
+
Object.assign(globalEventRegistry, messages)
|
|
247
|
+
if (!globalRoom) {
|
|
248
|
+
throw new Error('Room not initialized. Make sure the SDK network transport is initialized.')
|
|
249
|
+
}
|
|
250
|
+
// Update the room registry
|
|
251
|
+
;(globalRoom as any).registry = globalEventRegistry
|
|
252
|
+
return globalRoom as unknown as Room<T>
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Get a typed version of the global room
|
|
257
|
+
* Use this when you want the room with your specific message types
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```typescript
|
|
261
|
+
* const MyMessages = { ... }
|
|
262
|
+
* registerMessages(MyMessages) // Register first
|
|
263
|
+
* const room = getRoom<typeof MyMessages>() // Then get typed version
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
export function getRoom<T extends EventSchemaRegistry>(): Room<T> {
|
|
267
|
+
if (!globalRoom) {
|
|
268
|
+
throw new Error('Room not initialized. Make sure the SDK network transport is initialized.')
|
|
269
|
+
}
|
|
270
|
+
return globalRoom as unknown as Room<T>
|
|
271
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Room - Multiplayer messaging for SDK7
|
|
3
|
+
*
|
|
4
|
+
* Simple room-based communication system for multiplayer scenes.
|
|
5
|
+
*
|
|
6
|
+
* @example Basic usage:
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { registerMessages, getRoom, isServer } from '@dcl/sdk/network/events'
|
|
9
|
+
*
|
|
10
|
+
* const MyMessages = {
|
|
11
|
+
* playerJump: Schemas.Map({ playerId: Schemas.String, position: Schemas.Vector3 }),
|
|
12
|
+
* gameUpdate: Schemas.Map({ message: Schemas.String })
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* // Option 1: Register and get typed room
|
|
16
|
+
* const room = registerMessages(MyMessages)
|
|
17
|
+
*
|
|
18
|
+
* // Option 2: Register first, then get typed room
|
|
19
|
+
* // registerMessages(MyMessages)
|
|
20
|
+
* // const room = getRoom<typeof MyMessages>()
|
|
21
|
+
*
|
|
22
|
+
* export function main() {
|
|
23
|
+
* if (isServer()) {
|
|
24
|
+
* room.onMessage('playerJump', (data, context) => {
|
|
25
|
+
* console.log(`Player ${context?.from} jumped`)
|
|
26
|
+
* room.send('gameUpdate', { message: 'Player jumped!' })
|
|
27
|
+
* })
|
|
28
|
+
* } else {
|
|
29
|
+
* room.send('playerJump', { playerId: 'me', position: { x: 1, y: 2, z: 3 } })
|
|
30
|
+
* room.onMessage('gameUpdate', (data) => {
|
|
31
|
+
* console.log('Server says:', data.message)
|
|
32
|
+
* })
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @packageDocumentation
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
// Import public API and types
|
|
41
|
+
import { registerMessages, getRoom, EventContext } from './implementation'
|
|
42
|
+
import { EventSchemaRegistry } from './registry'
|
|
43
|
+
|
|
44
|
+
// Re-export public API - only what users need
|
|
45
|
+
export { registerMessages, getRoom }
|
|
46
|
+
|
|
47
|
+
// Re-export types that users need
|
|
48
|
+
export type { EventContext, EventSchemaRegistry }
|