@nayan-ui/engine 1.0.0-beta.3 → 1.0.0-beta.5

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 (71) hide show
  1. package/README.md +229 -113
  2. package/lib/module/assets/AssetLoader.js +1 -1
  3. package/lib/module/assets/ProceduralCache.js +31 -4
  4. package/lib/module/assets/font.js +10 -7
  5. package/lib/module/assets/model.js +41 -19
  6. package/lib/module/assets/storage.shared.js +2 -0
  7. package/lib/module/assets/storage.web.js +1 -0
  8. package/lib/module/assets/texture.js +14 -11
  9. package/lib/module/index.js +4 -1
  10. package/lib/module/media/audio.js +6 -3
  11. package/lib/module/native/BrowserEngine.js +17 -0
  12. package/lib/module/render/GameView.js +86 -18
  13. package/lib/module/render/light.js +24 -0
  14. package/lib/module/render/quality.js +12 -5
  15. package/lib/module/render/useWorld.js +72 -0
  16. package/lib/module/world/Scoreboard.js +21 -3
  17. package/lib/module/world/TransformBatch.js +5 -3
  18. package/lib/module/world/World.js +27 -13
  19. package/lib/module/world/color.js +80 -0
  20. package/lib/module/world/desc.js +6 -5
  21. package/lib/module/world/effects.js +20 -8
  22. package/lib/typescript/src/assets/AssetLoader.d.ts +1 -1
  23. package/lib/typescript/src/assets/ProceduralCache.d.ts +12 -0
  24. package/lib/typescript/src/assets/model.d.ts +10 -0
  25. package/lib/typescript/src/index.d.ts +4 -1
  26. package/lib/typescript/src/native/EngineBridge.d.ts +6 -2
  27. package/lib/typescript/src/native/spec/NativeNayanEngine.d.ts +29 -0
  28. package/lib/typescript/src/render/GameView.d.ts +19 -7
  29. package/lib/typescript/src/render/light.d.ts +12 -0
  30. package/lib/typescript/src/render/quality.d.ts +4 -2
  31. package/lib/typescript/src/render/useWorld.d.ts +20 -0
  32. package/lib/typescript/src/types.d.ts +13 -4
  33. package/lib/typescript/src/world/Scoreboard.d.ts +12 -2
  34. package/lib/typescript/src/world/TransformBatch.d.ts +2 -2
  35. package/lib/typescript/src/world/World.d.ts +10 -2
  36. package/lib/typescript/src/world/color.d.ts +18 -0
  37. package/lib/typescript/src/world/effects.d.ts +13 -12
  38. package/lib/typescript/src/world/types.d.ts +11 -6
  39. package/lib/web/engine_core_bg.wasm +0 -0
  40. package/lib/web/index.js +521 -255
  41. package/lib/web/types/src/assets/AssetLoader.d.ts +1 -1
  42. package/lib/web/types/src/assets/ProceduralCache.d.ts +12 -0
  43. package/lib/web/types/src/assets/model.d.ts +10 -0
  44. package/lib/web/types/src/index.d.ts +4 -1
  45. package/lib/web/types/src/native/EngineBridge.d.ts +6 -2
  46. package/lib/web/types/src/native/spec/NativeNayanEngine.d.ts +29 -0
  47. package/lib/web/types/src/render/GameView.d.ts +19 -7
  48. package/lib/web/types/src/render/light.d.ts +11 -0
  49. package/lib/web/types/src/render/quality.d.ts +4 -2
  50. package/lib/web/types/src/render/useWorld.d.ts +19 -0
  51. package/lib/web/types/src/types.d.ts +13 -4
  52. package/lib/web/types/src/world/Scoreboard.d.ts +12 -2
  53. package/lib/web/types/src/world/TransformBatch.d.ts +2 -2
  54. package/lib/web/types/src/world/World.d.ts +10 -2
  55. package/lib/web/types/src/world/color.d.ts +17 -0
  56. package/lib/web/types/src/world/effects.d.ts +13 -12
  57. package/lib/web/types/src/world/types.d.ts +11 -6
  58. package/native/android/src/main/java/com/nayanui/engine/EngineSurfaceView.java +87 -26
  59. package/native/core/build/EngineCore.xcframework/ios-arm64/libengine_core.a +0 -0
  60. package/native/core/build/EngineCore.xcframework/ios-arm64-simulator/libengine_core.a +0 -0
  61. package/native/core/build/android/arm64-v8a/libengine_core.a +0 -0
  62. package/native/core/build/android/armeabi-v7a/libengine_core.a +0 -0
  63. package/native/core/build/android/x86/libengine_core.a +0 -0
  64. package/native/core/build/android/x86_64/libengine_core.a +0 -0
  65. package/native/core/include/engine_core.h +9 -0
  66. package/native/cpp/NayanEngineModule.cpp +171 -0
  67. package/native/cpp/NayanEngineModule.h +30 -0
  68. package/native/cpp/generated/NayanEngineSpecJSI.h +105 -0
  69. package/native/ios/NayanEngineSurface.mm +30 -1
  70. package/package.json +2 -2
  71. package/src/native/spec/NativeNayanEngine.ts +39 -0
package/lib/web/index.js CHANGED
@@ -1066,7 +1066,7 @@ function __wbg_get_imports() {
1066
1066
  const a = state0.a;
1067
1067
  state0.a = 0;
1068
1068
  try {
1069
- return __wasm_bindgen_func_elem_9776(a, state0.b, arg0, arg1);
1069
+ return __wasm_bindgen_func_elem_9779(a, state0.b, arg0, arg1);
1070
1070
  } finally {
1071
1071
  state0.a = a;
1072
1072
  }
@@ -2069,23 +2069,23 @@ function __wbg_get_imports() {
2069
2069
  }, arguments);
2070
2070
  },
2071
2071
  __wbindgen_generic_0000000000000001: function(arg0, arg1) {
2072
- const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_9774);
2072
+ const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_9777);
2073
2073
  return addHeapObject(ret);
2074
2074
  },
2075
2075
  __wbindgen_generic_0000000000000002: function(arg0, arg1) {
2076
- const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2411);
2076
+ const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2414);
2077
2077
  return addHeapObject(ret);
2078
2078
  },
2079
2079
  __wbindgen_generic_0000000000000003: function(arg0, arg1) {
2080
- const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2412);
2080
+ const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2415);
2081
2081
  return addHeapObject(ret);
2082
2082
  },
2083
2083
  __wbindgen_generic_0000000000000004: function(arg0, arg1) {
2084
- const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2411_9);
2084
+ const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2414_9);
2085
2085
  return addHeapObject(ret);
2086
2086
  },
2087
2087
  __wbindgen_generic_0000000000000005: function(arg0, arg1) {
2088
- const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2411_10);
2088
+ const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2414_10);
2089
2089
  return addHeapObject(ret);
2090
2090
  },
2091
2091
  __wbindgen_generic_0000000000000006: function(arg0) {
@@ -2138,16 +2138,16 @@ function __wbg_get_imports() {
2138
2138
  "./snippets/engine-core-64626f8edd8232bb/inline0.js": exports_inline0
2139
2139
  };
2140
2140
  }
