@codexo/exojs 0.6.3 → 0.6.4

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.
package/dist/exo.esm.js CHANGED
@@ -372,6 +372,202 @@ const canvasSourceToDataUrl = (source) => {
372
372
  return canvasElement.toDataURL();
373
373
  };
374
374
 
375
+ /// <reference types="@webgpu/types" />
376
+ // Browser-environment feature detection. Construction is private; the
377
+ // only public entry is `Capabilities.ready`, a lazy-cached `Promise<Capabilities>`
378
+ // that fires the (mostly) async probes on first access and returns the
379
+ // same Promise for every subsequent call. Once it resolves, the returned
380
+ // instance is frozen — every property is read once and never mutates.
381
+ //
382
+ // Synchronous callsites should keep the resolved instance in scope (e.g.,
383
+ // `app.capabilities` after `await app.start(...)`); there is no global
384
+ // sync mirror, by design.
385
+ const hasWindow = typeof window !== 'undefined';
386
+ const hasDocument = typeof document !== 'undefined';
387
+ const hasNavigator = typeof navigator !== 'undefined';
388
+ class Capabilities {
389
+ static _readyPromise = null;
390
+ /**
391
+ * Lazy-cached Promise that resolves to a frozen Capabilities instance.
392
+ *
393
+ * The first read kicks off the async probes (currently just the WebGPU
394
+ * adapter request); every subsequent read returns the same Promise.
395
+ * Concurrent callers share the in-flight detection — no double work.
396
+ *
397
+ * Early-warmup pattern for callers who want to overlap detection with
398
+ * other startup work:
399
+ *
400
+ * ```ts
401
+ * void Capabilities.ready; // fire-and-forget; starts probes now
402
+ * // ... unrelated bootstrap ...
403
+ * const caps = await Capabilities.ready; // typically already resolved
404
+ * ```
405
+ */
406
+ static get ready() {
407
+ if (Capabilities._readyPromise === null) {
408
+ Capabilities._readyPromise = Capabilities._detect();
409
+ }
410
+ return Capabilities._readyPromise;
411
+ }
412
+ webgl2;
413
+ webgpu;
414
+ webgpuAdapter;
415
+ webgpuVendor;
416
+ webgpuArchitecture;
417
+ pointer;
418
+ keyboard;
419
+ gamepad;
420
+ touch;
421
+ maxTouchPoints;
422
+ audio;
423
+ fullscreen;
424
+ vibration;
425
+ offscreenCanvas;
426
+ webWorkers;
427
+ devicePixelRatio;
428
+ constructor(values) {
429
+ this.webgl2 = values.webgl2;
430
+ this.webgpu = values.webgpu;
431
+ this.webgpuAdapter = values.webgpuAdapter;
432
+ this.webgpuVendor = values.webgpuVendor;
433
+ this.webgpuArchitecture = values.webgpuArchitecture;
434
+ this.pointer = values.pointer;
435
+ this.keyboard = values.keyboard;
436
+ this.gamepad = values.gamepad;
437
+ this.touch = values.touch;
438
+ this.maxTouchPoints = values.maxTouchPoints;
439
+ this.audio = values.audio;
440
+ this.fullscreen = values.fullscreen;
441
+ this.vibration = values.vibration;
442
+ this.offscreenCanvas = values.offscreenCanvas;
443
+ this.webWorkers = values.webWorkers;
444
+ this.devicePixelRatio = values.devicePixelRatio;
445
+ Object.freeze(this);
446
+ }
447
+ static async _detect() {
448
+ const [webgpuAdapter, webgpuInfo] = await probeWebGpu();
449
+ return new Capabilities({
450
+ webgl2: probeWebGl2(),
451
+ webgpu: probeWebGpuApiSurface(),
452
+ webgpuAdapter,
453
+ webgpuVendor: webgpuInfo?.vendor ?? null,
454
+ webgpuArchitecture: webgpuInfo?.architecture ?? null,
455
+ pointer: probePointer(),
456
+ keyboard: probeKeyboard(),
457
+ gamepad: probeGamepad(),
458
+ touch: probeTouchSupported(),
459
+ maxTouchPoints: probeMaxTouchPoints(),
460
+ audio: probeAudio(),
461
+ fullscreen: probeFullscreen(),
462
+ vibration: probeVibration(),
463
+ offscreenCanvas: probeOffscreenCanvas(),
464
+ webWorkers: probeWebWorkers(),
465
+ devicePixelRatio: hasWindow ? window.devicePixelRatio : 1,
466
+ });
467
+ }
468
+ }
469
+ // --- probes ---------------------------------------------------------------
470
+ function probeWebGl2() {
471
+ if (!hasDocument)
472
+ return false;
473
+ try {
474
+ const canvas = document.createElement('canvas');
475
+ const gl = canvas.getContext('webgl2');
476
+ return gl !== null;
477
+ }
478
+ catch {
479
+ return false;
480
+ }
481
+ }
482
+ function probeWebGpuApiSurface() {
483
+ return hasNavigator && 'gpu' in navigator;
484
+ }
485
+ async function probeWebGpu() {
486
+ if (!probeWebGpuApiSurface())
487
+ return [null, null];
488
+ const gpu = navigator.gpu;
489
+ if (!gpu || typeof gpu.requestAdapter !== 'function')
490
+ return [null, null];
491
+ try {
492
+ const adapter = await gpu.requestAdapter();
493
+ if (!adapter)
494
+ return [null, null];
495
+ // Modern path: GPUAdapter.info is a sync property (Chrome 116+,
496
+ // Safari 18+). Older browsers exposed a deprecated async
497
+ // requestAdapterInfo() instead. Try the modern path first, fall
498
+ // back if needed.
499
+ const adapterAny = adapter;
500
+ if (adapterAny.info) {
501
+ return [adapter, adapterAny.info];
502
+ }
503
+ if (typeof adapterAny.requestAdapterInfo === 'function') {
504
+ try {
505
+ return [adapter, await adapterAny.requestAdapterInfo()];
506
+ }
507
+ catch {
508
+ return [adapter, null];
509
+ }
510
+ }
511
+ return [adapter, null];
512
+ }
513
+ catch {
514
+ return [null, null];
515
+ }
516
+ }
517
+ function probePointer() {
518
+ return hasWindow && 'PointerEvent' in window;
519
+ }
520
+ function probeKeyboard() {
521
+ return hasWindow && 'KeyboardEvent' in window;
522
+ }
523
+ function probeGamepad() {
524
+ return hasNavigator && typeof navigator.getGamepads === 'function';
525
+ }
526
+ function probeTouchSupported() {
527
+ if (!hasWindow)
528
+ return false;
529
+ if ('ontouchstart' in window)
530
+ return true;
531
+ if (probeMaxTouchPoints() > 0)
532
+ return true;
533
+ return false;
534
+ }
535
+ function probeMaxTouchPoints() {
536
+ if (!hasNavigator)
537
+ return 0;
538
+ const points = navigator.maxTouchPoints;
539
+ return typeof points === 'number' ? points : 0;
540
+ }
541
+ function probeAudio() {
542
+ if (!hasWindow)
543
+ return false;
544
+ const w = window;
545
+ return typeof w.AudioContext !== 'undefined' || typeof w.webkitAudioContext !== 'undefined';
546
+ }
547
+ function probeFullscreen() {
548
+ if (!hasDocument)
549
+ return false;
550
+ const el = document.documentElement;
551
+ return typeof el.requestFullscreen === 'function' || typeof el.webkitRequestFullscreen === 'function';
552
+ }
553
+ function probeVibration() {
554
+ return hasNavigator && typeof navigator.vibrate === 'function';
555
+ }
556
+ function probeOffscreenCanvas() {
557
+ // The browser global is verbatim `OffscreenCanvas`; eslint's
558
+ // strict-camelCase rule rejects the property name even though we
559
+ // can't rename a web standard.
560
+ // eslint-disable-next-line @typescript-eslint/naming-convention
561
+ return hasWindow && typeof window.OffscreenCanvas !== 'undefined';
562
+ }
563
+ function probeWebWorkers() {
564
+ // The browser global is verbatim `Worker`; eslint's strict-camelCase
565
+ // rule rejects the property name even though we can't rename a web
566
+ // standard.
567
+ // eslint-disable-next-line @typescript-eslint/naming-convention
568
+ return hasWindow && typeof window.Worker === 'function';
569
+ }
570
+
375
571
  class Clock {
376
572
  _startTime;
377
573
  _elapsedTime = new Time(0);
@@ -14655,6 +14851,7 @@ class Application {
14655
14851
  _frameRequest = 0;
14656
14852
  _backendType;
14657
14853
  _backend;
14854
+ _capabilities = null;
14658
14855
  constructor(appSettings) {
14659
14856
  this.options = {
14660
14857
  canvas: appSettings?.canvas ?? createDefaultCanvas(),
@@ -14696,11 +14893,26 @@ class Application {
14696
14893
  get backend() {
14697
14894
  return this._backend;
14698
14895
  }
14896
+ /**
14897
+ * Resolved capabilities for the host browser. Available after
14898
+ * {@link Application.start} resolves; reading before that throws.
14899
+ * For pre-start access use {@link Capabilities.ready} directly.
14900
+ */
14901
+ get capabilities() {
14902
+ if (this._capabilities === null) {
14903
+ throw new Error('Application.capabilities is unavailable before start() resolves. Use `await Capabilities.ready` for pre-start checks.');
14904
+ }
14905
+ return this._capabilities;
14906
+ }
14699
14907
  async start(scene) {
14700
14908
  if (this._status === ApplicationStatus.Stopped) {
14701
14909
  this._status = ApplicationStatus.Loading;
14910
+ // Kick off capability detection in parallel with renderer init —
14911
+ // both are mostly-async startup work, no point serializing them.
14912
+ const capabilitiesPromise = Capabilities.ready;
14702
14913
  try {
14703
14914
  await this.initializeRenderManager();
14915
+ this._capabilities = await capabilitiesPromise;
14704
14916
  await this.sceneManager.setScene(scene);
14705
14917
  this._frameRequest = requestAnimationFrame(this._updateHandler);
14706
14918
  this._frameClock.restart();
@@ -14798,100 +15010,6 @@ class Application {
14798
15010
  }
14799
15011
  }
14800
15012
 
14801
- // Browser feature-detection probes evaluated once at module load. The
14802
- // resulting `capabilities` object is a `Readonly<Record<CapabilityName,
14803
- // boolean>>` and can be inspected directly or queried via `isSupported`.
14804
- //
14805
- // All probes are synchronous. Async questions ("can I actually acquire a
14806
- // WebGPU adapter?", "can the audio decoder play this OGG file?") are
14807
- // outside this module's scope — they're owned by the Application's
14808
- // backend selection and the Loader respectively. `capabilities.webgpu`
14809
- // returning `true` only guarantees that the browser advertises WebGPU,
14810
- // not that an adapter request will succeed.
14811
- const hasWindow = typeof window !== 'undefined';
14812
- const hasDocument = typeof document !== 'undefined';
14813
- const hasNavigator = typeof navigator !== 'undefined';
14814
- const probeWebGl2 = () => {
14815
- if (!hasDocument)
14816
- return false;
14817
- try {
14818
- const canvas = document.createElement('canvas');
14819
- const gl = canvas.getContext('webgl2');
14820
- return gl !== null;
14821
- }
14822
- catch {
14823
- return false;
14824
- }
14825
- };
14826
- const probeWebGpu = () => {
14827
- return hasNavigator && 'gpu' in navigator;
14828
- };
14829
- const probeAudio = () => {
14830
- if (!hasWindow)
14831
- return false;
14832
- const w = window;
14833
- return typeof w.AudioContext !== 'undefined' || typeof w.webkitAudioContext !== 'undefined';
14834
- };
14835
- const probePointer = () => {
14836
- return hasWindow && 'PointerEvent' in window;
14837
- };
14838
- const probeTouch = () => {
14839
- if (!hasWindow)
14840
- return false;
14841
- if ('ontouchstart' in window)
14842
- return true;
14843
- if (hasNavigator && typeof navigator.maxTouchPoints === 'number' && navigator.maxTouchPoints > 0)
14844
- return true;
14845
- return false;
14846
- };
14847
- const probeGamepad = () => {
14848
- return hasNavigator && typeof navigator.getGamepads === 'function';
14849
- };
14850
- const probeKeyboard = () => {
14851
- return hasWindow && 'KeyboardEvent' in window;
14852
- };
14853
- const probeFullscreen = () => {
14854
- if (!hasDocument)
14855
- return false;
14856
- const el = document.documentElement;
14857
- return typeof el.requestFullscreen === 'function' || typeof el.webkitRequestFullscreen === 'function';
14858
- };
14859
- const probeVibration = () => {
14860
- return hasNavigator && typeof navigator.vibrate === 'function';
14861
- };
14862
- const probeOffscreenCanvas = () => {
14863
- // The browser global is verbatim `OffscreenCanvas`; eslint's
14864
- // strict-camelCase rule rejects the property name even though we
14865
- // can't rename a web standard.
14866
- // eslint-disable-next-line @typescript-eslint/naming-convention
14867
- return hasWindow && typeof window.OffscreenCanvas !== 'undefined';
14868
- };
14869
- /**
14870
- * Synchronous, one-shot feature-detection results. Computed once at
14871
- * module load and frozen. Use either as a property bag (`capabilities.touch`)
14872
- * or via {@link isSupported} for typed lookup.
14873
- */
14874
- const capabilities = Object.freeze({
14875
- webgl2: probeWebGl2(),
14876
- webgpu: probeWebGpu(),
14877
- audio: probeAudio(),
14878
- pointer: probePointer(),
14879
- touch: probeTouch(),
14880
- gamepad: probeGamepad(),
14881
- keyboard: probeKeyboard(),
14882
- fullscreen: probeFullscreen(),
14883
- vibration: probeVibration(),
14884
- offscreenCanvas: probeOffscreenCanvas(),
14885
- });
14886
- /**
14887
- * Typed lookup over {@link capabilities}. Identical to
14888
- * `capabilities[name]` but the function form gives clearer call-sites
14889
- * when the name is computed.
14890
- */
14891
- function isSupported(name) {
14892
- return capabilities[name];
14893
- }
14894
-
14895
15013
  class Quadtree {
14896
15014
  static maxSceneNodes = 50;
14897
15015
  static maxLevel = 5;
@@ -19079,5 +19197,5 @@ const createRapierPhysicsWorld = async (options = {}) => {
19079
19197
  return await RapierPhysicsWorld.create(options);
19080
19198
  };
19081
19199
 
19082
- export { AbstractAssetFactory, AbstractMedia, AbstractWebGl2BatchedRenderer, AbstractWebGl2Renderer, AbstractWebGpuRenderer, AnimatedSprite, Application, ApplicationStatus, ArcadeStickGamepadMapping, AudioAnalyser, BinaryFactory, BlendModes, BlurFilter, Bounds, BufferTypes, BufferUsage, BundleLoadError, CacheFirstStrategy, CallbackRenderPass, ChannelOffset, ChannelSize, Circle, CircleGeometry, Clock, CollisionType, Color, ColorAffector, ColorFilter, Container, Drawable, DrawableShape, Ellipse, FactoryRegistry, Filter, Flags, FontFactory, ForceAffector, GameCubeGamepadMapping, Gamepad, GamepadChannel, GamepadControl, GamepadMapping, GamepadMappingFamily, GamepadPromptLayouts, GenericDualAnalogGamepadMapping, Geometry, Graphics, ImageFactory, IndexedDbDatabase, IndexedDbStore, Input, InputManager, Interval, JoyConLeftGamepadMapping, JoyConRightGamepadMapping, Json, JsonFactory, Keyboard, Line, Loader, Matrix, Mesh, Music, MusicFactory, NetworkOnlyStrategy, ObservableSize, ObservableVector, Particle, ParticleOptions, ParticleSystem, PlayStationGamepadMapping, Pointer, PointerState, PointerStateFlag, PolarVector, Polygon, Quadtree, Random, RapierPhysicsBinding, RapierPhysicsWorld, Rectangle, RenderBackendType, RenderNode, RenderTarget, RenderTargetPass, RenderTexture, RendererRegistry, RenderingPrimitives, Sampler, ScaleAffector, ScaleModes, Scene, SceneManager, SceneNode, Segment, Shader, ShaderAttribute, ShaderPrimitives, ShaderUniform, Signal, Size, Sound, SoundFactory, Sprite, SpriteFlags, Spritesheet, SteamControllerGamepadMapping, SvgAsset, SvgFactory, SwitchProGamepadMapping, Text, TextAsset, TextFactory, TextStyle, Texture, TextureFactory, Time, Timer, TorqueAffector, UniversalEmitter, Vector, Video, VideoFactory, View, ViewFlags, VoronoiRegion, VttAsset, VttFactory, WasmFactory, WebGl2Backend, WebGl2MeshRenderer, WebGl2ParticleRenderer, WebGl2PrimitiveRenderer, WebGl2RenderBuffer, WebGl2ShaderBlock, WebGl2SpriteRenderer, WebGl2VertexArrayObject, WebGpuBackend, WebGpuMeshRenderer, WebGpuParticleRenderer, WebGpuPrimitiveRenderer, WebGpuSpriteRenderer, WrapModes, XboxGamepadMapping, bezierCurveTo, buildCircle, buildEllipse, buildLine, buildPath, buildPolygon, buildRectangle, buildStar, builtInGamepadDefinitions, canvasSourceToDataUrl, capabilities, clamp, createRapierPhysicsWorld, createRenderStats, createWebGl2ShaderProgram, decodeAudioData, defineAssetManifest, degreesPerRadian, degreesToRadians, determineMimeType, emptyArrayBuffer, getAudioContext, getCanvasSourceSize, getCollisionCircleCircle, getCollisionCircleRectangle, getCollisionPolygonCircle, getCollisionRectangleRectangle, getCollisionSat, getDistance, getOfflineAudioContext, getPreciseTime, getTextureSourceSize, getVoronoiRegion$1 as getVoronoiRegion, getWebGpuBlendState, hours, inRange, intersectionCircleCircle, intersectionCircleEllipse, intersectionCirclePoly, intersectionEllipseEllipse, intersectionEllipsePoly, intersectionLineCircle, intersectionLineEllipse, intersectionLineLine, intersectionLinePoly, intersectionLineRect, intersectionPointCircle, intersectionPointEllipse, intersectionPointLine, intersectionPointPoint, intersectionPointPoly, intersectionPointRect, intersectionPolyPoly, intersectionRectCircle, intersectionRectEllipse, intersectionRectPoly, intersectionRectRect, intersectionSat, isAudioContextReady, isPowerOfTwo, isSupported, lerp, matchesIds, milliseconds, minutes, noop$1 as noop, normalizeIds, onAudioContextReady, parseGamepadDescriptor, quadraticCurveTo, radiansPerDegree, radiansToDegrees, rand, removeArrayItems, resetRenderStats, resolveDefinition, resolveGamepadDefinition, seconds, sign$1 as sign, stopEvent, supportsCodec, supportsEventOptions, supportsIndexedDb, supportsPointerEvents, supportsTouchEvents, supportsWebAudio, tau, trimRotation, webGl2PrimitiveArrayConstructors, webGl2PrimitiveByteSizeMapping, webGl2PrimitiveTypeNames };
19200
+ export { AbstractAssetFactory, AbstractMedia, AbstractWebGl2BatchedRenderer, AbstractWebGl2Renderer, AbstractWebGpuRenderer, AnimatedSprite, Application, ApplicationStatus, ArcadeStickGamepadMapping, AudioAnalyser, BinaryFactory, BlendModes, BlurFilter, Bounds, BufferTypes, BufferUsage, BundleLoadError, CacheFirstStrategy, CallbackRenderPass, Capabilities, ChannelOffset, ChannelSize, Circle, CircleGeometry, Clock, CollisionType, Color, ColorAffector, ColorFilter, Container, Drawable, DrawableShape, Ellipse, FactoryRegistry, Filter, Flags, FontFactory, ForceAffector, GameCubeGamepadMapping, Gamepad, GamepadChannel, GamepadControl, GamepadMapping, GamepadMappingFamily, GamepadPromptLayouts, GenericDualAnalogGamepadMapping, Geometry, Graphics, ImageFactory, IndexedDbDatabase, IndexedDbStore, Input, InputManager, Interval, JoyConLeftGamepadMapping, JoyConRightGamepadMapping, Json, JsonFactory, Keyboard, Line, Loader, Matrix, Mesh, Music, MusicFactory, NetworkOnlyStrategy, ObservableSize, ObservableVector, Particle, ParticleOptions, ParticleSystem, PlayStationGamepadMapping, Pointer, PointerState, PointerStateFlag, PolarVector, Polygon, Quadtree, Random, RapierPhysicsBinding, RapierPhysicsWorld, Rectangle, RenderBackendType, RenderNode, RenderTarget, RenderTargetPass, RenderTexture, RendererRegistry, RenderingPrimitives, Sampler, ScaleAffector, ScaleModes, Scene, SceneManager, SceneNode, Segment, Shader, ShaderAttribute, ShaderPrimitives, ShaderUniform, Signal, Size, Sound, SoundFactory, Sprite, SpriteFlags, Spritesheet, SteamControllerGamepadMapping, SvgAsset, SvgFactory, SwitchProGamepadMapping, Text, TextAsset, TextFactory, TextStyle, Texture, TextureFactory, Time, Timer, TorqueAffector, UniversalEmitter, Vector, Video, VideoFactory, View, ViewFlags, VoronoiRegion, VttAsset, VttFactory, WasmFactory, WebGl2Backend, WebGl2MeshRenderer, WebGl2ParticleRenderer, WebGl2PrimitiveRenderer, WebGl2RenderBuffer, WebGl2ShaderBlock, WebGl2SpriteRenderer, WebGl2VertexArrayObject, WebGpuBackend, WebGpuMeshRenderer, WebGpuParticleRenderer, WebGpuPrimitiveRenderer, WebGpuSpriteRenderer, WrapModes, XboxGamepadMapping, bezierCurveTo, buildCircle, buildEllipse, buildLine, buildPath, buildPolygon, buildRectangle, buildStar, builtInGamepadDefinitions, canvasSourceToDataUrl, clamp, createRapierPhysicsWorld, createRenderStats, createWebGl2ShaderProgram, decodeAudioData, defineAssetManifest, degreesPerRadian, degreesToRadians, determineMimeType, emptyArrayBuffer, getAudioContext, getCanvasSourceSize, getCollisionCircleCircle, getCollisionCircleRectangle, getCollisionPolygonCircle, getCollisionRectangleRectangle, getCollisionSat, getDistance, getOfflineAudioContext, getPreciseTime, getTextureSourceSize, getVoronoiRegion$1 as getVoronoiRegion, getWebGpuBlendState, hours, inRange, intersectionCircleCircle, intersectionCircleEllipse, intersectionCirclePoly, intersectionEllipseEllipse, intersectionEllipsePoly, intersectionLineCircle, intersectionLineEllipse, intersectionLineLine, intersectionLinePoly, intersectionLineRect, intersectionPointCircle, intersectionPointEllipse, intersectionPointLine, intersectionPointPoint, intersectionPointPoly, intersectionPointRect, intersectionPolyPoly, intersectionRectCircle, intersectionRectEllipse, intersectionRectPoly, intersectionRectRect, intersectionSat, isAudioContextReady, isPowerOfTwo, lerp, matchesIds, milliseconds, minutes, noop$1 as noop, normalizeIds, onAudioContextReady, parseGamepadDescriptor, quadraticCurveTo, radiansPerDegree, radiansToDegrees, rand, removeArrayItems, resetRenderStats, resolveDefinition, resolveGamepadDefinition, seconds, sign$1 as sign, stopEvent, supportsCodec, supportsEventOptions, supportsIndexedDb, supportsPointerEvents, supportsTouchEvents, supportsWebAudio, tau, trimRotation, webGl2PrimitiveArrayConstructors, webGl2PrimitiveByteSizeMapping, webGl2PrimitiveTypeNames };
19083
19201
  //# sourceMappingURL=exo.esm.js.map