रेंडर करने के लिए डीप-डाइव: BlinkNG

स्टीफ़न ज़ेगर
स्टीफ़न ज़ैगर
क्रिस हैरलसन
क्रिस हैरेलसन

ब्लिंक का मतलब है, Chromium के वेब प्लैटफ़ॉर्म को लागू करना. इसमें कंपोज़िटिंग से पहले रेंडरिंग के सभी फ़ेज़ शामिल होते हैं. यही चरण कंपोज़िटर कमिट से मिलते हैं. ब्लिंक रेंडरिंग आर्किटेक्चर के बारे में ज़्यादा जानने के लिए, इस सीरीज़ के पिछले लेख में जाएं.

ब्लिंक ने अपने जीवन की शुरुआत WebKit के साथ की, जो साल 1998 में KHTML का एक फ़ोर्क है. इसमें Chromium का कुछ सबसे पुराना (और सबसे ज़रूरी) कोड है और 2014 तक यह साफ़ तौर पर अपनी उम्र दिखा रहा था. उस साल, हमने BlinkNG नाम के बैनर के तहत कुछ महत्वाकांक्षी प्रोजेक्ट की शुरुआत की, ताकि Blink कोड के संगठन और स्ट्रक्चर से जुड़ी कमियों को दूर किया जा सके. इस लेख में BlinkNG और इससे जुड़े प्रोजेक्ट के बारे में जानकारी दी गई है: हमने ऐसा क्यों किया, उन्हें क्या हासिल हुआ, इनके डिज़ाइन को तैयार करने वाले बुनियादी सिद्धांत, और आने वाले समय में इन्हें बेहतर बनाने के अवसर क्या हैं.

BlinkNG से पहले और बाद की रेंडरिंग पाइपलाइन.

Pre-NG रेंडर किया जा रहा है

Blink में रेंडरिंग पाइपलाइन को सैद्धांतिक रूप से हमेशा फ़ेज़ (स्टाइल, लेआउट, पेंट वगैरह) में बांटा गया. हालांकि, ऐब्स्ट्रैक्शन की रुकावटें कम थीं. मोटे तौर पर, रेंडरिंग से जुड़े डेटा में, लंबे समय तक मौजूद रहने वाले और बदले जा सकने वाले ऑब्जेक्ट होते हैं. इन ऑब्जेक्ट को किसी भी समय बदला जा सकता था और इनमें बदलाव भी किया जा सकता था. साथ ही, लगातार होने वाले रेंडरिंग अपडेट की मदद से, इन्हें बार-बार रीसाइकल और फिर से इस्तेमाल किया जाता था. इन आसान सवालों का भरोसेमंद तरीके से जवाब देना नामुमकिन था, जैसे:

  • क्या स्टाइल, लेआउट या पेंट के आउटपुट को अपडेट करने की ज़रूरत है?
  • इन डेटा को उनकी "फ़ाइनल" वैल्यू कब मिलेगी?
  • इस डेटा में कब बदलाव करना सही होता है?
  • यह ऑब्जेक्ट कब मिटाया जाएगा?

इसके कई उदाहरण हैं. जैसे:

स्टाइल, स्टाइलशीट के आधार पर ComputedStyle जनरेट करेगा, लेकिन ComputedStyle को बदला नहीं जा सकता. कुछ मामलों में, इसे बाद के पाइपलाइन स्टेज से बदला जाएगा.

स्टाइल, LayoutObject का ट्री जनरेट करेगा. इसके बाद, लेआउट उन ऑब्जेक्ट के साइज़ और पोज़िशन की जानकारी के साथ एनोटेट करेगा. कुछ मामलों में, लेआउट ट्री की संरचना को भी बदल देगा. लेआउट के इनपुट और आउटपुट में साफ़ तौर पर कोई अंतर नहीं था.

स्टाइल, ऐसी ऐक्सेसरी डेटा स्ट्रक्चर जनरेट करेगी जिससे कंपोज़िटिंग के प्रोसेस को तय किया जाता है. साथ ही, उन डेटा स्ट्रक्चर में स्टाइल के बाद, हर चरण के हिसाब से बदलाव किए जाते थे.

निचले लेवल पर, रेंडरिंग डेटा टाइप में ज़्यादातर खास तरह के ट्री शामिल होते हैं. उदाहरण के लिए, डीओएम ट्री, स्टाइल ट्री, लेआउट ट्री, पेंट प्रॉपर्टी ट्री. और रेंडरिंग फ़ेज़, बार-बार होने वाले ट्री वॉक के तौर पर लागू किए जाते हैं. आम तौर पर, ट्री वॉक में शामिल होना चाहिए: किसी ट्री नोड को प्रोसेस करते समय, हमें उस नोड पर मौजूद सबट्री के बाहर की किसी भी जानकारी को ऐक्सेस नहीं करना चाहिए. प्री-रेंडरिंग के समय यह कभी सही नहीं था. ट्री वॉक के ज़रिए बार-बार प्रोसेस किए जा रहे नोड के पूर्वजों से मिली जानकारी को ऐक्सेस किया जाता है. इससे सिस्टम बहुत नाज़ुक और गड़बड़ी की संभावना वाला हो गया. पेड़ की जड़ के अलावा कहीं से भी पैदल चलना मुमकिन नहीं था.

आखिर में, पूरे कोड में रेंडरिंग पाइपलाइन में कई ऑन-रैंप मौजूद थे: JavaScript से ट्रिगर किए गए फ़ोर्स किए गए लेआउट, दस्तावेज़ लोड होने के दौरान ट्रिगर किए गए आंशिक अपडेट, इवेंट टारगेटिंग के लिए फ़ोर्स किए गए अपडेट, डिसप्ले सिस्टम से अनुरोध किए गए शेड्यूल किए गए अपडेट, और सिर्फ़ टेस्ट कोड के लिए दिखाए गए खास एपीआई वगैरह. रेंडरिंग पाइपलाइन में कुछ ऐसे पाथ भी थे जो बार-बार होते थे और फिर से साइन इन किए जाते थे. इसका मतलब है कि किसी एक चरण की शुरुआत से लेकर दूसरे के बीच में स्विच करना. इनमें से हर रैंप के लिए, अलग-अलग तरीके अपनाए जाते हैं. कुछ मामलों में, रेंडरिंग का आउटपुट, रेंडरिंग अपडेट के ट्रिगर होने के तरीके पर निर्भर करता है.

