action-engine-js 1.0.0

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.
Files changed (93) hide show
  1. package/LICENSE +45 -0
  2. package/README.md +348 -0
  3. package/actionengine/3rdparty/goblin/goblin.js +9609 -0
  4. package/actionengine/3rdparty/goblin/goblin.min.js +5 -0
  5. package/actionengine/camera/actioncamera.js +90 -0
  6. package/actionengine/camera/cameracollisionhandler.js +69 -0
  7. package/actionengine/character/actioncharacter.js +360 -0
  8. package/actionengine/character/actioncharacter3D.js +61 -0
  9. package/actionengine/core/app.js +430 -0
  10. package/actionengine/debug/basedebugpanel.js +858 -0
  11. package/actionengine/display/canvasmanager.js +75 -0
  12. package/actionengine/display/gl/programmanager.js +570 -0
  13. package/actionengine/display/gl/shaders/lineshader.js +118 -0
  14. package/actionengine/display/gl/shaders/objectshader.js +1756 -0
  15. package/actionengine/display/gl/shaders/particleshader.js +43 -0
  16. package/actionengine/display/gl/shaders/shadowshader.js +319 -0
  17. package/actionengine/display/gl/shaders/spriteshader.js +100 -0
  18. package/actionengine/display/gl/shaders/watershader.js +67 -0
  19. package/actionengine/display/graphics/actionmodel3D.js +191 -0
  20. package/actionengine/display/graphics/actionsprite3D.js +230 -0
  21. package/actionengine/display/graphics/lighting/actiondirectionalshadowlight.js +864 -0
  22. package/actionengine/display/graphics/lighting/actionlight.js +211 -0
  23. package/actionengine/display/graphics/lighting/actionomnidirectionalshadowlight.js +862 -0
  24. package/actionengine/display/graphics/lighting/lightingconstants.js +263 -0
  25. package/actionengine/display/graphics/lighting/lightmanager.js +789 -0
  26. package/actionengine/display/graphics/renderableobject.js +44 -0
  27. package/actionengine/display/graphics/renderers/actionrenderer2D.js +341 -0
  28. package/actionengine/display/graphics/renderers/actionrenderer3D/actionrenderer3D.js +655 -0
  29. package/actionengine/display/graphics/renderers/actionrenderer3D/canvasmanager3D.js +82 -0
  30. package/actionengine/display/graphics/renderers/actionrenderer3D/debugrenderer3D.js +493 -0
  31. package/actionengine/display/graphics/renderers/actionrenderer3D/objectrenderer3D.js +790 -0
  32. package/actionengine/display/graphics/renderers/actionrenderer3D/spriteRenderer3D.js +266 -0
  33. package/actionengine/display/graphics/renderers/actionrenderer3D/sunrenderer3D.js +140 -0
  34. package/actionengine/display/graphics/renderers/actionrenderer3D/waterrenderer3D.js +173 -0
  35. package/actionengine/display/graphics/renderers/actionrenderer3D/weatherrenderer3D.js +87 -0
  36. package/actionengine/display/graphics/texture/proceduraltexture.js +192 -0
  37. package/actionengine/display/graphics/texture/texturemanager.js +242 -0
  38. package/actionengine/display/graphics/texture/textureregistry.js +177 -0
  39. package/actionengine/input/actionscrollablearea.js +1405 -0
  40. package/actionengine/input/inputhandler.js +1647 -0
  41. package/actionengine/math/geometry/geometrybuilder.js +161 -0
  42. package/actionengine/math/geometry/glbexporter.js +364 -0
  43. package/actionengine/math/geometry/glbloader.js +722 -0
  44. package/actionengine/math/geometry/modelcodegenerator.js +97 -0
  45. package/actionengine/math/geometry/triangle.js +33 -0
  46. package/actionengine/math/geometry/triangleutils.js +34 -0
  47. package/actionengine/math/mathutils.js +25 -0
  48. package/actionengine/math/matrix4.js +785 -0
  49. package/actionengine/math/physics/actionphysics.js +108 -0
  50. package/actionengine/math/physics/actionphysicsobject3D.js +164 -0
  51. package/actionengine/math/physics/actionphysicsworld3D.js +238 -0
  52. package/actionengine/math/physics/actionraycast.js +129 -0
  53. package/actionengine/math/physics/shapes/actionphysicsbox3D.js +158 -0
  54. package/actionengine/math/physics/shapes/actionphysicscapsule3D.js +200 -0
  55. package/actionengine/math/physics/shapes/actionphysicscompoundshape3D.js +147 -0
  56. package/actionengine/math/physics/shapes/actionphysicscone3D.js +126 -0
  57. package/actionengine/math/physics/shapes/actionphysicsconvexshape3D.js +72 -0
  58. package/actionengine/math/physics/shapes/actionphysicscylinder3D.js +117 -0
  59. package/actionengine/math/physics/shapes/actionphysicsmesh3D.js +74 -0
  60. package/actionengine/math/physics/shapes/actionphysicsplane3D.js +100 -0
  61. package/actionengine/math/physics/shapes/actionphysicssphere3D.js +95 -0
  62. package/actionengine/math/quaternion.js +61 -0
  63. package/actionengine/math/vector2.js +277 -0
  64. package/actionengine/math/vector3.js +318 -0
  65. package/actionengine/math/viewfrustum.js +136 -0
  66. package/actionengine/network/ACTIONNETREADME.md +810 -0
  67. package/actionengine/network/client/ActionNetManager.js +802 -0
  68. package/actionengine/network/client/ActionNetManagerGUI.js +1709 -0
  69. package/actionengine/network/client/ActionNetManagerP2P.js +1537 -0
  70. package/actionengine/network/client/SyncSystem.js +422 -0
  71. package/actionengine/network/p2p/ActionNetPeer.js +142 -0
  72. package/actionengine/network/p2p/ActionNetTrackerClient.js +623 -0
  73. package/actionengine/network/p2p/DataConnection.js +282 -0
  74. package/actionengine/network/p2p/README.md +510 -0
  75. package/actionengine/network/p2p/example.html +502 -0
  76. package/actionengine/network/server/ActionNetServer.js +577 -0
  77. package/actionengine/network/server/ActionNetServerSSL.js +579 -0
  78. package/actionengine/network/server/ActionNetServerUtils.js +458 -0
  79. package/actionengine/network/server/SERVERREADME.md +314 -0
  80. package/actionengine/network/server/package-lock.json +35 -0
  81. package/actionengine/network/server/package.json +13 -0
  82. package/actionengine/network/server/start.bat +27 -0
  83. package/actionengine/network/server/start.sh +25 -0
  84. package/actionengine/network/server/startwss.bat +27 -0
  85. package/actionengine/sound/audiomanager.js +1589 -0
  86. package/actionengine/sound/soundfont/ACTIONSOUNDFONT_README.md +205 -0
  87. package/actionengine/sound/soundfont/actionparser.js +718 -0
  88. package/actionengine/sound/soundfont/actionreverb.js +252 -0
  89. package/actionengine/sound/soundfont/actionsoundfont.js +543 -0
  90. package/actionengine/sound/soundfont/sf2playerlicence.txt +29 -0
  91. package/actionengine/sound/soundfont/soundfont.js +2 -0
  92. package/dist/action-engine.min.js +328 -0
  93. package/package.json +35 -0
