Needle Engine

Changes between version 4.0.0-alpha and 4.0.1-alpha
Files changed (5) hide show
  1. src/engine-components/Component.ts +2 -2
  2. src/engine/engine_networking_files.ts +3 -1
  3. src/engine/engine_networking_instantiate.ts +20 -8
  4. src/engine/engine_networking.ts +46 -3
  5. src/engine/engine_utils_screenshot.ts +10 -1
src/engine-components/Component.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  import { activeInHierarchyFieldName } from "../engine/engine_constants.js";
6
6
  import { destroy, findByGuid, foreachComponent, HideFlags, type IInstantiateOptions, instantiate, isActiveInHierarchy, isActiveSelf, isDestroyed, isUsingInstancing, markAsInstancedRendered, setActive } from "../engine/engine_gameobject.js";
7
7
  import * as main from "../engine/engine_mainloop_utils.js";
8
- import { syncDestroy, syncInstantiate } from "../engine/engine_networking_instantiate.js";
8
+ import { syncDestroy, syncInstantiate, SyncInstantiateOptions } from "../engine/engine_networking_instantiate.js";
9
9
  import { Context, FrameEvent } from "../engine/engine_setup.js";
10
10
  import * as threeutils from "../engine/engine_three_utils.js";
11
11
  import type { Collision, ComponentInit, Constructor, ConstructorConcrete, GuidsMap, ICollider, IComponent, IGameObject, SourceIdentifier } from "../engine/engine_types.js";
@@ -115,7 +115,7 @@
115
115
  * @param instance object to instantiate
116
116
  * @param opts options for the instantiation
117
117
  */
118
- public static instantiateSynced(instance: GameObject | Object3D | null, opts: IInstantiateOptions): GameObject | null {
118
+ public static instantiateSynced(instance: GameObject | Object3D | null, opts: SyncInstantiateOptions): GameObject | null {
119
119
  if (!instance) return null;
120
120
  return syncInstantiate(instance as any, opts) as GameObject | null;
121
121
  }
src/engine/engine_networking_files.ts CHANGED
@@ -188,10 +188,12 @@
188
188
 
189
189
 
190
190
  params.parent.add(root);
191
+ root.rotateY(Math.PI / 2);
192
+ // root.quaternion.copy(params.parent.quaternion);
193
+
191
194
  if (params.position) root.position?.copy(params.position);
192
195
  if (params.size) {
193
196
  root.worldScale = new Vector3().copy(params.size);
194
- // root.scale?.copy(params.size);
195
197
  }
196
198
  root.position.y = root.scale.y / 2;
197
199
 
src/engine/engine_networking_instantiate.ts CHANGED
@@ -247,8 +247,8 @@
247
247
  // listener.target = go;
248
248
  // }
249
249
  if (go.guid) {
250
- if (debug)
251
- console.log("[Local] new instance", "gameobject:", instance?.guid);
250
+
251
+ if (debug) console.log("[Local] new instance", "gameobject:", instance?.guid);
252
252
  const model = new NewInstanceModel(obj.guid, go.guid);
253
253
  model.seed = seed;
254
254
  if (opts.deleteOnDisconnect === true)
@@ -279,6 +279,8 @@
279
279
  const con = opts?.context?.connection;
280
280
  if (!con && isDevEnvironment())
281
281
  console.debug("Object will be instantiated but it will not be synced: not connected", obj.guid);
282
+
283
+ if (opts.context.connection.isInRoom) syncedInstantiated.push(new WeakRef(go));
282
284
  opts?.context?.connection.send(InstantiateEvent.NewInstanceCreated, model);
283
285
  }
284
286
  else console.warn("Missing guid, can not send new instance event", go);
@@ -290,7 +292,11 @@
290
292
  return Math.random() * 9_999_999;// Number.MAX_VALUE;;
291
293
  }
292
294
 
295
+ const syncedInstantiated = new Array<WeakRef<Object3D>>();
296
+
293
297
  export function beginListenInstantiate(context: Context) {
298
+
299
+
294
300
  context.connection.beginListen(InstantiateEvent.NewInstanceCreated, async (model: NewInstanceModel) => {
295
301
  const obj: GameObject | null = await tryResolvePrefab(model.originalGuid, context.scene) as GameObject;
296
302
  if (model.preventCreation === true) {
@@ -300,6 +306,7 @@
300
306
  console.warn("could not find object that was instantiated: " + model.guid);
301
307
  return;
302
308
  }
309
+ console.log(obj);
303
310
  const options = new InstantiateOptions();
304
311
  if (model.position)
305
312
  options.position = new Vector3(model.position.x, model.position.y, model.position.z);
@@ -315,22 +322,27 @@
315
322
  if (debug && context.alias)
316
323
  console.log("[Remote] instantiate in: " + context.alias);
317
324
  const inst = instantiate(obj as GameObject, options);
325
+ syncedInstantiated.push(new WeakRef(inst));
318
326
 
319
327
  if (inst) {
320
328
  if (model.parent === "scene")
321
329
  context.scene.add(inst);
322
- // console.log(inst, model.parent === "scene");
323
- // if (inst.guid) {
324
- // const listener = GameObject.addNewComponent(inst, DestroyListener);
325
- // listener.target = inst;
326
- // }
327
330
  if (debug)
328
331
  console.log("[Remote] new instance", "gameobject:", inst?.guid, obj);
329
332
  }
330
333
  });
331
-
334
+ context.connection.beginListen("left-room", () => {
335
+ if (syncedInstantiated.length > 0)
336
+ console.debug(`Left networking room, cleaning up ${syncedInstantiated.length} instantiated objects`);
337
+ for (const prev of syncedInstantiated) {
338
+ const obj = prev.deref();
339
+ if (obj) obj.destroy();
340
+ }
341
+ syncedInstantiated.length = 0;
342
+ })
332
343
  }