हमने क्या बदलाव किए हैं

BlinkNG कई बड़े और छोटे सब-प्रोजेक्ट से मिलकर बना है. इन सब-प्रोजेक्ट का मकसद, पहले बताए गए आर्किटेक्चर से जुड़ी दिक्कतों को दूर करना है. ये प्रोजेक्ट, रेंडरिंग पाइपलाइन को असल पाइपलाइन बनाने के लिए बनाए गए कुछ दिशा-निर्देश वाले सिद्धांत शेयर करते हैं:

  • एंट्री पॉइंट की एक जैसी जगह: हमें हमेशा पाइपलाइन में शुरुआत में ही पहुंचना चाहिए.
  • फ़ंक्शनल स्टेज: हर चरण में अच्छी तरह से तय किए गए इनपुट और आउटपुट होने चाहिए. साथ ही, इसका व्यवहार फ़ंक्शनल होना चाहिए. इसका मतलब है कि यह डिटरमिनिस्टिक और बार-बार काम करने वाला होना चाहिए. इसके अलावा, आउटपुट सिर्फ़ तय किए गए इनपुट के हिसाब से मिलने चाहिए.
  • कॉन्सटेंट इनपुट: स्टेज के चलने के दौरान, किसी भी स्टेज के इनपुट असरदार तरीके से एक जैसे होने चाहिए.
  • बदले न जा सकने वाले आउटपुट: स्टेज खत्म हो जाने के बाद, रेंडरिंग के बचे हुए अपडेट के लिए, इसके आउटपुट में बदलाव नहीं किया जा सकेगा.
  • चेकपॉइंट एक जैसा होना: हर चरण के आखिर में, अब तक बनाए गए रेंडरिंग डेटा की स्थिति हर चरण में एक जैसी होनी चाहिए.
  • काम की डुप्लीकेट कॉपी हटाने की तकनीक: हर चीज़ को सिर्फ़ एक बार कंप्यूट करें.

BlinkNG सब-प्रोजेक्ट की पूरी सूची से इसे पढ़ना मुश्किल हो सकता है, लेकिन इसके कुछ खास नतीजे नीचे दिए गए हैं.

दस्तावेज़ का लाइफ़साइकल

DocumentLifecycle क्लास, रेंडरिंग पाइपलाइन के ज़रिए हमारी प्रोग्रेस को ट्रैक करती है. इससे हमें बुनियादी जांच करने की सुविधा मिलती है, जिससे पहले बताए गए इन्वैरिएंट लागू किए जा सकते हैं. जैसे:

  • अगर हम किसी ComputedStyle प्रॉपर्टी में बदलाव कर रहे हैं, तो दस्तावेज़ की लाइफ़साइकल kInStyleRecalc होनी चाहिए.
  • अगर Documentलाइफ़साइकल स्थिति kStyleClean या उसके बाद की है, तो NeedsStyleRecalc() किसी भी अटैच किए गए नोड के लिए false दिखाना चाहिए.
  • पेंट लाइफ़साइकल फ़ेज़ में डालते समय, लाइफ़साइकल की स्थिति kPrePaintClean होनी चाहिए.

BlinkNG को लागू करने के दौरान, हमने व्यवस्थित तरीके से ऐसे कोड पाथ को हटा दिया है जो इन इन वैरिएंट का उल्लंघन करते हैं. साथ ही, हमने पूरे कोड में कई और दावे किए, ताकि हम वापस न आएं.

अगर आपको कभी भी खरगोश की जगह पर नीचे-लेवल का रेंडरिंग कोड देखने की ज़रूरत पड़ती है, तो आप खुद से पूछें, "मैं यहां कैसे पहुंचा?" जैसा कि पहले बताया गया था, रेंडरिंग पाइपलाइन में कई तरह के पॉइंट शामिल होते हैं. पहले, इसमें बार-बार आने वाले और बार-बार आने वाले कॉल पाथ शामिल थे. साथ ही, इसमें वे जगहें भी शामिल थीं जहां हम शुरू से शुरू करने के बजाय, इंटरमीडिएट चरण में पाइपलाइन पर चले थे. BlinkNG के दौरान, हमने इन कॉल पाथ का विश्लेषण किया और यह पता चला कि उन सभी को दो बुनियादी स्थितियों के लिए फिर से इस्तेमाल किया जा सकता था:

  • रेंडरिंग के सभी डेटा को अपडेट करना ज़रूरी है. उदाहरण के लिए, डिसप्ले के लिए नए पिक्सल जनरेट करते समय या इवेंट टारगेटिंग के लिए हिट की जांच करते समय.
  • हमें किसी क्वेरी के लिए अप-टू-डेट वैल्यू की ज़रूरत होती है. इसका जवाब, रेंडरिंग के पूरे डेटा को अपडेट किए बिना दिया जा सकता है. इसमें ज़्यादातर JavaScript क्वेरी शामिल होती हैं, जैसे कि node.offsetTop.

इन दोनों स्थितियों के हिसाब से, रेंडरिंग पाइपलाइन में अब सिर्फ़ दो पॉइंट होने चाहिए. फिर से रीसेंटर कोड पाथ को हटा दिया गया है या रीफ़ैक्टर कर दिया गया है. इसलिए, किसी इंटरमीडिएट चरण से शुरू होने वाली पाइपलाइन में अब शामिल नहीं किया जा सकता. इससे, इस बात से जुड़ी काफ़ी उलझन खत्म हो गई है कि रेंडरिंग अपडेट कब और कैसे होते हैं. इससे सिस्टम के काम करने के तरीके के बारे में पता लगाना आसान हो जाता है.

