Remove cache functions from map and tokens and add to assets

This commit is contained in:
Mitchell McCaffrey 2021-04-30 11:56:37 +10:00
parent fa94275ac8
commit a91d559ba6
9 changed files with 72 additions and 156 deletions

View File

@ -19,7 +19,7 @@ const listTokenClassName = "list-token";
function Tokens({ onMapTokenStateCreate }) {
const { userId } = useAuth();
const { ownedTokens, tokens, updateToken } = useTokenData();
const { ownedTokens, tokens } = useTokenData();
const [fullScreen] = useSetting("map.fullScreen");
function handleProxyDragEnd(isOnMap, token) {
@ -51,13 +51,6 @@ function Tokens({ onMapTokenStateCreate }) {
tokenState.key = token.key;
}
onMapTokenStateCreate(tokenState);
// TODO: Remove when cache is moved to assets
// Update last used for cache invalidation
// Keep last modified the same
updateToken(token.id, {
lastUsed: Date.now(),
lastModified: token.lastModified,
});
}
}

View File

@ -44,9 +44,16 @@ import { omit } from "../helpers/shared";
*/
const AssetsContext = React.createContext();
// 100 MB max cache size
const maxCacheSize = 1e8;
export function AssetsProvider({ children }) {
const { worker, database } = useDatabase();
useEffect(() => {
worker.cleanAssetCache(maxCacheSize);
}, [worker]);
const getAsset = useCallback(
async (assetId) => {
return await database.table("assets").get(assetId);
@ -56,7 +63,7 @@ export function AssetsProvider({ children }) {
const addAssets = useCallback(
async (assets) => {
return database.table("assets").bulkAdd(assets);
await database.table("assets").bulkAdd(assets);
},
[database]
);

View File

@ -1,10 +1,4 @@
import React, {
useEffect,
useState,
useContext,
useCallback,
useRef,
} from "react";
import React, { useEffect, useState, useContext, useCallback } from "react";
import { decode } from "@msgpack/msgpack";
import { useAuth } from "./AuthContext";
@ -14,9 +8,6 @@ import { maps as defaultMaps } from "../maps";
const MapDataContext = React.createContext();
// Maximum number of maps to keep in the cache
const cachedMapMax = 15;
const defaultMapState = {
tokens: {},
drawShapes: {},
@ -89,16 +80,7 @@ export function MapDataProvider({ children }) {
loadMaps();
}, [userId, database, databaseStatus, worker]);
const mapsRef = useRef(maps);
useEffect(() => {
mapsRef.current = maps;
}, [maps]);
const getMap = useCallback((mapId) => {
return mapsRef.current.find((map) => map.id === mapId);
}, []);
const getMapFromDB = useCallback(
const getMap = useCallback(
async (mapId) => {
let map = await database.table("maps").get(mapId);
return map;
@ -106,7 +88,7 @@ export function MapDataProvider({ children }) {
[database]
);
const getMapStateFromDB = useCallback(
const getMapState = useCallback(
async (mapId) => {
let mapState = await database.table("states").get(mapId);
return mapState;
@ -114,25 +96,6 @@ export function MapDataProvider({ children }) {
[database]
);
/**
* Keep up to cachedMapMax amount of maps that you don't own
* Sorted by when they we're last used
*/
const updateCache = useCallback(async () => {
const cachedMaps = await database
.table("maps")
.where("owner")
.notEqual(userId)
.sortBy("lastUsed");
if (cachedMaps.length > cachedMapMax) {
const cacheDeleteCount = cachedMaps.length - cachedMapMax;
const idsToDelete = cachedMaps
.slice(0, cacheDeleteCount)
.map((map) => map.id);
database.table("maps").where("id").anyOf(idsToDelete).delete();
}
}, [database, userId]);
/**
* Adds a map to the database, also adds an assosiated state for that map
* @param {Object} map map to add
@ -143,11 +106,8 @@ export function MapDataProvider({ children }) {
const state = { ...defaultMapState, mapId: map.id };
await database.table("maps").add(map);
await database.table("states").add(state);
if (map.owner !== userId) {
await updateCache();
}
},
[database, updateCache, userId]
[database]
);
const removeMaps = useCallback(
@ -182,16 +142,9 @@ export function MapDataProvider({ children }) {
const updateMap = useCallback(
async (id, update) => {
// fake-indexeddb throws an error when updating maps in production.
// Catch that error and use put when it fails
try {
await database.table("maps").update(id, update);
} catch (error) {
const map = (await getMapFromDB(id)) || {};
await database.table("maps").put({ ...map, id, ...update });
}
await database.table("maps").update(id, update);
},
[database, getMapFromDB]
[database]
);
const updateMaps = useCallback(
@ -210,21 +163,6 @@ export function MapDataProvider({ children }) {
[database]
);
/**
* Adds a map to the database if none exists or replaces a map if it already exists
* Note: this does not add a map state to do that use AddMap
* @param {Object} map the map to put
*/
const putMap = useCallback(
async (map) => {
await database.table("maps").put(map);
if (map.owner !== userId) {
await updateCache();
}
},
[database, updateCache, userId]
);
// Create DB observable to sync creating and deleting
useEffect(() => {
if (!database || databaseStatus === "loading") {
@ -301,11 +239,9 @@ export function MapDataProvider({ children }) {
updateMap,
updateMaps,
updateMapState,
putMap,
getMap,
getMapFromDB,
mapsLoading,
getMapStateFromDB,
getMapState,
};
return (
<MapDataContext.Provider value={value}>{children}</MapDataContext.Provider>

View File

@ -1,10 +1,4 @@
import React, {
useEffect,
useState,
useContext,
useCallback,
useRef,
} from "react";
import React, { useEffect, useState, useContext, useCallback } from "react";
import { decode } from "@msgpack/msgpack";
import { useAuth } from "./AuthContext";
@ -14,8 +8,6 @@ import { tokens as defaultTokens } from "../tokens";
const TokenDataContext = React.createContext();
const cachedTokenMax = 100;
export function TokenDataProvider({ children }) {
const { database, databaseStatus, worker } = useDatabase();
const { userId } = useAuth();
@ -62,16 +54,7 @@ export function TokenDataProvider({ children }) {
loadTokens();
}, [userId, database, databaseStatus, worker]);
const tokensRef = useRef(tokens);
useEffect(() => {
tokensRef.current = tokens;
}, [tokens]);
const getToken = useCallback((tokenId) => {
return tokensRef.current.find((token) => token.id === tokenId);
}, []);
const getTokenFromDB = useCallback(
const getToken = useCallback(
async (tokenId) => {
let token = await database.table("tokens").get(tokenId);
return token;
@ -79,33 +62,11 @@ export function TokenDataProvider({ children }) {
[database]
);
/**
* Keep up to cachedTokenMax amount of tokens that you don't own
* Sorted by when they we're last used
*/
const updateCache = useCallback(async () => {
const cachedTokens = await database
.table("tokens")
.where("owner")
.notEqual(userId)
.sortBy("lastUsed");
if (cachedTokens.length > cachedTokenMax) {
const cacheDeleteCount = cachedTokens.length - cachedTokenMax;
const idsToDelete = cachedTokens
.slice(0, cacheDeleteCount)
.map((token) => token.id);
database.table("tokens").where("id").anyOf(idsToDelete).delete();
}
}, [database, userId]);
const addToken = useCallback(
async (token) => {
await database.table("tokens").add(token);
if (token.owner !== userId) {
await updateCache();
}
},
[database, updateCache, userId]
[database]
);
const removeTokens = useCallback(
@ -142,16 +103,6 @@ export function TokenDataProvider({ children }) {
[database]
);
const putToken = useCallback(
async (token) => {
await database.table("tokens").put(token);
if (token.owner !== userId) {
await updateCache();
}
},
[database, updateCache, userId]
);
// Create DB observable to sync creating and deleting
useEffect(() => {
if (!database || databaseStatus === "loading") {
@ -209,11 +160,9 @@ export function TokenDataProvider({ children }) {
removeTokens,
updateToken,
updateTokens,
putToken,
getToken,
tokensById,
tokensLoading,
getTokenFromDB,
getToken,
};
return (

View File

@ -556,6 +556,7 @@ const versions = {
delete map[res];
}
}
delete map.lastUsed;
});
});
},
@ -573,6 +574,7 @@ const versions = {
} else {
token.outline = "rect";
}
delete token.lastUsed;
});
});
},

View File

@ -13,14 +13,14 @@ import { isEmpty } from "../helpers/shared";
import useResponsiveLayout from "../hooks/useResponsiveLayout";
function EditTokenModal({ isOpen, onDone, tokenId }) {
const { updateToken, getTokenFromDB } = useTokenData();
const { updateToken, getToken } = useTokenData();
const [isLoading, setIsLoading] = useState(true);
const [token, setToken] = useState();
useEffect(() => {
async function loadToken() {
setIsLoading(true);
setToken(await getTokenFromDB(tokenId));
setToken(await getToken(tokenId));
setIsLoading(false);
}
@ -29,7 +29,7 @@ function EditTokenModal({ isOpen, onDone, tokenId }) {
} else {
setToken();
}
}, [isOpen, tokenId, getTokenFromDB]);
}, [isOpen, tokenId, getToken]);
function handleClose() {
setTokenSettingChanges({});

View File

@ -67,11 +67,9 @@ function SelectMapModal({
addMap,
removeMaps,
resetMap,
updateMap,
updateMaps,
mapsLoading,
getMapFromDB,
getMapStateFromDB,
getMapState,
} = useMapData();
const { addAssets } = useAssets();
@ -260,7 +258,11 @@ function SelectMapModal({
}
// Create thumbnail
const thumbnailImage = await createThumbnail(image, file.type);
const thumbnail = { ...thumbnailImage, id: uuid(), owner: userId };
const thumbnail = {
...thumbnailImage,
id: uuid(),
owner: userId,
};
assets.push(thumbnail);
const fileAsset = {
@ -297,7 +299,6 @@ function SelectMapModal({
id: uuid(),
created: Date.now(),
lastModified: Date.now(),
lastUsed: Date.now(),
owner: userId,
...defaultMapProps,
};
@ -392,19 +393,11 @@ function SelectMapModal({
return;
}
if (selectedMapIds.length === 1) {
// Update last used for cache invalidation
const lastUsed = Date.now();
setIsLoading(true);
const map = selectedMaps[0];
const mapState = await getMapStateFromDB(map.id);
if (map.type === "file") {
setIsLoading(true);
await updateMap(map.id, { lastUsed });
const updatedMap = await getMapFromDB(map.id);
onMapChange(updatedMap, mapState);
setIsLoading(false);
} else {
onMapChange(map, mapState);
}
const mapState = await getMapState(map.id);
onMapChange(map, mapState);
setIsLoading(false);
} else {
onMapChange(null, null);
}

View File

@ -196,7 +196,6 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
type: "file",
created: Date.now(),
lastModified: Date.now(),
lastUsed: Date.now(),
owner: userId,
defaultSize: 1,
defaultCategory: "character",

View File

@ -141,6 +141,43 @@ let service = {
});
importDB.close();
},
/**
* Ensure the asset cache doesn't go over `maxCacheSize` by removing cached assets
* Removes largest assets first
* @param {number} maxCacheSize Max size of cache in bytes
*/
async cleanAssetCache(maxCacheSize) {
try {
let db = getDatabase({});
const userId = (await db.table("user").get("userId")).value;
const cachedAssets = await db
.table("assets")
.where("owner")
.notEqual(userId)
.toArray();
const totalSize = cachedAssets.reduce(
(acc, cur) => acc + cur.file.byteLength,
0
);
if (totalSize > maxCacheSize) {
// Remove largest assets first
const largestAssets = cachedAssets.sort(
(a, b) => b.file.byteLength - a.file.byteLength
);
let assetsToDelete = [];
let deletedBytes = 0;
for (let asset of largestAssets) {
assetsToDelete.push(asset.id);
deletedBytes += asset.file.byteLength;
if (totalSize - deletedBytes < maxCacheSize) {
break;
}
}
await db.table("assets").bulkDelete(assetsToDelete);
}
} catch {}
},
};
Comlink.expose(service);