براؤزر ٹیب بند کرنے سے چار گھنٹے کی پیشرفت ختم نہیں ہونی چاہیے۔ یہ بات بالکل واضح معلوم ہوتی ہے، پھر بھی بہت سے براؤزر گیمز لوکل اسٹوریج (local storage) کو محض ایک ضمنی چیز سمجھتے ہیں۔ ایک کھلاڑی ہائی اسکور حاصل کرتا ہے، اپنی سیٹنگز تبدیل کرتا ہے، اگلے دن واپس آتا ہے، اور اسے کچھ بھی نہیں ملتا۔ اس سے بھی بدتر صورتحال یہ ہے کہ جب وہ کسی پیچ (patch) کے بعد واپس آتا ہے، تو گیم ایرر دے دیتا ہے کیونکہ اس کی مشین پر موجود سیو فائل اب اس کوڈ سے مطابقت نہیں رکھتی جو آپ نے ابھی جاری کیا ہے۔ Phaser 4 میں ایک survivor-style shooter بنانے کا مطلب ہے دشمنوں کی مسلسل لہروں سے نمٹنا، لیکن اصل طویل مدتی خطرہ آپ کے اپنے مستقبل کے اپ ڈیٹس ہیں۔
زیادہ تر ڈویلپرز اپنا پہلا سیو سسٹم ایک آبجیکٹ لے کر، اسے JSON.stringify کے ذریعے گزار کر، اور localStorage میں ڈال کر بناتے ہیں۔ لوڈ کرتے وقت، وہ اسے پارس (parse) کرتے ہیں اور خام شکل میں گیم کو واپس دے دیتے ہیں۔ یہ پہلے دن تو کام کرتا ہے۔ لیکن جیسے ہی آپ کوئی نئی سیٹنگ، کوئی نیا ان لاک فلیگ، یا نیسٹڈ کنفیگریشن (nested configuration) کی تیسری تہہ شامل کرتے ہیں، یہ ٹوٹ جاتا ہے۔ اگر واپس آنے والے کھلاڑی کے پاس ایک پرانی سیو فائل ہے جس میں vignette پراپرٹی موجود نہیں ہے، اور آپ کا نیا کوڈ اس کے موجود ہونے کی توقع رکھتا ہے، تو آپ کو وہاں undefined ملے گا جہاں آپ کو ایک boolean کی توقع تھی۔ اگر آپ اس کا اطلاق ایک درجن نئے فیچرز پر کریں، تو یہ ڈی بگنگ کا ایک ایسا ڈراونا خواب بن جائے گا جو سب سے پہلے آپ کے وفادار کھلاڑیوں کو متاثر کرے گا۔
ایک معاہدے (Contract) سے آغاز کریں، نہ کہ محض ایک خام آبجیکٹ سے
اس سے پہلے کہ آپ localStorage کو چھوئیں، اپنے کوڈ بیس میں ایک ڈیفالٹ سیو اسکیمہ (default save schema) متعین کریں۔ اسے ایک معاہدے کے طور پر سمجھیں جس کی ہر سیو فائل کو پاسداری کرنی چاہیے، چاہے وہ پانچ منٹ پہلے بنائی گئی ہو یا پانچ ماہ پہلے۔ ایک واضح آغاز کچھ اس طرح ہو سکتا ہے:
const defaultSave = {
highScore: 0,
settings: {
screenShake: true,
vignette: true
}
};
یہ آبجیکٹ آپ کے سورس کوڈ میں رہتا ہے۔ جب گیم شروع ہوتا ہے، تو یہ ڈھانچہ ہمیشہ دستیاب ہوتا ہے۔ یہ آپ کو ایک بیس لائن (baseline) فراہم کرتا ہے۔ یہ آپ کو کسی بھی چیز کو سیریلائز (serialize) کرنے سے پہلے اس کے ڈھانچے کے بارے میں سوچنے پر بھی مجبور کرتا ہے۔ اگر آپ اس مرحلے کو چھوڑ دیتے ہیں اور صرف وہی اسٹیٹ آبجیکٹ اسٹور کرتے ہیں جو اس وقت آسان ہو، تو آپ کو غیر مستقل کیز (inconsistent keys)، غائب فیلڈز، اور خام ناکامیوں (silent failures) کا سامنا کرنا پڑے گا جب پرانی سیوز آپ کی توقعات سے ہٹ جائیں گی۔
Try/Catch کے ساتھ دفاعی لوڈنگ (Defensive Loading)
لوکل اسٹوریج کوئی ڈیٹا بیس نہیں ہے۔ یہ براؤزر میں ایک اسٹرنگ کلوزٹ (string closet) ہے، اور اس میں کچھ بھی جا سکتا ہے۔ صارف نے شاید دستی طور پر کسی ویلیو کو ایڈٹ کیا ہو، کوئی ادھورا رائٹ آپریشن (write operation) منقطع ہو گیا ہو، یا کسی براؤزر ایکسٹینشن نے اس کی (key) میں کچرا ڈال دیا ہو۔ جب آپ اس اسٹرنگ کو واپس نکالتے ہیں اور اسے JSON.parse کو دیتے ہیں، تو ایک بھی خراب کیریکٹر ایک سخت استثنا (exception) پیدا کر سکتا ہے۔ Phaser گیم میں، وہ غیر ہینڈل شدہ ایرر آپ کے بوٹ سیکوئنس کو روک سکتا ہے یا کھلاڑی کو واپس ایک خالی اسکرین پر بھیج سکتا ہے۔
ہمیشہ اپنی ریڈ (read) اور پارس (parse) لاجک کو try/catch بلاک میں لپیٹ کر رکھیں۔ ناکامی کی صورت میں، اپنے ڈیفالٹ اسکیمہ پر واپس چلے جائیں۔ مقصد سادہ ہے: اگر سیو فائل ناقابلِ خواندگی ہے، تو پورے سیشن کو کریش کرنے کے بجائے کھلاڑی کے ساتھ ایک نئے صارف کے طور پر پیش آئیں۔ یہ ایک عادت ہابی پروجیکٹس کو پروڈکشن گریڈ بلڈز سے الگ کرتی ہے۔ اسے نافذ کرنا تقریباً کچھ بھی نہیں لگتا، اور یہ آپ کو ان پراسرار بگ رپورٹس سے بچاتا ہے جنہیں دوبارہ پیدا کرنا ناممکن ہوتا ہے۔
پرانے ڈیٹا کو ڈیفالٹس کے ساتھ مکس (Merge) کریں
ایک کامیاب پارس کا مطلب یہ نہیں ہے کہ آپ محفوظ ہیں۔ اپنے ڈیفالٹ آبجیکٹ کو کبھی بھی مکمل طور پر پارس شدہ نتیجے سے تبدیل نہ کریں۔ اس پرانی سیو فائل میں آپ کی تازہ ترین سیٹنگز موجود نہیں ہو سکتیں۔ ہو سکتا ہے اس میں screenShake ہو لیکن vignette نہ ہو۔ اگر آپ کے گیم لاجک کا یہ فرض ہے کہ vignette موجود ہے کیونکہ یہ تازہ ترین اپ ڈیٹ کے ساتھ آیا ہے، تو آپ دوبارہ undefined ایررز کے پیچھے بھاگنے لگیں گے۔
اس کے بجائے، لوڈ کیے گئے ڈیٹا کو اپنے ڈیفالٹس کے ساتھ مکس کریں۔ بیس لائن اسکیمہ کے اوپر سیو شدہ ویلیوز کی تہیں چڑھانے کے لیے Object.assign کا استعمال کریں۔ ڈیفالٹس خود بخود ہر غائب گیپ کو بھر دیتے ہیں۔ ورژن دو میں آپ نے جو نئی پراپرٹیز شامل کی ہیں، انہیں ڈیفالٹ آبجیکٹ سے ابتدائی ویلیوز مل جائیں گی۔ موجودہ پراپرٹیز جنہیں کھلاڑی نے حقیقت میں تبدیل کیا ہے، وہ ان کی محفوظ شدہ ترجیحات کے ساتھ اوور رائٹ ہو جائیں گی۔ اس طرح سب کا فائدہ ہوتا ہے۔ واپس آنے والا کھلاڑی اپنا ہائی اسکور برقرار رکھتا ہے، اور گیم کو کل کے نئے ٹوگل (toggle) تک رسائی مل جاتی ہے بغیر کسی خرابی کے۔
اس بات کو ذہن میں رکھیں کہ Object.assign ایک شالو مرج (shallow merge) کرتا ہے۔ اگر وقت کے ساتھ آپ کا سیٹنگز آبجیکٹ گہرائی میں نیسٹڈ (deeply nested) ہو جاتا ہے، تو آپ کو ان اندرونی آبجیکٹس کو تھوڑی زیادہ احتیاط سے سنبھالنے کی ضرورت پڑ سکتی ہے۔ پھر بھی، اصول وہی رہتا ہے: کھلاڑی کا ڈیٹا آپ کے ڈیفالٹس کو سجانا چاہیے، انہیں مکمل طور پر تبدیل کرنا نہیں۔
اپنی کیز (Keys) کو ورژن دیں
براؤزرز پرانی لوکل اسٹوریج اینٹریز کو خود بخود ڈیلیٹ نہیں کرتے۔ اگر آپ اپنے ڈیٹا کے ڈھانچے میں بڑی تبدیلی کرتے ہیں، تو آپ کو پرانے فارمیٹ کو چھوڑنے کا ایک صاف ستھرا طریقہ چاہیے۔ اپنی اسٹوریج کی (key) کا نام ورژن سرفکس کے ساتھ رکھیں۔ bitSurvivorsSave_v1 واضح ہے۔ یہ آپ کو بالکل بتاتا ہے کہ کون سا اسکیمہ اس فائل کو لکھ چکا ہے۔ بعد میں، جب آپ پروگریشن کو مکمل طور پر تبدیل کرتے ہیں یا ایک مکمل انوینٹری سسٹم شامل کرتے ہیں، تو bitSurvivorsSave_v2 پر منتقل ہو جائیں۔
یہ آپ کو دو عملی فوائد فراہم کرتا ہے۔ پہلا یہ کہ آپ کبھی بھی غلطی سے v2 لاجک کے ساتھ v1 بلاگ (blob) کو پارس نہیں کریں گے۔ دوسرا یہ کہ اگر آپ چاہیں تو مائیگریشن کوڈ لکھ سکتے ہیں۔ بوٹ کے وقت، v1 کو چیک کریں۔ اگر یہ موجود ہے اور v2 نہیں ہے، تو پرانے ڈیٹا کو نئی ساخت (structure) میں منتقل کریں، اسے نئی کی (key) میں لکھیں، اور آگے بڑھ جائیں۔ اگر آپ مائیگریشن نہیں کرنا چاہتے، تو کم از کم پرانی کی (key) اسٹوریج میں محفوظ رہے گی جبکہ آپ کا نیا کوڈ اسے نظر انداز کر دے گا۔ دونوں صورتوں میں، ورژننگ خاموش کرپشن (silent corruption) کو روکتی ہے۔
سیونگ کو غیر محسوس بنائیں
ڈیٹا کا برقرار رہنا (Persistence) بالکل سانس لینے کی طرح ہونا چاہیے۔ کھلاڑی کو اس کے بارے میں کبھی سوچنے کی ضرورت نہیں پڑنی چاہیے۔ اپنے سیٹنگز مینو میں 'Apply' بٹن نہ ڈالیں۔ 'Apply' بٹن رکاوٹ پیدا کرتے ہیں اور صارفین کو اس بات کی فکر کرنے پر مجبور کرتے ہیں کہ آیا ان کے انتخاب واقعی محفوظ ہوئے یا نہیں۔ یہ ڈیٹا کے ضیاع کا سبب بھی بنتے ہیں جب کوئی کھلاڑی تین آپشنز کو تبدیل کرتا ہے، 'Apply' دبانا بھول جاتا ہے، اور ٹیب بند کر دیتا ہے۔
جیسے ہی کوئی عمل (interaction) ہو، فوراً سیو کر لیں۔ جب کھلاڑی اسکرین شیک کو غیر فعال کرنے کے لیے چیک باکس پر کلک کرے، تو فوراً اپنا رائٹ (write) فنکشن کال کریں۔ جب گیم ختم ہو اور فائنل اسکور جمع ہو جائے، تو گیم اوور اسکرین کی اینیمیشن ختم ہونے سے پہلے نیا ہائی اسکور لکھ دیں۔ ایونٹ ڈرون سیونگ (Event-driven saving) آپ کے آرکیٹیکچر کو قابلِ پیش گوئی رکھتی ہے کیونکہ سیو ہمیشہ اسی ایکشن کے بالکل ساتھ ہوتا ہے جس نے ڈیٹا کو تبدیل کیا ہو۔ آپ کو کبھی بھی کسی سینٹرل بیچنگ فنکشن (central batching function) کو ڈھونڈنے یا پرانی حالت (stale state) کی فکر کرنے کی ضرورت نہیں پڑے گی۔
یہ طریقہ آپ کے ذہنی ماڈل کو بھی سادہ بناتا ہے۔ آپ کو بالکل معلوم ہوگا کہ ڈیٹا کہاں محفوظ ہو رہا ہے: اس کال بیک (callback) میں جو ٹوگل کو سنبھالتا ہے، اور اس فنکشن میں جو موت (death) کو سنبھالتا ہے۔ کوڈ بیس میں کہیں بھی پراسرار رائٹس (mystery writes) نہیں ہوں گے۔
اپنے لیے ایک ری سیٹ بٹن بنائیں
آپ ڈویلپمنٹ کے دوران اپنے ہی سیوز کو خراب کر دیں گے۔ آپ غلط ڈیٹا لکھیں گے، ایج کیسز (edge cases) کا تجربہ کریں گے، اور آپ کو تیزی سے ایک صاف حالت (clean state) پر واپس آنے کی ضرورت ہوگی۔ ڈی بگ مینو یا کسی چھپی ہوئی کی (key) کمبینیشن میں ایک ری سیٹ بٹن بنائیں۔ اس ری سیٹ بٹن سے بالکل اسی ترتیب میں دو کام کروائیں: پہلے اپنی ان میموری اسٹیٹ (in-memory state) کو ڈیفالٹ اسکیمہ (default schema) پر ری سیٹ کریں، پھر فوراً وہی سیو فنکشن کال کریں جو لوکل اسٹوریج میں لکھتا ہے۔
اگر آپ صرف لوکل ویری ایبل کو صاف کرتے ہیں اور رائٹ (write) کا مرحلہ چھوڑ دیتے ہیں، تو آپ نے کچھ حاصل نہیں کیا۔ اگلی بار پیج ریفریش کرنے پر براؤزر پرانے ڈیٹا کو دوبارہ نکال لائے گا اور اسے زندہ کر دے گا۔ ایسا ری سیٹ جو ڈیٹا کو محفوظ کرنا بھول جائے، وہ ایک ایسا بگ ہے جو آپ کا پورا دوپہر ضائع کر سکتا ہے۔ ایک بار ترتیب کو درست کر لیں، اور آپ کا ٹیسٹنگ لوپ پورے پروجیکٹ کے دوران تیز رہے گا۔
اصل حاصلِ کلام
سیونگ کوئی ایسی فیچر نہیں ہے جسے آپ آخر میں جوڑ دیتے ہیں۔ یہ وہ انفراسٹرکچر ہے جو یہ طے کرتا ہے کہ آیا آپ کا گیم پائیدار محسوس ہوتا ہے اور کھلاڑی کے وقت کا احترام کرتا ہے۔ ایک Phaser 4 سروائیور شوٹر (survivor shooter) بار بار کھیلے جانے والے رنز پر زندہ رہتا ہے یا مر جاتا ہے۔ اگر براؤزر ٹیب کھلاڑی کی پیش رفت کے سامنے ایک تیار بندوق کی طرح ہو، تو وہ آخر کار واپس آنا چھوڑ دیں گے۔ ایک اسکیمہ لکھیں، غلط ڈیٹا سے بچاؤ کریں، ری پلیس کرنے کے بجائے مرج (merge) کریں، اپنی کیز (keys) کو ورژن کریں، اور ہر اہم ایونٹ پر سیو کریں۔ آپ کا مستقبل کا خود (future self)، اور ہر کھلاڑی جو آپ کے اگلے اپ ڈیٹ کے بعد واپس آئے گا، آپ کا شکر گزار ہوگا۔