पाइपलाइनिंग की स्टाइल, लेआउट, और प्री-पेंट

कुल मिलाकर, पेंट से पहले रेंडरिंग के चरण, इन चीज़ों के लिए ज़िम्मेदार होते हैं:

  • डीओएम नोड के लिए, फ़ाइनल स्टाइल प्रॉपर्टी का हिसाब लगाने के लिए स्टाइल कैस्केड एल्गोरिदम चलाना.
  • दस्तावेज़ के बॉक्स हैरारकी को दिखाने वाला लेआउट ट्री जनरेट किया जा रहा है.
  • सभी बॉक्स के साइज़ और पोज़िशन की जानकारी तय करना.
  • पेंटिंग के लिए, सब-पिक्सल ज्यामिति को पूरे पिक्सल सीमाओं में राउंड या स्नैप करना.
  • कंपोज़िट लेयर की प्रॉपर्टी तय करना, जैसे कि ट्रांसफ़ॉर्मेशन, फ़िल्टर, ओपैसिटी या किसी भी ऐसी चीज़ को तय करना जो जीपीयू ऐक्सेलरेटेड हो सकती है.
  • यह तय किया जा रहा है कि पेंट के पिछले चरण के बाद से लेकर अब तक, कॉन्टेंट में क्या बदलाव हुए हैं और उसे पेंट या फिर से पेंट करने की ज़रूरत है (पेंट अमान्य है).

इस सूची में कोई बदलाव नहीं हुआ है. हालांकि, BlinkNG के साथ काम करने से पहले, इसमें ज़्यादातर काम ज़रूरत के हिसाब से किया जाता था. यह काम रेंडर करने के कई चरणों में किया जाता था. इसमें पहले से मौजूद गड़बड़ियां और डुप्लीकेट फ़ंक्शन मौजूद होते हैं. उदाहरण के लिए, नोड के लिए फ़ाइनल स्टाइल प्रॉपर्टी का हिसाब लगाने के लिए स्टाइल फ़ेज़ हमेशा से मुख्य तौर पर ज़िम्मेदार रहा है. हालांकि, कुछ खास मामले भी थे जहां हम स्टाइल फ़ेज़ पूरा होने तक, फ़ाइनल स्टाइल प्रॉपर्टी की वैल्यू तय नहीं कर पाए. रेंडरिंग की प्रोसेस में ऐसा कोई औपचारिक या लागू करने लायक पॉइंट नहीं था जिसकी वजह से हम पक्के तौर पर कह सकें कि स्टाइल की जानकारी पूरी थी और उसे बदला नहीं जा सकता.

प्री-BlinkNG परेशानी का एक और अच्छा उदाहरण पेंट अमान्य करना है. पहले, पेंट करने तक, रेंडरिंग के सभी चरणों में पेंट अमान्य होने की वजह से, पेंट अमान्य होने की आशंका बनी रहती थी. स्टाइल या लेआउट कोड में बदलाव करते समय, यह जानना मुश्किल था कि अमान्य वैल्यू को पेंट करने के लिए किन बदलावों की ज़रूरत है. साथ ही, ऐसी गलती करना भी आसान था जिससे गड़बड़ियां कम या ज़्यादा अमान्य हो जाती हैं. LayoutNG के बारे में बनाए गए इस सीरीज़ के लेख में, पेंट अमान्य होने के पुराने सिस्टम की बारीकियों के बारे में ज़्यादा जाना जा सकता है.

पेंटिंग के लिए सब-पिक्सल लेआउट ज्यामिति को पूरे पिक्सल सीमाओं में स्नैप करना एक ऐसा उदाहरण है जहां हमने एक ही फ़ंक्शन को कई बार लागू किया है और बहुत से गैर-ज़रूरी काम किए हैं. पेंट सिस्टम एक पिक्सल-स्नैपिंग कोड पाथ का इस्तेमाल करता था. साथ ही, पेंट कोड के बाहर पिक्सल-स्नैप किए गए निर्देशांकों की कैलकुलेशन करने के लिए, ज़रूरत पड़ने पर एक पूरी तरह से अलग कोड पाथ का इस्तेमाल किया जाता था. यह बताने की ज़रूरत नहीं है कि हर बार लागू करने पर उसकी अपनी गड़बड़ियां थीं और उनके नतीजे हमेशा मेल नहीं खाते थे. इस जानकारी को कैश मेमोरी में सेव नहीं किया जा सकता था. इसलिए, सिस्टम कभी-कभी एक ही तरह का कैलकुलेशन बार-बार करता था—यह परफ़ॉर्मेंस पर एक और दबाव पड़ता है.

यहां कुछ अहम प्रोजेक्ट की जानकारी दी गई है. इनकी मदद से, पेंट करने से पहले रेंडरिंग के चरणों में आने वाली, वास्तुकला में होने वाली कमियों को दूर किया जा सकता है.

प्रोजेक्ट स्क्वाड: स्टाइल फ़ेज़ को पाइपलाइन करना

इस प्रोजेक्ट ने स्टाइल फ़ेज़ में दो मुख्य कमियों को दूर किया, जिसकी वजह से इसे पाइपलाइन नहीं कर पा रहे थे:

स्टाइल फ़ेज़ के दो मुख्य आउटपुट होते हैं: ComputedStyle, जिसमें डीओएम ट्री पर सीएसएस कैस्केड एल्गोरिदम को चलाने का नतीजा शामिल होता है; और LayoutObjects का ट्री, जो लेआउट फ़ेज़ के लिए कार्रवाइयों का क्रम तय करता है. सैद्धान्तिक तौर पर, लेआउट ट्री जनरेट करने से पहले कैस्केड एल्गोरिदम चलाना पूरी तरह से होना चाहिए. हालांकि, पहले इन दो कार्रवाइयों को इंटरलीव किया गया था. Project Squad इन दोनों को अलग-अलग क्रम में, अलग-अलग चरणों में बांट सका.

