From 6d2ec97a241339671c7b75c6971003d24d3fe233 Mon Sep 17 00:00:00 2001 From: Gon Pombo Date: Wed, 24 Apr 2024 12:14:43 -0300 Subject: [PATCH] refactor observables. (#928) * refactor observables: use systems & components instead of sendBatch events * add realminfo & playerclicked component * add missing observables * add fallback to sdk6 obserables pollEvents * add missing comms * replace player clicked with ponterevents * fix build * add tests * remove non used code * update protocol version * add fallback support because renderer release --- package-lock.json | 14 +- package.json | 2 +- .../etc/playground-assets.api.md | 171 ++----- packages/@dcl/sdk-commands/package-lock.json | 14 +- packages/@dcl/sdk-commands/package.json | 2 +- .../internal/transports/rendererTransport.ts | 1 - packages/@dcl/sdk/src/observables.ts | 466 ++++++------------ packages/@dcl/sdk/src/players/index.ts | 1 + test/ecs/components/RealmInfo.spec.ts | 27 + test/ecs/runtime.spec.ts | 115 ----- 10 files changed, 235 insertions(+), 578 deletions(-) create mode 100644 test/ecs/components/RealmInfo.spec.ts delete mode 100644 test/ecs/runtime.spec.ts diff --git a/package-lock.json b/package-lock.json index 4c987fbf8..c46051d3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "license": "Apache-2.0", "dependencies": { "@actions/core": "^1.10.0", - "@dcl/protocol": "^1.0.0-8691799990.commit-4ba546c", + "@dcl/protocol": "1.0.0-8758013256.commit-44aab53", "@dcl/quickjs-emscripten": "^0.21.0-3680274614.commit-1808aa1", "@dcl/ts-proto": "1.153.0", "@types/fs-extra": "^9.0.12", @@ -577,9 +577,9 @@ } }, "node_modules/@dcl/protocol": { - "version": "1.0.0-8691799990.commit-4ba546c", - "resolved": "https://registry.npmjs.org/@dcl/protocol/-/protocol-1.0.0-8691799990.commit-4ba546c.tgz", - "integrity": "sha512-hU/8zZXdjgRSUvfi1QRL6YAMF/8QDS/hhh8cs4bE2dw9obXTziyekiR7IEwAOmF60wt/u6+s7JLIacHd35G+HQ==", + "version": "1.0.0-8758013256.commit-44aab53", + "resolved": "https://registry.npmjs.org/@dcl/protocol/-/protocol-1.0.0-8758013256.commit-44aab53.tgz", + "integrity": "sha512-VbrbfUEOLIOiQjN2c53RSg1lNNkvt0+ow+d27c+O7RSyjqDyZZkX2uMW4kdYN1TOk45OrvxCyrrxdYN0ZXW52Q==", "dependencies": { "@dcl/ts-proto": "1.154.0" } @@ -8232,9 +8232,9 @@ } }, "@dcl/protocol": { - "version": "1.0.0-8691799990.commit-4ba546c", - "resolved": "https://registry.npmjs.org/@dcl/protocol/-/protocol-1.0.0-8691799990.commit-4ba546c.tgz", - "integrity": "sha512-hU/8zZXdjgRSUvfi1QRL6YAMF/8QDS/hhh8cs4bE2dw9obXTziyekiR7IEwAOmF60wt/u6+s7JLIacHd35G+HQ==", + "version": "1.0.0-8758013256.commit-44aab53", + "resolved": "https://registry.npmjs.org/@dcl/protocol/-/protocol-1.0.0-8758013256.commit-44aab53.tgz", + "integrity": "sha512-VbrbfUEOLIOiQjN2c53RSg1lNNkvt0+ow+d27c+O7RSyjqDyZZkX2uMW4kdYN1TOk45OrvxCyrrxdYN0ZXW52Q==", "requires": { "@dcl/ts-proto": "1.154.0" }, diff --git a/package.json b/package.json index f783a0621..a83adfdc4 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "bugs": "https://github.com/decentraland/js-sdk-toolchain/issues", "dependencies": { "@actions/core": "^1.10.0", - "@dcl/protocol": "^1.0.0-8691799990.commit-4ba546c", + "@dcl/protocol": "1.0.0-8758013256.commit-44aab53", "@dcl/quickjs-emscripten": "^0.21.0-3680274614.commit-1808aa1", "@dcl/ts-proto": "1.153.0", "@types/fs-extra": "^9.0.12", diff --git a/packages/@dcl/playground-assets/etc/playground-assets.api.md b/packages/@dcl/playground-assets/etc/playground-assets.api.md index 963ccacc2..a314701cb 100644 --- a/packages/@dcl/playground-assets/etc/playground-assets.api.md +++ b/packages/@dcl/playground-assets/etc/playground-assets.api.md @@ -564,6 +564,7 @@ export const componentDefinitionByName: { "core::PointerLock": LwwComponentGetter>; "core::Raycast": LwwComponentGetter>; "core::RaycastResult": LwwComponentGetter>; + "core::RealmInfo": LwwComponentGetter>; "core::TextShape": LwwComponentGetter>; "core::Tween": LwwComponentGetter>; "core::TweenSequence": LwwComponentGetter>; @@ -982,12 +983,6 @@ export function Engine(options?: IEngineOptions): IEngine; // @public export const engine: IEngine; -// @public (undocumented) -export type EngineEvent = { - type: T; - data: Readonly; -}; - // @public (undocumented) export const EngineInfo: LastWriteWinElementSetComponentDefinition; @@ -1097,24 +1092,6 @@ export function getComponentEntityTree(engine: Pick; -// @public (undocumented) -export type GizmoDragEndEvent = { - type: 'gizmoDragEnded'; - transforms: Array<{ - position: Vector3Type; - rotation: QuaternionType; - scale: Vector3Type; - entityId: unknown; - }>; -}; - -// @public (undocumented) -export type GizmoSelectedEvent = { - type: 'gizmoSelected'; - gizmoType: 'MOVE' | 'ROTATE' | 'SCALE' | 'NONE'; - entities: string[]; -}; - // Warning: (ae-missing-release-tag) "GlobalDirectionRaycastOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1127,11 +1104,6 @@ export type GlobalDirectionRaycastSystemOptions = { direction?: PBVector3; }; -// @public (undocumented) -export type GlobalInputEventResult = InputEventResult & { - type: 0 | 1; -}; - // Warning: (ae-missing-release-tag) "GlobalTargetRaycastOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1219,88 +1191,22 @@ export type IEventNames = keyof IEvents; // @public export interface IEvents { - actionButtonEvent: GlobalInputEventResult; - builderSceneStart: unknown; - builderSceneUnloaded: unknown; - cameraModeChanged: { - cameraMode: 0 | 1 | 2; - }; - chatMessage: { - id: string; - sender: string; - message: string; - isCommand: boolean; - }; comms: { sender: string; message: string; }; - entitiesOutOfBoundaries: { - entities: string[]; - }; - entityBackInScene: { - entityId: unknown; - }; - entityOutOfScene: { - entityId: unknown; - }; - // (undocumented) - externalAction: { - type: string; - [key: string]: any; - }; - gizmoEvent: GizmoDragEndEvent | GizmoSelectedEvent; - idleStateChanged: { - isIdle: boolean; - }; - // (undocumented) - limitsExceeded: { - given: Record; - limit: Record; - }; - // (undocumented) - metricsUpdate: { - given: Record; - limit: Record; - }; - onAnimationEnd: { - clipName: string; - }; - onBlur: { - entityId: unknown; - pointerId: number; - }; - onChange: { - value?: any; - pointerId?: number; - }; - onClick: { - entityId: unknown; - }; - onEnter: unknown; onEnterScene: { userId: string; }; - onFocus: { - entityId: unknown; - pointerId: number; - }; onLeaveScene: { userId: string; }; - onPointerLock: { - locked?: boolean; - }; onRealmChanged: { domain: string; room: string; serverName: string; displayName: string; }; - // (undocumented) - onTextSubmit: { - text: string; - }; playerClicked: { userId: string; ray: { @@ -1319,37 +1225,11 @@ export interface IEvents { playerExpression: { expressionId: string; }; - pointerDown: InputEventResult; - // @deprecated - pointerEvent: GlobalInputEventResult; - pointerHoverEnter: unknown; - pointerHoverExit: unknown; - pointerUp: InputEventResult; - positionChanged: { - position: Vector3Type; - cameraPosition: Vector3Type; - playerHeight: number; - }; profileChanged: { ethAddress: string; version: number; }; - raycastResponse: RaycastResponsePayload; - rotationChanged: { - rotation: Vector3Type; - quaternion: QuaternionType; - }; sceneStart: unknown; - // (undocumented) - stateEvent: { - type: string; - payload: any; - }; - // (undocumented) - uuidEvent: { - uuid: string; - payload: any; - }; videoEvent: { componentId: string; videoClipId: string; @@ -1460,21 +1340,6 @@ export const enum InputAction { IA_WALK = 9 } -// @public (undocumented) -export type InputEventResult = { - origin: Vector3Type; - direction: Vector3Type; - buttonId: number; - hit?: { - length: number; - hitPoint: Vector3Type; - meshName: string; - normal: Vector3Type; - worldNormal: Vector3Type; - entityId: unknown; - }; -}; - // @public export const inputSystem: IInputSystem; @@ -2800,6 +2665,30 @@ export namespace PBRaycastResult { export function encode(message: PBRaycastResult, writer?: _m0.Writer): _m0.Writer; } +// @public (undocumented) +export interface PBRealmInfo { + // (undocumented) + baseUrl: string; + // (undocumented) + commsAdapter: string; + // (undocumented) + isPreview: boolean; + // (undocumented) + networkId: number; + // (undocumented) + realmName: string; + // (undocumented) + room?: string | undefined; +} + +// @public (undocumented) +export namespace PBRealmInfo { + // (undocumented) + export function decode(input: _m0.Reader | Uint8Array, length?: number): PBRealmInfo; + // (undocumented) + export function encode(message: PBRealmInfo, writer?: _m0.Writer): _m0.Writer; +} + // @public (undocumented) export interface PBTextShape { font?: Font | undefined; @@ -3464,13 +3353,6 @@ export const enum RaycastQueryType { RQT_QUERY_ALL = 1 } -// @public (undocumented) -export type RaycastResponsePayload = { - queryId: string; - queryType: string; - payload: T; -}; - // @public (undocumented) export const RaycastResult: LastWriteWinElementSetComponentDefinition; @@ -3566,6 +3448,9 @@ export type ReadOnlyLastWriteWinElementSetComponentDefinition = Omit; + // @public (undocumented) export type ReceiveMessage = CrdtMessageBody & { transportId?: number; diff --git a/packages/@dcl/sdk-commands/package-lock.json b/packages/@dcl/sdk-commands/package-lock.json index b856125d2..71a2105c5 100644 --- a/packages/@dcl/sdk-commands/package-lock.json +++ b/packages/@dcl/sdk-commands/package-lock.json @@ -15,7 +15,7 @@ "@dcl/inspector": "file:../inspector", "@dcl/linker-dapp": "^0.12.0", "@dcl/mini-comms": "1.0.1-20230216163137.commit-a4c75be", - "@dcl/protocol": "^1.0.0-8691799990.commit-4ba546c", + "@dcl/protocol": "1.0.0-8758013256.commit-44aab53", "@dcl/quests-client": "^1.0.3", "@dcl/quests-manager": "^0.1.4", "@dcl/rpc": "^1.1.1", @@ -238,9 +238,9 @@ } }, "node_modules/@dcl/protocol": { - "version": "1.0.0-8691799990.commit-4ba546c", - "resolved": "https://registry.npmjs.org/@dcl/protocol/-/protocol-1.0.0-8691799990.commit-4ba546c.tgz", - "integrity": "sha512-hU/8zZXdjgRSUvfi1QRL6YAMF/8QDS/hhh8cs4bE2dw9obXTziyekiR7IEwAOmF60wt/u6+s7JLIacHd35G+HQ==", + "version": "1.0.0-8758013256.commit-44aab53", + "resolved": "https://registry.npmjs.org/@dcl/protocol/-/protocol-1.0.0-8758013256.commit-44aab53.tgz", + "integrity": "sha512-VbrbfUEOLIOiQjN2c53RSg1lNNkvt0+ow+d27c+O7RSyjqDyZZkX2uMW4kdYN1TOk45OrvxCyrrxdYN0ZXW52Q==", "dependencies": { "@dcl/ts-proto": "1.154.0" } @@ -3227,9 +3227,9 @@ } }, "@dcl/protocol": { - "version": "1.0.0-8691799990.commit-4ba546c", - "resolved": "https://registry.npmjs.org/@dcl/protocol/-/protocol-1.0.0-8691799990.commit-4ba546c.tgz", - "integrity": "sha512-hU/8zZXdjgRSUvfi1QRL6YAMF/8QDS/hhh8cs4bE2dw9obXTziyekiR7IEwAOmF60wt/u6+s7JLIacHd35G+HQ==", + "version": "1.0.0-8758013256.commit-44aab53", + "resolved": "https://registry.npmjs.org/@dcl/protocol/-/protocol-1.0.0-8758013256.commit-44aab53.tgz", + "integrity": "sha512-VbrbfUEOLIOiQjN2c53RSg1lNNkvt0+ow+d27c+O7RSyjqDyZZkX2uMW4kdYN1TOk45OrvxCyrrxdYN0ZXW52Q==", "requires": { "@dcl/ts-proto": "1.154.0" } diff --git a/packages/@dcl/sdk-commands/package.json b/packages/@dcl/sdk-commands/package.json index 66cce4f4d..21ba204c7 100644 --- a/packages/@dcl/sdk-commands/package.json +++ b/packages/@dcl/sdk-commands/package.json @@ -13,7 +13,7 @@ "@dcl/inspector": "file:../inspector", "@dcl/linker-dapp": "^0.12.0", "@dcl/mini-comms": "1.0.1-20230216163137.commit-a4c75be", - "@dcl/protocol": "^1.0.0-8691799990.commit-4ba546c", + "@dcl/protocol": "1.0.0-8758013256.commit-44aab53", "@dcl/quests-client": "^1.0.3", "@dcl/quests-manager": "^0.1.4", "@dcl/rpc": "^1.1.1", diff --git a/packages/@dcl/sdk/src/internal/transports/rendererTransport.ts b/packages/@dcl/sdk/src/internal/transports/rendererTransport.ts index d36af9ec5..ec0e67fe8 100644 --- a/packages/@dcl/sdk/src/internal/transports/rendererTransport.ts +++ b/packages/@dcl/sdk/src/internal/transports/rendererTransport.ts @@ -11,7 +11,6 @@ export function createRendererTransport(engineApi: EngineApiForTransport): Trans const response = await engineApi.crdtSendToRenderer({ data: new Uint8Array(message) }) - if (response && response.data && response.data.length) { if (rendererTransport.onmessage) { for (const byteArray of response.data) { diff --git a/packages/@dcl/sdk/src/observables.ts b/packages/@dcl/sdk/src/observables.ts index 0033f1ef8..5404e6284 100644 --- a/packages/@dcl/sdk/src/observables.ts +++ b/packages/@dcl/sdk/src/observables.ts @@ -1,257 +1,32 @@ import { Observable } from './internal/Observable' -import { QuaternionType, Vector3Type } from '@dcl/ecs' +import { + AvatarBase, + AvatarEmoteCommand, + AvatarEquippedData, + Entity, + PlayerIdentityData, + PointerEventsResult, + RealmInfo, + Vector3Type, + engine +} from '@dcl/ecs' import { ManyEntityAction, SendBatchResponse, subscribe } from '~system/EngineApi' - -let subscribeFunction: typeof subscribe = subscribe - -/** @public */ -export type InputEventResult = { - /** Origin of the ray, relative to the scene */ - origin: Vector3Type - /** Direction vector of the ray (normalized) */ - direction: Vector3Type - /** ID of the pointer that triggered the event */ - buttonId: number - /** Does this pointer event hit any object? */ - hit?: { - /** Length of the ray */ - length: number - /** If the ray hits a mesh the intersection point will be this */ - hitPoint: Vector3Type - /** If the mesh has a name, it will be assigned to meshName */ - meshName: string - /** Normal of the hit */ - normal: Vector3Type - /** Normal of the hit, in world space */ - worldNormal: Vector3Type - /** Hit entity ID if any */ - entityId: unknown - } -} - -/** @public */ -export type GlobalInputEventResult = InputEventResult & { - /** - * DOWN = 0, - * UP = 1 - */ - type: 0 | 1 -} - -/** @public */ -export type RaycastResponsePayload = { - queryId: string - queryType: string - payload: T -} - -/** @public */ -export type GizmoDragEndEvent = { - type: 'gizmoDragEnded' - transforms: Array<{ - position: Vector3Type - rotation: QuaternionType - scale: Vector3Type - entityId: unknown - }> -} - -/** @public */ -export type GizmoSelectedEvent = { - type: 'gizmoSelected' - gizmoType: 'MOVE' | 'ROTATE' | 'SCALE' | 'NONE' - entities: string[] -} +import players from './players' /// --- EVENTS --- /** @public */ export type IEventNames = keyof IEvents -/** @public */ -export type EngineEvent = { - /** eventName */ - type: T - data: Readonly -} - /** * @public * Note: Don't use `on` prefix for IEvents to avoid redundancy with `event.on("onEventName")` syntax. */ export interface IEvents { - /** - * `positionChanged` is triggered when the position of the camera changes - * This event is throttled to 10 times per second. - */ - positionChanged: { - /** Camera position relative to the base parcel of the scene */ - position: Vector3Type - - /** Camera position, this is a absolute world position */ - cameraPosition: Vector3Type - - /** Eye height, in meters. */ - playerHeight: number - } - - /** - * `rotationChanged` is triggered when the rotation of the camera changes. - * This event is throttled to 10 times per second. - */ - rotationChanged: { - /** Degree vector. Same as entities */ - rotation: Vector3Type - /** Rotation quaternion, useful in some scenarios. */ - quaternion: QuaternionType - } - - /** - * `cameraModeChanged` is triggered when the user changes the camera mode - */ - cameraModeChanged: { - /** - * FIRST_PERSON = 0, - * THIRD_PERSON = 1, - * FREE_CAMERA = 2 - */ - cameraMode: 0 | 1 | 2 - } - - /** - * `idleStateChanged` is triggered when the user not moves for a defined period of time - */ - idleStateChanged: { - isIdle: boolean - } - playerExpression: { expressionId: string } - /** - * `pointerUp` is triggered when the user releases an input pointer. - * It could be a VR controller, a touch screen or the mouse. - */ - pointerUp: InputEventResult - - /** - * `pointerDown` is triggered when the user press an input pointer. - * It could be a VR controller, a touch screen or the mouse. - */ - pointerDown: InputEventResult - - /** - * `pointerEvent` is triggered when the user press or releases an input pointer. - * It could be a VR controller, a touch screen or the mouse. - * - * @deprecated use actionButtonEvent instead - */ - pointerEvent: GlobalInputEventResult - - /** - * `actionButtonEvent` is triggered when the user press or releases an input pointer. - * It could be a VR controller, a touch screen or the mouse. - * - * This event is exactly the same as `pointerEvent` but the logic in the ECS had an unsolvable - * condition that required us to create this new event to handle more cases for new buttons. - */ - actionButtonEvent: GlobalInputEventResult - - /** - * `raycastResponse` is triggered in response to a raycast query - */ - raycastResponse: RaycastResponsePayload - - /** - * `chatMessage` is triggered when the user sends a message through chat entity. - */ - chatMessage: { - id: string - sender: string - message: string - isCommand: boolean - } - - /** - * `onChange` is triggered when an entity changes its own internal state. - * Dispatched by the `ui-*` entities when their value is changed. It triggers a callback. - * Notice: Only entities with ID will be listening for click events. - */ - onChange: { - value?: any - /** ID of the pointer that triggered the event */ - pointerId?: number - } - - /** - * `onEnter` is triggered when the user hits the "Enter" key from the keyboard - * Used principally by the Chat internal scene - */ - onEnter: unknown - - /** - * `onPointerLock` is triggered when the user clicks the world canvas and the - * pointer locks to it so the pointer moves the camera - */ - onPointerLock: { - locked?: boolean - } - - /** - * `onAnimationEnd` is triggered when an animation clip gets finish - */ - onAnimationEnd: { - clipName: string - } - - /** - * `onFocus` is triggered when an entity focus is active. - * Dispatched by the `ui-input` and `ui-password` entities when the value is changed. - * It triggers a callback. - * - * Notice: Only entities with ID will be listening for click events. - */ - onFocus: { - /** ID of the entitiy of the event */ - entityId: unknown - /** ID of the pointer that triggered the event */ - pointerId: number - } - - /** - * `onBlur` is triggered when an entity loses its focus. - * Dispatched by the `ui-input` and `ui-password` entities when the value is changed. - * It triggers a callback. - * - * Notice: Only entities with ID will be listening for click events. - */ - onBlur: { - /** ID of the entitiy of the event */ - entityId: unknown - /** ID of the pointer that triggered the event */ - pointerId: number - } - - /** The onClick event is only used for UI elements */ - onClick: { - entityId: unknown - } - - /** - * This event gets triggered when an entity leaves the scene fences. - */ - entityOutOfScene: { - entityId: unknown - } - - /** - * This event gets triggered when an entity enters the scene fences. - */ - entityBackInScene: { - entityId: unknown - } - /** * This event gets triggered when the user enters the scene */ @@ -279,56 +54,6 @@ export interface IEvents { */ sceneStart: unknown - /** - * This is triggered once the builder scene is loaded. - */ - builderSceneStart: unknown - - /** - * This is triggered once the builder scene is unloaded. - */ - builderSceneUnloaded: unknown - - /** - * After checking entities outside the fences, if any is outside, this event - * will be triggered with all the entities outside the scene. - */ - entitiesOutOfBoundaries: { - entities: string[] - } - - uuidEvent: { - uuid: string - payload: any - } - - onTextSubmit: { - text: string - } - - metricsUpdate: { - given: Record - limit: Record - } - - limitsExceeded: { - given: Record - limit: Record - } - - /** For gizmos */ - gizmoEvent: GizmoDragEndEvent | GizmoSelectedEvent - - externalAction: { - type: string - [key: string]: any - } - - stateEvent: { - type: string - payload: any - } - /** This is triggered at least for each videoStatus change */ videoEvent: { componentId: string @@ -374,12 +99,6 @@ export interface IEvents { distance: number } } - - /** Triggered when pointer start hovering an entities' shape */ - pointerHoverEnter: unknown - - /** Triggered when pointer stop hovering an entities' shape */ - pointerHoverExit: unknown } /** @@ -387,9 +106,13 @@ export interface IEvents { * This function generates a callback that is passed to the Observable * constructor to subscribe to the events of the DecentralandInterface */ -function createSubscriber(eventName: string) { +function createSubscriber(eventName: keyof IEvents) { return () => { - subscribeFunction({ eventId: eventName }).catch(console.error) + if (eventName === 'comms' || (globalThis as any).__OBSERVABLES_FALLBACK_SUPPORT) { + subscribe({ eventId: eventName }).catch(console.error) + } else { + SDK7ComponentsObservable?.subscribe(eventName) + } } } @@ -477,14 +200,6 @@ export const onPlayerClickedObservable = new Observable(createSubscriber('comms')) -/** - * @internal - * Used for testing purpose - */ -export function setSubscribeFunction(fn: (event: { eventId: string }) => Promise) { - subscribeFunction = fn -} - /** * @internal * @deprecated this is an OLD API. @@ -545,3 +260,148 @@ export async function pollEvents(sendBatch: (body: ManyEntityAction) => Promise< } } } + +const SDK7ComponentsObservable = processObservables() +function processObservables() { + if ((globalThis as any).__OBSERVABLES_FALLBACK_SUPPORT) return + const subscriptions = new Set() + + function subscribe(eventName: keyof IEvents) { + if (subscriptions.has(eventName)) return + switch (eventName) { + case 'playerClicked': { + subscribePlayerClick() + } + case 'onEnterScene': + case 'playerConnected': { + subscribeEnterScene() + } + case 'onLeaveScene': + case 'playerDisconnected': { + subscribeLeaveScene() + } + case 'onRealmChanged': { + subscribeRealmChange() + } + case 'playerExpression': { + subscribePlayerExpression() + } + case 'profileChanged': { + subscribeProfileChange() + } + } + subscriptions.add(eventName) + } + /** + * PLAYER ENTER/CONNECTED observable + */ + function subscribeEnterScene() { + players.onEnterScene((player) => { + if (subscriptions.has('onEnterScene')) { + onEnterSceneObservable.notifyObservers({ userId: player.userId }) + } + + if (subscriptions.has('playerConnected')) { + onPlayerConnectedObservable.notifyObservers({ userId: player.userId }) + } + }) + } + /** + * PLAYER LEAVE/DISCONNECTED observable + */ + function subscribeLeaveScene() { + players.onLeaveScene((userId) => { + if (subscriptions.has('onLeaveScene')) { + onLeaveSceneObservable.notifyObservers({ userId }) + } + + if (subscriptions.has('playerDisconnected')) { + onPlayerDisconnectedObservable.notifyObservers({ userId }) + } + }) + } + /** + * REALM CHANGE observable + */ + function subscribeRealmChange() { + RealmInfo.onChange(engine.RootEntity, (value) => { + if (value) { + onRealmChangedObservable.notifyObservers({ + domain: value.baseUrl, + displayName: value.realmName, + room: value.room ?? '', + serverName: value.realmName + }) + } + }) + } + /** + * PLAYER/AVATAR CLICKED observable + */ + function subscribePlayerClick() { + const playerEntities = new Set() + engine.addSystem(() => { + for (const [entity] of engine.getEntitiesWith(PlayerIdentityData)) { + if (playerEntities.has(entity)) return + playerEntities.add(entity) + + PointerEventsResult.onChange(entity, (data) => { + if (data?.hit) { + onPlayerClickedObservable.notifyObservers({ + userId: PlayerIdentityData.getOrNull(entity)?.address ?? '', + ray: { + direction: data.hit.direction!, + distance: data.hit.length, + origin: data.hit.globalOrigin! + } + }) + } + }) + } + }) + } + + /** + * Player expression observable + */ + function subscribePlayerExpression() { + AvatarEmoteCommand.onChange(engine.PlayerEntity, (value) => { + onPlayerExpressionObservable.notifyObservers({ expressionId: value?.emoteUrn ?? '' }) + }) + } + + /** + * PROFILE CHANGE observable + */ + function subscribeProfileChange() { + AvatarBase.onChange(engine.PlayerEntity, () => { + if (!profileAddress) return + onProfileChanged.notifyObservers({ ethAddress: profileAddress, version: 0 }) + }) + + AvatarEquippedData.onChange(engine.PlayerEntity, () => { + if (!profileAddress) return + onProfileChanged.notifyObservers({ ethAddress: profileAddress, version: 0 }) + }) + } + + // Flag to call once the scene is initalized. + let sceneReady = false + let profileAddress: string | undefined + + function observableSystem() { + if (sceneReady && profileAddress) { + return engine.removeSystem(observableSystem) + } + if (!sceneReady) { + sceneReady = true + onSceneReadyObservable.notifyObservers({}) + } + if (profileAddress) return + profileAddress = PlayerIdentityData.getOrNull(engine.PlayerEntity)?.address + } + + engine.addSystem(observableSystem) + + return { subscribe } +} diff --git a/packages/@dcl/sdk/src/players/index.ts b/packages/@dcl/sdk/src/players/index.ts index 865662b17..149e3a23b 100644 --- a/packages/@dcl/sdk/src/players/index.ts +++ b/packages/@dcl/sdk/src/players/index.ts @@ -28,6 +28,7 @@ function definePlayerHelper(engine: IEngine) { const AvatarEquippedData = defineAvatarEquippedData(engine) const AvatarBase = defineAvatarBase(engine) const playerEntities = new Map() + let onEnterSceneCb: ((player: GetPlayerDataRes) => void) | undefined = undefined let onLeaveSceneCb: ((userId: string) => void) | undefined = undefined diff --git a/test/ecs/components/RealmInfo.spec.ts b/test/ecs/components/RealmInfo.spec.ts new file mode 100644 index 000000000..8cfb50cb8 --- /dev/null +++ b/test/ecs/components/RealmInfo.spec.ts @@ -0,0 +1,27 @@ +import { Engine, components } from '../../../packages/@dcl/ecs/src' +import { testComponentSerialization } from './assertion' + +describe('Generated RealmInfo ProtoBuf', () => { + it('should serialize/deserialize move RealmInfo', () => { + const newEngine = Engine() + const RealmInfo = components.RealmInfo(newEngine) + + testComponentSerialization(RealmInfo, { + baseUrl: 'boedo://casla', + commsAdapter: 'boedo-casla', + networkId: 1, + realmName: 'boedo', + room: 'casla', + isPreview: false + }) + + testComponentSerialization(RealmInfo, { + baseUrl: 'boedo://casla', + commsAdapter: 'boedo-casla', + networkId: 1, + realmName: 'boedo', + room: 'casla', + isPreview: false + }) + }) +}) diff --git a/test/ecs/runtime.spec.ts b/test/ecs/runtime.spec.ts deleted file mode 100644 index 2ba79e394..000000000 --- a/test/ecs/runtime.spec.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { Engine } from '../../packages/@dcl/ecs/src/engine' -import { - onEnterSceneObservable, - onLeaveSceneObservable, - onPlayerClickedObservable, - onPlayerConnectedObservable, - onPlayerDisconnectedObservable, - onPlayerExpressionObservable, - onProfileChanged, - onRealmChangedObservable, - onSceneReadyObservable, - onVideoEvent, - onCommsMessage, - pollEvents, - setSubscribeFunction -} from '../../packages/@dcl/sdk/src/observables' -import { createRendererTransport } from '../../packages/@dcl/sdk/src/internal/transports/rendererTransport' -import { SendBatchResponse } from '~system/EngineApi' - -describe('Observable tests', () => { - beforeEach(() => { - jest.resetAllMocks() - jest.restoreAllMocks() - }) - - it('should avoid echo messages', async () => { - const crdtSendToRenderer = jest.fn() - const rendererTransport = createRendererTransport({ crdtSendToRenderer }) - const engine = Engine() - engine.addTransport(rendererTransport) - - const eventToEmit = [ - { eventId: 'onEnterScene', eventData: '{}' }, - { eventId: 'onLeaveScene', eventData: '{}' }, - { eventId: 'sceneStart', eventData: '{}' }, - { eventId: 'playerExpression', eventData: '{}' }, - { eventId: 'videoEvent', eventData: '{}' }, - { eventId: 'profileChanged', eventData: '{}' }, - { eventId: 'playerConnected', eventData: '{}' }, - { eventId: 'playerDisconnected', eventData: '{}' }, - { eventId: 'onRealmChanged', eventData: '{}' }, - { eventId: 'playerClicked', eventData: '{}' }, - { eventId: 'comms', eventData: '{}' } - ] - const counter = { - onEnterSceneObservable: 0, - onLeaveSceneObservable: 0, - onSceneReadyObservable: 0, - onPlayerExpressionObservable: 0, - onVideoEvent: 0, - onProfileChanged: 0, - onPlayerConnectedObservable: 0, - onPlayerDisconnectedObservable: 0, - onRealmChangedObservable: 0, - onPlayerClickedObservable: 0, - onCommsMessage: 0 - } - onEnterSceneObservable.add(() => { - counter.onEnterSceneObservable++ - }) - onLeaveSceneObservable.add(() => { - counter.onLeaveSceneObservable++ - }) - onSceneReadyObservable.add(() => { - counter.onSceneReadyObservable++ - }) - onPlayerExpressionObservable.add(() => { - counter.onPlayerExpressionObservable++ - }) - onVideoEvent.add(() => { - counter.onVideoEvent++ - }) - onProfileChanged.add(() => { - counter.onProfileChanged++ - }) - onPlayerConnectedObservable.add(() => { - counter.onPlayerConnectedObservable++ - }) - onPlayerDisconnectedObservable.add(() => { - counter.onPlayerDisconnectedObservable++ - }) - onCommsMessage.add(() => { - counter.onCommsMessage++ - }) - let counterSubscribe = 0 - setSubscribeFunction(async () => { - counterSubscribe++ - }) - onRealmChangedObservable.add(() => { - counter.onRealmChangedObservable++ - }) - onPlayerClickedObservable.add(() => { - counter.onPlayerClickedObservable++ - }) - - await pollEvents( - async (): Promise => ({ - events: eventToEmit.map(($) => ({ type: 0 /*generic*/, generic: $ })) - }) - ) - - expect(counter.onEnterSceneObservable).toBe(1) - expect(counter.onLeaveSceneObservable).toBe(1) - expect(counter.onSceneReadyObservable).toBe(1) - expect(counter.onPlayerExpressionObservable).toBe(1) - expect(counter.onVideoEvent).toBe(1) - expect(counter.onProfileChanged).toBe(1) - expect(counter.onPlayerConnectedObservable).toBe(1) - expect(counter.onPlayerDisconnectedObservable).toBe(1) - expect(counter.onRealmChangedObservable).toBe(1) - expect(counter.onPlayerClickedObservable).toBe(1) - expect(counter.onCommsMessage).toBe(1) - expect(counterSubscribe).toBe(2) - }) -})