Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 17 additions & 23 deletions packages/core/src/asset/ResourceManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,32 +339,33 @@ export class ResourceManager {
this._loadingPromises = null;
}

private _assignDefaultOptions(assetInfo: LoadItem): LoadItem {
assetInfo.type = assetInfo.type ?? ResourceManager._getTypeByUrl(assetInfo.url);
private _assignDefaultOptions(assetInfo: LoadItem, remoteConfig?: EditorResourceItem): void {
assetInfo.type ??= remoteConfig?.type ?? ResourceManager._getTypeByUrl(assetInfo.url);
if (assetInfo.type === undefined) {
throw `asset type should be specified: ${assetInfo.url}`;
}
assetInfo.retryCount = assetInfo.retryCount ?? this.retryCount;
assetInfo.timeout = assetInfo.timeout ?? this.timeout;
assetInfo.retryInterval = assetInfo.retryInterval ?? this.retryInterval;
assetInfo.url = assetInfo.url ?? assetInfo.urls.join(",");
return assetInfo;
}

private _loadSingleItem<T>(itemOrURL: LoadItem | string): AssetPromise<T> {
const item = this._assignDefaultOptions(typeof itemOrURL === "string" ? { url: itemOrURL } : itemOrURL);
let { url } = item;

// Not absolute and base url is set
if (!Utils.isAbsoluteUrl(url) && this.baseUrl) url = Utils.resolveAbsoluteUrl(this.baseUrl, url);

const item = typeof itemOrURL === "string" ? { url: itemOrURL } : itemOrURL;
item.url = item.url ?? item.urls.join(",");
// Parse url
const { assetBaseURL, queryPath } = this._parseURL(url);
const { assetBaseURL, queryPath } = this._parseURL(item.url);
const paths = queryPath ? this._parseQueryPath(queryPath) : [];

// Get remote asset base url
const remoteConfig = this._virtualPathResourceMap[assetBaseURL];
const remoteAssetBaseURL = remoteConfig?.path ?? assetBaseURL;
this._assignDefaultOptions(item, remoteConfig);

// Not absolute and base url is set
item.url =
!Utils.isAbsoluteUrl(assetBaseURL) && this.baseUrl
? Utils.resolveAbsoluteUrl(this.baseUrl, assetBaseURL)
: assetBaseURL;
const remoteAssetBaseURL = remoteConfig?.path ?? item.url;

// Check cache
const cacheObject = this._assetUrlPool[remoteAssetBaseURL];
Expand Down Expand Up @@ -413,35 +414,28 @@ export class ResourceManager {
// Check whether load main asset
const mainPromise =
loadingPromises[remoteAssetBaseURL] ||
this._loadSubpackageAndMainAsset(loader, item, remoteAssetBaseURL, assetBaseURL, subpackageName);
this._loadSubpackageAndMainAsset(loader, item, remoteAssetBaseURL, subpackageName);
mainPromise.catch((e) => {
this._onSubAssetFail(remoteAssetBaseURL, queryPath, e);
});

return this._createSubAssetPromiseCallback<T>(remoteAssetBaseURL, remoteAssetURL, queryPath);
}

return this._loadSubpackageAndMainAsset(loader, item, remoteAssetBaseURL, assetBaseURL, subpackageName);
return this._loadSubpackageAndMainAsset(loader, item, remoteAssetBaseURL, subpackageName);
}

// For adapter mini-game platform
private _loadSubpackageAndMainAsset<T>(
loader: Loader<T>,
item: LoadItem,
remoteAssetBaseURL: string,
assetBaseURL: string,
subpackageName: string
): AssetPromise<T> {
return this._loadMainAsset(loader, item, remoteAssetBaseURL, assetBaseURL);
return this._loadMainAsset(loader, item, remoteAssetBaseURL);
}

private _loadMainAsset<T>(
loader: Loader<T>,
item: LoadItem,
remoteAssetBaseURL: string,
assetBaseURL: string
): AssetPromise<T> {
item.url = assetBaseURL;
private _loadMainAsset<T>(loader: Loader<T>, item: LoadItem, remoteAssetBaseURL: string): AssetPromise<T> {
const loadingPromises = this._loadingPromises;
const promise = loader.load(item, this);
loadingPromises[remoteAssetBaseURL] = promise;
Expand Down
89 changes: 88 additions & 1 deletion tests/src/core/resource/ResourceManager.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AssetType, ResourceManager, Texture2D } from "@galacean/engine";
import { AssetPromise, AssetType, ResourceManager, Texture2D } from "@galacean/engine";
import "@galacean/engine-loader";
import { WebGLEngine } from "@galacean/engine";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
Expand Down Expand Up @@ -87,4 +87,91 @@ describe("ResourceManager", () => {
}
});
});