इससे पहले, स्टाइल को कैलकुलेट करने के दौरान, ComputedStyle को हमेशा फ़ाइनल वैल्यू नहीं मिलती थी. इसलिए, कुछ ऐसी स्थितियां थीं जिनमें ComputedStyle को बाद के पाइपलाइन चरण में अपडेट किया गया था. Project Squad ने इन कोड पाथ को फिर से रीफ़ैक्ट किया है, ताकि स्टाइल फ़ेज़ के बाद ComputedStyle में कभी बदलाव न हो.

LayoutNG: लेआउट फ़ेज़ को पाइपलाइन करना

यह मोनूमेंटल प्रोजेक्ट— रेंडरिंगNG के बहुत अहम पहलुओं में से एक है. यह लेआउट रेंडरिंग के चरण को पूरी तरह से दोबारा बनाने का काम था. हम यहां पूरे प्रोजेक्ट के साथ ठीक से काम नहीं करेंगे, लेकिन पूरे BlinkNG प्रोजेक्ट में कुछ अहम पहलू हैं:

  • इससे पहले, लेआउट फ़ेज़ में LayoutObject का एक ट्री मिला, जिसे स्टाइल फ़ेज़ के हिसाब से बनाया गया था. साथ ही, इस ट्री के साइज़ और पोज़िशन की जानकारी के साथ एनोटेट किया गया था. इस वजह से, आउटपुट से इनपुट को अलग नहीं किया जा सका. LayoutNG ने फ़्रैगमेंट ट्री लॉन्च किया है. यह लेआउट का प्राइमरी और रीड-ओनली आउटपुट है. साथ ही, यह रेंडरिंग के बाद के चरणों के लिए, प्राइमरी इनपुट के तौर पर काम करता है.
  • LayoutNG ने कंटेनमेंट प्रॉपर्टी को लेआउट में लाया: किसी दिए गए LayoutObject के साइज़ और पोज़िशन का पता लगाते समय, हम उस ऑब्जेक्ट पर रूट की गई सबट्री से बाहर नहीं देखते. किसी ऑब्जेक्ट के लिए लेआउट को अपडेट करने के लिए, ज़रूरी सारी जानकारी पहले से ही कैलकुलेट की जाती है. साथ ही, एल्गोरिदम को रीड-ओनली इनपुट के तौर पर दी जाती है.
  • पहले, ऐसे कई मामले थे जिनमें लेआउट एल्गोरिदम पूरी तरह से काम नहीं करता था: एल्गोरिदम का नतीजा, लेआउट के हाल ही के अपडेट पर निर्भर करता था. LayoutNG ने उन केस को खत्म कर दिया है.

प्री-पेंट फ़ेज़

पहले, प्री-पेंट रेंडरिंग का कोई औपचारिक चरण नहीं होता था. यह सिर्फ़ लेआउट के बाद की जाने वाली कार्रवाइयों को समझने की एक क्षमता होती थी. प्री-पेंट फ़ेज़ में लोगों को यह पता चला कि उनमें कुछ संबंधित फ़ंक्शन थे, जिन्हें लेआउट पूरा होने के बाद, लेआउट ट्री के व्यवस्थित ट्रैवर्सल के रूप में सबसे अच्छे से लागू किया जा सकता था. सबसे अहम बात यह है कि:

  • पेंट अमान्य होने का अनुरोध: जब हमारे पास पूरी जानकारी नहीं होती, तो लेआउट के दौरान, अमान्य पेंट करने की प्रक्रिया को सही तरीके से करने में बहुत मुश्किल होती है. कॉन्टेंट को बेहतर बनाना ज़्यादा आसान है. अगर इसे दो अलग-अलग प्रोसेस में बांटा जाए, तो कॉन्टेंट को ज़्यादा आसानी से मैनेज किया जा सकता है: स्टाइल और लेआउट के दौरान, कॉन्टेंट को एक सामान्य बूलियन फ़्लैग से मार्क किया जा सकता है. जैसे, "संभावित तौर पर पेंट अमान्य होने की ज़रूरत है". प्री-पेंट ट्री वॉक के दौरान, हम इन फ़्लैग की जांच करते हैं और ज़रूरत के हिसाब से अमान्य होने की जानकारी देते हैं.
  • पेंट प्रॉपर्टी ट्री जनरेट करना: इस प्रक्रिया के बारे में ज़्यादा जानकारी दी गई है.
  • पिक्सल-स्नैप किए गए पेंट की जगहों की गणना करना और उन्हें रिकॉर्ड करना: रिकॉर्ड किए गए नतीजों का इस्तेमाल, पेंट फ़ेज़ में किया जा सकता है. साथ ही, किसी भी डाउनस्ट्रीम कोड के लिए, डेटा को बिना किसी ज़रूरी कंप्यूटेशन के इस्तेमाल किया जा सकता है.

प्रॉपर्टी ट्री: एक ही स्टाइल में ज्यामिति

स्क्रोलिंग की मुश्किलों को हल करने के लिए, प्रॉपर्टी ट्री को RenderingNG में पहले बनाया गया था. स्क्रोल करने की मुश्किलों को कम करने के लिए, वेब पर इस पेड़ का स्ट्रक्चर, दूसरे सभी तरह के विज़ुअल इफ़ेक्ट से अलग होता है. प्रॉपर्टी ट्री से पहले, Chromium के कंपोज़िटर में कंपोज़िट किए गए कॉन्टेंट के ज्यामितीय संबंध को दिखाने के लिए, एक "लेयर" हैरारकी का इस्तेमाल किया जाता था. हालांकि, पोज़िशन:फ़िक्स जैसी सुविधाओं की जटिलताओं से साफ़ तौर पर यह जानकारी हटा दी गई. लेयर के लेआउट में अतिरिक्त गैर-लोकल पॉइंटर की संख्या बढ़ गई है. इससे, किसी लेयर के "स्क्रोल पैरंट" या "क्लिप पैरंट" की जानकारी मिलती है. इससे पहले, कोड को समझना बहुत मुश्किल था.

