2014-12-13 47 views
2

這個問題回真的是以下兩個問題的延伸:檢索Python類型從C++

  1. How can I implement a C++ class in Python, to be called by C++?
  2. Swig downcasting from Base* to Derived*

假設我有以下的C++類(簡體) ,我正在使用SWIG公開Python:

struct Component 
{ 
    virtual void update(double dt); 
} 

struct DerivedComponent : public Component 
{ 
    void update(double dt) { std::cout << "DerivedComponent::update()" << std::endl; } 
    void f() { std::cout << "DerivedComponent::f()" << std::endl; } 
} 

class Entity 
{ 
public: 
    Component* component(const std::string& class_name) 
    { 
     return m_components[class_name]; 
    } 

    Component* create_component(const std::string& class_name) 
    { 
     // Creates a new Component, possibly using the method described in (1), 
     // adds it to m_components and returns the component. 
     // This method can also instantiate subclasses of Component that were 
     // defined in c++. 
    } 

private: 
    std::unordered_map<std::string, Component*> m_components; 
} 

現在,在Python中,我定義一個類從Component繼承:

class PythonDerivedComponent(Component): 
    def __init__(self): 
     Component.__init__(self) 
    def update(self, dt): 
     print("DerivedComponent::update(" + str(dt) + ")") 
    def g() 
     print("DerivedComponent::g()") 

我得在那裏我可以將組件添加到實體的階段。此外,使用通過柔性版(2)中描述的方法,我可以從create_component()檢索衍生成分類型當組件在C++定義爲:

e = Entity() 
c = e.create_component("DerivedComponent") # Has type <class 'module.DerivedComponent'> 
c.f() # Prints "DerivedComponent::f()" as expected. 

現在,我的問題是,是否有可能得到派生組件類型create_component()是什麼時候在Python中定義了組件?當然,我只需要能夠在Python中做到這一點。

e = Entity("Player") 
e.create_component("PythonDerivedComponent") 

# Somewhere else... 
e = get_entity("Player") 
c = e.component("PythonDerivedComponent") # Has type <class 'module.Component'> 
c.g() # Can't call this function. 
+0

肯定可以解決的,我看你要去哪裏,你能詳細說明「怎樣也可以實例化Component的子類」 rks - 是否只是(例如)一個知道如何創建每個註冊類型的實例的'std :: function'對象的映射? – Flexo 2014-12-13 00:55:41

+0

你完全正確。你可以在這個問題的答案中看到完整的細節:http://gamedev.stackexchange.com/questions/17746/entity-component-systems-in-c-how-do-i-discover-types-and-construct -零件 – Homar 2014-12-13 01:02:37

回答

2

爲了使一個完整的工作演示,我不得不對你的頭文件略有擴大,礦山結束了看起來像:

#ifndef TEST_HH 
#define TEST_HH 

#include <map> 
#include <functional> 
#include <iostream> 
#include <string> 

struct Component 
{ 
    virtual void update(double dt) = 0; 
    virtual ~Component() {} 
}; 

struct DerivedComponent : public Component 
{ 
    void update(double) { std::cout << "DerivedComponent::update()" << std::endl; } 
    void f() { std::cout << "DerivedComponent::f()" << std::endl; } 

    static DerivedComponent *create() { 
     return new DerivedComponent; 
    } 
}; 

class Entity 
{ 
public: 
    Component* component(const std::string& class_name) 
    { 
     return m_components[class_name]; 
    } 

    Component* create_component(const std::string& class_name) 
    { 
     // Creates a new Component, possibly using the method described in (1), 
     // adds it to m_components and returns the component. 
     // This method can also instantiate subclasses of Component that were 
     // defined in c++. 
     Component *result = nullptr; 
     if (m_components[class_name]) { 
      result = m_components[class_name]; 
     } 
     else if (m_registry[class_name]) { 
      result = m_registry[class_name](); 
      m_components[class_name] = result; 
     } 
     return result; // Or raise an exception if null? 
    } 

    void register_component(const std::string& class_name, std::function<Component*()> creator) { 
     m_registry[class_name] = creator; 
    } 
private: 
    std::map<std::string, Component*> m_components; 
    std::map<std::string, std::function<Component*()> > m_registry; 
}; 

inline void register_builtins(Entity& e) { 
    e.register_component("DerivedComponent", DerivedComponent::create);  
} 

#endif 

主要是修正了一些語法錯誤和添加的註冊表類型,用std::function知道如何創建實例的對象。

