@needle-tools/engine 3.0.0-alpha → 3.0.1-alpha

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 (75) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/dist/needle-engine.js +9552 -9570
  3. package/dist/needle-engine.min.js +4812 -0
  4. package/dist/needle-engine.umd.cjs +279 -279
  5. package/lib/engine/engine_addressables.js +1 -1
  6. package/lib/engine/engine_addressables.js.map +1 -1
  7. package/lib/engine/engine_assetdatabase.js +24 -17
  8. package/lib/engine/engine_assetdatabase.js.map +1 -1
  9. package/lib/engine/engine_context.d.ts +0 -1
  10. package/lib/engine/engine_context.js +15 -17
  11. package/lib/engine/engine_context.js.map +1 -1
  12. package/lib/engine/engine_context_registry.d.ts +2 -1
  13. package/lib/engine/engine_context_registry.js +6 -1
  14. package/lib/engine/engine_context_registry.js.map +1 -1
  15. package/lib/engine/engine_element.js +1 -1
  16. package/lib/engine/engine_element.js.map +1 -1
  17. package/lib/engine/engine_gltf.d.ts +2 -2
  18. package/lib/engine/engine_mainloop_utils.js +13 -11
  19. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  20. package/lib/engine/engine_networking_files.js +4 -3
  21. package/lib/engine/engine_networking_files.js.map +1 -1
  22. package/lib/engine/engine_patcher.d.ts +4 -3
  23. package/lib/engine/engine_patcher.js +61 -44
  24. package/lib/engine/engine_patcher.js.map +1 -1
  25. package/lib/engine/engine_scenetools.d.ts +4 -4
  26. package/lib/engine/engine_scenetools.js +5 -5
  27. package/lib/engine/engine_scenetools.js.map +1 -1
  28. package/lib/engine/engine_time.d.ts +2 -1
  29. package/lib/engine/engine_time.js.map +1 -1
  30. package/lib/engine/engine_types.d.ts +5 -0
  31. package/lib/engine/engine_types.js.map +1 -1
  32. package/lib/engine-components/Animation.d.ts +1 -1
  33. package/lib/engine-components/Animation.js +3 -3
  34. package/lib/engine-components/Animation.js.map +1 -1
  35. package/lib/engine-components/Renderer.d.ts +1 -1
  36. package/lib/engine-components/Renderer.js +15 -4
  37. package/lib/engine-components/Renderer.js.map +1 -1
  38. package/lib/engine-components/api.d.ts +1 -0
  39. package/lib/engine-components/api.js +2 -0
  40. package/lib/engine-components/api.js.map +1 -0
  41. package/lib/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusion.d.ts +2 -0
  42. package/lib/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusion.js +18 -1
  43. package/lib/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusion.js.map +1 -1
  44. package/lib/engine-components/postprocessing/PostProcessingHandler.d.ts +3 -0
  45. package/lib/engine-components/postprocessing/PostProcessingHandler.js +1 -1
  46. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  47. package/lib/engine-components/ui/Text.d.ts +1 -1
  48. package/lib/engine-components/ui/Text.js +42 -17
  49. package/lib/engine-components/ui/Text.js.map +1 -1
  50. package/lib/needle-engine.d.ts +1 -0
  51. package/lib/needle-engine.js +1 -0
  52. package/lib/needle-engine.js.map +1 -1
  53. package/lib/tsconfig.tsbuildinfo +1 -1
  54. package/package.json +3 -4
  55. package/plugins/vite/copyfiles.js +37 -10
  56. package/plugins/vite/reload.js +2 -1
  57. package/src/engine/engine_addressables.ts +1 -1
  58. package/src/engine/engine_assetdatabase.ts +29 -18
  59. package/src/engine/engine_context.ts +15 -18
  60. package/src/engine/engine_context_registry.ts +7 -1
  61. package/src/engine/engine_element.ts +1 -1
  62. package/src/engine/engine_gltf.ts +2 -2
  63. package/src/engine/engine_mainloop_utils.ts +12 -10
  64. package/src/engine/engine_networking_files.ts +5 -4
  65. package/src/engine/engine_patcher.ts +104 -47
  66. package/src/engine/engine_scenetools.ts +7 -7
  67. package/src/engine/engine_time.ts +2 -1
  68. package/src/engine/engine_types.ts +6 -0
  69. package/src/engine-components/Animation.ts +3 -3
  70. package/src/engine-components/Renderer.ts +19 -5
  71. package/src/engine-components/api.ts +1 -0
  72. package/src/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusion.ts +15 -1
  73. package/src/engine-components/postprocessing/PostProcessingHandler.ts +1 -1
  74. package/src/engine-components/ui/Text.ts +41 -17
  75. package/src/needle-engine.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "3.0.0-alpha",
