Heavy changes: refactoring the pusher to always send the textures (and the front to accept them)

This commit is contained in:
David Négrier
2022-02-23 21:08:21 +01:00
parent d65fe0ee26
commit 378a95962a
31 changed files with 290 additions and 270 deletions
+5 -31
View File
@@ -134,7 +134,7 @@ class ConnectionManager {
console.error("Invalid data received from /register route. Data: ", data);
throw new Error("Invalid data received from /register route.");
}
this.localUser = new LocalUser(data.userUuid, data.textures, data.email);
this.localUser = new LocalUser(data.userUuid, data.email);
this.authToken = data.authToken;
localUserStore.saveUser(this.localUser);
localUserStore.setAuthToken(this.authToken);
@@ -214,32 +214,6 @@ class ConnectionManager {
}
}
this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
if (this._currentRoom.textures != undefined && this._currentRoom.textures.length > 0) {
//check if texture was changed
if (this.localUser.textures.length === 0) {
this.localUser.textures = this._currentRoom.textures;
} else {
// TODO: the local store should NOT be used as a buffer for all the texture we were authorized to have. Bad idea.
// Instead, it is the responsibility of the ADMIN to return the EXACT list of textures we can have in a given context
// + this list can change over time or over rooms.
// 1- a room could forbid a particular dress code. In this case, the user MUST change its skin.
// 2- a room can allow "external skins from other maps" => important: think about fediverse! => switch to URLs? (with a whitelist mechanism?) => but what about NFTs?
// Note: stocker des URL dans le localstorage pour les utilisateurs actuels: mauvaise idée (empêche de mettre l'URL à jour dans le futur) => en même temps, problème avec le portage de user d'un serveur à l'autre
// Réfléchir à une notion de "character server" ??
this._currentRoom.textures.forEach((newTexture) => {
const alreadyExistTexture = this.localUser.textures.find((c) => newTexture.id === c.id);
if (this.localUser.textures.findIndex((c) => newTexture.id === c.id) !== -1) {
return;
}
this.localUser.textures.push(newTexture);
});
}
localUserStore.saveUser(this.localUser);
}
}
if (this._currentRoom == undefined) {
return Promise.reject(new Error("Invalid URL"));
@@ -265,7 +239,7 @@ class ConnectionManager {
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
const data = await axiosWithRetry.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data);
this.localUser = new LocalUser(data.userUuid, [], data.email);
this.localUser = new LocalUser(data.userUuid, data.email);
this.authToken = data.authToken;
if (!isBenchmark) {
// In benchmark, we don't have a local storage.
@@ -275,7 +249,7 @@ class ConnectionManager {
}
public initBenchmark(): void {
this.localUser = new LocalUser("", []);
this.localUser = new LocalUser("");
}
public connectToRoomSocket(
@@ -352,13 +326,13 @@ class ConnectionManager {
throw new Error("No Auth code provided");
}
}
const { authToken, userUuid, textures, email } = await Axios.get(`${PUSHER_URL}/login-callback`, {
const { authToken, userUuid, email } = await Axios.get(`${PUSHER_URL}/login-callback`, {
params: { code, nonce, token, playUri: this.currentRoom?.key },
}).then((res) => {
return res.data;
});
localUserStore.setAuthToken(authToken);
this.localUser = new LocalUser(userUuid, textures, email);
this.localUser = new LocalUser(userUuid, email);
localUserStore.saveUser(this.localUser);
this.authToken = authToken;
+2
View File
@@ -2,6 +2,7 @@ import type { SignalData } from "simple-peer";
import type { RoomConnection } from "./RoomConnection";
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
import { PositionMessage_Direction } from "../Messages/ts-proto-generated/messages";
import { CharacterLayer } from "../../../back/src/Model/Websocket/CharacterLayer";
export interface PointInterface {
x: number;
@@ -83,6 +84,7 @@ export interface RoomJoinedMessageInterface {
//groups: GroupCreatedUpdatedMessageInterface[],
items: { [itemId: number]: unknown };
variables: Map<string, unknown>;
characterLayers: BodyResourceDescriptionInterface[];
}
export interface PlayGlobalMessageInterface {
+5 -8
View File
@@ -1,10 +1,11 @@
import { MAX_USERNAME_LENGTH } from "../Enum/EnvironmentVariable";
export type LayerNames = "woka" | "body" | "eyes" | "hair" | "clothes" | "hat" | "accessory";
export interface CharacterTexture {
id: number;
level: number;
id: string;
layer: LayerNames;
url: string;
rights: string;
}
export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
@@ -24,9 +25,5 @@ export function areCharacterLayersValid(value: string[] | null): boolean {
}
export class LocalUser {
constructor(
public readonly uuid: string,
public textures: CharacterTexture[],
public email: string | null = null
) {}
constructor(public readonly uuid: string, public email: string | null = null) {}
}
+2 -8
View File
@@ -9,7 +9,7 @@ import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
export class MapDetail {
constructor(public readonly mapUrl: string, public readonly textures: CharacterTexture[] | undefined) {}
constructor(public readonly mapUrl: string) {}
}
export interface RoomRedirect {
@@ -25,7 +25,6 @@ export class Room {
private _authenticationMandatory: boolean = DISABLE_ANONYMOUS;
private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER;
private _mapUrl: string | undefined;
private _textures: CharacterTexture[] | undefined;
private instance: string | undefined;
private readonly _search: URLSearchParams;
private _contactPage: string | undefined;
@@ -118,7 +117,6 @@ export class Room {
} else if (isMapDetailsData(data)) {
console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
this._mapUrl = data.mapUrl;
this._textures = data.textures;
this._group = data.group;
this._authenticationMandatory =
data.authenticationMandatory != null ? data.authenticationMandatory : DISABLE_ANONYMOUS;
@@ -128,7 +126,7 @@ export class Room {
this._expireOn = new Date(data.expireOn);
}
this._canReport = data.canReport ?? false;
return new MapDetail(data.mapUrl, data.textures);
return new MapDetail(data.mapUrl);
} else {
throw new Error("Data received by the /map endpoint of the Pusher is not in a valid format.");
}
@@ -205,10 +203,6 @@ export class Room {
return this.roomUrl.toString();
}
get textures(): CharacterTexture[] | undefined {
return this._textures;
}
get mapUrl(): string {
if (!this._mapUrl) {
throw new Error("Map URL not fetched yet");
+30 -7
View File
@@ -20,7 +20,7 @@ import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTe
import { adminMessagesService } from "./AdminMessagesService";
import { connectionManager } from "./ConnectionManager";
import { get } from "svelte/store";
import { warningContainerStore } from "../Stores/MenuStore";
import { menuIconVisiblilityStore, menuVisiblilityStore, warningContainerStore } from "../Stores/MenuStore";
import { followStateStore, followRoleStore, followUsersStore } from "../Stores/FollowStore";
import { localUserStore } from "./LocalUserStore";
import {
@@ -52,10 +52,14 @@ import {
PositionMessage_Direction,
SetPlayerDetailsMessage as SetPlayerDetailsMessageTsProto,
PingMessage as PingMessageTsProto,
CharacterLayerMessage,
} from "../Messages/ts-proto-generated/messages";
import { Subject } from "rxjs";
import { OpenPopupEvent } from "../Api/Events/OpenPopupEvent";
import { match } from "assert";
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
import { gameManager } from "../Phaser/Game/GameManager";
import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene";
const manualPingDelay = 20000;
@@ -336,12 +340,16 @@ export class RoomConnection implements RoomConnection {
this.userId = roomJoinedMessage.currentUserId;
this.tags = roomJoinedMessage.tag;
this._userRoomToken = roomJoinedMessage.userRoomToken;
const characterLayers = roomJoinedMessage.characterLayer.map(
this.mapCharactgerLayerToBodyResourceDescription.bind(this)
);
this._roomJoinedMessageStream.next({
connection: this,
room: {
items,
variables,
characterLayers,
} as RoomJoinedMessageInterface,
});
break;
@@ -351,6 +359,15 @@ export class RoomConnection implements RoomConnection {
this.closed = true;
break;
}
case "invalidTextureMessage": {
menuVisiblilityStore.set(false);
menuIconVisiblilityStore.set(false);
selectCharacterSceneVisibleStore.set(true);
gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene());
this.closed = true;
break;
}
case "tokenExpiredMessage": {
connectionManager.logout().catch((e) => console.error(e));
this.closed = true; //technically, this isn't needed since loadOpenIDScreen() will do window.location.assign() but I prefer to leave it for consistency
@@ -591,6 +608,15 @@ export class RoomConnection implements RoomConnection {
});
}*/
private mapCharactgerLayerToBodyResourceDescription(
characterLayer: CharacterLayerMessage
): BodyResourceDescriptionInterface {
return {
name: characterLayer.name,
img: characterLayer.url,
};
}
// TODO: move this to protobuf utils
private toMessageUserJoined(message: UserJoinedMessageTsProto): MessageUserJoined {
const position = message.position;
@@ -598,12 +624,9 @@ export class RoomConnection implements RoomConnection {
throw new Error("Invalid JOIN_ROOM message");
}
const characterLayers = message.characterLayers.map((characterLayer): BodyResourceDescriptionInterface => {
return {
name: characterLayer.name,
img: characterLayer.url,
};
});
const characterLayers = message.characterLayers.map(
this.mapCharactgerLayerToBodyResourceDescription.bind(this)
);
const companion = message.companion;
+10 -1
View File
@@ -83,7 +83,16 @@ export abstract class Character extends Container implements OutlineableInterfac
});
})
.catch(() => {
return lazyLoadPlayerCharacterTextures(scene.load, ["color_22", "eyes_23"]).then((textures) => {
return lazyLoadPlayerCharacterTextures(scene.load, [
{
name: "color_22",
img: "resources/customisation/character_color/character_color21.png",
},
{
name: "eyes_23",
img: "resources/customisation/character_eyes/character_eyes23.png",
},
]).then((textures) => {
this.addTextures(textures, frame);
this.invisible = false;
this.playAnimation(direction, moving);
+13 -2
View File
@@ -1,7 +1,5 @@
//The list of all the player textures, both the default models and the partial textures used for customization
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
export interface BodyResourceDescriptionListInterface {
[key: string]: BodyResourceDescriptionInterface;
}
@@ -12,6 +10,19 @@ export interface BodyResourceDescriptionInterface {
level?: number;
}
/**
* Temporary object to map layers to the old "level" concept.
*/
export const mapLayerToLevel = {
woka: -1,
body: 0,
eyes: 1,
hair: 2,
clothes: 3,
hat: 4,
accessory: 5,
};
enum PlayerTexturesKey {
Accessory = "accessory",
Body = "body",
@@ -1,6 +1,6 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import type { CharacterTexture } from "../../Connexion/LocalUser";
import { BodyResourceDescriptionInterface, PlayerTextures } from "./PlayerTextures";
import { BodyResourceDescriptionInterface, mapLayerToLevel, PlayerTextures } from "./PlayerTextures";
import CancelablePromise from "cancelable-promise";
export interface FrameConfig {
@@ -28,13 +28,11 @@ export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptio
return returnArray;
};
export const loadCustomTexture = (
export const loadWokaTexture = (
loaderPlugin: LoaderPlugin,
texture: CharacterTexture
texture: BodyResourceDescriptionInterface
): CancelablePromise<BodyResourceDescriptionInterface> => {
const name = "customCharacterTexture" + texture.id;
const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level };
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
return createLoadingPromise(loaderPlugin, texture, {
frameWidth: 32,
frameHeight: 32,
});
@@ -42,16 +40,15 @@ export const loadCustomTexture = (
export const lazyLoadPlayerCharacterTextures = (
loadPlugin: LoaderPlugin,
texturekeys: Array<string | BodyResourceDescriptionInterface>
textures: BodyResourceDescriptionInterface[]
): CancelablePromise<string[]> => {
const promisesList: CancelablePromise<unknown>[] = [];
texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => {
textures.forEach((texture) => {
try {
//TODO refactor
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
if (!loadPlugin.textureManager.exists(texture.name)) {
promisesList.push(
createLoadingPromise(loadPlugin, playerResourceDescriptor, {
createLoadingPromise(loadPlugin, texture, {
frameWidth: 32,
frameHeight: 32,
})
@@ -64,9 +61,9 @@ export const lazyLoadPlayerCharacterTextures = (
let returnPromise: CancelablePromise<Array<string | BodyResourceDescriptionInterface>>;
if (promisesList.length > 0) {
loadPlugin.start();
returnPromise = CancelablePromise.all(promisesList).then(() => texturekeys);
returnPromise = CancelablePromise.all(promisesList).then(() => textures);
} else {
returnPromise = CancelablePromise.resolve(texturekeys);
returnPromise = CancelablePromise.resolve(textures);
}
//If the loading fail, we render the default model instead.
@@ -77,23 +74,6 @@ export const lazyLoadPlayerCharacterTextures = (
);
};
export const getRessourceDescriptor = (
textureKey: string | BodyResourceDescriptionInterface
): BodyResourceDescriptionInterface => {
if (typeof textureKey !== "string" && textureKey.img) {
return textureKey;
}
const textureName: string = typeof textureKey === "string" ? textureKey : textureKey.name;
const playerResource = PlayerTextures.PLAYER_RESOURCES[textureName];
if (playerResource !== undefined) return playerResource;
for (let i = 0; i < PlayerTextures.LAYERS.length; i++) {
const playerResource = PlayerTextures.LAYERS[i][textureName];
if (playerResource !== undefined) return playerResource;
}
throw new Error("Could not find a data for texture " + textureName);
};
export const createLoadingPromise = (
loadPlugin: LoaderPlugin,
playerResourceDescriptor: BodyResourceDescriptionInterface,
+20 -10
View File
@@ -18,7 +18,7 @@ import { soundManager } from "./SoundManager";
import { SharedVariablesManager } from "./SharedVariablesManager";
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
import { lazyLoadPlayerCharacterTextures, loadWokaTexture } from "../Entity/PlayerTexturesLoadingManager";
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
import { iframeListener } from "../../Api/IframeListener";
import { DEBUG_MODE, JITSI_URL, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
@@ -97,6 +97,8 @@ import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite";
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
import { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
import CancelablePromise from "cancelable-promise";
export interface GameSceneInitInterface {
initPosition: PointInterface | null;
reconnecting: boolean;
@@ -244,13 +246,6 @@ export class GameScene extends DirtyScene {
//initialize frame event of scripting API
this.listenToIframeEvents();
const localUser = localUserStore.getLocalUser();
const textures = localUser?.textures;
if (textures) {
for (const texture of textures) {
loadCustomTexture(this.load, texture).catch((e) => console.error(e));
}
}
this.load.image("iconTalk", "/resources/icons/icon_talking.png");
if (touchScreenManager.supportTouchScreen) {
@@ -745,6 +740,14 @@ export class GameScene extends DirtyScene {
.then((onConnect: OnConnectInterface) => {
this.connection = onConnect.connection;
lazyLoadPlayerCharacterTextures(this.load, onConnect.room.characterLayers)
.then((layers) => {
this.currentPlayerTexturesResolve(layers);
})
.catch((e) => {
this.currentPlayerTexturesReject(e);
});
playersStore.connectToRoomConnection(this.connection);
userIsAdminStore.set(this.connection.hasTag("admin"));
@@ -1689,16 +1692,23 @@ ${escapedMessage}
}
}
// The promise that will resolve to the current player texture. This will be available only after connection is established.
private currentPlayerTexturesResolve!: (value: string[]) => void;
private currentPlayerTexturesReject!: (reason: unknown) => void;
private currentPlayerTexturesPromise: CancelablePromise<string[]> = new CancelablePromise((resolve, reject) => {
this.currentPlayerTexturesResolve = resolve;
this.currentPlayerTexturesReject = reject;
});
private createCurrentPlayer() {
//TODO create animation moving between exit and start
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
try {
this.CurrentPlayer = new Player(
this,
this.startPositionCalculator.startPosition.x,
this.startPositionCalculator.startPosition.y,
this.playerName,
texturesPromise,
this.currentPlayerTexturesPromise,
PlayerAnimationDirections.Down,
false,
this.companion,
@@ -1,41 +1,45 @@
import { ResizableScene } from "./ResizableScene";
import { localUserStore } from "../../Connexion/LocalUserStore";
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
import { loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
import { loadWokaTexture } from "../Entity/PlayerTexturesLoadingManager";
import type { CharacterTexture } from "../../Connexion/LocalUser";
import type CancelablePromise from "cancelable-promise";
import { PlayerTextures } from "../Entity/PlayerTextures";
import { Loader } from "../Components/Loader";
import { CustomizeSceneName } from "./CustomizeScene";
export abstract class AbstractCharacterScene extends ResizableScene {
protected playerTextures: PlayerTextures;
constructor(params: { key: string }) {
super(params);
this.playerTextures = new PlayerTextures();
}
loadCustomSceneSelectCharacters(): Promise<BodyResourceDescriptionInterface[]> {
const textures = this.getTextures();
const textures = PlayerTextures.PLAYER_RESOURCES;
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
if (textures) {
for (const texture of textures) {
for (const texture of Object.values(textures)) {
if (texture.level === -1) {
continue;
}
promises.push(loadCustomTexture(this.load, texture));
promises.push(loadWokaTexture(this.load, texture));
}
}
return Promise.all(promises);
}
loadSelectSceneCharacters(): Promise<BodyResourceDescriptionInterface[]> {
const textures = this.getTextures();
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
if (textures) {
for (const texture of textures) {
for (const textures of PlayerTextures.LAYERS) {
for (const texture of Object.values(textures)) {
if (texture.level !== -1) {
continue;
}
promises.push(loadCustomTexture(this.load, texture));
promises.push(loadWokaTexture(this.load, texture));
}
}
return Promise.all(promises);
}
private getTextures(): CharacterTexture[] | undefined {
const localUser = localUserStore.getLocalUser();
return localUser?.textures;
}
}
+1 -3
View File
@@ -5,7 +5,7 @@ import Sprite = Phaser.GameObjects.Sprite;
import { gameManager } from "../Game/GameManager";
import { localUserStore } from "../../Connexion/LocalUserStore";
import { Loader } from "../Components/Loader";
import { BodyResourceDescriptionInterface, PlayerTextures } from "../Entity/PlayerTextures";
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
import { AbstractCharacterScene } from "./AbstractCharacterScene";
import { areCharacterLayersValid } from "../../Connexion/LocalUser";
import { SelectCharacterSceneName } from "./SelectCharacterScene";
@@ -32,14 +32,12 @@ export class CustomizeScene extends AbstractCharacterScene {
private moveVertically: number = 0;
private loader: Loader;
private playerTextures: PlayerTextures;
constructor() {
super({
key: CustomizeSceneName,
});
this.loader = new Loader(this);
this.playerTextures = new PlayerTextures();
}
preload() {
@@ -33,7 +33,6 @@ export class SelectCharacterScene extends AbstractCharacterScene {
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
private loader: Loader;
private playerTextures: PlayerTextures;
constructor() {
super({