विस्तृत लिंक में thet संदर्भित Unity3D coroutines मृत है। चूंकि यह टिप्पणियों और उत्तर में वर्णित है, इसलिए मैं यहां लेख की सामग्री पोस्ट करने जा रहा हूं। यह सामग्री इस दर्पण से आती है ।
यूनिटी 3 डी कोरटाइन विस्तार से
खेलों में कई प्रक्रियाएं कई फ्रेम के दौरान होती हैं। आपको पाथफाइंडिंग जैसी 'घनी' प्रक्रियाएं मिली हैं, जो प्रत्येक फ्रेम को कड़ी मेहनत करती हैं, लेकिन कई फ्रेमों में विभाजित हो जाती हैं ताकि फ्रैमरेट पर बहुत अधिक प्रभाव न पड़े। आपको गेमप्ले ट्रिगर्स की तरह 'विरल' प्रक्रियाएं मिली हैं, जो कि ज्यादातर फ्रेम नहीं करते हैं, लेकिन कभी-कभी महत्वपूर्ण काम करने के लिए कहा जाता है। और आप दोनों के बीच मिश्रित प्रक्रियाएं हुई हैं।
जब भी आप एक ऐसी प्रक्रिया का निर्माण कर रहे हैं, जो कई फ़्रेमों में होगी - बिना मल्टीथ्रेडिंग के - आपको काम को तोड़ने के कुछ तरीके खोजने की आवश्यकता है जो कि एक-प्रति-फ्रेम चलाया जा सकता है। केंद्रीय लूप के साथ किसी भी एल्गोरिथ्म के लिए, यह काफी स्पष्ट है: एक ए * पाथफाइंडर, उदाहरण के लिए, इस तरह संरचित किया जा सकता है कि यह अपनी नोड सूचियों को अर्द्ध-स्थायी रूप से बनाए रखता है, खुली सूची में से केवल एक मुट्ठी भर नोड्स को संसाधित करना, बजाय कोशिश करना एक ही बार में सभी काम करना। विलंबता को प्रबंधित करने के लिए कुछ संतुलन होना चाहिए - आखिरकार, यदि आप 60 या 30 फ्रेम प्रति सेकंड पर अपने फ्रैमरेट को लॉक कर रहे हैं, तो आपकी प्रक्रिया में प्रति सेकंड केवल 60 या 30 कदम होंगे, और यह प्रक्रिया सिर्फ लेने का कारण बन सकती है। कुल मिलाकर बहुत लंबा। एक साफ डिजाइन एक स्तर पर काम की सबसे छोटी संभव इकाई की पेशकश कर सकता है - जैसे एकल ए * नोड - और शीर्ष पर परत को समूहीकृत करने के तरीके को एक साथ बड़ा हिस्सा बनाने की प्रक्रिया करें - जैसे एक्स मिलीसेकंड के लिए ए * नोड को संसाधित करते रहें। (कुछ लोग इसे 'टाइमसीलिंग' कहते हैं, हालांकि मैं नहीं)।
फिर भी, इस तरह से काम करने की अनुमति देने का मतलब है कि आपको राज्य को एक फ्रेम से दूसरे में स्थानांतरित करना होगा। यदि आप एक पुनरावृत्त एल्गोरिथ्म को तोड़ रहे हैं, तो आपको पुनरावृत्तियों में साझा किए गए सभी राज्य को संरक्षित करने के लिए मिला है, साथ ही ट्रैकिंग का एक साधन जो आगे चलना है। यह आमतौर पर बहुत बुरा नहीं है - 'ए * पाथफाइंडर क्लास' का डिजाइन काफी स्पष्ट है - लेकिन अन्य मामले भी हैं, जो कम सुखद हैं। कभी-कभी आपको लंबी गणनाओं का सामना करना पड़ेगा जो फ्रेम से फ्रेम तक विभिन्न प्रकार के काम कर रहे हैं; उनके राज्य पर कब्जा करने वाली वस्तु अर्ध-उपयोगी 'स्थानीय लोगों' की एक बड़ी गड़बड़ी के साथ समाप्त हो सकती है, जो एक फ्रेम से अगले तक डेटा पास करने के लिए रखी गई है। और अगर आप एक विरल प्रक्रिया के साथ काम कर रहे हैं, तो आप अक्सर एक छोटी राज्य मशीन को लागू करने के लिए अंत में ट्रैक करते हैं जब काम बिल्कुल किया जाना चाहिए।
क्या यह साफ-सुथरा नहीं होगा, यदि आप इस पूरे राज्य को स्पष्ट रूप से कई फ़्रेमों में ट्रैक करने के बजाय, और सिंक्रनाइज़ेशन और लॉकिंग इत्यादि को मल्टीथ्रेड और प्रबंधित करने के बजाय, आप अपने फ़ंक्शन को कोड के एकल भाग के रूप में लिख सकते हैं, और उन विशेष स्थानों को चिह्नित करें जहां फ़ंक्शन को 'विराम' देना चाहिए और बाद के समय पर ले जाना चाहिए?
एकता - कई अन्य वातावरणों और भाषाओं के साथ - यह कोराटाइन्स के रूप में प्रदान करता है।
वो कैसे दिखते हैं? "यूनीस्क्रिप्ट" (जावास्क्रिप्ट) में:
function LongComputation()
{
while(someCondition)
{
/* Do a chunk of work */
// Pause here and carry on next frame
yield;
}
}
C # में:
IEnumerator LongComputation()
{
while(someCondition)
{
/* Do a chunk of work */
// Pause here and carry on next frame
yield return null;
}
}
वो कैसे काम करते है? मुझे जल्दी से कहना है, कि मैं एकता टेक्नोलॉजीज के लिए काम नहीं करते। मैंने एकता स्रोत कोड नहीं देखा है। मैंने कभी यूनिटी के कॉरआउट इंजन की हिम्मत नहीं देखी। हालांकि, अगर उन्होंने इसे इस तरह से लागू किया है, जो मेरे वर्णन के बारे में मौलिक रूप से अलग है, तो मैं काफी आश्चर्यचकित हो जाऊंगा। यदि UT का कोई व्यक्ति इसमें झंकार करना चाहता है और इस बारे में बात करना चाहता है कि यह वास्तव में कैसे काम करता है, तो यह बहुत अच्छा होगा।
बड़े सुराग C # संस्करण में हैं। सबसे पहले, ध्यान दें कि फ़ंक्शन के लिए वापसी प्रकार IEnumerator है। और दूसरी बात, ध्यान दें कि एक स्टेटमेंट यील्ड रिटर्न है। इसका मतलब यह है कि उपज एक कीवर्ड होना चाहिए, और जैसा कि एकता का C # समर्थन वेनिला C # 3.5 है, यह एक वेनिला C # 3.5 कीवर्ड होना चाहिए। दरअसल, यहाँ यह MSDN में है - 'इट्रेटर ब्लॉक' नामक किसी चीज़ के बारे में बात करना। तो क्या हो रहा है?
सबसे पहले, यह IEnumerator प्रकार है। IEnumerator प्रकार एक अनुक्रम पर एक कर्सर की तरह कार्य करता है, दो महत्वपूर्ण सदस्य प्रदान करता है: वर्तमान, जो आपको एक तत्व देता है जो कर्सर वर्तमान में खत्म हो गया है, और MoveNext (), एक फ़ंक्शन जो अनुक्रम में अगले तत्व पर जाता है। क्योंकि IEnumerator एक इंटरफ़ेस है, यह निर्दिष्ट नहीं करता है कि ये सदस्य कैसे कार्यान्वित किए जाते हैं; MoveNext () बस एक समवर्ती जोड़ सकता है, या यह एक फ़ाइल से नए मूल्य को लोड कर सकता है, या यह इंटरनेट से एक छवि डाउनलोड कर सकता है और इसे हैश कर सकता है और नए हैश को करंट में संग्रहीत कर सकता है ... या यह पहली के लिए एक काम भी कर सकता है। अनुक्रम में तत्व, और दूसरे के लिए पूरी तरह से अलग। यदि आप चाहें तो अनंत अनुक्रम उत्पन्न करने के लिए भी आप इसका उपयोग कर सकते हैं। MoveNext () अनुक्रम में अगले मान की गणना करता है (यदि कोई अधिक मान नहीं है तो गलत लौटता है),
आमतौर पर, यदि आप एक इंटरफ़ेस लागू करना चाहते हैं, तो आपको एक वर्ग लिखना होगा, सदस्यों को लागू करना होगा, और इसी तरह। Iterator ब्लॉक्स बिना किसी परेशानी के IEnumerator लागू करने का एक सुविधाजनक तरीका है - आप बस कुछ नियमों का पालन करते हैं, और IEnumerator कार्यान्वयन कंपाइलर द्वारा स्वचालित रूप से उत्पन्न होता है।
एक पुनरावृत्ति अवरोधक एक नियमित कार्य है जो (a) IEnumerator लौटाता है, और (b) उपज खोजशब्द का उपयोग करता है। तो उपज खोजशब्द वास्तव में क्या करता है? यह घोषणा करता है कि अनुक्रम में अगला मूल्य क्या है - या यह कि अधिक मूल्य नहीं हैं। जिस बिंदु पर कोड एक यील्ड रिटर्न एक्स या यील्ड ब्रेक का सामना करता है वह बिंदु है जिस पर IEnumerator.MoveNext () बंद होना चाहिए; एक यील्ड रिटर्न X, MoveNext () को सही रिटर्न करने का कारण बनता है और Current को वैल्यू X सौंपा जा सकता है, जबकि एक यील्ड ब्रेक के कारण MoveNext () गलत वापस आ जाता है।
अब, यहाँ चाल है। यह मायने नहीं रखता है कि अनुक्रम द्वारा लौटाए गए वास्तविक मूल्य क्या हैं। आप MoveNext () को बार-बार कॉल कर सकते हैं और करंट को अनदेखा कर सकते हैं; अभिकलन अभी भी प्रदर्शन किया जाएगा। हर बार MoveNext () को कहा जाता है, आपका पुनरावृत्ति अवरोधक अगले 'उपज' स्टेटमेंट पर चलता है, चाहे वह वास्तव में कोई भी पैदावार क्यों न हो। तो आप कुछ इस तरह लिख सकते हैं:
IEnumerator TellMeASecret()
{
PlayAnimation("LeanInConspiratorially");
while(playingAnimation)
yield return null;
Say("I stole the cookie from the cookie jar!");
while(speaking)
yield return null;
PlayAnimation("LeanOutRelieved");
while(playingAnimation)
yield return null;
}
और जो आपने वास्तव में लिखा है वह एक इटैलर ब्लॉक है जो अशक्त मानों का एक लंबा अनुक्रम उत्पन्न करता है, लेकिन जो महत्वपूर्ण है वह उन गणनाओं के कार्य के साइड-इफेक्ट्स हैं। आप इस तरह से एक साधारण लूप का उपयोग करके इस कोरआउट को चला सकते हैं:
IEnumerator e = TellMeASecret();
while(e.MoveNext()) { }
या, अधिक उपयोगी रूप से, आप इसे अन्य कार्यों के साथ मिला सकते हैं:
IEnumerator e = TellMeASecret();
while(e.MoveNext())
{
// If they press 'Escape', skip the cutscene
if(Input.GetKeyDown(KeyCode.Escape)) { break; }
}
यह सभी समय में है जैसा कि आपने देखा है, प्रत्येक यील्ड रिटर्न स्टेटमेंट में एक अभिव्यक्ति (जैसे शून्य) प्रदान करनी चाहिए ताकि इटरेटर ब्लॉक के पास वास्तव में IEnumerator.Current को असाइन करने के लिए कुछ हो। नल का एक लंबा अनुक्रम बिल्कुल उपयोगी नहीं है, लेकिन हम दुष्प्रभावों में अधिक रुचि रखते हैं। क्या हम नहीं हैं?
वास्तव में उस अभिव्यक्ति के साथ हम कुछ काम कर सकते हैं। क्या होगा, अगर सिर्फ अशक्त करने और इसे नजरअंदाज करने के बजाय, हमने कुछ ऐसा किया जो संकेत दिया कि जब हमें अधिक काम करने की आवश्यकता होगी? अक्सर हमें अगले फ्रेम पर सीधे ले जाने की आवश्यकता होगी, निश्चित रूप से, लेकिन हमेशा नहीं: ऐसे बहुत से समय होंगे जहां हम एनीमेशन या साउंड बजने के बाद या किसी विशेष राशि के बीतने के बाद ले जाना चाहते हैं। जबकि (playAnimation) उपज वापसी अशक्त; निर्माण थकाऊ हैं, आपको नहीं लगता?
एकता YieldInstruction बेस प्रकार की घोषणा करती है, और कुछ ठोस व्युत्पन्न प्रकार प्रदान करती है जो विशेष प्रकार के प्रतीक्षा का संकेत देती हैं। आपको WaitForSeconds मिल गए हैं, जो निर्धारित समय बीत जाने के बाद कोरटाइन को फिर से शुरू करता है। आपको WaitForEndOfFrame मिला है, जो बाद में एक ही फ्रेम में एक विशेष बिंदु पर कोरआउट को फिर से शुरू करता है। आपको कोराटाइन प्रकार खुद ही मिल गया है, जो कि, जब करौटीन ए पैदावार कर लेता है, तो करौटीन ए तब तक रुकता है, जब तक कि कोरोटीन बी समाप्त नहीं हो जाता।
रनटाइम पॉइंट से यह कैसा दिखता है? जैसा कि मैंने कहा, मैं एकता के लिए काम नहीं करता, इसलिए मैंने उनका कोड कभी नहीं देखा; लेकिन मुझे लगता है कि यह इस तरह एक छोटा सा लग सकता है:
List<IEnumerator> unblockedCoroutines;
List<IEnumerator> shouldRunNextFrame;
List<IEnumerator> shouldRunAtEndOfFrame;
SortedList<float, IEnumerator> shouldRunAfterTimes;
foreach(IEnumerator coroutine in unblockedCoroutines)
{
if(!coroutine.MoveNext())
// This coroutine has finished
continue;
if(!coroutine.Current is YieldInstruction)
{
// This coroutine yielded null, or some other value we don't understand; run it next frame.
shouldRunNextFrame.Add(coroutine);
continue;
}
if(coroutine.Current is WaitForSeconds)
{
WaitForSeconds wait = (WaitForSeconds)coroutine.Current;
shouldRunAfterTimes.Add(Time.time + wait.duration, coroutine);
}
else if(coroutine.Current is WaitForEndOfFrame)
{
shouldRunAtEndOfFrame.Add(coroutine);
}
else /* similar stuff for other YieldInstruction subtypes */
}
unblockedCoroutines = shouldRunNextFrame;
यह कल्पना करना मुश्किल नहीं है कि अन्य मामलों को संभालने के लिए YieldInstruction सबटाइप्स को कैसे जोड़ा जा सकता है - संकेतों के लिए इंजन-स्तर का समर्थन, उदाहरण के लिए, एक WaitForSignal ("सिग्नलनाम") के साथ जोड़ा जा सकता है YieldInstruction इसका समर्थन कर रहे हैं। अधिक YieldInstructions जोड़कर, स्वयं coroutines अधिक अभिव्यंजक बन सकते हैं - यील्ड रिटर्न नया WaitForSignal ("GameOver"), पढ़ने के लिए अच्छा है (! सिग्नल .asFired ("GameOver)") उपज वापसी, यदि आप मुझसे पूछें, तो काफी अलग! तथ्य यह है कि इंजन में इसे करना स्क्रिप्ट में करने से अधिक तेज हो सकता है।
गैर-स्पष्ट प्रभाव के एक जोड़े में इस सब के बारे में उपयोगी चीजें हैं जो लोगों को कभी-कभी याद आती हैं कि मुझे लगा कि मुझे इंगित करना चाहिए।
सबसे पहले, यील्ड रिटर्न सिर्फ एक अभिव्यक्ति है - किसी भी अभिव्यक्ति - और यील्डइंस्ट्रक्शन एक नियमित प्रकार है। इसका मतलब है कि आप इस तरह की चीजें कर सकते हैं:
YieldInstruction y;
if(something)
y = null;
else if(somethingElse)
y = new WaitForEndOfFrame();
else
y = new WaitForSeconds(1.0f);
yield return y;
विशिष्ट लाइनों की उपज नए WaitForSeconds (), यील्ड रिटर्न नए WaitForEndOfFrame (), आदि आम हैं, लेकिन वे वास्तव में अपने आप में विशेष रूप नहीं हैं।
दूसरे, क्योंकि ये कॉरटॉयन्स सिर्फ इटरेटर ब्लॉक होते हैं, आप चाहें तो अपने आप पर इनरेट कर सकते हैं - आपको इसके लिए इंजन की जरूरत नहीं है। मैंने इससे पहले एक coroutine में रुकावट की स्थिति जोड़ने के लिए इसका उपयोग किया है:
IEnumerator DoSomething()
{
/* ... */
}
IEnumerator DoSomethingUnlessInterrupted()
{
IEnumerator e = DoSomething();
bool interrupted = false;
while(!interrupted)
{
e.MoveNext();
yield return e.Current;
interrupted = HasBeenInterrupted();
}
}
तीसरे, तथ्य यह है कि आप अन्य कोरटाइन पर उपज कर सकते हैं आप अपने खुद के YieldInstructions को लागू करने की अनुमति दे सकते हैं, भले ही वे इंजन द्वारा कार्यान्वित नहीं किए गए हों। उदाहरण के लिए:
IEnumerator UntilTrueCoroutine(Func fn)
{
while(!fn()) yield return null;
}
Coroutine UntilTrue(Func fn)
{
return StartCoroutine(UntilTrueCoroutine(fn));
}
IEnumerator SomeTask()
{
/* ... */
yield return UntilTrue(() => _lives < 3);
/* ... */
}
हालांकि, मैं वास्तव में इसकी सिफारिश नहीं करूंगा - एक कॉरआउट शुरू करने की लागत मेरी पसंद के लिए थोड़ी भारी है।
निष्कर्ष मुझे उम्मीद है कि यह थोड़ा स्पष्ट करता है कि वास्तव में क्या हो रहा है जब आप एकता में एक कॉरआउट का उपयोग करते हैं। C # के पुनरावृत्त ब्लॉक एक छोटे से निर्माण हैं, और यहां तक कि अगर आप एकता का उपयोग नहीं कर रहे हैं, तो शायद आप उन्हें उसी तरह से लाभ उठाने के लिए उपयोगी पाएंगे।
IEnumerator
/IEnumerable
(या जेनेरिक समतुल्य) लौटाते हैं और जिसमेंyield
कीवर्ड होता है। पुनरावृत्तियों को देखें।