3
+ "version": "3.0.1-alpha",
4
4
  "description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally.",
5
5
  "main": "dist/needle-engine.umd.cjs",
6
6
  "module": "lib/needle-engine.js",
@@ -57,8 +57,8 @@
57
57
  "flatbuffers": "2.0.4",
58
58
  "md5": "^2.3.0",
59
59
  "peerjs": "1.3.2",
60
- "simplex-noise": "^4.0.1",
61
60
  "postprocessing": "^6.30.1",
61
+ "simplex-noise": "^4.0.1",
62
62
  "stats.js": "^0.17.0",
63
63
  "three": "npm:@needle-tools/three@^0.146.6",
64
64
  "three-mesh-ui": "^6.4.5",
@@ -72,10 +72,9 @@
72
72
  "@needle-tools/helper": "^0.4.5",
73
73
  "@needle-tools/needle-component-compiler": "1.9.3",
74
74
  "@types/three": "0.146.0",
75
- "copy-files-from-to": "^3.7.0",
76
75
  "esbuild": "^0.15.10",
77
76
  "esbuild-node-externals": "^1.5.0",
78
- "fs-extra": "^11.1.0",
77
+ "fs-extra": "^11.1.1",
79
78
  "jsdoc-babel": "^0.5.0",
80
79
  "jsdoc-to-markdown": "^7.1.1",
81
80
  "madge": "^5.0.1",
@@ -1,7 +1,6 @@
1
1
 
2
- import { copy } from 'fs-extra'
3
- import { resolve } from 'path'
4
- import { existsSync } from 'fs';
2
+ import { resolve, join } from 'path'
3
+ import { existsSync, statSync, mkdirSync, readdirSync, copyFileSync, mkdir } from 'fs';
5
4
 
6
5
 
7
6
  /** copy files on build from assets to dist */
@@ -11,32 +10,60 @@ export const needleCopyFiles = (command, config, userSettings) => {
11
10
  return;
12
11
  }
13
12
 
13
+ const copyIncludesFromEngine = config?.copyIncludesFromEngine ?? true;
14
+
14
15
  return {
15
16
  name: 'needle-copy-files',
16
17
  apply: 'build',
17
18
  async closeBundle() {
18
19
  const baseDir = process.cwd();
20
+ const pluginName = "needle-copy-files";
19
21
 
20
22
  const outdirName = "dist";
21
23
  const outDir = resolve(baseDir, outdirName);
22
24
  if (!existsSync(outDir)) {
23
- console.log(`No ${outdirName} folder found, skipping copy files: ${outDir}`);
24
- return;
25
+ mkdirSync(outDir);
26
+ }
27
+
28
+ if (copyIncludesFromEngine !== false) {
29
+ // copy include from engine
30
+ const engineIncludeDir = resolve(baseDir, 'node_modules', '@needle-tools', 'engine', 'src', 'include');
31
+ if (existsSync(engineIncludeDir)) {
32
+ console.log(`[${pluginName}] - Copy engine include to ${baseDir}/include`)
33
+ const targetDir = resolve(baseDir, 'include');
34
+ copyRecursiveSync(engineIncludeDir, targetDir);
35
+ }
25
36
  }
37
+
26
38
  // copy assets dir
27
39
  const assetsDir = resolve(baseDir, 'assets');
28
40
  if (existsSync(assetsDir)) {
29
- console.log(`Copy assets to ${outdirName}/assets`)
41
+ console.log(`[${pluginName}] - Copy assets to ${outdirName}/assets`)
30
42
  const targetDir = resolve(outDir, 'assets');
31
- await copy(assetsDir, targetDir, { overwrite: true });
43
+ copyRecursiveSync(assetsDir, targetDir);
32
44
  }
33
45
  // copy include dir
34
46
  const includeDir = resolve(baseDir, 'include');
35
47
  if (existsSync(includeDir)) {
36
- console.log(`Copy include to ${outdirName}/include`)
48
+ console.log(`[${pluginName}] - Copy include to ${outdirName}/include`)
37
49
  const targetDir = resolve(outDir, 'include');
38
- await copy(includeDir, targetDir, { overwrite: true });
50
+ copyRecursiveSync(includeDir, targetDir);
39
51
  }
40
52
  }
41
53
  }
