C ++ में Entity / Component Systems, मैं किस प्रकार के प्रकारों और निर्माण घटकों की खोज कर सकता हूँ?


37

मैं C ++ में एक इकाई घटक प्रणाली पर काम कर रहा हूं जो मुझे उम्मीद है कि आर्टेमिस की शैली का पालन करने के लिए (http://piemaster.net/2011/07/entity-component-artemis/) उस घटक में ज्यादातर डेटा बैग हैं और यह है सिस्टम में तर्क होते हैं। मैं इस दृष्टिकोण के डेटा-केंद्रित-नेस का लाभ उठाने और कुछ अच्छी सामग्री टूल बनाने की उम्मीद कर रहा हूं।

हालाँकि, मैं जो एक कूबड़ कर रहा हूं, वह यह है कि डेटा फ़ाइल से कुछ पहचानकर्ता स्ट्रिंग या GUID कैसे लें और एक इकाई के लिए घटक का उपयोग करें। जाहिर है मैं सिर्फ एक बड़ा पार्स फ़ंक्शन कर सकता था:

Component* ParseComponentType(const std::string &typeName)
{
    if (typeName == "RenderComponent") {
        return new RenderComponent();
    }

    else if (typeName == "TransformComponent") {
        return new TransformComponent();
    }

    else {
        return NULL:
    }
}

लेकिन यह वास्तव में बदसूरत है। मैं बार-बार घटकों को जोड़ने और संशोधित करने का इरादा रखता हूं, और उम्मीद है कि किसी प्रकार की ScriptedComponentComponent का निर्माण कर सकता हूं, जैसे कि आप प्रोटोटाइप के प्रयोजनों के लिए Lua में एक घटक और प्रणाली को लागू कर सकते हैं। मैं कुछ BaseComponentवर्ग से विरासत में मिली एक कक्षा को लिखने में सक्षम होना चाहूंगा , हो सकता है कि सब कुछ काम करने के लिए मैक्रोज़ के एक जोड़े में टॉस किया जाए, और फिर रनटाइम के समय क्लास उपलब्ध हो।

सी # और जावा में यह बहुत सीधा होगा, क्योंकि आपको कक्षाएं और कंस्ट्रक्टर्स देखने के लिए अच्छा प्रतिबिंब एपीआई मिलेगा। लेकिन, मैं इसे C ++ में कर रहा हूं क्योंकि मैं उस भाषा में अपनी प्रवीणता बढ़ाना चाहता हूं।

तो यह C ++ में कैसे पूरा होता है? मैंने आरटीटीआई को सक्षम करने के बारे में पढ़ा है, लेकिन ऐसा लगता है कि ज्यादातर लोग इस बारे में सावधान हैं, खासकर ऐसी स्थिति में जब मुझे केवल ऑब्जेक्ट प्रकारों के सबसेट की आवश्यकता होती है। यदि एक कस्टम RTTI प्रणाली है जो मुझे वहां चाहिए, तो मैं एक लिखना सीखने के लिए कहां जा सकता हूं?


1
काफी असंबंधित टिप्पणी: यदि आप C ++ में प्रवीणता प्राप्त करना चाहते हैं, तो स्ट्रिंग्स के बारे में C ++ और C का उपयोग न करें। इसके लिए क्षमा करें, लेकिन यह कहना पड़ा।
क्रिस का कहना है कि

मैं आपको सुनता हूं, यह एक खिलौना उदाहरण था और मेरे पास std :: string api याद नहीं है। । । अभी तक!
michael.bartnett

@bearcdp मैंने अपने उत्तर पर एक बड़ा अपडेट पोस्ट किया है। कार्यान्वयन अब अधिक मजबूत और कुशल होना चाहिए।
पॉल मंटा

@PaulManta आपके उत्तर को अपडेट करने के लिए बहुत बहुत धन्यवाद! इससे सीखने के लिए बहुत कम चीजें हैं।
michael.bartnett

जवाबों:


36

एक टिप्पणी:
आर्टेमिस कार्यान्वयन दिलचस्प है। मैं एक समान समाधान के साथ आया, इसके अलावा मैंने अपने घटकों को "गुण" और "व्यवहार" कहा। विभिन्न प्रकार के घटकों के इस दृष्टिकोण ने मेरे लिए बहुत अच्छी तरह से काम किया है।

समाधान के बारे में:
कोड का उपयोग करना आसान है, लेकिन यदि आप C ++ के साथ अनुभवी नहीं हैं तो कार्यान्वयन का पालन करना कठिन हो सकता है। इसलिए...

वांछित इंटरफ़ेस

मैंने जो किया वह सभी घटकों का एक केंद्रीय भंडार है। प्रत्येक घटक प्रकार को एक निश्चित स्ट्रिंग में मैप किया जाता है (जो घटक नाम का प्रतिनिधित्व करता है)। यह है कि आप सिस्टम का उपयोग कैसे करते हैं:

// Every time you write a new component class you have to register it.
// For that you use the `COMPONENT_REGISTER` macro.
class RenderingComponent : public Component
{
    // Bla, bla
};
COMPONENT_REGISTER(RenderingComponent, "RenderingComponent")

int main()
{
    // To then create an instance of a registered component all you have
    // to do is call the `create` function like so...
    Component* comp = component::create("RenderingComponent");

    // I found that if you have a special `create` function that returns a
    // pointer, it's best to have a corresponding `destroy` function
    // instead of using `delete` directly.
    component::destroy(comp);
}

कार्यान्वयन

कार्यान्वयन उतना बुरा नहीं है, लेकिन यह अभी भी बहुत जटिल है; इसके लिए टेम्प्लेट और फंक्शन पॉइंटर्स के कुछ ज्ञान की आवश्यकता होती है।

नोट: जोएस्सेनिग ने टिप्पणियों में कुछ अच्छे अंक बनाए हैं, मुख्य रूप से इस बात पर कि मेरे पिछले क्रियान्वयन ने कोड के अनुकूलन में कितना अच्छा संकलन किया है, इस बारे में कई धारणाएं हैं; मुद्दा हानिकारक नहीं था, इमो, लेकिन इसने मुझे भी बग कर दिया। मैंने यह भी देखा कि पूर्व COMPONENT_REGISTERमैक्रो टेम्पलेट्स के साथ काम नहीं करता था।

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

घटक / component.h

#ifndef COMPONENT_COMPONENT_H
#define COMPONENT_COMPONENT_H

// Standard libraries
#include <string>

// Custom libraries
#include "detail.h"


class Component
{
    // ...
};


namespace component
{
    Component* create(const std::string& name);
    void destroy(const Component* comp);
}

#define COMPONENT_REGISTER(TYPE, NAME)                                        \
    namespace component {                                                     \
    namespace detail {                                                        \
    namespace                                                                 \
    {                                                                         \
        template<class T>                                                     \
        class ComponentRegistration;                                          \
                                                                              \
        template<>                                                            \
        class ComponentRegistration<TYPE>                                     \
        {                                                                     \
            static const ::component::detail::RegistryEntry<TYPE>& reg;       \
        };                                                                    \
                                                                              \
        const ::component::detail::RegistryEntry<TYPE>&                       \
            ComponentRegistration<TYPE>::reg =                                \
                ::component::detail::RegistryEntry<TYPE>::Instance(NAME);     \
    }}}


#endif // COMPONENT_COMPONENT_H

घटक / detail.h

#ifndef COMPONENT_DETAIL_H
#define COMPONENT_DETAIL_H

// Standard libraries
#include <map>
#include <string>
#include <utility>

class Component;

namespace component
{
    namespace detail
    {
        typedef Component* (*CreateComponentFunc)();
        typedef std::map<std::string, CreateComponentFunc> ComponentRegistry;

        inline ComponentRegistry& getComponentRegistry()
        {
            static ComponentRegistry reg;
            return reg;
        }

        template<class T>
        Component* createComponent() {
            return new T;
        }

        template<class T>
        struct RegistryEntry
        {
          public:
            static RegistryEntry<T>& Instance(const std::string& name)
            {
                // Because I use a singleton here, even though `COMPONENT_REGISTER`
                // is expanded in multiple translation units, the constructor
                // will only be executed once. Only this cheap `Instance` function
                // (which most likely gets inlined) is executed multiple times.

                static RegistryEntry<T> inst(name);
                return inst;
            }

          private:
            RegistryEntry(const std::string& name)
            {
                ComponentRegistry& reg = getComponentRegistry();
                CreateComponentFunc func = createComponent<T>;

                std::pair<ComponentRegistry::iterator, bool> ret =
                    reg.insert(ComponentRegistry::value_type(name, func));

                if (ret.second == false) {
                    // This means there already is a component registered to
                    // this name. You should handle this error as you see fit.
                }
            }

            RegistryEntry(const RegistryEntry<T>&) = delete; // C++11 feature
            RegistryEntry& operator=(const RegistryEntry<T>&) = delete;
        };

    } // namespace detail

} // namespace component

#endif // COMPONENT_DETAIL_H

घटक / component.cpp

// Matching header
#include "component.h"

// Standard libraries
#include <string>

// Custom libraries
#include "detail.h"


Component* component::create(const std::string& name)
{
    detail::ComponentRegistry& reg = detail::getComponentRegistry();
    detail::ComponentRegistry::iterator it = reg.find(name);

    if (it == reg.end()) {
        // This happens when there is no component registered to this
        // name. Here I return a null pointer, but you can handle this
        // error differently if it suits you better.
        return nullptr;
    }

    detail::CreateComponentFunc func = it->second;
    return func();
}

void component::destroy(const Component* comp)
{
    delete comp;
}

लुआ के साथ विस्तार

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


धन्यवाद! आप सही हैं, मैं अभी तक पूरी तरह से समझने के लिए C ++ टेम्पलेट्स की काली कला में पर्याप्त धाराप्रवाह नहीं हूं। लेकिन, एक-लाइन मैक्रो वही है जो मैं देख रहा था, और इसके शीर्ष पर मैं इसका उपयोग अधिक गहराई से समझने के लिए शुरू करने के लिए करूंगा।
michael.bartnett

6
मैं मानता हूं कि यह मूल रूप से सही दृष्टिकोण है, लेकिन दो चीजें जो मुझसे चिपकी रहती हैं: 1. क्यों न केवल एक टेम्पर्ड फंक्शन का उपयोग किया जाए और ComponentTypeImpl इंस्टेंसेस बनाने के बजाय फंक्शन पॉइंटर्स का एक नक्शा स्टोर किया जाए जो बाहर निकलने पर लीक हो जाएगा (वास्तव में जब तक कोई समस्या नहीं है आप एक .SO / DLL या हालांकि कुछ बना रहे हैं) 2. तथाकथित "स्थिर आरंभीकरण आदेश फ़िस्को" के कारण घटकवाद वस्तु टूट सकती है। कंपोनेंट्री सुनिश्चित करने के लिए सबसे पहले आपको एक ऐसा फंक्शन बनाना होगा, जो किसी लोकल स्टैटिक वेरिएबल का रेफरेंस देता हो और कॉल करता हो, बजाय इसके कि कंपोनेंटरी का इस्तेमाल सीधे किया जाए।
लुकास

@ लुकास आह, आप उन लोगों के बारे में पूरी तरह से सही हैं। मैंने तदनुसार कोड बदल दिया। मुझे नहीं लगता कि पिछले कोड में कोई लीक थी, हालांकि मैंने इस्तेमाल किया था shared_ptr, लेकिन आपकी सलाह अभी भी अच्छी है।
पॉल मंटा

1
@Paul: ठीक है, लेकिन यह सैद्धांतिक नहीं है, आपको कम से कम यह संभव करना चाहिए कि संभव प्रतीक दृश्यता रिसाव / लिंकर शिकायतों से बचें। इसके अलावा आपकी टिप्पणी "आपको इस त्रुटि को संभालना चाहिए जैसा कि आप फिट देखते हैं" इसके बजाय "यह एक त्रुटि नहीं है" कहना चाहिए।

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

9

ऐसा लगता है कि आप जो चाहते हैं वह एक कारखाना है।

http://en.wikipedia.org/wiki/Factory_method_pattern

आप क्या कर सकते हैं अपने विभिन्न घटकों को कारखाने के साथ पंजीकृत करें कि वे किस नाम से मेल खाते हैं, और फिर आपके पास अपने घटकों को उत्पन्न करने के लिए स्ट्रिंग आइडेंटिफ़ायर का निर्माणकर्ता के हस्ताक्षर का कुछ नक्शा है।


1
तो मुझे अभी भी कुछ अनुभागों की आवश्यकता होगी जो मेरी सभी Componentकक्षाओं के बारे में जानते हों , बुला रहे हों ComponentSubclass::RegisterWithFactory(), है ना? क्या इसे स्थापित करने का एक तरीका यह है कि यह अधिक गतिशील और स्वचालित रूप से हो? मैं जिस वर्कफ़्लो की तलाश कर रहा हूँ, वह है 1. एक वर्ग लिखना, केवल कॉरडिंग हेडिंग और सीपीपी फ़ाइल को देखना 2. री-कंपाइल गेम 3. स्टार्ट एडिटर और नया कंपोनेंट क्लास उपयोग के लिए उपलब्ध है।
michael.bartnett

2
स्वचालित रूप से ऐसा होने का कोई रास्ता नहीं है। आप इसे प्रति स्क्रिप्ट के आधार पर 1 लाइन मैक्रो कॉल तक तोड़ सकते हैं, हालांकि। पॉल का जवाब थोड़ा सा है।
तेतराड

1

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

struct Object {
    virtual ~Object(){}
};

स्थैतिक कारखाना वर्ग निम्नानुसार है:

struct Factory {
    // the template used by the macro
    template<class ObjectType>
    struct RegisterObject {
        // passing a vector of strings allows many id's to map to the same sub-type
        RegisterObject(std::vector<std::string> names){
            for (auto name : names){
                objmap[name] = instantiate<ObjectType>;
            }
        }
    };

    // Factory method for creating objects
    static Object* createObject(const std::string& name){
        auto it = objmap.find(name);
        if (it == objmap.end()){
            return nullptr;
        } else {
            return it->second();
        }
    }

    private:
    // ensures the Factory cannot be instantiated
    Factory() = delete;

    // the map from string id's to instantiator functions
    static std::map<std::string, Object*(*)(void)> objmap;

    // templated sub-type instantiator function
    // requires that the sub-type has a parameter-less constructor
    template<class ObjectType>
    static Object* instantiate(){
        return new ObjectType();
    }
};
// pesky outside-class initialization of static member (grumble grumble)
std::map<std::string, Object*(*)(void)> Factory::objmap;

एक उप-प्रकार के पंजीकरण के लिए मैक्रो Objectइस प्रकार है:

#define RegisterObject(type, ...) \
namespace { \
    ::Factory::RegisterObject<type> register_object_##type({##__VA_ARGS__}); \
}

अब उपयोग निम्नानुसार है:

struct SpecialObject : Object {
    void beSpecial(){}
};
RegisterObject(SpecialObject, "SpecialObject", "Special", "SpecObj");

...

int main(){
    Object* obj1 = Factory::createObject("SpecialObject");
    Object* obj2 = Factory::createObject("SpecObj");
    ...
    if (obj1){
        delete obj1;
    }
    if (obj2){
        delete obj2;
    }
    return 0;
}

कई स्ट्रिंग आईडी की प्रति उप-प्रकार की क्षमता मेरे आवेदन में उपयोगी थी, लेकिन प्रति उप-प्रकार के लिए एक ही आईडी पर प्रतिबंध काफी सीधा होगा।

मुझे आशा है कि यह उपयोगी रहा है!


1

के बंद का निर्माण @TimStraubinger के जवाब है, मैं का उपयोग कर एक कारखाने वर्ग बनाया सी ++ 14 मानकों जो स्टोर कर सकते हैं युक्तियों के स्वेच्छाचारी संख्या के साथ सदस्यों ली गई । मेरा उदाहरण, टिम के विपरीत, केवल एक नाम / कुंजी प्रति फ़ंक्शन लेता है। टिम की तरह, हर वर्ग को संग्रहीत किया जा रहा है एक बेस क्लास से प्राप्त किया जाता है , मेरा आधार कहा जाता है ।

Base.h

#ifndef BASE_H
#define BASE_H

class Base{
    public:
        virtual ~Base(){}
};

#endif

EX_Factory.h

#ifndef EX_COMPONENT_H
#define EX_COMPONENT_H

#include <string>
#include <map>
#include "Base.h"

struct EX_Factory{
    template<class U, typename... Args>
    static void registerC(const std::string &name){
        registry<Args...>[name] = &create<U>;
    }
    template<typename... Args>
    static Base * createObject(const std::string &key, Args... args){
        auto it = registry<Args...>.find(key);
        if(it == registry<Args...>.end()) return nullptr;
        return it->second(args...);
    }
    private:
        EX_Factory() = delete;
        template<typename... Args>
        static std::map<std::string, Base*(*)(Args...)> registry;

        template<class U, typename... Args>
        static Base* create(Args... args){
            return new U(args...);
        }
};

template<typename... Args>
std::map<std::string, Base*(*)(Args...)> EX_Factory::registry; // Static member declaration.


#endif

main.cpp

#include "EX_Factory.h"
#include <iostream>

using namespace std;

struct derived_1 : public Base{
    derived_1(int i, int j, float f){
        cout << "Derived 1:\t" << i * j + f << endl;
    }
};
struct derived_2 : public Base{
    derived_2(int i, int j){
        cout << "Derived 2:\t" << i + j << endl;
    }
};

int main(){
    EX_Factory::registerC<derived_1, int, int, float>("derived_1"); // Need to include arguments
                                                                    //  when registering classes.
    EX_Factory::registerC<derived_2, int, int>("derived_2");
    derived_1 * d1 = static_cast<derived_1*>(EX_Factory::createObject<int, int, float>("derived_1", 8, 8, 3.0));
    derived_2 * d2 = static_cast<derived_2*>(EX_Factory::createObject<int, int>("derived_2", 3, 3));
    delete d1;
    delete d2;
    return 0;
}

उत्पादन

Derived 1:  67
Derived 2:  6

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

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