Refactored peer sync logic to use the data providers

This commit is contained in:
Mitchell McCaffrey 2020-05-19 22:15:08 +10:00
parent 0f5f90faa6
commit 887bce81d1
4 changed files with 131 additions and 113 deletions

View File

@ -54,10 +54,7 @@ export function MapDataProvider({ children }) {
} }
async function loadMaps() { async function loadMaps() {
let storedMaps = await database let storedMaps = await database.table("maps").toArray();
.table("maps")
.where({ owner: userId })
.toArray();
const sortedMaps = storedMaps.sort((a, b) => b.created - a.created); const sortedMaps = storedMaps.sort((a, b) => b.created - a.created);
const defaultMapsWithIds = await getDefaultMaps(); const defaultMapsWithIds = await getDefaultMaps();
const allMaps = [...sortedMaps, ...defaultMapsWithIds]; const allMaps = [...sortedMaps, ...defaultMapsWithIds];
@ -129,14 +126,37 @@ export function MapDataProvider({ children }) {
}); });
} }
async function putMap(map) {
await database.table("maps").put(map);
setMaps((prevMaps) => {
const newMaps = [...prevMaps];
const i = newMaps.findIndex((m) => m.id === map.id);
if (i > -1) {
newMaps[i] = { ...newMaps[i], ...map };
} else {
newMaps.unshift(map);
}
return newMaps;
});
}
function getMap(mapId) {
return maps.find((map) => map.id === mapId);
}
const ownedMaps = maps.filter((map) => map.owner === userId);
const value = { const value = {
maps, maps,
ownedMaps,
mapStates, mapStates,
addMap, addMap,
removeMap, removeMap,
resetMap, resetMap,
updateMap, updateMap,
updateMapState, updateMapState,
putMap,
getMap,
}; };
return ( return (
<MapDataContext.Provider value={value}>{children}</MapDataContext.Provider> <MapDataContext.Provider value={value}>{children}</MapDataContext.Provider>

View File

@ -68,11 +68,21 @@ export function TokenDataProvider({ children }) {
} }
async function putToken(token) { async function putToken(token) {
if (tokens.includes((t) => t.id === token.id)) { await database.table("tokens").put(token);
await updateToken(token.id, token); setTokens((prevTokens) => {
const newTokens = [...prevTokens];
const i = newTokens.findIndex((t) => t.id === token.id);
if (i > -1) {
newTokens[i] = { ...newTokens[i], ...token };
} else { } else {
await addToken(token); newTokens.unshift(token);
} }
return newTokens;
});
}
function getToken(tokenId) {
return tokens.find((token) => token.id === tokenId);
} }
const ownedTokens = tokens.filter((token) => token.owner === userId); const ownedTokens = tokens.filter((token) => token.owner === userId);
@ -84,6 +94,7 @@ export function TokenDataProvider({ children }) {
removeToken, removeToken,
updateToken, updateToken,
putToken, putToken,
getToken,
}; };
return ( return (

View File

@ -30,7 +30,7 @@ function SelectMapModal({
}) { }) {
const { userId } = useContext(AuthContext); const { userId } = useContext(AuthContext);
const { const {
maps, ownedMaps,
mapStates, mapStates,
addMap, addMap,
removeMap, removeMap,
@ -44,7 +44,7 @@ function SelectMapModal({
// The map selected in the modal // The map selected in the modal
const [selectedMapId, setSelectedMapId] = useState(null); const [selectedMapId, setSelectedMapId] = useState(null);
const selectedMap = maps.find((map) => map.id === selectedMapId); const selectedMap = ownedMaps.find((map) => map.id === selectedMapId);
const selectedMapState = mapStates.find( const selectedMapState = mapStates.find(
(state) => state.mapId === selectedMapId (state) => state.mapId === selectedMapId
); );
@ -189,7 +189,7 @@ function SelectMapModal({
Select or import a map Select or import a map
</Label> </Label>
<MapTiles <MapTiles
maps={maps} maps={ownedMaps}
onMapAdd={openImageDialog} onMapAdd={openImageDialog}
onMapRemove={handleMapRemove} onMapRemove={handleMapRemove}
selectedMap={selectedMap} selectedMap={selectedMap}

View File

@ -18,9 +18,9 @@ import AuthModal from "../modals/AuthModal";
import AuthContext from "../contexts/AuthContext"; import AuthContext from "../contexts/AuthContext";
import DatabaseContext from "../contexts/DatabaseContext"; import DatabaseContext from "../contexts/DatabaseContext";
import TokenDataContext from "../contexts/TokenDataContext"; import TokenDataContext from "../contexts/TokenDataContext";
import MapDataContext from "../contexts/MapDataContext";
function Game() { function Game() {
const { database } = useContext(DatabaseContext);
const { id: gameId } = useParams(); const { id: gameId } = useParams();
const { authenticationStatus, userId, nickname, setNickname } = useContext( const { authenticationStatus, userId, nickname, setNickname } = useContext(
AuthContext AuthContext
@ -40,55 +40,58 @@ function Game() {
* Map state * Map state
*/ */
const [map, setMap] = useState(null); const [currentMap, setCurrentMap] = useState(null);
const [mapState, setMapState] = useState(null); const [currentMapState, setCurrentMapState] = useState(null);
const [mapLoading, setMapLoading] = useState(false); const [mapLoading, setMapLoading] = useState(false);
const canEditMapDrawing = const canEditMapDrawing =
map !== null && currentMap !== null &&
mapState !== null && currentMapState !== null &&
(mapState.editFlags.includes("drawing") || map.owner === userId); (currentMapState.editFlags.includes("drawing") ||
currentMap.owner === userId);
const canEditFogDrawing = const canEditFogDrawing =
map !== null && currentMap !== null &&
mapState !== null && currentMapState !== null &&
(mapState.editFlags.includes("fog") || map.owner === userId); (currentMapState.editFlags.includes("fog") || currentMap.owner === userId);
const disabledMapTokens = {}; const disabledMapTokens = {};
// If we have a map and state and have the token permission disabled // If we have a map and state and have the token permission disabled
// and are not the map owner // and are not the map owner
if ( if (
mapState !== null && currentMapState !== null &&
map !== null && currentMap !== null &&
!mapState.editFlags.includes("tokens") && !currentMapState.editFlags.includes("tokens") &&
map.owner !== userId currentMap.owner !== userId
) { ) {
for (let token of Object.values(mapState.tokens)) { for (let token of Object.values(currentMapState.tokens)) {
if (token.owner !== userId) { if (token.owner !== userId) {
disabledMapTokens[token.id] = true; disabledMapTokens[token.id] = true;
} }
} }
} }
const { database } = useContext(DatabaseContext);
// Sync the map state to the database after 500ms of inactivity // Sync the map state to the database after 500ms of inactivity
const debouncedMapState = useDebounce(mapState, 500); const debouncedMapState = useDebounce(currentMapState, 500);
useEffect(() => { useEffect(() => {
if ( if (
debouncedMapState && debouncedMapState &&
debouncedMapState.mapId && debouncedMapState.mapId &&
map && currentMap &&
map.owner === userId && currentMap.owner === userId &&
database database
) { ) {
// Update the database directly to avoid re-renders
database database
.table("states") .table("states")
.update(debouncedMapState.mapId, debouncedMapState); .update(debouncedMapState.mapId, debouncedMapState);
} }
}, [map, debouncedMapState, userId, database]); }, [currentMap, debouncedMapState, userId, database]);
function handleMapChange(newMap, newMapState) { function handleMapChange(newMap, newMapState) {
setMapState(newMapState); setCurrentMapState(newMapState);
setMap(newMap); setCurrentMap(newMap);
for (let peer of Object.values(peers)) { for (let peer of Object.values(peers)) {
// Clear the map so the new map state isn't shown on an old map // Clear the map so the new map state isn't shown on an old map
peer.connection.send({ id: "map", data: null }); peer.connection.send({ id: "map", data: null });
@ -110,14 +113,14 @@ function Game() {
} }
function handleMapStateChange(newMapState) { function handleMapStateChange(newMapState) {
setMapState(newMapState); setCurrentMapState(newMapState);
for (let peer of Object.values(peers)) { for (let peer of Object.values(peers)) {
peer.connection.send({ id: "mapState", data: newMapState }); peer.connection.send({ id: "mapState", data: newMapState });
} }
} }
function addMapDrawActions(actions, indexKey, actionsKey) { function addMapDrawActions(actions, indexKey, actionsKey) {
setMapState((prevMapState) => { setCurrentMapState((prevMapState) => {
const newActions = [ const newActions = [
...prevMapState[actionsKey].slice(0, prevMapState[indexKey] + 1), ...prevMapState[actionsKey].slice(0, prevMapState[indexKey] + 1),
...actions, ...actions,
@ -133,11 +136,11 @@ function Game() {
function updateDrawActionIndex(change, indexKey, actionsKey, peerId) { function updateDrawActionIndex(change, indexKey, actionsKey, peerId) {
const newIndex = Math.min( const newIndex = Math.min(
Math.max(mapState[indexKey] + change, -1), Math.max(currentMapState[indexKey] + change, -1),
mapState[actionsKey].length - 1 currentMapState[actionsKey].length - 1
); );
setMapState((prevMapState) => ({ setCurrentMapState((prevMapState) => ({
...prevMapState, ...prevMapState,
[indexKey]: newIndex, [indexKey]: newIndex,
})); }));
@ -206,10 +209,27 @@ function Game() {
* Token state * Token state
*/ */
// Get all tokens from a token state and send it to a peer
function sendTokensToPeer(peer, state) {
let sentTokens = {};
for (let tokenState of Object.values(state.tokens)) {
if (
tokenState.tokenType === "file" &&
!(tokenState.tokenId in sentTokens)
) {
sentTokens[tokenState.tokenId] = true;
const token = getToken(tokenState.tokenId);
// Omit file from token peer will request file if needed
const { file, ...rest } = token;
peer.connection.send({ id: "token", data: rest });
}
}
}
async function handleMapTokenStateCreate(tokenState) { async function handleMapTokenStateCreate(tokenState) {
// If file type token send the token to the other peers // If file type token send the token to the other peers
if (tokenState.tokenType === "file") { if (tokenState.tokenType === "file") {
const token = await database.table("tokens").get(tokenState.tokenId); const token = getToken(tokenState.tokenId);
const { file, ...rest } = token; const { file, ...rest } = token;
for (let peer of Object.values(peers)) { for (let peer of Object.values(peers)) {
peer.connection.send({ id: "token", data: rest }); peer.connection.send({ id: "token", data: rest });
@ -219,10 +239,10 @@ function Game() {
} }
function handleMapTokenStateChange(tokenState) { function handleMapTokenStateChange(tokenState) {
if (mapState === null) { if (currentMapState === null) {
return; return;
} }
setMapState((prevMapState) => ({ setCurrentMapState((prevMapState) => ({
...prevMapState, ...prevMapState,
tokens: { tokens: {
...prevMapState.tokens, ...prevMapState.tokens,
@ -236,7 +256,7 @@ function Game() {
} }
function handleMapTokenStateRemove(tokenState) { function handleMapTokenStateRemove(tokenState) {
setMapState((prevMapState) => { setCurrentMapState((prevMapState) => {
const { [tokenState.id]: old, ...rest } = prevMapState.tokens; const { [tokenState.id]: old, ...rest } = prevMapState.tokens;
return { ...prevMapState, tokens: rest }; return { ...prevMapState, tokens: rest };
}); });
@ -273,58 +293,37 @@ function Game() {
* Peer handlers * Peer handlers
*/ */
const { putToken } = useContext(TokenDataContext); const { putToken, getToken } = useContext(TokenDataContext);
const { putMap, getMap } = useContext(MapDataContext);
function sendTokensToPeer(peer, state) {
let sentTokens = {};
for (let tokenState of Object.values(state.tokens)) {
if (
tokenState.tokenType === "file" &&
!(tokenState.tokenId in sentTokens)
) {
sentTokens[tokenState.tokenId] = true;
database
.table("tokens")
.get(tokenState.tokenId)
.then((token) => {
const { file, ...rest } = token;
peer.connection.send({ id: "token", data: rest });
});
}
}
}
function handlePeerData({ data, peer }) { function handlePeerData({ data, peer }) {
if (data.id === "sync") { if (data.id === "sync") {
if (mapState) { if (currentMapState) {
peer.connection.send({ id: "mapState", data: mapState }); peer.connection.send({ id: "mapState", data: currentMapState });
sendTokensToPeer(peer, mapState); sendTokensToPeer(peer, currentMapState);
} }
if (map) { if (currentMap) {
sendMapDataToPeer(peer, map); sendMapDataToPeer(peer, currentMap);
} }
} }
if (data.id === "map") { if (data.id === "map") {
const newMap = data.data; const newMap = data.data;
// If is a file map check cache and request the full file if outdated // If is a file map check cache and request the full file if outdated
if (newMap && newMap.type === "file") { if (newMap && newMap.type === "file") {
database const cachedMap = getMap(newMap.id);
.table("maps")
.get(newMap.id)
.then((cachedMap) => {
if (cachedMap && cachedMap.lastModified === newMap.lastModified) { if (cachedMap && cachedMap.lastModified === newMap.lastModified) {
setMap(cachedMap); setCurrentMap(cachedMap);
} else { } else {
setMapLoading(true); setMapLoading(true);
peer.connection.send({ id: "mapRequest" }); peer.connection.send({ id: "mapRequest", data: newMap.id });
} }
});
} else { } else {
setMap(newMap); setCurrentMap(newMap);
} }
} }
// Send full map data including file // Send full map data including file
if (data.id === "mapRequest") { if (data.id === "mapRequest") {
const map = getMap(data.data);
peer.connection.send({ id: "mapResponse", data: map }); peer.connection.send({ id: "mapResponse", data: map });
} }
// A new map response with a file attached // A new map response with a file attached
@ -332,27 +331,20 @@ function Game() {
setMapLoading(false); setMapLoading(false);
if (data.data && data.data.type === "file") { if (data.data && data.data.type === "file") {
const newMap = { ...data.data, file: data.data.file }; const newMap = { ...data.data, file: data.data.file };
// Store in db putMap(newMap).then(() => {
database setCurrentMap(newMap);
.table("maps")
.put(newMap)
.then(() => {
setMap(newMap);
}); });
} else { } else {
setMap(data.data); setCurrentMap(data.data);
} }
} }
if (data.id === "mapState") { if (data.id === "mapState") {
setMapState(data.data); setCurrentMapState(data.data);
} }
if (data.id === "token") { if (data.id === "token") {
const newToken = data.data; const newToken = data.data;
if (newToken && newToken.type === "file") { if (newToken && newToken.type === "file") {
database const cachedToken = getToken(newToken.id);
.table("tokens")
.get(newToken.id)
.then((cachedToken) => {
if ( if (
!cachedToken || !cachedToken ||
cachedToken.lastModified !== newToken.lastModified cachedToken.lastModified !== newToken.lastModified
@ -363,16 +355,11 @@ function Game() {
data: newToken.id, data: newToken.id,
}); });
} }
});
} }
} }
if (data.id === "tokenRequest") { if (data.id === "tokenRequest") {
database const token = getToken(data.data);
.table("tokens")
.get(data.data)
.then((token) => {
peer.connection.send({ id: "tokenResponse", data: token }); peer.connection.send({ id: "tokenResponse", data: token });
});
} }
if (data.id === "tokenResponse") { if (data.id === "tokenResponse") {
setMapLoading(false); setMapLoading(false);
@ -382,13 +369,13 @@ function Game() {
} }
} }
if (data.id === "tokenStateEdit") { if (data.id === "tokenStateEdit") {
setMapState((prevMapState) => ({ setCurrentMapState((prevMapState) => ({
...prevMapState, ...prevMapState,
tokens: { ...prevMapState.tokens, ...data.data }, tokens: { ...prevMapState.tokens, ...data.data },
})); }));
} }
if (data.id === "tokenStateRemove") { if (data.id === "tokenStateRemove") {
setMapState((prevMapState) => ({ setCurrentMapState((prevMapState) => ({
...prevMapState, ...prevMapState,
tokens: omit(prevMapState.tokens, Object.keys(data.data)), tokens: omit(prevMapState.tokens, Object.keys(data.data)),
})); }));
@ -403,7 +390,7 @@ function Game() {
addMapDrawActions(data.data, "mapDrawActionIndex", "mapDrawActions"); addMapDrawActions(data.data, "mapDrawActionIndex", "mapDrawActions");
} }
if (data.id === "mapDrawIndex") { if (data.id === "mapDrawIndex") {
setMapState((prevMapState) => ({ setCurrentMapState((prevMapState) => ({
...prevMapState, ...prevMapState,
mapDrawActionIndex: data.data, mapDrawActionIndex: data.data,
})); }));
@ -412,7 +399,7 @@ function Game() {
addMapDrawActions(data.data, "fogDrawActionIndex", "fogDrawActions"); addMapDrawActions(data.data, "fogDrawActionIndex", "fogDrawActions");
} }
if (data.id === "mapFogIndex") { if (data.id === "mapFogIndex") {
setMapState((prevMapState) => ({ setCurrentMapState((prevMapState) => ({
...prevMapState, ...prevMapState,
fogDrawActionIndex: data.data, fogDrawActionIndex: data.data,
})); }));
@ -532,8 +519,8 @@ function Game() {
onStreamEnd={handleStreamEnd} onStreamEnd={handleStreamEnd}
/> />
<Map <Map
map={map} map={currentMap}
mapState={mapState} mapState={currentMapState}
loading={mapLoading} loading={mapLoading}
onMapTokenStateChange={handleMapTokenStateChange} onMapTokenStateChange={handleMapTokenStateChange}
onMapTokenStateRemove={handleMapTokenStateRemove} onMapTokenStateRemove={handleMapTokenStateRemove}