C ++ में इकाई घटक प्रणाली के बीच जोड़ने पर सलाह


10

इकाई-घटक प्रणाली के बारे में कुछ दस्तावेज पढ़ने के बाद, मैंने अपना कार्यान्वयन करने का निर्णय लिया। अब तक, मेरे पास एक वर्ल्ड क्लास है जिसमें इकाइयाँ और सिस्टम मैनेजर (सिस्टम), एंटिटी क्लास शामिल हैं जिसमें एसटीडी :: मैप और कुछ सिस्टम के रूप में घटक शामिल हैं। मैं दुनिया में एक std :: वेक्टर के रूप में संस्थाओं को पकड़ रहा हूं। अब तक कोई समस्या नहीं। मुझे भ्रमित करने वाली बात यह है कि मैं संस्थाओं की पुनरावृत्ति कर रहा हूं, मेरे पास उस पर एक क्रिस्टल स्पष्ट दिमाग नहीं हो सकता है, इसलिए मैं अभी भी उस हिस्से को लागू नहीं कर सकता हूं। क्या प्रत्येक प्रणाली को उन संस्थाओं की एक स्थानीय सूची रखनी चाहिए, जिनमें वे रुचि रखते हैं? या मैं सिर्फ विश्व स्तर पर संस्थाओं के माध्यम से पुनरावृत्ति करूं और सिस्टम के माध्यम से पुनरावृत्त करने के लिए एक नेस्टेड लूप बनाऊं और यह जांच करूं कि क्या इकाई में ऐसे घटक हैं जिनकी प्रणाली में रुचि है? मेरा मतलब :

for (entity x : listofentities) {
   for (system y : listofsystems) {
       if ((x.componentBitmask & y.bitmask) == y.bitmask)
             y.update(x, deltatime)
       }
 }

लेकिन मुझे लगता है कि एक स्क्रिप्टिंग भाषा को एम्बेड करने के मामले में एक बिटमैस्क सिस्टम लचीलापन खो देगा। या प्रत्येक प्रणाली के लिए स्थानीय सूची होने से कक्षाओं के लिए स्मृति उपयोग बढ़ेगा। मैं बहुत उलझन में हूं।


आप लिपि बाइंडिंग में बाधा डालने के लिए बिटमस्क दृष्टिकोण की अपेक्षा क्यों करते हैं? एक तरफ के रूप में, संस्थाओं और प्रणालियों की नकल से बचने के लिए प्रत्येक लूप में संदर्भ (कास्ट, यदि संभव हो) का उपयोग करें।
बेंजामिन क्लोस्टर

एक इंटम उदाहरण के लिए एक बिटमास्क का उपयोग करते हुए, केवल 32 अलग-अलग घटकों को रखेगा। मैं नहीं लगा रहा हूं कि 32 से अधिक घटक होंगे लेकिन अगर मेरे पास है तो क्या होगा? मुझे एक और int या 64bit int बनाना होगा, यह गतिशील नहीं होगा।
डेनिज

आप std :: बिटसेट या std :: वेक्टर <bool> का उपयोग कर सकते हैं, यह निर्भर करता है कि आप इसे रन-टाइम डायनामिक बनाना चाहते हैं या नहीं।
बेंजामिन क्लोस्टर

जवाबों:


7

प्रत्येक सिस्टम के लिए स्थानीय सूचियाँ होने से कक्षाओं के लिए मेमोरी का उपयोग बढ़ेगा।

यह एक पारंपरिक स्पेस-टाइम ट्रेडऑफ है

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

उस ने कहा, यह दृष्टिकोण अभी भी आपके लक्ष्यों के आधार पर काफी अच्छा हो सकता है।

यद्यपि, यदि आप गति के बारे में चिंतित हैं, तो निश्चित रूप से विचार करने के लिए एक और समाधान है।

क्या प्रत्येक प्रणाली को उन संस्थाओं की एक स्थानीय सूची रखनी चाहिए, जिनमें वे रुचि रखते हैं?

बिल्कुल सही। यह एक मानक दृष्टिकोण है जो आपको सभ्य प्रदर्शन देना चाहिए और इसे लागू करना आसान है। मेमोरी ओवरहेड मेरी राय में लापरवाही है - हम स्टोरिंग पॉइंटर्स के बारे में बात कर रहे हैं।

अब इन "हितों की सूचियों" को कैसे बनाए रखा जाए, यह स्पष्ट नहीं हो सकता है। डेटा कंटेनर के लिए, std::vector<entity*> targetsसिस्टम की कक्षा के अंदर पूरी तरह से पर्याप्त है। अब मैं यह क्या करता हूं:

  • निर्माण पर इकाई खाली है और किसी भी प्रणाली से संबंधित नहीं है।
  • जब भी मैं किसी इकाई में एक घटक जोड़ता हूं:

    • अपने वर्तमान बिट हस्ताक्षर प्राप्त करें ,
    • पर्याप्त घटक आकार (व्यक्तिगत रूप से मैं बढ़ावा :: पूल का उपयोग करता हूं) के विश्व के पूल के मानचित्र घटक का आकार और वहां घटक आवंटित करता हूं
    • इकाई का नया बिट हस्ताक्षर प्राप्त करें (जो कि "वर्तमान बिट हस्ताक्षर" प्लस नया घटक है)
    • विश्व के सभी प्रणालियों के माध्यम से दोहराएं और अगर वहाँ एक प्रणाली है जिनके हस्ताक्षर नहीं करता है इकाई की मौजूदा हस्ताक्षर से मेल और करता है नया हस्ताक्षर से मेल, यह स्पष्ट हो जाता है कि हम वहाँ हमारी संस्था के लिए सूचक push_back चाहिए।

          for(auto sys = owner_world.systems.begin(); sys != owner_world.systems.end(); ++sys)
                  if((*sys)->components_signature.matches(new_signature) && !(*sys)->components_signature.matches(old_signature)) 
                          (*sys)->add(this);