@@ -0,0 +1,422 @@
1
+ /**
2
+ * SyncSystem - Generic client-to-client state synchronization
3
+ *
4
+ * A transport-agnostic, event-driven synchronization primitive for multiplayer games.
5
+ *
6
+ * DESIGN PRINCIPLES:
7
+ * - Generic: Works with any data, any transport, any game
8
+ * - Registry-based: Register multiple sync sources by ID
9
+ * - Event-driven: Emit events instead of tight coupling
10
+ * - Self-healing: Periodic broadcasts mean dropped messages don't matter
11
+ * - Observable: Track liveness and staleness of remote data
12
+ *
13
+ * USAGE EXAMPLE:
14
+ * ```javascript
15
+ * // Create sync system with transport function
16
+ * const sync = new SyncSystem({
17
+ * send: (msg) => networkManager.send(msg),
18
+ * broadcastInterval: 16, // 60fps
19
+ * staleThreshold: 200 // 12 frames
20
+ * });
21
+ *
22
+ * // Register sync sources
23
+ * sync.register('match', {
24
+ * getFields: () => ({
25
+ * state: matchStateMachine.getState(),
26
+ * ready: isReady
27
+ * })
28
+ * });
29
+ *
30
+ * sync.register('player', {
31
+ * getFields: () => ({
32
+ * score: player.score,
33
+ * level: player.level,
34
+ * alive: !player.gameOver
35
+ * })
36
+ * });
37
+ *
38
+ * // Listen for remote updates
39
+ * sync.on('remoteUpdated', (allRemoteData) => {
40
+ * console.log('Remote data changed:', allRemoteData);
41
+ * checkGameConditions();
42
+ * });
43
+ *
44
+ * sync.on('remoteStale', () => {
45
+ * console.warn('Remote client stopped responding');
46
+ * });
47
+ *
48
+ * // Start syncing
49
+ * sync.start();
50
+ *
51
+ * // Query remote data
52
+ * const remoteMatch = sync.getRemote('match');
53
+ * if (remoteMatch && remoteMatch.ready) {
54
+ * startGame();
55
+ * }
56
+ *
57
+ * // Stop syncing
58
+ * sync.stop();
59
+ * ```
60
+ */
61
+ class SyncSystem {
62
+ constructor(options = {}) {
63
+ // Transport function (how to send messages)
64
+ this.sendFunction = options.send || null;
65
+
66
+ // Configuration
67
+ this.broadcastInterval = options.broadcastInterval || 16; // 16ms = ~60fps
68
+ this.staleThreshold = options.staleThreshold || 200; // 200ms = ~12 frames
69
+
70
+ // Registry of sync sources
71
+ this.syncSources = new Map(); // id -> source object with getFields()
72
+
73
+ // Local data (what we broadcast)
74
+ this.localData = {};
75
+
76
+ // Remote data (what we receive)
77
+ this.remoteData = {};
78
+ this.lastRemoteUpdate = 0;
79
+ this.wasStale = false;
80
+
81
+ // Event listeners
82
+ this.listeners = new Map();
83
+
84
+ // Broadcast timer
85
+ this.broadcastTimer = null;
86
+ this.isRunning = false;
87
+
88
+ // console.log('[SyncSystem] Initialized', {
89
+ // broadcastInterval: this.broadcastInterval,
90
+ // staleThreshold: this.staleThreshold
91
+ // });
92
+ }
93
+
94
+ /**
95
+ * Update the send function (transport pipeline)
96
+ * Call this when transport conditions change (e.g., new dataChannel available)
97
+ *
98
+ * @param {Function} sendFunction - New send function
99
+ */
100
+ setSendFunction(sendFunction) {
101
+ if (typeof sendFunction !== 'function') {
102
+ console.error('[SyncSystem] setSendFunction requires a function');
103
+ return false;
104
+ }
105
+
106
+ this.sendFunction = sendFunction;
107
+ // console.log('[SyncSystem] Updated send function');
108
+ return true;
109
+ }
110
+
111
+ /**
112
+ * Register a sync source
113
+ *
114
+ * @param {String} id - Unique identifier for this sync source
115
+ * @param {Object} source - Object with getFields() method that returns data to sync
116
+ *
117
+ * Example:
118
+ * sync.register('player', {
119
+ * getFields: () => ({ score: player.score, level: player.level })
120
+ * });
121
+ */
122
+ register(id, source) {
123
+ if (!source || typeof source.getFields !== 'function') {
124
+ console.error(`[SyncSystem] Source '${id}' must have a getFields() method`);
125
+ return false;
126
+ }
127
+
128
+ this.syncSources.set(id, source);
129
+ // console.log(`[SyncSystem] Registered sync source: '${id}'`);
130
+
131
+ // Immediately update local data for this source
132
+ this._updateLocalDataForSource(id);
133
+
134
+ return true;
135
+ }
136
+
137
+ /**
138
+ * Unregister a sync source
139
+ *
140
+ * @param {String} id - ID of source to remove
141
+ */
142
+ unregister(id) {
143
+ if (this.syncSources.delete(id)) {
144
+ delete this.localData[id];
145
+ // console.log(`[SyncSystem] Unregistered sync source: '${id}'`);
146
+ return true;
147
+ }
148
+ return false;
149
+ }
150
+
151
+ /**
152
+ * Register an event listener
153
+ *
154
+ * Available events:
155
+ * - 'remoteUpdated': (remoteData) => {} - Remote data received
156
+ * - 'remoteStale': () => {} - Remote stopped sending updates
157
+ * - 'remoteFresh': () => {} - Remote resumed sending after being stale
158
+ * - 'broadcast': (localData) => {} - We broadcasted data
159
+ *
160
+ * @param {String} event - Event name
161
+ * @param {Function} handler - Event handler
162
+ */
163
+ on(event, handler) {
164
+ if (!this.listeners.has(event)) {
165
+ this.listeners.set(event, []);
166
+ }
167
+ this.listeners.get(event).push(handler);
168
+ }
169
+
170
+ /**
171
+ * Remove an event listener
172
+ *
173
+ * @param {String} event - Event name
174
+ * @param {Function} handler - Event handler to remove
175
+ */
176
+ off(event, handler) {
177
+ if (!this.listeners.has(event)) return;
178
+ const handlers = this.listeners.get(event);
179
+ const index = handlers.indexOf(handler);
180
+ if (index > -1) {
181
+ handlers.splice(index, 1);
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Emit an event to all registered handlers
187
+ * @private
188
+ */
189
+ _emit(event, ...args) {
190
+ if (!this.listeners.has(event)) return;
191
+ const handlers = this.listeners.get(event);
192
+ handlers.forEach(handler => {
193
+ try {
194
+ handler(...args);
195
+ } catch (error) {
196
+ console.error(`[SyncSystem] Error in '${event}' handler:`, error);
197
+ }
198
+ });
199
+ }
200
+
201
+ /**
202
+ * Start periodic broadcasting
203
+ */
204
+ start() {
205
+ if (this.isRunning) {
206
+ console.warn('[SyncSystem] Already running');
207
+ return;
208
+ }
209
+
210
+ if (!this.sendFunction) {
211
+ console.error('[SyncSystem] Cannot start - no send function provided');
212
+ return;
213
+ }
214
+
215
+ // console.log('[SyncSystem] Starting periodic broadcast...');
216
+ this.isRunning = true;
217
+
218
+ // Broadcast immediately
219
+ this._broadcast();
220
+
221
+ // Then broadcast periodically
222
+ this.broadcastTimer = setInterval(() => {
223
+ this._broadcast();
224
+ }, this.broadcastInterval);
225
+ }
226
+
227
+ /**
228
+ * Stop periodic broadcasting
229
+ */
230
+ stop() {
231
+ if (!this.isRunning) {
232
+ return;
233
+ }
234
+
235
+ // console.log('[SyncSystem] Stopping broadcast');
236
+ this.isRunning = false;
237
+
238
+ if (this.broadcastTimer) {
239
+ clearInterval(this.broadcastTimer);
240
+ this.broadcastTimer = null;
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Update local data for a specific source
246
+ * @private
247
+ */
248
+ _updateLocalDataForSource(id) {
249
+ const source = this.syncSources.get(id);
250
+ if (!source) return;
251
+
252
+ try {
253
+ this.localData[id] = source.getFields();
254
+ } catch (error) {
255
+ console.error(`[SyncSystem] Error getting fields from source '${id}':`, error);
256
+ this.localData[id] = null;
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Update local data from all registered sources
262
+ * @private
263
+ */
264
+ _updateAllLocalData() {
265
+ for (const id of this.syncSources.keys()) {
266
+ this._updateLocalDataForSource(id);
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Broadcast current data to remote clients
272
+ * @private
273
+ */
274
+ _broadcast() {
275
+ // Update local data from all sources
276
+ this._updateAllLocalData();
277
+
278
+ // Send sync update message
279
+ const message = {
280
+ type: 'syncUpdate',
281
+ data: this.localData,
282
+ timestamp: Date.now()
283
+ };
284
+
285
+ this.sendFunction(message);
286
+
287
+ // Emit broadcast event
288
+ this._emit('broadcast', this.localData);
289
+ }
290
+
291
+ /**
292
+ * Handle incoming sync update from remote client
293
+ *
294
+ * @param {Object} message - Sync update message with data field
295
+ */
296
+ handleSyncUpdate(message) {
297
+ if (!message || !message.data) {
298
+ console.warn('[SyncSystem] Received syncUpdate without data');
299
+ return;
300
+ }
301
+
302
+ const now = Date.now();
303
+ const wasStale = this.isRemoteStale();
304
+
305
+ // Store remote data
306
+ this.remoteData = message.data;
307
+ this.lastRemoteUpdate = now;
308
+
309
+ // Emit updated event
310
+ this._emit('remoteUpdated', this.remoteData);
311
+
312
+ // Check staleness transition
313
+ if (wasStale && !this.isRemoteStale()) {
314
+ // Remote was stale but is now fresh
315
+ this._emit('remoteFresh');
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Get remote data for a specific source
321
+ *
322
+ * @param {String} id - Source ID
323
+ * @returns {Object|null} - Remote data for that source, or null if not available
324
+ */
325
+ getRemote(id) {
326
+ return this.remoteData[id] || null;
327
+ }
328
+
329
+ /**
330
+ * Get specific field from remote data
331
+ *
332
+ * @param {String} sourceId - Source ID
333
+ * @param {String} field - Field name
334
+ * @returns {*} - Field value or null
335
+ */
336
+ getRemoteField(sourceId, field) {
337
+ const sourceData = this.getRemote(sourceId);
338
+ return sourceData ? sourceData[field] : null;
339
+ }
340
+
341
+ /**
342
+ * Get all remote data
343
+ *
344
+ * @returns {Object} - All remote data from all sources
345
+ */
346
+ getAllRemote() {
347
+ return { ...this.remoteData };
348
+ }
349
+
350
+ /**
351
+ * Check if remote client data is stale (hasn't updated recently)
352
+ *
353
+ * @returns {Boolean} - True if data is stale or never received
354
+ */
355
+ isRemoteStale() {
356
+ if (this.lastRemoteUpdate === 0) {
357
+ return true; // Never received data
358
+ }
359
+
360
+ const timeSinceUpdate = Date.now() - this.lastRemoteUpdate;
361
+ const isStale = timeSinceUpdate > this.staleThreshold;
362
+
363
+ // Emit stale event on transition
364
+ if (isStale && !this.wasStale) {
365
+ this.wasStale = true;
366
+ this._emit('remoteStale');
367
+ } else if (!isStale) {
368
+ this.wasStale = false;
369
+ }
370
+
371
+ return isStale;
372
+ }
373
+
374
+ /**
375
+ * Get time since last remote update in milliseconds
376
+ *
377
+ * @returns {Number} - Milliseconds since last update (Infinity if never received)
378
+ */
379
+ getTimeSinceLastUpdate() {
380
+ if (this.lastRemoteUpdate === 0) {
381
+ return Infinity;
382
+ }
383
+ return Date.now() - this.lastRemoteUpdate;
384
+ }
385
+
386
+ /**
387
+ * Check if remote client has sent at least one update
388
+ *
389
+ * @returns {Boolean} - True if we've received data
390
+ */
391
+ hasRemoteData() {
392
+ return this.lastRemoteUpdate > 0;
393
+ }
394
+
395
+ /**
396
+ * Force an immediate broadcast (outside normal interval)
397
+ */
398
+ forceBroadcast() {
399
+ if (this.isRunning) {
400
+ this._broadcast();
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Clear remote data (useful when remote client disconnects)
406
+ */
407
+ clearRemoteData() {
408
+ this.remoteData = {};
409
+ this.lastRemoteUpdate = 0;
410
+ this.wasStale = false;
411
+ // console.log('[SyncSystem] Remote data cleared');
412
+ }
413
+
414
+ /**
415
+ * Get list of registered source IDs
416
+ *
417
+ * @returns {Array<String>} - Array of source IDs
418
+ */
419
+ getRegisteredSources() {
420
+ return Array.from(this.syncSources.keys());
421
+ }
422
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * ActionNetPeer - Wrapper around DataConnection
3
+ *
4
+ * Simple facade that wraps DataConnection and delegates all interface calls.
5
+ * Exists for compatibility and to handle tracker signaling via signal() method.
6
+ */
7
+
8
+ class ActionNetPeer {
9
+ constructor(opts = {}) {
10
+ this.opts = {
11
+ initiator: opts.initiator || false,
12
+ trickle: opts.trickle !== false,
13
+ iceServers: opts.iceServers || [
14
+ { urls: 'stun:stun.l.google.com:19302' },
15
+ { urls: 'stun:stun1.l.google.com:19302' },
16
+ { urls: 'stun:stun2.l.google.com:19302' }
17
+ ],
18
+ ...opts
19
+ };
20
+
21
+ // Peer IDs for initiator determination
22
+ this.localPeerId = opts.localPeerId || 'local';
23
+ this.remotePeerId = opts.remotePeerId || 'remote';
24
+
25
+ // Create internal DataConnection (the actual RTCPeerConnection)
26
+ this.connection = new DataConnection({
27
+ localPeerId: this.localPeerId,
28
+ remotePeerId: this.remotePeerId,
29
+ iceServers: this.opts.iceServers,
30
+ initiator: opts.initiator
31
+ });
32
+
33
+ // Expose DataConnection's pc directly
34
+ this.pc = this.connection.pc;
35
+ this.dataChannel = this.connection.dataChannel;
36
+
37
+ // State
38
+ this.destroyed = false;
39
+ this.connected = false;
40
+ this.handlers = new Map();
41
+ this.readyState = 'new';
42
+
43
+ // Delegate DataConnection events
44
+ this.connection.on('connect', () => {
45
+ this.connected = true;
46
+ this.readyState = 'connected';
47
+ this.emit('connect');
48
+ });
49
+
50
+ this.connection.on('error', (err) => {
51
+ this.emit('error', err);
52
+ });
53
+
54
+ this.connection.on('close', () => {
55
+ this.connected = false;
56
+ this.readyState = 'closed';
57
+ this.emit('close');
58
+ });
59
+
60
+ this.connection.on('data', (data) => {
61
+ this.emit('data', data);
62
+ });
63
+
64
+ this.connection.on('signal', (msg) => {
65
+ // Relay DataConnection's signaling messages (offer/answer/ICE)
66
+ this.emit('signal', msg);
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Handle tracker offer/answer/ICE candidates
72
+ */
73
+ signal(data) {
74
+ if (!this.connection) return;
75
+ this.connection.signal(data);
76
+ }
77
+
78
+ /**
79
+ * Send data through DataConnection
80
+ */
81
+ send(data) {
82
+ if (!this.connection) return false;
83
+ return this.connection.send(typeof data === 'string' ? JSON.parse(data) : data);
84
+ }
85
+
86
+ /**
87
+ * Event handlers
88
+ */
89
+ on(event, handler) {
90
+ if (!this.handlers.has(event)) {
91
+ this.handlers.set(event, []);
92
+ }
93
+ this.handlers.get(event).push(handler);
94
+ }
95
+
96
+ once(event, handler) {
97
+ const wrapper = (...args) => {
98
+ handler(...args);
99
+ this.off(event, wrapper);
100
+ };
101
+ this.on(event, wrapper);
102
+ }
103
+
104
+ off(event, handler) {
105
+ if (!this.handlers.has(event)) return;
106
+ const handlers = this.handlers.get(event);
107
+ const index = handlers.indexOf(handler);
108
+ if (index !== -1) {
109
+ handlers.splice(index, 1);
110
+ }
111
+ }
112
+
113
+ emit(event, ...args) {
114
+ if (!this.handlers.has(event)) return;
115
+ for (const handler of this.handlers.get(event)) {
116
+ try {
117
+ handler(...args);
118
+ } catch (e) {
119
+ console.error(`Error in ${event} handler:`, e);
120
+ }
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Cleanup
126
+ */
127
+ destroy() {
128
+ if (this.destroyed) return;
129
+
130
+ this.destroyed = true;
131
+ this.connected = false;
132
+
133
+ if (this.connection) {
134
+ this.connection.close();
135
+ this.connection = null;
136
+ }
137
+
138
+ this.pc = null;
139
+ this.dataChannel = null;
140
+ this.handlers.clear();
141
+ }
142
+ }