diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 2892a7bd..491dd4af 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -327,6 +327,11 @@ export class GameRoom { const readableBy = variableManager.setVariable(name, value, user); + // If the variable was not changed, let's not dispatch anything. + if (readableBy === false) { + return; + } + // TODO: should we batch those every 100ms? const variableMessage = new VariableWithTagMessage(); variableMessage.setName(name); diff --git a/back/src/Services/VariablesManager.ts b/back/src/Services/VariablesManager.ts index 5137a32d..e8aaef25 100644 --- a/back/src/Services/VariablesManager.ts +++ b/back/src/Services/VariablesManager.ts @@ -51,6 +51,17 @@ export class VariablesManager { } const variables = await variablesRepository.loadVariables(this.roomUrl); for (const key in variables) { + // Let's only set variables if they are in the map (if the map has changed, maybe stored variables do not exist anymore) + if (this.variableObjects) { + const variableObject = this.variableObjects.get(key); + if (variableObject === undefined) { + continue; + } + if (!variableObject.persist) { + continue; + } + } + this._variables.set(key, variables[key]); } return this; @@ -134,10 +145,21 @@ export class VariablesManager { return variable; } - setVariable(name: string, value: string, user: User): string | undefined { + /** + * Sets the variable. + * + * Returns who is allowed to read the variable (the readableby property) or "undefined" if anyone can read it. + * Also, returns "false" if the variable was not modified (because we set it to the value it already has) + * + * @param name + * @param value + * @param user + */ + setVariable(name: string, value: string, user: User): string | undefined | false { let readableBy: string | undefined; + let variableObject: Variable | undefined; if (this.variableObjects) { - const variableObject = this.variableObjects.get(name); + variableObject = this.variableObjects.get(name); if (variableObject === undefined) { throw new Error('Trying to set a variable "' + name + '" that is not defined as an object in the map.'); } @@ -159,10 +181,19 @@ export class VariablesManager { readableBy = variableObject.readableBy; } + // If the value is not modified, return false + if (this._variables.get(name) === value) { + return false; + } + this._variables.set(name, value); - variablesRepository - .saveVariable(this.roomUrl, name, value) - .catch((e) => console.error("Error while saving variable in Redis:", e)); + + if (variableObject !== undefined && variableObject.persist) { + variablesRepository + .saveVariable(this.roomUrl, name, value) + .catch((e) => console.error("Error while saving variable in Redis:", e)); + } + return readableBy; } diff --git a/docs/maps/scripting.md b/docs/maps/scripting.md index 5f645b81..5be57ee1 100644 --- a/docs/maps/scripting.md +++ b/docs/maps/scripting.md @@ -55,10 +55,10 @@ Start by testing this with a simple message sent to the chat. **script.js** ```javascript -WA.sendChatMessage('Hello world', 'Mr Robot'); +WA.chat.sendChatMessage('Hello world', 'Mr Robot'); ``` -The `WA` objects contains a number of useful methods enabling you to interact with the WorkAdventure game. For instance, `WA.sendChatMessage` opens the chat and adds a message in it. +The `WA` objects contains a number of useful methods enabling you to interact with the WorkAdventure game. For instance, `WA.chat.sendChatMessage` opens the chat and adds a message in it. In your browser console, when you open the map, the chat message should be displayed right away. diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index adf97e11..e76b20d9 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -33,10 +33,11 @@ import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent"; import { isLoadPageEvent } from "./Events/LoadPageEvent"; import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent"; import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent"; -import { isSetVariableIframeEvent, SetVariableEvent } from "./Events/SetVariableEvent"; +import type { SetVariableEvent } from "./Events/SetVariableEvent"; type AnswererCallback = ( - query: IframeQueryMap[T]["query"] + query: IframeQueryMap[T]["query"], + source: MessageEventSource | null ) => IframeQueryMap[T]["answer"] | PromiseLike; /** @@ -197,7 +198,7 @@ class IframeListener { }; try { - Promise.resolve(answerer(query.data)) + Promise.resolve(answerer(query.data, message.source)) .then((value) => { iframe?.contentWindow?.postMessage( { @@ -212,21 +213,6 @@ class IframeListener { } catch (reason) { errorHandler(reason); } - - if (isSetVariableIframeEvent(payload.query)) { - // Let's dispatch the message to the other iframes - for (iframe of this.iframes) { - if (iframe.contentWindow !== message.source) { - iframe.contentWindow?.postMessage( - { - type: "setVariable", - data: payload.query.data, - }, - "*" - ); - } - } - } } else if (isIframeEventWrapper(payload)) { if (payload.type === "showLayer" && isLayerEvent(payload.data)) { this._showLayerStream.next(payload.data); @@ -457,6 +443,24 @@ class IframeListener { public unregisterAnswerer(key: keyof IframeQueryMap): void { delete this.answerers[key]; } + + dispatchVariableToOtherIframes(key: string, value: unknown, source: MessageEventSource | null) { + // Let's dispatch the message to the other iframes + for (const iframe of this.iframes) { + if (iframe.contentWindow !== source) { + iframe.contentWindow?.postMessage( + { + type: "setVariable", + data: { + key, + value, + }, + }, + "*" + ); + } + } + } } export const iframeListener = new IframeListener(); diff --git a/front/src/Api/iframe/state.ts b/front/src/Api/iframe/state.ts index 011be1bc..3b551864 100644 --- a/front/src/Api/iframe/state.ts +++ b/front/src/Api/iframe/state.ts @@ -23,11 +23,11 @@ export const initVariables = (_variables: Map): void => { setVariableResolvers.subscribe((event) => { const oldValue = variables.get(event.key); - // If we are setting the same value, no need to do anything. - if (oldValue === event.value) { + // No need to do this check since it is already performed in SharedVariablesManager + /*if (JSON.stringify(oldValue) === JSON.stringify(event.value)) { return; - } + }*/ variables.set(event.key, event.value); const subject = variableSubscribers.get(event.key); diff --git a/front/src/Phaser/Game/SharedVariablesManager.ts b/front/src/Phaser/Game/SharedVariablesManager.ts index 2d015246..6a06d97e 100644 --- a/front/src/Phaser/Game/SharedVariablesManager.ts +++ b/front/src/Phaser/Game/SharedVariablesManager.ts @@ -54,7 +54,7 @@ export class SharedVariablesManager { }); // When a variable is modified from an iFrame - iframeListener.registerAnswerer("setVariable", (event) => { + iframeListener.registerAnswerer("setVariable", (event, source) => { const key = event.key; const object = this.variableObjects.get(key); @@ -82,10 +82,18 @@ export class SharedVariablesManager { throw new Error(errMsg); } + // Let's stop any propagation of the value we set is the same as the existing value. + if (JSON.stringify(event.value) === JSON.stringify(this._variables.get(key))) { + return; + } + this._variables.set(key, event.value); // Dispatch to the room connection. this.roomConnection.emitSetVariableEvent(key, event.value); + + // Dispatch to other iframes + iframeListener.dispatchVariableToOtherIframes(key, event.value, source); }); }