333
344
 
345
+
334
346
  function instantiateSeeded(obj: GameObject, opts: IInstantiateOptions | null): { instance: GameObject | null, seed: number } {
335
347
  const seed = generateSeed();
336
348
  const options = opts ?? new InstantiateOptions();
src/engine/engine_networking.ts CHANGED
@@ -260,7 +260,10 @@
260
260
  (data: any | flatbuffers.ByteBuffer): void;
261
261
  }
262
262
 
263
- /** Main class to communicate with the networking backend */
263
+ /** Main class to communicate with the networking backend
264
+ * @link https://engine.needle.tools/docs/networking.html
265
+ *
266
+ */
264
267
  export class NetworkConnection implements INetworkConnection {
265
268
 
266
269
  private context: Context;
@@ -454,7 +457,28 @@
454
457
  this._ws?.send(message);
455
458
  }
456
459
 
457
- /** Use to start listening to networking events */
460
+ /** Use to start listening to networking events.
461
+ * To unsubscribe from events use the `stopListen` method.
462
+ * See the example below for typical usage:
463
+ *
464
+ * ### Component Example
465
+ * ```ts
466
+ * // Make sure to unsubscribe from events when the component is disabled
467
+ * export class MyComponent extends Behaviour {
468
+ * onEnable() {
469
+ * this.connection.beginListen("joined-room", this.onJoinedRoom)
470
+ * }
471
+ * onDisable() {
472
+ * this.connection.stopListen("joined-room", this.onJoinedRoom)
473
+ * }
474
+ * onJoinedRoom = () => {
475
+ * console.log("I joined a networked room")
476
+ * }
477
+ * }
478
+ * ```
479
+ * @link https://engine.needle.tools/docs/networking.html
480
+ *
481
+ */
458
482
  public beginListen(key: (string & {}) | OwnershipEvent | OwnershipEventNamesIncoming | RoomEventsIncoming | RoomEvents, callback: Function): Function {
459
483
  if (!this._listeners[key])
460
484
  this._listeners[key] = [];
@@ -465,7 +489,26 @@
465
489
  /**@deprecated please use stopListen instead (2.65.2-pre) */
466
490
  public stopListening(key: (string & {}) | OwnershipEvent | OwnershipEventNamesIncoming | RoomEventsIncoming | RoomEvents, callback: Function | null) { return this.stopListen(key, callback); }
467
491
 
468
- /** Use to stop listening to networking events */
492
+ /** Use to stop listening to networking events
493
+ * To subscribe to events use the `beginListen` method.
494
+ * See the example below for typical usage:
495
+ *
496
+ * ### Component Example
497
+ * ```ts
498
+ * // Make sure to unsubscribe from events when the component is disabled
499
+ * export class MyComponent extends Behaviour {
500
+ * onEnable() {
501
+ * this.connection.beginListen("joined-room", this.onJoinedRoom)
502
+ * }
503
+ * onDisable() {
504
+ * this.connection.stopListen("joined-room", this.onJoinedRoom)
505
+ * }
506
+ * onJoinedRoom = () => {
507
+ * console.log("I joined a networked room")
508
+ * }
509
+ * }
510
+ * ```
511
+ */
469
512
  public stopListen(key: (string & {}) | OwnershipEvent | OwnershipEventNamesIncoming | RoomEventsIncoming | RoomEvents, callback: Function | null) {
470
513
  if (!callback) return;
471
514
  if (!this._listeners[key]) return;
src/engine/engine_utils_screenshot.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  import { Context } from "./engine_setup.js";
7
7
  import { ICamera } from "./engine_types.js";
8
8
  import { RGBAColor } from "./js-extensions/index.js";
9
+ import { setCustomVisibility } from "./js-extensions/Layers.js";
9
10
 
10
11
  declare type ScreenshotImageMimeType = "image/webp" | "image/png";
11
12
 
@@ -180,7 +181,15 @@
180
181
  const renderers = new Array<Renderer>();
181
182
  if (callRenderEvents) {
182
183
  getComponentsInChildren(context.scene, Renderer, renderers);
183
- renderers.forEach(r => r?.onBeforeRender());
184
+ renderers.forEach(r => {
185
+ r?.onBeforeRender();
186
+ if (r.isInstancingActive && r.instances) {
187
+ for (let i = 0; i < r.instances?.length; i++) {
188
+ const handle = r.instances[i];
189
+ setCustomVisibility(handle.object, true);
190
+ }
191
+ }
192
+ });
184
193
  }
185
194
 
186
195
  if (transparent) {