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

Stefan Zager
Stefan Zager
Chris Harrelson
Chris Harrelson

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

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

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

प्री-एनजी रेंडर हो रहा है

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

यहां कुछ अहम प्रोजेक्ट दिए गए हैं जिन्होंने पेंट करने से पहले, रेंडरिंग फ़ेज़ में आर्किटेक्चर से जुड़ी कमियों को दूर किया.

Project Squad: स्टाइल के चरण को पूरा करना

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

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

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

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

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

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

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

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

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

प्रॉपर्टी ट्री: एक जैसी ज्यामिति

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

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

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

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

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

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

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

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

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

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

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

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

फ़ायदे

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

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

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

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

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

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

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

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

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

आने वाले समय में: ऑफ़-मेन-थ्रेड कंपोज़िटिंग ... और उससे आगे!

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

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

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

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

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

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

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