ஒரு பிரவுசர் டேப்பை மூடுவது நான்கு மணிநேர முன்னேற்றத்தை அழித்துவிடக்கூடாது. இது மிகவும் எளிமையான விஷயமாகத் தோன்றலாம், ஆனால் பல பிரவுசர் கேம்கள் localStorage-ஐ ஒரு முக்கியமற்ற விஷயமாகவே கருதுகின்றன. ஒரு வீரர் அதிக மதிப்பெண்ணைப் (high score) பெறுகிறார், தனது அமைப்புகளை (settings) மாற்றியமைக்கிறார், மறுநாள் மீண்டும் வருகை தருகிறார், ஆனால் அங்கு எதுவும் இல்லை என்பதைக் காண்கிறார். அதைவிட மோசமாக, ஒரு பேட்ச் (patch) செய்த பிறகு அவர் திரும்ப வரும்போது, அவரது கணினியில் உள்ள சேவ் ஃபைல் (save file) நீங்கள் இப்போது அனுப்பிய கோட்டுடன் பொருந்தாததால் கேம் ஒரு பிழையை (error) காட்டுகிறது. Phaser 4-இல் ஒரு சர்வைவர்-பாணி ஷூட்டரை (survivor-style shooter) உருவாக்குவது என்பது எதிரிகளின் தொடர்ச்சியான அலைகளைக் கையாள்வதாகும், ஆனால் உண்மையான நீண்டகால அச்சுறுத்தல் உங்கள் எதிர்கால அப்டேட்கள் (updates) தான்.

பெரும்பாலான டெவலப்பர்கள் ஒரு ஆப்ஜெக்ட்டை எடுத்து, அதை JSON.stringify மூலம் இயக்கி, localStorage-ல் சேமிப்பதன் மூலம் தங்கள் முதல் சேவ் சிஸ்டத்தை உருவாக்குகிறார்கள். லோட் (load) செய்யும் போது, அதை பார்ஸ் (parse) செய்து கேமிற்கு அப்படியே வழங்குகிறார்கள். இது முதல் நாளன்று வேலை செய்யும். ஆனால் நீங்கள் ஒரு புதிய செட்டிங், ஒரு புதிய அன்லாக் ஃபிளாக் (unlock flag), அல்லது மூன்றாவது அடுக்கு நெஸ்டட் கான்ஃபிகரேஷனை (nested configuration) சேர்க்கும் தருணமே இது உடைந்துவிடும். திரும்ப வரும் ஒரு வீரரிடம் vignette பண்பு (property) இல்லாத பழைய சேவ் ஃபைல் இருந்தால், மற்றும் உங்கள் புதிய கோட் அது இருக்க வேண்டும் என்று எதிர்பார்த்தால், நீங்கள் ஒரு பூலியனுக்கு (boolean) பதிலாக undefined என்பதைப் பெறுவீர்கள். இது போன்ற டஜன் கணக்கான புதிய அம்சங்களை நீங்கள் சேர்க்கும்போது, அது உங்கள் மிகவும் விசுவாசமான வீரர்களை முதலில் பாதிக்கும் ஒரு டீபக்கிங் பயங்கரமாக (debugging nightmare) மாறும்.

ஒரு மூல ஆப்ஜெக்ட்டுடன் (Raw Object) தொடங்காமல், ஒரு ஒப்பந்தத்துடன் (Contract) தொடங்குங்கள்

நீங்கள் localStorage-ஐத் தொடங்குவதற்கு முன்பே, உங்கள் கோட் பேஸில் (codebase) ஒரு இயல்புநிலை சேவ் ஸ்கீமாவை (default save schema) வரையறுக்கவும். இது ஒவ்வொரு சேவ் ஃபைலும் மதிக்க வேண்டிய ஒரு ஒப்பந்தம் (contract) என்று நினைத்துக் கொள்ளுங்கள், அது ஐந்து நிமிடங்களுக்கு முன்பு உருவாக்கப்பட்டதாக இருந்தாலும் அல்லது ஐந்து மாதங்களுக்கு முன்பு உருவாக்கப்பட்டதாக இருந்தாலும் சரி. ஒரு தெளிவான தொடக்கப் புள்ளி இப்படி இருக்கலாம்:

