Asset Manager

TurboWarp カスタム拡張機能 · v0.13.0

すべての素材を、ひとつの名前で扱う。

Asset Manager は、Web 上の画像・音声、プロジェクト内のコスチューム・背景・音、 そして動的なテキストを登録します。表示、再生、アニメーション、キャッシュを、 共通のブロックとアセット名で操作できます。

URL・プロジェクト内素材
名前付きアセット
名前付きアセット
スプライト・ステージ・音

紙芝居DSL 4.0集約ランタイム

集約されたブロックも、このリファレンスを使います。

DSL 4.0ランタイムのパレットでAsset Managerの見出しを探し、その直下の ドキュメントボタンを押すと、このガイドが開きます。集約後もブロックの意味と名前は同じで、 ランタイムが拡張機能別の名前空間とアイコンを加えます。

クイックスタート

読み込む、登録する、使う。

Asset Manager は TurboWarp のレンダラー、音声機能、プロジェクト内リソースを利用するため、 サンドボックスを無効にして実行する必要があります。

  1. 拡張機能を読み込む

    TurboWarp で 拡張機能を追加 → カスタム拡張機能を選び、次の URL を貼り付けます。

    https://cdn.jsdelivr.net/npm/@kubohiroya/turbowarp-asset-manager@0.13.0/dist/asset-manager.js

    サンドボックスなしで実行を有効にします。

  2. リソースを登録する

    プロジェクトのほかの場所で使う短い名前を付けます。

    リソース [https://example.com/hero.png] を
      アセット [Hero] として登録する
  3. 名前でアセットを使う

    種類が合う表示・音声・アニメーションの各ブロックで同じ名前を使えます。

    このスプライトにアセット [Hero] を表示する

動作の仕組み

ひとつの登録簿が、素材と出力先をつなぎます。

登録時に、選んだ名前と素材の種類・場所を記録します。その後のブロックは名前から種類を調べ、 画像、音声、テキストのうち適切な処理へ自動的に振り分けます。

リソース識別子から表示・再生までの流れ

Web URL、プロジェクト内リソース、動的テキストを登録ブロックへ渡します。アセット登録簿が名前と 種類を記憶し、表示・再生・アニメーションのブロックがスプライト、ステージ、音声出力、 Animated Text のいずれかへ振り分けます。

どのリソース識別子を使えばよいですか?

素材リソース識別子登録されるもの
Web 上の画像・音声 https://example.com/asset.png ダウンロードしたデータをメモリーと IndexedDB に保存
スプライトのコスチューム costume:Sprite1:costume1 プロジェクト内の既存コスチュームへの参照
ステージの背景 backdrop:backdrop1 プロジェクト内の既存背景への参照
スプライト/ステージの音 sound:Sprite1:sound1 または sound:@stage:name プロジェクト内の既存の音への参照
動的テキスト text:Narration 実行時変数名。表示時に最新の値を読み取る
以前キャッシュした Web 素材 RESOURCE_ID を空欄にする アセット名に対応する IndexedDB のデータ

Web 素材とキャッシュの仕組み

URL を指定すると必ず新しいデータを取得し、同名のキャッシュを更新します。 リソース識別子を空欄にすると通信せず、同じ名前のデータを IndexedDB から読み込みます。 名前ごとの世代を確認してから IndexedDB とメモリーを更新するため、先に開始した古い通信が 遅れて完了しても、最後に開始した有効な登録を上書きしません。

メモリー上の登録簿を用意する 2 つの経路

URL を指定した場合は、リソースの取得、メディア種別の正規化、最新世代であることの確認、 同名の IndexedDB エントリーへの保存を行い、メモリーへ登録します。空のリソース識別子を 指定した場合は、通信せず同名の IndexedDB エントリーを読み、メモリーへ登録します。

Compositionホスト向けの正確なプロジェクト素材locator

バージョン0.8.0では、compositionホストがコスチューム、背景、音をstructured locatorで登録できます。前後の空白、./:、 制御文字を含むScratch名をそのまま照合します。structured locatorでは、登録に使う論理名も 既定でliteralに保持します。

await assets.registerProjectAsset({
  name: 'Costume.1 / presentation',
  locator: {
    kind: 'costume',
    target: 'Actor / presenter',
    name: 'Costume.1 / source'
  }
});

音のlocatorでtargetを省略するとStageを選びます。既存の文字列 resourceId形式には互換性があり、従来のtrimとcolon grammarを維持します。 embedded assetで論理名をliteralにする場合はnameMode: 'literal'を指定します。

Composition ホスト向け検証済みリモートキャッシュ

バージョン 0.6.0 では、app shell や複合拡張機能からブロックを追加せずに使える cache-first の経路を追加しました。これは上記の従来型「アセット名キー」のキャッシュとは別です。 ホストがネットワーク loader と、期待する SHA-256・バイト数・Content-Type を渡します。 IndexedDB の内容が正しければ通信せず返し、なければダウンロード後に検証を通ったデータだけを 保存します。取得元 URL や認証情報は保存しません。

import {
  createAssetManagerComposition,
  createVerifiedRemoteCacheDatabaseName
} from '@kubohiroya/turbowarp-asset-manager/composition';

const databaseName = createVerifiedRemoteCacheDatabaseName({
  id: story.cacheId,
  label: story.fileName
});
const assets = createAssetManagerComposition(undefined, {
  verifiedRemoteCache: {
    cacheIdentity: {id: story.cacheId, label: story.fileName, databaseName}
  }
});
const result = await assets.resolveVerifiedRemoteBinary(model, {
  load: async ({url}, {signal}) => {
    const response = await fetch(url, {signal});
    return {
      bytes: await response.arrayBuffer(),
      contentType: response.headers.get('content-type'),
      transferOwnership: true
    };
  }
});
永続キャッシュと、実体化後のメモリーは別々のライフサイクルで管理する

台本 manifest から安定した台本 ID と可読なファイル名を渡します。Asset Manager は台本ごとの IndexedDB を識別し、取得データを検証してホストへ返します。ホストは画像、音声、モデルとして 実体化し、scene または story の保持期間に従ってメモリー上のリソースを解放します。その際、 永続化された検証済みデータは削除しません。

保持期間・掃除ホスト側の責務
返されたバイナリー 解決後は呼び出し側が所有 登録後に参照を外します。transferOwnership: true なら JavaScript 上の全量コピーを 1 回減らせます。
実体化したメモリー retention: scene または retention: story 保持期間が終わったら releaseAsset または releaseAll を呼びます。
IndexedDB の検証済み bytes TTL、LRU、origin 全体の上限、または明示消去 容量表示と消去手段を提供します。メモリー解放だけではこのキャッシュを削除しません。
台本 catalog と lease 台本ごとの可読な DB 情報。実行中の DB は保護 実行中は lease を更新し、台本停止・session 終了時に解放します。

台本 DB は tw-kamishibai-assets-v1--opening-yaml--story-0001 のような名前です。 小さな共通 catalog には、理解可能な識別情報、サイズ、日時、実行時 lease だけを記録し、 アセット本体は入れません。app shell はこれを使って台本別キャッシュを一覧・掃除・削除できます。 有効な lease がある DB は掃除しません。既定の上限は 256 MiB と、ブラウザーが報告する origin 容量の 20% のうち小さい方で、未使用の台本 DB、次に LRU のレコードから削除します。30 日間使われていない レコードは削除対象です。IndexedDB の失敗や、他の実行中台本が容量を占有して保存できない場合は、 機械判読可能な warning を返しつつ、検証済み bytes 自体はメモリーで利用できます。

app のストレージ管理には listVerifiedRemoteStoryCachespruneVerifiedRemoteStoryCachesdeleteVerifiedRemoteStoryCache を使います。実行中の所有権には renewVerifiedRemoteStoryCacheLeasereleaseVerifiedRemoteStoryCacheLease を使います。DSL の retention: sceneretention: story はホスト側のメモリー保持方針です。 Asset Manager は YAML を解釈せず、これらを IndexedDB の TTL として扱いません。

複数ファイルを一括保存するtransactional bundle

バージョン 0.7.0 では、Teachable Machine Pose modelのようなself-contained assetを扱う block非公開のbinary bundle storeを追加しました。composition subpathをimportし、台本専用DBを 指定した上で、台本/source namespace、asset名、manifestのbundle integrityをすべての操作へ 結び付けます。

const assets = createAssetManagerComposition(undefined, {
  binaryBundleStore: {
    databaseName: `${story.cacheDatabaseName}--bundles-v1`
  }
});

await assets.putBinaryBundle({
  namespace: `${story.id}/${story.sourceIntegrity}`,
  name: 'RescuePose',
  integrity: pose.bundleIntegrity,
  files: [
    {path: 'model.json', size: model.size, integrity: model.integrity, bytes: model.bytes},
    {path: 'metadata.json', size: metadata.size, integrity: metadata.integrity, bytes: metadata.bytes},
    {path: 'weights.bin', size: weights.size, integrity: weights.integrity, bytes: weights.bytes}
  ]
});

const stored = await assets.getBinaryBundle({
  namespace: `${story.id}/${story.sourceIntegrity}`,
  name: 'RescuePose',
  integrity: pose.bundleIntegrity
});
1 transactionのcommit後にだけ完全なbundleを公開する

ホストが台本単位のkeyとbundle全fileを渡します。Asset Managerは宣言されたsizeとSHA-256を fileごとに検証し、bundleとmetadataを1つのIndexedDB transactionでcommitします。後のreadでも bundle全体を再検証し、missing、partial、corruptなrecordは返しません。

putBinaryBundleIDBTransaction.oncomplete 後にだけ完了し、入力bytesを defensive copyします。getBinaryBundle はfile path、size、canonical SHA-256を再検証して 返します。deleteBinaryBundle はdataとmetadataをatomicに削除し、 releaseBinaryStore は永続recordを消さずにstore instanceの処理を中止します。 TTL、LRU、bundle数、byte数の制限は、verified remote cacheやStandalone asset DBとは独立です。 失敗はpayload bytesを含めず、安定した ASSET_BINARY_BUNDLE_* codeで通知します。

埋め込みpackage用のsession限定backing

バージョン0.10.0では、現在のapplication package内にすでに含まれる大容量binary向けに、 別のcomposition契約を追加しました。上のpersistent bundle storeとは異なり、次回起動時に recordを再利用しません。1つの共有versioned DBを使い、全compound keyへ起動ごとのsession IDを 含め、正常disposeではそのsessionだけを削除します。verified remote cacheは変更しません。

const backing = await assets.createSessionBinaryBacking({
  policy: 'prefer',
  sessionId: crypto.randomUUID(),
  assets: embeddedManifest.assets,
  source: embeddedPackageSource,
  onFatalError(error) {
    stopRuntimeAndShowDiagnostic(error);
  }
});

const pose = await backing.get(poseAssetKey);
await backing.dispose();
起動時にmodeを一度だけ選び、session中は変更しない

disabled modeはIndexedDBを開かず元sourceを読みます。preferとrequiredはassetを一件ずつ書き、 commit済みrecordを検証します。preferがdirect modeを選べるのは、確立前のstorage availability failure後にpartial recordを削除できた場合だけです。session mode確立後のread失敗はすべてfatalで、 sourceを途中変更しません。

disabledはIndexedDBを一度も開きません。requiredはbackingを確立できなければ 起動失敗にします。preferがwarning付きdirect readへ移れるのは、確立前にIndexedDBが unavailable、blocked、quota不足、abortとなり、partial recordを削除できた場合だけです。 descriptor、size、SHA-256の不一致は常にfail closedです。session modeでは全assetのcommitと read-back成功後にだけsourceを解放します。その後のmissing、corrupt、integrity、abort、connection closeはfatalとなり、source再読込、暗黙再書込み、mode途中変更は行いません。短いheartbeat leaseと bounded startup cleanupにより、未期限の別tabを残してcrash sessionを回収します。renderer、audio decoder、model loaderの失敗はmaterialize診断としてstorage/source errorと分離します。

同名アセットを安全に置き換える仕組み

置換機能は、起動時に固定される 2 つのフラグで段階導入します。どちらも既定値は false です。利用するプロジェクトでは、拡張機能の読み込み前に次の設定を行います。

globalThis.__TW_ASSET_MANAGER_FEATURE_FLAGS__ = {
  ENABLE_LIVE_ASSET_REPLACEMENT: true,
  ENABLE_STRICT_ASSET_KIND_REPLACEMENT: true
};
新しい内容の準備が完了してからライブ置換を確定する

Asset Manager は新しい内容を準備し、登録簿を更新し、同じアセット名と公開種別に結び付いている 対象だけへ表示を適用し直し、成功後に古い所有リソースを解放します。準備や表示の再適用に失敗した 場合は、古い登録、表示、キャッシュを復元します。

画像とテキストはすぐに更新されます。テキストは最新の本文と全スタイルを読み直し、指定された アニメーションを先頭から開始します。再生中の音声は中断せず、次回再生から新しい登録を使います。 同名の確定処理は直列に実行されるため、開始済みの確定処理は一貫した状態まで完了し、最後に開始された 成功済み登録が残ります。後から開始した無効な試行は、先行する有効な登録を取り消しません。画像では 再適用前に Asset Manager が設定した renderer skin が現在も使われているか確認します。別の衣装変更や renderer 更新があった対象は古い表示管理から外し、その新しい表示を上書きしません。strict kind を 有効にすると、公開種別 externalcostumebackdropsoundtext 間の変更と、external の画像・音声間の 変更を拒否します。意図的に種類を変える場合だけ、先に古い登録を削除してください。

診断エラーコード

0.3.0から更新する場合: 従来の小文字種別トークンと asset registration error typeを比較しているスクリプトを更新してください。 0.4.0では、以下の大文字診断コードを返します。

利用者向けエラーは [Asset Manager][CODE] で始まり、操作名、asset/resource/actor 名、 修正ヒント、近い候補、元の例外を保持します。登録エラーの Reporter は最新のコードと関連名を返します。 同期失敗は同じ内容を二重ログせず throw し、バックグラウンドの再生・アニメーション失敗だけを 文脈付きで 1 回 console.error します。

コード確認すること
INVALID_ASSET_NAME空でない有効な登録名を使います。
ASSET_NOT_REGISTERED対象名を登録し、提示された近い候補を確認します。
ASSET_TYPE_MISMATCH操作が必要とする画像・音声・テキスト種別を使います。
ASSET_TYPE_CHANGE意図的な公開種別/external媒体種別変更では先に削除します。
SPRITE_NOT_FOUNDactor/対象名と描画対象が存在することを確認します。
SPRITE_NAME_AMBIGUOUSactor 対象へ一意な名前を付けます。
SOURCE_ASSET_NOT_FOUNDコスチューム、背景、音の名前を確認します。
RESOURCE_ID_INVALIDHTTP(S) または対応するプロジェクト内識別子を使います。
DEPENDENCY_MISSING必要な TurboWarp 拡張またはランタイム機能を読み込みます。
STYLE_PROPERTY_INVALID対応するテキストスタイルのプロパティを使います。
STYLE_VALUE_INVALID保存されたテキストスタイル値を修正します。
PLAYBACK_FAILED音声データとブラウザーの再生許可を確認します。
ANIMATION_FAILEDactor、アセット、Animated Text 依存を確認します。
REPLACEMENT_FAILED読み込み・表示準備を確認します。古い状態は維持されます。

アクターのアニメーションが進む仕組み

ASSETSDURATIONS が時間軸を表します。画像ならアクターの見た目を変え、 音声なら再生を開始します。長さが 0 のとき、次の素材も同じ時点で開始します。

例:ASSETS = NoonSkin,Bell,NextSkinDURATIONS = 0,1,2

開始時に NoonSkin を表示します。区切りの長さが 0 なので Bell も同時に再生します。 1 秒後に NextSkin を表示します。ループならさらに 2 秒待って最初へ戻ります。

動的テキストがスプライトに届く仕組み

テキストアセットでは、サンドボックスなしの補助拡張機能を 2 つ使います。 Temporary Variables が本文とスタイルを保持し、Animated Text がスプライトやクローンに描画します。

表示のたびに最新の本文とスタイルを読み直す

Narration テキストを登録し、本文と必要なスタイルを設定します。表示ブロックが実行されると、 Asset Manager は Temporary Variables の最新値を読み、すべてのスタイルを適用し直して、 Animated Text に対象スプライトへの表示を依頼します。表示中の本文を更新すると、そのアセットを 表示中のすべての対象が更新されます。

実践的な使用例

よく使う操作

Web の画像を表示する

リソース [https://example.com/card.png] を
  アセット [Card] として登録する
このスプライトにアセット [Card] を表示する

ダウンロードした画像は、ブラウザーのキャッシュにも Card という名前で保存されます。

プロジェクト内のコスチュームを使う

リソース [costume:Hero:running] を
  アセット [HeroRunning] として登録する
このスプライトにアセット [HeroRunning] を表示する

画像は複製されず、プロジェクトが持つ既存コスチュームを参照します。

音を再生する

リソース [sound:@stage:opening] を
  アセット [Opening] として登録する
アセット [Opening] を終わるまで再生する

再生直後に次の処理へ進めたい場合は、待たない再生ブロックを使います。

スタイル付き動的テキストを表示する

リソース [text:Narration] を
  アセット [Narration] として登録する
テキストアセット [Narration] を [むかしむかし…] にする
[Narration] のスタイル [animation] を [typing] にする
このスプライトにアセット [Narration] を表示する

テキストを表示する前に Temporary Variables と Animated Text を読み込んでください。

ブロックガイド

やりたいことからブロックを選ぶ

やりたいこと使うブロック重要な動作
素材を登録するregister resource … as asset …URL はキャッシュを更新し、空欄はキャッシュを読みます。
登録失敗を調べるasset registration error type/label安定した診断コードと関連名を返します。
登録を確認するasset … is loadedどの種類の登録済みアセットにも使えます。
スプライト/クローンに表示するshow asset … on this sprite画像とテキストを表示できます。
ステージを変えるset stage backdrop to asset …外部画像、コスチューム、背景に使えます。
音声を再生するplay asset … as soundスクリプトを待たせるときは「終わるまで」を使います。
アクターを動かすloop actor … / play actor … once画像と音声をひとつのリストに混在できます。
動的テキストを更新するset text asset …そのテキストを表示中の対象も更新されます。
登録を解放するdelete asset … from memoryプロジェクト所有のコスチュームや音は削除しません。

トラブルシューティング

表示・再生されないとき

外部 URL の読み込みに失敗する
URL が HTTP または HTTPS で、直接開けること、TurboWarp エディターのオリジンを許可する CORS ヘッダーが返ることを確認します。Web 取得に失敗すると asset registration error typeREPLACEMENT_FAILED を返します。
コスチューム、背景、音が見つからない
名前を正確に合わせてください。省略形が曖昧になる場合は costume:Hero:running の ようにすべて指定します。asset registration error typeasset registration error label で見つからなかった素材を確認できます。コードは SOURCE_ASSET_NOT_FOUND、素材元のスプライト自体がない場合は SPRITE_NOT_FOUND です。
同名での再登録が拒否される
ASSET_TYPE_CHANGE は、strict kind により公開種別または external の画像・音声種別の 変更が検出されたことを表します。意図的な変更の場合だけ、既存のメモリー登録を先に削除します。 REPLACEMENT_FAILED の場合は新内容の準備・再適用に失敗しており、古い登録と管理中の 表示が維持されています。
テキストが空になる/依存拡張機能のエラーが出る
Temporary Variables と Animated Text の両方をサンドボックスなしで読み込み、表示前に本文を 設定します。登録済みでも本文が未設定のテキストアセットは、仕様どおり空文字列を表示します。
アニメーションの長さリストが拒否される
ループではアセットと同じ個数、1 回再生ではアセットより 1 個少ない長さを指定します。 すべて 0 以上である必要があり、ループには 1 個以上の正の長さが必要です。
キャッシュしたアセットを復元できない
キャッシュは登録時と完全に同じ名前で検索され、現在のブラウザーの IndexedDB に保存されます。 ブラウザーデータの消去、プライベートセッション、別のアプリプロファイルでは以前のデータを 利用できないことがあります。