प्रॉपर्टी ट्री ने कॉन्टेंट के ओवरफ़्लो स्क्रोल और क्लिप के पहलुओं को दूसरे सभी विज़ुअल इफ़ेक्ट से अलग दिखाकर इसे ठीक किया है. इससे वेबसाइटों के विज़ुअल और स्क्रोलिंग स्ट्रक्चर को सही तरीके से मॉडल करने में मदद मिली. इसके बाद, हमें "सभी" को प्रॉपर्टी ट्री के ऊपर एल्गोरिदम लागू करना था. जैसे, कंपोज़िट की गई लेयर का स्क्रीन-स्पेस ट्रांसफ़ॉर्मेशन या यह तय करना कि कौनसी लेयर स्क्रोल की गई और कौनसी नहीं.

असल में, हमें जल्द ही पता चला कि कोड में ऐसी कई अन्य जगहें भी थीं जहां मिलते-जुलते ज्यामितीय सवाल पूछे गए थे. (मुख्य डेटा स्ट्रक्चर वाली पोस्ट में पूरी सूची दी गई है.) उनमें से कई के लिए, कंपोज़िटर कोड की तरह ही डुप्लीकेट तरीकों का इस्तेमाल किया गया था. सभी में गड़बड़ियों का अलग-अलग सबसेट था. साथ ही, इनमें से किसी में भी वेबसाइट के स्ट्रक्चर को सही तरीके से मॉडल नहीं किया गया था. इसके बाद, समस्या का हल साफ़ तौर पर सामने आया: सभी ज्यामिति एल्गोरिदम को एक ही जगह पर रखें और उनका इस्तेमाल करने के लिए, सभी कोड की रीफ़ैक्टर करें.

ये सभी एल्गोरिदम, प्रॉपर्टी ट्री पर निर्भर करते हैं. इसलिए, प्रॉपर्टी ट्री एक मुख्य डेटा स्ट्रक्चर है. इसका मतलब है कि इसका इस्तेमाल अच्छा परफ़ॉर्म करने के लिए, पूरी पाइपलाइन में किया जाता है. इसलिए, सेंट्रलाइज़्ड ज्यामिति कोड के इस लक्ष्य को हासिल करने के लिए, हमें प्रॉपर्टी ट्री के सिद्धांत को पहले से ही पाइपलाइन में पेश करना था, प्री-पेंट करना था. साथ ही, उन सभी एपीआई को बदलना था जो अब उन पर निर्भर हैं और जिन्हें लागू करने से पहले, प्री-पेंट चलाने की ज़रूरत होती है.

यह कहानी, BlinkNG रीफ़ैक्टिंग पैटर्न का एक और पहलू है: मुख्य कंप्यूटेशन को पहचानें, उनकी डुप्लीकेट कॉपी बनाने से बचने के लिए रीफ़ैक्टरिंग की पहचान करें, और उन्हें फ़ीड करने वाले डेटा स्ट्रक्चर बनाने के लिए अच्छी तरह से तय किए गए पाइपलाइन स्टेज बनाएं. हम प्रॉपर्टी ट्री को ठीक उस समय पर कंप्यूट करते हैं जब पूरी ज़रूरी जानकारी उपलब्ध होती है. साथ ही, हम यह पक्का करते हैं कि बाद में रेंडरिंग स्टेज चलने पर प्रॉपर्टी ट्री बदले न जा सकें.

पेंट के बाद कंपोज़िट: पाइपलाइनिंग पेंट और कंपोज़िटिंग

लेयराइज़ेशन यह पता लगाने की प्रोसेस है कि कौनसा DOM कॉन्टेंट अपनी खुद की कंपोज़िट लेयर में जाता है (जो जीपीयू टेक्सचर को दिखाता है). रेंडर करने से पहले, लेयराइज़ेशन पेंट करने से पहले चलता है, बाद में नहीं (मौजूदा पाइपलाइन के लिए यहां देखें–क्रम में बदलाव पर ध्यान दें). हम सबसे पहले यह तय करेंगे कि डीओएम के कौनसे हिस्से, कंपोज़िट की गई लेयर में डाले जाएंगे. इसके बाद, हम उन टेक्सचर के लिए डिसप्ले लिस्ट बनाएंगे. स्वाभाविक रूप से, फ़ैसले कुछ फ़ैक्टर के आधार पर लिए जाते थे. जैसे, कौनसे डीओएम एलिमेंट को ऐनिमेट या स्क्रोल किया जा रहा था या किन एलिमेंट में 3D बदलाव किए गए थे और किन एलिमेंट के ऊपर पेंट किया गया था.

इससे बड़ी समस्याएं हुईं, क्योंकि कोड में सर्कुलर डिपेंडेंसी की कमोबेश ज़रूरत थी, जो रेंडरिंग पाइपलाइन के लिए एक बड़ी समस्या है. आइए एक उदाहरण से इसकी वजह देखते हैं. मान लीजिए कि हमें पेंट को invalidate करना है (इसका मतलब है कि हमें डिसप्ले सूची दोबारा बनानी होगी और उसे फिर से रास्टर करना होगा). अमान्य करने की ज़रूरत डीओएम में किए गए बदलाव या बदली गई स्टाइल या लेआउट की वजह से हो सकती है. लेकिन बेशक, हम सिर्फ़ उन हिस्सों को अमान्य करना चाहेंगे जो असल में बदल गए हैं. इसका मतलब यह पता लगाना था कि कौन-कौनसी कंपोज़िट लेयर पर असर पड़ा है, और फिर उन लेयर की सभी डिसप्ले सूचियों को अमान्य कर दिया गया है.

इसका मतलब है कि अमान्य होने की प्रोसेस, डीओएम, स्टाइल, लेआउट, और लेयराइज़ेशन के पुराने फ़ैसलों पर निर्भर करती है. यह पुराना तरीका, रेंडर किए गए पिछले फ़्रेम का मतलब होता है. लेकिन वर्तमान लेयराइज़ेशन भी उन सभी चीज़ों पर निर्भर करता है. हमारे पास सभी लेयराइज़ेशन डेटा की दो कॉपी नहीं थीं, इसलिए बीते और आने वाले समय के लेयराइज़ेशन फ़ैसलों के बीच अंतर कर पाना मुश्किल था. इसलिए, हमें सर्कुलर रीज़निंग से जुड़े कई कोड मिले. अगर हम बहुत सावधानी बरतते हैं, तो कभी-कभी इसके चलते हमें गलत या गलत कोड, क्रैश या सुरक्षा से जुड़ी समस्याएं आ जाती हैं.

इस स्थिति से निपटने के लिए, हमने शुरुआत में DisableCompositingQueryAsserts ऑब्जेक्ट का सिद्धांत पेश किया था. ज़्यादातर मामलों में, अगर कोड ने लेयराइज़ेशन के पिछले फ़ैसलों पर क्वेरी करने की कोशिश की, तो इससे दावा नहीं किया जा सकेगा. साथ ही, डीबग मोड में होने पर ब्राउज़र क्रैश हो जाएगा. इससे हमें नई गड़बड़ियों से बचने में मदद मिली. साथ ही, हर मामले में जहां कोड को पिछले लेयराइज़ेशन फ़ैसलों की क्वेरी करने की कानूनी तौर पर ज़रूरत होती है, वहां हम एक DisableCompositingQueryAsserts ऑब्जेक्ट देकर उसकी अनुमति देने के लिए कोड डालते हैं.

हमारी योजना थी कि समय के साथ, सभी कॉल साइटों DisableCompositingQueryAssert ऑब्जेक्ट को हटा दिया जाए और फिर कोड को सुरक्षित और सही एलान किया जाए. हालांकि, हमें पता चला कि अगर पेंट करने से पहले लेयराइज़ेशन की प्रक्रिया चल रही है, तो कई कॉल को हटाया नहीं जा सकता. (आखिर में, हम इसे हाल ही में हटा पाए!) यह कंपोज़िट आफ़्टर पेंट प्रोजेक्ट के लिए खोजी गई पहली वजह थी. हमने यह सीखा कि भले ही आपके पास किसी काम को करने के लिए एक अच्छी तरह से तय किया गया पाइपलाइन हो, लेकिन अगर यह पाइपलाइन में गलत जगह पर हो, तो भी आप अटक जाएंगे.

Composite आफ़्टर पेंट प्रोजेक्ट की दूसरी वजह, फ़ंडामेंटल कंपोज़िटिंग बग थी. इस गड़बड़ी के बारे में बताने का एक तरीका यह है कि डीओएम एलिमेंट, वेब पेज के कॉन्टेंट के लिए 1:1 बेहतर या पूरी लेयराइज़ेशन स्कीम के हिसाब से सही नहीं हैं. कंपोज़िटिंग पेंट से पहले की थी, इसलिए यह काफ़ी हद तक डीओएम एलिमेंट पर निर्भर करती थी, न कि डिसप्ले लिस्ट या प्रॉपर्टी ट्री पर. यह काफ़ी हद तक प्रॉपर्टी ट्री की वजह से ही है, जैसा कि प्रॉपर्टी ट्री के साथ होता है. अगर आपने सही पाइपलाइन फ़ेज़ का पता लगा लिया है, उसे सही समय पर चलाया, और सही मुख्य डेटा स्ट्रक्चर उपलब्ध कराए, तो यह समस्या सीधे तौर पर हल हो जाएगी. प्रॉपर्टी ट्री की तरह ही, यह भी एक अच्छा अवसर था कि पेंट का काम पूरा हो जाने के बाद, उसके बाद के सभी पाइपलाइन चरणों में इसका आउटपुट नहीं बदला जा सकेगा.

फ़ायदे

आपने देखा होगा, एक अच्छी तरह से बनाई गई रेंडरिंग पाइपलाइन से लंबे समय में बहुत ज़्यादा फ़ायदे मिलते हैं. ऐसे और भी बहुत से फ़ॉर्मैट हैं जिनकी आपने उम्मीद नहीं की होगी:

  • बहुत बेहतर विश्वसनीयता: यह काफ़ी आसान है. अच्छी तरह से तय और समझ में आने वाले इंटरफ़ेस वाले साफ़ कोड को समझना, लिखना, और टेस्ट करना आसान होता है. इससे यह ज़्यादा भरोसेमंद बनता है. इससे कोड, क्रैश होने की समस्या और बिना इस्तेमाल के बाद होने वाली गड़बड़ियों की संख्या कम करता है. साथ ही, यह कोड को ज़्यादा सुरक्षित और स्थिर बनाता है.
  • बड़े पैमाने पर टेस्ट कवरेज: BlinkNG के दौरान, हमने अपने सुइट में कई बेहतरीन जांच जोड़ी हैं. इसमें इंटरनल सिस्टम की पुष्टि करने के लिए, यूनिट टेस्ट शामिल हैं. रिग्रेशन की ऐसी जांच जो हमें ठीक की गई पुरानी गड़बड़ियों को फिर से पेश करने से रोकती हैं (कई तरह के!); और सार्वजनिक तौर पर एक साथ मैनेज किए जा रहे वेब प्लैटफ़ॉर्म टेस्ट सुइट में कई ऐसी सुविधाएं शामिल हैं जिनका इस्तेमाल सभी ब्राउज़र, वेब स्टैंडर्ड के अनुपालन को मापने के लिए करते हैं.
  • बढ़ाने में आसान: अगर किसी सिस्टम को साफ़ कॉम्पोनेंट में बांटा गया है, तो मौजूदा कॉम्पोनेंट के बारे में जानने के लिए, दूसरे कॉम्पोनेंट को किसी भी लेवल पर समझना ज़रूरी नहीं है. इससे सभी लोगों के लिए रेंडरिंग कोड में वैल्यू जोड़ना आसान हो जाता है. इसके लिए, विशेषज्ञ होने की ज़रूरत नहीं होती. साथ ही, इसकी मदद से पूरे सिस्टम के व्यवहार के बारे में आसानी से पता लगाया जा सकता है.
  • परफ़ॉर्मेंस: स्पैगेटी कोड में लिखे गए एल्गोरिदम को ऑप्टिमाइज़ करना काफ़ी मुश्किल है, लेकिन ऐसी पाइपलाइन के बिना यूनिवर्सल थ्रेडेड स्क्रोलिंग और ऐनिमेशन या साइट आइसोलेशन के लिए प्रोसेस और थ्रेड जैसी बड़ी चीज़ें हासिल करना करीब-करीब नामुमकिन है. समानता के साथ काम करने से, परफ़ॉर्मेंस को बेहतर बनाने में मदद मिल सकती है. हालांकि, यह बेहद मुश्किल भी है.
  • ईल्डिंग और कंटेनमेंट: BlinkNG के ज़रिए कई ऐसी नई सुविधाएं बनाई गई हैं जो नए और नए तरीकों से पाइपलाइन में काम करती हैं. उदाहरण के लिए, अगर हम बजट खत्म होने तक सिर्फ़ रेंडरिंग पाइपलाइन चलाना चाहें, तो क्या होगा? क्या उन सब-ट्री के लिए रेंडरिंग को स्किप करना है जो फ़िलहाल उपयोगकर्ताओं के काम के नहीं हैं? content- visibility सीएसएस प्रॉपर्टी से यह सुविधा चालू हो जाती है. कॉम्पोनेंट की स्टाइल उसके लेआउट पर निर्भर करती है या नहीं? यह कंटेनर क्वेरी है.

