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

Bundles Refactor #5675

Merged
merged 28 commits into from Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b7d15d3
wip
Maksims Sep 13, 2023
3751973
Bundles refactor
Maksims Sep 25, 2023
ddc24fe
Merge branch 'master' into bundles-refactor
Maksims Sep 25, 2023
33a340c
fix jsdoc
Maksims Sep 25, 2023
49fed73
Merge branch 'main' into bundles-refactor
Maksims Sep 26, 2023
953a5d0
@internal > @ignore
Maksims Sep 26, 2023
f0da59b
PR comments
Maksims Sep 26, 2023
ed664fa
engine-only example for bundle loading
Maksims Sep 28, 2023
51125b4
Merge branch 'main' into bundles-refactor
Maksims Sep 28, 2023
a656bb3
Merge branch 'main' into bundles-refactor
Maksims Oct 3, 2023
3777956
Merge branch 'main' into bundles-refactor
Maksims Oct 7, 2023
c60a3b8
Merge branch 'main' into bundles-refactor
Maksims Oct 13, 2023
6efe8ad
merge
Maksims Oct 24, 2023
3652a88
update bundle example to new examples format
Maksims Oct 24, 2023
67fb021
merge
Maksims Nov 10, 2023
4442d9c
Merge branch 'main' into bundles-refactor
Maksims Nov 14, 2023
1bed8e9
Merge branch 'main' into bundles-refactor
Maksims Nov 23, 2023
984a08a
Merge branch 'main' into bundles-refactor
Maksims Nov 28, 2023
cf9a172
Merge branch 'main' into bundles-refactor
Maksims Dec 12, 2023
a96d04f
edits based on PR review comments
Maksims Mar 4, 2024
ec11e79
merge
Maksims Mar 4, 2024
6a0108a
lint, fix
Maksims Mar 4, 2024
682e9b0
events docs
Maksims Mar 4, 2024
5b0b6ed
lazy initialize TextDecoder
Maksims Mar 4, 2024
2a24e92
example update
Maksims Mar 4, 2024
2927e30
linter
Maksims Mar 4, 2024
043847f
Merge branch 'main' into bundles-refactor
kpal81xd Mar 5, 2024
9a0f7ee
Merge branch 'main' into bundles-refactor
kpal81xd Mar 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file added examples/assets/bundles/bundle.tar
Binary file not shown.
Binary file added examples/bundle.tar
Binary file not shown.
6 changes: 6 additions & 0 deletions examples/src/examples/loaders/bundle/config.mjs
@@ -0,0 +1,6 @@
/**
* @type {import('../../../../types.mjs').ExampleConfig}
*/
export default {
WEBGPU_ENABLED: true
};
116 changes: 116 additions & 0 deletions examples/src/examples/loaders/bundle/example.mjs
@@ -0,0 +1,116 @@
import * as pc from 'playcanvas';
import { deviceType, rootPath } from '@examples/utils';

// The example demonstrates loading multiple assets from a single bundle file

// This tar file has been created by a command line:
// : cd engine/examples/
// : tar cvf bundle.tar assets/models/geometry-camera-light.glb assets/models/torus.png

const canvas = document.getElementById('application-canvas');
if (!(canvas instanceof HTMLCanvasElement)) {
throw new Error('No canvas found');
}

const assets = {
bundle: new pc.Asset('bundle', 'bundle', { url: '/static/assets/bundles/bundle.tar' }),
scene: new pc.Asset('scene', 'container', { url: 'assets/models/geometry-camera-light.glb' }),
torus: new pc.Asset('torus', 'container', { url: 'assets/models/torus.glb' })
};

// Bundle should list asset IDs in its data
assets.bundle.data = { assets: [assets.scene.id, assets.torus.id] };

const gfxOptions = {
deviceTypes: [deviceType],
glslangUrl: rootPath + '/static/lib/glslang/glslang.js',
twgslUrl: rootPath + '/static/lib/twgsl/twgsl.js'
};

const device = await pc.createGraphicsDevice(canvas, gfxOptions);
const createOptions = new pc.AppOptions();
createOptions.graphicsDevice = device;