एक इकाई को निकालना पूरी तरह से समान है, केवल इस अंतर के साथ कि हम हटाते हैं यदि कोई सिस्टम हमारे वर्तमान हस्ताक्षर के साथ मेल खाता है (जिसका अर्थ है कि इकाई वहां थी) और नए हस्ताक्षर के साथ मेल नहीं खाती (जिसका अर्थ है कि इकाई अब नहीं होनी चाहिए। )।

अब आप std :: list का उपयोग करने पर विचार कर सकते हैं क्योंकि वेक्टर से हटाना O (n) है, इस बात का उल्लेख नहीं है कि आपको हर बार बीच से हटाने पर डेटा का बड़ा हिस्सा स्थानांतरित करना होगा। वास्तव में, आपके पास नहीं है - क्योंकि हम इस स्तर पर प्रसंस्करण के आदेश के बारे में परवाह नहीं करते हैं, हम सिर्फ std :: निकाल सकते हैं और इस तथ्य के साथ रह सकते हैं कि प्रत्येक विलोपन पर हमें केवल O (n) खोज करनी है निकाले जाने वाली इकाई।

std :: सूची आपको O (1) निकाल देगी लेकिन दूसरी तरफ आपके पास अतिरिक्त मेमोरी ओवरहेड का थोड़ा सा हिस्सा है। यह भी याद रखें कि ज्यादातर समय आप संस्थाओं को संसाधित करेंगे और उन्हें नहीं हटाएंगे - और यह निश्चित रूप से तेजी से किया जाता है: एसटीडी :: वेक्टर।

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


5

एक दृष्टिकोण है जो इस बात पर विचार करने के लायक है कि प्रत्येक प्रणाली खुद से जुड़े घटकों का मालिक है और संस्थाएं केवल उन्हें संदर्भित करती हैं। मूल रूप से, आपका (सरलीकृत) Entityवर्ग इस तरह दिखता है:

class Entity {
  std::map<ComponentType, Component*> components;
};

जब आप किसी RigidBodyघटक को संलग्न करने के लिए कहते हैं Entity, तो आप इसे अपने Physicsसिस्टम से अनुरोध करते हैं। सिस्टम घटक बनाता है और इकाई को एक संकेतक रखने देता है। आपका सिस्टम तब दिखता है:

class PhysicsSystem {
  std::vector<RigidBodyComponent> rigidBodyComponents;
};

अब, यह पहली बार में थोड़ा सहज लग सकता है, लेकिन इसका फायदा यह है कि कंपोनेंट एंटिटी सिस्टम अपने स्टेट को अपडेट करते हैं। अक्सर, आप अपने सिस्टम के माध्यम से पुनरावृति करेंगे और अनुरोध करेंगे कि वे संबंधित घटकों को अपडेट करें

for(auto it = systems.begin(); it != systems.end(); ++it) {
  it->update();
}

सन्निहित स्मृति में सिस्टम के स्वामित्व वाले सभी घटकों को रखने की ताकत यह है कि जब आपका सिस्टम हर घटक से अधिक पुनरावृत्ति करता है और इसे अपडेट करता है, तो यह मूल रूप से केवल करना है

for(auto it = rigidBodyComponents.begin(); it != rigidBodyComponents.end(); ++it) {
  it->update();
}

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

उस बिंदु पर Worldसिस्टम के माध्यम से आपका एकमात्र लूप होता है और updateउन पर कॉल करने के लिए बिना संस्थाओं को पुनरावृत्त किए बिना। यह (imho) बेहतर डिजाइन है क्योंकि तब सिस्टम की जिम्मेदारियां बहुत स्पष्ट होती हैं।

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


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

@deniz यह सब आपके डिजाइन पर निर्भर करता है। यदि आपके घटकों में कोई विधि नहीं है, लेकिन केवल डेटा है, तो सिस्टम अभी भी उन पर पुनरावृति कर सकता है और आवश्यक क्रियाएं कर सकता है। संस्थाओं को वापस लिंक करने के लिए, हाँ, आप घटक में स्वामी इकाई के लिए एक पॉइंटर को स्टोर कर सकते हैं या आपके सिस्टम में घटक हैंडल और संस्थाओं के बीच एक नक्शा बनाए रखा जा सकता है। आमतौर पर यद्यपि, आप चाहते हैं कि आपके घटक यथासंभव स्व-निहित हों। एक घटक जो कि मूल इकाई के बारे में बिल्कुल भी नहीं जानता है वह आदर्श है। यदि आपको उस दिशा में संचार की आवश्यकता है, तो घटनाओं और पसंद को प्राथमिकता दें।
प्यासी

यदि आप कहते हैं कि यह दक्षता के लिए बेहतर होगा, तो मैं आपके पैटर्न का उपयोग करूंगा।
डेनिज़

@deniz सुनिश्चित करें कि आप वास्तव में अपने कोड को जल्दी और अक्सर पहचानते हैं कि क्या काम करता है और आपके विशेष
इंजन के

ठीक है :) मैं थोड़े तनाव परीक्षण करूँगा
थोथा

1

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

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.