From 6a474691166813e44d8ad6277626b0791ab39643 Mon Sep 17 00:00:00 2001 From: ponderingdemocritus Date: Tue, 17 Dec 2024 20:18:10 +1100 Subject: [PATCH 1/6] cache --- client/package.json | 14 +-- client/src/dojo/indexedDB.ts | 108 ++++++++++++++++++ client/src/dojo/queries.ts | 36 ++---- client/src/dojo/setup.ts | 12 ++ .../resources/InventoryResources.tsx | 9 +- .../components/trading/ResourceArrivals.tsx | 9 +- client/src/ui/layouts/World.tsx | 17 ++- client/src/ui/modules/settings/Settings.tsx | 12 +- pnpm-lock.yaml | 82 ++++++------- 9 files changed, 215 insertions(+), 84 deletions(-) create mode 100644 client/src/dojo/indexedDB.ts diff --git a/client/package.json b/client/package.json index 1249bad2c..a6f0ae922 100644 --- a/client/package.json +++ b/client/package.json @@ -19,14 +19,14 @@ "@bibliothecadao/eternum": "workspace:^", "@cartridge/connector": "0.5.6", "@cartridge/controller": "0.5.6", - "@dojoengine/core": "1.0.4-alpha.3.1.0", - "@dojoengine/create-burner": "1.0.4-alpha.3.1.0", - "@dojoengine/react": "1.0.4-alpha.3.1.0", + "@dojoengine/core": "1.0.4-alpha.3.1.1", + "@dojoengine/create-burner": "1.0.4-alpha.3.1.1", + "@dojoengine/react": "1.0.4-alpha.3.1.1", "@dojoengine/recs": "^2.0.13", - "@dojoengine/state": "1.0.4-alpha.3.1.0", - "@dojoengine/torii-client": "1.0.4-alpha.3.1.0", - "@dojoengine/torii-wasm": "1.0.4-alpha.3.1.0", - "@dojoengine/utils": "1.0.4-alpha.3.1.0", + "@dojoengine/state": "1.0.4-alpha.3.1.1", + "@dojoengine/torii-client": "1.0.4-alpha.3.1.1", + "@dojoengine/torii-wasm": "1.0.4-alpha.3.1.1", + "@dojoengine/utils": "1.0.4-alpha.3.1.1", "@headlessui/react": "^1.7.18", "@latticexyz/utils": "^2.0.0-next.12", "@radix-ui/react-collapsible": "^1.1.1", diff --git a/client/src/dojo/indexedDB.ts b/client/src/dojo/indexedDB.ts new file mode 100644 index 000000000..6f055131b --- /dev/null +++ b/client/src/dojo/indexedDB.ts @@ -0,0 +1,108 @@ +import { Component, Metadata, Schema } from "@dojoengine/recs"; +import { setEntities } from "@dojoengine/state"; +import { Entities } from "@dojoengine/torii-client"; + +const DB_NAME = "eternum-db"; +const DB_VERSION = 1; + +function openDatabase(): Promise { + let db: IDBDatabase; + + return new Promise((resolve, reject) => { + const request: IDBOpenDBRequest = indexedDB.open(DB_NAME, DB_VERSION); + + request.onupgradeneeded = (event: IDBVersionChangeEvent) => { + const db = (event.target as IDBOpenDBRequest).result; + if (!db.objectStoreNames.contains("entities")) { + db.createObjectStore("entities", { keyPath: "id" }); + } + }; + + request.onsuccess = (event: Event) => { + db = (event.target as IDBOpenDBRequest).result; + resolve(db); + }; + + request.onerror = (event: Event) => { + console.error("Database error:", (event.target as IDBOpenDBRequest).error); + reject((event.target as IDBOpenDBRequest).error); + }; + }); +} + +async function syncEntitiesFromStorage( + dbConnection: IDBDatabase, + components: Component[], +): Promise { + return new Promise((resolve, reject) => { + const transaction = dbConnection.transaction(["entities"], "readonly"); + const store = transaction.objectStore("entities"); + const request = store.getAll(); + + request.onsuccess = () => { + const entities = request.result; + const entityMap: Entities = {}; + + for (const entity of entities) { + const { id, ...data } = entity; + entityMap[id] = data; + } + + setEntities(entityMap, components, false); + + resolve(); + }; + + request.onerror = () => { + console.log("Error fetching entities from storage:", request.error); + reject(request.error); + }; + }); +} + +async function insertEntitiesInDB(db: IDBDatabase, entities: Entities): Promise { + return new Promise((resolve, reject) => { + const transaction = db.transaction(["entities"], "readwrite"); + const store = transaction.objectStore("entities"); + + let error: Error | null = null; + + // Handle transaction completion + transaction.oncomplete = () => { + if (error) { + reject(error); + } else { + resolve(); + } + }; + + transaction.onerror = () => { + reject(transaction.error); + }; + + // Store each entity + for (const [entityId, data] of Object.entries(entities)) { + const entityData = { + id: entityId, + ...data, + }; + + const request = store.put(entityData); + + request.onerror = () => { + error = request.error; + }; + } + }); +} + +async function clearCache() { + Object.keys(localStorage) + .filter((x) => x.endsWith("_query")) + .forEach((x) => localStorage.removeItem(x)); + + indexedDB.deleteDatabase(DB_NAME); + location.reload(); +} + +export { clearCache, insertEntitiesInDB, openDatabase, syncEntitiesFromStorage }; diff --git a/client/src/dojo/queries.ts b/client/src/dojo/queries.ts index b0334ecad..5b3b0141a 100644 --- a/client/src/dojo/queries.ts +++ b/client/src/dojo/queries.ts @@ -4,35 +4,11 @@ import { Component, Metadata, Schema } from "@dojoengine/recs"; import { getEntities } from "@dojoengine/state"; import { PatternMatching, ToriiClient } from "@dojoengine/torii-client"; -// on hexception -> fetch below queries based on entityID - -// background sync after load -> - -export const syncPosition = async ( - client: ToriiClient, - components: Component[], - entityID: string, -) => { - await getEntities( - client, - { - Keys: { - keys: [entityID], - pattern_matching: "FixedLen" as PatternMatching, - models: ["s0_eternum-Position"], - }, - }, - components, - [], - [], - 30_000, - ); -}; - export const addToSubscriptionTwoKeyModelbyRealmEntityId = async ( client: ToriiClient, components: Component[], entityID: string[], + db: IDBDatabase, ) => { await getEntities( client, @@ -54,6 +30,8 @@ export const addToSubscriptionTwoKeyModelbyRealmEntityId = async [], entityID: string[], + db: IDBDatabase, ) => { await getEntities( client, @@ -82,6 +61,8 @@ export const addToSubscriptionOneKeyModelbyRealmEntityId = async ( client: ToriiClient, components: Component[], entityID: string[], + db: IDBDatabase, position?: { x: number; y: number }[], ) => { const start = performance.now(); @@ -121,6 +103,8 @@ export const addToSubscription = async ( [], [], 30_000, + false, + { dbConnection: db, timestampCacheKey: `entity_${entityID}_query` }, ); const end = performance.now(); console.log("AddToSubscriptionEnd", end - start); @@ -129,6 +113,7 @@ export const addToSubscription = async ( export const addMarketSubscription = async ( client: ToriiClient, components: Component[], + db: IDBDatabase, ) => { const start = performance.now(); await getEntities( @@ -145,6 +130,7 @@ export const addMarketSubscription = async ( [], 30_000, false, + { dbConnection: db, timestampCacheKey: "market_query" }, ); const end = performance.now(); console.log("MarketEnd", end - start); diff --git a/client/src/dojo/setup.ts b/client/src/dojo/setup.ts index 0c1677cdf..13d162eab 100644 --- a/client/src/dojo/setup.ts +++ b/client/src/dojo/setup.ts @@ -10,6 +10,7 @@ import { Clause, EntityKeysClause, ToriiClient } from "@dojoengine/torii-client" import { debounce } from "lodash"; import { createClientComponents } from "./createClientComponents"; import { createSystemCalls } from "./createSystemCalls"; +import { openDatabase, syncEntitiesFromStorage } from "./indexedDB"; import { ClientConfigManager } from "./modelManager/ConfigManager"; import { setupNetwork } from "./setupNetwork"; @@ -114,10 +115,18 @@ export async function setup({ ...config }: DojoConfig) { }, ]; + const indexedDB = await openDatabase(); + await syncEntitiesFromStorage(indexedDB, network.contractComponents as any); + await getEntities( network.toriiClient, { Composite: { operator: "Or", clauses: configClauses } }, network.contractComponents as any, + [], + [], + 40_000, + false, + { dbConnection: indexedDB, timestampCacheKey: "config_query" }, ); // fetch all existing entities from torii @@ -149,6 +158,7 @@ export async function setup({ ...config }: DojoConfig) { [], 40_000, false, + { dbConnection: indexedDB, timestampCacheKey: "single_keyed_query" }, ); await getEntities( @@ -165,6 +175,7 @@ export async function setup({ ...config }: DojoConfig) { [], 40_000, false, + { dbConnection: indexedDB, timestampCacheKey: "double_keyed_query" }, ); const sync = await syncEntitiesDebounced(network.toriiClient, network.contractComponents as any, [], false); @@ -206,5 +217,6 @@ export async function setup({ ...config }: DojoConfig) { systemCalls, sync, eventSync, + db: indexedDB, }; } diff --git a/client/src/ui/components/resources/InventoryResources.tsx b/client/src/ui/components/resources/InventoryResources.tsx index ce042c370..08f78f79c 100644 --- a/client/src/ui/components/resources/InventoryResources.tsx +++ b/client/src/ui/components/resources/InventoryResources.tsx @@ -51,9 +51,12 @@ export const InventoryResources = ({ setIsSyncing(true); try { - await addToSubscription(dojo.network.toriiClient, dojo.network.contractComponents as any, [ - entityId.toString(), - ]); + await addToSubscription( + dojo.network.toriiClient, + dojo.network.contractComponents as any, + [entityId.toString()], + dojo.setup.db, + ); localStorage.setItem(cacheKey, now.toString()); } catch (error) { console.error("Fetch failed", error); diff --git a/client/src/ui/components/trading/ResourceArrivals.tsx b/client/src/ui/components/trading/ResourceArrivals.tsx index 389d85cc2..1205d597e 100644 --- a/client/src/ui/components/trading/ResourceArrivals.tsx +++ b/client/src/ui/components/trading/ResourceArrivals.tsx @@ -29,9 +29,12 @@ export const AllResourceArrivals = memo( }); // Move API call outside of state updates - addToSubscription(dojo.network.toriiClient, dojo.network.contractComponents as any, unsubscribedIds).catch( - (error) => console.error("Fetch failed", error), - ); + addToSubscription( + dojo.network.toriiClient, + dojo.network.contractComponents as any, + unsubscribedIds, + dojo.setup.db, + ).catch((error) => console.error("Fetch failed", error)); }, [arrivals, subscribedIds]); return ( diff --git a/client/src/ui/layouts/World.tsx b/client/src/ui/layouts/World.tsx index 21c769b63..63d486714 100644 --- a/client/src/ui/layouts/World.tsx +++ b/client/src/ui/layouts/World.tsx @@ -151,28 +151,37 @@ export const World = ({ backgroundImage }: { backgroundImage: string }) => { dojo.network.toriiClient, dojo.network.contractComponents as any, [...filteredStructures.map((structure) => structure.entity_id.toString())], + dojo.setup.db, ), addToSubscriptionTwoKeyModelbyRealmEntityId( dojo.network.toriiClient, dojo.network.contractComponents as any, [...filteredStructures.map((structure) => structure.entity_id.toString())], + dojo.setup.db, ), addToSubscription( dojo.network.toriiClient, dojo.network.contractComponents as any, [structureEntityId.toString()], + dojo.setup.db, [{ x: position?.x || 0, y: position?.y || 0 }], ), - addToSubscription(dojo.network.toriiClient, dojo.network.contractComponents as any, [ - ADMIN_BANK_ENTITY_ID.toString(), - ]), + + addToSubscription( + dojo.network.toriiClient, + dojo.network.contractComponents as any, + [ADMIN_BANK_ENTITY_ID.toString()], + dojo.setup.db, + ), + addToSubscription( dojo.network.toriiClient, dojo.network.contractComponents as any, [...filteredStructures.map((structure) => structure.entity_id.toString())], + dojo.setup.db, [...filteredStructures.map((structure) => ({ x: structure.position.x, y: structure.position.y }))], ), - addMarketSubscription(dojo.network.toriiClient, dojo.network.contractComponents as any), + addMarketSubscription(dojo.network.toriiClient, dojo.network.contractComponents as any, dojo.setup.db), ]); } catch (error) { console.error("Fetch failed", error); diff --git a/client/src/ui/modules/settings/Settings.tsx b/client/src/ui/modules/settings/Settings.tsx index 700b8e54d..ba066498e 100644 --- a/client/src/ui/modules/settings/Settings.tsx +++ b/client/src/ui/modules/settings/Settings.tsx @@ -5,6 +5,7 @@ import { ReactComponent as Unmuted } from "@/assets/icons/common/unmuted.svg"; import { ReactComponent as Controller } from "@/assets/icons/Controller.svg"; import { ReactComponent as DojoMark } from "@/assets/icons/dojo-mark-full-dark.svg"; import { ReactComponent as RealmsWorld } from "@/assets/icons/rw-logo.svg"; +import { clearCache } from "@/dojo/indexedDB"; import { useDojo } from "@/hooks/context/DojoContext"; import { useRealm } from "@/hooks/helpers/useRealm"; import useUIStore from "@/hooks/store/useUIStore"; @@ -61,7 +62,7 @@ export const SettingsWindow = () => { const isOpen = useUIStore((state) => state.isPopupOpen(settings)); - const GRAPHICS_SETTING = localStorage.getItem("GRAPHICS_SETTING") as GraphicsSettings || GraphicsSettings.HIGH; + const GRAPHICS_SETTING = (localStorage.getItem("GRAPHICS_SETTING") as GraphicsSettings) || GraphicsSettings.HIGH; return ( togglePopup(settings)} show={isOpen} title={settings}> @@ -171,6 +172,15 @@ export const SettingsWindow = () => { Github +