42
- }
54
+ }
55
+
56
+ function copyRecursiveSync(src, dest) {
57
+ var exists = existsSync(src);
58
+ var stats = exists && statSync(src);
59
+ var isDirectory = exists && stats.isDirectory();
60
+ if (isDirectory) {
61
+ if (!existsSync(dest))
62
+ mkdirSync(dest);
63
+ readdirSync(src).forEach(function (childItemName) {
64
+ copyRecursiveSync(join(src, childItemName), join(dest, childItemName));
65
+ });
66
+ } else {
67
+ copyFileSync(src, dest);
68
+ }
69
+ };
@@ -43,7 +43,8 @@ export const needleReload = (command, config, userSettings) => {
43
43
  else if (!config.server.watch.ignored) config.server.watch.ignored = [];
44
44
  for (const pattern of ignorePatterns)
45
45
  config.server.watch.ignored.push(pattern);
46
- setTimeout(() => console.log("Updated server ignore patterns: ", config.server.watch.ignored), 100);
46
+ if(config?.debug === true || userSettings?.debug === true)
47
+ setTimeout(() => console.log("Updated server ignore patterns: ", config.server.watch.ignored), 100);
47
48
  },
48
49
  handleHotUpdate(args) {
49
50
  args.buildDirectory = buildDirectory;
@@ -163,7 +163,7 @@ export class AssetReference {
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
  }
@@ -278,27 +278,21 @@ function updateUsers(symbol: symbol, user: object, object: object | object[], ad
278
278
 
279
279
  // We dont want to update users during rendering
280
280
 
281
- const $renderMethod = Symbol("render-method");
282
-
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
- });
291
281
 
282
+ try {
292
283
 
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
+ // );
292
+ }
293
+ catch (e) {
294
+ console.warn("Could not wrap WebGLRenderer.render", e);
300
295
  }
301
-
302
296
 
303
297
 
304
298
  // addGltfLoadEventListener(GltfLoadEventType.BeforeLoad, (_) => {
