@needle-tools/engine 5.1.0-alpha.3 → 5.1.0-alpha.5
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/CHANGELOG.md +68 -0
- package/SKILL.md +4 -1
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-DF01sSGQ.js → needle-engine.bundle-C-LG00ZZ.js} +10681 -10100
- package/dist/needle-engine.bundle-D7tzaiYE.min.js +1733 -0
- package/dist/{needle-engine.bundle-C-ixARur.umd.cjs → needle-engine.bundle-OPkPmdUM.umd.cjs} +161 -161
- package/dist/needle-engine.d.ts +1349 -317
- package/dist/needle-engine.js +556 -555
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/three.js +1 -0
- package/dist/three.min.js +21 -21
- package/dist/three.umd.cjs +16 -16
- package/lib/engine/api.d.ts +5 -0
- package/lib/engine/api.js +4 -0
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/codegen/register_types.js +10 -18
- package/lib/engine/codegen/register_types.js.map +1 -1
- package/lib/engine/engine_camera.fit.js +16 -4
- package/lib/engine/engine_camera.fit.js.map +1 -1
- package/lib/engine/engine_context.d.ts +20 -7
- package/lib/engine/engine_context.js +31 -15
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_context_eventbus.d.ts +47 -0
- package/lib/engine/engine_context_eventbus.js +47 -0
- package/lib/engine/engine_context_eventbus.js.map +1 -0
- package/lib/engine/engine_disposable.d.ts +172 -0
- package/lib/engine/engine_disposable.js +136 -0
- package/lib/engine/engine_disposable.js.map +1 -0
- package/lib/engine/engine_gameobject.d.ts +1 -10
- package/lib/engine/engine_gameobject.js +20 -118
- package/lib/engine/engine_gameobject.js.map +1 -1
- package/lib/engine/engine_gltf_builtin_components.js +7 -69
- package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
- package/lib/engine/engine_input.d.ts +23 -4
- package/lib/engine/engine_input.js +2 -1
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_instantiate_resolve.d.ts +42 -0
- package/lib/engine/engine_instantiate_resolve.js +372 -0
- package/lib/engine/engine_instantiate_resolve.js.map +1 -0
- package/lib/engine/engine_mainloop_utils.js +2 -2
- package/lib/engine/engine_mainloop_utils.js.map +1 -1
- package/lib/engine/engine_networking.d.ts +51 -37
- package/lib/engine/engine_networking.js +132 -82
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_networking.transport.websocket.d.ts +15 -0
- package/lib/engine/engine_networking.transport.websocket.js +38 -0
- package/lib/engine/engine_networking.transport.websocket.js.map +1 -0
- package/lib/engine/engine_networking_instantiate.js +2 -2
- package/lib/engine/engine_networking_instantiate.js.map +1 -1
- package/lib/engine/engine_networking_types.d.ts +39 -1
- package/lib/engine/engine_networking_types.js +7 -0
- package/lib/engine/engine_networking_types.js.map +1 -1
- package/lib/engine/engine_physics_rapier.d.ts +21 -3
- package/lib/engine/engine_physics_rapier.js +94 -25
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine/engine_serialization_builtin_serializer.js +1 -5
- package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
- package/lib/engine/engine_serialization_core.d.ts +1 -0
- package/lib/engine/engine_serialization_core.js +7 -0
- package/lib/engine/engine_serialization_core.js.map +1 -1
- package/lib/engine/engine_types.d.ts +29 -11
- package/lib/engine/engine_types.js +1 -1
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine/engine_util_decorator.js +7 -2
- package/lib/engine/engine_util_decorator.js.map +1 -1
- package/lib/engine/engine_utils.d.ts +1 -1
- package/lib/engine/engine_utils.js +19 -5
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine-components/AnimationBuilder.d.ts +158 -0
- package/lib/engine-components/AnimationBuilder.js +305 -0
- package/lib/engine-components/AnimationBuilder.js.map +1 -0
- package/lib/engine-components/Animator.d.ts +6 -0
- package/lib/engine-components/Animator.js +23 -13
- package/lib/engine-components/Animator.js.map +1 -1
- package/lib/engine-components/AnimatorController.builder.d.ts +191 -0
- package/lib/engine-components/AnimatorController.builder.js +263 -0
- package/lib/engine-components/AnimatorController.builder.js.map +1 -0
- package/lib/engine-components/AnimatorController.d.ts +2 -119
- package/lib/engine-components/AnimatorController.js +33 -232
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/Collider.d.ts +18 -9
- package/lib/engine-components/Collider.js +61 -14
- package/lib/engine-components/Collider.js.map +1 -1
- package/lib/engine-components/Component.d.ts +72 -9
- package/lib/engine-components/Component.js +114 -10
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/ContactShadows.d.ts +1 -0
- package/lib/engine-components/ContactShadows.js +14 -1
- package/lib/engine-components/ContactShadows.js.map +1 -1
- package/lib/engine-components/DragControls.js +0 -7
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/DropListener.js +3 -0
- package/lib/engine-components/DropListener.js.map +1 -1
- package/lib/engine-components/EventList.d.ts +31 -9
- package/lib/engine-components/EventList.js +37 -76
- package/lib/engine-components/EventList.js.map +1 -1
- package/lib/engine-components/Joints.d.ts +4 -2
- package/lib/engine-components/Joints.js +19 -3
- package/lib/engine-components/Joints.js.map +1 -1
- package/lib/engine-components/Light.js +9 -1
- package/lib/engine-components/Light.js.map +1 -1
- package/lib/engine-components/OrbitControls.d.ts +0 -2
- package/lib/engine-components/OrbitControls.js +14 -1
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/RigidBody.d.ts +12 -4
- package/lib/engine-components/RigidBody.js +18 -4
- package/lib/engine-components/RigidBody.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.js +3 -0
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/api.d.ts +2 -1
- package/lib/engine-components/api.js +2 -1
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +7 -13
- package/lib/engine-components/codegen/components.js +7 -13
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
- package/lib/engine-components/timeline/PlayableDirector.d.ts +21 -11
- package/lib/engine-components/timeline/PlayableDirector.js +75 -67
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/SignalAsset.d.ts +3 -1
- package/lib/engine-components/timeline/SignalAsset.js +1 -0
- package/lib/engine-components/timeline/SignalAsset.js.map +1 -1
- package/lib/engine-components/timeline/TimelineBuilder.d.ts +413 -0
- package/lib/engine-components/timeline/TimelineBuilder.js +506 -0
- package/lib/engine-components/timeline/TimelineBuilder.js.map +1 -0
- package/lib/engine-components/timeline/TimelineModels.d.ts +2 -1
- package/lib/engine-components/timeline/TimelineModels.js +3 -0
- package/lib/engine-components/timeline/TimelineModels.js.map +1 -1
- package/lib/engine-components/timeline/TimelineTracks.d.ts +37 -6
- package/lib/engine-components/timeline/TimelineTracks.js +92 -26
- package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
- package/lib/engine-components/timeline/index.d.ts +2 -1
- package/lib/engine-components/timeline/index.js +2 -0
- package/lib/engine-components/timeline/index.js.map +1 -1
- package/lib/engine-components/web/CursorFollow.d.ts +0 -1
- package/lib/engine-components/web/CursorFollow.js +0 -1
- package/lib/engine-components/web/CursorFollow.js.map +1 -1
- package/package.json +2 -83
- package/plugins/common/cloud.js +6 -1
- package/plugins/common/license.js +5 -2
- package/plugins/common/worker.js +9 -4
- package/plugins/vite/dependencies.js +1 -11
- package/plugins/vite/dependency-watcher.js +2 -2
- package/plugins/vite/editor-connection.js +3 -3
- package/plugins/vite/license.js +19 -1
- package/plugins/vite/reload.js +1 -1
- package/plugins/vite/server.js +2 -1
- package/src/engine/api.ts +7 -0
- package/src/engine/codegen/register_types.ts +10 -18
- package/src/engine/engine_camera.fit.ts +15 -4
- package/src/engine/engine_context.ts +32 -16
- package/src/engine/engine_context_eventbus.ts +73 -0
- package/src/engine/engine_disposable.ts +214 -0
- package/src/engine/engine_gameobject.ts +52 -157
- package/src/engine/engine_gltf_builtin_components.ts +7 -76
- package/src/engine/engine_input.ts +27 -6
- package/src/engine/engine_instantiate_resolve.ts +407 -0
- package/src/engine/engine_mainloop_utils.ts +2 -2
- package/src/engine/engine_networking.transport.websocket.ts +45 -0
- package/src/engine/engine_networking.ts +161 -137
- package/src/engine/engine_networking_instantiate.ts +2 -2
- package/src/engine/engine_networking_types.ts +41 -1
- package/src/engine/engine_physics_rapier.ts +102 -33
- package/src/engine/engine_serialization_builtin_serializer.ts +1 -6
- package/src/engine/engine_serialization_core.ts +9 -0
- package/src/engine/engine_types.ts +46 -27
- package/src/engine/engine_util_decorator.ts +7 -2
- package/src/engine/engine_utils.ts +16 -5
- package/src/engine-components/AnimationBuilder.ts +472 -0
- package/src/engine-components/Animator.ts +24 -12
- package/src/engine-components/AnimatorController.builder.ts +387 -0
- package/src/engine-components/AnimatorController.ts +20 -291
- package/src/engine-components/Collider.ts +66 -18
- package/src/engine-components/Component.ts +118 -20
- package/src/engine-components/ContactShadows.ts +15 -1
- package/src/engine-components/DragControls.ts +0 -9
- package/src/engine-components/DropListener.ts +3 -0
- package/src/engine-components/EventList.ts +45 -83
- package/src/engine-components/Joints.ts +20 -4
- package/src/engine-components/Light.ts +10 -2
- package/src/engine-components/OrbitControls.ts +16 -5
- package/src/engine-components/RigidBody.ts +18 -4
- package/src/engine-components/SceneSwitcher.ts +3 -0
- package/src/engine-components/api.ts +2 -1
- package/src/engine-components/codegen/components.ts +7 -13
- package/src/engine-components/timeline/PlayableDirector.ts +83 -81
- package/src/engine-components/timeline/SignalAsset.ts +4 -1
- package/src/engine-components/timeline/TimelineBuilder.ts +824 -0
- package/src/engine-components/timeline/TimelineModels.ts +5 -1
- package/src/engine-components/timeline/TimelineTracks.ts +96 -27
- package/src/engine-components/timeline/index.ts +2 -1
- package/src/engine-components/web/CursorFollow.ts +0 -1
- package/dist/needle-engine.bundle-CHmXdnE1.min.js +0 -1733
- package/lib/engine-components/AvatarLoader.d.ts +0 -80
- package/lib/engine-components/AvatarLoader.js +0 -232
- package/lib/engine-components/AvatarLoader.js.map +0 -1
- package/lib/engine-components/avatar/AvatarBlink_Simple.d.ts +0 -11
- package/lib/engine-components/avatar/AvatarBlink_Simple.js +0 -77
- package/lib/engine-components/avatar/AvatarBlink_Simple.js.map +0 -1
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.d.ts +0 -14
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js +0 -69
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.d.ts +0 -29
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.js +0 -122
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_MouthShapes.d.ts +0 -15
- package/lib/engine-components/avatar/Avatar_MouthShapes.js +0 -80
- package/lib/engine-components/avatar/Avatar_MouthShapes.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_MustacheShake.d.ts +0 -9
- package/lib/engine-components/avatar/Avatar_MustacheShake.js +0 -30
- package/lib/engine-components/avatar/Avatar_MustacheShake.js.map +0 -1
- package/src/engine-components/AvatarLoader.ts +0 -264
- package/src/engine-components/avatar/AvatarBlink_Simple.ts +0 -70
- package/src/engine-components/avatar/AvatarEyeLook_Rotation.ts +0 -64
- package/src/engine-components/avatar/Avatar_Brain_LookAt.ts +0 -140
- package/src/engine-components/avatar/Avatar_MouthShapes.ts +0 -84
- package/src/engine-components/avatar/Avatar_MustacheShake.ts +0 -32
|
@@ -2,7 +2,6 @@ const defaultNetworkingBackendUrlProvider = "https://urls.needle.tools/default-n
|
|
|
2
2
|
let networkingServerUrl: string | undefined = "wss://networking-2.needle.tools/socket";
|
|
3
3
|
|
|
4
4
|
import * as flatbuffers from 'flatbuffers';
|
|
5
|
-
import { type Websocket } from 'websocket-ts';
|
|
6
5
|
|
|
7
6
|
import type { Networking } from "../engine-components/Networking.js";
|
|
8
7
|
import type { SyncedRoom } from "../engine-components/SyncedRoom.js";
|
|
@@ -10,11 +9,16 @@ import * as schemes from "../engine-schemes/schemes.js";
|
|
|
10
9
|
import { isDevEnvironment } from './debug/index.js';
|
|
11
10
|
import { Telemetry } from './engine_license.js';
|
|
12
11
|
import { PeerNetworking } from './engine_networking_peer.js';
|
|
13
|
-
import { type IModel, type INetworkConnection, SendQueue } from './engine_networking_types.js';
|
|
12
|
+
import { type IModel, type INetworkConnection, type INetworkTransport, SendQueue } from './engine_networking_types.js';
|
|
13
|
+
import { WebsocketTransport } from './engine_networking.transport.websocket.js';
|
|
14
14
|
import { isHostedOnGlitch } from './engine_networking_utils.js';
|
|
15
15
|
import { Context } from './engine_setup.js';
|
|
16
|
+
import type { DisposeFn } from "./engine_disposable.js";
|
|
16
17
|
import * as utils from "./engine_utils.js";
|
|
17
18
|
|
|
19
|
+
/** @internal Symbol used to attach the original callback to unsubscribe functions returned by beginListen/beginListenBinary */
|
|
20
|
+
const $originalCallback = Symbol("originalCallback");
|
|
21
|
+
|
|
18
22
|
export const debugNet = utils.getParam("debugnet") ? true : false;
|
|
19
23
|
export const debugOwner = debugNet || utils.getParam("debugowner") ? true : false;
|
|
20
24
|
const debugnetBin = utils.getParam("debugnetbin");
|
|
@@ -58,16 +62,6 @@ export enum RoomEvents {
|
|
|
58
62
|
/** When a user joins a room, the server sends the entire room state. Afterwards, the server sends the room-state-sent event. */
|
|
59
63
|
RoomStateSent = "room-state-sent",
|
|
60
64
|
}
|
|
61
|
-
/**
|
|
62
|
-
* See {@link RoomEvents} for all event names and docs.
|
|
63
|
-
* - `joined-room`: When the local user has joined a room
|
|
64
|
-
* - `left-room`: When the local user has left a room
|
|
65
|
-
* - `user-joined-room`: When a other user has joined the room
|
|
66
|
-
* - `user-left-room`: When a other user has left the room
|
|
67
|
-
* - `room-state-sent`: When the server has sent the room state to the client
|
|
68
|
-
*/
|
|
69
|
-
type RoomEventsIncoming = Exclude<`${RoomEvents}`, "join-room" | "leave-room">;
|
|
70
|
-
|
|
71
65
|
/** Received when listening to `RoomEvents.JoinedRoom` event */
|
|
72
66
|
export class JoinedRoomResponse {
|
|
73
67
|
room!: string; // room name
|
|
@@ -100,8 +94,6 @@ export enum OwnershipEvent {
|
|
|
100
94
|
GainedOwnershipBroadcast = 'gained-ownership-broadcast',
|
|
101
95
|
LostOwnershipBroadcast = 'lost-ownership-broadcast',
|
|
102
96
|
}
|
|
103
|
-
type OwnershipEventNamesIncoming = Exclude<`${OwnershipEvent}`, "request-has-owner" | "request-is-owner" | "request-ownership" | "remove-ownership">;
|
|
104
|
-
|
|
105
97
|
declare type GainedOwnershipBroadcastResponse = {
|
|
106
98
|
guid: string;
|
|
107
99
|
owner: string;
|
|
@@ -116,7 +108,7 @@ declare type OwnershipResponse = {
|
|
|
116
108
|
value: boolean;
|
|
117
109
|
}
|
|
118
110
|
|
|
119
|
-
declare type WebsocketSendType = IModel | object | boolean |
|
|
111
|
+
declare type WebsocketSendType = IModel | object | boolean | string | number;
|
|
120
112
|
|
|
121
113
|
/** Maps known networking event keys to their callback signatures.
|
|
122
114
|
* Used by {@link NetworkConnection.beginListen} and {@link NetworkConnection.stopListen} for type-safe event handling.
|
|
@@ -559,8 +551,7 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
559
551
|
* The current server url that the networking backend is connected to (e.g. the url of the websocket server)
|
|
560
552
|
*/
|
|
561
553
|
public get currentServerUrl(): string | null {
|
|
562
|
-
|
|
563
|
-
return this._ws?.url ?? null;
|
|
554
|
+
return this._transport?.url ?? null;
|
|
564
555
|
}
|
|
565
556
|
|
|
566
557
|
/** A ping is sent to the server at a regular interval while the browser tab is active. This method can be used to send additional ping messages when needed so that the user doesn't get disconnected from the networking backend */
|
|
@@ -620,25 +611,16 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
620
611
|
/** Send a message to the networking backend - it will be broadcasted to all connected users (except yourself) in the same room by default */
|
|
621
612
|
public send<K extends NetworkEventKey>(
|
|
622
613
|
key: K,
|
|
623
|
-
data?: (K extends keyof NetworkEventMap ? NetworkEventData<K> : WebsocketSendType)
|
|
614
|
+
data?: (K extends keyof NetworkEventMap ? NetworkEventData<K> : WebsocketSendType),
|
|
624
615
|
queue?: SendQueue,
|
|
625
616
|
): void
|
|
626
|
-
public send(key: string, data
|
|
627
|
-
|
|
628
|
-
//@ts-ignore
|
|
629
|
-
if (data === null) data = {};
|
|
617
|
+
public send(key: string, data?: WebsocketSendType, queue: SendQueue = SendQueue.Queued) {
|
|
630
618
|
|
|
631
619
|
if (queue === SendQueue.Queued) {
|
|
632
620
|
this._defaultMessagesBuffer.push({ key: key, value: data });
|
|
633
621
|
return;
|
|
634
622
|
}
|
|
635
623
|
|
|
636
|
-
// if (!this.connected) return;
|
|
637
|
-
// if (this.channelId)
|
|
638
|
-
// data["__id"] = this.channelId;
|
|
639
|
-
// else if (this.connectionId)
|
|
640
|
-
// data["__id"] = this.connectionId;
|
|
641
|
-
// this.sendGeckosIo(key, data);
|
|
642
624
|
return this.sendWithWebsocket(key, data, queue);
|
|
643
625
|
}
|
|
644
626
|
|
|
@@ -668,13 +650,13 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
668
650
|
/** Send a binary message to the server (broadcasted to all connected users) */
|
|
669
651
|
public sendBinary(bin: Uint8Array) {
|
|
670
652
|
if (debugnetBin) console.log("<< send binary", this.context.time.frame, (bin.length / 1024) + " KB");
|
|
671
|
-
this.
|
|
653
|
+
this._transport?.send(bin);
|
|
672
654
|
}
|
|
673
655
|
|
|
674
656
|
private _defaultMessagesBuffer: Array<{ key: string, value: any }> = [];
|
|
675
657
|
private _defaultMessagesBufferArray: Array<{ key: string, data: any }> = [];
|
|
676
658
|
public sendBufferedMessagesNow() {
|
|
677
|
-
if (!this.
|
|
659
|
+
if (!this._transport) return;
|
|
678
660
|
this._defaultMessagesBufferArray.length = 0;
|
|
679
661
|
const count = Object.keys(this._defaultMessagesBuffer).length;
|
|
680
662
|
for (const key in this._defaultMessagesBuffer) {
|
|
@@ -692,47 +674,59 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
692
674
|
console.log("SEND BUFFERED", this._defaultMessagesBufferArray.length);
|
|
693
675
|
if (this._defaultMessagesBufferArray.length <= 0) return;
|
|
694
676
|
const message = JSON.stringify(this._defaultMessagesBufferArray);
|
|
695
|
-
this.
|
|
677
|
+
this._transport?.send(message);
|
|
696
678
|
}
|
|
697
679
|
|
|
698
|
-
/** Use to start listening to networking events.
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
*
|
|
680
|
+
/** Use to start listening to networking events.
|
|
681
|
+
* Returns an unsubscribe function that removes the listener when called.
|
|
682
|
+
* The returned function can also be passed to {@link stopListen} or {@link Component.autoCleanup} for automatic lifecycle management.
|
|
683
|
+
*
|
|
684
|
+
* @example With autoCleanup (recommended)
|
|
685
|
+
* ```ts
|
|
686
|
+
* export class MyComponent extends Behaviour {
|
|
687
|
+
* onEnable() {
|
|
688
|
+
* this.autoCleanup(this.context.connection.beginListen("joined-room", () => {
|
|
689
|
+
* console.log("I joined a networked room");
|
|
690
|
+
* }));
|
|
691
|
+
* }
|
|
692
|
+
* // Automatically unsubscribed on disable — no manual cleanup needed!
|
|
693
|
+
* }
|
|
694
|
+
* ```
|
|
695
|
+
*
|
|
696
|
+
* @example Manual unsubscribe
|
|
702
697
|
* ```ts
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
* });
|
|
698
|
+
* const unsub = this.context.connection.beginListen("joined-room", () => { });
|
|
699
|
+
* // Later:
|
|
700
|
+
* unsub(); // removes the listener
|
|
707
701
|
* ```
|
|
708
702
|
*
|
|
709
|
-
* @example
|
|
703
|
+
* @example With stopListen (legacy pattern, still supported)
|
|
710
704
|
* ```ts
|
|
711
|
-
* // Make sure to unsubscribe from events when the component is disabled
|
|
712
705
|
* export class MyComponent extends Behaviour {
|
|
713
706
|
* onEnable() {
|
|
714
|
-
* this.connection.beginListen("joined-room", this.onJoinedRoom)
|
|
707
|
+
* this.context.connection.beginListen("joined-room", this.onJoinedRoom);
|
|
715
708
|
* }
|
|
716
709
|
* onDisable() {
|
|
717
|
-
* this.connection.stopListen("joined-room", this.onJoinedRoom)
|
|
710
|
+
* this.context.connection.stopListen("joined-room", this.onJoinedRoom);
|
|
718
711
|
* }
|
|
719
712
|
* onJoinedRoom = () => {
|
|
720
|
-
*
|
|
713
|
+
* console.log("I joined a networked room");
|
|
721
714
|
* }
|
|
722
715
|
* }
|
|
723
716
|
* ```
|
|
724
717
|
* @link https://engine.needle.tools/docs/networking.html
|
|
725
|
-
*
|
|
718
|
+
*
|
|
726
719
|
*/
|
|
727
720
|
public beginListen<K extends NetworkEventKey>(
|
|
728
721
|
key: K,
|
|
729
722
|
callback: K extends keyof NetworkEventMap ? NetworkEventMap[K] : (...args: any[]) => void,
|
|
730
|
-
):
|
|
723
|
+
): DisposeFn {
|
|
731
724
|
if (!this._listeners[key])
|
|
732
725
|
this._listeners[key] = [];
|
|
733
726
|
this._listeners[key].push(callback);
|
|
734
|
-
|
|
735
|
-
|
|
727
|
+
const unsub = () => this.stopListen(key, callback as any);
|
|
728
|
+
(unsub as any)[$originalCallback] = callback;
|
|
729
|
+
return unsub;
|
|
736
730
|
}
|
|
737
731
|
|
|
738
732
|
/**@deprecated please use stopListen instead (2.65.2-pre) */
|
|
@@ -741,51 +735,59 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
741
735
|
callback: (K extends keyof NetworkEventMap ? NetworkEventMap[K] : (...args: any[]) => void) | null,
|
|
742
736
|
) { return this.stopListen(key, callback as any); }
|
|
743
737
|
|
|
744
|
-
/** Use to stop listening to networking events
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
738
|
+
/** Use to stop listening to networking events.
|
|
739
|
+
* Accepts either the original callback or the unsubscribe function returned by {@link beginListen}.
|
|
740
|
+
* To subscribe to events use the `{@link beginListen}` method.
|
|
741
|
+
*
|
|
742
|
+
* @example
|
|
749
743
|
* ```ts
|
|
750
|
-
* //
|
|
751
|
-
*
|
|
752
|
-
*
|
|
753
|
-
* this.connection.beginListen("joined-room", this.onJoinedRoom)
|
|
754
|
-
* }
|
|
755
|
-
* onDisable() {
|
|
756
|
-
* this.connection.stopListen("joined-room", this.onJoinedRoom)
|
|
757
|
-
* }
|
|
758
|
-
* onJoinedRoom = () => {
|
|
759
|
-
* console.log("I joined a networked room")
|
|
760
|
-
* }
|
|
761
|
-
* }
|
|
744
|
+
* // Both patterns work:
|
|
745
|
+
* this.context.connection.stopListen("joined-room", this.onJoinedRoom); // original callback
|
|
746
|
+
* this.context.connection.stopListen("joined-room", unsub); // unsubscribe fn from beginListen
|
|
762
747
|
* ```
|
|
763
748
|
*/
|
|
749
|
+
private static _didLogStopListenHint = false;
|
|
764
750
|
public stopListen<K extends NetworkEventKey>(
|
|
765
751
|
key: K,
|
|
766
|
-
callback: (K extends keyof NetworkEventMap ? NetworkEventMap[K] : (...args: any[]) => void) | null,
|
|
752
|
+
callback: (K extends keyof NetworkEventMap ? NetworkEventMap[K] : (...args: any[]) => void) | Function | null,
|
|
767
753
|
): void
|
|
768
754
|
public stopListen(key: string, callback: Function | null) {
|
|
769
755
|
if (!callback) return;
|
|
770
756
|
if (!this._listeners[key]) return;
|
|
771
|
-
|
|
757
|
+
// Backwards compat: if an unsubscribe function returned by beginListen was passed to stopListen,
|
|
758
|
+
// resolve it to the original callback via symbol so the listener can be found and removed.
|
|
759
|
+
const original = (callback as any)[$originalCallback] ?? callback;
|
|
760
|
+
if (original !== callback && isDevEnvironment() && !NetworkConnection._didLogStopListenHint) {
|
|
761
|
+
NetworkConnection._didLogStopListenHint = true;
|
|
762
|
+
console.warn("[Needle Engine] Tip: beginListen() returns an unsubscribe function — you can call it directly instead of using stopListen().");
|
|
763
|
+
}
|
|
764
|
+
const index = this._listeners[key].indexOf(original);
|
|
772
765
|
if (index >= 0) {
|
|
773
766
|
this._listeners[key].splice(index, 1);
|
|
774
767
|
}
|
|
775
768
|
}
|
|
776
769
|
|
|
777
|
-
/** Use to start listening to networking binary events
|
|
778
|
-
|
|
770
|
+
/** Use to start listening to networking binary events.
|
|
771
|
+
* Returns an unsubscribe function that removes the listener when called.
|
|
772
|
+
* The returned function can also be passed to {@link stopListenBinary} or {@link Component.autoCleanup}.
|
|
773
|
+
*/
|
|
774
|
+
public beginListenBinary(identifier: string, callback: BinaryCallback): DisposeFn {
|
|
779
775
|
if (!this._listenersBinary[identifier])
|
|
780
776
|
this._listenersBinary[identifier] = [];
|
|
781
777
|
this._listenersBinary[identifier].push(callback);
|
|
782
|
-
|
|
778
|
+
const unsub = () => this.stopListenBinary(identifier, callback);
|
|
779
|
+
(unsub as any)[$originalCallback] = callback;
|
|
780
|
+
return unsub;
|
|
783
781
|
}
|
|
784
782
|
|
|
785
|
-
/** Use to stop listening to networking binary events
|
|
783
|
+
/** Use to stop listening to networking binary events.
|
|
784
|
+
* Accepts either the original callback or the unsubscribe function returned by {@link beginListenBinary}.
|
|
785
|
+
*/
|
|
786
786
|
public stopListenBinary(identifier: string, callback: any) {
|
|
787
787
|
if (!this._listenersBinary[identifier]) return;
|
|
788
|
-
|
|
788
|
+
// Backwards compat: resolve unsubscribe function to original callback via symbol
|
|
789
|
+
const original = callback?.[$originalCallback] ?? callback;
|
|
790
|
+
const index = this._listenersBinary[identifier].indexOf(original);
|
|
789
791
|
if (index >= 0) {
|
|
790
792
|
this._listenersBinary[identifier].splice(index, 1);
|
|
791
793
|
}
|
|
@@ -802,14 +804,18 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
802
804
|
|
|
803
805
|
/** Used to connect to the networking server
|
|
804
806
|
* @param url Optional url to connect to. If not provided, it will use the url from the registered `INetworkingWebsocketUrlProvider` or the default backend networking url. If you want to change the url after connecting, you need to disconnect first and then connect again with the new url.
|
|
807
|
+
* @param transport Optional custom transport to use instead of the default websocket. Useful for testing or alternative transports.
|
|
805
808
|
*/
|
|
806
|
-
public async connect(url?: string) {
|
|
809
|
+
public async connect(url?: string, transport?: INetworkTransport) {
|
|
807
810
|
if (this.connected && url && url !== networkingServerUrl) {
|
|
808
811
|
return Promise.reject("Can not connect to different server url. Please disconnect first.");
|
|
809
812
|
}
|
|
810
813
|
if (this.connected) {
|
|
811
814
|
return Promise.resolve(true);
|
|
812
815
|
}
|
|
816
|
+
if (transport) {
|
|
817
|
+
return this.connectTransport(transport);
|
|
818
|
+
}
|
|
813
819
|
if (url)
|
|
814
820
|
console.debug("Connecting to user provided url " + url);
|
|
815
821
|
const overrideUrl = url || this.netWebSocketUrlProvider?.getWebsocketUrl();
|
|
@@ -824,11 +830,20 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
824
830
|
|
|
825
831
|
/** Disconnect from the networking backend + reset internal state */
|
|
826
832
|
public disconnect() {
|
|
827
|
-
this.
|
|
828
|
-
|
|
833
|
+
if (this._transport) {
|
|
834
|
+
// Clear callbacks before closing so the onClose handler doesn't fire
|
|
835
|
+
this._transport.onOpen = null;
|
|
836
|
+
this._transport.onClose = null;
|
|
837
|
+
this._transport.onError = null;
|
|
838
|
+
this._transport.onMessage = null;
|
|
839
|
+
this._transport.close();
|
|
840
|
+
this._transport = undefined;
|
|
841
|
+
}
|
|
829
842
|
networkingServerUrl = undefined;
|
|
830
843
|
|
|
831
|
-
// Reset all state
|
|
844
|
+
// Reset all state synchronously so callers can rely on isConnected/isInRoom immediately
|
|
845
|
+
this.connected = false;
|
|
846
|
+
this._connectionId = null;
|
|
832
847
|
this._currentRoomAllowEditing = true;
|
|
833
848
|
this._currentRoomName = null;
|
|
834
849
|
this._currentRoomViewId = null;
|
|
@@ -836,16 +851,26 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
836
851
|
this._currentInRoom.length = 0;
|
|
837
852
|
this._state = {};
|
|
838
853
|
this._currentDelay = -1;
|
|
854
|
+
this._connectingToWebsocketPromise = null;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/** Full teardown: disconnect, clear all listeners, and release all resources.
|
|
858
|
+
* Called when the owning Context is destroyed. After dispose(), this instance should not be reused. */
|
|
859
|
+
public dispose() {
|
|
860
|
+
this.disconnect();
|
|
861
|
+
this._listeners = {};
|
|
862
|
+
this._listenersBinary = {};
|
|
863
|
+
this._waitingForSocket = {};
|
|
864
|
+
this._peer = null;
|
|
839
865
|
}
|
|
840
866
|
|
|
841
867
|
private _listeners: { [key: string]: Function[] } = {};
|
|
842
868
|
private _listenersBinary: { [key: string]: BinaryCallback[] } = {};
|
|
843
869
|
private connected: boolean = false;
|
|
844
|
-
private channelId: string | undefined;
|
|
845
870
|
private _connectionId: string | null = null;
|
|
846
871
|
|
|
847
|
-
//
|
|
848
|
-
private
|
|
872
|
+
// Transport ------------------------------------------------------------
|
|
873
|
+
private _transport: INetworkTransport | undefined;
|
|
849
874
|
private _waitingForSocket: { [key: string]: Array<Function> } = {};
|
|
850
875
|
private _isInRoom: boolean = false;
|
|
851
876
|
private _currentRoomName: string | null = null;
|
|
@@ -857,71 +882,70 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
857
882
|
|
|
858
883
|
private _connectingToWebsocketPromise: Promise<boolean> | null = null;
|
|
859
884
|
|
|
860
|
-
|
|
885
|
+
/** Wire up a transport's event callbacks and start it */
|
|
886
|
+
private connectTransport(transport: INetworkTransport): Promise<boolean> {
|
|
861
887
|
if (this._connectingToWebsocketPromise) return this._connectingToWebsocketPromise;
|
|
862
|
-
return this._connectingToWebsocketPromise = new Promise(
|
|
888
|
+
return this._connectingToWebsocketPromise = new Promise((res) => {
|
|
863
889
|
let didResolve = false;
|
|
864
890
|
const resolve = (val: boolean) => {
|
|
865
891
|
if (didResolve) return;
|
|
866
892
|
didResolve = true;
|
|
867
893
|
res(val);
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
if (
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
transport.onOpen = () => {
|
|
897
|
+
this._connectingToWebsocketPromise = null;
|
|
898
|
+
this._transport = transport;
|
|
899
|
+
this.connected = true;
|
|
900
|
+
if (isDevEnvironment() || debugNet) console.log("Connected to networking backend\n" + (transport.url ?? ""));
|
|
901
|
+
else console.debug("Connected to networking backend", transport.url ?? "");
|
|
902
|
+
resolve(true);
|
|
903
|
+
this.onSendQueued(SendQueue.OnConnection);
|
|
904
|
+
};
|
|
905
|
+
transport.onClose = () => {
|
|
906
|
+
this._connectingToWebsocketPromise = null;
|
|
907
|
+
this.connected = false;
|
|
908
|
+
this._isInRoom = false;
|
|
878
909
|
resolve(false);
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
else console.debug("Connected to networking backend", networkingServerUrl);
|
|
896
|
-
resolve(true);
|
|
897
|
-
this.onSendQueued(SendQueue.OnConnection);
|
|
898
|
-
})
|
|
899
|
-
.onClose((_evt) => {
|
|
900
|
-
this._connectingToWebsocketPromise = null;
|
|
901
|
-
this.connected = false;
|
|
902
|
-
this._isInRoom = false;
|
|
903
|
-
resolve(false);
|
|
904
|
-
let msg = "Websocket connection closed...";
|
|
905
|
-
if (!networkingServerUrl?.includes("/socket")) msg += ` Do you perhaps mean to connect to \"/socket\"?`;
|
|
906
|
-
console.error(msg);
|
|
907
|
-
})
|
|
908
|
-
.onError((_e) => {
|
|
909
|
-
console.error("Websocket connection failed...");
|
|
910
|
-
resolve(false);
|
|
911
|
-
Telemetry.sendEvent(this.context, "networking", {
|
|
912
|
-
event: "connection_error",
|
|
913
|
-
});
|
|
914
|
-
})
|
|
915
|
-
.onRetry(() => { console.log("Retry connecting to networking websocket") })
|
|
916
|
-
.build();
|
|
917
|
-
ws.addEventListener(pkg.WebsocketEvent.message, (socket, msg) => {
|
|
918
|
-
this.onMessage(socket, msg);
|
|
919
|
-
});
|
|
910
|
+
let msg = "Websocket connection closed...";
|
|
911
|
+
if (!transport.url?.includes("/socket")) msg += ` Do you perhaps mean to connect to "/socket"?`;
|
|
912
|
+
console.error(msg);
|
|
913
|
+
};
|
|
914
|
+
transport.onError = () => {
|
|
915
|
+
console.error("Websocket connection failed...");
|
|
916
|
+
resolve(false);
|
|
917
|
+
Telemetry.sendEvent(this.context, "networking", {
|
|
918
|
+
event: "connection_error",
|
|
919
|
+
});
|
|
920
|
+
};
|
|
921
|
+
transport.onMessage = (data) => {
|
|
922
|
+
this.onMessage(data);
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
transport.start();
|
|
920
926
|
});
|
|
921
927
|
}
|
|
922
928
|
|
|
923
|
-
private
|
|
924
|
-
|
|
929
|
+
private async connectWebsocket() {
|
|
930
|
+
if (this._connectingToWebsocketPromise) return this._connectingToWebsocketPromise;
|
|
931
|
+
|
|
932
|
+
if (networkingServerUrl === undefined) {
|
|
933
|
+
console.log("Fetch default backend url: " + defaultNetworkingBackendUrlProvider);
|
|
934
|
+
const defaultUrlResponse = await fetch(defaultNetworkingBackendUrlProvider);
|
|
935
|
+
networkingServerUrl = await defaultUrlResponse.text();
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (networkingServerUrl === undefined) {
|
|
939
|
+
return false;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
console.debug("Connecting to networking backend on\n" + networkingServerUrl);
|
|
943
|
+
const transport = new WebsocketTransport(networkingServerUrl);
|
|
944
|
+
return this.connectTransport(transport);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
private onMessage(data: string | Blob) {
|
|
948
|
+
const msg = data;
|
|
925
949
|
try {
|
|
926
950
|
if (typeof msg !== "string") {
|
|
927
951
|
if (msg.size) {
|
|
@@ -1098,9 +1122,9 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
1098
1122
|
};
|
|
1099
1123
|
}
|
|
1100
1124
|
|
|
1101
|
-
private sendWithWebsocket(key: string, data:
|
|
1125
|
+
private sendWithWebsocket(key: string, data: WebsocketSendType | undefined, queue: SendQueue = SendQueue.OnRoomJoin) {
|
|
1102
1126
|
// console.log(key);
|
|
1103
|
-
if (!this.
|
|
1127
|
+
if (!this._transport) {
|
|
1104
1128
|
const arr = this._waitingForSocket[queue] || [];
|
|
1105
1129
|
arr.push(() => this.sendWithWebsocket(key, data, queue));
|
|
1106
1130
|
this._waitingForSocket[queue] = arr;
|
|
@@ -1109,7 +1133,7 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
1109
1133
|
}
|
|
1110
1134
|
const str = JSON.stringify(this.toMessage(key, data));
|
|
1111
1135
|
if (debugNet) console.log(">>", key);
|
|
1112
|
-
this.
|
|
1136
|
+
this._transport.send(str);
|
|
1113
1137
|
}
|
|
1114
1138
|
|
|
1115
1139
|
private onSendQueued(queue: SendQueue) {
|
|
@@ -498,8 +498,8 @@ export function beginListenInstantiate(context: Context) {
|
|
|
498
498
|
});
|
|
499
499
|
|
|
500
500
|
return () => {
|
|
501
|
-
|
|
502
|
-
|
|
501
|
+
cb1();
|
|
502
|
+
cb2();
|
|
503
503
|
}
|
|
504
504
|
}
|
|
505
505
|
|
|
@@ -11,15 +11,55 @@ export declare interface IModel {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
/** Controls when a network message is actually sent to the server.
|
|
15
|
+
* @see {@link NetworkConnection.send}
|
|
16
|
+
*/
|
|
14
17
|
export enum SendQueue {
|
|
18
|
+
/** Hold the message until the transport connection opens, then send it. Use for messages that must arrive as soon as the socket is ready (e.g. join-room). */
|
|
15
19
|
OnConnection,
|
|
20
|
+
/** Hold the message until the client has joined a room, then send it. Use for messages that require room context. */
|
|
16
21
|
OnRoomJoin,
|
|
22
|
+
/** Buffer the message and send it on the next `sendBufferedMessagesNow()` call (typically once per frame). This is the default for `send()`. */
|
|
17
23
|
Queued,
|
|
24
|
+
/** Send the message to the server immediately without buffering. */
|
|
18
25
|
Immediate,
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
export declare interface INetworkConnection {
|
|
22
29
|
get isConnected(): boolean;
|
|
23
30
|
get isInRoom(): boolean;
|
|
24
|
-
send(key: string, data
|
|
31
|
+
send(key: string, data?: IModel | object | boolean | string | number, queue?: SendQueue): unknown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @experimental
|
|
36
|
+
* Interface for a network transport layer used by {@link NetworkConnection}.
|
|
37
|
+
* The default implementation wraps a websocket via `websocket-ts`.
|
|
38
|
+
* Custom implementations can be injected into {@link NetworkConnection.connect}
|
|
39
|
+
* for testing or alternative transports.
|
|
40
|
+
*
|
|
41
|
+
* **Lifecycle:** After creating a transport and passing it to `connect()`,
|
|
42
|
+
* `NetworkConnection` sets the four event callbacks (`onOpen`, `onClose`,
|
|
43
|
+
* `onError`, `onMessage`) and then calls {@link start}. The transport
|
|
44
|
+
* should call `onOpen` when the connection is ready.
|
|
45
|
+
*/
|
|
46
|
+
export interface INetworkTransport {
|
|
47
|
+
/** Start the connection. Called by NetworkConnection after event callbacks are set.
|
|
48
|
+
* May return a promise if setup is async (e.g. dynamic imports). */
|
|
49
|
+
start(): void | Promise<void>;
|
|
50
|
+
/** Send data (string for JSON messages, Uint8Array for binary) */
|
|
51
|
+
send(data: string | Uint8Array): void;
|
|
52
|
+
/** Close the transport */
|
|
53
|
+
close(): void | Promise<void>;
|
|
54
|
+
/** The URL this transport is connected to, if applicable */
|
|
55
|
+
readonly url: string | undefined;
|
|
56
|
+
|
|
57
|
+
/** Called when the transport connection opens */
|
|
58
|
+
onOpen: (() => void) | null;
|
|
59
|
+
/** Called when the transport connection closes */
|
|
60
|
+
onClose: (() => void) | null;
|
|
61
|
+
/** Called when an error occurs */
|
|
62
|
+
onError: ((err: any) => void) | null;
|
|
63
|
+
/** Called when a message is received. Data is either a string (JSON) or Blob (binary). */
|
|
64
|
+
onMessage: ((data: string | Blob) => void) | null;
|
|
25
65
|
}
|