我們建立在你引用的兩個問題的類型映射上,所以我不會在這個答案中談論那部分內容,除了說爲了讓你的Python例子類工作,我們必須使用導演(否則它是永久抽象的),並且上一個問題的'out'類型圖已被修改爲在更多的地方應用以及添加額外的功能。 (注意,這個假設與class_name字符串將arg2一如既往。你可以一個函數添加到返回這個名字這將消除對這個假設需要的基類)

主要有兩種技巧都是我們需要讓這個工作:

  1. 我們需要實現'Python意識'版本Component::register_component。在這個例子中,我用C++ 11 lambda函數實現了它,它保留了它將要產生的產品類型的引用。 (請注意,我的示例會泄漏這些類型,因爲它永遠不會減少引用計數器。如果這對您的使用有問題,您應該使用智能指針)。
  2. 我們需要保留構建我們的Python派生類型時創建的真實PyObject。有幾種方法可以做到這一點,例如使用全球地圖std::map<Component*,PyObject*>,或者通過在test.hh中的Entity類中實際添加它。我不喜歡全局變量,並且假設您不希望最終將Python接口與C+++實現混爲一談。因此,我選擇添加另一個繼承自Component的中間抽象類,僅用於記住實現它的PyObject實際上是什麼。

添加中間級PythonComponent類可能會帶來一些附加好處,也就是說您不會爲純C++派生類型支付SWIG導向器的成本。 (如果你想,你可以使用%pythoncode%rename假裝Python開發人員,他們真的只是使用Component而不是PythonComponent玩遊戲,但我沒有這樣做,在這裏)

我痛飲接口文件從而結束了尋找像:

%module(directors=1) test 

%{ 
#include "test.hh" 
#include <cassert> 
struct PythonComponent : Component { 
    PyObject *derrived; 
}; 
%} 

%feature("director") PythonComponent; 

%include <std_string.i> 

// Note: this now gets applied to anything returning Component * 
%typemap(out) Component * { 
    const PythonComponent * const pycomp = dynamic_cast<PythonComponent*>($1); 
    if (pycomp) { 
     $result = pycomp->derrived; 
     Py_INCREF($result); 
    } 
    else { 
     const std::string lookup_typename = *arg2 + " *"; 
     swig_type_info * const outtype = SWIG_TypeQuery(lookup_typename.c_str()); 
     $result = SWIG_NewPointerObj(SWIG_as_voidptr($1), outtype, $owner); 
    } 
} 

%include "test.hh" 


struct PythonComponent : Component { 
}; 

%extend Entity { 
    void register_component(const std::string& class_name, PyObject *python_type) { 
     assert(PyCallable_Check(python_type)); 
     Py_INCREF(python_type); 
     $self->register_component(class_name, [python_type](){ 
      PyObject *pyinstance = PyObject_CallObject(python_type, NULL); 
      void *result; 
      const auto res = SWIG_ConvertPtr(pyinstance, &result,SWIGTYPE_p_PythonComponent, 0); 
      if (!SWIG_IsOK(res)) { 
       assert(false); // TODO: raise exception 
      } 
      const auto out = reinterpret_cast<PythonComponent *>(result); 
      out->derrived = pyinstance; 
      return out; 
     }); 
    } 
} 

我們使用%extend實現的register_component過載。這種重載也可以讓我們從Python內部控制本地C++類型的註冊,只需極少額外的努力,I wrote an answer about that previously

就Python封裝而言,PythonComponent類型實際上並沒有根本改變Component。保留的引用的細節保留爲實現細節。

有了我們所需要的,以使這項工作做這些機制是貫徹落實新的「出」類型映射,增加了一個dynamic_cast弄清楚,如果在表中的C++類確實是一個PythonComponent,如果它使用保留的PyObject而不是查找SWIG類型。

我們編譯:

swig -py3 -c++ -python -Wall test.i 
g++ -std=c++11 -Wall -Wextra test_wrap.cxx -I/usr/include/python3.4/ -lpython3.4m -shared -o _test.so 

而且我適應你的測試用例來糾正一些問題,並呼籲register_component

from test import * 

e = Entity() 
register_builtins(e) 

c = e.create_component("DerivedComponent") # Has type <class 'module.DerivedComponent'> 
c.f() # Prints "DerivedComponent::f()" as expected. 

class PythonDerivedComponent(PythonComponent): 
    def update(self, dt): 
     print("PythonDerivedComponent::update(" + str(dt) + ")") 
    def g(self): 
     print("PythonDerivedComponent::g()") 

e.register_component("PythonDerivedComponent", PythonDerivedComponent) 
e.create_component("PythonDerivedComponent") 

c = e.component("PythonDerivedComponent") 
print(type(c)) 
c.g() # Now works. 

當我們運行它,我們看到:

DerivedComponent::f() 
<class '__main__.PythonDerivedComponent'> 
PythonDerivedComponent::g(