diff --git a/front/package.json b/front/package.json index 9a17f4a0..86308a89 100644 --- a/front/package.json +++ b/front/package.json @@ -47,6 +47,7 @@ "@types/simple-peer": "^9.11.1", "@types/socket.io-client": "^1.4.32", "axios": "^0.21.2", + "cancelable-promise": "^4.2.1", "cross-env": "^7.0.3", "deep-copy-ts": "^0.5.0", "easystarjs": "^0.4.4", diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index e1fd3a5a..dc984c6d 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -15,6 +15,7 @@ import { TexturesHelper } from "../Helpers/TexturesHelper"; import type { PictureStore } from "../../Stores/PictureStore"; import { Unsubscriber, Writable, writable } from "svelte/store"; import { createColorStore } from "../../Stores/OutlineColorStore"; +import type CancelablePromise from "cancelable-promise"; const playerNameY = -25; @@ -43,12 +44,13 @@ export abstract class Character extends Container { private readonly _pictureStore: Writable; private readonly outlineColorStore = createColorStore(); private readonly outlineColorStoreUnsubscribe: Unsubscriber; + private texturePromise: CancelablePromise | undefined; constructor( scene: GameScene, x: number, y: number, - texturesPromise: Promise, + texturesPromise: CancelablePromise, name: string, direction: PlayerAnimationDirections, moving: boolean, @@ -66,7 +68,7 @@ export abstract class Character extends Container { this._pictureStore = writable(undefined); //textures are inside a Promise in case they need to be lazyloaded before use. - texturesPromise + this.texturePromise = texturesPromise .then((textures) => { this.addTextures(textures, frame); this.invisible = false; @@ -81,6 +83,9 @@ export abstract class Character extends Container { this.invisible = false; this.playAnimation(direction, moving); }); + }) + .finally(() => { + this.texturePromise = undefined; }); this.playerName = new Text(scene, 0, playerNameY, name, { @@ -326,6 +331,7 @@ export abstract class Character extends Container { this.scene.sys.updateList.remove(sprite); } } + this.texturePromise?.cancel(); this.list.forEach((objectContaining) => objectContaining.destroy()); this.outlineColorStoreUnsubscribe(); super.destroy(); diff --git a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts index 58fcf84c..40e68427 100644 --- a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts +++ b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts @@ -1,6 +1,7 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin; import type { CharacterTexture } from "../../Connexion/LocalUser"; import { BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES } from "./PlayerTextures"; +import CancelablePromise from "cancelable-promise"; export interface FrameConfig { frameWidth: number; @@ -30,7 +31,7 @@ export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptio export const loadCustomTexture = ( loaderPlugin: LoaderPlugin, texture: CharacterTexture -): Promise => { +): CancelablePromise => { const name = "customCharacterTexture" + texture.id; const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level }; return createLoadingPromise(loaderPlugin, playerResourceDescriptor, { @@ -42,8 +43,8 @@ export const loadCustomTexture = ( export const lazyLoadPlayerCharacterTextures = ( loadPlugin: LoaderPlugin, texturekeys: Array -): Promise => { - const promisesList: Promise[] = []; +): CancelablePromise => { + const promisesList: CancelablePromise[] = []; texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => { try { //TODO refactor @@ -60,12 +61,12 @@ export const lazyLoadPlayerCharacterTextures = ( console.error(err); } }); - let returnPromise: Promise>; + let returnPromise: CancelablePromise>; if (promisesList.length > 0) { loadPlugin.start(); - returnPromise = Promise.all(promisesList).then(() => texturekeys); + returnPromise = CancelablePromise.all(promisesList).then(() => texturekeys); } else { - returnPromise = Promise.resolve(texturekeys); + returnPromise = CancelablePromise.resolve(texturekeys); } //If the loading fail, we render the default model instead. @@ -98,10 +99,17 @@ export const createLoadingPromise = ( playerResourceDescriptor: BodyResourceDescriptionInterface, frameConfig: FrameConfig ) => { - return new Promise((res, rej) => { + return new CancelablePromise((res, rej, cancel) => { if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { return res(playerResourceDescriptor); } + + cancel(() => { + loadPlugin.off("loaderror"); + loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name); + return; + }); + loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig); const errorCallback = (file: { src: string }) => { if (file.src !== playerResourceDescriptor.img) return; diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index 1cd03d12..d8bb5388 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -3,6 +3,7 @@ import type { PointInterface } from "../../Connexion/ConnexionModels"; import { Character } from "../Entity/Character"; import type { PlayerAnimationDirections } from "../Player/Animation"; import { requestVisitCardsStore } from "../../Stores/GameStore"; +import type CancelablePromise from "cancelable-promise"; /** * Class representing the sprite of a remote player (a player that plays on another computer) @@ -17,7 +18,7 @@ export class RemotePlayer extends Character { x: number, y: number, name: string, - texturesPromise: Promise, + texturesPromise: CancelablePromise, direction: PlayerAnimationDirections, moving: boolean, visitCardUrl: string | null, diff --git a/front/src/Phaser/Login/AbstractCharacterScene.ts b/front/src/Phaser/Login/AbstractCharacterScene.ts index 6376498a..67e2ba3d 100644 --- a/front/src/Phaser/Login/AbstractCharacterScene.ts +++ b/front/src/Phaser/Login/AbstractCharacterScene.ts @@ -3,11 +3,12 @@ import { localUserStore } from "../../Connexion/LocalUserStore"; import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; import { loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; import type { CharacterTexture } from "../../Connexion/LocalUser"; +import type CancelablePromise from "cancelable-promise"; export abstract class AbstractCharacterScene extends ResizableScene { loadCustomSceneSelectCharacters(): Promise { const textures = this.getTextures(); - const promises: Promise[] = []; + const promises: CancelablePromise[] = []; if (textures) { for (const texture of textures) { if (texture.level === -1) { @@ -21,7 +22,7 @@ export abstract class AbstractCharacterScene extends ResizableScene { loadSelectSceneCharacters(): Promise { const textures = this.getTextures(); - const promises: Promise[] = []; + const promises: CancelablePromise[] = []; if (textures) { for (const texture of textures) { if (texture.level !== -1) { diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index c5134f1c..a6fe29a1 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -6,6 +6,7 @@ import { Character } from "../Entity/Character"; import { get } from "svelte/store"; import { userMovingStore } from "../../Stores/GameStore"; import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore"; +import type CancelablePromise from "cancelable-promise"; export const hasMovedEventName = "hasMoved"; export const requestEmoteEventName = "requestEmote"; @@ -20,7 +21,7 @@ export class Player extends Character { x: number, y: number, name: string, - texturesPromise: Promise, + texturesPromise: CancelablePromise, direction: PlayerAnimationDirections, moving: boolean, companion: string | null, diff --git a/front/yarn.lock b/front/yarn.lock index 329bce4d..3572e44b 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -1352,6 +1352,11 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== +cancelable-promise@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/cancelable-promise/-/cancelable-promise-4.2.1.tgz#b02f79c5dde2704acfff1bc1ac2b4090f55541fe" + integrity sha512-PJZ/000ocWhPZQBAuNewAOMA2WEkJ8RhXI6AxeGLiGdW8EYDmumzo9wKyNgjDgxc1q/HbXuTdlcI+wXrOe/jMw== + caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"