const defaultSave = {
  highScore: 0,
  settings: {
    screenShake: true,
    vignette: true
  }
};

இந்த ஆப்ஜெக்ட் உங்கள் சோர்ஸ் கோடில் (source code) இருக்கும். கேம் தொடங்கும் போது, இந்த வடிவம் எப்போதும் உங்களிடம் இருக்கும். இது உங்களுக்கு ஒரு அடிப்படைத் தரவை (baseline) வழங்குகிறது. மேலும் நீங்கள் எதையும் சீரியலைஸ் (serialize) செய்வதற்கு முன்பே அதன் கட்டமைப்பைப் பற்றி சிந்திக்க இது உங்களைத் தூண்டுகிறது. நீங்கள் இந்தத் தடையைத் தவிர்த்துவிட்டு, அந்த நேரத்தில் வசதியாக இருக்கும் எந்த ஒரு ஸ்டேட் ஆப்ஜெக்ட்டையும் (state object) சேமித்தால், இறுதியில் முரண்பட்ட கீய்கள் (inconsistent keys), விடுபட்ட புலங்கள் (missing fields) மற்றும் பழைய சேவ்கள் உங்கள் எதிர்பார்ப்புகளுடன் ஒத்துப்போகாத போது ஏற்படும் அமைதியான தோல்விகளை (silent failures) நீங்கள் சந்திக்க நேரிடும்.

Try/Catch மூலம் பாதுகாப்பான லோடிங் (Defensive Loading)

Local storage என்பது ஒரு டேட்டாபேஸ் அல்ல. இது பிரவுசரில் உள்ள ஒரு ஸ்டிரிங் அலமாரி (string closet) போன்றது, அதில் எது வேண்டுமானாலும் சேமிக்கப்படலாம். பயனர் ஒரு மதிப்பை கைமுறையாக மாற்றியிருக்கலாம், பாதி எழுதப்பட்ட ஒரு செயல்முறை இடையில் தடைபட்டிருக்கலாம், அல்லது ஒரு பிரவுசர் எக்ஸ்டென்ஷன் நீங்கள் குறிப்பிட்ட கீ-யில் குப்பைகளைத் தள்ளியிருக்கலாம். நீங்கள் அந்த ஸ்டிரிங்கை மீண்டும் எடுத்து JSON.parse-க்கு கொடுக்கும்போது, ஒரு சிதைந்த எழுத்து (corrupted character) கூட ஒரு கடினமான எக்ஸ்பெக்ஷனை (exception) ஏற்படுத்தும். ஒரு Phaser கேமில், கையாளப்படாத அந்த பிழை உங்கள் பூட் சீக்வென்ஸை (boot sequence) முடக்கலாம் அல்லது வீரரை ஒரு வெற்றுத் திரைக்குத் தள்ளலாம்.

எப்போதும் உங்கள் ரீட் (read) மற்றும் பார்ஸ் (parse) லாஜிக்கை ஒரு try/catch பிளாக்கிற்குள் வையுங்கள். தோல்வி ஏற்படும் போது, உங்கள் இயல்புநிலை ஸ்கீமாவிற்கு (default schema) மாறுங்கள். இலக்கு எளிமையானது: சேவ் ஃபைலை படிக்க முடியாவிட்டால், முழு செஷனையும் முடக்குவதற்குப் பதிலாக, அந்த வீரரை ஒரு புதிய பயனராகக் கருதுங்கள். இந்த ஒரு பழக்கம் சாதாரண புராஜெக்ட்களையும், புரொடக்ஷன்-கிரேடு பில்ட்களையும் (production-grade builds) வேறுபடுத்துகிறது. இதைச் செயல்படுத்துவதற்குப் பெரிய செலவு ஏதுமில்லை, மேலும் மீண்டும் உருவாக்க முடியாத மர்மமான பிழை அறிக்கைகளிலிருந்து இது உங்களைக் காப்பாற்றும்.