createOptions.componentSystems = [pc.RenderComponentSystem, pc.CameraComponentSystem, pc.LightComponentSystem];
createOptions.resourceHandlers = [pc.TextureHandler, pc.ContainerHandler];

const app = new pc.AppBase(canvas);
app.init(createOptions);

// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);

// Ensure canvas is resized when window changes size
const resize = () => app.resizeCanvas();
window.addEventListener('resize', resize);
app.on('destroy', () => {
window.removeEventListener('resize', resize);
});

// load assets
// notice that scene and torus are loaded as blob's and only tar file is downloaded
const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets);
assetListLoader.load(() => {

app.start();

/**
* the array will store loaded cameras
* @type {pc.CameraComponent[]}
*/
let camerasComponents = null;

// glb lights use physical units
app.scene.physicalUnits = true;

// create an instance using render component
const entity = assets.scene.resource.instantiateRenderEntity();
app.root.addChild(entity);

// create an instance using render component
const entityTorus = assets.torus.resource.instantiateRenderEntity();
app.root.addChild(entityTorus);
entityTorus.setLocalPosition(0, 0, 2);

// find all cameras - by default they are disabled
camerasComponents = entity.findComponents("camera");
camerasComponents.forEach((component) => {

// set the aspect ratio to automatic to work with any window size
component.aspectRatioMode = pc.ASPECT_AUTO;

// set up exposure for physical units
component.aperture = 4;
component.shutter = 1 / 100;
component.sensitivity = 500;
});

/** @type {pc.LightComponent[]} */
const lightComponents = entity.findComponents("light");
lightComponents.forEach((component) => {
component.enabled = true;
});

let time = 0;
let activeCamera = 0;
app.on("update", function (dt) {
time -= dt;

entityTorus.rotateLocal(360 * dt, 0, 0);

// change the camera every few seconds
if (time <= 0) {
time = 2;

// disable current camera
camerasComponents[activeCamera].enabled = false;

// activate next camera
activeCamera = (activeCamera + 1) % camerasComponents.length;
camerasComponents[activeCamera].enabled = true;
}
});
});