@@ -320,3 +314,20 @@ function wrapMethod(fn: Function) {
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);
@@ -87,15 +87,12 @@ export function registerComponent(script: IComponent, context?: Context) {
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;
@@ -307,7 +304,7 @@ export class Context implements IContext {
307
304
  // private _requestSizeUpdate : boolean = false;
308
305
 
309
306
  updateSize() {
310
- if (!this.isManagedExternally && !this.renderer.xr.isPresenting) {
307
+ if (!this.isManagedExternally && this.renderer.xr?.isPresenting === false) {
311
308
  this._sizeChanged = false;
312
309
  const scaleFactor = this.resolutionScaleFactor;
313
310
  const width = this.domWidth * scaleFactor;
@@ -516,10 +513,10 @@ export class Context implements IContext {
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 @@ export class Context implements IContext {
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 @@ export class Context implements IContext {
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 @@ export class Context implements IContext {
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 @@ export class Context implements IContext {
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 @@ export class Context implements IContext {
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 @@ export class Context implements IContext {
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 @@ export class Context implements IContext {
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 @@ export class Context implements IContext {
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 @@ export class Context implements IContext {
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
  }
@@ -14,7 +14,13 @@ export type ContextEventArgs = {
14
14
  export type ContextCallback = (evt: ContextEventArgs) => void;
15
15
 
16
16
  export class ContextRegistry {
17
- static Current: IContext;
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
+ }
18
24
 
19
25
  static Registered: IContext[] = [];
20
26
 
@@ -240,7 +240,7 @@ export class EngineElement extends HTMLElement implements INeedleEngineComponent
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);
@@ -8,8 +8,8 @@ import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'
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;
@@ -370,17 +370,19 @@ export function runPrewarm(context: IContext) {
370
370
  if (cam) {
371
371
  if (debugPrewarm) console.log("prewarm", list.length, "objects", [...list]);
372
372
  const renderer = context.renderer;
373
- const scene = context.scene;
374
- renderer.compile(scene, cam!)
375
- prewarmTarget ??= new WebGLCubeRenderTarget(64)
376
- prewarmCamera ??= new CubeCamera(0.001, 9999999, prewarmTarget);
377
- prewarmCamera.update(renderer, scene);
378
- for (const obj of list) {
379
- obj[$prewarmedFlag] = true;
380
- obj[$waitingForPrewarm] = false;
373
+ if (renderer.compile) {
374
+ const scene = context.scene;
375
+ renderer.compile(scene, cam!)
376
+ prewarmTarget ??= new WebGLCubeRenderTarget(64)
377
+ prewarmCamera ??= new CubeCamera(0.001, 9999999, prewarmTarget);
378
+ prewarmCamera.update(renderer, scene);
379
+ for (const obj of list) {
380
+ obj[$prewarmedFlag] = true;
381
+ obj[$waitingForPrewarm] = false;
382
+ }
383
+ list.length = 0;
384
+ if (debugPrewarm) console.log("prewarm done");
381
385
  }
382
- list.length = 0;
383
- if (debugPrewarm) console.log("prewarm done");
384
386
  }
385
387
  }
386
388
 
@@ -48,13 +48,13 @@ export async function addFile(file: File, context: Context, backendUrl?: string)
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 @@ export async function addFileFromUrl(url: URL, context: Context): Promise<GLTF |
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
@@ -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;
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;
8
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) {
48
+
49
+ // TODO
50
+ return;
14
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
- }
51
- else if (prototype.hasOwnProperty(backingField)) {
52
64
  }
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 @@ export function removePatch(prototype: object, fieldName: string, cb: Function)
76
111
  }
77
112
 
78
113
 
114
+ export const NeedlePatchesKey = "Needle:Patches";
79
115
 
80
-
81
- const patches = new WeakMap<object, Map<string, Function[]>>();
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
+ }
82
126
 
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
+ });
150
+ }
151
+
152
+ function executePrefixes(prototype, fieldName: string, instance: object, ...args) {
153
+ if (!instance) return;
154
+ const patches = getPatches(prototype, fieldName);
155
+ if (patches) {
156
+ for (const patchInfo of patches) {
157
+ patchInfo.prefix?.call(instance, ...args);
158
+ }
159
+ }
103
160
  }
104
161
 
105
- function executePatches(prototype, fieldName: string, instance: object, oldValue: any, newValue: any) {
162
+ function executePostFixes(prototype, fieldName: string, instance: object, result: any, ...args) {
106
163
  if (!instance) return;
107
164
  const patches = getPatches(prototype, fieldName);
108
165
  if (patches) {
109
- for (const patch of patches) {
110
- patch(instance, oldValue, newValue);
166
+ for (const patchInfo of patches) {
167
+ patchInfo.postfix?.call(instance, result, ...args);
111
168
  }
112
169
  }
113
170
  }