@thi.ng/wasm-api 1.6.7 → 2.0.1

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 (99) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +118 -60
  3. package/api.d.ts +41 -13
  4. package/bridge.d.ts +16 -2
  5. package/bridge.js +40 -13
  6. package/index.d.ts +1 -0
  7. package/index.js +1 -0
  8. package/memory.d.ts +14 -0
  9. package/memory.js +40 -0
  10. package/object-index.d.ts +22 -3
  11. package/object-index.js +20 -3
  12. package/package.json +16 -13
  13. package/zig/.zig-cache/z/53d8da468c12563c8ba398391b5e75e7 +0 -0
  14. package/zig/.zig-cache/tmp/9b11f06a25b08ec8/build +0 -0
  15. package/zig/.zig-cache/tmp/9e6b7db30c645e00/build +0 -0
  16. package/zig/.zig-cache/tmp/a38e6b0ab4712e40/build +0 -0
  17. package/zig/.zig-cache/tmp/b13ac822bd6aa967/build +0 -0
  18. package/zig/.zig-cache/tmp/e23560c26cbc775d/build +0 -0
  19. package/zig/.zig-cache/tmp/f908b8bbbbb51e24/build +0 -0
  20. package/zig/zig-cache/h/bc052b73addda9fe899d4556c8f21a5c.txt +0 -0
  21. package/zig/zig-cache/h/timestamp +0 -0
  22. package/zig/zig-cache/o/20f8f8362ac39302905d03992fa818bc/dependencies.zig +0 -2
  23. package/zig/zig-cache/tmp/0696373596682bd9/build +0 -0
  24. package/zig/zig-cache/tmp/06fd7da717810606/build +0 -0
  25. package/zig/zig-cache/tmp/0a39f7dbdddbea21/build +0 -0
  26. package/zig/zig-cache/tmp/0f72c6fd825322f3/build +0 -0
  27. package/zig/zig-cache/tmp/119286fc10b777f9/build +0 -0
  28. package/zig/zig-cache/tmp/1891fd08324fa403/build +0 -0
  29. package/zig/zig-cache/tmp/1eb500fac3d5ce62/build +0 -0
  30. package/zig/zig-cache/tmp/2043d7dd4743109c/build +0 -0
  31. package/zig/zig-cache/tmp/224ea1ba9258bfe3/build +0 -0
  32. package/zig/zig-cache/tmp/25343e986edbd1ad/build +0 -0
  33. package/zig/zig-cache/tmp/2d366ba992d14d5f/build +0 -0
  34. package/zig/zig-cache/tmp/2ddcd2b8d14b81eb/build +0 -0
  35. package/zig/zig-cache/tmp/2dfe9a721aef8bbd/build +0 -0
  36. package/zig/zig-cache/tmp/306b574d8ec701bf/build +0 -0
  37. package/zig/zig-cache/tmp/3411e025a5f335c7/build +0 -0
  38. package/zig/zig-cache/tmp/3b7519b048bc31f6/build +0 -0
  39. package/zig/zig-cache/tmp/4b5e6a399cb5aee9/build +0 -0
  40. package/zig/zig-cache/tmp/4fb96aef1e2a7ae7/build +0 -0
  41. package/zig/zig-cache/tmp/527925617da9c3dd/build +0 -0
  42. package/zig/zig-cache/tmp/56dbc6d60b6441e9/build +0 -0
  43. package/zig/zig-cache/tmp/57954e69e2d9522f/build +0 -0
  44. package/zig/zig-cache/tmp/57cb18f507029d64/build +0 -0
  45. package/zig/zig-cache/tmp/5d8fe28f3373c9ca/build +0 -0
  46. package/zig/zig-cache/tmp/65c1c50c9b1018bd/build +0 -0
  47. package/zig/zig-cache/tmp/6977aca430f59bc9/build +0 -0
  48. package/zig/zig-cache/tmp/6c0722b71c685402/build +0 -0
  49. package/zig/zig-cache/tmp/6c4ec579c09d1f80/build +0 -0
  50. package/zig/zig-cache/tmp/6eae82521f2631ac/build +0 -0
  51. package/zig/zig-cache/tmp/6f62ac363f21537f/build +0 -0
  52. package/zig/zig-cache/tmp/7556d654f70c4ae9/build +0 -0
  53. package/zig/zig-cache/tmp/7ae3fef48d8f3ada/build +0 -0
  54. package/zig/zig-cache/tmp/7fd336b7694ba54b/build +0 -0
  55. package/zig/zig-cache/tmp/899150dbae119836/build +0 -0
  56. package/zig/zig-cache/tmp/913281630cad1c0a/build +0 -0
  57. package/zig/zig-cache/tmp/927255f410775c17/build +0 -0
  58. package/zig/zig-cache/tmp/956b7aafe87eed23/build +0 -0
  59. package/zig/zig-cache/tmp/97e4f8c36515e21c/build +0 -0
  60. package/zig/zig-cache/tmp/9de1ed1e5e51add9/build +0 -0
  61. package/zig/zig-cache/tmp/a051bffb2bb19130/build +0 -0
  62. package/zig/zig-cache/tmp/a2d24144aa720ff5/build +0 -0
  63. package/zig/zig-cache/tmp/a6450c98790914aa/build +0 -0
  64. package/zig/zig-cache/tmp/a7851baacd09b97e/build +0 -0
  65. package/zig/zig-cache/tmp/a9620e90f7f21f27/build +0 -0
  66. package/zig/zig-cache/tmp/a9d0ae4a598bbae7/build +0 -0
  67. package/zig/zig-cache/tmp/adc434dfc8cff980/build +0 -0
  68. package/zig/zig-cache/tmp/b5906f3589d7a44e/build +0 -0
  69. package/zig/zig-cache/tmp/b92139a1c898f577/build +0 -0
  70. package/zig/zig-cache/tmp/bae01421ea15bdba/build +0 -0
  71. package/zig/zig-cache/tmp/bb366772a50163c4/build +0 -0
  72. package/zig/zig-cache/tmp/c2d91fef7812604a/build +0 -0
  73. package/zig/zig-cache/tmp/ca6f88cc7990ad70/build +0 -0
  74. package/zig/zig-cache/tmp/ce13ae17f06e2897/build +0 -0
  75. package/zig/zig-cache/tmp/d24e82056000cd3c/build +0 -0
  76. package/zig/zig-cache/tmp/d2d6630e2babce19/build +0 -0
  77. package/zig/zig-cache/tmp/d64ec8b30efbd39c/build +0 -0
  78. package/zig/zig-cache/tmp/db4af70a9676f22f/build +0 -0
  79. package/zig/zig-cache/tmp/dfc96c446ee3d83c/build +0 -0
  80. package/zig/zig-cache/tmp/e02fe3c9c8a1234f/build +0 -0
  81. package/zig/zig-cache/tmp/e1245d4f3790795e/build +0 -0
  82. package/zig/zig-cache/tmp/e680fd8b7b04a063/build +0 -0
  83. package/zig/zig-cache/tmp/ed7c14c98bd9f25f/build +0 -0
  84. package/zig/zig-cache/tmp/f26e509f102208e6/build +0 -0
  85. package/zig/zig-cache/tmp/f8720cafd74033d4/build +0 -0
  86. package/zig/zig-cache/tmp/fac1b43e1ee19272/build +0 -0
  87. package/zig/zig-cache/tmp/fb98957abd64e116/build +0 -0
  88. package/zig/zig-cache/tmp/fc4e6e1289006215/build +0 -0
  89. package/zig/zig-cache/tmp/fcb97051d8caa0cc/build +0 -0
  90. /package/zig/.zig-cache/h/{7cd9f9fd941f2892ab224c4945ba076c.txt → 34a475bf928ad516cafd7711e8a32f5d.txt} +0 -0
  91. /package/zig/.zig-cache/tmp/{0a4dffdc5a15f6d6 → 2cfdf4ebb8ec5012}/build +0 -0
  92. /package/zig/.zig-cache/tmp/{0d9630e458f496b4 → 37deb3ceec94f9fa}/build +0 -0
  93. /package/zig/.zig-cache/tmp/{1d7d03c03ea37492 → 5a0d6fd7e8e0f799}/build +0 -0
  94. /package/zig/.zig-cache/tmp/{3946003e031e6f2d → 6ab190663d80e209}/build +0 -0
  95. /package/zig/.zig-cache/tmp/{44408331dcb9625f → adf29279445d953e}/build +0 -0
  96. /package/zig/.zig-cache/tmp/{5da4e501f0b1dec3 → db475d6887896a7a}/build +0 -0
  97. /package/zig/.zig-cache/tmp/{8204f942d1b78e57 → dd360cd6bf167465}/build +0 -0
  98. /package/zig/.zig-cache/tmp/{8e6e9ee6f0c3a9ed → e7395051760bb72f}/build +0 -0
  99. /package/zig/.zig-cache/tmp/{99f07abb0b44ff6e → ee051c6236b3150a}/build +0 -0
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2024-08-10T15:03:07Z
3
+ - **Last updated**: 2024-09-19T21:09:34Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -9,6 +9,32 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ # [2.0.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@2.0.0) (2024-08-18)
13
+
14
+ #### 🛑 Breaking changes
15
+
16
+ - update WASM module dependency handling ([31f0358](https://github.com/thi-ng/umbrella/commit/31f0358))
17
+ - BREAKING CHANGE: update WASM module dependency handling
18
+ - add `WasmModuleSpec` interface to declare WASM modules with their deps
19
+ - update `WasmBridge` ctor to accept array of module specs
20
+ - add buildModuleGraph() to process dependencies & init modules from specs
21
+ - add/update docstrings
22
+ - update tests
23
+
24
+ #### 🚀 Features
25
+
26
+ - add ObjectIndex.addUnique() ([e79275d](https://github.com/thi-ng/umbrella/commit/e79275d))
27
+ - add MemoryView types ([b621adc](https://github.com/thi-ng/umbrella/commit/b621adc))
28
+ - add internal memory view accessors ([6830337](https://github.com/thi-ng/umbrella/commit/6830337))
29
+ - these accessors are shared by various types generated via [@thi.ng/wasm-api-bindgen](https://github.com/thi-ng/umbrella/tree/main/packages/wasm-api-bindgen)
30
+ and help to drastically cut down filesize of generated code
31
+ - update pkg exports
32
+ - update ObjectIndex ctor, make opts fully optional ([564e0f3](https://github.com/thi-ng/umbrella/commit/564e0f3))
33
+
34
+ #### ♻️ Refactoring
35
+
36
+ - update internal array mem accessors ([b442d92](https://github.com/thi-ng/umbrella/commit/b442d92))
37
+
12
38
  ## [1.6.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@1.6.0) (2024-06-21)
13
39
 
14
40
  #### 🚀 Features
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![Mastodon Follow](https://img.shields.io/mastodon/follow/109331703950160316?domain=https%3A%2F%2Fmastodon.thi.ng&style=social)](https://mastodon.thi.ng/@toxi)
8
8
 
9
9
  > [!NOTE]
10
- > This is one of 198 standalone projects, maintained as part
10
+ > This is one of 199 standalone projects, maintained as part
11
11
  > of the [@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo
12
12
  > and anti-framework.
13
13
  >
@@ -15,6 +15,7 @@
15
15
  > GitHub](https://github.com/sponsors/postspectacular). Thank you! ❤️
16
16
 
17
17
  - [About](#about)
18
+ - [Polyglot bindings generator](#polyglot-bindings-generator)
18
19
  - [Custom API modules](#custom-api-modules)
19
20
  - [String handling](#string-handling)
20
21
  - [Memory allocations](#memory-allocations)
@@ -46,22 +47,24 @@ Generic, modular, extensible API bridge and infrastructure for hybrid JS & WebAs
46
47
 
47
48
  (Screenshots of selected projects made with Zig & TypeScript using the interop features provided by this package. Images link to respective project info)
48
49
 
49
- This package provides the following:
50
+ This package form the core of a larger WASM toolkit which includes a polyglot
51
+ bindings generator and a growing number of API modules to interop with different
52
+ browser APIs. This core package provides the following:
50
53
 
51
54
  1. A small
52
55
  [`WasmBridge`](https://docs.thi.ng/umbrella/wasm-api/classes/WasmBridge.html)
53
- class as generic interop basis and much reduced boilerplate for hybrid JS/WebAssembly
54
- applications.
55
- 2. A minimal core API for debug output, string/pointer/typedarray accessors for
56
+ class as generic interop basis and much reduced boilerplate for hybrid
57
+ JS/WebAssembly applications.
58
+ 2. Child WASM API modules with dependency graph resolution & initialization
59
+ 3. A minimal core API for debug output, string/pointer/typedarray accessors for
56
60
  8/16/32/64 bit (u)ints and 32/64 bit floats. Additionally, a number of support
57
61
  modules for [DOM
58
62
  manipulation](https://github.com/thi-ng/umbrella/tree/develop/packages/wasm-api-dom/),
59
63
  [scheduled function
60
64
  execution](https://github.com/thi-ng/umbrella/tree/develop/packages/wasm-api-schedule/),
61
65
  WebGL, WebGPU, WebAudio etc. is being actively worked on.
62
- 3. Different types of memory-mapped (UTF-8) string abstractions (slice or pointer based)
66
+ 4. Different types of memory-mapped (UTF-8) string abstractions (slice or pointer based)
63
67
  5. Shared (opt-in) memory allocation mechanism, also accessible from JS/TS side
64
- 4. Simple registration & dependency-order initialization for child WASM API modules
65
68
  6. Include files for
66
69
  [Zig](https://github.com/thi-ng/umbrella/tree/develop/packages/wasm-api/zig),
67
70
  and
@@ -71,46 +74,87 @@ WebGL, WebGPU, WebAudio etc. is being actively worked on.
71
74
  by this package
72
75
  7. [Zig build files](#using-the-zig-build-system) to simplify using hybrid
73
76
  TS/Zig packages with the built-in build system
74
- 8. Extensible shared [datatype code generator
75
- infrastructure](https://github.com/thi-ng/umbrella/tree/develop/packages/wasm-api-bindgen/)
76
- for (currently) Zig & TypeScript and C11. For TS fully type checked and
77
- memory-mapped (zero-copy) accessors of WASM-side data are generated. In
78
- principle, all languages with a WASM target are supported, however currently
79
- only bindings for these mentioned langs are included.
80
- 9. [CLI
81
- frontend/utility](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api-bindgen/README.md#cli-generator)
82
- for the code generator(s)
77
+
78
+ ## Polyglot bindings generator
79
+
80
+ The toolkit includes an extensible [code
81
+ generator](https://github.com/thi-ng/umbrella/tree/develop/packages/wasm-api-bindgen/)
82
+ for shared datatypes and (currently) supports Zig & TypeScript and C11. For TS
83
+ fully type checked and memory-mapped (mostly zero-copy) accessors of WASM-side
84
+ data are generated. In principle, all languages with a WASM target are
85
+ supported, however currently only bindings for the languages mentioned are
86
+ included. The codegen also includes a [CLI
87
+ frontend/utility](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api-bindgen/README.md#cli-generator).
83
88
 
84
89
  ## Custom API modules
85
90
 
86
91
  The
87
92
  [`WasmBridge`](https://docs.thi.ng/umbrella/wasm-api/classes/WasmBridge.html)
88
- can be extented via custom defined API modules. Such API extensions will consist
93
+ can be extented via custom defined API modules. Such child modules will consist
89
94
  of a collection of JS/TS functions & variables, their related counterparts
90
95
  (import definitions) for the WASM target and (optionally) some shared data types
91
- ([bindings for which _can_ be generated by this package
92
- too](#data-bindings--code-generators)).
96
+ ([bindings for which can be generated via
97
+ thi.ng/wasm-api-bindgen](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api-bindgen)).
93
98
 
94
- On the JS side, custom API modules can be easily integrated via the [`IWasmAPI`
95
- interface](https://docs.thi.ng/umbrella/wasm-api/interfaces/IWasmAPI.html). The
96
- following example provides a brief overview:
99
+ On the JS side, custom API extensions can be easily integrated and exposed via
100
+ the [`IWasmAPI`
101
+ interface](https://docs.thi.ng/umbrella/wasm-api/interfaces/IWasmAPI.html) and
102
+ their module descriptors via
103
+ [`WasmModuleSpec`](https://docs.thi.ng/umbrella/wasm-api/interfaces/WasmModuleSpec.html).
104
+ The following example provides a brief overview:
97
105
 
98
106
  ```ts
99
- import { IWasmAPI, WasmBridge } from "@thi.ng/wasm-api";
107
+ import { WasmBridge, type IWasmAPI, type WasmModuleSpec } from "@thi.ng/wasm-api";
108
+ import { WasmDomModule, type WasmDom, type WasmDomExports } from "@thi.ng/wasm-api-dom";
109
+
110
+ export const CustomModule: WasmModuleSpec = {
111
+ // Unique API module identifier to group WASM imports, must match ID used
112
+ // by the native code (see further below).
113
+ id: "custom",
114
+ // Optional array of API modules this module depends on. This is used to
115
+ // infer the full dependency graph and correct initialization order.
116
+ // Note: Each of the wasm-api support packages includes a module spec...
117
+ depes: [WasmDomModule],
118
+ // Factory function to pre-instantiate the API module. Full initialization
119
+ // only happens at a later point via WasmBridge.instantiate() or
120
+ // WasmBridge.init() and each module's own init() method...
121
+ factory: () => new CustomAPI(),
122
+ };
123
+
124
+ // Optional declarations for JS-side functions which can be used from the WASM side.
125
+ // Using an interface for this is useful to establish a contract.
126
+ export interface CustomImports extends WebAssembly.ModuleImports {
127
+ /**
128
+ * Writes `num` random float32 numbers from given address
129
+ */
130
+ fillRandom(addr: number, num: number): void;
131
+ }
100
132
 
101
- export class CustomAPI implements IWasmAPI {
102
- // Unique API module identifier to group WASM imports,
103
- // must match ID used by native code (see further below).
104
- readonly id = "custom";
105
- // optionally list IDs of other API modules this module depends on
106
- // these are used to infer the correct initialization order
107
- readonly dependencies = [];
133
+ // Optional (but likely used) interface declarations of functions exposed
134
+ // by the WASM binary and callable from the JS side (i.e. the opposite of the above)
135
+ //
136
+ // Note: If your module has dependencies on other modules you should declare
137
+ // your exports as extension of these other module's exports for better
138
+ // downstream developer experience
139
+ export interface CustomWasmExports extends WasmDomExports {
140
+ // ...
141
+ }
108
142
 
143
+ // Actual custom API extension implentation
144
+ export class CustomAPI implements IWasmAPI<CustomWasmExports> {
109
145
  parent!: WasmBridge;
146
+ dom!: WasmDom;
110
147
 
148
+ /**
149
+ * Actual module initialization, called from the main WasmBridge which
150
+ * initializes all modules in dependency order...
151
+ */
111
152
  async init(parent: WasmBridge) {
112
153
  this.parent = parent;
113
154
  this.parent.logger.debug("initializing custom API");
155
+ // Store a direct reference to the DOM module for future ref
156
+ // (just shown as example, we're not actually using this module here...)
157
+ this.dom = <WasmDom>this.parent.modules[WasmDomModule.id];
114
158
 
115
159
  // any other tasks you might need to do...
116
160
 
@@ -118,15 +162,15 @@ export class CustomAPI implements IWasmAPI {
118
162
  }
119
163
 
120
164
  /**
121
- * Returns object of functions to import as externals into the
122
- * WASM module during instantiation. These imports are merged
123
- * into a larger imports object alongside the bridge's core API...
165
+ * Returns object of functions/constants to import as externals into the
166
+ * WASM module during instantiation. These imports are merged into a
167
+ * larger imports object alongside the bridge's core API...
168
+ *
169
+ * Each module's imports will be grouped by its declared module ID, which
170
+ * also needs to be used to declare extern functions on the WASM side.
124
171
  */
125
- getImports(): WebAssembly.Imports {
172
+ getImports(): CustomImports {
126
173
  return {
127
- /**
128
- * Writes `num` random float32 numbers from given address
129
- */
130
174
  fillRandom: (addr: number, num: number) => {
131
175
  addr >>>= 2;
132
176
  while(num-- > 0) this.parent.f32[addr++] = Math.random();
@@ -136,13 +180,12 @@ export class CustomAPI implements IWasmAPI {
136
180
  }
137
181
 
138
182
  // now we can supply this custom API when creating the main WASM bridge:
139
- export const bridge = new WasmBridge([new CustomAPI()]);
183
+ export const bridge = new WasmBridge([CustomModule]);
140
184
  ```
141
185
 
142
186
  In Zig (or any other language of your choice) we can then utilize this custom
143
- API like so (Please also see [example
144
- projects](https://github.com/thi-ng/umbrella/tree/develop/examples/zig-canvas/)
145
- & other example snippets in this readme):
187
+ API like so (Please also see [example projects](#usage-examples) & other code
188
+ snippets in this readme):
146
189
 
147
190
  Bindings file / lib:
148
191
 
@@ -155,26 +198,31 @@ Bindings file / lib:
155
198
  ///
156
199
  /// The bridge core API uses "wasmapi" as reserved import group name
157
200
  extern "custom" fn fillRandom(addr: [*]f32, num: usize) void;
201
+
202
+ /// Syntax sugar for `fillRandom()`
203
+ pub fn fillRandomSlice(slice: []f32) void {
204
+ fillRandom(slice.ptr, slice.len);
205
+ }
158
206
  ```
159
207
 
160
208
  Main Zig file:
161
209
 
162
210
  ```zig
163
211
  // Import JS core API
164
- const js = @import("wasm-api");
212
+ const wasm = @import("wasm-api");
165
213
  const custom = @import("custom.zig");
166
214
 
167
215
  export fn test_randomVec4() void {
168
216
  var foo = [4]f32{ 1, 2, 3, 4 };
169
217
 
170
218
  // print original
171
- js.printF32Array(foo[0..]);
219
+ wasm.printF32Array(foo[0..]);
172
220
 
173
221
  // populate foo with random numbers
174
- custom.fillRandom(&foo, foo.len);
222
+ custom.fillRandomSlice(foo[0..]);
175
223
 
176
224
  // print result
177
- js.printF32Array(foo[0..]);
225
+ wasm.printF32Array(foo[0..]);
178
226
  }
179
227
  ```
180
228
 
@@ -257,32 +305,41 @@ etc.) use an auto-intialization hook related to the above `WASM_ALLOCATOR`
257
305
  mechanism: If that allocator is available, the WASM side of these modules will
258
306
  auto initialize and thus reduce boilerplate. However, if no such central
259
307
  allocator is defined and/or a custom allocator should be used, then these API
260
- modules will be have to be initialized manually.
308
+ modules will have to be initialized manually.
261
309
 
262
310
  ## Object indices & handles
263
311
 
264
- Since only numeric values can be exchanged between the WASM module and the JS
265
- host, any JS native objects the WASM side might want to be working with must be
266
- managed manually in JS. For this purpose the [`ObjectIndex`
312
+ Since only numeric values can be directly passed between the WASM module and the
313
+ JS host, any JS native objects the WASM side might want to be working with must
314
+ be managed manually in JS. For this purpose the [`ObjectIndex`
267
315
  class](https://docs.thi.ng/umbrella/wasm-api/classes/ObjectIndex.html) can be
268
- used by API modules to handle ID generation (incl. recycling, using
269
- [@thi.ng/idgen](https://github.com/thi-ng/umbrella/tree/develop/packages/idgen))
270
- and the indexing of different types of JS objects/values. Only the numeric IDs
271
- (handles) will then need to be exchanged with the WASM module...
316
+ used by API modules to handle the indexing of different types of JS
317
+ objects/values and their ID generation (incl. recycling of IDs, using
318
+ [@thi.ng/idgen](https://github.com/thi-ng/umbrella/tree/develop/packages/idgen)).
319
+ Using this approach, only the numeric IDs (handles) will then need to be
320
+ exchanged with the WASM module...
272
321
 
273
322
  ```ts
274
323
  import { ObjectIndex } from "@thi.ng/wasm-api";
324
+ import { ConsoleLogger } from "@thi.ng/logger";
275
325
 
326
+ // create index (see API docs for options)
276
327
  const canvases = new ObjectIndex<HTMLCanvasElement>({ name: "canvas" });
277
328
 
278
329
  // index item and assign new ID
279
- canvases.add(document.createElement("canvas"));
330
+ canvases.add(<HTMLCanvasElement>document.createElement("canvas"));
280
331
  // 0
281
332
 
282
333
  // look up item by ID
283
334
  canvases.get(0);
284
335
  // <canvas ...>
285
336
 
337
+ // by default, items to be indexed are not checked for equality
338
+ // but we can use the below to ensure double indexing is avoided...
339
+ // (supports custom predicates to check for equality)
340
+ canvases.addUnique(canvases.get(0));
341
+ // 0 (returned already known ID)
342
+
286
343
  // work w/ retrieved item
287
344
  canvases.get(0).id = "foo";
288
345
 
@@ -326,8 +383,8 @@ built-in build system is as simple as:
326
383
  **IMPORTANT:** Due to recent syntax & build system changes in Zig
327
384
  [v0.12.0](https://ziglang.org/download/0.12.0/release-notes.html) &
328
385
  [v0.13.0](https://ziglang.org/download/0.13.0/release-notes.html), older Zig
329
- versions are not actively supported (however, [build files for older versions
330
- are still
386
+ versions are not actively supported anymore (however, [build files for older
387
+ versions are still
331
388
  included](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/zig))
332
389
 
333
390
  ```zig
@@ -359,8 +416,7 @@ pub fn build(b: *std.Build) void {
359
416
  ### Example projects
360
417
 
361
418
  All bundled example projects (see [list below](#usage-examples)) are being built
362
- via this script. **Please find more details/options in the commented source
363
- code:**
419
+ via this script. **More details/options in the commented source code:**
364
420
 
365
421
  - [`/zig/build.zig`](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/zig/build.zig)
366
422
 
@@ -382,6 +438,7 @@ please also consult the information in
382
438
  - [@thi.ng/wasm-api-canvas](https://github.com/thi-ng/umbrella/tree/develop/packages/wasm-api-canvas) - HTML Canvas2D bridge API for hybrid TypeScript & WASM (Zig) applications
383
439
  - [@thi.ng/wasm-api-dom](https://github.com/thi-ng/umbrella/tree/develop/packages/wasm-api-dom) - Browser DOM bridge API for hybrid TypeScript & WASM (Zig) applications
384
440
  - [@thi.ng/wasm-api-schedule](https://github.com/thi-ng/umbrella/tree/develop/packages/wasm-api-schedule) - Delayed & scheduled function execution (via setTimeout() etc.) for hybrid WASM apps
441
+ - [@thi.ng/wasm-api-webgl](https://github.com/thi-ng/umbrella/tree/develop/packages/wasm-api-webgl) - WebGL bridge API for hybrid TypeScript & WASM (Zig) applications
385
442
 
386
443
  ## Installation
387
444
 
@@ -403,7 +460,7 @@ Browser ESM import:
403
460
 
404
461
  [JSDelivr documentation](https://www.jsdelivr.com/)
405
462
 
406
- Package sizes (brotli'd, pre-treeshake): ESM: 2.69 KB
463
+ Package sizes (brotli'd, pre-treeshake): ESM: 3.00 KB
407
464
 
408
465
  ## Dependencies
409
466
 
@@ -419,7 +476,7 @@ Note: @thi.ng/api is in _most_ cases a type-only import (not used at runtime)
419
476
 
420
477
  ## Usage examples
421
478
 
422
- Four projects in this repo's
479
+ Five projects in this repo's
423
480
  [/examples](https://github.com/thi-ng/umbrella/tree/develop/examples)
424
481
  directory are using this package:
425
482
 
@@ -429,6 +486,7 @@ directory are using this package:
429
486
  | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/zig-cellular.jpg" width="240"/> | Zig-based 2D multi-behavior cellular automata | [Demo](https://demo.thi.ng/umbrella/zig-cellular/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/zig-cellular) |
430
487
  | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/zig-counter.png" width="240"/> | Simple Zig/WASM click counter DOM component | [Demo](https://demo.thi.ng/umbrella/zig-counter/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/zig-counter) |
431
488
  | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/zig-todo-list.png" width="240"/> | Zig-based To-Do list, DOM creation, local storage task persistence | [Demo](https://demo.thi.ng/umbrella/zig-todo-list/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/zig-todo-list) |
489
+ | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/zig-webgl.avif" width="240"/> | Basic Zig/WebAssembly WebGL demo | [Demo](https://demo.thi.ng/umbrella/zig-webgl/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/zig-webgl) |
432
490
 
433
491
  ## API
434
492
 
package/api.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { EVENT_ALL, Fn, IDeref, ILength } from "@thi.ng/api";
1
+ import type { BigTypedArray, EVENT_ALL, Fn, IDeref, ILength, TypedArray, Values } from "@thi.ng/api";
2
2
  import type { WasmBridge } from "./bridge.js";
3
3
  export declare const EVENT_MEMORY_CHANGED = "memory-changed";
4
4
  export declare const EVENT_PANIC = "panic";
@@ -52,18 +52,6 @@ export type WasmTypeConstructor<T> = Fn<IWasmMemoryAccess, WasmType<T>>;
52
52
  * certain exports declared by WASM module.
53
53
  */
54
54
  export interface IWasmAPI<T extends WasmExports = WasmExports> {
55
- /**
56
- * The unique ID for grouping the WASM imports of this module. MUST be the
57
- * same as used by the native side of the module.
58
- */
59
- readonly id: string;
60
- /**
61
- * IDs of other WASM API modules which this module depends on. Used to infer
62
- * correct initialization order. The core module (w/ unique ID: `wasmapi`)
63
- * is always considered an implicit dependency, will be initialized first
64
- * and MUST NOT be stated here.
65
- */
66
- readonly dependencies?: string[];
67
55
  /**
68
56
  * Called by {@link WasmBridge.init} to initialize all child APIs (async)
69
57
  * after the WASM module has been instantiated. If the method returns false
@@ -79,6 +67,31 @@ export interface IWasmAPI<T extends WasmExports = WasmExports> {
79
67
  */
80
68
  getImports(): WebAssembly.ModuleImports;
81
69
  }
70
+ export interface WasmModuleSpec<T extends WasmExports = WasmExports> {
71
+ /**
72
+ * The unique ID for grouping the WASM imports of this module. MUST be the
73
+ * same as used by the native side of the module.
74
+ */
75
+ id: string;
76
+ /**
77
+ * Optional array of other {@link WasmModuleSpec}s which this module depends
78
+ * on (element order is irrelevant). Used to construct a module dependency
79
+ * graph and the correct initialization order of modules. The core module
80
+ * (defined via {@link WasmBridge} w/ unique ID: `wasmapi`) is always
81
+ * considered an implicit dependency, will be initialized first and MUST NOT
82
+ * be stated here.
83
+ */
84
+ deps?: WasmModuleSpec<T>[];
85
+ /**
86
+ * Factory function to pre-instantiate the API module.
87
+ *
88
+ * @remarks
89
+ * Note: All modules will only be fully initialized at a later point via
90
+ * {@link WasmBridge.instantiate} or {@link WasmBridge.init} and each
91
+ * modules own {@link IWasmAPI.init} method.
92
+ */
93
+ factory: Fn<WasmBridge<T>, IWasmAPI<T>>;
94
+ }
82
95
  /**
83
96
  * Base interface of exports declared by the WASM module. At the very least, a
84
97
  * compatible module needs to export its memory and the functions defined in
@@ -133,6 +146,21 @@ export interface WasmExports {
133
146
  _wasm_free(addr: number, numBytes: number): void;
134
147
  }
135
148
  export type MemorySlice = [addr: number, len: number];
149
+ export type MemoryViewType = "i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" | "f32" | "f64";
150
+ export interface MemoryViewTypeMap extends Record<MemoryViewType, TypedArray | BigTypedArray> {
151
+ u8: Uint8Array;
152
+ u8c: Uint8ClampedArray;
153
+ i8: Int8Array;
154
+ u16: Uint16Array;
155
+ i16: Int16Array;
156
+ u32: Uint32Array;
157
+ i32: Int32Array;
158
+ i64: BigInt64Array;
159
+ u64: BigUint64Array;
160
+ f32: Float32Array;
161
+ f64: Float64Array;
162
+ }
163
+ export type MemoryView = Values<MemoryViewTypeMap>;
136
164
  export interface IWasmMemoryAccess {
137
165
  i8: Int8Array;
138
166
  u8: Uint8Array;
package/bridge.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Event, INotify, IObjectOf, Listener, NumericArray } from "@thi.ng/api";
2
2
  import type { ILogger } from "@thi.ng/logger";
3
- import { type BigIntArray, type BridgeEventType, type CoreAPI, type IWasmAPI, type IWasmMemoryAccess, type MemorySlice, type WasmExports } from "./api.js";
3
+ import { type BigIntArray, type BridgeEventType, type CoreAPI, type IWasmAPI, type IWasmMemoryAccess, type MemorySlice, type WasmExports, type WasmModuleSpec } from "./api.js";
4
4
  export declare const Panic: {
5
5
  new (msg?: string | undefined): {
6
6
  origMessage: string;
@@ -60,7 +60,21 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> implements
60
60
  exports: T;
61
61
  api: CoreAPI;
62
62
  modules: IObjectOf<IWasmAPI<T>>;
63
- constructor(modules?: IWasmAPI<T>[], logger?: ILogger);
63
+ order: string[];
64
+ constructor(modules?: WasmModuleSpec<T>[], logger?: ILogger);
65
+ /**
66
+ * Takes array of root module specs, extracts all transitive dependencies,
67
+ * pre-computes their topological order, then calls
68
+ * {@link WasmModuleSpec.factory} for each module and stores all modules for
69
+ * future reference.
70
+ *
71
+ * @remarks
72
+ * Note: The pre-instantiated modules will only be fully initialized later
73
+ * via {@link WasmBridge.instantiate} or {@link WasmBridge.init}.
74
+ *
75
+ * @param specs
76
+ */
77
+ protected _buildModuleGraph(specs: WasmModuleSpec<T>[]): void;
64
78
  /**
65
79
  * Instantiates WASM module from given `src` (and optional provided extra
66
80
  * imports), then automatically calls {@link WasmBridge.init} with the
package/bridge.js CHANGED
@@ -70,14 +70,7 @@ let WasmBridge = class {
70
70
  timer: () => performance.now(),
71
71
  epoch: () => BigInt(Date.now())
72
72
  };
73
- this.modules = modules.reduce((acc, x) => {
74
- assert(
75
- acc[x.id] === void 0 && x.id !== this.id,
76
- `duplicate API module ID: ${x.id}`
77
- );
78
- acc[x.id] = x;
79
- return acc;
80
- }, {});
73
+ this._buildModuleGraph(modules);
81
74
  }
82
75
  id = "wasmapi";
83
76
  i8;
@@ -96,6 +89,44 @@ let WasmBridge = class {
96
89
  exports;
97
90
  api;
98
91
  modules;
92
+ order;
93
+ /**
94
+ * Takes array of root module specs, extracts all transitive dependencies,
95
+ * pre-computes their topological order, then calls
96
+ * {@link WasmModuleSpec.factory} for each module and stores all modules for
97
+ * future reference.
98
+ *
99
+ * @remarks
100
+ * Note: The pre-instantiated modules will only be fully initialized later
101
+ * via {@link WasmBridge.instantiate} or {@link WasmBridge.init}.
102
+ *
103
+ * @param specs
104
+ */
105
+ _buildModuleGraph(specs) {
106
+ const unique = /* @__PURE__ */ new Set();
107
+ const queue = [...specs];
108
+ while (queue.length) {
109
+ const mod = queue.shift();
110
+ unique.add(mod);
111
+ if (!mod.deps) continue;
112
+ for (let d of mod.deps) {
113
+ if (!unique.has(d)) queue.push(d);
114
+ }
115
+ }
116
+ const graph = [...unique].reduce((acc, mod) => {
117
+ assert(
118
+ (acc[mod.id] === void 0 || acc[mod.id] === mod) && mod.id !== this.id,
119
+ `duplicate API module ID: ${mod.id}`
120
+ );
121
+ acc[mod.id] = mod;
122
+ return acc;
123
+ }, {});
124
+ this.order = topoSort(graph, (mod) => mod.deps?.map((x) => x.id));
125
+ this.modules = this.order.reduce((acc, id) => {
126
+ acc[id] = graph[id].factory(this);
127
+ return acc;
128
+ }, {});
129
+ }
99
130
  /**
100
131
  * Instantiates WASM module from given `src` (and optional provided extra
101
132
  * imports), then automatically calls {@link WasmBridge.init} with the
@@ -130,11 +161,7 @@ let WasmBridge = class {
130
161
  async init(exports) {
131
162
  this.exports = exports;
132
163
  this.ensureMemory(false);
133
- for (let id of topoSort(
134
- this.modules,
135
- (module) => module.dependencies
136
- )) {
137
- assert(!!this.modules[id], `missing API module: ${id}`);
164
+ for (let id of this.order) {
138
165
  this.logger.debug(`initializing API module: ${id}`);
139
166
  const status = await this.modules[id].init(this);
140
167
  if (!status) return false;
package/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./api.js";
2
2
  export * from "./bridge.js";
3
+ export * from "./memory.js";
3
4
  export * from "./object-index.js";
4
5
  export * from "./pointer.js";
5
6
  export * from "./string.js";
package/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./api.js";
2
2
  export * from "./bridge.js";
3
+ export * from "./memory.js";
3
4
  export * from "./object-index.js";
4
5
  export * from "./pointer.js";
5
6
  export * from "./string.js";
package/memory.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { IWasmMemoryAccess, MemoryView, WasmType, WasmTypeConstructor } from "./api.js";
2
+ /** @internal */
3
+ export declare const __instanceArray: <T>(type: WasmType<T>, addr: number, num: number, size?: number) => T[];
4
+ /** @internal */
5
+ export declare const __array: <T>(mem: IWasmMemoryAccess, ctor: WasmTypeConstructor<T>, addr: number, len: number) => T[];
6
+ /** @internal */
7
+ export declare const __slice32: <T>(mem: IWasmMemoryAccess, ctor: WasmTypeConstructor<T>, offset: number) => T[];
8
+ /** @internal */
9
+ export declare const __slice64: <T>(mem: IWasmMemoryAccess, ctor: WasmTypeConstructor<T>, offset: number) => T[];
10
+ /** @internal */
11
+ export declare const __primslice32: <T extends MemoryView>(mem: IWasmMemoryAccess, view: T, offset: number, shift: number) => T;
12
+ /** @internal */
13
+ export declare const __primslice64: <T extends MemoryView>(mem: IWasmMemoryAccess, view: T, offset: number, shift: number) => T;
14
+ //# sourceMappingURL=memory.d.ts.map
package/memory.js ADDED
@@ -0,0 +1,40 @@
1
+ const __instanceArray = (type, addr, num, size = type.size) => {
2
+ const items = [];
3
+ for (; num-- > 0; addr += size) items.push(type.instance(addr));
4
+ return items;
5
+ };
6
+ const __array = (mem, ctor, addr, len) => __instanceArray(ctor(mem), addr, len);
7
+ const __slice32 = (mem, ctor, offset) => {
8
+ return __array(
9
+ mem,
10
+ ctor,
11
+ mem.u32[offset >>> 2],
12
+ mem.u32[offset + 4 >>> 2]
13
+ );
14
+ };
15
+ const __slice64 = (mem, ctor, offset) => {
16
+ return __array(
17
+ mem,
18
+ ctor,
19
+ Number(mem.u64[offset >>> 3]),
20
+ Number(mem.u64[offset + 8 >>> 3])
21
+ );
22
+ };
23
+ const __primslice32 = (mem, view, offset, shift) => {
24
+ const addr = mem.u32[offset >>> 2] >>> shift;
25
+ const len = mem.u32[offset + 4 >>> 2];
26
+ return view.subarray(addr, addr + len);
27
+ };
28
+ const __primslice64 = (mem, view, offset, shift) => {
29
+ const addr = Number(mem.u64[offset >>> 3]) >>> shift;
30
+ const len = Number(mem.u64[offset + 8 >>> 3]);
31
+ return view.subarray(addr, addr + len);
32
+ };
33
+ export {
34
+ __array,
35
+ __instanceArray,
36
+ __primslice32,
37
+ __primslice64,
38
+ __slice32,
39
+ __slice64
40
+ };
package/object-index.d.ts CHANGED
@@ -9,20 +9,26 @@ export interface ObjectIndexOpts {
9
9
  /**
10
10
  * Optional logger instance
11
11
  */
12
- logger?: ILogger;
12
+ logger: ILogger;
13
13
  /**
14
14
  * Number of bits for IDs, [1..32] range.
15
15
  *
16
16
  * @defaultValue 32
17
17
  */
18
- bits?: Range1_32;
18
+ bits: Range1_32;
19
19
  }
20
+ /**
21
+ * Object cache with numeric ID handle management.
22
+ *
23
+ * @remarks
24
+ * [Further reference](https://docs.thi.ng/umbrella/wasm-api/#md:object-indices--handles)
25
+ */
20
26
  export declare class ObjectIndex<T> {
21
27
  readonly name: string;
22
28
  logger?: ILogger;
23
29
  protected idgen: IDGen;
24
30
  protected items: T[];
25
- constructor(opts: ObjectIndexOpts);
31
+ constructor(opts?: Partial<ObjectIndexOpts>);
26
32
  keys(): Generator<number, void, unknown>;
27
33
  values(): Generator<T, void, unknown>;
28
34
  /**
@@ -32,6 +38,19 @@ export declare class ObjectIndex<T> {
32
38
  * @param item
33
39
  */
34
40
  add(item: T): number;
41
+ /**
42
+ * Similar to {@link ObjectIndex.add}, but first checks if `item` has
43
+ * already been indexed and if so returns the ID of already indexed item
44
+ * without adding `item` to the index again. Uses `equiv` for checking item
45
+ * equality (by default: `===`).
46
+ *
47
+ * @remarks
48
+ * Currently an O(n) implementation.
49
+ *
50
+ * @param item
51
+ * @param equiv
52
+ */
53
+ addUnique(item: T, equiv?: Predicate<T>): number;
35
54
  /**
36
55
  * Returns true if the given `id` is valid/active.
37
56
  *
package/object-index.js CHANGED
@@ -1,14 +1,15 @@
1
1
  import { assert } from "@thi.ng/errors/assert";
2
2
  import { IDGen } from "@thi.ng/idgen";
3
+ let __nextID = 0;
3
4
  class ObjectIndex {
4
5
  name;
5
6
  logger;
6
7
  idgen;
7
8
  items = [];
8
9
  constructor(opts) {
9
- this.name = opts.name;
10
- this.logger = opts.logger;
11
- this.idgen = new IDGen(opts.bits || 32, 0);
10
+ this.name = opts?.name ?? `idx-${__nextID++}`;
11
+ this.logger = opts?.logger;
12
+ this.idgen = new IDGen(opts?.bits ?? 32, 0);
12
13
  }
13
14
  keys() {
14
15
  return this.idgen[Symbol.iterator]();
@@ -30,6 +31,22 @@ class ObjectIndex {
30
31
  this.items[id] = item;
31
32
  return id;
32
33
  }
34
+ /**
35
+ * Similar to {@link ObjectIndex.add}, but first checks if `item` has
36
+ * already been indexed and if so returns the ID of already indexed item
37
+ * without adding `item` to the index again. Uses `equiv` for checking item
38
+ * equality (by default: `===`).
39
+ *
40
+ * @remarks
41
+ * Currently an O(n) implementation.
42
+ *
43
+ * @param item
44
+ * @param equiv
45
+ */
46
+ addUnique(item, equiv = (x) => x === item) {
47
+ const id = this.find(equiv, false);
48
+ return id === void 0 ? this.add(item) : id;
49
+ }
33
50
  /**
34
51
  * Returns true if the given `id` is valid/active.
35
52
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/wasm-api",
3
- "version": "1.6.7",
3
+ "version": "2.0.1",
4
4
  "description": "Generic, modular, extensible API bridge and infrastructure for hybrid JS & WebAssembly projects",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -37,19 +37,19 @@
37
37
  "tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
38
38
  },
39
39
  "dependencies": {
40
- "@thi.ng/api": "^8.11.8",
41
- "@thi.ng/arrays": "^2.9.14",
42
- "@thi.ng/checks": "^3.6.10",
43
- "@thi.ng/errors": "^2.5.14",
44
- "@thi.ng/hex": "^2.3.52",
45
- "@thi.ng/idgen": "^2.2.48",
46
- "@thi.ng/logger": "^3.0.18"
40
+ "@thi.ng/api": "^8.11.10",
41
+ "@thi.ng/arrays": "^2.10.1",
42
+ "@thi.ng/checks": "^3.6.12",
43
+ "@thi.ng/errors": "^2.5.16",
44
+ "@thi.ng/hex": "^2.3.54",
45
+ "@thi.ng/idgen": "^2.2.50",
46
+ "@thi.ng/logger": "^3.0.20"
47
47
  },
48
48
  "devDependencies": {
49
- "@microsoft/api-extractor": "^7.47.5",
50
- "esbuild": "^0.23.0",
51
- "typedoc": "^0.26.5",
52
- "typescript": "^5.5.4"
49
+ "@microsoft/api-extractor": "^7.47.9",
50
+ "esbuild": "^0.23.1",
51
+ "typedoc": "^0.26.7",
52
+ "typescript": "^5.6.2"
53
53
  },
54
54
  "keywords": [
55
55
  "allocator",
@@ -102,6 +102,9 @@
102
102
  "./bridge": {
103
103
  "default": "./bridge.js"
104
104
  },
105
+ "./memory": {
106
+ "default": "./memory.js"
107
+ },
105
108
  "./object-index": {
106
109
  "default": "./object-index.js"
107
110
  },
@@ -116,5 +119,5 @@
116
119
  "status": "alpha",
117
120
  "year": 2022
118
121
  },
119
- "gitHead": "ec78f98d015e4d214a0b840e72e497407807daf3\n"
122
+ "gitHead": "b52baa3750ddd1256892df966ab7ac9b4806a9ef\n"
120
123
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,2 +0,0 @@
1
- pub const packages = struct {};
2
- pub const root_deps: []const struct { []const u8, []const u8 } = &.{};
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes