हम बुनियादी प्रणालियों-घटकों-संस्थाओं के दृष्टिकोण से शुरू करते हैं ।
आइए संयोजन बनाते हैं ( इस लेख से व्युत्पन्न शब्द ) केवल घटकों के प्रकार के बारे में जानकारी से बाहर । यह गतिशील रूप से रनटाइम पर किया जाता है, जैसे हम एक इकाई में एक-एक करके घटकों को जोड़ेंगे / हटाएंगे, लेकिन चलो इसे अधिक सटीक नाम दें क्योंकि यह केवल टाइप जानकारी के बारे में है।
फिर हम उनमें से प्रत्येक के लिए असेंबलिंग निर्दिष्ट करने वाली संस्थाओं का निर्माण करते हैं। एक बार जब हम इकाई बनाते हैं, तो इसका संयोजन अपरिवर्तनीय होता है, जिसका अर्थ है कि हम इसे सीधे जगह में संशोधित नहीं कर सकते हैं, लेकिन फिर भी हम मौजूदा इकाई के हस्ताक्षर को एक स्थानीय प्रति (सामग्री के साथ) प्राप्त कर सकते हैं, इसके लिए उचित परिवर्तन कर सकते हैं और एक नई इकाई बना सकते हैं इसका।
अब मुख्य अवधारणा के लिए: जब भी कोई इकाई बनाई जाती है, तो उसे असेंबलिंग बकेट नामक एक वस्तु को सौंपा जाता है , जिसका अर्थ है कि एक ही हस्ताक्षर की सभी इकाइयाँ एक ही कंटेनर (जैसे std :: वेक्टर) में होंगी।
अब प्रणालियाँ अपनी रुचि के हर बकेट के माध्यम से पुनरावृति करती हैं और अपना काम करती हैं।
इस दृष्टिकोण के कुछ फायदे हैं:
- घटकों को कुछ में संग्रहित किया जाता है (ठीक-ठीक: बाल्टियों की संख्या) सन्निहित मेमोरी चंक्स - इससे मेमोरी मित्रता में सुधार होता है और किसी भी गेम स्टेट को डंप करना आसान होता है
- सिस्टम एक रैखिक तरीके से घटकों को संसाधित करते हैं, जिसका अर्थ है कि कैश सुसंगतता में सुधार - अलविदा शब्दकोष और यादृच्छिक मेमोरी जंप
- एक नई इकाई बनाना उतना ही आसान है जितना कि एक असेंबलिंग को बाल्टी में मैप करना और इसके वेक्टर में आवश्यक घटकों को पीछे धकेलना
- एक इकाई को हटाना std के लिए एक कॉल के रूप में आसान है :: हटाए गए के साथ अंतिम तत्व को स्वैप करने के लिए कदम, क्योंकि इस समय आदेश नहीं देता है
यदि हमारे पास पूरी तरह से अलग-अलग हस्ताक्षरों के साथ बहुत सारी संस्थाएं हैं, तो कैश कोशिनी के प्रकार का लाभ कम हो जाता है, लेकिन मुझे नहीं लगता कि यह अधिकांश अनुप्रयोगों में होगा।
एक बार वैक्टर के पुन: व्यवस्थित होने पर पॉइंटर अमान्य होने की समस्या भी होती है - इसे एक संरचना की तरह पेश करके हल किया जा सकता है:
struct assemblage_bucket {
struct entity_watcher {
assemblage_bucket* owner;
entity_id real_index_in_vector;
};
std::unordered_map<entity_id, std::vector<entity_watcher*>> subscribers;
//...
};
इसलिए जब भी हमारे गेम लॉजिक में किसी कारण से हम एक नई बनाई गई इकाई पर नज़र रखना चाहते हैं, तो बाल्टी के अंदर हम एक Unit_watcher रजिस्टर करते हैं , और एक बार इकाई को std होने के बाद :: हटाने के दौरान स्थानांतरित किया जाता है, हम इसके वॉचर्स को अपडेट करते हैं और अपडेट करते हैं उनके real_index_in_vector
नए मूल्यों के लिए। अधिकांश समय यह प्रत्येक निकाय विलोपन के लिए केवल एक शब्दकोष लुकअप लगाता है।
क्या इस दृष्टिकोण के लिए कोई और नुकसान हैं?
बहुत स्पष्ट होने के बावजूद समाधान का उल्लेख क्यों नहीं किया गया है?
संपादित करें : मैं सवालों के जवाब देने के लिए सवाल का संपादन कर रहा हूं, क्योंकि टिप्पणियां अपर्याप्त हैं।
आप प्लग करने योग्य घटकों की गतिशील प्रकृति को खो देते हैं, जो विशेष रूप से स्थिर वर्ग निर्माण से दूर करने के लिए बनाया गया था।
मैं नही। शायद मैंने इसे स्पष्ट रूप से पर्याप्त नहीं बताया:
auto signature = world.get_signature(entity_id); // this would just return entity_id.bucket_owner->bucket_signature or so
signature.add(foo_component);
signature.remove(bar_component);
world.delete_entity(entity_id); // entity_id would hold information about its bucket owner
world.create_entity(signature); // automatically assigns new entity to an existing or a new bucket
यह केवल मौजूदा इकाई के हस्ताक्षर लेने के रूप में सरल है, इसे संशोधित करना और एक नई इकाई के रूप में फिर से अपलोड करना है। सुगम्य, गतिशील प्रकृति ? बेशक। यहाँ मैं इस बात पर ज़ोर देना चाहता हूँ कि केवल एक "असेंबलिंग" और एक "बकेट" क्लास है। बाल्टी डेटा-चालित हैं और एक इष्टतम मात्रा में रनटाइम पर बनाई गई हैं।
आपको सभी बाल्टियों से गुजरना होगा जिसमें एक वैध लक्ष्य हो सकता है। बाहरी डेटा संरचना के बिना, टकराव का पता लगाना उतना ही मुश्किल हो सकता है।
खैर, यही कारण है कि हमारे पास बाह्य डेटा संरचनाएं हैं । वर्कअराउंड सिस्टम क्लास में एक पुनरावृत्ति शुरू करने के रूप में सरल है जो अगले बकेट में कूदने के लिए पता लगाता है। कूद विशुद्ध रूप से तर्क के लिए पारदर्शी होगा।