Needle Engine

Changes between version 2.67.13-pre and 3.0.1-alpha
Files changed (21) hide show
  1. plugins/generate-font.js +0 -65
  2. plugins/vite/index.js +2 -0
  3. src/engine/api.ts +3 -2
  4. src/engine/engine_addressables.ts +1 -1
  5. src/engine/engine_assetdatabase.ts +29 -18
  6. src/engine/engine_components_internal.ts +33 -20
  7. src/engine/engine_components.ts +3 -3
  8. src/engine/engine_constants.ts +2 -3
  9. src/engine/engine_context_registry.ts +7 -1
  10. src/engine/engine_context.ts +14 -17
  11. src/engine/engine_element.ts +1 -1
  12. src/engine/engine_gameobject.ts +5 -5
  13. src/engine/engine_gltf_builtin_components.ts +2 -2
  14. src/engine/engine_gltf.ts +2 -2
  15. src/engine/engine_networking_files.ts +5 -4
  16. src/engine/engine_patcher.ts +104 -47
  17. src/engine/engine_scenetools.ts +7 -7
  18. src/engine/engine_time.ts +2 -1
  19. src/engine/engine_types.ts +6 -0
  20. src/engine-components/ui/Text.ts +41 -17
  21. plugins/vite/alias.js +45 -0
plugins/generate-font.js DELETED
@@ -1,65 +0,0 @@
1
- import generateBMFont from 'msdf-bmfont-xml';
2
- import fs from 'fs';
3
- import path from 'path';
4
-
5
- const args = process.argv;
6
-
7
- const fontPath = args[2];
8
- const outputDir = args[3];
9
- const charsetPath = args.length > 4 ? args[4] : null;
10
-
11
- if (!fontPath || !fs.existsSync(fontPath)) {
12
- console.error("Missing font path. Please call this script with a path to a font file. Called with: \"" + fontPath + "\"");
13
- process.exit(1);
14
- }
15
- if (!outputDir || !fs.existsSync(outputDir)) {
16
- console.error("Missing output directory, please provide an output directory as the second argument. Called with: \"" + outputDir + "\"");
17
- process.exit(1);
18
- }
19
-
20
- // https://soimy.github.io/msdf-bmfont-xml/#module-usage-examples
21
-
22
-
23
- let message = "Generate font texture " + fontPath + " to " + outputDir;
24
-
25
- let charset = null;
26
- if (charsetPath && fs.existsSync(charsetPath)) {
27
- message += " using chars from \"" + charsetPath + "\"";
28
- charset = fs.readFileSync(charsetPath, 'utf8');
29
- console.log("charset: ", charset);
30
- if (charset.length <= 0) {
31
- console.warn("WARN: Charset file is empty, using default charset");
32
- charset = null;
33
- }
34
- }
35
-
36
- console.log(message);
37
- const opts = {
38
- outputType: "json",
39
- fieldType: "msdf",
40
- textureSize: [4096, 4096],
41
- smartSize: true, // shrink atlas to the smallest possible square
42
- // rtl: true, // use RTL(Arabic/Persian) charators fix
43
- };
44
- if (charset?.length)
45
- opts.charset = charset;
46
-
47
- generateBMFont(fontPath, opts,
48
- (error, textures, font) => {
49
- if (error) throw error;
50
- textures.forEach((texture, index) => {
51
- const fileName = path.parse(texture.filename).name.toLocaleLowerCase() + ".png";
52
- const outputPath = outputDir + "/" + fileName;
53
- console.log("Write to", outputPath);
54
- if (index > 0) console.log("WARN: Multiple font textures generated but they will override each other. You have currently " + charset?.length + " characters configured. Maybe too many?");
55
- fs.writeFile(outputPath, texture.texture, (err) => {
56
- if (err) throw err;
57
- });
58
- });
59
-
60
- const fileName = path.parse(font.filename).name;
61
- const name = outputDir + "/" + fileName.toLocaleLowerCase() + "-msdf.json";
62
- fs.writeFile(name, font.data, (err) => {
63
- if (err) throw err;
64
- });
65
- });
plugins/vite/index.js CHANGED
@@ -5,6 +5,7 @@
5
5
  import { needleDrop } from "./drop.js";
6
6
  import { editorConnection } from "./editor-connection.js";
7
7
  import { needleCopyFiles } from "./copyfiles.js";
8
+ import { needleViteAlias } from "./alias.js";
8
9
 
9
10
  export * from "./gzip.js";
10
11
  export * from "./config.js";
@@ -18,6 +19,7 @@
18
19
  // ensure we have user settings initialized with defaults
19
20
  userSettings = { ...defaultUserSettings, ...userSettings }
20
21
  const array = [
22
+ needleViteAlias(command, config, userSettings),
21
23
  needleMeta(command, config, userSettings),
22
24
  needlePoster(command),
23
25
  needleReload(command, config, userSettings),
src/engine/api.ts CHANGED
@@ -1,10 +1,11 @@
1
-
1
+ export * from "./engine_constants";
2
2
  export { TypeStore } from "./engine_typestore";
3
3
  export * from "./engine_context_registry";
4
4
  export * from "./extensions/extensions"
5
5
  export { InstancingUtil } from "./engine_instancing";
6
6
  export * from "./engine_gameobject";
7
- export * from "./engine_components"
7
+ export * from "./engine_components";
8
+ export * from "./engine_components_internal";
8
9
  export { AssetReference } from "./engine_addressables";
9
10
  export { Context, FrameEvent } from "./engine_setup";
10
11
  export * from "./debug/debug";
src/engine/engine_addressables.ts CHANGED
@@ -163,7 +163,7 @@
163
163
  }