केस स्टडी: कंटेनर से जुड़ी क्वेरी

कंटेनर की क्वेरी, आने वाले समय में वेब प्लैटफ़ॉर्म पर उपलब्ध होने वाली सुविधा है. इस सुविधा के बारे में काफ़ी समय से सोचा जा रहा था. कई सालों से, सीएसएस डेवलपर ने इस सुविधा के लिए सबसे ज़्यादा अनुरोध किए हैं. अगर यह बहुत ही शानदार है, तो अब तक मौजूद क्यों नहीं है? इसकी वजह यह है कि कंटेनर क्वेरी लागू करने के लिए, स्टाइल और लेआउट कोड के बीच के संबंध को समझना और उसे कंट्रोल करना बेहद ज़रूरी है. चलिए, इस पर एक नज़र डालते हैं.

कंटेनर क्वेरी, किसी एलिमेंट पर लागू होने वाली स्टाइल को अनुमति देती है, ताकि वह पहले के ऑब्जेक्ट के तय साइज़ पर निर्भर कर सके. लेआउट के दौरान, लेआउट साइज़ का हिसाब लगाया जाता है. इसका मतलब है कि हमें लेआउट के बाद स्टाइल को फिर से कैलकुलेट करना होगा. हालांकि, स्टाइल को लेआउट से पहले कैलकुलेट करना होगा! हम BlinkNG से पहले कंटेनर क्वेरी लागू क्यों नहीं कर सके, यह पूरी वजह है कि चिकन और अंडे वाला विरोधाभास.

हम इस समस्या को कैसे हल कर सकते हैं? क्या यह बैकवर्ड पाइपलाइन डिपेंडेंसी नहीं है, बल्कि यह वही समस्या है जो Composite After Paint जैसे प्रोजेक्ट के ज़रिए हल हो जाती है? इससे भी बुरा यह है कि अगर नई स्टाइल, पूर्वज के आकार को बदल दें, तो क्या होगा? क्या इससे कभी-कभी अनंत लूप नहीं बनता?

आम तौर पर, सर्कुलर डिपेंडेंसी को कंटेन्ड सीएसएस प्रॉपर्टी का इस्तेमाल करके हल किया जा सकता है. इससे किसी एलिमेंट के बाहर रेंडरिंग की जा सकती है, उस एलिमेंट के सबट्री में रेंडरिंग पर निर्भर नहीं रहना चाहिए. इसका मतलब है कि कंटेनर से लागू की गई नई स्टाइल, कंटेनर के साइज़ पर असर नहीं डाल सकती, क्योंकि कंटेनर क्वेरी में कंटेनमेंट की ज़रूरत होती है.

हालांकि, यह काफ़ी नहीं था. इसलिए, सिर्फ़ साइज़ को कंट्रोल करने के बजाय, कमज़ोर कंटेनमेंट को पेश करना ज़रूरी था. ऐसा इसलिए होता है, क्योंकि आम तौर पर यह इच्छा होती है कि कंटेनर क्वेरी कंटेनर, इनलाइन डाइमेंशन के आधार पर सिर्फ़ एक ही दिशा (आम तौर पर ब्लॉक है) में साइज़ बदल सके. इसलिए, इनलाइन-साइज़ कंटेनमेंट का कॉन्सेप्ट जोड़ा गया. हालांकि, जैसा कि उस सेक्शन के बहुत लंबे नोट से देखा जा सकता है, अगर इनलाइन साइज़ को कंटेन किया जा सकता था, तो काफ़ी समय तक यह साफ़ तौर पर नहीं पता होता था.

कंटेनमेंट को ऐब्सट्रैक्ट खास भाषा में बताना एक बात है और इसे सही तरीके से लागू करना दूसरी चीज़ है. याद रखें कि BlinkNG का एक मकसद, ट्री वॉक के लिए कंटेनमेंट के सिद्धांत को लागू करना था, जो रेंडरिंग का मुख्य तर्क है: सबट्री को एक्सप्लोर करते समय, सबट्री के बाहर से कोई जानकारी इकट्ठा करने की ज़रूरत नहीं होनी चाहिए. जैसा कि होता है (यह कोई हादसा नहीं था), यह ज़्यादा साफ़ होता है. साथ ही, अगर रेंडरिंग कोड, कंटेनमेंट के सिद्धांत के हिसाब से काम करता है, तो सीएसएस को कंटेनमेंट लागू करना आसान हो जाता है.

