diff --git a/src/helpers/useNetworkedState.js b/src/helpers/useNetworkedState.js new file mode 100644 index 0000000..dd13746 --- /dev/null +++ b/src/helpers/useNetworkedState.js @@ -0,0 +1,24 @@ +import { useEffect, useState, useRef } from "react"; + +function useNetworkedState(defaultState, session, eventName) { + const [state, _setState] = useState(defaultState); + // Used to control whether the state needs to be sent to the socket + const dirtyRef = useRef(false); + + // Update dirty at the same time as state + function setState(update, sync = true) { + dirtyRef.current = sync; + _setState(update); + } + + useEffect(() => { + if (dirtyRef.current) { + session.socket.emit(eventName, state); + dirtyRef.current = false; + } + }, [state, eventName]); + + return [state, setState]; +} + +export default useNetworkedState; diff --git a/src/network/NetworkedMapAndTokens.js b/src/network/NetworkedMapAndTokens.js index 8566c54..b342959 100644 --- a/src/network/NetworkedMapAndTokens.js +++ b/src/network/NetworkedMapAndTokens.js @@ -8,6 +8,7 @@ import DatabaseContext from "../contexts/DatabaseContext"; import { omit } from "../helpers/shared"; import useDebounce from "../helpers/useDebounce"; +import useNetworkedState from "../helpers/useNetworkedState"; // Load session for auto complete // eslint-disable-next-line no-unused-vars import Session from "./Session"; @@ -38,7 +39,11 @@ function NetworkedMapAndTokens({ session }) { ); const [currentMap, setCurrentMap] = useState(null); - const [currentMapState, setCurrentMapState] = useState(null); + const [currentMapState, setCurrentMapState] = useNetworkedState( + null, + session, + "map_state" + ); /** * Map state @@ -69,7 +74,6 @@ function NetworkedMapAndTokens({ session }) { return; } - session.send("mapState", newMapState); session.send("map", getMapDataToSend(newMap), "map"); const tokensToSend = getMapTokensToSend(newMapState); for (let token of tokensToSend) { @@ -90,7 +94,6 @@ function NetworkedMapAndTokens({ session }) { function handleMapStateChange(newMapState) { setCurrentMapState(newMapState); - session.send("mapState", newMapState); } function addMapDrawActions(actions, indexKey, actionsKey) { @@ -123,48 +126,26 @@ function NetworkedMapAndTokens({ session }) { function handleMapDraw(action) { addMapDrawActions([action], "mapDrawActionIndex", "mapDrawActions"); - session.send("mapDraw", [action]); } function handleMapDrawUndo() { - const index = updateDrawActionIndex( - -1, - "mapDrawActionIndex", - "mapDrawActions" - ); - session.send("mapDrawIndex", index); + updateDrawActionIndex(-1, "mapDrawActionIndex", "mapDrawActions"); } function handleMapDrawRedo() { - const index = updateDrawActionIndex( - 1, - "mapDrawActionIndex", - "mapDrawActions" - ); - session.send("mapDrawIndex", index); + updateDrawActionIndex(1, "mapDrawActionIndex", "mapDrawActions"); } function handleFogDraw(action) { addMapDrawActions([action], "fogDrawActionIndex", "fogDrawActions"); - session.send("mapFog", [action]); } function handleFogDrawUndo() { - const index = updateDrawActionIndex( - -1, - "fogDrawActionIndex", - "fogDrawActions" - ); - session.send("mapFogIndex", index); + updateDrawActionIndex(-1, "fogDrawActionIndex", "fogDrawActions"); } function handleFogDrawRedo() { - const index = updateDrawActionIndex( - 1, - "fogDrawActionIndex", - "fogDrawActions" - ); - session.send("mapFogIndex", index); + updateDrawActionIndex(1, "fogDrawActionIndex", "fogDrawActions"); } function handleNoteChange(note) { @@ -175,7 +156,6 @@ function NetworkedMapAndTokens({ session }) { [note.id]: note, }, })); - session.send("mapNoteChange", note); } function handleNoteRemove(noteId) { @@ -183,7 +163,6 @@ function NetworkedMapAndTokens({ session }) { ...prevMapState, notes: omit(prevMapState.notes, [noteId]), })); - session.send("mapNoteRemove", noteId); } /** @@ -234,7 +213,6 @@ function NetworkedMapAndTokens({ session }) { ...change, }, })); - session.send("tokenStateEdit", change); } function handleMapTokenStateRemove(tokenState) { @@ -242,14 +220,12 @@ function NetworkedMapAndTokens({ session }) { const { [tokenState.id]: old, ...rest } = prevMapState.tokens; return { ...prevMapState, tokens: rest }; }); - session.send("tokenStateRemove", { [tokenState.id]: tokenState }); } useEffect(() => { async function handlePeerData({ id, data, reply }) { if (id === "sync") { if (currentMapState) { - reply("mapState", currentMapState); const tokensToSend = getMapTokensToSend(currentMapState); for (let token of tokensToSend) { reply("token", token, "token"); @@ -354,9 +330,6 @@ function NetworkedMapAndTokens({ session }) { const updatedMap = await getMapFromDB(data.id); setCurrentMap(updatedMap); } - if (id === "mapState") { - setCurrentMapState(data); - } if (id === "token") { const newToken = data; if (newToken && newToken.type === "file") { @@ -384,51 +357,6 @@ function NetworkedMapAndTokens({ session }) { putToken(newToken); } } - if (id === "tokenStateEdit" && currentMapState) { - setCurrentMapState((prevMapState) => ({ - ...prevMapState, - tokens: { ...prevMapState.tokens, ...data }, - })); - } - if (id === "tokenStateRemove" && currentMapState) { - setCurrentMapState((prevMapState) => ({ - ...prevMapState, - tokens: omit(prevMapState.tokens, Object.keys(data)), - })); - } - if (id === "mapDraw" && currentMapState) { - addMapDrawActions(data, "mapDrawActionIndex", "mapDrawActions"); - } - if (id === "mapDrawIndex" && currentMapState) { - setCurrentMapState((prevMapState) => ({ - ...prevMapState, - mapDrawActionIndex: data, - })); - } - if (id === "mapFog" && currentMapState) { - addMapDrawActions(data, "fogDrawActionIndex", "fogDrawActions"); - } - if (id === "mapFogIndex" && currentMapState) { - setCurrentMapState((prevMapState) => ({ - ...prevMapState, - fogDrawActionIndex: data, - })); - } - if (id === "mapNoteChange" && currentMapState) { - setCurrentMapState((prevMapState) => ({ - ...prevMapState, - notes: { - ...prevMapState.notes, - [data.id]: data, - }, - })); - } - if (id === "mapNoteRemove" && currentMapState) { - setCurrentMapState((prevMapState) => ({ - ...prevMapState, - notes: omit(prevMapState.notes, [data]), - })); - } } function handlePeerDataProgress({ id, total, count }) { @@ -441,12 +369,22 @@ function NetworkedMapAndTokens({ session }) { assetProgressUpdate({ id, total, count }); } + function handleSocketMapState(mapState) { + setCurrentMapState(mapState, false); + } + session.on("data", handlePeerData); session.on("dataProgress", handlePeerDataProgress); + if (session.socket) { + session.socket.on("map_state", handleSocketMapState); + } return () => { session.off("data", handlePeerData); session.off("dataProgress", handlePeerDataProgress); + if (session.socket) { + session.socket.off("map_state", handleSocketMapState); + } }; }); diff --git a/src/network/Session.js b/src/network/Session.js index 0a9b360..d6d50ac 100644 --- a/src/network/Session.js +++ b/src/network/Session.js @@ -59,7 +59,7 @@ class Session extends EventEmitter { _iceServers; // Store party id and password for reconnect - _partyId; + _gameId; _password; constructor() { @@ -83,17 +83,11 @@ class Session extends EventEmitter { withCredentials: true, }); - this.socket.on( - "party member joined", - this._handlePartyMemberJoined.bind(this) - ); - this.socket.on( - "party member left", - this._handlePartyMemberLeft.bind(this) - ); - this.socket.on("joined party", this._handleJoinedParty.bind(this)); + this.socket.on("player_joined", this._handlePlayerJoined.bind(this)); + this.socket.on("player_left", this._handlePlayerLeft.bind(this)); + this.socket.on("joined_game", this._handleJoinedGame.bind(this)); this.socket.on("signal", this._handleSignal.bind(this)); - this.socket.on("auth error", this._handleAuthError.bind(this)); + this.socket.on("auth_error", this._handleAuthError.bind(this)); this.socket.on("disconnect", this._handleSocketDisconnect.bind(this)); this.socket.io.on("reconnect", this._handleSocketReconnect.bind(this)); @@ -120,22 +114,22 @@ class Session extends EventEmitter { /** * Join a party * - * @param {string} partyId - the id of the party to join + * @param {string} gameId - the id of the party to join * @param {string} password - the password of the party */ - async joinParty(partyId, password) { - if (typeof partyId !== "string" || typeof password !== "string") { + async joinGame(gameId, password) { + if (typeof gameId !== "string" || typeof password !== "string") { console.error( - "Unable to join party: invalid party ID or password", - partyId, + "Unable to join game: invalid game ID or password", + gameId, password ); return; } - this._partyId = partyId; + this._gameId = gameId; this._password = password; - this.socket.emit("join party", partyId, password); + this.socket.emit("join_game", gameId, password); } _addPeer(id, initiator, sync) { @@ -226,18 +220,7 @@ class Session extends EventEmitter { } } - _handlePartyMemberJoined(id) { - this._addPeer(id, false, false); - } - - _handlePartyMemberLeft(id) { - if (id in this.peers) { - this.peers[id].connection.destroy(); - delete this.peers[id]; - } - } - - _handleJoinedParty(otherIds) { + _handleJoinedGame(otherIds) { for (let i = 0; i < otherIds.length; i++) { const id = otherIds[i]; // Send a sync request to the first member of the party @@ -248,6 +231,17 @@ class Session extends EventEmitter { this.emit("connected"); } + _handlePlayerJoined(id) { + this._addPeer(id, false, false); + } + + _handlePlayerLeft(id) { + if (id in this.peers) { + this.peers[id].connection.destroy(); + delete this.peers[id]; + } + } + _handleSignal(data) { const { from, signal } = data; if (from in this.peers) { @@ -273,8 +267,8 @@ class Session extends EventEmitter { } _handleSocketReconnect() { - if (this._partyId) { - this.joinParty(this._partyId, this._password); + if (this._gameId) { + this.joinGame(this._gameId, this._password); } } } diff --git a/src/routes/Game.js b/src/routes/Game.js index 76d4a15..11580f4 100644 --- a/src/routes/Game.js +++ b/src/routes/Game.js @@ -93,7 +93,7 @@ function Game() { // Join game useEffect(() => { if (session.state === "online" && databaseStatus !== "loading") { - session.joinParty(gameId, password); + session.joinGame(gameId, password); } }, [gameId, password, databaseStatus, session, offline]);