IndexedDB के साथ game state सेव और लोड करें
browser में पर्सिस्टेंट game डेटा के लिए IndexedDB सबसे बढ़िया विकल्प है. यह बड़ी मात्रा में डेटा संभालता है, ऑफलाइन काम करता है, और main thread को ब्लॉक नहीं करता.
1) localStorage के बजाय IndexedDB क्यों?
| फीचर | localStorage | IndexedDB |
|---|---|---|
| स्टोरेज लिमिट | ~5-10 MB | 50+ MB (अक्सर GB) |
| डेटा टाइप | सिर्फ strings | objects, blobs, arrays |
| Async | नहीं (ब्लॉक करता है) | हाँ |
| Indexed queries | नहीं | हाँ |
games के लिए IndexedDB लगभग हमेशा सही चुनाव होता है.
2) एक database खोलना
function openGameDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('MyGame', 1)
request.onerror = () => reject(request.error)
request.onsuccess = () => resolve(request.result)
request.onupgradeneeded = (event) => {
const db = event.target.result
// Create stores
if (!db.objectStoreNames.contains('saves')) {
db.createObjectStore('saves', { keyPath: 'slot' })
}
if (!db.objectStoreNames.contains('settings')) {
db.createObjectStore('settings', { keyPath: 'key' })
}
}
})
}3) game state सेव करना
async function saveGame(slot, gameState) {
const db = await openGameDB()
return new Promise((resolve, reject) => {
const tx = db.transaction('saves', 'readwrite')
const store = tx.objectStore('saves')
const saveData = {
slot,
state: gameState,
timestamp: Date.now(),
}
const request = store.put(saveData)
request.onsuccess = () => resolve()
request.onerror = () => reject(request.error)
})
}4) game state लोड करना
async function loadGame(slot) {
const db = await openGameDB()
return new Promise((resolve, reject) => {
const tx = db.transaction('saves', 'readonly')
const store = tx.objectStore('saves')
const request = store.get(slot)
request.onsuccess = () => resolve(request.result?.state || null)
request.onerror = () => reject(request.error)
})
}5) सभी सेव की सूची बनाना
async function listSaves() {
const db = await openGameDB()
return new Promise((resolve, reject) => {
const tx = db.transaction('saves', 'readonly')
const store = tx.objectStore('saves')
const request = store.getAll()
request.onsuccess = () => resolve(request.result)
request.onerror = () => reject(request.error)
})
}6) एक सेव डिलीट करना
async function deleteSave(slot) {
const db = await openGameDB()
return new Promise((resolve, reject) => {
const tx = db.transaction('saves', 'readwrite')
const store = tx.objectStore('saves')
const request = store.delete(slot)
request.onsuccess = () => resolve()
request.onerror = () => reject(request.error)
})
}7) एक पूरी GameStorage class
class GameStorage {
constructor(dbName = 'GameData', version = 1) {
this.dbName = dbName
this.version = version
this.db = null
}
async init() {
this.db = await this.openDB()
}
openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version)
request.onerror = () => reject(request.error)
request.onsuccess = () => resolve(request.result)
request.onupgradeneeded = (e) => {
const db = e.target.result
if (!db.objectStoreNames.contains('saves')) {
db.createObjectStore('saves', { keyPath: 'slot' })
}
if (!db.objectStoreNames.contains('settings')) {
db.createObjectStore('settings', { keyPath: 'key' })
}
if (!db.objectStoreNames.contains('assets')) {
db.createObjectStore('assets', { keyPath: 'url' })
}
}
})
}
async save(slot, data) {
const tx = this.db.transaction('saves', 'readwrite')
tx.objectStore('saves').put({ slot, data, timestamp: Date.now() })
return tx.complete
}
async load(slot) {
const tx = this.db.transaction('saves', 'readonly')
const result = await this.promisify(tx.objectStore('saves').get(slot))
return result?.data || null
}
async setSetting(key, value) {
const tx = this.db.transaction('settings', 'readwrite')
tx.objectStore('settings').put({ key, value })
}
async getSetting(key, defaultValue = null) {
const tx = this.db.transaction('settings', 'readonly')
const result = await this.promisify(tx.objectStore('settings').get(key))
return result?.value ?? defaultValue
}
promisify(request) {
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result)
request.onerror = () => reject(request.error)
})
}
}8) बाइनरी डेटा स्टोर करना (textures, audio)
IndexedDB Blobs और ArrayBuffers को संभालता है:
async function cacheAsset(url, blob) {
const tx = db.transaction('assets', 'readwrite')
tx.objectStore('assets').put({ url, blob, cached: Date.now() })
}
async function getCachedAsset(url) {
const tx = db.transaction('assets', 'readonly')
const result = await promisify(tx.objectStore('assets').get(url))
return result?.blob || null
}9) Autosave पैटर्न
class AutoSave {
constructor(storage, interval = 60000) {
this.storage = storage
this.interval = interval
this.timer = null
this.dirty = false
}
markDirty() {
this.dirty = true
}
start(getState) {
this.timer = setInterval(async () => {
if (this.dirty) {
await this.storage.save('autosave', getState())
this.dirty = false
console.log('Autosaved')
}
}, this.interval)
}
stop() {
clearInterval(this.timer)
}
}10) एरर हैंडलिंग और fallbacks
async function safeLoad(slot, defaultState) {
try {
const saved = await loadGame(slot)
if (saved) {
// Validate/migrate old saves if needed
return migrateSave(saved)
}
} catch (err) {
console.warn('Failed to load save:', err)
}
return defaultState
}
function migrateSave(save) {
// Handle old save formats
if (!save.version) {
save.version = 1
save.settings = save.settings || {}
}
return save
}11) सेव को eviction से बचाएं
IndexedDB का बना रहना तय नहीं है. डिफ़ॉल्ट रूप से एक origin "best-effort" स्टोरेज इस्तेमाल करता है, और browser इसे तब हटा सकता है जब डिस्क भर जाए या, Safari/WebKit पर, जब काफी समय तक आपकी साइट के साथ कोई यूज़र इंटरैक्शन न हो. game सेव के लिए यह ठीक वही डेटा है जिसे आप खोना नहीं चाहते.
पर्सिस्टेंट स्टोरेज का अनुरोध करें ताकि browser किसी स्पष्ट यूज़र एक्शन के बिना आपका डेटा न मिटाए:
async function makeStoragePersistent() {
if (navigator.storage && navigator.storage.persist) {
const persisted = await navigator.storage.persist()
console.log(persisted ? 'Saves are protected from eviction' : 'Saves may be evicted under storage pressure')
return persisted
}
return false
}browsers यह तय करते हैं कि इसे देना है या नहीं, और यह फैसला engagement संकेतों पर आधारित होता है (यूज़र आपकी साइट के साथ कितना इंटरैक्ट करता है, क्या यह PWA के रूप में इंस्टॉल है), इसलिए यह मत मानिए कि यह हमेशा सफल होगा. बड़े सेव या cached assets लिखने से पहले आप यह भी जाँच सकते हैं कि आपके पास कितनी जगह बची है:
async function checkStorage() {
if (navigator.storage && navigator.storage.estimate) {
const { usage, quota } = await navigator.storage.estimate()
console.log(`Using ${usage} of ${quota} bytes`)
}
}दोनों APIs को एक secure context चाहिए (HTTPS या localhost).
संबंधित
- ऑफलाइन games के लिए PWA
- game caching के लिए service workers
- एक web game शिप करें जो तेज़ी से लोड हो
- स्ट्रीमिंग asset लोडिंग — IndexedDB को asset cache के रूप में इस्तेमाल करना
- web games के लिए Analytics — प्लेयर व्यवहार समझने के लिए सेव/लोड पैटर्न को ट्रैक करना
बाहरी संसाधन
- MDN: IndexedDB API — पूरा API रेफरेंस
- MDN: Using IndexedDB — स्टेप-बाय-स्टेप गाइड
- idb library — एक छोटा Promise-आधारित IndexedDB wrapper