Adding wokalist type checking on front

Additionally, we are making superLoad.json accept a new "immediateCallback" parameter that resolves during the event handler (and not after)
This commit is contained in:
David Négrier 2022-03-21 10:33:25 +01:00
parent 2d3e2805a1
commit 705c75e3c7
18 changed files with 114 additions and 132 deletions

View File

@ -55,7 +55,8 @@
"query-string": "^6.13.3", "query-string": "^6.13.3",
"redis": "^3.1.2", "redis": "^3.1.2",
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0", "uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",
"uuidv4": "^6.0.7" "uuidv4": "^6.0.7",
"zod": "^3.12.0"
}, },
"devDependencies": { "devDependencies": {
"@types/busboy": "^0.2.3", "@types/busboy": "^0.2.3",

View File

@ -2258,3 +2258,8 @@ yn@3.1.1:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
zod@^3.12.0:
version "3.14.2"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.2.tgz#0b4ed79085c471adce0e7f2c0a4fbb5ddc516ba2"
integrity sha512-iF+wrtzz7fQfkmn60PG6XFxaWBhYYKzp2i+nv24WbLUWb2JjymdkHlzBwP0erpc78WotwP5g9AAu7Sk8GWVVNw==

View File

@ -119,7 +119,6 @@ export class Loader {
} }
public resize(): void { public resize(): void {
console.log("RESIZE TRIGGERED");
const loadingBarWidth: number = Math.floor(this.scene.game.renderer.width / 3); const loadingBarWidth: number = Math.floor(this.scene.game.renderer.width / 3);
this.progressContainer.clear(); this.progressContainer.clear();

View File

@ -1,5 +1,7 @@
//The list of all the player textures, both the default models and the partial textures used for customization //The list of all the player textures, both the default models and the partial textures used for customization
import { WokaList, WokaPartType } from "../../Messages/JsonMessages/PlayerTextures";
export interface BodyResourceDescriptionListInterface { export interface BodyResourceDescriptionListInterface {
[key: string]: BodyResourceDescriptionInterface; [key: string]: BodyResourceDescriptionInterface;
} }
@ -33,24 +35,6 @@ export enum PlayerTexturesKey {
Woka = "woka", Woka = "woka",
} }
type PlayerTexturesMetadata = Record<PlayerTexturesKey, PlayerTexturesCategory>;
interface PlayerTexturesCategory {
collections: PlayerTexturesCollection[];
required?: boolean;
}
interface PlayerTexturesCollection {
name: string;
textures: PlayerTexturesRecord[];
}
interface PlayerTexturesRecord {
id: string;
name: string;
url: string;
}
export class PlayerTextures { export class PlayerTextures {
private PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {}; private PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {};
private COLOR_RESOURCES: BodyResourceDescriptionListInterface = {}; private COLOR_RESOURCES: BodyResourceDescriptionListInterface = {};
@ -61,7 +45,7 @@ export class PlayerTextures {
private ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {}; private ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {};
private LAYERS: BodyResourceDescriptionListInterface[] = []; private LAYERS: BodyResourceDescriptionListInterface[] = [];
public loadPlayerTexturesMetadata(metadata: PlayerTexturesMetadata): void { public loadPlayerTexturesMetadata(metadata: WokaList): void {
this.mapTexturesMetadataIntoResources(metadata); this.mapTexturesMetadataIntoResources(metadata);
} }
@ -88,7 +72,7 @@ export class PlayerTextures {
return this.LAYERS; return this.LAYERS;
} }
private mapTexturesMetadataIntoResources(metadata: PlayerTexturesMetadata): void { private mapTexturesMetadataIntoResources(metadata: WokaList): void {
this.PLAYER_RESOURCES = this.getMappedResources(metadata.woka); this.PLAYER_RESOURCES = this.getMappedResources(metadata.woka);
this.COLOR_RESOURCES = this.getMappedResources(metadata.body); this.COLOR_RESOURCES = this.getMappedResources(metadata.body);
this.EYES_RESOURCES = this.getMappedResources(metadata.eyes); this.EYES_RESOURCES = this.getMappedResources(metadata.eyes);
@ -107,7 +91,7 @@ export class PlayerTextures {
]; ];
} }
private getMappedResources(category: PlayerTexturesCategory): BodyResourceDescriptionListInterface { private getMappedResources(category: WokaPartType): BodyResourceDescriptionListInterface {
const resources: BodyResourceDescriptionListInterface = {}; const resources: BodyResourceDescriptionListInterface = {};
if (!category) { if (!category) {
return {}; return {};

View File

@ -15,39 +15,4 @@ export abstract class AbstractCharacterScene extends ResizableScene {
this.playerTextures = new PlayerTextures(); this.playerTextures = new PlayerTextures();
this.superLoad = new SuperLoaderPlugin(this); this.superLoad = new SuperLoaderPlugin(this);
} }
loadCustomSceneSelectCharacters(): Promise<BodyResourceDescriptionInterface[]> {
const textures = this.playerTextures.getTexturesResources(PlayerTexturesKey.Woka);
const promises: CancelablePromise<Texture>[] = [];
const bodyResourceDescriptions: BodyResourceDescriptionInterface[] = [];
if (textures) {
for (const texture of Object.values(textures)) {
if (texture.level === -1) {
continue;
}
promises.push(loadWokaTexture(this.superLoad, texture));
bodyResourceDescriptions.push(texture);
}
}
return Promise.all(promises).then(() => {
return bodyResourceDescriptions;
});
}
loadSelectSceneCharacters(): Promise<BodyResourceDescriptionInterface[]> {
const promises: CancelablePromise<Texture>[] = [];
const bodyResourceDescriptions: BodyResourceDescriptionInterface[] = [];
for (const textures of this.playerTextures.getLayers()) {
for (const texture of Object.values(textures)) {
if (texture.level !== -1) {
continue;
}
promises.push(loadWokaTexture(this.superLoad, texture));
bodyResourceDescriptions.push(texture);
}
}
return Promise.all(promises).then(() => {
return bodyResourceDescriptions;
});
}
} }

View File

@ -16,6 +16,7 @@ import { get } from "svelte/store";
import { analyticsClient } from "../../Administration/AnalyticsClient"; import { analyticsClient } from "../../Administration/AnalyticsClient";
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils"; import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
import { PUSHER_URL } from "../../Enum/EnvironmentVariable"; import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
import { wokaList } from "../../Messages/JsonMessages/PlayerTextures";
export const CustomizeSceneName = "CustomizeScene"; export const CustomizeSceneName = "CustomizeScene";
@ -41,12 +42,12 @@ export class CustomizeScene extends AbstractCharacterScene {
} }
preload() { preload() {
const wokaMetadataKey = "woka-list"; const wokaMetadataKey = "woka-list" + gameManager.currentStartedRoom.href;
this.cache.json.remove(wokaMetadataKey); this.cache.json.remove(wokaMetadataKey);
// FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!) this.superLoad
this.load.json( .json(
wokaMetadataKey, wokaMetadataKey,
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(window.location.href), `${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(gameManager.currentStartedRoom.href),
undefined, undefined,
{ {
responseType: "text", responseType: "text",
@ -54,32 +55,17 @@ export class CustomizeScene extends AbstractCharacterScene {
Authorization: localUserStore.getAuthToken() ?? "", Authorization: localUserStore.getAuthToken() ?? "",
}, },
withCredentials: true, withCredentials: true,
} },
); (key, type, data) => {
this.load.once(`filecomplete-json-${wokaMetadataKey}`, () => { this.playerTextures.loadPlayerTexturesMetadata(wokaList.parse(data));
this.playerTextures.loadPlayerTexturesMetadata(this.cache.json.get(wokaMetadataKey));
this.loadCustomSceneSelectCharacters()
.then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
if (
bodyResourceDescription.level == undefined ||
bodyResourceDescription.level < 0 ||
bodyResourceDescription.level > 5
) {
throw new Error("Texture level is null");
}
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
});
this.lazyloadingAttempt = true;
})
.catch((e) => console.error(e));
this.layers = loadAllLayers(this.load, this.playerTextures); this.layers = loadAllLayers(this.load, this.playerTextures);
this.lazyloadingAttempt = false; this.lazyloadingAttempt = false;
}
)
.catch((e) => console.error(e));
//this function must stay at the end of preload function //this function must stay at the end of preload function
this.loader.addLoader(); this.loader.addLoader();
});
} }
create() { create() {

View File

@ -16,6 +16,7 @@ import { analyticsClient } from "../../Administration/AnalyticsClient";
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils"; import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
import { PUSHER_URL } from "../../Enum/EnvironmentVariable"; import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
import { customizeAvailableStore } from "../../Stores/SelectCharacterSceneStore"; import { customizeAvailableStore } from "../../Stores/SelectCharacterSceneStore";
import { wokaList } from "../../Messages/JsonMessages/PlayerTextures";
//todo: put this constants in a dedicated file //todo: put this constants in a dedicated file
export const SelectCharacterSceneName = "SelectCharacterScene"; export const SelectCharacterSceneName = "SelectCharacterScene";
@ -44,13 +45,13 @@ export class SelectCharacterScene extends AbstractCharacterScene {
} }
preload() { preload() {
const wokaMetadataKey = "woka-list"; const wokaMetadataKey = "woka-list" + gameManager.currentStartedRoom.href;
this.cache.json.remove(wokaMetadataKey); this.cache.json.remove(wokaMetadataKey);
// FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!) this.superLoad
this.load.json( .json(
wokaMetadataKey, wokaMetadataKey,
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(window.location.href), `${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(gameManager.currentStartedRoom.href),
undefined, undefined,
{ {
responseType: "text", responseType: "text",
@ -58,24 +59,17 @@ export class SelectCharacterScene extends AbstractCharacterScene {
Authorization: localUserStore.getAuthToken() ?? "", Authorization: localUserStore.getAuthToken() ?? "",
}, },
withCredentials: true, withCredentials: true,
} },
); (key, type, data) => {
this.load.once(`filecomplete-json-${wokaMetadataKey}`, () => { this.playerTextures.loadPlayerTexturesMetadata(wokaList.parse(data));
this.playerTextures.loadPlayerTexturesMetadata(this.cache.json.get(wokaMetadataKey));
this.loadSelectSceneCharacters()
.then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
this.playerModels.push(bodyResourceDescription);
});
this.lazyloadingAttempt = true;
})
.catch((e) => console.error(e));
this.playerModels = loadAllDefaultModels(this.load, this.playerTextures); this.playerModels = loadAllDefaultModels(this.load, this.playerTextures);
this.lazyloadingAttempt = false; this.lazyloadingAttempt = false;
}
)
.catch((e) => console.error(e));
//this function must stay at the end of preload function //this function must stay at the end of preload function
this.loader.addLoader(); this.loader.addLoader();
});
} }
create() { create() {

View File

@ -51,11 +51,43 @@ export class SuperLoaderPlugin {
); );
} }
/**
* @param key
* @param url
* @param dataKey
* @param xhrSettings
* @param immediateCallback The function returns a promise BUT the "then" promise will be triggered after the current Javascript thread finishes. In case of Phaser loader, this can be a problem if you want to add additional resources to the loader in the callback. The "immediateCallback" triggers directly in the
*/
public json(
key: string,
url: string,
dataKey?: string,
xhrSettings?: Phaser.Types.Loader.XHRSettingsObject,
immediateCallback?: (key: string, type: string, data: unknown) => void
) {
return this.loadResource<unknown>(
() => {
this.scene.load.json(key, url, dataKey, xhrSettings);
},
key,
url,
() => {
if (this.scene.load.cacheManager.json.exists(key)) {
return this.scene.load.cacheManager.json.get(key);
}
return undefined;
},
"json",
immediateCallback
);
}
/** /**
* @param callback The function that calls the loader to load a resource * @param callback The function that calls the loader to load a resource
* @param key The key of the resource to be loaded * @param key The key of the resource to be loaded
* @param fromCache A function that checks in the cache if the resource is already available * @param fromCache A function that checks in the cache if the resource is already available
* @param type The type of resource loaded * @param type The type of resource loaded
* @param immediateCallback The function returns a promise BUT the "then" promise will be triggered after the current Javascript thread finishes. In case of Phaser loader, this can be a problem if you want to add additional resources to the loader in the callback. The "immediateCallback" triggers directly in the
* @private * @private
*/ */
private loadResource<T>( private loadResource<T>(
@ -63,7 +95,8 @@ export class SuperLoaderPlugin {
key: string, key: string,
url: string, url: string,
fromCache: () => T | undefined, fromCache: () => T | undefined,
type: string type: string,
immediateCallback?: (key: string, type: string, data: unknown) => void
): CancelablePromise<T> { ): CancelablePromise<T> {
// If for some reason, the "url" is empty, let's reject the promise. // If for some reason, the "url" is empty, let's reject the promise.
if (!url) { if (!url) {
@ -98,7 +131,7 @@ export class SuperLoaderPlugin {
unloadCallbacks(); unloadCallbacks();
}; };
const successCallback = () => { const successCallback = (key: string, type: string, data: unknown) => {
this.scene.load.off("loaderror", errorCallback); this.scene.load.off("loaderror", errorCallback);
this.scene.events.off(Phaser.Scenes.Events.DESTROY, unloadCallbacks); this.scene.events.off(Phaser.Scenes.Events.DESTROY, unloadCallbacks);
const resource = fromCache(); const resource = fromCache();
@ -106,6 +139,15 @@ export class SuperLoaderPlugin {
return rej(new Error("Newly loaded resource not available in cache")); return rej(new Error("Newly loaded resource not available in cache"));
} }
res(resource); res(resource);
// The "then" callbacks registered on the promise are not executed immediately when "res" is called.
// Instead, they are called after the current JS thread finishes.
// In some cases, we want the callbacks to execute right now. In particular if we load a map / json file
// that contains references to other files that needs to be loaded.
if (immediateCallback) {
immediateCallback(key, type, data);
}
console.log("Resolve done for ", url);
}; };
cancel(() => { cancel(() => {

View File

@ -21,7 +21,8 @@
"generic-type-guard": "^3.5.0", "generic-type-guard": "^3.5.0",
"google-protobuf": "^3.13.0", "google-protobuf": "^3.13.0",
"grpc": "^1.24.4", "grpc": "^1.24.4",
"ts-proto": "^1.96.0" "ts-proto": "^1.96.0",
"zod": "^3.12.0"
}, },
"devDependencies": { "devDependencies": {
"@types/google-protobuf": "^3.7.4", "@types/google-protobuf": "^3.7.4",

View File

@ -4645,3 +4645,8 @@ year@^0.2.1:
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0" resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0"
integrity sha1-QIOuUgoxiyPshgN/MADLiSvfm7A= integrity sha1-QIOuUgoxiyPshgN/MADLiSvfm7A=
zod@^3.12.0:
version "3.14.2"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.2.tgz#0b4ed79085c471adce0e7f2c0a4fbb5ddc516ba2"
integrity sha512-iF+wrtzz7fQfkmn60PG6XFxaWBhYYKzp2i+nv24WbLUWb2JjymdkHlzBwP0erpc78WotwP5g9AAu7Sk8GWVVNw==

View File

@ -37,7 +37,7 @@ import { InvalidTokenError } from "../Controller/InvalidTokenError";
import HyperExpress from "hyper-express"; import HyperExpress from "hyper-express";
import { localWokaService } from "../Services/LocalWokaService"; import { localWokaService } from "../Services/LocalWokaService";
import { WebSocket } from "uWebSockets.js"; import { WebSocket } from "uWebSockets.js";
import { WokaDetail } from "../Enum/PlayerTextures"; import { WokaDetail } from "../Messages/JsonMessages/PlayerTextures";
/** /**
* The object passed between the "open" and the "upgrade" methods when opening a websocket * The object passed between the "open" and the "upgrade" methods when opening a websocket

View File

@ -11,7 +11,7 @@ import {
import { ClientDuplexStream } from "grpc"; import { ClientDuplexStream } from "grpc";
import { Zone } from "_Model/Zone"; import { Zone } from "_Model/Zone";
import { compressors } from "hyper-express"; import { compressors } from "hyper-express";
import { WokaDetail } from "_Enum/PlayerTextures"; import { WokaDetail } from "../../Messages/JsonMessages/PlayerTextures";
export type BackConnection = ClientDuplexStream<PusherToBackMessage, ServerToClientMessage>; export type BackConnection = ClientDuplexStream<PusherToBackMessage, ServerToClientMessage>;

View File

@ -9,7 +9,7 @@ import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface";
import Direction = PositionMessage.Direction; import Direction = PositionMessage.Direction;
import { ItemEventMessageInterface } from "_Model/Websocket/ItemEventMessage"; import { ItemEventMessageInterface } from "_Model/Websocket/ItemEventMessage";
import { PositionInterface } from "_Model/PositionInterface"; import { PositionInterface } from "_Model/PositionInterface";
import { WokaDetail } from "_Enum/PlayerTextures"; import { WokaDetail } from "../../Messages/JsonMessages/PlayerTextures";
export class ProtobufUtils { export class ProtobufUtils {
public static toPositionMessage(point: PointInterface): PositionMessage { public static toPositionMessage(point: PointInterface): PositionMessage {

View File

@ -5,7 +5,7 @@ import { isRoomRedirect, RoomRedirect } from "../Messages/JsonMessages/RoomRedir
import { AdminApiData, isAdminApiData } from "../Messages/JsonMessages/AdminApiData"; import { AdminApiData, isAdminApiData } from "../Messages/JsonMessages/AdminApiData";
import * as tg from "generic-type-guard"; import * as tg from "generic-type-guard";
import { isNumber } from "generic-type-guard"; import { isNumber } from "generic-type-guard";
import { isWokaDetail } from "../Enum/PlayerTextures"; import { isWokaDetail } from "../Messages/JsonMessages/PlayerTextures";
import qs from "qs"; import qs from "qs";
export interface AdminBannedData { export interface AdminBannedData {

View File

@ -1,6 +1,6 @@
import axios, { AxiosResponse } from "axios"; import axios, { AxiosResponse } from "axios";
import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable"; import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable";
import { wokaList, WokaList } from "../Enum/PlayerTextures"; import { wokaList, WokaList } from "../Messages/JsonMessages/PlayerTextures";
import { WokaServiceInterface } from "./WokaServiceInterface"; import { WokaServiceInterface } from "./WokaServiceInterface";
class AdminWokaService implements WokaServiceInterface { class AdminWokaService implements WokaServiceInterface {

View File

@ -1,4 +1,4 @@
import { WokaDetail, WokaDetailsResult, WokaList, wokaPartNames } from "../Enum/PlayerTextures"; import { WokaDetail, WokaDetailsResult, WokaList, wokaPartNames } from "../Messages/JsonMessages/PlayerTextures";
import { WokaServiceInterface } from "./WokaServiceInterface"; import { WokaServiceInterface } from "./WokaServiceInterface";
class LocalWokaService implements WokaServiceInterface { class LocalWokaService implements WokaServiceInterface {

View File

@ -1,4 +1,4 @@
import { WokaDetailsResult, WokaList } from "../Enum/PlayerTextures"; import { WokaList } from "../Messages/JsonMessages/PlayerTextures";
export interface WokaServiceInterface { export interface WokaServiceInterface {
/** /**