2141
- function __wasm_bindgen_func_elem_2412(arg0, arg1, arg2) {
2142
- wasm.__wasm_bindgen_func_elem_2412(arg0, arg1, addHeapObject(arg2));
2141
+ function __wasm_bindgen_func_elem_2415(arg0, arg1, arg2) {
2142
+ wasm.__wasm_bindgen_func_elem_2415(arg0, arg1, addHeapObject(arg2));
2143
2143
  }
2144
- function __wasm_bindgen_func_elem_9776(arg0, arg1, arg2, arg3) {
2145
- wasm.__wasm_bindgen_func_elem_9776(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
2144
+ function __wasm_bindgen_func_elem_9779(arg0, arg1, arg2, arg3) {
2145
+ wasm.__wasm_bindgen_func_elem_9779(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
2146
2146
  }
2147
- function __wasm_bindgen_func_elem_9774(arg0, arg1, arg2) {
2147
+ function __wasm_bindgen_func_elem_9777(arg0, arg1, arg2) {
2148
2148
  try {
2149
2149
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
2150
- wasm.__wasm_bindgen_func_elem_9774(retptr, arg0, arg1, addHeapObject(arg2));
2150
+ wasm.__wasm_bindgen_func_elem_9777(retptr, arg0, arg1, addHeapObject(arg2));
2151
2151
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
2152
2152
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
2153
2153
  if (r1) {
@@ -2157,10 +2157,10 @@ function __wasm_bindgen_func_elem_9774(arg0, arg1, arg2) {
2157
2157
  wasm.__wbindgen_add_to_stack_pointer(16);
2158
2158
  }
2159
2159
  }
2160
- function __wasm_bindgen_func_elem_2411(arg0, arg1, arg2) {
2160
+ function __wasm_bindgen_func_elem_2414(arg0, arg1, arg2) {
2161
2161
  try {
2162
2162
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
2163
- wasm.__wasm_bindgen_func_elem_2411(retptr, arg0, arg1, addHeapObject(arg2));
2163
+ wasm.__wasm_bindgen_func_elem_2414(retptr, arg0, arg1, addHeapObject(arg2));
2164
2164
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
2165
2165
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
2166
2166
  if (r1) {
@@ -2170,10 +2170,10 @@ function __wasm_bindgen_func_elem_2411(arg0, arg1, arg2) {
2170
2170
  wasm.__wbindgen_add_to_stack_pointer(16);
2171
2171
  }
2172
2172
  }
2173
- function __wasm_bindgen_func_elem_2411_9(arg0, arg1, arg2) {
2173
+ function __wasm_bindgen_func_elem_2414_9(arg0, arg1, arg2) {
2174
2174
  try {
2175
2175
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
2176
- wasm.__wasm_bindgen_func_elem_2411_9(retptr, arg0, arg1, addHeapObject(arg2));
2176
+ wasm.__wasm_bindgen_func_elem_2414_9(retptr, arg0, arg1, addHeapObject(arg2));
2177
2177
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
2178
2178
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
2179
2179
  if (r1) {
@@ -2183,10 +2183,10 @@ function __wasm_bindgen_func_elem_2411_9(arg0, arg1, arg2) {
2183
2183
  wasm.__wbindgen_add_to_stack_pointer(16);
2184
2184
  }
2185
2185
  }
2186
- function __wasm_bindgen_func_elem_2411_10(arg0, arg1, arg2) {
2186
+ function __wasm_bindgen_func_elem_2414_10(arg0, arg1, arg2) {
2187
2187
  try {
2188
2188
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
2189
- wasm.__wasm_bindgen_func_elem_2411_10(retptr, arg0, arg1, addHeapObject(arg2));
2189
+ wasm.__wasm_bindgen_func_elem_2414_10(retptr, arg0, arg1, addHeapObject(arg2));
2190
2190
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
2191
2191
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
2192
2192
  if (r1) {
@@ -2974,6 +2974,7 @@ function createBrowserEngine(core) {
2974
2974
  fn(name, id, p);
2975
2975
  return Array.from(float ? new Float32Array(memory(), p, count) : new Uint32Array(memory(), p, count));
2976
2976
  };
2977
+ const settle = (work) => new Promise((resolve) => resolve(work()));
2977
2978
  const engine = {
2978
2979
  createWorld(capacity) {
2979
2980
  if (!Number.isInteger(capacity) || capacity < 1 || capacity > 1e6)
@@ -3172,6 +3173,19 @@ function createBrowserEngine(core) {
3172
3173
  const [x, y, z] = dimensions("model_size", mesh, 3, true);
3173
3174
  return [x, y, z];
3174
3175
  },
3176
+ modelLoadAsync(value, center, fit) {
3177
+ return settle(() => engine.modelLoad(value, center, fit));
3178
+ },
3179
+ textureLoadAsync(value) {
3180
+ return settle(() => engine.textureLoad(value));
3181
+ },
3182
+ fontLoadAsync(value, depth, chars, weight) {
3183
+ return settle(() => engine.fontLoad(value, depth, chars, weight));
3184
+ },
3185
+ audioLoadAsync(value) {
3186
+ return settle(() => engine.audioLoad(value));
3187
+ },
3188
+ renderModel: (mesh, texture, shine) => typeof core.engine_render_model === "function" && fn("render_model", mesh, texture, shine) !== 0,
3175
3189
  modelTexture: (mesh) => fn("model_texture", mesh),
3176
3190
  meshAlias: (mesh) => fn("mesh_alias", mesh),
3177
3191
  meshCreate(vertices, indices) {
@@ -3386,137 +3400,6 @@ function prepareEngine2() {
3386
3400
  return prepareEngine();
3387
3401
  }
3388
3402
 
3389
- // src/types.ts
3390
- var MAX_MESHES = 4096;
3391
- var SHAPE_MESH = {
3392
- cube: 0,
3393
- sphere: 1,
3394
- plane: 2,
3395
- cylinder: 3,
3396
- cone: 4,
3397
- capsule: 5,
3398
- torus: 6,
3399
- roundedBox: 7,
3400
- puff: 8,
3401
- flame: 9,
3402
- none: 15
3403
- };
3404
-
3405
- // src/render/meshes.ts
3406
- var VERTEX_FLOATS = 11;
3407
-
3408
- class Builder {
3409
- vertices = [];
3410
- indices = [];
3411
- vertex(p, n, u, v) {
3412
- this.vertices.push(p[0], p[1], p[2], n[0], n[1], n[2], 1, 1, 1, u, v);
3413
- return this.vertices.length / VERTEX_FLOATS - 1;
3414
- }
3415
- quad(a, b, c, d) {
3416
- this.indices.push(a, b, c, a, c, d);
3417
- }
3418
- grid(rows, cols, at) {
3419
- const first = this.vertices.length / VERTEX_FLOATS;
3420
- for (let i = 0;i <= rows; i++) {
3421
- for (let j = 0;j <= cols; j++) {
3422
- const [p, n] = at(j / cols, i / rows);
3423
- this.vertex(p, n, j / cols, i / rows);
3424
- }
3425
- }
3426
- for (let i = 0;i < rows; i++) {
3427
- for (let j = 0;j < cols; j++) {
3428
- const a = first + i * (cols + 1) + j;
3429
- const b = a + cols + 1;
3430
- this.indices.push(a, b, a + 1, a + 1, b, b + 1);
3431
- }
3432
- }
3433
- }
3434
- disc(y, radius, facing, segments) {
3435
- const center = this.vertex([0, y, 0], [0, facing, 0], 0.5, 0.5);
3436
- const first = center + 1;
3437
- for (let j = 0;j <= segments; j++) {
3438
- const t = j / segments * TAU;
3439
- const x = Math.cos(t);
3440
- const z = Math.sin(t);
3441
- this.vertex([x * radius, y, z * radius], [0, facing, 0], 0.5 + x / 2, 0.5 + z / 2);
3442
- }
3443
- for (let j = 0;j < segments; j++) {
3444
- if (facing === 1)
3445
- this.indices.push(center, first + j + 1, first + j);
3446
- else
3447
- this.indices.push(center, first + j, first + j + 1);
3448
- }
3449
- }
3450
- }
3451
- var TAU = Math.PI * 2;
3452
- var registered = new Map;
3453
- var idLimit = 16;
3454
- function registerMesh(mesh, info, uploadGeometry = true) {
3455
- registered.set(mesh, info);
3456
- idLimit = Math.max(idLimit, mesh + 1);
3457
- if (uploadGeometry)
3458
- upload(mesh, info);
3459
- }
3460
- var getMeshIdLimit = () => idLimit;
3461
- function exact(data) {
3462
- const { buffer, byteOffset, byteLength } = data;
3463
- if (buffer instanceof ArrayBuffer && byteOffset === 0 && byteLength === buffer.byteLength)
3464
- return buffer;
3465
- return data.slice().buffer;
3466
- }
3467
- function upload(mesh, info) {
3468
- const { vertices, indices } = info.geometry;
3469
- NativeNayanEngine_web_default?.renderMesh(mesh, exact(vertices), exact(indices), info.texture ?? -1, info.shine ?? 0);
3470
- }
3471
- var builtInsUploaded = false;
3472
- function uploadBuiltInMeshes() {
3473
- if (builtInsUploaded)
3474
- return;
3475
- NativeNayanEngine_web_default?.generateBuiltinMeshes();
3476
- builtInsUploaded = true;
3477
- }
3478
- function warmUpEngine() {
3479
- const pending = prepareEngine2();
3480
- const warm = () => {
3481
- const native = NativeNayanEngine_web_default;
3482
- if (!native)
3483
- return;
3484
- uploadBuiltInMeshes();
3485
- if (typeof native.renderWarmUp === "function")
3486
- native.renderWarmUp();
3487
- };
3488
- if (pending)
3489
- return pending.then(warm);
3490
- warm();
3491
- return Promise.resolve();
3492
- }
3493
- function meshInfo(mesh) {
3494
- const info = registered.get(mesh);
3495
- if (info || mesh === SHAPE_MESH.none)
3496
- return info;
3497
- const shape = Object.keys(SHAPE_MESH).find((s) => SHAPE_MESH[s] === mesh);
3498
- if (!shape || shape === "none")
3499
- return;
3500
- const native = NativeNayanEngine_web_default;
3501
- if (!native)
3502
- return;
3503
- let vertices = new Float32Array(native.renderMeshVertices(mesh));
3504
- if (!vertices.length && !builtInsUploaded && typeof native.generateBuiltinMeshes === "function") {
3505
- try {
3506
- uploadBuiltInMeshes();
3507
- } catch {
3508
- return;
3509
- }
3510
- vertices = new Float32Array(native.renderMeshVertices(mesh));
3511
- }
3512
- const indices = new Uint32Array(native.renderMeshIndices(mesh));
3513
- if (!vertices.length)
3514
- return;
3515
- const made = { geometry: { vertices, indices } };
3516
- registered.set(mesh, made);
3517
- return made;
3518
- }
3519
-
3520
3403
  // src/assets/CacheGeneration.ts
3521
3404
  var generation = 0;
3522
3405
  var cacheGeneration = () => generation;
@@ -3526,6 +3409,7 @@ var advanceCacheGeneration = () => ++generation;
3526
3409
  function proceduralStorage(revision) {
3527
3410
  const native = NativeNayanEngine_web_default;
3528
3411
  return {
3412
+ verified: true,
3529
3413
  read(key) {
3530
3414
  if (typeof native?.cacheRead !== "function")
3531
3415
  return null;
@@ -3562,6 +3446,7 @@ function proceduralStorage2(revision) {
3562
3446
  if (!storage)
3563
3447
  return;
3564
3448
  return {
3449
+ verified: storage.verified,
3565
3450
  read: storage.read,
3566
3451
  write(key, bytes) {
3567
3452
  storage.write(key, bytes);
@@ -3696,7 +3581,7 @@ class AssetLoader {
3696
3581
  throw new Error("the engine core is not linked into this build");
3697
3582
  const { bytes } = await loadAssetBytes(source);
3698
3583
  try {
3699
- return decode(NativeNayanEngine_web_default, bytes);
3584
+ return await decode(NativeNayanEngine_web_default, bytes);
3700
3585
  } catch (cause) {
3701
3586
  forgetAssetBytes(uri);
3702
3587
  throw cause;
@@ -3716,6 +3601,137 @@ class AssetLoader {
3716
3601
  }
3717
3602
  }
3718
3603
 
3604
+ // src/types.ts
3605
+ var MAX_MESHES = 4096;
3606
+ var SHAPE_MESH = {
3607
+ cube: 0,
3608
+ sphere: 1,
3609
+ plane: 2,
3610
+ cylinder: 3,
3611
+ cone: 4,
3612
+ capsule: 5,
3613
+ torus: 6,
3614
+ roundedBox: 7,
3615
+ puff: 8,
3616
+ flame: 9,
3617
+ none: 15
3618
+ };
3619
+
3620
+ // src/render/meshes.ts
3621
+ var VERTEX_FLOATS = 11;
3622
+
3623
+ class Builder {
3624
+ vertices = [];
3625
+ indices = [];
3626
+ vertex(p, n, u, v) {
3627
+ this.vertices.push(p[0], p[1], p[2], n[0], n[1], n[2], 1, 1, 1, u, v);
3628
+ return this.vertices.length / VERTEX_FLOATS - 1;
3629
+ }
3630
+ quad(a, b, c, d) {
3631
+ this.indices.push(a, b, c, a, c, d);
3632
+ }
3633
+ grid(rows, cols, at) {
3634
+ const first = this.vertices.length / VERTEX_FLOATS;
3635
+ for (let i = 0;i <= rows; i++) {
3636
+ for (let j = 0;j <= cols; j++) {
3637
+ const [p, n] = at(j / cols, i / rows);
3638
+ this.vertex(p, n, j / cols, i / rows);
3639
+ }
3640
+ }
3641
+ for (let i = 0;i < rows; i++) {
3642
+ for (let j = 0;j < cols; j++) {
3643
+ const a = first + i * (cols + 1) + j;
3644
+ const b = a + cols + 1;
3645
+ this.indices.push(a, b, a + 1, a + 1, b, b + 1);
3646
+ }
3647
+ }
3648
+ }
3649
+ disc(y, radius, facing, segments) {
3650
+ const center = this.vertex([0, y, 0], [0, facing, 0], 0.5, 0.5);
3651
+ const first = center + 1;
3652
+ for (let j = 0;j <= segments; j++) {
3653
+ const t = j / segments * TAU;
3654
+ const x = Math.cos(t);
3655
+ const z = Math.sin(t);
3656
+ this.vertex([x * radius, y, z * radius], [0, facing, 0], 0.5 + x / 2, 0.5 + z / 2);
3657
+ }
3658
+ for (let j = 0;j < segments; j++) {
3659
+ if (facing === 1)
3660
+ this.indices.push(center, first + j + 1, first + j);
3661
+ else
3662
+ this.indices.push(center, first + j, first + j + 1);
3663
+ }
3664
+ }
3665
+ }
3666
+ var TAU = Math.PI * 2;
3667
+ var registered = new Map;
3668
+ var idLimit = 16;
3669
+ function registerMesh(mesh, info, uploadGeometry = true) {
3670
+ registered.set(mesh, info);
3671
+ idLimit = Math.max(idLimit, mesh + 1);
3672
+ if (uploadGeometry)
3673
+ upload(mesh, info);
3674
+ }
3675
+ var getMeshIdLimit = () => idLimit;
3676
+ function exact(data) {
3677
+ const { buffer, byteOffset, byteLength } = data;
3678
+ if (buffer instanceof ArrayBuffer && byteOffset === 0 && byteLength === buffer.byteLength)
3679
+ return buffer;
3680
+ return data.slice().buffer;
3681
+ }
3682
+ function upload(mesh, info) {
3683
+ const { vertices, indices } = info.geometry;
3684
+ NativeNayanEngine_web_default?.renderMesh(mesh, exact(vertices), exact(indices), info.texture ?? -1, info.shine ?? 0);
3685
+ }
3686
+ var builtInsUploaded = false;
3687
+ function uploadBuiltInMeshes() {
3688
+ if (builtInsUploaded)
3689
+ return;
3690
+ NativeNayanEngine_web_default?.generateBuiltinMeshes();
3691
+ builtInsUploaded = true;
3692
+ }
3693
+ function warmUpEngine() {
3694
+ const pending = prepareEngine2();
3695
+ const warm = () => {
3696
+ const native = NativeNayanEngine_web_default;
3697
+ if (!native)
3698
+ return;
3699
+ uploadBuiltInMeshes();
3700
+ if (typeof native.renderWarmUp === "function")
3701
+ native.renderWarmUp();
3702
+ };
3703
+ if (pending)
3704
+ return pending.then(warm);
3705
+ warm();
3706
+ return Promise.resolve();
3707
+ }
3708
+ function meshInfo(mesh) {
3709
+ const info = registered.get(mesh);
3710
+ if (info || mesh === SHAPE_MESH.none)
3711
+ return info;
3712
+ const shape = Object.keys(SHAPE_MESH).find((s) => SHAPE_MESH[s] === mesh);
3713
+ if (!shape || shape === "none")
3714
+ return;
3715
+ const native = NativeNayanEngine_web_default;
3716
+ if (!native)
3717
+ return;
3718
+ let vertices = new Float32Array(native.renderMeshVertices(mesh));
3719
+ if (!vertices.length && !builtInsUploaded && typeof native.generateBuiltinMeshes === "function") {
3720
+ try {
3721
+ uploadBuiltInMeshes();
3722
+ } catch {
3723
+ return;
3724
+ }
3725
+ vertices = new Float32Array(native.renderMeshVertices(mesh));
3726
+ }
3727
+ const indices = new Uint32Array(native.renderMeshIndices(mesh));
3728
+ if (!vertices.length)
3729
+ return;
3730
+ const made = { geometry: { vertices, indices } };
3731
+ registered.set(mesh, made);
3732
+ return made;
3733
+ }
3734
+
3719
3735
  // src/assets/model.ts
3720
3736
  var loader = new AssetLoader("loadModel");
3721
3737
  function nativeGeometry(mesh) {
@@ -3724,6 +3740,18 @@ function nativeGeometry(mesh) {
3724
3740
  indices: new Uint32Array(NativeNayanEngine_web_default.modelIndices(mesh))
3725
3741
  };
3726
3742
  }
3743
+ function registerCoreMesh(mesh, info = {}, geometry = () => nativeGeometry(mesh)) {
3744
+ const native = NativeNayanEngine_web_default;
3745
+ let cached;
3746
+ const inRust = typeof native.renderModel === "function" && native.renderModel(mesh, info.texture ?? -1, info.shine ?? 0);
3747
+ registerMesh(mesh, {
3748
+ get geometry() {
3749
+ return cached ??= geometry();
3750
+ },
3751
+ texture: info.texture,
3752
+ shine: info.shine
3753
+ }, !inRust);
3754
+ }
3727
3755
  var materials = new Map;
3728
3756
  function registerModelMesh(mesh, shine) {
3729
3757
  const info = meshInfo(mesh);
@@ -3736,11 +3764,7 @@ function registerModelMesh(mesh, shine) {
3736
3764
  const id = info ? NativeNayanEngine_web_default.meshAlias(mesh) : mesh;
3737
3765
  if (id < 0)
3738
3766
  throw new Error(NativeNayanEngine_web_default.loadError() || "couldn't register a material variant");
3739
- registerMesh(id, {
3740
- geometry: info?.geometry ?? nativeGeometry(id),
3741
- texture: info?.texture,
3742
- shine
3743
- });
3767
+ registerCoreMesh(id, { texture: info?.texture, shine }, info ? () => info.geometry : undefined);
3744
3768
  materials.set(key, id);
3745
3769
  return id;
3746
3770
  }
@@ -3749,25 +3773,25 @@ function loadModel(source, options = {}) {
3749
3773
  const fit = options.fit ?? 0;
3750
3774
  const shine = Math.max(0, Math.min(1, options.shine ?? 0));
3751
3775
  return loader.load(source, (native, bytes) => {
3752
- const mesh = native.modelLoad(bytes, center, fit);
3753
- if (mesh < 0)
3754
- throw new Error(native.loadError() || "not a glTF model");
3755
- const texture = native.modelTexture(mesh);
3756
- registerMesh(mesh, {
3757
- geometry: nativeGeometry(mesh),
3758
- texture: texture >= 0 ? texture : undefined,
3759
- shine
3760
- });
3761
- const [x, y, z] = native.modelSize(mesh);
3762
- const rig = JSON.parse(native.modelRig(mesh));
3763
- return {
3764
- animations: rig.clips,
3765
- jointCount: rig.joints,
3766
- id: mesh,
3767
- size: [x, y, z]
3768
- };
3776
+ if (typeof native.modelLoadAsync === "function")
3777
+ return native.modelLoadAsync(bytes, center, fit).then((mesh) => finishModel(native, mesh, shine));
3778
+ return finishModel(native, native.modelLoad(bytes, center, fit), shine);
3769
3779
  }, [center, fit, shine]);
3770
3780
  }
3781
+ function finishModel(native, mesh, shine) {
3782
+ if (mesh < 0)
3783
+ throw new Error(native.loadError() || "not a glTF model");
3784
+ const texture = native.modelTexture(mesh);
3785
+ registerCoreMesh(mesh, { texture: texture >= 0 ? texture : undefined, shine });
3786
+ const [x, y, z] = native.modelSize(mesh);
3787
+ const rig = JSON.parse(native.modelRig(mesh));
3788
+ return {
3789
+ animations: rig.clips,
3790
+ jointCount: rig.joints,
3791
+ id: mesh,
3792
+ size: [x, y, z]
3793
+ };
3794
+ }
3771
3795
 
3772
3796
  // src/assets/font.ts
3773
3797
  var DEFAULT_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~×÷±−°·•–—…‘’“”";
@@ -3783,7 +3807,11 @@ function loadFont(source, options = {}) {
3783
3807
  const chars = [...new Set(`${options.chars ?? DEFAULT_CHARS} ?`.normalize("NFC"))].filter((ch) => !isInvisible(ch)).join("");
3784
3808
  const weight = options.weight ?? 0;
3785
3809
  const pending = loader2.load(source, (native, bytes) => {
3786
- const id = native.fontLoad(bytes, depth, chars, weight);
3810
+ if (typeof native.fontLoadAsync === "function")
3811
+ return native.fontLoadAsync(bytes, depth, chars, weight).then((id) => finish(native, bytes, id));
3812
+ return finish(native, bytes, native.fontLoad(bytes, depth, chars, weight));
3813
+ }, [depth, weight]);
3814
+ function finish(native, bytes, id) {
3787
3815
  if (id < 0)
3788
3816
  throw new Error(native.loadError() || "not a TrueType / OpenType font");
3789
3817
  const glyphs = new Map;
@@ -3793,7 +3821,7 @@ function loadFont(source, options = {}) {
3793
3821
  for (let i = 0;i + 3 <= data.length - 1; i += 3) {
3794
3822
  const mesh = data[i + 1];
3795
3823
  if (mesh >= 0 && !registered.has(mesh)) {
3796
- registerMesh(mesh, { geometry: nativeGeometry(mesh) });
3824
+ registerCoreMesh(mesh);
3797
3825
  registered.add(mesh);
3798
3826
  }
3799
3827
  glyphs.set(String.fromCodePoint(data[i]), {
@@ -3827,7 +3855,7 @@ function loadFont(source, options = {}) {
3827
3855
  }
3828
3856
  });
3829
3857
  return font;
3830
- }, [depth, weight]);
3858
+ }
3831
3859
  return pending.then((font) => {
3832
3860
  extend.get(font)?.(chars);
3833
3861
  return font;
@@ -3882,11 +3910,13 @@ function layoutText(font, text, align, options = {}) {
3882
3910
  var loader3 = new AssetLoader("loadTexture");
3883
3911
  function loadTexture(source) {
3884
3912
  return loader3.load(source, (native, bytes) => {
3885
- const id = native.textureLoad(bytes);
3886
- if (id < 0)
3887
- throw new Error(native.loadError() || "not a PNG or JPEG image");
3888
- const [width, height] = native.textureSize(id);
3889
- return { id, width, height };
3913
+ const finish = (id) => {
3914
+ if (id < 0)
3915
+ throw new Error(native.loadError() || "not a PNG or JPEG image");
3916
+ const [width, height] = native.textureSize(id);
3917
+ return { id, width, height };
3918
+ };
3919
+ return typeof native.textureLoadAsync === "function" ? native.textureLoadAsync(bytes).then(finish) : finish(native.textureLoad(bytes));
3890
3920
  });
3891
3921
  }
3892
3922
  var variants = new Map;
@@ -3900,16 +3930,76 @@ function texturedMesh(mesh, texture) {
3900
3930
  id = NativeNayanEngine_web_default.meshAlias(mesh);
3901
3931
  if (id < 0)
3902
3932
  throw new Error(`texture: ${NativeNayanEngine_web_default.loadError()}`);
3903
- registerMesh(id, {
3904
- geometry: base.geometry,
3905
- texture: texture.id,
3906
- shine: base.shine
3907
- });
3933
+ registerCoreMesh(id, { texture: texture.id, shine: base.shine }, () => base.geometry);
3908
3934
  variants.set(key, id);
3909
3935
  }
3910
3936
  return id;
3911
3937
  }
3912
3938
 
3939
+ // src/world/color.ts
3940
+ var HEX = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
3941
+ var RGB = /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+%?)\s*)?\)$/i;
3942
+ var NAMED = {
3943
+ transparent: [0, 0, 0, 0],
3944
+ black: [0, 0, 0, 1],
3945
+ white: [1, 1, 1, 1],
3946
+ red: [1, 0, 0, 1],
3947
+ green: [0, 128 / 255, 0, 1],
3948
+ blue: [0, 0, 1, 1],
3949
+ yellow: [1, 1, 0, 1],
3950
+ orange: [1, 165 / 255, 0, 1],
3951
+ purple: [128 / 255, 0, 128 / 255, 1],
3952
+ gray: [128 / 255, 128 / 255, 128 / 255, 1],
3953
+ grey: [128 / 255, 128 / 255, 128 / 255, 1]
3954
+ };
3955
+ var parsed = new Map;
3956
+ var PARSED_LIMIT = 1024;
3957
+ function parse(css) {
3958
+ const text = css.trim();
3959
+ const named = NAMED[text.toLowerCase()];
3960
+ if (named)
3961
+ return named;
3962
+ const hex = HEX.exec(text);
3963
+ if (hex) {
3964
+ let digits = hex[1];
3965
+ if (digits.length <= 4)
3966
+ digits = [...digits].map((d) => d + d).join("");
3967
+ const n = (i) => parseInt(digits.slice(i, i + 2), 16) / 255;
3968
+ return [n(0), n(2), n(4), digits.length === 8 ? n(6) : 1];
3969
+ }
3970
+ const rgb = RGB.exec(text);
3971
+ if (rgb) {
3972
+ const alpha = rgb[4] === undefined ? 1 : rgb[4].endsWith("%") ? parseFloat(rgb[4]) / 100 : Number(rgb[4]);
3973
+ return [Number(rgb[1]) / 255, Number(rgb[2]) / 255, Number(rgb[3]) / 255, alpha];
3974
+ }
3975
+ throw new Error(`@nayan-ui/engine: "${css}" is not a color (use '#RRGGBB', '#RGB', '#RRGGBBAA', 'rgb(…)', 'rgba(…)' or a 0..1 tuple)`);
3976
+ }
3977
+ function toColor(color, alpha = 1) {
3978
+ let c;
3979
+ if (typeof color === "string") {
3980
+ let hit = parsed.get(color);
3981
+ if (!hit) {
3982
+ hit = parse(color);
3983
+ if (parsed.size >= PARSED_LIMIT)
3984
+ parsed.clear();
3985
+ parsed.set(color, hit);
3986
+ }
3987
+ c = hit;
3988
+ } else
3989
+ c = color;
3990
+ return [c[0], c[1], c[2], (c[3] ?? 1) * alpha];
3991
+ }
3992
+ function resolveColor(color) {
3993
+ return typeof color === "string" ? toColor(color) : color;
3994
+ }
3995
+ function resolveColors(colors) {
3996
+ if (typeof colors === "string")
3997
+ return [toColor(colors)];
3998
+ if (typeof colors[0] === "number")
3999
+ return [colors];
4000
+ return colors.map(resolveColor);
4001
+ }
4002
+
3913
4003
  // src/world/desc.ts
3914
4004
  var DESC_LEN = 64;
3915
4005
  var FLAG = {
@@ -3998,10 +4088,11 @@ function encode(d, o, mesh) {
3998
4088
  }
3999
4089
  if (o.color) {
4000
4090
  flags |= FLAG.COLOR;
4001
- d[SLOT.COLOR] = o.color[0];
4002
- d[SLOT.COLOR + 1] = o.color[1];
4003
- d[SLOT.COLOR + 2] = o.color[2];
4004
- d[SLOT.COLOR + 3] = o.color[3] ?? 1;
4091
+ const c = resolveColor(o.color);
4092
+ d[SLOT.COLOR] = c[0];
4093
+ d[SLOT.COLOR + 1] = c[1];
4094
+ d[SLOT.COLOR + 2] = c[2];
4095
+ d[SLOT.COLOR + 3] = c[3] ?? 1;
4005
4096
  }
4006
4097
  if (o.velocity) {
4007
4098
  flags |= FLAG.VELOCITY;
@@ -4104,6 +4195,7 @@ var FLAG2 = {
4104
4195
  class Scoreboard {
4105
4196
  world;
4106
4197
  handlers = {};
4198
+ listeners = new Set;
4107
4199
  state;
4108
4200
  constructor(world) {
4109
4201
  this.world = world;
@@ -4171,12 +4263,23 @@ class Scoreboard {
4171
4263
  this.stop();
4172
4264
  }
4173
4265
  }
4266
+ listen(handlers) {
4267
+ this.listeners.add(handlers);
4268
+ return () => {
4269
+ this.listeners.delete(handlers);
4270
+ };
4271
+ }
4174
4272
  dispatch() {
4175
4273
  const flags = this.state[S.flags];
4176
4274
  if (flags === 0)
4177
4275
  return;
4178
4276
  this.state[S.flags] = 0;
4179
- const h = this.handlers;
4277
+ this.run(this.handlers, flags);
4278
+ if (this.listeners.size)
4279
+ for (const h of [...this.listeners])
4280
+ this.run(h, flags);
4281
+ }
4282
+ run(h, flags) {
4180
4283
  if (flags & FLAG2.score)
4181
4284
  h.onScore?.(this.score);
4182
4285
  if (flags & FLAG2.lives)
@@ -4275,7 +4378,8 @@ class TransformBatch {
4275
4378
  if (color)
4276
4379
  this.writeColor(offset, color);
4277
4380
  }
4278
- writeColor(offset, color) {
4381
+ writeColor(offset, input) {
4382
+ const color = resolveColor(input);
4279
4383
  this.data[offset + 12] = color[0];
4280
4384
  this.data[offset + 13] = color[1];
4281
4385
  this.data[offset + 14] = color[2];
@@ -4358,6 +4462,7 @@ class World {
4358
4462
  gravityValue = [0, -9.81, 0];
4359
4463
  boundsValue = null;
4360
4464
  listenerValue = null;
4465
+ updates = 0;
4361
4466
  constructor(capacity) {
4362
4467
  this.capacity = capacity;
4363
4468
  if (!NativeNayanEngine_web_default) {
@@ -4405,7 +4510,7 @@ class World {
4405
4510
  letterSpacing: options.letterSpacing,
4406
4511
  tabular: options.tabular
4407
4512
  },
4408
- color: options.color ?? [1, 1, 1],
4513
+ color: resolveColor(options.color ?? WHITE),
4409
4514
  glyphs: []
4410
4515
  };
4411
4516
  try {
@@ -4434,7 +4539,7 @@ class World {
4434
4539
  tabular: options.tabular ?? text.layout.tabular
4435
4540
  };
4436
4541
  if (options.color)
4437
- text.color = options.color;
4542
+ text.color = resolveColor(options.color);
4438
4543
  if (rebuild)
4439
4544
  this.buildText(e, text);
4440
4545
  else if (options.color) {
@@ -4604,10 +4709,11 @@ class World {
4604
4709
  }
4605
4710
  if (k.color) {
4606
4711
  flags |= KEY_FLAG.color;
4607
- a[o + 12] = k.color[0];
4608
- a[o + 13] = k.color[1];
4609
- a[o + 14] = k.color[2];
4610
- a[o + 15] = k.color[3] ?? 1;
4712
+ const c = resolveColor(k.color);
4713
+ a[o + 12] = c[0];
4714
+ a[o + 13] = c[1];
4715
+ a[o + 14] = c[2];
4716
+ a[o + 15] = c[3] ?? 1;
4611
4717
  }
4612
4718
  if (k.moveBy) {
4613
4719
  flags |= KEY_FLAG.moveBy;
@@ -4631,9 +4737,8 @@ class World {
4631
4737
  }
4632
4738
  burst(options) {
4633
4739
  const b = this.burstDesc;
4634
- const color = options.color ?? WHITE;
4635
- const single = typeof color[0] === "number";
4636
- const colorCount = single ? 1 : Math.max(1, Math.min(4, color.length));
4740
+ const colors = resolveColors(options.color ?? WHITE);
4741
+ const colorCount = Math.max(1, Math.min(4, colors.length));
4637
4742
  b.set(options.position, 0);
4638
4743
  b.set(options.direction ?? UP, 3);
4639
4744
  b[6] = options.spread ?? Math.PI;
@@ -4645,12 +4750,12 @@ class World {
4645
4750
  b[12] = options.gravity ?? -9.81;
4646
4751
  b[13] = colorCount;
4647
4752
  for (let k = 0;k < colorCount; k++)
4648
- writeRgba(b, 14 + k * 4, single ? color : color[k]);
4753
+ writeRgba(b, 14 + k * 4, colors[k]);
4649
4754
  b[30] = options.endSize ?? 1;
4650
4755
  const end = options.endColor;
4651
4756
  b[31] = end ? 1 : 0;
4652
4757
  if (end)
4653
- writeRgba(b, 32, end);
4758
+ writeRgba(b, 32, resolveColor(end));
4654
4759
  return this.native.burst(this.id, b.buffer);
4655
4760
  }
4656
4761
  after(seconds, fn) {
@@ -4693,6 +4798,9 @@ class World {
4693
4798
  get scoreboard() {
4694
4799
  return this.scoreboardValue ??= new Scoreboard(this.id);
4695
4800
  }
4801
+ get updateCount() {
4802
+ return this.updates;
4803
+ }
4696
4804
  get timerCount() {
4697
4805
  return this.timers.size + this.fallbackTimers.length;
4698
4806
  }
@@ -4834,6 +4942,8 @@ class World {
4834
4942
  this.native.setListener(this.id, e ?? -1);
4835
4943
  }
4836
4944
  update(dt) {
4945
+ this.updates++;
4946
+ this.transformsValue?.flush();
4837
4947
  this.native.update(this.id, dt);
4838
4948
  this.resolveFinished();
4839
4949
  this.runFallbackTimers(dt);
@@ -4932,10 +5042,16 @@ var quat = {
4932
5042
  }
4933
5043
  };
4934
5044
  // src/world/effects.ts
4935
- var rgba = (c) => [c[0], c[1], c[2], c[3] ?? 1];
4936
- var list = (colors) => typeof colors[0] === "number" ? [rgba(colors)] : colors.map(rgba);
5045
+ var rgba = (input) => {
5046
+ const c = resolveColor(input);
5047
+ return [c[0], c[1], c[2], c[3] ?? 1];
5048
+ };
5049
+ var list = (colors) => resolveColors(colors).map(rgba);
4937
5050
  var part = (count, share) => Math.max(1, Math.round(count * share));
4938
- var fade = (c) => [c[0], c[1], c[2], 0];
5051
+ var fade = (input) => {
5052
+ const c = resolveColor(input);
5053
+ return [c[0], c[1], c[2], 0];
5054
+ };
4939
5055
  function mean(colors) {
4940
5056
  const sum = [0, 0, 0, 0];
4941
5057
  for (const c of colors)
@@ -4943,11 +5059,15 @@ function mean(colors) {
4943
5059
  sum[i] += c[i];
4944
5060
  return sum.map((v) => v / colors.length);
4945
5061
  }
4946
- function hot(c, peak = 2.2, alpha = c[3] ?? 1) {
5062
+ function hot(input, peak = 2.2, alpha) {
5063
+ const c = resolveColor(input);
5064
+ alpha ??= c[3] ?? 1;
4947
5065
  const k = peak / Math.max(c[0], c[1], c[2], 0.25);
4948
5066
  return [c[0] * k, c[1] * k, c[2] * k, alpha];
4949
5067
  }
4950
- function pale(c, t, alpha = c[3] ?? 1) {
5068
+ function pale(input, t, alpha) {
5069
+ const c = resolveColor(input);
5070
+ alpha ??= c[3] ?? 1;
4951
5071
  return [c[0] + (1 - c[0]) * t, c[1] + (1 - c[1]) * t, c[2] + (1 - c[2]) * t, alpha];
4952
5072
  }
4953
5073
  function aim(e) {
@@ -4971,7 +5091,7 @@ function glowFlash(world, e, colors, size) {
4971
5091
  flash(world, { position: e.position, color: hot(mean(colors), 2, 0.85), size });
4972
5092
  }
4973
5093
  function dust(world, o) {
4974
- const single = typeof o.color[0] === "number";
5094
+ const colors = resolveColors(o.color);
4975
5095
  return world.burst({
4976
5096
  position: o.position,
4977
5097
  count: o.count,
@@ -4982,9 +5102,9 @@ function dust(world, o) {
4982
5102
  gravity: o.gravity ?? 0,
4983
5103
  direction: o.direction,
4984
5104
  spread: o.spread,
4985
- color: o.color,
5105
+ color: colors.length === 1 ? colors[0] : colors,
4986
5106
  endSize: o.endSize ?? 2,
4987
- endColor: o.endColor ?? (single ? fade(o.color) : undefined)
5107
+ endColor: o.endColor ?? (colors.length === 1 ? fade(colors[0]) : undefined)
4988
5108
  });
4989
5109
  }
4990
5110
  function confetti(world, e) {
@@ -5803,11 +5923,13 @@ var QUALITY_LEVELS = {
5803
5923
  lowest: { maxPixelRatio: 2, samples: 1, shadowSize: 512, softShadows: false }
5804
5924
  };
5805
5925
  var SLOW_FRAME_MS = 20;
5926
+ var VERY_SLOW_FRAME_MS = 30;
5806
5927
  var FAST_FRAME_MS = 17.4;
5807
5928
  var RENDER_SHARE = 0.6;
5808
5929
  var STARTUP_MS = 2500;
5809
- var SETTLE_MS = 2000;
5930
+ var SETTLE_MS = 1500;
5810
5931
  var SLOW_SECONDS = 3;
5932
+ var VERY_SLOW_SECONDS = 1;
5811
5933
  var FAST_SECONDS = 10;
5812
5934
  var RELAPSE_MS = 20000;
5813
5935
 
@@ -5818,6 +5940,7 @@ class AutoQuality {
5818
5940
  changed = 0;
5819
5941
  raisedAt = -Infinity;
5820
5942
  slow = 0;
5943
+ verySlow = 0;
5821
5944
  fast = 0;
5822
5945
  get quality() {
5823
5946
  return QUALITIES[this.index];
@@ -5830,8 +5953,9 @@ class AutoQuality {
5830
5953
  const gpuBound = frames.frameMs > SLOW_FRAME_MS && frames.renderMs > frames.frameMs * RENDER_SHARE;
5831
5954
  const smooth = frames.frameMs <= FAST_FRAME_MS;
5832
5955
  this.slow = gpuBound ? this.slow + 1 : 0;
5956
+ this.verySlow = gpuBound && frames.frameMs > VERY_SLOW_FRAME_MS ? this.verySlow + 1 : 0;
5833
5957
  this.fast = smooth ? this.fast + 1 : 0;
5834
- if (this.slow >= SLOW_SECONDS && this.index < QUALITIES.length - 1) {
5958
+ if ((this.slow >= SLOW_SECONDS || this.verySlow >= VERY_SLOW_SECONDS) && this.index < QUALITIES.length - 1) {
5835
5959
  if (now - this.raisedAt < RELAPSE_MS)
5836
5960
  this.ceiling = Math.max(this.ceiling, this.index + 1);
5837
5961
  this.index++;
@@ -5847,6 +5971,7 @@ class AutoQuality {
5847
5971
  moved(now) {
5848
5972
  this.changed = now;
5849
5973
  this.slow = 0;
5974
+ this.verySlow = 0;
5850
5975
  this.fast = 0;
5851
5976
  return true;
5852
5977
  }
@@ -5874,6 +5999,35 @@ var defaultLight = () => ({
5874
5999
  direction: [0.4, 0.8, 0.5],
5875
6000
  ambient: 0.35
5876
6001
  });
6002
+ var afterFrame = [];
6003
+ var runningLoops = 0;
6004
+ function runAfterFrame() {
6005
+ if (afterFrame.length === 0)
6006
+ return;
6007
+ const due = afterFrame;
6008
+ afterFrame = [];
6009
+ for (const fn of due)
6010
+ fn();
6011
+ }
6012
+ function afterNextFrame(fn) {
6013
+ let cancelled = false;
6014
+ const run = () => {
6015
+ if (!cancelled)
6016
+ fn();
6017
+ };
6018
+ if (runningLoops > 0)
6019
+ afterFrame.push(run);
6020
+ else {
6021
+ const raf = requestAnimationFrame(run);
6022
+ return () => {
6023
+ cancelled = true;
6024
+ cancelAnimationFrame(raf);
6025
+ };
6026
+ }
6027
+ return () => {
6028
+ cancelled = true;
6029
+ };
6030
+ }
5877
6031
  function GameView(props) {
5878
6032
  const { source, camera, light, background, fog, style } = props;
5879
6033
  const active = useActive();
@@ -5920,6 +6074,9 @@ function GameView(props) {
5920
6074
  let alive = true;
5921
6075
  let raf = 0;
5922
6076
  let errors = 0;
6077
+ const native2 = native;
6078
+ const threaded = native2.hasFrameClock?.() === true && typeof native2.publishFrame === "function" && typeof native2.startFrames === "function";
6079
+ let presentedAt = 0;
5923
6080
  if (autoQuality.current?.source !== source)
5924
6081
  autoQuality.current = { source, auto: new AutoQuality };
5925
6082
  const auto = autoQuality.current.auto;
@@ -5965,7 +6122,8 @@ function GameView(props) {
5965
6122
  const frame = (now) => {
5966
6123
  if (!alive)
5967
6124
  return;
5968
- raf = requestAnimationFrame(frame);
6125
+ if (!threaded)
6126
+ raf = requestAnimationFrame(frame);
5969
6127
  if (!last)
5970
6128
  last = fpsStart = now;
5971
6129
  if (now > last)
@@ -5974,7 +6132,14 @@ function GameView(props) {
5974
6132
  last = now;
5975
6133
  const { props: current, camera, light, environment } = live.current;
5976
6134
  const t0 = performance.now();
6135
+ const layer = current.overlay;
6136
+ const sourceSteps = source.updateCount;
6137
+ const layerSteps = layer?.updateCount;
5977
6138
  current.onUpdate?.(dt);
6139
+ if (source.update && !source.isDisposed && source.updateCount === sourceSteps)
6140
+ source.update(dt);
6141
+ if (layer?.update && !layer.isDisposed && layer.updateCount === layerSteps)
6142
+ layer.update(dt);
5978
6143
  updateTime += performance.now() - t0;
5979
6144
  follow(camera, source, followed, dt);
5980
6145
  const view = shake(camera, shaken, dt);
@@ -5991,11 +6156,11 @@ function GameView(props) {
5991
6156
  f[11] = light.ambient;
5992
6157
  f[12] = light.shadows ?? true ? 1 : 0;
5993
6158
  f[13] = light.shadowExtent ?? 30;
5994
- const lightColor = light.color ?? WHITE3;
6159
+ const lightColor = light.color === undefined ? WHITE3 : resolveColor(light.color);
5995
6160
  f[14] = lightColor[0];
5996
6161
  f[15] = lightColor[1];
5997
6162
  f[16] = lightColor[2];
5998
- const sky = environment.background;
6163
+ const sky = resolveColor(environment.background);
5999
6164
  f[17] = sky[0];
6000
6165
  f[18] = sky[1];
6001
6166
  f[19] = sky[2];
@@ -6006,7 +6171,6 @@ function GameView(props) {
6006
6171
  f[24] = level.shadowSize;
6007
6172
  f[25] = level.softShadows ? 1 : 0;
6008
6173
  const t1 = performance.now();
6009
- const layer = current.overlay;
6010
6174
  if (layer) {
6011
6175
  if (overlay?.source !== layer)
6012
6176
  overlay = { source: layer, regions: wholeRegions(layer) };
@@ -6015,28 +6179,41 @@ function GameView(props) {
6015
6179
  overlay = null;
6016
6180
  native.renderOverlay(surfaceId, regions.buffer, regions.buffer, regions.buffer, regions.buffer, 0, 0, 0);
6017
6181
  }
6018
- const rendered = native.renderFrame(surfaceId, f.buffer, source.matrices.buffer, source.colors.buffer, regions.buffer, source.ranges.buffer, drawn(source), viewProj.buffer, source.nativeWorld ?? -1);
6182
+ const render = threaded ? native2.publishFrame : native.renderFrame;
6183
+ const rendered = render(surfaceId, f.buffer, source.matrices.buffer, source.colors.buffer, regions.buffer, source.ranges.buffer, drawn(source), viewProj.buffer, source.nativeWorld ?? -1);
6019
6184
  const drawMs = performance.now() - t1;
6020
- renderTime += drawMs;
6021
- draws.push(drawMs);
6022
- if (rendered === 1) {
6023
- shown = true;
6185
+ if (!threaded) {
6186
+ renderTime += drawMs;
6187
+ draws.push(drawMs);
6188
+ }
6189
+ if (rendered === 1 || threaded && rendered === 0)
6024
6190
  source.setView?.(viewProj, width, height);
6025
- } else if (rendered < 0 && errors++ < 3) {
6191
+ if (rendered === 1)
6192
+ shown = true;
6193
+ else if (rendered < 0 && errors++ < 3)
6026
6194
  report(new Error(native.renderError() || "rendering failed"));
6027
- }
6028
6195
  frames++;
6029
6196
  if (now - fpsStart >= 1000) {
6030
- auto.report(now, {
6031
- frameMs: median(intervals),
6032
- renderMs: median(draws)
6033
- });
6197
+ const elapsed = now - fpsStart;
6198
+ let fps = frames * 1000 / elapsed;
6199
+ let frameMs = median(intervals);
6200
+ let renderMs = median(draws);
6201
+ let meanRenderMs = renderTime / frames;
6202
+ if (threaded) {
6203
+ const [presented = 0, nativeMs = 0] = native2.renderStats?.(surfaceId) ?? [];
6204
+ const shownFrames = presented - presentedAt;
6205
+ presentedAt = presented;
6206
+ fps = shownFrames * 1000 / elapsed;
6207
+ frameMs = shownFrames > 0 ? Math.max(frameMs, elapsed / shownFrames) : elapsed;
6208
+ renderMs = meanRenderMs = nativeMs;
6209
+ }
6210
+ auto.report(now, { frameMs, renderMs });
6034
6211
  intervals.length = 0;
6035
6212
  draws.length = 0;
6036
6213
  current.onStats?.({
6037
- fps: frames * 1000 / (now - fpsStart),
6214
+ fps,
6038
6215
  updateMs: updateTime / frames,
6039
- renderMs: renderTime / frames,
6216
+ renderMs: meanRenderMs,
6040
6217
  quality,
6041
6218
  backend: native.renderBackend()
6042
6219
  });
@@ -6045,11 +6222,22 @@ function GameView(props) {
6045
6222
  renderTime = 0;
6046
6223
  fpsStart = now;
6047
6224
  }
6225
+ runAfterFrame();
6048
6226
  };
6049
- raf = requestAnimationFrame(frame);
6227
+ if (threaded)
6228
+ native2.startFrames(surfaceId, frame);
6229
+ else
6230
+ raf = requestAnimationFrame(frame);
6231
+ runningLoops++;
6050
6232
  return () => {
6051
6233
  alive = false;
6052
- cancelAnimationFrame(raf);
6234
+ runningLoops--;
6235
+ if (runningLoops === 0 && afterFrame.length)
6236
+ requestAnimationFrame(runAfterFrame);
6237
+ if (threaded)
6238
+ native2.stopFrames(surfaceId);
6239
+ else
6240
+ cancelAnimationFrame(raf);
6053
6241
  };
6054
6242
  }, [source, props.paused, active]);
6055
6243
  const touchable = props.onTap || props.onSwipe || props.onDrag || props.onLongPress || props.onPinch || props.onOverlayTouch;
@@ -6064,6 +6252,58 @@ function GameView(props) {
6064
6252
  onPrimaryAction: props.onPrimaryAction ? () => live.current.props.onPrimaryAction?.() : undefined
6065
6253
  });
6066
6254
  }
6255
+ // src/render/useWorld.ts
6256
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
6257
+ function useWorld(capacity, setup, deps = [], onError) {
6258
+ const [world, setWorld] = useState2(null);
6259
+ const latest = useRef3({ setup, onError });
6260
+ latest.current = { setup, onError };
6261
+ useEffect3(() => {
6262
+ let active = true;
6263
+ let created = null;
6264
+ let cleanup;
6265
+ const report = (error) => {
6266
+ const e = error instanceof Error ? error : new Error(String(error));
6267
+ (latest.current.onError ?? ((x) => console.error(`useWorld: ${x.message}`)))(e);
6268
+ };
6269
+ if (!isEngineAvailable) {
6270
+ report(new Error("the native engine is not linked into this build (a development build is needed, not Expo Go)"));
6271
+ return;
6272
+ }
6273
+ warmUpEngine().then(() => {
6274
+ if (!active)
6275
+ return;
6276
+ try {
6277
+ created = new World(capacity);
6278
+ cleanup = latest.current.setup?.(created);
6279
+ setWorld(created);
6280
+ } catch (error) {
6281
+ created?.dispose();
6282
+ created = null;
6283
+ report(error);
6284
+ }
6285
+ }, report);
6286
+ return () => {
6287
+ active = false;
6288
+ setWorld(null);
6289
+ if (!created)
6290
+ return;
6291
+ try {
6292
+ cleanup?.();
6293
+ } finally {
6294
+ created.dispose();
6295
+ }
6296
+ };
6297
+ }, [capacity, ...deps]);
6298
+ return world;
6299
+ }
6300
+ // src/render/light.ts
6301
+ function frontLight(direction, ambient) {
6302
+ const [x, y, z] = direction;
6303
+ const facing = z / Math.hypot(x, y, z);
6304
+ const k = 1 / (ambient * 0.8 + (1 - ambient) * facing);
6305
+ return { direction, ambient, shadows: false, color: [k, k, k] };
6306
+ }
6067
6307
  // src/assets/AssetCache.ts
6068
6308
  async function clearAssetCache() {
6069
6309
  advanceCacheGeneration();
@@ -6118,6 +6358,24 @@ class ProceduralCache {
6118
6358
  async flush() {
6119
6359
  await Promise.all([...this.writes]);
6120
6360
  }
6361
+ peek(key) {
6362
+ if (this.memory.has(key))
6363
+ return this.memory.get(key);
6364
+ if (!this.storage)
6365
+ return;
6366
+ try {
6367
+ const bytes = this.storage.read(key);
6368
+ if (!bytes)
6369
+ return;
6370
+ const value = this.restore(bytes, key);
6371
+ this.memory.set(key, value);
6372
+ this.hits++;
6373
+ return value;
6374
+ } catch {
6375
+ this.invalid++;
6376
+ return;
6377
+ }
6378
+ }
6121
6379
  get(key, build) {
6122
6380
  if (this.memory.has(key))
6123
6381
  return this.memory.get(key);
@@ -6226,7 +6484,7 @@ class ProceduralCache {
6226
6484
  bytes.set(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), offset);
6227
6485
  offset += data.byteLength;
6228
6486
  }
6229
- view.setUint32(12, checksum(bytes.subarray(16)), true);
6487
+ view.setUint32(12, this.storage?.verified ? 0 : checksum(bytes.subarray(16)), true);
6230
6488
  return bytes;
6231
6489
  }
6232
6490
  restore(raw, key) {
@@ -6235,7 +6493,7 @@ class ProceduralCache {
6235
6493
  const bytes = raw.byteOffset % 4 ? raw.slice() : raw;
6236
6494
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
6237
6495
  const jsonLength = view.getUint32(4, true);
6238
- if (view.getUint32(0, true) !== MAGIC || view.getUint32(8, true) !== bytes.length || jsonLength > bytes.length - 16 || view.getUint32(12, true) !== checksum(bytes.subarray(16)))
6496
+ if (view.getUint32(0, true) !== MAGIC || view.getUint32(8, true) !== bytes.length || jsonLength > bytes.length - 16 || !this.storage?.verified && view.getUint32(12, true) !== checksum(bytes.subarray(16)))
6239
6497
  throw Error("cache integrity");
6240
6498
  const header = decodeJson(bytes.subarray(16, 16 + jsonLength));
6241
6499
  if (!("value" in header) || header.revision !== this.revision || header.key !== key || !Array.isArray(header.meshes))
@@ -6250,12 +6508,14 @@ class ProceduralCache {
6250
6508
  throw Error("cache truncated");
6251
6509
  const vertices = new Float32Array(bytes.buffer, bytes.byteOffset + offset, mesh.floats);
6252
6510
  const indices = new Uint32Array(bytes.buffer, bytes.byteOffset + offset + mesh.floats * 4, mesh.indices);
6253
- for (const value of vertices)
6254
- if (!Number.isFinite(value))
6255
- throw Error("cache vertex");
6256
- for (const index of indices)
6257
- if (index >= mesh.floats / 11)
6258
- throw Error("cache index");
6511
+ if (!this.storage?.verified) {
6512
+ for (const value of vertices)
6513
+ if (!Number.isFinite(value))
6514
+ throw Error("cache vertex");
6515
+ for (const index of indices)
6516
+ if (index >= mesh.floats / 11)
6517
+ throw Error("cache index");
6518
+ }
6259
6519
  payloads.push({ vertices, indices, shine: mesh.shine });
6260
6520
  offset = end;
6261
6521
  }
@@ -7387,10 +7647,12 @@ function smoothCurve(points) {
7387
7647
  var loader4 = new AssetLoader("audio");
7388
7648
  function loadSound(source) {
7389
7649
  return loader4.load(source, (native, bytes) => {
7390
- const id = native.audioLoad(bytes);
7391
- if (id < 0)
7392
- throw new Error(native.loadError() || "not a supported WAV file");
7393
- return id;
7650
+ const finish = (id) => {
7651
+ if (id < 0)
7652
+ throw new Error(native.loadError() || "not a supported WAV file");
7653
+ return id;
7654
+ };
7655
+ return typeof native.audioLoadAsync === "function" ? native.audioLoadAsync(bytes).then(finish) : finish(native.audioLoad(bytes));
7394
7656
  }).catch((error) => {
7395
7657
  console.warn(error instanceof Error ? error.message : String(error));
7396
7658
  return null;
@@ -7509,15 +7771,15 @@ var haptics = {
7509
7771
  }
7510
7772
  };
7511
7773
  // src/input/Joystick.web.tsx
7512
- import { useCallback, useEffect as useEffect3, useRef as useRef3 } from "react";
7774
+ import { useCallback, useEffect as useEffect4, useRef as useRef4 } from "react";
7513
7775
  import { jsx as jsx3 } from "react/jsx-runtime";
7514
7776
  function useJoystick() {
7515
- return useRef3({ x: 0, y: 0 }).current;
7777
+ return useRef4({ x: 0, y: 0 }).current;
7516
7778
  }
7517
7779
  function Joystick({ state, size = 150, style }) {
7518
- const knob = useRef3(null);
7519
- const pointer = useRef3(null);
7520
- const origin = useRef3({ x: 0, y: 0 });
7780
+ const knob = useRef4(null);
7781
+ const pointer = useRef4(null);
7782
+ const origin = useRef4({ x: 0, y: 0 });
7521
7783
  const reach = size * 0.3;
7522
7784
  const reset = useCallback(() => {
7523
7785
  pointer.current = null;
@@ -7525,7 +7787,7 @@ function Joystick({ state, size = 150, style }) {
7525
7787
  if (knob.current)
7526
7788
  knob.current.style.transform = "translate(0px, 0px)";
7527
7789
  }, [state]);
7528
- useEffect3(() => {
7790
+ useEffect4(() => {
7529
7791
  const visibility = () => {
7530
7792
  if (document.hidden)
7531
7793
  reset();
@@ -7730,7 +7992,7 @@ class DrawingDocument {
7730
7992
  }
7731
7993
  }
7732
7994
  // src/drawing/DrawingCanvas.web.tsx
7733
- import { memo, useEffect as useEffect4, useRef as useRef4, useState as useState2 } from "react";
7995
+ import { memo, useEffect as useEffect5, useRef as useRef5, useState as useState3 } from "react";
7734
7996
  import { jsx as jsx4 } from "react/jsx-runtime";
7735
7997
  var DrawingCanvas = memo(function DrawingCanvas({
7736
7998
  document: drawing,
@@ -7741,11 +8003,11 @@ var DrawingCanvas = memo(function DrawingCanvas({
7741
8003
  onChange,
7742
8004
  style
7743
8005
  }) {
7744
- const ref = useRef4(null);
7745
- const [error, setError] = useState2("");
7746
- const live = useRef4({ mode, background, active, onError, onChange });
8006
+ const ref = useRef5(null);
8007
+ const [error, setError] = useState3("");
8008
+ const live = useRef5({ mode, background, active, onError, onChange });
7747
8009
  live.current = { mode, background, active, onError, onChange };
7748
- useEffect4(() => {
8010
+ useEffect5(() => {
7749
8011
  const canvas = ref.current;
7750
8012
  setError("");
7751
8013
  let alive = true, ready = false, raf = 0, drawingPointer = null;
@@ -7936,6 +8198,7 @@ export {
7936
8198
  Scoreboard,
7937
8199
  Solid,
7938
8200
  World,
8201
+ afterNextFrame,
7939
8202
  audio,
7940
8203
  block,
7941
8204
  clearAssetCache,
@@ -7947,6 +8210,7 @@ export {
7947
8210
  effects,
7948
8211
  ellipsoid,
7949
8212
  extrude,
8213
+ frontLight,
7950
8214
  getGeometryStats,
7951
8215
  getMeshIdLimit,
7952
8216
  haptics,
@@ -7966,9 +8230,11 @@ export {
7966
8230
  smoothCurve,
7967
8231
  smoothNormals,
7968
8232
  solidGeometry,
8233
+ toColor,
7969
8234
  tube,
7970
8235
  useJoystick,
8236
+ useWorld,
7971
8237
  warmUpEngine
7972
8238
  };
7973
8239
 
7974
- //# debugId=415D0E5A2154518A64756E2164756E21
8240
+ //# debugId=1367AF6CC344167A64756E2164756E21