பழைய தரவை இயல்புநிலைத் தரவுகளுடன் இணைக்கவும் (Merge Old Data with Defaults)

வெற்றிகரமாக பார்ஸ் (parse) செய்வது மட்டுமே நீங்கள் பாதுகாப்பாக இருக்கிறீர்கள் என்று அர்த்தமல்ல. பார்ஸ் செய்யப்பட்ட முடிவைக் கொண்டு உங்கள் இயல்புநிலை ஆப்ஜெக்ட்டை (default object) முழுமையாக மாற்றாதீர்கள். அந்த பழைய சேவ் ஃபைலில் உங்கள் புதிய செட்டிங்ஸ் இல்லாமல் இருக்கலாம். அது screenShake-ஐச் சேமித்திருக்கலாம் ஆனால் vignette-ஐச் சேமித்திருக்காது. உங்கள் கேம் லாஜிக் சமீபத்திய அப்டேட்டுடன் வந்த vignette இருப்பதாகக் கருதினால், நீங்கள் மீண்டும் undefined பிழைகளைத் தேட வேண்டியிருக்கும்.

அதற்குப் பதிலாக, லோட் செய்யப்பட்ட தரவை உங்கள் இயல்புநிலைத் தரவுகளுடன் இணைக்கவும் (merge). அடிப்படை ஸ்கீமாவின் மேல் சேமிக்கப்பட்ட மதிப்புகளை அடுக்கி வைக்க Object.assign-ஐப் பயன்படுத்தவும். இயல்புநிலை மதிப்புகள் விடுபட்ட அனைத்து இடைவெளிகளையும் தானாகவே நிரப்பும். பதிப்பு இரண்டில் நீங்கள் சேர்த்த புதிய பண்புகள் (properties) இயல்புநிலை ஆப்ஜெக்ட்டிலிருந்து அவற்றின் ஆரம்ப மதிப்புகளைப் பெறும். வீரர் உண்மையில் மாற்றிய ஏற்கனவே உள்ள பண்புகள், அவரது சேமிக்கப்பட்ட விருப்பங்களுடன் மாற்றியமைக்கப்படும். இதில் அனைவரும் வெற்றி பெறுகிறார்கள். திரும்ப வரும் வீரர் தனது அதிக மதிப்பெண்ணைத் தக்க வைத்துக் கொள்கிறார், மேலும் கேம் முடங்காமல் நீங்கள் நேற்று சேர்த்த புதிய டாக்லைங்கையும் (toggle) அணுக முடிகிறது.

Object.assign ஒரு ஷாலோ மெர்ஜ் (shallow merge) செய்கிறது என்பதை நினைவில் கொள்ளுங்கள். உங்கள் செட்டிங்ஸ் ஆப்ஜெக்ட் காலப்போக்கில் ஆழமாக நெஸ்டட் (deeply nested) செய்யப்பட்டால், அந்த உள் ஆப்ஜெக்ட்களைச் சற்று கூடுதல் கவனத்துடன் கையாள வேண்டியிருக்கும். இருப்பினும், கொள்கை மாறாது: வீரரின் தரவு உங்கள் இயல்புநிலைத் தரவை அலங்கரிக்க வேண்டுமே தவிர, அதை முழுமையாக மாற்றக்கூடாது.

உங்கள் கீய்களுக்கு பதிப்பு (Version) வழங்கவும்