export { app };
2 changes: 1 addition & 1 deletion src/core/event-handle.js
Expand Up @@ -97,7 +97,7 @@ class EventHandle {
/**
* Mark if event has been removed.
* @type {boolean}
* @internal
* @ignore
*/
set removed(value) {
if (!value) return;
Expand Down
84 changes: 76 additions & 8 deletions src/framework/asset/asset-registry.js
Expand Up @@ -26,6 +26,14 @@ import { Asset } from './asset.js';
* @param {Asset} [asset] - The loaded asset if no errors were encountered.
*/

/**
* Callback used by {@link ResourceLoader#load} and called when an asset is choosing a bundle
* to load from. Return a single bundle to ensure asset is loaded from it.
*
* @callback BundlesFilterCallback
* @param {import('../bundle/bundle.js').Bundle[]} bundles - List of bundles which contain the asset.
*/

/**
* Container for all assets that are available to this application. Note that PlayCanvas scripts
* are provided with an AssetRegistry instance as `app.assets`.
Expand Down Expand Up @@ -189,6 +197,13 @@ class AssetRegistry extends EventHandler {
*/
prefix = null;

/**
* BundleRegistry
*
* @type {import('../bundle/bundle-registry.js').BundleRegistry|null}
*/
bundles = null;

/**
* Create an instance of an AssetRegistry.
*
Expand Down Expand Up @@ -335,6 +350,15 @@ class AssetRegistry extends EventHandler {
* out when it is loaded.
*
* @param {Asset} asset - The asset to load.
* @param {object} [options] - Options for asset loading.
* @param {boolean} [options.bundlesIgnore] - If set to true, then asset will not try to load
* from a bundle. Defaults to false.
* @param {boolean} [options.force] - If set to true, then the check of asset being loaded or
* is already loaded is bypassed, which forces loading of asset regardless.
* @param {BundlesFilterCallback} [options.bundlesFilter] - A callback that will be called
* when loading an asset that is contained in any of the bundles. It provides an array of
* bundles and will ensure asset is loaded from bundle returned from a callback. By default
* smallest filesize bundle is choosen.
* @example
* // load some assets
* const assetsToLoad = [
Expand All @@ -352,16 +376,24 @@ class AssetRegistry extends EventHandler {
* app.assets.load(assetToLoad);
* });
*/
load(asset) {
load(asset, options) {
kpal81xd marked this conversation as resolved.
Show resolved Hide resolved
// do nothing if asset is already loaded
// note: lots of code calls assets.load() assuming this check is present
// don't remove it without updating calls to assets.load() with checks for the asset.loaded state
if (asset.loading || asset.loaded) {
if ((asset.loading || asset.loaded) && !options?.force) {
Maksims marked this conversation as resolved.
Show resolved Hide resolved
Maksims marked this conversation as resolved.
Show resolved Hide resolved
return;
}

const file = asset.file;

const _fireLoad = () => {
this.fire('load', asset);
this.fire('load:' + asset.id, asset);
if (file && file.url)
this.fire('load:url:' + file.url, asset);
asset.fire('load', asset);
};

// open has completed on the resource
const _opened = (resource) => {
if (resource instanceof Array) {
Expand All @@ -373,11 +405,28 @@ class AssetRegistry extends EventHandler {
// let handler patch the resource
this._loader.patch(asset, this);

this.fire('load', asset);
this.fire('load:' + asset.id, asset);
if (file && file.url)
this.fire('load:url:' + file.url, asset);
asset.fire('load', asset);
if (asset.type === 'bundle') {
const assetIds = asset.data.assets;
for (let i = 0; i < assetIds.length; i++) {
const assetInBundle = this._idToAsset.get(assetIds[i]);
if (assetInBundle && !assetInBundle.loaded) {
this.load(assetInBundle, { force: true });
}
}

if (asset.resource.loaded) {
_fireLoad();
} else {
this.fire('load:start', asset);
this.fire('load:start:' + asset.id, asset);
if (file && file.url)
this.fire('load:start:url:' + file.url, asset);
asset.fire('load:start', asset);
asset.resource.on('load', _fireLoad);
}
} else {
_fireLoad();
}
};

// load has completed on the resource
Expand Down Expand Up @@ -409,7 +458,26 @@ class AssetRegistry extends EventHandler {
this.fire('load:' + asset.id + ':start', asset);

asset.loading = true;
this._loader.load(asset.getFileUrl(), asset.type, _loaded, asset);

const fileUrl = asset.getFileUrl();

// mark bundle assets as loading
if (asset.type === 'bundle') {
const assetIds = asset.data.assets;
for (let i = 0; i < assetIds.length; i++) {
const assetInBundle = this._idToAsset.get(assetIds[i]);
if (!assetInBundle)
continue;

if (assetInBundle.loaded || assetInBundle.resource || assetInBundle.loading)
continue;

assetInBundle.loading = true;
}
}


this._loader.load(fileUrl, asset.type, _loaded, asset, options);
} else {
// asset has no file to load, open it directly
const resource = this._loader.open(asset.type, asset.data);
Expand Down
9 changes: 8 additions & 1 deletion src/framework/asset/asset.js
Expand Up @@ -133,7 +133,7 @@ class Asset extends EventHandler {
*
* @param {string} name - A non-unique but human-readable name which can be later used to
* retrieve the asset.
* @param {string} type - Type of asset. One of ["animation", "audio", "binary", "container",
* @param {string} type - Type of asset. One of ["animation", "audio", "binary", "bundle", "container",
* "cubemap", "css", "font", "json", "html", "material", "model", "script", "shader", "sprite",
* "template", text", "texture", "textureatlas"]
* @param {object} [file] - Details about the file the asset is made from. At the least must
Expand Down Expand Up @@ -197,6 +197,8 @@ class Asset extends EventHandler {
// This is where the loaded resource(s) will be
this._resources = [];

this.urlObject = null;

// a string-assetId dictionary that maps
// locale to asset id
this._i18n = {};
Expand Down Expand Up @@ -533,6 +535,11 @@ class Asset extends EventHandler {

const old = this._resources;

if (this.urlObject) {
URL.revokeObjectURL(this.urlObject);
this.urlObject = null;
}

// clear resources on the asset
this.resources = [];
this.loaded = false;
Expand Down