From 8eb3fb67b541568062d42453c63334849c4ccf19 Mon Sep 17 00:00:00 2001 From: DogLooksGood Date: Fri, 29 Sep 2023 12:50:19 +0800 Subject: [PATCH] Improve game bundle caching, add metadata field to INft --- CHANGELOG.md | 5 ++++ js/sdk-core/src/accounts.ts | 2 ++ js/sdk-core/src/app-client.ts | 41 +++++++++++++++++++++++---- js/sdk-solana/src/solana-transport.ts | 1 + 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddf81797..28502269 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,16 @@ Race Protocol: A multi-chain infrastructure for asymmetric competitive games # Master(Unreleased) +# 0.2.1 + ## Features - CLI: Add `claim` command to claim tokens from a recipient account. ## Enhancements - SDK: Add caching for token/NFT fetching. +- SDK: Add caching for game bundle fetching. (Caching for wasm module is TBD) +- SDK: Add `data` and `dataLen` to `AppHelper.info`. +- SDK: Add original metadata to NFT structure. ## Breaking changes - CONTRACT: Remove `claim_amount_cap`. diff --git a/js/sdk-core/src/accounts.ts b/js/sdk-core/src/accounts.ts index b28175de..f476ed95 100644 --- a/js/sdk-core/src/accounts.ts +++ b/js/sdk-core/src/accounts.ts @@ -125,6 +125,7 @@ export interface INft { readonly name: string; readonly symbol: string; readonly collection: string | undefined; + readonly metadata: any; } export interface IRecipientAccount { @@ -242,6 +243,7 @@ export class Nft implements INft { readonly symbol!: string; @field(option('string')) readonly collection: string | undefined; + readonly metadata: any; constructor(fields: INft) { Object.assign(this, fields); } diff --git a/js/sdk-core/src/app-client.ts b/js/sdk-core/src/app-client.ts index cb704ed8..327d6bfd 100644 --- a/js/sdk-core/src/app-client.ts +++ b/js/sdk-core/src/app-client.ts @@ -18,14 +18,16 @@ import { IWallet } from './wallet'; import { Handler, InitAccount } from './handler'; import { Encryptor, IEncryptor } from './encryptor'; import { SdkError } from './error'; -import { EntryType, EntryTypeCash, GameAccount, IToken, PlayerProfile } from './accounts'; +import { EntryType, EntryTypeCash, GameAccount, GameBundle, IToken, PlayerProfile } from './accounts'; import { TxState } from './tx-state'; import { Client } from './client'; import { Custom, GameEvent, ICustomEvent } from './events'; import { ProfileCache } from './profile-cache'; -import { IStorage } from './storage'; +import { IStorage, getTtlCache, setTtlCache } from './storage'; import { DecryptionCache } from './decryption-cache'; +const BUNDLE_CACHE_TTL = 3600 * 365; + export type EventCallbackFunction = ( context: GameContextSnapshot, state: Uint8Array, @@ -60,6 +62,8 @@ export type GameInfo = { token: IToken; tokenAddr: string; bundleAddr: string; + data: Uint8Array; + dataLen: number; }; export class AppClient { @@ -79,6 +83,7 @@ export class AppClient { #profileCaches: ProfileCache; #info: GameInfo; #decryptionCache: DecryptionCache; + #storage?: IStorage; constructor( gameAddr: string, @@ -95,7 +100,8 @@ export class AppClient { onConnectionState: OnConnectionStateCallbackFunction | undefined, encryptor: IEncryptor, info: GameInfo, - decryptionCache: DecryptionCache + decryptionCache: DecryptionCache, + storage?: IStorage, ) { this.#gameAddr = gameAddr; this.#handler = handler; @@ -113,6 +119,7 @@ export class AppClient { this.#profileCaches = new ProfileCache(transport); this.#info = info; this.#decryptionCache = decryptionCache; + this.#storage = storage; } static async initialize(opts: AppClientInitOpts): Promise { @@ -126,11 +133,30 @@ export class AppClient { throw SdkError.gameAccountNotFound(gameAddr); } console.log('Game account:', gameAccount); - const gameBundle = await transport.getGameBundle(gameAccount.bundleAddr); + + // Fetch game bundle + // The bundle can be considered as immutable, so we use cache whenever possible + const bundleCacheKey = `BUNDLE__${transport.chain}_${gameAccount.bundleAddr}`; + + let gameBundle: GameBundle | undefined; + if (storage !== undefined) { + gameBundle = getTtlCache(storage, bundleCacheKey); + console.log('Use game bundle from cache:', gameBundle); + if (gameBundle !== undefined) { + Object.assign(gameBundle, { data: Uint8Array.of() }) + } + } + if (gameBundle === undefined) { + gameBundle = await transport.getGameBundle(gameAccount.bundleAddr); + console.log('Game bundle:', gameBundle); + if (gameBundle !== undefined && storage !== undefined && gameBundle.data.length === 0) { + setTtlCache(storage, bundleCacheKey, gameBundle, BUNDLE_CACHE_TTL); + } + } if (gameBundle === undefined) { throw SdkError.gameBundleNotFound(gameAccount.bundleAddr); } - console.log('Game bundle:', gameBundle); + const transactorAddr = gameAccount.transactorAddr; if (transactorAddr === undefined) { throw SdkError.gameNotServed(gameAddr); @@ -156,6 +182,8 @@ export class AppClient { maxPlayers: gameAccount.maxPlayers, tokenAddr: gameAccount.tokenAddr, bundleAddr: gameAccount.bundleAddr, + data: gameAccount.data, + dataLen: gameAccount.dataLen, token, }; @@ -179,7 +207,8 @@ export class AppClient { onConnectionState, encryptor, info, - decryptionCache + decryptionCache, + storage, ); } finally { console.groupEnd(); diff --git a/js/sdk-solana/src/solana-transport.ts b/js/sdk-solana/src/solana-transport.ts index 2e7bec98..3b23638a 100644 --- a/js/sdk-solana/src/solana-transport.ts +++ b/js/sdk-solana/src/solana-transport.ts @@ -543,6 +543,7 @@ export class SolanaTransport implements ITransport { symbol: trimString(metadataState.data.symbol), image, collection: metadataState?.collection?.key.toBase58(), + metadata: metadataState, }; if (storage !== undefined) {