பிரவுசர்கள் பழைய local storage பதிவுகளைத் தானாகவே நீக்காது. உங்கள் தரவு கட்டமைப்பை நீங்கள் வியத்தகு முறையில் மாற்றினால், பழைய வடிவமைப்பைத் துறக்க ஒரு தெளிவான வழி தேவைப்படும். உங்கள் ஸ்டோரேஜ் கீ-யுடன் ஒரு பதிப்பு எண்ணைச் (version suffix) சேர்க்கவும். bitSurvivorsSave_v1 என்பது தெளிவானது. எந்த ஸ்கீமா அந்த ஃபைலை எழுதியது என்பதை அது உங்களுக்குத் துல்லியமாகச் சொல்லும். பின்னர், நீங்கள் முன்னேற்ற முறையை (progression) மாற்றியமைக்கும்போதோ அல்லது முழுமையான இன்வென்டரி சிஸ்டத்தைச் சேர்க்கும்போதோ, bitSurvivorsSave_v2-க்கு மாறவும்.

இது உங்களுக்கு இரண்டு நடைமுறைப் பயன்களைத் தருகிறது. முதலாவதாக, நீங்கள் தவறுதலாக v1 blob-ஐ v2 logic மூலம் பகுப்பாய்வு செய்ய மாட்டீர்கள். இரண்டாவதாக, நீங்கள் விரும்பினால் migration code எழுதலாம். இயக்கம் தொடங்கும் போது (boot), v1 இருக்கிறதா என்று சரிபார்க்கவும். அது இருந்து, v2 இல்லையென்றால், பழைய தரவை புதிய அமைப்பிற்கு மாற்றி (migrate), அதை புதிய key-இல் எழுதிவிட்டுத் தொடரவும். நீங்கள் மாற்ற விரும்பவில்லை என்றால், உங்கள் புதிய குறியீடு அதைத் தவிர்த்தாலும், பழைய key சேமிப்பகத்தில் (storage) எந்தப் பாதிப்பும் இன்றி இருக்கும். எதுவாக இருந்தாலும், versioning என்பது தரவு மறைமுகமாகச் சிதைவதைத் தடுக்கிறது.

சேமிப்பினைத் தெரியாமலேயே நிகழ்த்துங்கள்

Persistence என்பது சுவாசிப்பதைப் போலத் தெரிய வேண்டும். வீரர் அதைப்பற்றி ஒருபோதும் சிந்திக்க வேண்டியதில்லை. உங்கள் அமைப்புகள் மெனுவில் (settings menu) ஒரு Apply பொத்தானைச் சேர்க்காதீர்கள். Apply பொத்தான்கள் தேவையற்றத் தடைகளை (friction) உருவாக்கி, பயனர்கள் தாங்கள் எடுத்த முடிவுகள் சரியாகச் சேமிக்கப்பட்டதா என்று கவலைப்படத் தூண்டுகின்றன. மேலும், ஒரு வீரர் மூன்று விருப்பங்களை மாற்றிய பிறகு, Apply பொத்தானை அழுத்தாமல் டேப்பை மூடிவிட்டால், அது தரவு இழப்பிற்கும் வழிவகுக்கும்.

ஒரு செயல் (interaction) நடக்கும் தருணத்திலேயே சேமிக்கவும். திரையின் அதிர்வை (screen shake) முடக்க ஒரு வீரர் checkbox-ஐக் கிளிக் செய்தவுடன், உங்கள் write function-ஐ உடனடியாக அழைக்கவும். ஒரு விளையாட்டு முடிந்ததும் இறுதி மதிப்பெண் கணக்கிடப்படும்போது, game over திரை அனிமேஷன் முடிவதற்கு முன்பே புதிய அதிகபட்ச மதிப்பெண்ணை (high score) எழுதிவிடவும். Event-driven saving உங்கள் கட்டமைப்பை (architecture) கணிக்கக்கூடியதாக வைத்திருக்கும், ஏனெனில் தரவை மாற்றிய செயலுக்கு அருகிலேயே சேமிப்பும் இருக்கும். நீங்கள் ஒரு மையப்படுத்தப்பட்ட batching function-ஐத் தேடவோ அல்லது stale state பற்றி கவலைப்படவோ வேண்டியிருக்காது.

