Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support assetBundle #510

Merged
merged 9 commits into from
Nov 21, 2024
Merged
87 changes: 72 additions & 15 deletions src/AssetManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
VideoAssetConfigurationBase,
VectorImageAssetConfigurationBase,
BinaryAssetConfigurationBase,
ModuleMainPathsMap
ModuleMainPathsMap,
AssetBundleConfiguration,

Check failure on line 16 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

'"@akashic/game-configuration"' has no exported member named 'AssetBundleConfiguration'. Did you mean 'AssetConfiguration'?

Check failure on line 16 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

'"@akashic/game-configuration"' has no exported member named 'AssetBundleConfiguration'. Did you mean 'AssetConfiguration'?

Check failure on line 16 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

'"@akashic/game-configuration"' has no exported member named 'AssetBundleConfiguration'. Did you mean 'AssetConfiguration'?

Check failure on line 16 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

'"@akashic/game-configuration"' has no exported member named 'AssetBundleConfiguration'. Did you mean 'AssetConfiguration'?
BundledAssetConfiguration

Check failure on line 17 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

'"@akashic/game-configuration"' has no exported member named 'BundledAssetConfiguration'. Did you mean 'AssetConfiguration'?

Check failure on line 17 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

'"@akashic/game-configuration"' has no exported member named 'BundledAssetConfiguration'. Did you mean 'AssetConfiguration'?

Check failure on line 17 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

'"@akashic/game-configuration"' has no exported member named 'BundledAssetConfiguration'. Did you mean 'AssetConfiguration'?

Check failure on line 17 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

