diff --git a/CHANGELOG.md b/CHANGELOG.md
index e303ae12..6bb2bd75 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,6 +38,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
+- Fixed issue where the Loader could run twice even if already loaded when included in the scene loader.
+- Fixed issue where pixel ratio was accidentally doubled during load if the loader was included in the scene loader.
- Fixed issue where Slide trasition did not work properly when DisplayMode was FitScreenAndFill
- Fixed issue where not(tags) and not(component) queries weren't updating in the querymanager
- Fixed Tilemap/Isometric map pointer performance on moderate to large maps, we changed the strategy to only consider tiles under the pointer instead of try to do sorted dispatch on NxM tiles.
diff --git a/package.json b/package.json
index db955ff5..97d9e02b 100644
--- a/package.json
+++ b/package.json
@@ -71,6 +71,7 @@
"test:watch": "npm run test -- --watch",
"test:unit": "vitest --project unit",
"test:unit:headless": "npm run test:unit -- --browser.headless",
+ "test:debug:unit:headless": "npm run test:unit -- --browser.headless --inspect --no-file-parallelism",
"test:visual": "vitest --project visual",
"test:visual:headless": "npm run test:visual -- --browser.headless",
"apidocs": "node scripts/apidocs.js",
diff --git a/sandbox/tests/loader-scene/index.html b/sandbox/tests/loader-scene/index.html
new file mode 100644
index 00000000..5aa6b4b3
--- /dev/null
+++ b/sandbox/tests/loader-scene/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+ Document
+
+
+
+
+
diff --git a/sandbox/tests/loader-scene/index.ts b/sandbox/tests/loader-scene/index.ts
new file mode 100644
index 00000000..a2bf3ac0
--- /dev/null
+++ b/sandbox/tests/loader-scene/index.ts
@@ -0,0 +1,35 @@
+console.log('hello world');
+
+var img = new ex.ImageSource(
+ 'data:image/png;base64,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'
+);
+
+var loader = new ex.Loader();
+
+loader.addResource(img);
+
+class Scene extends ex.Scene {
+ onInitialize() {
+ const a = new ex.Actor({
+ pos: ex.vec(400, 300),
+ width: 100,
+ height: 100
+ });
+
+ a.graphics.use(img.toSprite());
+ somecoolgame.add(a);
+ }
+}
+
+var somecoolgame = new ex.Engine({
+ displayMode: ex.DisplayMode.FitScreen,
+ width: 800,
+ height: 600,
+ pixelRatio: 2,
+ pixelArt: true,
+ scenes: {
+ scene: { scene: Scene, loader }
+ }
+}) as any;
+
+somecoolgame.start('scene', { loader });
diff --git a/src/engine/Director/DefaultLoader.ts b/src/engine/Director/DefaultLoader.ts
index ab972a7c..f4da2e7b 100644
--- a/src/engine/Director/DefaultLoader.ts
+++ b/src/engine/Director/DefaultLoader.ts
@@ -111,6 +111,7 @@ export class DefaultLoader implements Loadable[]> {
*/
public addResource(loadable: Loadable) {
this._resources.push(loadable);
+ this._loaded = false;
}
/**
@@ -124,6 +125,7 @@ export class DefaultLoader implements Loadable[]> {
for (i; i < len; i++) {
this.addResource(loadables[i]);
}
+ this._loaded = false;
}
public markResourceComplete(): void {
@@ -138,11 +140,13 @@ export class DefaultLoader implements Loadable[]> {
return total > 0 ? clamp(this._numLoaded, 0, total) / total : 1;
}
+ private _loaded = false;
+ private _isLoading = false;
/**
* Returns true if the loader has completely loaded all resources
*/
public isLoaded() {
- return this._numLoaded === this._resources.length;
+ return this._loaded || this._resources.length === 0;
}
private _totalTimeMs = 0;
@@ -185,15 +189,17 @@ export class DefaultLoader implements Loadable[]> {
ctx.restore();
}
- private _loadingFuture = new Future();
+ private _resourcesLoadedFuture = new Future();
public areResourcesLoaded() {
if (this._resources.length === 0) {
// special case no resources mean loaded;
return Promise.resolve();
}
- return this._loadingFuture.promise;
+ return this._resourcesLoadedFuture.promise;
}
+ private _loaderCompleteFuture = new Future[]>();
+
/**
* Not meant to be overridden
*
@@ -201,20 +207,33 @@ export class DefaultLoader implements Loadable[]> {
* that resolves when loading of all is complete AND the user has interacted with the loading screen
*/
public async load(): Promise[]> {
+ if (this._isLoading) {
+ return this._loaderCompleteFuture.promise;
+ }
+ if (this.isLoaded()) {
+ // Already loaded quick exit
+ return (this.data = this._resources);
+ }
+ this._isLoading = true;
+ this._loaderCompleteFuture = new Future();
await this.onBeforeLoad();
this.events.emit('beforeload');
this.canvas.flagDirty();
await Promise.all(
- this._resources.map(async (r) => {
- this.events.emit('loadresourcestart', r);
- await r.load().finally(() => {
- // capture progress
- this._numLoaded++;
- this.canvas.flagDirty();
- this.events.emit('loadresourceend', r);
- });
- })
+ this._resources
+ .filter((r) => {
+ return !r.isLoaded();
+ })
+ .map(async (r) => {
+ this.events.emit('loadresourcestart', r);
+ await r.load().finally(() => {
+ // capture progress
+ this._numLoaded++;
+ this.canvas.flagDirty();
+ this.events.emit('loadresourceend', r);
+ });
+ })
);
// Wire all sound to the engine
@@ -224,7 +243,7 @@ export class DefaultLoader implements Loadable[]> {
}
}
- this._loadingFuture.resolve();
+ this._resourcesLoadedFuture.resolve();
this.canvas.flagDirty();
// Unlock browser AudioContext in after user gesture
// See: https://github.com/excaliburjs/Excalibur/issues/262
@@ -235,6 +254,9 @@ export class DefaultLoader implements Loadable[]> {
await this.onAfterLoad();
this.events.emit('afterload');
+ this._isLoading = false;
+ this._loaded = true;
+ this._loaderCompleteFuture.resolve(this._resources);
return (this.data = this._resources);
}
diff --git a/src/engine/Director/Loader.ts b/src/engine/Director/Loader.ts
index 2a34fee5..a9558066 100644
--- a/src/engine/Director/Loader.ts
+++ b/src/engine/Director/Loader.ts
@@ -327,7 +327,7 @@ export class Loader extends DefaultLoader {
public override async onBeforeLoad(): Promise {
this.screen.pushResolutionAndViewport();
- this.screen.resolution = { width: this.canvas.width, height: this.canvas.height };
+ this.screen.resolution = { width: this.screen.resolution.width, height: this.screen.resolution.height };
this.screen.applyResolutionAndViewport();
const image = this._image;
await this._imageLoaded.promise;
diff --git a/src/spec/vitest/LoaderSpec.ts b/src/spec/vitest/LoaderSpec.ts
index dd0eb13a..6a20c644 100644
--- a/src/spec/vitest/LoaderSpec.ts
+++ b/src/spec/vitest/LoaderSpec.ts
@@ -295,6 +295,26 @@ describe('A loader', () => {
expect(oldPos).not.toEqual(newPos);
});
+ it('should not load more than once when added to the engine and scene', async () => {
+ const img = new ex.ImageSource(
+ 'data:image/png;base64,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'
+ );
+
+ const loader = new ex.DefaultLoader();
+
+ const onBeforeLoad = vi.spyOn(loader, 'onBeforeLoad').mockImplementation(async () => await Promise.resolve());
+ const onAfterLoad = vi.spyOn(loader, 'onAfterLoad').mockImplementation(async () => await Promise.resolve());
+ const onUserAction = vi.spyOn(loader, 'onUserAction').mockImplementation(async () => await Promise.resolve());
+
+ loader.addResource(img);
+
+ engine.addScene('scene', { scene: ex.Scene, loader });
+ await engine.start('scene', { loader });
+ expect(onBeforeLoad).toHaveBeenCalledOnce();
+ expect(onAfterLoad).toHaveBeenCalledOnce();
+ expect(onUserAction).toHaveBeenCalledOnce();
+ });
+
it('does not throw when more than 256 images are being loaded', () =>
new Promise((done) => {
/**