@kubohiroya/turbowarp-asset-cache 0.6.0 → 0.6.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.
package/README.ja.md CHANGED
@@ -54,6 +54,11 @@ Standalone拡張機能を使います。
54
54
  importし、registryを内部に閉じたまま、パレットへAsset Cacheのblockを追加したくない場合はComposition APIを
55
55
  使います。
56
56
 
57
+ 独立したプレビューVMでは、`createAssetManagerComposition.supportsRuntimeIsolation === true`を確認し、
58
+ `createAssetManagerComposition(undefined, {runtime: previewVm.runtime})`で描画とイベント購読の所有先を指定します。
59
+ 省略時は従来どおり拡張機能のホストruntimeを使います。終了時の`releaseAll()`は、そのcompositionが所有する
60
+ `STOP_FOR_TARGET`購読も解除します。同じcompositionへ再登録すると必要な購読を再開します。
61
+
57
62
  ## 動作条件と安全性
58
63
 
59
64
  standaloneでの利用には、TurboWarp Desktop、またはunsandboxedなカスタム拡張機能を読み込める他の
@@ -83,14 +88,14 @@ SVG入力は既定で拒否します。remote binaryのcache entryは、保存
83
88
  versionを付けたnpm packageにも、同じレビュー済みビルドが入っています。
84
89
 
85
90
  ```bash
86
- pnpm add --save-exact @kubohiroya/turbowarp-asset-cache@0.6.0
91
+ pnpm add --save-exact @kubohiroya/turbowarp-asset-cache@0.6.1
87
92
  ```
88
93
 
89
94
  `node_modules/@kubohiroya/turbowarp-asset-cache/dist/asset-cache.js`を読み込むか、version固定の
90
95
  CDN URLを使います。
91
96
 
92
97
  ```text
93
- https://cdn.jsdelivr.net/npm/@kubohiroya/turbowarp-asset-cache@0.6.0/dist/asset-cache.js
98
+ https://cdn.jsdelivr.net/npm/@kubohiroya/turbowarp-asset-cache@0.6.1/dist/asset-cache.js
94
99
  ```
95
100
 
96
101
  ## クイックスタート
package/README.md CHANGED
@@ -66,13 +66,13 @@ The built JavaScript file is committed to this repository so users do not need t
66
66
  The versioned npm package contains the same reviewed build:
67
67
 
68
68
  ```bash
69
- pnpm add --save-exact @kubohiroya/turbowarp-asset-cache@0.6.0
69
+ pnpm add --save-exact @kubohiroya/turbowarp-asset-cache@0.6.1
70
70
  ```
71
71
 
72
72
  Load `node_modules/@kubohiroya/turbowarp-asset-cache/dist/asset-cache.js`, or use the version-pinned CDN URL:
73
73
 
74
74
  ```text
75
- https://cdn.jsdelivr.net/npm/@kubohiroya/turbowarp-asset-cache@0.6.0/dist/asset-cache.js
75
+ https://cdn.jsdelivr.net/npm/@kubohiroya/turbowarp-asset-cache@0.6.1/dist/asset-cache.js
76
76
  ```
77
77
 
78
78
  ## Quick Start
@@ -2341,9 +2341,9 @@ function validateProjectAssetAddress(runtime, assetName, resourceIdentifier) {
2341
2341
  }
2342
2342
  }