आने वाले समय के लिए: ऑफ़-मेन-थ्रेड कंपोज़िटिंग ... और इसके बाद की सुविधाएं!

यहां दिखाई गई रेंडरिंग पाइपलाइन असल में मौजूदा renderNG को लागू करने से थोड़ा आगे है. यह लेयराइज़ेशन को मुख्य थ्रेड के बंद होने के तौर पर दिखाता है, जबकि फ़िलहाल यह मुख्य थ्रेड पर ही है. हालांकि, इस काम को पूरा होने में कुछ ही समय लगता है, क्योंकि अब कंपोज़िट आफ़्टर पेंट की शिपिंग हो गई है और लेयरिंग पेंट के बाद की जा रही है.

यह समझने के लिए कि यह क्यों ज़रूरी है और यह कहां से लाया जा सकता है, हमें रेंडरिंग इंजन के आर्किटेक्चर को कुछ हद तक बेहतर तरीके से समझना होगा. Chromium की परफ़ॉर्मेंस को बेहतर बनाने में सबसे बड़ी रुकावट यह है कि रेंडरर का मुख्य थ्रेड मुख्य ऐप्लिकेशन लॉजिक (यानी कि स्क्रिप्ट चलाना है) और रेंडरिंग के बल्क, दोनों को हैंडल करता है. इसकी वजह से, मुख्य थ्रेड अक्सर काम से भर जाता है. साथ ही, मुख्य थ्रेड का कंजेशन अक्सर पूरे ब्राउज़र में बॉटलनेक होता है.

अच्छी बात यह है कि ज़रूरी नहीं कि यह ऐसा ही हो! Chromium के आर्किटेक्चर का यह पहलू, KHTML दिनों से पहले का है. उस समय, प्रोग्रामिंग मॉडल में सिंगल-थ्रेड का इस्तेमाल होता था. जब तक मल्टी-कोर प्रोसेसर, उपभोक्ता-ग्रेड के डिवाइसों में आम होने लगे, तब तक सिंगल-थ्रेड के अनुमान को पूरी तरह से Blink (जिसे पहले WebKit कहा जाता था) में ढाला जाने लगा था. हम लंबे समय से, रेंडरिंग इंजन में ज़्यादा थ्रेडिंग की सुविधा लॉन्च करना चाहते थे. हालांकि, पुराने सिस्टम में ऐसा करना नामुमकिन था. NG को रेंडर करने का एक मुख्य मकसद खुद को इस छेद से बाहर निकालना था. इसके अलावा, रेंडरिंग के काम को उसके किसी हिस्से या पूरे थ्रेड (या थ्रेड) में ले जाना भी आसान हो जाता है.

BlinkNG अब पूरा होने वाला है. इसलिए, हम इस क्षेत्र को एक्सप्लोर करना शुरू कर रहे हैं. रेंडरर के थ्रेडिंग मॉडल में बदलाव करने की दिशा में, Non-Block Commit पहला कदम है. कंपोज़िटर कमिट (या सिर्फ़ commit), मुख्य थ्रेड और कंपोज़िटर थ्रेड के बीच का सिंक करने का चरण है. तय की गई प्रोसेस के दौरान, हम मुख्य थ्रेड पर बने रेंडरिंग डेटा की कॉपी बनाते हैं. इसका इस्तेमाल कंपोज़िटर थ्रेड पर चल रहे डाउनस्ट्रीम कंपोज़िटिंग कोड के लिए किया जा सकता है. जब यह सिंक होता है, तो मुख्य थ्रेड का एक्ज़ीक्यूशन बंद हो जाता है. वहीं, कंपोज़िटर थ्रेड पर कॉपी किया जा रहा कोड चलता है. ऐसा यह पक्का करने के लिए किया जाता है कि कंपोज़िटर थ्रेड की ओर से कॉपी करने के दौरान, मुख्य थ्रेड अपने रेंडरिंग डेटा में बदलाव न करे.

नॉन-ब्लॉकिंग कमिट, मुख्य थ्रेड के बंद होने और उसके खत्म होने का इंतज़ार करने की ज़रूरत को खत्म कर देगा. कंपोज़िटर थ्रेड पर एक साथ काम करने के दौरान, मुख्य थ्रेड काम करता रहेगा. नॉन-ब्लॉकिंग कमिट का कुल असर मुख्य थ्रेड पर काम को रेंडर करने में लगने वाले समय को कम करेगा. इससे मुख्य थ्रेड पर कंजेशन कम होगा और परफ़ॉर्मेंस बेहतर होगी. मार्च 2022 को यह ईमेल भेजा गया था. हमने नॉन-ब्लॉकिंग कमिट का एक प्रोटोटाइप तैयार किया हुआ है. हम इस बारे में विस्तार से विश्लेषण करने की तैयारी कर रहे हैं कि इससे परफ़ॉर्मेंस पर क्या असर पड़ेगा.

विंग में, मुख्य थ्रेड की कंपोज़िटिंग से अलग का इस्तेमाल किया जाता है. इसमें, रेंडरिंग इंजन को मुख्य थ्रेड से लेयराइज़ेशन और वर्कर थ्रेड पर ले जाकर, इलस्ट्रेशन से मैच करने के लिए बनाया जाता है. नॉन-ब्लॉकिंग कमिट की तरह ही, यह मुख्य थ्रेड पर होने वाली कंजेशन को कम करेगा. साथ ही, इससे रेंडरिंग का वर्कलोड कम हो जाएगा. कंपोज़िट आफ़्टर पेंट के आर्किटेक्चर में सुधार के बिना, ऐसा प्रोजेक्ट मुमकिन नहीं था.

साथ ही, और भी प्रोजेक्ट पर काम चल रहा है (हम उम्मीद करते हैं कि)! आखिरकार, हमारे पास एक ऐसा प्लैटफ़ॉर्म है जिसकी मदद से रेंडरिंग के काम को फिर से डिस्ट्रिब्यूट किया जा सकता है. हम यह देखने के लिए बहुत उत्साहित हैं कि यह कैसे किया जा सकता है!