இந்த அணுகுமுறை உங்கள் mental model-ஐ எளிதாக்குகிறது. Persistence எங்கு நிகழ்கிறது என்பது உங்களுக்குத் துல்லியமாகத் தெரியும்: toggle-ஐக் கையாளும் callback-இல் மற்றும் மரணத்தைக் கையாளும் function-இல். codebase முழுவதும் எங்கும் புரியாத வகையில் தரவுகள் எழுதப்படாது.

உங்களுக்கென ஒரு Reset பொத்தானை உருவாக்குங்கள்

மேம்படுத்தும் போது (development) நீங்கள் உங்கள் சொந்த சேமிப்புகளைத் (saves) தவறாகச் சிதைக்கக்கூடும். நீங்கள் தவறான தரவுகளை எழுதலாம், edge cases-களைச் சோதிக்கலாம் மற்றும் விரைவாக ஒரு சுத்தமான நிலைக்குத் திரும்ப வேண்டியிருக்கும். ஒரு debug menu அல்லது மறைக்கப்பட்ட key combination-இல் ஒரு reset பொத்தானை உருவாக்குங்கள். அந்த reset பொத்தான் இந்த வரிசையில் இரண்டு விஷயங்களைச் செய்ய வேண்டும்: முதலில் உங்கள் in-memory state-ஐ default schema-விற்கு மாற்றவும், பின்னர் உடனடியாக local storage-இல் எழுதும் அதே save function-ஐ அழைக்கவும்.

நீங்கள் local variable-ஐ மட்டும் நீக்கிவிட்டு, write செய்வதைத் தவிர்த்தால், நீங்கள் எதையும் சாதிக்கவில்லை என்றுதான் அர்த்தம். அடுத்த முறை page refresh செய்யும்போது, பழைய தரவு உலாவியிலிருந்து (browser) மீண்டும் வந்துவிடும். சேமிக்க மறந்துவிடும் ஒரு reset என்பது ஒரு முழு மதிய நேரத்தையும் வீணடிக்கும் ஒரு வகை bug ஆகும். இந்த வரிசையை (sequence) ஒருமுறை சரியாக அமைத்துவிட்டால், உங்கள் திட்டத்தின் மீதமுள்ள காலத்திற்கு உங்கள் testing loop வேகமானதாக இருக்கும்.

முக்கியக் கருத்து

சேமிப்பது என்பது இறுதியில் நீங்கள் இணைக்கும் ஒரு அம்சம் (feature) அல்ல. உங்கள் விளையாட்டு நீடித்திருக்கும் தன்மையுடனும், வீரரின் நேரத்திற்கு மதிப்பளிப்பதாகவும் இருக்கிறதா என்பதைத் தீர்மானிக்கும் ஒரு கட்டமைப்பு (infrastructure) அது. ஒரு Phaser 4 survivor shooter விளையாட்டு மீண்டும் மீண்டும் விளையாடப்படுவதைப் பொறுத்தே உயிர்வாழும். உலாவியின் டேப் (browser tab) வீரரின் முன்னேற்றத்திற்கு எதிராகச் சுடத் தயாராக இருக்கும் துப்பாக்கியைப் போல இருந்தால், அவர்கள் இறுதியில் மீண்டும் வரமாட்டார்கள். ஒரு schema-வை எழுதுங்கள், தவறான தரவுகளுக்கு எதிராகப் பாதுகாக்கவும், மாற்றத்திற்குப் பதிலாக merge செய்யவும், உங்கள் keys-களை version செய்யவும், மற்றும் ஒவ்வொரு முக்கியமான நிகழ்விலும் (event) சேமிக்கவும். உங்கள் எதிர்கால நீங்களும், உங்கள் அடுத்த அப்டேட்டிற்குப் பிறகு மீண்டும் வரும் ஒவ்வொரு வீரரும் உங்களுக்கு நன்றி சொல்வார்கள்.