'"@akashic/game-configuration"' has no exported member named 'BundledAssetConfiguration'. Did you mean 'AssetConfiguration'?
} from "@akashic/game-configuration";
import type {
Asset,
Expand All @@ -32,6 +34,7 @@
import type { AssetManagerLoadHandler } from "./AssetManagerLoadHandler";
import type { AudioSystem } from "./AudioSystem";
import type { AudioSystemManager } from "./AudioSystemManager";
import { BundledScriptAsset } from "./auxiliary/BundledScriptAsset";
import { EmptyBinaryAsset } from "./auxiliary/EmptyBinaryAsset";
import { EmptyGeneratedVectorImageAsset } from "./auxiliary/EmptyGeneratedVectorImageAsset";
import { EmptyVectorImageAsset } from "./auxiliary/EmptyVectorImageAsset";
Expand Down Expand Up @@ -261,6 +264,11 @@
*/
private _generatedAssetCount: number;

/**
* アセットバンドル。
*/
private _assetBundle: AssetBundleConfiguration | null;

/**
* `AssetManager` のインスタンスを生成する。
*
Expand Down Expand Up @@ -290,6 +298,7 @@
this._refCounts = {};
this._loadings = {};
this._generatedAssetCount = 0;
this._assetBundle = null;

const assetIds = Object.keys(this.configuration);
for (let i = 0; i < assetIds.length; ++i) {
Expand All @@ -313,6 +322,7 @@
this._liveAssetPathTable = undefined!;
this._refCounts = undefined!;
this._loadings = undefined!;
this._assetBundle = undefined!;
}

/**
Expand Down Expand Up @@ -359,12 +369,28 @@
}

/**
* プリロードすべきスクリプトアセットのIDを全て返す
* プリロードすべきスクリプトアセットの path を全て返す
*/
preloadScriptAssetIds(): string[] {
return Object.entries(this.configuration)
.filter(([, conf]) => conf.type === "script" && conf.global && conf.preload)
.map(([assetId]) => assetId);
preloadScriptAssetPaths(): string[] {
let assetPaths: string[] = [];

if (this._assetBundle) {
assetPaths.push(
...Object.entries(this._assetBundle.assets)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assetId の方使わなくなったので Object.values() でよさそうです。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

69f3a1e にて修正しました。

.filter(([, conf]) => conf.type === "script" && conf.preload)

Check failure on line 380 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

'conf' is of type 'unknown'.

Check failure on line 380 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

'conf' is of type 'unknown'.

Check failure on line 380 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

'conf' is of type 'unknown'.

Check failure on line 380 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

'conf' is of type 'unknown'.

Check failure on line 380 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

'conf' is of type 'unknown'.

Check failure on line 380 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

'conf' is of type 'unknown'.

Check failure on line 380 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

'conf' is of type 'unknown'.

Check failure on line 380 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

'conf' is of type 'unknown'.
.map(([, conf]) => conf.path)

Check failure on line 381 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

'conf' is of type 'unknown'.

Check failure on line 381 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

'conf' is of type 'unknown'.

Check failure on line 381 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

'conf' is of type 'unknown'.

Check failure on line 381 in src/AssetManager.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

'conf' is of type 'unknown'.
);
}

assetPaths.push(
...Object.entries(this.configuration)
.filter(([, conf]) => conf.type === "script" && conf.global && conf.preload)
.map(([, conf]) => conf.virtualPath!) // この箇所ではすでに virtualPath が補完されていることが前提
);

assetPaths = assetPaths.map(path => (path.startsWith("./") ? path : `./${path}`));

return assetPaths;
}

/**
Expand Down Expand Up @@ -563,6 +589,15 @@
return "/" + virtualPath;
}

/**
* アセットバンドルを設定する。
*
* @param assetBundle アセットバンドル
*/
setAssetBundle(assetBundle: AssetBundleConfiguration | null): void {
this._assetBundle = assetBundle;
}

/**
* @ignore
*/
Expand Down Expand Up @@ -648,7 +683,23 @@
let id: string;
let uri: string;
let conf: AssetConfiguration | DynamicAssetConfiguration;
if (typeof idOrConf === "string") {
if (this._assetBundle && typeof idOrConf === "string") {
const id = idOrConf;
const conf = this._assetBundle.assets[id] as BundledAssetConfiguration;
const type = conf.type;
switch (type) {
case "script":
const asset = new BundledScriptAsset({
id,
...conf
});
return asset;
default:
throw ExceptionFactory.createAssertionError(
`AssertionError#_createAssetFor: unknown asset type ${type} for asset ID: ${id}`
);
}
} else if (typeof idOrConf === "string") {
id = idOrConf;
conf = this.configuration[id];
uri = this.configuration[id].path;
Expand Down Expand Up @@ -849,17 +900,23 @@
*/
_addAssetToTables(asset: OneOfAsset): void {
this._assets[asset.id] = asset;
let path: string | undefined;

// DynamicAsset の場合は configuration に書かれていないので以下の判定が偽になる
if (this.configuration[asset.id]) {
const virtualPath = this.configuration[asset.id].virtualPath!; // virtualPath の存在は _normalize() で確認済みのため 非 null アサーションとする
if (!this._liveAssetVirtualPathTable.hasOwnProperty(virtualPath)) {
this._liveAssetVirtualPathTable[virtualPath] = asset;
} else {
if (this._liveAssetVirtualPathTable[virtualPath].path !== asset.path)
throw ExceptionFactory.createAssertionError("AssetManager#_onAssetLoad(): duplicated asset path");
}
if (!this._liveAssetPathTable.hasOwnProperty(asset.path)) this._liveAssetPathTable[asset.path] = virtualPath;
path = this.configuration[asset.id].virtualPath!; // virtualPath の存在は _normalize() で確認済みのため 非 null アサーションとする
} else if (this._assetBundle && this._assetBundle.assets[asset.id]) {
path = this._assetBundle.assets[asset.id].path;
}

if (!path) return;

if (!this._liveAssetVirtualPathTable.hasOwnProperty(path)) {
this._liveAssetVirtualPathTable[path] = asset;
} else {
if (this._liveAssetVirtualPathTable[path].path !== asset.path)
throw ExceptionFactory.createAssertionError("AssetManager#_onAssetLoad(): duplicated asset path");
}
if (!this._liveAssetPathTable.hasOwnProperty(asset.path)) this._liveAssetPathTable[asset.path] = path;
}
}
17 changes: 9 additions & 8 deletions src/Game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type { EventFilter } from "./EventFilter";
import { ExceptionFactory } from "./ExceptionFactory";
import type { GameHandlerSet } from "./GameHandlerSet";
import type { GameMainParameterObject } from "./GameMainParameterObject";
import { InitialScene } from "./InitialScene";
import { LoadingScene } from "./LoadingScene";
import type { LocalTickModeString } from "./LocalTickModeString";
import { ModuleManager } from "./ModuleManager";
Expand All @@ -33,7 +34,7 @@ import { OperationPluginManager } from "./OperationPluginManager";
import type { InternalOperationPluginOperation } from "./OperationPluginOperation";
import { PointEventResolver } from "./PointEventResolver";
import type { RandomGenerator } from "./RandomGenerator";
import { Scene } from "./Scene";
import type { Scene } from "./Scene";
import type { SnapshotSaveRequest } from "./SnapshotSaveRequest";
import { SurfaceAtlasSet } from "./SurfaceAtlasSet";
import type { TickGenerationModeString } from "./TickGenerationModeString";
Expand Down Expand Up @@ -640,7 +641,7 @@ export class Game {
* グローバルアセットを読み込むための初期シーン。必ずシーンスタックの一番下に存在する。これをpopScene()することはできない。
* @private
*/
_initialScene: Scene;
_initialScene: InitialScene;

/**
* デフォルトローディングシーン。
Expand Down Expand Up @@ -1009,13 +1010,13 @@ export class Game {

this.onUpdate = new Trigger<void>();

this._initialScene = new Scene({
this._initialScene = new InitialScene({
game: this,
assetIds: this._assetManager.globalAssetIds(),
local: true,
name: "akashic:initial-scene"
});
this._initialScene.onLoad.add(this._handleInitialSceneLoad, this);
this._initialScene.onAllAssetsLoad.add(this._handleInitialSceneLoad, this);

this._reset({ age: 0 });
}
Expand Down Expand Up @@ -2013,11 +2014,11 @@ export class Game {
}
this.operationPlugins = this.operationPluginManager.plugins;

const preloadAssetIds = this._assetManager.preloadScriptAssetIds();
for (const preloadAssetId of preloadAssetIds) {
const fun = this._moduleManager._internalRequire(preloadAssetId);
const preloadAssetPaths = this._assetManager.preloadScriptAssetPaths();
for (const preloadAssetPath of preloadAssetPaths) {
const fun = this._moduleManager._internalRequire(preloadAssetPath);
if (!fun || typeof fun !== "function")
throw ExceptionFactory.createAssertionError(`Game#_handleLoad: ${preloadAssetId} has no-exported function.`);
throw ExceptionFactory.createAssertionError(`Game#_handleLoad: ${preloadAssetPath} has no-exported function.`);
fun();
}

Expand Down
45 changes: 45 additions & 0 deletions src/InitialScene.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { AssetBundleConfiguration } from "@akashic/game-configuration";

Check failure on line 1 in src/InitialScene.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

'"@akashic/game-configuration"' has no exported member named 'AssetBundleConfiguration'. Did you mean 'AssetConfiguration'?

Check failure on line 1 in src/InitialScene.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

'"@akashic/game-configuration"' has no exported member named 'AssetBundleConfiguration'. Did you mean 'AssetConfiguration'?

Check failure on line 1 in src/InitialScene.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

'"@akashic/game-configuration"' has no exported member named 'AssetBundleConfiguration'. Did you mean 'AssetConfiguration'?

Check failure on line 1 in src/InitialScene.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

'"@akashic/game-configuration"' has no exported member named 'AssetBundleConfiguration'. Did you mean 'AssetConfiguration'?
import { Trigger } from "@akashic/trigger";
import type { SceneParameterObject } from "./Scene";
import { Scene } from "./Scene";

/**
* グローバルアセットを読み込むための初期シーン。
*/
export class InitialScene extends Scene {
/**
* ゲームの実行に必要なグローバルアセットがすべて読み込まれた際に発火される Trigger。
* `gameConfiguration` に `assetBundle` が指定されている場合は、そのアセットもすべて読み込み完了後に発火される。
* 一方、`this.onLoad` は `gameConfiguration` の `assetBundle` 指定を無視して発火する点に注意が必要。
*/
onAllAssetsLoad: Trigger<void>;

constructor(param: SceneParameterObject) {
super(param);
this.onAllAssetsLoad = new Trigger();
this.onLoad.add(this._handleLoad, this);
}

override destroy(): void {
super.destroy();
if (!this.onAllAssetsLoad.destroyed()) {
this.onAllAssetsLoad.destroy();
}
this.onAllAssetsLoad = undefined!;
}

_handleLoad(): void {
if (this.game._configuration.assetBundle) {

Check failure on line 32 in src/InitialScene.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

Property 'assetBundle' does not exist on type 'GameConfiguration'.

Check failure on line 32 in src/InitialScene.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

Property 'assetBundle' does not exist on type 'GameConfiguration'.

Check failure on line 32 in src/InitialScene.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

Property 'assetBundle' does not exist on type 'GameConfiguration'.

Check failure on line 32 in src/InitialScene.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

Property 'assetBundle' does not exist on type 'GameConfiguration'.
const assetBundle: AssetBundleConfiguration = this.game._moduleManager._internalRequire(this.game._configuration.assetBundle);

Check failure on line 33 in src/InitialScene.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

Property 'assetBundle' does not exist on type 'GameConfiguration'.

Check failure on line 33 in src/InitialScene.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

Property 'assetBundle' does not exist on type 'GameConfiguration'.

Check failure on line 33 in src/InitialScene.ts

View workflow job for this annotation

GitHub Actions / Node 18.x / ubuntu-latest

Property 'assetBundle' does not exist on type 'GameConfiguration'.

Check failure on line 33 in src/InitialScene.ts

View workflow job for this annotation

GitHub Actions / Node 20.x / ubuntu-latest

Property 'assetBundle' does not exist on type 'GameConfiguration'.
this.game._assetManager.setAssetBundle(assetBundle);
const assetIds = Object.keys(assetBundle.assets);
this.requestAssets(assetIds, this._handleRequestAssets.bind(this));
} else {
this.onAllAssetsLoad.fire();
}
}

_handleRequestAssets(): void {
this.onAllAssetsLoad.fire();
}
}
9 changes: 6 additions & 3 deletions src/ModuleManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,17 @@ export class ModuleManager {

if (currentModule) {
if (!currentModule._virtualDirname) {
throw ExceptionFactory.createAssertionError("g._require.resolve: couldn't resolve the moudle path without virtualPath");
throw ExceptionFactory.createAssertionError("g._require.resolve: couldn't resolve the module path without virtualPath");
}
resolvedPath = PathUtil.resolvePath(currentModule._virtualDirname, path);
} else {
if (!/^\.\//.test(path)) {
if (/^\.\//.test(path)) {
resolvedPath = path.substring(2);
} else if (/^\//.test(path)) {
resolvedPath = path.substring(1);
} else {
throw ExceptionFactory.createAssertionError("g._require.resolve: entry point path must start with './'");
}
resolvedPath = path.substring(2);
}

// 2.a. LOAD_AS_FILE(Y + X)
Expand Down
22 changes: 11 additions & 11 deletions src/__tests__/AssetManagerSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -739,35 +739,35 @@ describe("test AssetManager", () => {
assets: {
asset1: {
type: "script",
path: "/path/to/real/file",
virtualPath: "path/to/virtual/file",
path: "/path/to/real/file1",
virtualPath: "path/to/virtual/file1",
global: true
},
asset2: {
type: "script",
path: "/path/to/real/file",
virtualPath: "path/to/virtual/file",
path: "/path/to/real/file2",
virtualPath: "path/to/virtual/file2",
global: true,
preload: false
},
asset3: {
type: "script",
path: "/path/to/real/file",
virtualPath: "path/to/virtual/file",
path: "/path/to/real/file3",
virtualPath: "path/to/virtual/file3",
global: true,
preload: true
},
asset4: {
type: "script",
path: "/path/to/real/file",
virtualPath: "path/to/virtual/file",
path: "/path/to/real/file4",
virtualPath: "path/to/virtual/file4",
global: true,
preload: 0 as any
},
asset5: {
type: "script",
path: "/path/to/real/file",
virtualPath: "path/to/virtual/file",
path: "/path/to/real/file5",
virtualPath: "path/to/virtual/file5",
global: true,
preload: true
}
Expand All @@ -778,7 +778,7 @@ describe("test AssetManager", () => {
const manager = game._assetManager;

// NOTE: 配列の順序は実装依存であることに注意
expect(manager.preloadScriptAssetIds()).toEqual(["asset3", "asset5"]);
expect(manager.preloadScriptAssetPaths()).toEqual(["./path/to/virtual/file3", "./path/to/virtual/file5"]);
});

it("can get accessorPath from assetId", async () => {
Expand Down
6 changes: 3 additions & 3 deletions src/__tests__/GameSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1398,12 +1398,12 @@ describe("test Game", () => {
});

const loadScene = game._defaultLoadingScene;
expect(game._initialScene.onLoad.contains(game._handleInitialSceneLoad, game)).toBe(true);
expect(game._initialScene.onAllAssetsLoad.contains(game._handleInitialSceneLoad, game)).toBe(true);
expect(loadScene.onLoad.contains(loadScene._doReset, loadScene)).toBe(false);

game._loadAndStart();
expect(game.isLoaded).toBe(false); // _loadAndStartしたがまだ読み込みは終わっていない
expect(game._initialScene.onLoad.contains(game._handleInitialSceneLoad, game)).toBe(true);
expect(game._initialScene.onAllAssetsLoad.contains(game._handleInitialSceneLoad, game)).toBe(true);
expect(game.scenes.length).toBe(2);
expect(game.scenes[0]).toBe(game._initialScene);
expect(game.scenes[1]).toBe(loadScene);
Expand All @@ -1413,7 +1413,7 @@ describe("test Game", () => {
expect(loadScene2).not.toBe(loadScene);
expect(loadScene.destroyed()).toBe(true);
expect(game.isLoaded).toBe(false);
expect(game._initialScene.onLoad.contains(game._handleInitialSceneLoad, game)).toBe(true);
expect(game._initialScene.onAllAssetsLoad.contains(game._handleInitialSceneLoad, game)).toBe(true);
expect(loadScene2.onLoad.contains(loadScene2._doReset, loadScene2)).toBe(false);
expect(game.scenes.length).toBe(0);

Expand Down
Loading
Loading