164
164
  else {
165
165
  if (debug) console.log("Load async", this.uri);
166
- this._loading = getLoader().loadSync(context, this._hashedUri, null, true, prog => {
166
+ this._loading = getLoader().loadSync(context, this._hashedUri, this.uri, null, prog => {
167
167
  this.raiseProgressEvent(prog);
168
168
  });
169
169
  }
src/engine/engine_assetdatabase.ts CHANGED
@@ -278,29 +278,23 @@
278
278
 
279
279
  // We dont want to update users during rendering
280
280
 
281
- const $renderMethod = Symbol("render-method");
282
281
 
283
- Object.defineProperty(WebGLRenderer.prototype, "render", {
284
- set: function (this: WebGLRenderer, value: Function) {
285
- this[$renderMethod] = wrapMethod(value);
286
- },
287
- get: function (this: WebGLRenderer) {
288
- return this[$renderMethod];
289
- }
290
- });
282
+ try {
291
283
 
292
-
293
- function wrapMethod(fn: Function) {
294
- return function (this: WebGLRenderer, ...args) {
295
- noUpdateScope++;
296
- const result = fn.apply(this, args);
297
- noUpdateScope--;
298
- return result;
299
- }
284
+ // addPatch(WebGLRenderer.prototype, "render",
285
+ // () => {
286
+ // noUpdateScope++;
287
+ // },
288
+ // () => {
289
+ // noUpdateScope--;
290
+ // }
291
+ // );
300
292
  }
293
+ catch (e) {
294
+ console.warn("Could not wrap WebGLRenderer.render", e);
295
+ }
301
296
 
302
297
 
303
-
304
298
  // addGltfLoadEventListener(GltfLoadEventType.BeforeLoad, (_) => {
305
299
  // noUpdateScope++;
306
300
  // });
@@ -320,3 +314,20 @@
320
314
  // }
321
315
  // });
322
316
 
317
+
318
+
319
+
320
+
321
+ // class MyObject {
322
+ // myNumber: number = 1;
323
+ // }
324
+
325
+ // addPatch(MyObject.prototype, "myNumber", (obj, oldValue, newValue) => {
326
+ // console.log("myNumber changed", oldValue, newValue);
327
+ // });
328
+
329
+ // const i = new MyObject();
330
+ // setInterval(() => {
331
+ // console.log("RUN");
332
+ // i.myNumber += 1;
333
+ // }, 1000);
src/engine/engine_components_internal.ts CHANGED
@@ -1,30 +1,43 @@
1
1
  import { IComponent } from "./engine_types";
2
+ import { getParam } from "./engine_utils";
2
3
 
3
-
4
- const eventListeners = new Map<string, ((data: IComponent) => void)[]>();
5
-
6
4
  export enum ComponentEvents {
7
5
  Added = "component-added",
8
6
  Removing = "removing-component"
9
7
  }
10
8
 
11
- export function __internalAddComponentLifecycleEventListener(evt: string, cb: (data: IComponent) => void) {
12
- if (!eventListeners.has(evt)) eventListeners.set(evt, []);
13
- eventListeners.get(evt)?.push(cb);
14
- }
9
+ const debug = getParam("debugcomponentevents");
15
10
 
16
- export function __internalRemoveComponentLifecycleEventListener(evt: string, cb: (data: IComponent) => void) {
17
- const listeners = eventListeners.get(evt);
18
- if (!listeners) return;
19
- const index = listeners.indexOf(cb);
20
- if (index < 0) return;
21
- listeners.splice(index, 1);
22
- }
11
+ export class ComponentLifecycleEvents {
23
12
 
24
- export function __internalDispatchComponentLifecycleEvent(evt: string, data: IComponent) {
25
- const listeners = eventListeners.get(evt);
26
- if (!listeners) return;
27
- for (const listener of listeners) {
28
- listener(data);
13
+ private static eventListeners = new Map<string, ((data: IComponent) => void)[]>();
14
+
15
+ static addComponentLifecylceEventListener(evt: string, cb: (data: IComponent) => void) {
16
+ if (this.eventListeners.has(evt)) {
17
+ this.eventListeners.set(evt, []);
18
+ }
19
+ let arr = this.eventListeners.get(evt);
20
+ if (!arr) arr = [];
21
+ arr.push(cb);
22
+ this.eventListeners.set(evt, arr);
23
+ if(debug) console.log("Added event listener for " + evt, this.eventListeners)
29
24
  }
30
- }
25
+
26
+ static removeComponentLifecylceEventListener(evt: string, cb: (data: IComponent) => void) {
27
+ const listeners = this.eventListeners.get(evt);
28
+ if (!listeners) return;
29
+ const index = listeners.indexOf(cb);
30
+ if (index < 0) return;
31
+ listeners.splice(index, 1);
32
+
33
+ }
34
+
35
+ static dispatchComponentLifecycleEvent(evt: string, data: IComponent) {
36
+ const listeners = this.eventListeners.get(evt);
37
+ if(debug) console.log("Dispatching event " + evt, listeners)
38
+ if (!listeners) return;
39
+ for (const listener of listeners) {
40
+ listener(data);
41
+ }
42
+ }
43
+ }
src/engine/engine_components.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  import { activeInHierarchyFieldName } from "./engine_constants";
7
7
  import { apply } from "../engine-components/js-extensions/Object3D";
8
8
  import { InstantiateIdProvider } from "./engine_networking_instantiate";
9
- import { ComponentEvents, __internalDispatchComponentLifecycleEvent } from "./engine_components_internal";
9
+ import { ComponentEvents, ComponentLifecycleEvents } from "./engine_components_internal";
10
10
 
11
11
  const debug = getParam("debuggetcomponent");
12
12
 
@@ -30,7 +30,7 @@
30
30
  const index = go.userData.components.indexOf(componentInstance);
31
31
  if (index < 0) return;
32
32
 
33
- __internalDispatchComponentLifecycleEvent(ComponentEvents.Removing, componentInstance);
33
+ ComponentLifecycleEvents.dispatchComponentLifecycleEvent(ComponentEvents.Removing, componentInstance);
34
34
 
35
35
  //@ts-ignore
36
36
  componentInstance.gameObject = null;
@@ -66,7 +66,7 @@
66
66
  updateActiveInHierarchyWithoutEventCall(obj);
67
67
  componentInstance.__internalAwake();
68
68
  }
69
- __internalDispatchComponentLifecycleEvent(ComponentEvents.Added, componentInstance);
69
+ ComponentLifecycleEvents.dispatchComponentLifecycleEvent(ComponentEvents.Added, componentInstance);
70
70
  }
71
71
  catch (err) {
72
72
  console.error(err);
src/engine/engine_constants.ts CHANGED
@@ -1,6 +1,5 @@
1
1
 
2
2
  export const activeInHierarchyFieldName = Symbol("isActiveInHierarchy");
3
3
  export const builtinComponentKeyName = "builtin_components";
4
-
5
-
6
- export const $originalGuid = Symbol("originalGuid");
4
+ // It's easier to use a string than a symbol here because the symbol might not be the same when imported in other packages
5
+ export const editorGuidKeyName = "needle_editor_guid";
src/engine/engine_context_registry.ts CHANGED
@@ -14,8 +14,14 @@
14
14
  export type ContextCallback = (evt: ContextEventArgs) => void;
15
15
 
16
16
  export class ContextRegistry {
17
- static Current: IContext;
18
17
 
18
+ static get Current(): IContext{
19
+ return globalThis["NeedleEngine.Context.Current"]
20
+ }
21
+ static set Current(ctx: IContext) {
22
+ globalThis["NeedleEngine.Context.Current"] = ctx;
23
+ }
24
+
19
25
  static Registered: IContext[] = [];
20
26
 
21
27
  static register(ctx: IContext) {
src/engine/engine_context.ts CHANGED
@@ -87,15 +87,12 @@
87
87
 
88
88
  export class Context implements IContext {
89
89
 
90
- private static _current: Context;
91
-
92
90
  static get Current(): Context {
93
- return this._current;
91
+ return ContextRegistry.Current as Context;
94
92
  }
95
93
 
96
94
  static set Current(context: Context) {
97
95
  ContextRegistry.Current = context;
98
- this._current = context;
99
96
  }
100
97
 
101
98
  name: string;
@@ -516,10 +513,10 @@
516
513
  if (!this.isManagedExternally)
517
514
  this.domElement.prepend(this.renderer.domElement);
518
515
 
519
- Context._current = this;
516
+ Context.Current = this;
520
517
 
521
518
  // Setup
522
- Context._current = this;
519
+ Context.Current = this;
523
520
  for (let i = 0; i < this.new_scripts.length; i++) {
524
521
  const script = this.new_scripts[i];
525
522
  if (script.gameObject !== undefined && script.gameObject !== null) {
@@ -546,13 +543,13 @@
546
543
  // resolve post setup callbacks (things that rely on threejs objects having references to components)
547
544
  if (this.post_setup_callbacks) {
548
545
  for (let i = 0; i < this.post_setup_callbacks.length; i++) {
549
- Context._current = this;
546
+ Context.Current = this;
550
547
  await this.post_setup_callbacks[i](this);
551
548
  }
552
549
  }
553
550
 
554
551
  if (!this.mainCamera) {
555
- Context._current = this;
552
+ Context.Current = this;
556
553
  let camera: ICamera | null = null;
557
554
  foreachComponent(this.scene, comp => {
558
555
  const cam = comp as ICamera;
@@ -577,7 +574,7 @@
577
574
  }
578
575
  }
579
576
 
580
- Context._current = this;
577
+ Context.Current = this;
581
578
  looputils.processNewScripts(this);
582
579
 
583
580
  // const mainCam = this.mainCameraComponent as Camera;
@@ -624,10 +621,10 @@
624
621
 
625
622
  this._stats?.begin();
626
623
 
627
- Context._current = this;
624
+ Context.Current = this;
628
625
  if (this.onHandlePaused()) return;
629
626
 
630
- Context._current = this;
627
+ Context.Current = this;
631
628
  this.time.update();
632
629
  if (debugframerate)
633
630
  console.log("FPS", (this.time.smoothedFps).toFixed(0));
@@ -655,7 +652,7 @@
655
652
  const script = this.scripts_earlyUpdate[i];
656
653
  if (!script.activeAndEnabled) continue;
657
654
  if (script.earlyUpdate !== undefined) {
658
- Context._current = this;
655
+ Context.Current = this;
659
656
  script.earlyUpdate();
660
657
  }
661
658
  }
@@ -668,7 +665,7 @@
668
665
  const script = this.scripts_update[i];
669
666
  if (!script.activeAndEnabled) continue;
670
667
  if (script.update !== undefined) {
671
- Context._current = this;
668
+ Context.Current = this;
672
669
  script.update();
673
670
  }
674
671
  }
@@ -681,7 +678,7 @@
681
678
  const script = this.scripts_lateUpdate[i];
682
679
  if (!script.activeAndEnabled) continue;
683
680
  if (script.lateUpdate !== undefined) {
684
- Context._current = this;
681
+ Context.Current = this;
685
682
  script.lateUpdate();
686
683
  }
687
684
  }
@@ -712,7 +709,7 @@
712
709
  if (!script.activeAndEnabled) continue;
713
710
  // if(script.isActiveAndEnabled === false) continue;
714
711
  if (script.onBeforeRender !== undefined) {
715
- Context._current = this;
712
+ Context.Current = this;
716
713
  script.onBeforeRender(frame);
717
714
  }
718
715
  }
@@ -741,7 +738,7 @@
741
738
  const script = this.scripts_onAfterRender[i];
742
739
  if (!script.activeAndEnabled) continue;
743
740
  if (script.onAfterRender !== undefined) {
744
- Context._current = this;
741
+ Context.Current = this;
745
742
  script.onAfterRender();
746
743
  }
747
744
  }
@@ -798,7 +795,7 @@
798
795
  const script = this.scripts_pausedChanged[i];
799
796
  if (!script.activeAndEnabled) continue;
800
797
  if (script.onPausedChanged !== undefined) {
801
- Context._current = this;
798
+ Context.Current = this;
802
799
  script.onPausedChanged(paused, this._wasPaused);
803
800
  }
804
801
  }
src/engine/engine_element.ts CHANGED
@@ -240,7 +240,7 @@
240
240
  totalProgress01: this._loadingProgress01
241
241
  }
242
242
  }
243
- const res = await loader.loadSync(ctx, url, hash, false, prog => {
243
+ const res = await loader.loadSync(ctx, url, url, hash, prog => {
244
244
  // Calc progress
245
245
  progress.progress = prog;
246
246
  this._loadingProgress01 = calculateProgress01(progress);
src/engine/engine_gameobject.ts CHANGED
@@ -10,8 +10,8 @@
10
10
  import { activeInHierarchyFieldName } from "./engine_constants";
11
11
  import { assign } from "./engine_serialization_core";
12
12
  import { disposeObjectResources, __internalNotifyObjectDestroyed as __internalRemoveReferences } from "./engine_assetdatabase";
13
- import { $originalGuid } from "./engine_constants";
14
- import { __internalDispatchComponentLifecycleEvent, ComponentEvents } from "./engine_components_internal";
13
+ import { editorGuidKeyName } from "./engine_constants";
14
+ import { ComponentLifecycleEvents, ComponentEvents } from "./engine_components_internal";
15
15
 
16
16
  const debug = getParam("debuggetcomponent");
17
17
  const debugInstantiate = getParam("debuginstantiate");
@@ -373,13 +373,13 @@
373
373
  const copy = new comp.constructor();
374
374
  assign(copy, comp);
375
375
  // make sure the original guid stays intact
376
- if (comp[$originalGuid] !== undefined)
377
- copy[$originalGuid] = comp[$originalGuid];
376
+ if (comp[editorGuidKeyName] !== undefined)
377
+ copy[editorGuidKeyName] = comp[editorGuidKeyName];
378
378
  newComponents.push(copy);
379
379
  copy.gameObject = clone;
380
380
  // copy.transform = clone;
381
381
  componentsList.push(copy);
382
- __internalDispatchComponentLifecycleEvent(ComponentEvents.Added, copy);
382
+ ComponentLifecycleEvents.dispatchComponentLifecycleEvent(ComponentEvents.Added, copy);
383
383
  }
384
384
  }
385
385
 
src/engine/engine_gltf_builtin_components.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  import { assign, ImplementationInformation, ISerializable, SerializationContext } from "./engine_serialization_core";
9
9
  import { NEEDLE_components } from "./extensions/NEEDLE_components";
10
10
  import { debugExtension } from "./engine_default_parameters";
11
- import { $originalGuid, builtinComponentKeyName } from "./engine_constants";
11
+ import { editorGuidKeyName, builtinComponentKeyName } from "./engine_constants";
12
12
  import { GuidsMap, IComponent, IGameObject, SourceIdentifier } from "./engine_types";
13
13
  import { UIDProvider } from "./engine_types";
14
14
  import { addNewComponent } from "./engine_components";
@@ -174,7 +174,7 @@
174
174
 
175
175
  // assign the guid of the original instance
176
176
  if("guid" in compData)
177
- instance[$originalGuid] = compData.guid;
177
+ instance[editorGuidKeyName] = compData.guid;
178
178
 
179
179
  // Object.assign(instance, compData);
180
180
  // dont call awake here because some references might not be resolved yet and components that access those fields in awake will throw
src/engine/engine_gltf.ts CHANGED
@@ -8,8 +8,8 @@
8
8
  export interface INeedleGltfLoader {
9
9
  createBuiltinComponents(context: Context, gltfId: SourceIdentifier, gltf, seed: number | null | UIDProvider, extension?: NEEDLE_components): Promise<void>
10
10
  writeBuiltinComponentData(comp: object, context: SerializationContext);
11
- parseSync(context: Context, data, path: string, seed: number | UIDProvider | null): Promise<GLTF | undefined>;
12
- loadSync(context: Context, url: string, seed: number | UIDProvider | null, _allowAddingAnimator: boolean, prog?: (prog : ProgressEvent) => void): Promise<GLTF | undefined>
11
+ parseSync(context: Context, data : string | ArrayBuffer, path: string, seed: number | UIDProvider | null): Promise<GLTF | undefined>;
12
+ loadSync(context: Context, url: string, sourceId:string, seed: number | UIDProvider | null, prog?: (prog : ProgressEvent) => void): Promise<GLTF | undefined>
13
13
  }
14
14
 
15
15
  let gltfLoader: INeedleGltfLoader;
src/engine/engine_networking_files.ts CHANGED
@@ -48,13 +48,13 @@
48
48
  if (name.endsWith(".gltf") || name.endsWith(".glb")) {
49
49
  return new Promise((resolve, _reject) => {
50
50
  const reader = new FileReader()
51
- reader.readAsDataURL(file);
51
+ reader.readAsArrayBuffer(file);
52
52
  reader.onloadend = async (_ev: ProgressEvent<FileReader>) => {
53
- const content = reader.result as string;
53
+ const content = reader.result as ArrayBuffer;
54
54
  // first load it locally
55
55
  const seed = generateSeed();
56
56
  const prov = new InstantiateIdProvider(seed);
57
- const gltf: GLTF = await getLoader().loadSync(context, content, prov, true) as GLTF;
57
+ const gltf: GLTF = await getLoader().parseSync(context, content, file.name, prov) as GLTF;
58
58
  if (gltf && gltf.scene) {
59
59
  const obj = gltf.scene as unknown as IGameObject;
60
60
  // if we dont have a guid yet (because components guids are actually created in a callback a bit later)
@@ -85,7 +85,8 @@
85
85
  return new Promise(async (resolve, _reject) => {
86
86
  const seed = generateSeed();
87
87
  const prov = new InstantiateIdProvider(seed);
88
- const gltf: GLTF = await getLoader().loadSync(context, url.toString(), prov, true) as GLTF;
88
+ const urlStr = url.toString();
89
+ const gltf: GLTF = await getLoader().loadSync(context, urlStr, urlStr, prov) as GLTF;
89
90
  if (gltf && gltf.scene) {
90
91
  const obj = gltf.scene as unknown as IGameObject;
91
92
  // handleUpload(context.connection, file, seed, obj); // TODO needs to upload the URL only and store that
src/engine/engine_patcher.ts CHANGED
@@ -3,67 +3,102 @@
3
3
 
4
4
  const _wrappedMethods = new WeakSet();
5
5
 
6
- export declare type FieldPatch = (instance: object, oldValue: any, newValue: any) => any;
7
- export type MethodPatch<T> = (instance: T, result: any, ...args) => any;
8
6
 
7
+ // export function wrap<T>(prototype: object, methodName: string, before: (t: T) => void, after: (t: T) => void) {
8
+
9
+ // const $key = Symbol(methodName + "-patched");
10
+
11
+ // const alreadyDefined = Object.getOwnPropertyDescriptor(prototype, methodName);
12
+ // if (alreadyDefined) {
13
+ // const originalRender = alreadyDefined.get;
14
+ // if (originalRender) {
15
+ // // Object.defineProperty(prototype, "render", {
16
+ // // set: function (this: any, value: Function) {
17
+ // // originalRender.call(this);
18
+ // // },
19
+ // // get: function (this: ) {
20
+ // // return originalRender.call(this);
21
+ // // }
22
+ // // });
23
+ // }
24
+ // }
25
+ // else {
26
+ // Object.defineProperty(prototype, methodName, {
27
+ // set: function (this: any, value: Function) {
28
+ // this[$key] = value;
29
+ // },
30
+ // get: function (this: any) {
31
+ // return this[$key];
32
+ // }
33
+ // });
34
+ // }
35
+ // }
36
+
37
+
38
+ // export declare type FieldPatch = (instance: object, oldValue: any, newValue: any) => any;
39
+
40
+ export type Prefix = (...args) => any;
41
+ export type Postfix = (...args) => any;
42
+
9
43
  /**
10
44
  * Use patcher for patching properties insteadof calling Object.defineProperty individually
11
45
  * since this will cause conflicts if multiple patches need to be applied to the same property
12
46
  */
13
- export function addPatch<TType extends object, TCallback extends (FieldPatch | MethodPatch<any>)>(prototype: TType, fieldName: string, cb: TCallback) {
47
+ export function addPatch<T extends object>(prototype: T, fieldName: string, beforeCallback?: Prefix, afterCallback?: Postfix) {
14
48
 
49
+ // TODO
50
+ return;
51
+
15
52
  // TODO: we probably want to turn this into a symbol to prevent anyone from overriding it
16
53
  // But when we need to store the symbol per prototype to allow e.g. material disposing to iterate those and dispose all
17
- const backingField = fieldName + "__needle";// Symbol(fieldName);// + " (patched)";
54
+ const backingField = Symbol(fieldName + "__needle");// Symbol(fieldName);// + " (patched)";
18
55
 
19
- internalAddPatch(prototype, fieldName, cb);
56
+ internalAddPatch(prototype, fieldName, afterCallback, beforeCallback);
20
57
 
21
58
  const desc = Object.getOwnPropertyDescriptor(prototype, fieldName);
59
+
60
+ const existing = prototype[fieldName];
61
+ console.log(prototype);
22
62
 
23
63
  if (desc) {
24
- // TODO: check if the property is writable
25
- // the property might be a method in which case we want to wrap it
26
- if (typeof desc.value === "function") {
27
- const method = desc.value;
28
- if (method) {
29
- if (_wrappedMethods.has(method)) {
30
- return;
31
- }
32
- _wrappedMethods.add(method);
33
- prototype[fieldName] = function (this: object, ...args: any[]) {
34
- // call the original method
35
- const result = method.apply(this, args);
36
- // call the patches
37
- const patches = getPatches(prototype, fieldName);
38
- if (patches) {
39
- for (const patch of patches) {
40
- patch(this, result, ...args);
41
- }
42
- }
43
- return result;
44
- }
45
- }
46
- else {
47
- // TODO: declare method?
48
- }
49
- }
50
64
  }
51
- else if (prototype.hasOwnProperty(backingField)) {
52
- }
53
65
  else {
54
66
  Object.defineProperty(prototype, fieldName, {
55
67
  set: function (this: object, value: any) {
56
- const prev = this[backingField];
57
- this[backingField] = value;
58
- executePatches(prototype, fieldName, this, prev, value);
68
+ console.log("setting", fieldName, value);
69
+ if (typeof value === "function") {
70
+ this[backingField] = addWrapper(value, prototype, fieldName);
71
+ }
72
+ else {
73
+ const prev = this[backingField];
74
+ executePrefixes(prototype, fieldName, this, prev, value);
75
+ this[backingField] = value;
76
+ executePostFixes(prototype, fieldName, this, prev, value);
77
+ }
59
78
  },
60
79
  get: function (this: any) {
61
- return this[backingField];
80
+ console.log("GET", fieldName);
81
+ const value = this[backingField];
82
+ if (typeof value === "function") {
83
+ if (value[backingField]) {
84
+ return value[backingField];
85
+ }
86
+ }
87
+ return value;
62
88
  }
63
89
  });
64
90
  }
65
91
  }
66
92
 
93
+ function addWrapper(originalFunction: Function, prototype, fieldname) {
94
+ return function (this: object, ...args: any[]) {
95
+ executePrefixes(prototype, fieldname, this, ...args);
96
+ const result = originalFunction.apply(this, args);
97
+ executePostFixes(prototype, fieldname, this, result, ...args);
98
+ return result;
99
+ }
100
+ }
101
+
67
102
  export function removePatch(prototype: object, fieldName: string, cb: Function) {
68
103
  const patches = getPatches(prototype, fieldName);
69
104
  if (patches) {
@@ -76,38 +111,60 @@
76
111
  }
77
112
 
78
113
 
114
+ export const NeedlePatchesKey = "Needle:Patches";
79
115
 
116
+ declare type PatchInfo = {
117
+ prefix?: Prefix;
118
+ postfix?: Postfix;
119
+ }
120
+ function patches(): WeakMap<object, Map<string, PatchInfo[]>> {
121
+ if (!globalThis[NeedlePatchesKey]) {
122
+ globalThis[NeedlePatchesKey] = new WeakMap<object, Map<string, PatchInfo[]>>();
123
+ }
124
+ return globalThis[NeedlePatchesKey];
125
+ }
80
126
 
81
- const patches = new WeakMap<object, Map<string, Function[]>>();
82
-
83
127
  function getPatches(prototype, fieldName: string) {
84
- let patchesMap = patches.get(prototype);
128
+ let patchesMap = patches().get(prototype);
85
129
  if (!patchesMap) {
86
130
  return null;
87
131
  }
88
132
  return patchesMap.get(fieldName);;
89
133
  }
90
134
 
91
- function internalAddPatch(prototype, fieldName: string, cb: Function) {
92
- let patchesMap = patches.get(prototype);
135
+ function internalAddPatch(prototype, fieldName: string, postfix?: Postfix, prefix?: Prefix) {
136
+ let patchesMap = patches().get(prototype);
93
137
  if (!patchesMap) {
94
138
  patchesMap = new Map();
95
- patches.set(prototype, patchesMap);
139
+ patches().set(prototype, patchesMap);
96
140
  }
97
141
  let patchList = patchesMap.get(fieldName);
98
142
  if (!patchList) {
99
143
  patchList = [];
100
144
  patchesMap.set(fieldName, patchList);
101
145
  }
102
- patchList.push(cb);
146
+ patchList.push({
147
+ prefix: prefix,
148
+ postfix: postfix
149
+ });
103
150
  }
104
151
 
105
- function executePatches(prototype, fieldName: string, instance: object, oldValue: any, newValue: any) {
152
+ function executePrefixes(prototype, fieldName: string, instance: object, ...args) {
106
153
  if (!instance) return;
107
154
  const patches = getPatches(prototype, fieldName);
108
155
  if (patches) {
109
- for (const patch of patches) {
110
- patch(instance, oldValue, newValue);
156
+ for (const patchInfo of patches) {
157
+ patchInfo.prefix?.call(instance, ...args);
111
158
  }
112
159
  }
160
+ }
161
+
162
+ function executePostFixes(prototype, fieldName: string, instance: object, result: any, ...args) {
163
+ if (!instance) return;
164
+ const patches = getPatches(prototype, fieldName);
165
+ if (patches) {
166
+ for (const patchInfo of patches) {
167
+ patchInfo.postfix?.call(instance, result, ...args);
168
+ }
169
+ }
113
170
  }
src/engine/engine_scenetools.ts CHANGED
@@ -24,11 +24,11 @@
24
24
  return writeBuiltinComponentData(comp, context);
25
25
  }
26
26
 
27
- parseSync(context: Context, data, path: string, seed: number | UIDProvider | null): Promise<GLTF | undefined> {
27
+ parseSync(context: Context, data : string | ArrayBuffer, path: string, seed: number | UIDProvider | null): Promise<GLTF | undefined> {
28
28
  return parseSync(context, data, path, seed);
29
29
  }
30
- loadSync(context: Context, url: string, seed: number | UIDProvider | null, _allowAddingAnimator: boolean, prog?: ((ProgressEvent: any) => void) | undefined): Promise<GLTF | undefined> {
31
- return loadSync(context, url, seed, _allowAddingAnimator, prog);
30
+ loadSync(context: Context, url: string, sourceId:string, seed: number | UIDProvider | null, prog?: ((ProgressEvent: any) => void) | undefined): Promise<GLTF | undefined> {
31
+ return loadSync(context, url, sourceId, seed, prog);
32
32
  }
33
33
  }
34
34
 
@@ -87,7 +87,7 @@
87
87
 
88
88
  async function handleLoadedGltf(context: Context, gltfId: string, gltf, seed: number | null | UIDProvider, componentsExtension) {
89
89
  if (printGltf)
90
- console.log(gltf);
90
+ console.warn("glTF", gltfId, gltf);
91
91
  await getLoader().createBuiltinComponents(context, gltfId, gltf, seed, componentsExtension);
92
92
 
93
93
  // load and assign animation
@@ -102,7 +102,7 @@
102
102
  return loader;
103
103
  }
104
104
 
105
- export function parseSync(context: Context, data, path: string, seed: number | UIDProvider | null): Promise<GLTF | undefined> {
105
+ export function parseSync(context: Context, data : string | ArrayBuffer, path: string, seed: number | UIDProvider | null): Promise<GLTF | undefined> {
106
106
  if (typeof path !== "string") {
107
107
  console.warn("Parse gltf binary without path, this might lead to errors in resolving extensions. Please provide the source path of the gltf/glb file", path, typeof path);
108
108
  }
@@ -131,7 +131,7 @@
131
131
  });
132
132
  }
133
133
 
134
- export function loadSync(context: Context, url: string, seed: number | UIDProvider | null, _allowAddingAnimator: boolean = false, prog?: (ProgressEvent) => void): Promise<GLTF | undefined> {
134
+ export function loadSync(context: Context, url: string, sourceId:string, seed: number | UIDProvider | null, prog?: (ProgressEvent) => void): Promise<GLTF | undefined> {
135
135
  // better to create new loaders every time
136
136
  // (maybe we can cache them...)
137
137
  // but due to the async nature and potentially triggering multiple loads at the same time
@@ -145,7 +145,7 @@
145
145
  invokeEvents(GltfLoadEventType.BeforeLoad, new GltfLoadEvent(context, url, loader));
146
146
  loader.load(url, async data => {
147
147
  invokeEvents(GltfLoadEventType.AfterLoaded, new GltfLoadEvent(context, url, loader, data));
148
- await handleLoadedGltf(context, url, data, seed, componentsExtension);
148
+ await handleLoadedGltf(context, sourceId, data, seed, componentsExtension);
149
149
  invokeEvents(GltfLoadEventType.FinishedSetup, new GltfLoadEvent(context, url, loader, data));
150
150
  registerPrewarmObject(data.scene, context);
151
151
  resolve(data);
src/engine/engine_time.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { Clock } from 'three'
2
2
  import { getParam } from './engine_utils';
3
+ import { ITime } from './engine_types';
3
4
 
4
5
  const timescaleUrl = getParam("timescale");
5
6
  let timeScale = 1;
6
7
  if (typeof timescaleUrl === "number") timeScale = timescaleUrl;
7
8
 
8
- export class Time {
9
+ export class Time implements ITime {
9
10
 
10
11
  deltaTime = 0;
11
12
  time = 0;
src/engine/engine_types.ts CHANGED
@@ -27,9 +27,13 @@
27
27
  chained?: Array<Generator>
28
28
  }
29
29
 
30
+ export interface ITime {
31
+ get time(): number;
32
+ }
30
33
 
31
34
  export interface IContext {
32
35
  alias?: string | null;
36
+ hash?:string;
33
37
 
34
38
  scene: Scene;
35
39
  renderer: WebGLRenderer;
@@ -37,6 +41,8 @@
37
41
  mainCameraComponent: ICamera | undefined;
38
42
  domElement: HTMLElement;
39
43
 
44
+ time: ITime;
45
+
40
46
  scripts: IComponent[];
41
47
  scripts_pausedChanged: IComponent[];
42
48
  // scripts with update event
src/engine-components/ui/Text.ts CHANGED
@@ -466,7 +466,7 @@
466
466
  }
467
467
 
468
468
  private setFont(opts: any, fontStyle: FontStyle) {
469
- const name = this.getFontName(fontStyle);
469
+ const name = this.getFontStyleName(fontStyle);
470
470
  let family = name;
471
471
  if (!family?.endsWith("-msdf.json")) family += "-msdf.json";
472
472
  opts.fontFamily = family;
@@ -476,23 +476,47 @@
476
476
  opts.fontTexture = texture;
477
477
  }
478
478
 
479
- private getFontName(_fontStyle: FontStyle): string | null {
479
+ private getFontStyleName(style: FontStyle): string | null {
480
480
  if (!this.font) return null;
481
- // switch (fontStyle) {
482
- // case FontStyle.Normal:
483
- // return this.font;
484
- // case FontStyle.Bold:
485
- // if (!this.font.includes("-bold"))
486
- // return this.font + "-bold";
487
- // case FontStyle.Italic:
488
- // if (!this.font.includes("-italic"))
489
- // return this.font + "-italic";
490
- // case FontStyle.BoldAndItalic:
491
- // if (!this.font.includes("-bold-italic"))
492
- // return this.font + "-bold-italic";
493
- // }
494
- this.font = getPath(this.sourceId, this.font);
495
- return this.font;
481
+ let fontName = this.font;
482
+
483
+ // if a font path has a known suffix we remove it
484
+ if (fontName.endsWith("-regular")) {
485
+ if (style === FontStyle.Normal) return getPath(this.sourceId, fontName);
486
+ fontName = fontName.substring(0, fontName.length - "-regular".length);
487
+ }
488
+ else if (fontName.endsWith("-bold")) {
489
+ if (style === FontStyle.Bold)return getPath(this.sourceId, fontName);
490
+ fontName = fontName.substring(0, fontName.length - "-bold".length);
491
+ }
492
+ else if (fontName.endsWith("-italic")) {
493
+ if (style === FontStyle.Italic)return getPath(this.sourceId, fontName);
494
+ fontName = fontName.substring(0, fontName.length - "-italic".length);
495
+ }
496
+ else if (fontName.endsWith("-bolditalic")) {
497
+ if (style === FontStyle.BoldAndItalic)return getPath(this.sourceId, fontName);
498
+ fontName = fontName.substring(0, fontName.length - "-bolditalic".length);
499
+ }
500
+ else
501
+ // If a font does not have a specific style suffic we dont support getting the correct font style
502
+ return getPath(this.sourceId, fontName);
503
+
504
+ switch (style) {
505
+ case FontStyle.Normal:
506
+ fontName += "-regular";
507
+ break;
508
+ case FontStyle.Bold:
509
+ fontName += "-bold";
510
+ break;
511
+ case FontStyle.Italic:
512
+ fontName += "-italic";
513
+ break;
514
+ case FontStyle.BoldAndItalic:
515
+ fontName += "-bolditalic";
516
+ break;
517
+ }
518
+
519
+ return getPath(this.sourceId, fontName);
496
520
  }
497
521
  }
498
522
 
plugins/vite/alias.js ADDED
@@ -0,0 +1,45 @@
1
+ import { existsSync } from 'fs';
2
+ import path from 'path';
3
+
4
+ const projectDir = process.cwd() + "/";
5
+
6
+ const packages_to_resolve = {
7
+ 'three': {},
8
+ '@needle-tools/engine': {},
9
+ 'peerjs': {},
10
+ 'websocket-ts': {},
11
+ 'md5': {},
12
+ }
13
+
14
+ export const needleViteAlias = (command, config, userSettings) => {
15
+
16
+ if (config?.noAlias === true || userSettings?.noAlias === true)
17
+ return;
18
+
19
+ return {
20
+ name: "needle-alias",
21
+ config(config) {
22
+ setTimeout(() => {
23
+ console.log('[needle-alias] ProjectDirectory: ' + projectDir);
24
+ }, 150);
25
+
26
+ if (!config.resolve) config.resolve = {};
27
+ if (!config.resolve.alias) config.resolve.alias = {};
28
+ const aliasDict = config.resolve.alias;
29
+ for (const name in packages_to_resolve) {
30
+ if (!aliasDict[name]) {
31
+ addPathResolver(name, aliasDict);
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ function addPathResolver(name, aliasList) {
38
+ // If a package at the node_modules path exist we resolve the request there
39
+ // introduced in 89a50718c38940abb99ee16c5e029065e41d7d65
40
+ const res = path.resolve(projectDir, 'node_modules', name);
41
+ if (existsSync(res)) {
42
+ aliasList[name] = () => res;
43
+ }
44
+ }
45
+ };