2343
2343
  var AssetManagerExtension = class {
2344
- constructor(featureFlags = FEATURE_FLAGS) {
2345
- _defineProperty(this, "runtime", Scratch.vm.runtime);
2346
- _defineProperty(this, "renderer", this.runtime.renderer);
2344
+ constructor(featureFlags = FEATURE_FLAGS, runtime = Scratch.vm.runtime) {
2345
+ _defineProperty(this, "runtime", void 0);
2346
+ _defineProperty(this, "renderer", void 0);
2347
2347
  _defineProperty(this, "externalAssets", /* @__PURE__ */ new Map());
2348
2348
  _defineProperty(this, "costumeAssets", /* @__PURE__ */ new Map());
2349
2349
  _defineProperty(this, "soundAssets", /* @__PURE__ */ new Map());
@@ -2393,6 +2393,12 @@ var AssetManagerExtension = class {
2393
2393
  this.unbindNamedDataRegistryLifecycle = void 0;
2394
2394
  });
2395
2395
  _defineProperty(this, "fixedEditors", fixedInputRegistration(Scratch));
2396
+ _defineProperty(this, "listeningForTargetStops", false);
2397
+ _defineProperty(this, "releaseStoppedTarget", (target) => {
2398
+ if (target && !this.runtime.targets.includes(target)) this.displayedAssets.delete(target.id);
2399
+ });
2400
+ this.runtime = runtime;
2401
+ this.renderer = runtime.renderer;
2396
2402
  this.featureFlags = Object.freeze({ ...featureFlags });
2397
2403
  if (this.featureFlags.NAMED_ASSET_BODY_PROVIDER) {
2398
2404
  this.namedBodyProviderValue = this.createNamedBodyProvider();
@@ -2401,9 +2407,19 @@ var AssetManagerExtension = class {
2401
2407
  this.unbindNamedDataRegistryLifecycle = bindNamedDataRegistryLifecycle(this.runtime, registry);
2402
2408
  this.startListeningForNamedBodyLifecycle();
2403
2409
  }
2404
- this.runtime.on?.("STOP_FOR_TARGET", (target) => {
2405
- if (target && !this.runtime.targets.includes(target)) this.displayedAssets.delete(target.id);
2406
- });
2410
+ this.ensureRuntimeListeners();
2411
+ }
2412
+ /** A reusable composition reattaches its own listener when registering new assets. */
2413
+ ensureRuntimeListeners() {
2414
+ if (this.listeningForTargetStops || !this.runtime.on) return;
2415
+ this.runtime.on("STOP_FOR_TARGET", this.releaseStoppedTarget);
2416
+ this.listeningForTargetStops = true;
2417
+ }
2418
+ /** Release only this instance's subscription, leaving other runtime owners untouched. */
2419
+ releaseRuntimeListeners() {
2420
+ if (!this.listeningForTargetStops || !this.runtime.off) return;
2421
+ this.runtime.off("STOP_FOR_TARGET", this.releaseStoppedTarget);
2422
+ this.listeningForTargetStops = false;
2407
2423
  }
2408
2424
  setLoadingBackdrop(args) {
2409
2425
  this.loadingBackdropName = normalizeName(args.NAME);
@@ -2293,8 +2293,8 @@ function validateProjectAssetAddress(runtime, assetName, resourceIdentifier) {
2293
2293
  }
2294
2294
  }
2295
2295
  var AssetManagerExtension = class {
2296
- runtime = Scratch.vm.runtime;
2297
- renderer = this.runtime.renderer;
2296
+ runtime;
2297
+ renderer;
2298
2298
  externalAssets = /* @__PURE__ */ new Map();
2299
2299
  costumeAssets = /* @__PURE__ */ new Map();
2300
2300
  soundAssets = /* @__PURE__ */ new Map();
@@ -2344,7 +2344,13 @@ var AssetManagerExtension = class {
2344
2344
  this.unbindNamedDataRegistryLifecycle = void 0;
2345
2345
  };
2346
2346
  fixedEditors = fixedInputRegistration(Scratch);
2347
- constructor(featureFlags = FEATURE_FLAGS) {
2347
+ listeningForTargetStops = false;
2348
+ releaseStoppedTarget = (target) => {
2349
+ if (target && !this.runtime.targets.includes(target)) this.displayedAssets.delete(target.id);
2350
+ };
2351
+ constructor(featureFlags = FEATURE_FLAGS, runtime = Scratch.vm.runtime) {
2352
+ this.runtime = runtime;
2353
+ this.renderer = runtime.renderer;
2348
2354
  this.featureFlags = Object.freeze({ ...featureFlags });
2349
2355
  if (this.featureFlags.NAMED_ASSET_BODY_PROVIDER) {
2350
2356
  this.namedBodyProviderValue = this.createNamedBodyProvider();
@@ -2353,9 +2359,19 @@ var AssetManagerExtension = class {
2353
2359
  this.unbindNamedDataRegistryLifecycle = bindNamedDataRegistryLifecycle(this.runtime, registry);
2354
2360
  this.startListeningForNamedBodyLifecycle();
2355
2361
  }
2356
- this.runtime.on?.("STOP_FOR_TARGET", (target) => {
2357
- if (target && !this.runtime.targets.includes(target)) this.displayedAssets.delete(target.id);
2358
- });
2362
+ this.ensureRuntimeListeners();
2363
+ }
2364
+ /** A reusable composition reattaches its own listener when registering new assets. */
2365
+ ensureRuntimeListeners() {
2366
+ if (this.listeningForTargetStops || !this.runtime.on) return;
2367
+ this.runtime.on("STOP_FOR_TARGET", this.releaseStoppedTarget);
2368
+ this.listeningForTargetStops = true;
2369
+ }
2370
+ /** Release only this instance's subscription, leaving other runtime owners untouched. */
2371
+ releaseRuntimeListeners() {
2372
+ if (!this.listeningForTargetStops || !this.runtime.off) return;
2373
+ this.runtime.off("STOP_FOR_TARGET", this.releaseStoppedTarget);
2374
+ this.listeningForTargetStops = false;
2359
2375
  }
2360
2376
  setLoadingBackdrop(args) {
2361
2377
  this.loadingBackdropName = normalizeName(args.NAME);
@@ -8732,7 +8748,10 @@ async function createSessionBinaryBacking(inputValue, optionValue = {}, operatio
8732
8748
  //#region src/composition.ts
8733
8749
  function createAssetManagerComposition(featureFlags, options = {}) {
8734
8750
  if (!options || typeof options !== "object" || Array.isArray(options)) throw new TypeError("Asset Cache composition options must be an object.");
8735
- const extension = featureFlags ? new AssetManagerExtension(featureFlags) : new AssetManagerExtension();
8751
+ const runtimeInput = options.runtime === void 0 ? Scratch.vm.runtime : options.runtime;
8752
+ if (options.runtime !== void 0 && (!runtimeInput || typeof runtimeInput !== "object" || !Array.isArray(runtimeInput.targets) || !runtimeInput.renderer || typeof runtimeInput.renderer !== "object")) throw new TypeError("Asset Cache composition runtime must provide targets and renderer.");
8753
+ const runtime = runtimeInput;
8754
+ const extension = new AssetManagerExtension(featureFlags, runtime);
8736
8755
  const ownedNames = /* @__PURE__ */ new Map();
8737
8756
  let literalNameSequence = 0;
8738
8757
  let verifiedRemoteCache = null;
@@ -9012,7 +9031,6 @@ function createAssetManagerComposition(featureFlags, options = {}) {
9012
9031
  previous?.resource.release();
9013
9032
  return resource;
9014
9033
  }
9015
- const runtime = Scratch.vm.runtime;
9016
9034
  let listeningForDOMImageLifecycle = false;
9017
9035
  const releaseStoppedTargetDOMImageResources = (target) => {
9018
9036
  if (target && !runtime.targets.includes(target)) releaseDOMImageResourcesForOwner(target.id);
@@ -9036,6 +9054,7 @@ function createAssetManagerComposition(featureFlags, options = {}) {
9036
9054
  return Object.freeze({
9037
9055
  async registerProjectAsset(input) {
9038
9056
  if (!input || typeof input !== "object" || Array.isArray(input)) throw new TypeError("Project asset registration input must be an object.");
9057
+ extension.ensureRuntimeListeners();
9039
9058
  const hasLocator = Object.hasOwn(input, "locator");
9040
9059
  if (hasLocator === Object.hasOwn(input, "resourceId")) throw new TypeError("Provide exactly one project asset locator or resourceId.");
9041
9060
  const claimed = claimName(input.name, input.nameMode ?? (hasLocator ? "literal" : "trimmed"));
@@ -9045,6 +9064,7 @@ function createAssetManagerComposition(featureFlags, options = {}) {
9045
9064
  }));
9046
9065
  },
9047
9066
  async registerEmbeddedAsset(input) {
9067
+ extension.ensureRuntimeListeners();
9048
9068
  if (!input || typeof input !== "object" || Array.isArray(input)) throw new TypeError("Embedded asset registration input must be an object.");
9049
9069
  const claimed = claimName(input.name, input.nameMode ?? "trimmed");
9050
9070
  return trackRegistration(claimed, extension.registerEmbeddedAsset({
@@ -9057,9 +9077,13 @@ function createAssetManagerComposition(featureFlags, options = {}) {
9057
9077
  },
9058
9078
  releaseAsset,
9059
9079
  releaseAll() {
9060
- releaseAllDOMImageResources();
9061
- audioGeneration++;
9062
9080
  const errors = [];
9081
+ try {
9082
+ releaseAllDOMImageResources();
9083
+ } catch (error) {
9084
+ errors.push(error);
9085
+ }
9086
+ audioGeneration++;
9063
9087
  for (const playback of audioPlaybacks) try {
9064
9088
  playback.dispose();
9065
9089
  } catch (error) {
@@ -9070,6 +9094,11 @@ function createAssetManagerComposition(featureFlags, options = {}) {
9070
9094
  } catch (error) {
9071
9095
  errors.push(error);
9072
9096
  }
9097
+ try {
9098
+ extension.releaseRuntimeListeners();
9099
+ } catch (error) {
9100
+ errors.push(error);
9101
+ }
9073
9102
  if (errors.length > 0) throw new AggregateError(errors, "Failed to release assets");
9074
9103
  },
9075
9104
  isRegistered(name) {
@@ -9101,7 +9130,7 @@ function createAssetManagerComposition(featureFlags, options = {}) {
9101
9130
  applyToTarget(name, target) {
9102
9131
  return extension.setThisSpriteSkin({ NAME: ownedName(name)?.internal ?? name }, {
9103
9132
  target,
9104
- runtime: Scratch.vm.runtime
9133
+ runtime
9105
9134
  });
9106
9135
  },
9107
9136
  playSound(name, options = {}) {
@@ -9187,5 +9216,7 @@ function createAssetManagerComposition(featureFlags, options = {}) {
9187
9216
  }
9188
9217
  });
9189
9218
  }
9219
+ createAssetManagerComposition.supportsRuntimeIsolation = true;
9220
+ Object.defineProperty(createAssetManagerComposition, "supportsRuntimeIsolation", { writable: false });
9190
9221
  //#endregion
9191
9222
  export { NAMED_ASSET_BODY_NAMESPACE, NamedAssetBodyError, createAssetManagerComposition, createBinaryBundleStore, createIndexedDBBinaryObjectStore, createOpfsBinaryObjectStore, createSessionBinaryBacking, createVerifiedRemoteBinaryCache, createVerifiedRemoteCacheDatabaseName };
@@ -65,7 +65,20 @@ export interface DOMImageResourceBindingOptions {
65
65
  readonly attribute?: 'href' | 'src';
66
66
  readonly owner?: AssetManagerCompositionTarget;
67
67
  }
68
+ /** Public runtime boundary; rendering and asset capabilities are checked when used. */
69
+ export interface AssetManagerCompositionRuntime {
70
+ readonly renderer: object;
71
+ readonly targets: readonly object[];
72
+ readonly storage?: unknown;
73
+ readonly stageWidth?: number;
74
+ on?(eventName: string, listener: (target?: AssetManagerCompositionTarget) => void): unknown;
75
+ off?(eventName: string, listener: (target?: AssetManagerCompositionTarget) => void): unknown;
76
+ requestRedraw?(): unknown;
77
+ getOpcodeFunction?(opcode: string): unknown;
78
+ }
68
79
  export interface AssetManagerCompositionOptions {
80
+ /** Explicit owner for independent preview/player runtimes; defaults to the extension host. */
81
+ readonly runtime?: AssetManagerCompositionRuntime;
69
82
  readonly verifiedRemoteCache?: VerifiedRemoteBinaryCacheOptions;
70
83
  readonly binaryBundleStore?: BinaryBundleStoreOptions;
71
84
  readonly sessionBinaryBacking?: SessionBinaryBackingOptions;
@@ -112,3 +125,6 @@ export interface AssetManagerComposition {
112
125
  createSessionBinaryBacking(input: SessionBinaryBackingInput, options?: BinaryBundleOperationOptions): Promise<SessionBinaryBacking>;
113
126
  }
114
127
  export declare function createAssetManagerComposition(featureFlags?: AssetManagerFeatureFlags, options?: AssetManagerCompositionOptions): AssetManagerComposition;
128
+ export declare namespace createAssetManagerComposition {
129
+ var supportsRuntimeIsolation: true;
130
+ }
@@ -124,7 +124,13 @@ export declare class AssetManagerExtension {
124
124
  private readonly releaseNamedBodySnapshotsForLifecycle;
125
125
  private readonly unregisterNamedBodyProviderForRuntimeDispose;
126
126
  private readonly fixedEditors;
127
- constructor(featureFlags?: AssetManagerFeatureFlags);
127
+ private listeningForTargetStops;
128
+ private readonly releaseStoppedTarget;
129
+ constructor(featureFlags?: AssetManagerFeatureFlags, runtime?: TurboWarpRuntime);
130
+ /** A reusable composition reattaches its own listener when registering new assets. */
131
+ ensureRuntimeListeners(): void;
132
+ /** Release only this instance's subscription, leaving other runtime owners untouched. */
133
+ releaseRuntimeListeners(): void;
128
134
  setLoadingBackdrop(args: BlockArgs): void;
129
135
  setLoadingCostumes(args: BlockArgs): void;
130
136
  prepareLoadingAssets(args: BlockArgs, util: ScratchBlockUtility): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubohiroya/turbowarp-asset-cache",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "A browser-local image, audio, and runtime-text asset cache for TurboWarp projects.",
5
5
  "author": {
6
6
  "name": "Hiroya Kubo",