describe("virtualPath loading", () => {
it("infers loader type from virtualPathResourceMap when type is omitted", () => {
const resourceManager = engine.resourceManager;
resourceManager.initVirtualResources([
{ virtualPath: "Assets/extensionless", path: "https://cdn.ali.com/a.json", type: AssetType.Texture }
]);
// @ts-ignore
const loaderSpy = vi
.spyOn(ResourceManager._loaders[AssetType.Texture], "load")
.mockReturnValue(new AssetPromise(() => {}));

resourceManager.load({ url: "Assets/extensionless" });

expect(loaderSpy).toHaveBeenCalled();
expect(loaderSpy.mock.calls[0][0].type).equal(AssetType.Texture);
loaderSpy.mockRestore();
});

it("shares the main asset across sub-asset queries", () => {
const resourceManager = engine.resourceManager;
// @ts-ignore
const loaderSpy = vi
.spyOn(ResourceManager._loaders[AssetType.GLTF], "load")
.mockReturnValue(new AssetPromise(() => {}));

resourceManager.load("https://cdn.ali.com/shared.glb?q=materials[0]");
resourceManager.load("https://cdn.ali.com/shared.glb?q=materials[1]");

expect(loaderSpy).toHaveBeenCalledTimes(1);
loaderSpy.mockRestore();
});

it("keeps the explicit type over the virtualPath map type", () => {
const resourceManager = engine.resourceManager;
resourceManager.initVirtualResources([
{ virtualPath: "Assets/explicit", path: "https://cdn.ali.com/x.json", type: AssetType.Texture }
]);
// @ts-ignore
const loaderSpy = vi
.spyOn(ResourceManager._loaders[AssetType.GLTF], "load")
.mockReturnValue(new AssetPromise(() => {}));

resourceManager.load({ url: "Assets/explicit", type: AssetType.GLTF });

expect(loaderSpy).toHaveBeenCalled();
expect(loaderSpy.mock.calls[0][0].type).equal(AssetType.GLTF);
loaderSpy.mockRestore();
});

it("resolves virtualPath via map even when baseUrl is set", () => {
const resourceManager = engine.resourceManager;
resourceManager.initVirtualResources([
{ virtualPath: "Assets/withBaseUrl", path: "https://cdn.ali.com/real.json", type: AssetType.Texture }
]);
// @ts-ignore
const loaderSpy = vi
.spyOn(ResourceManager._loaders[AssetType.Texture], "load")
.mockReturnValue(new AssetPromise(() => {}));
resourceManager.baseUrl = "https://base.com/app/";

try {
resourceManager.load({ url: "Assets/withBaseUrl" });
expect(loaderSpy).toHaveBeenCalled();
expect(loaderSpy.mock.calls[0][0].type).equal(AssetType.Texture);
} finally {
resourceManager.baseUrl = null;
loaderSpy.mockRestore();
}
});

it("merges urls into a single url before parsing", () => {
const resourceManager = engine.resourceManager;
// @ts-ignore
const loaderSpy = vi
.spyOn(ResourceManager._loaders[AssetType.KTXCube], "load")
.mockReturnValue(new AssetPromise(() => {}));

resourceManager.load({
type: AssetType.KTXCube,
urls: ["px.ktx", "nx.ktx", "py.ktx", "ny.ktx", "pz.ktx", "nz.ktx"]
});

expect(loaderSpy).toHaveBeenCalled();
loaderSpy.mockRestore();
});
});
});
Loading