diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index 4837cd0d09..a97dac509c 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -12,7 +12,6 @@ jobs: release-snapshot: name: Publish snapshot release to npm runs-on: ubuntu-latest - if: github.event_name != 'workflow_dispatch' steps: - name: Checkout uses: actions/checkout@v3 diff --git a/packages/cli/src/commands/trace.ts b/packages/cli/src/commands/trace.ts index 8762d5072c..e21ff7ba90 100644 --- a/packages/cli/src/commands/trace.ts +++ b/packages/cli/src/commands/trace.ts @@ -7,7 +7,7 @@ import { MUDError } from "@latticexyz/common/errors"; import { cast, getRpcUrl, getSrcDirectory } from "@latticexyz/common/foundry"; import { StoreConfig } from "@latticexyz/store"; import { resolveWorldConfig, WorldConfig } from "@latticexyz/world"; -import IBaseWorldData from "@latticexyz/world/out/IBaseWorld.sol/IBaseWorld.abi.json" assert { type: "json" }; +import IBaseWorldAbi from "@latticexyz/world/out/IBaseWorld.sol/IBaseWorld.abi.json" assert { type: "json" }; import worldConfig from "@latticexyz/world/mud.config.js"; import { tableIdToHex } from "@latticexyz/common"; import { getChainId, getExistingContracts } from "../utils"; @@ -65,7 +65,7 @@ const commandModule: CommandModule = { // Create World contract instance from deployed address const provider = new ethers.providers.StaticJsonRpcProvider(rpc); - const WorldContract = new ethers.Contract(worldAddress, IBaseWorldData.abi, provider); + const WorldContract = new ethers.Contract(worldAddress, IBaseWorldAbi, provider); // TODO account for multiple namespaces (https://github.com/latticexyz/mud/issues/994) const namespace = mudConfig.namespace; diff --git a/packages/store-indexer/src/postgres/createQueryAdapter.ts b/packages/store-indexer/src/postgres/createQueryAdapter.ts index 17600fca5d..f9f3880929 100644 --- a/packages/store-indexer/src/postgres/createQueryAdapter.ts +++ b/packages/store-indexer/src/postgres/createQueryAdapter.ts @@ -13,11 +13,11 @@ import { getAddress } from "viem"; */ export async function createQueryAdapter(database: PgDatabase): Promise { const adapter: QueryAdapter = { - async findAll(chainId, address) { + async findAll({ chainId, address, tableIds }) { const internalTables = buildInternalTables(); - const tables = (await getTables(database)).filter( - (table) => address != null && getAddress(address) === getAddress(table.address) - ); + const tables = (await getTables(database)) + .filter((table) => address == null || getAddress(address) === getAddress(table.address)) + .filter((table) => tableIds == null || tableIds.includes(table.tableId)); const tablesWithRecords = await Promise.all( tables.map(async (table) => { diff --git a/packages/store-indexer/src/sqlite/createQueryAdapter.ts b/packages/store-indexer/src/sqlite/createQueryAdapter.ts index eae41e90c4..65552f1999 100644 --- a/packages/store-indexer/src/sqlite/createQueryAdapter.ts +++ b/packages/store-indexer/src/sqlite/createQueryAdapter.ts @@ -1,8 +1,9 @@ -import { eq } from "drizzle-orm"; +import { eq, inArray, and } from "drizzle-orm"; import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core"; import { createSqliteTable, chainState, getTables } from "@latticexyz/store-sync/sqlite"; import { QueryAdapter } from "@latticexyz/store-sync/trpc-indexer"; import { debug } from "../debug"; +import { Hex, getAddress } from "viem"; /** * Creates a storage adapter for the tRPC server/client to query data from SQLite. @@ -12,12 +13,51 @@ import { debug } from "../debug"; */ export async function createQueryAdapter(database: BaseSQLiteDatabase<"sync", any>): Promise { const adapter: QueryAdapter = { - async findAll(chainId, address) { - const tables = getTables(database).filter((table) => table.address === address); + async findAll({ chainId, address, tableIds, matchId }) { + const tables = getTables(database) + .filter((table) => address == null || getAddress(address) === getAddress(table.address)) + .filter((table) => tableIds == null || tableIds.includes(table.tableId)) + .filter((table) => + // we don't need KeysWithValue tables + matchId != null ? table.namespace !== "keyswval" : true + ); + + const entities = ((): Hex[] => { + if (!address || !matchId) return []; + try { + const Position = createSqliteTable({ + address, + namespace: "", + name: "Position", + keySchema: { key: "bytes32" }, + valueSchema: { + x: "int32", + y: "int32", + z: "int32", + }, + }); + const positions = database.select().from(Position).where(eq(Position.z, matchId)).all(); + return positions.map((pos) => pos.key); + } catch (error: unknown) { + return []; + } + })(); const tablesWithRecords = tables.map((table) => { const sqliteTable = createSqliteTable(table); - const records = database.select().from(sqliteTable).where(eq(sqliteTable.__isDeleted, false)).all(); + const records = database + .select() + .from(sqliteTable) + .where( + and( + eq(sqliteTable.__isDeleted, false), + entities.length && + (table.name === "MoveDifficulty" || table.name === "TerrainType") /* || table.name === "ArmorModifier"*/ + ? inArray(sqliteTable.__key, entities) + : undefined + ) + ) + .all(); return { ...table, records: records.map((record) => ({ @@ -35,7 +75,19 @@ export async function createQueryAdapter(database: BaseSQLiteDatabase<"sync", an tables: tablesWithRecords, }; - debug("findAll", chainId, address, result); + const count = tablesWithRecords.reduce((sum, table) => sum + table.records.length, 0); + + debug( + "findAll", + "chainId:", + chainId, + "address:", + address, + "tables:", + tablesWithRecords.length, + "records:", + count + ); return result; }, diff --git a/packages/store-sync/src/common.ts b/packages/store-sync/src/common.ts index d903b05ba1..0f50e1ed3c 100644 --- a/packages/store-sync/src/common.ts +++ b/packages/store-sync/src/common.ts @@ -99,6 +99,10 @@ export type SyncOptions = { * Optional maximum block range, if your RPC limits the amount of blocks fetched at a time. */ maxBlockRange?: bigint; + /** + * Optional table IDs to filter indexer state and RPC state. + */ + tableIds?: Hex[]; /** * Optional MUD tRPC indexer URL to fetch initial state from. */ @@ -110,6 +114,8 @@ export type SyncOptions = { blockNumber: bigint | null; tables: TableWithRecords[]; }; + /** Sky Strife-specific option to filter data by match ID */ + matchId?: number; }; export type SyncResult = { diff --git a/packages/store-sync/src/createStoreSync.ts b/packages/store-sync/src/createStoreSync.ts index 2e0cfc0857..082fd59f5d 100644 --- a/packages/store-sync/src/createStoreSync.ts +++ b/packages/store-sync/src/createStoreSync.ts @@ -1,6 +1,6 @@ import { ConfigToKeyPrimitives, ConfigToValuePrimitives, StoreConfig, storeEventsAbi } from "@latticexyz/store"; import { Hex, TransactionReceiptNotFoundError } from "viem"; -import { SetRecordOperation, StorageAdapter, SyncOptions, SyncResult, TableWithRecords } from "./common"; +import { BlockLogs, SetRecordOperation, StorageAdapter, SyncOptions, SyncResult, TableWithRecords } from "./common"; import { createBlockStream, blockRangeToLogs, groupLogsByBlockNumber } from "@latticexyz/block-logs-stream"; import { filter, @@ -19,6 +19,7 @@ import { combineLatest, scan, identity, + Observable, } from "rxjs"; import { BlockStorageOperations, blockLogsToStorage } from "./blockLogsToStorage"; import { debug as parentDebug } from "./debug"; @@ -48,8 +49,10 @@ export async function createStoreSync publicClient, startBlock: initialStartBlock = 0n, maxBlockRange, + tableIds, initialState, indexerUrl, + matchId, }: CreateStoreSyncOptions): Promise> { const initialState$ = defer( async (): Promise< @@ -74,7 +77,7 @@ export async function createStoreSync const indexer = createIndexerClient({ url: indexerUrl }); const chainId = publicClient.chain?.id ?? (await publicClient.getChainId()); - const result = await indexer.findAll.query({ chainId, address }); + const result = await indexer.findAll.query({ chainId, address, tableIds, matchId }); onProgress?.({ step: SyncStep.SNAPSHOT, @@ -114,6 +117,13 @@ export async function createStoreSync (initialState): initialState is { blockNumber: bigint; tables: TableWithRecords[] } => initialState != null && initialState.blockNumber != null && initialState.tables.length > 0 ), + // Initial state from indexer should already be filtered by table IDs, but we should + // still attempt to filter in case initialState was passed in as an argument or the + // indexer is being silly. + map(({ blockNumber, tables }) => ({ + blockNumber, + tables: tables.filter((table) => tableIds != null && tableIds.includes(table.tableId)), + })), concatMap(async ({ blockNumber, tables }) => { debug("hydrating from initial state to block", blockNumber); @@ -179,7 +189,7 @@ export async function createStoreSync let startBlock: bigint | null = null; let endBlock: bigint | null = null; - const blockLogs$ = combineLatest([startBlock$, latestBlockNumber$]).pipe( + const blockLogs$: Observable = combineLatest([startBlock$, latestBlockNumber$]).pipe( map(([startBlock, endBlock]) => ({ startBlock, endBlock })), tap((range) => { startBlock = range.startBlock; @@ -192,6 +202,10 @@ export async function createStoreSync maxBlockRange, }), mergeMap(({ toBlock, logs }) => from(groupLogsByBlockNumber(logs, toBlock))), + map(({ blockNumber, logs }) => ({ + blockNumber, + logs: logs.filter((log) => tableIds != null && tableIds.includes(log.args.table)), + })), share() ); diff --git a/packages/store-sync/src/recs/syncToRecs.ts b/packages/store-sync/src/recs/syncToRecs.ts index aecf0231ec..bbe7be83fd 100644 --- a/packages/store-sync/src/recs/syncToRecs.ts +++ b/packages/store-sync/src/recs/syncToRecs.ts @@ -27,6 +27,7 @@ export async function syncToRecs({ initialState, indexerUrl, startSync = true, + matchId, }: SyncToRecsOptions): Promise> { const { storageAdapter, components } = recsStorage({ world, config }); @@ -39,6 +40,7 @@ export async function syncToRecs({ maxBlockRange, indexerUrl, initialState, + matchId, onProgress: ({ step, percentage, latestBlockNumber, lastBlockNumberProcessed, message }) => { if (getComponentValue(components.SyncProgress, singletonEntity)?.step !== SyncStep.LIVE) { setComponent(components.SyncProgress, singletonEntity, { diff --git a/packages/store-sync/src/trpc-indexer/common.ts b/packages/store-sync/src/trpc-indexer/common.ts index c9ca712880..082054202d 100644 --- a/packages/store-sync/src/trpc-indexer/common.ts +++ b/packages/store-sync/src/trpc-indexer/common.ts @@ -2,10 +2,7 @@ import { Hex } from "viem"; import { TableWithRecords } from "../common"; export type QueryAdapter = { - findAll: ( - chainId: number, - address?: Hex - ) => Promise<{ + findAll: (opts: { chainId: number; address?: Hex; tableIds?: Hex[]; matchId?: number }) => Promise<{ blockNumber: bigint | null; tables: TableWithRecords[]; }>; diff --git a/packages/store-sync/src/trpc-indexer/createAppRouter.ts b/packages/store-sync/src/trpc-indexer/createAppRouter.ts index c965dac7c5..e773edfcdc 100644 --- a/packages/store-sync/src/trpc-indexer/createAppRouter.ts +++ b/packages/store-sync/src/trpc-indexer/createAppRouter.ts @@ -16,12 +16,12 @@ export function createAppRouter() { z.object({ chainId: z.number(), address: z.string().refine(isHex).optional(), + tableIds: z.array(z.string().refine(isHex)).optional(), + matchId: z.number().optional(), }) ) - .query(async (opts): ReturnType => { - const { queryAdapter } = opts.ctx; - const { chainId, address } = opts.input; - return queryAdapter.findAll(chainId, address); + .query(async ({ ctx, input }): ReturnType => { + return ctx.queryAdapter.findAll(input); }), }); }