From 814afdf57eae0eb284a4ca9d757384857468e51e Mon Sep 17 00:00:00 2001 From: Porteries Tristan Date: Thu, 15 Dec 2016 18:47:19 +0000 Subject: [PATCH] UPBGE: Initialize components during first update. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, components were initialized independently of the component update. This operation was made in the conversion and in the object replication, but some issues were noticed: in case of libloading the component must be run in an unmerged scene but they have to acces to the new scene. The workaround was to bind the final scene before initialize the python component, but this solution is unstable. To avoid this problematic case, the components could be initialized at the begining of theirs first update. As we ensure that the update is independent of the lib loading and the object creation. To realize this solution, the conversion from blender struct PythonComponent to KX_PythonComponent previously made in KX_GameObject::InitComponents() is now moves in the converter function: BL_ConvertComponentsObject. This function converts all the components, put them in a list and set the list to the game object thanks to KX_GameObject::SetComponents(…). This conversion is made only for objects converted and doesn't manage the duplicated object. These duplicated objects must use a new python component with the same python proxy subclassing. To do so the function GetReplica() in KX_PythonComponent is overrided. This function is used when the game object component list is replicated, in the same time each component remap their game object owner. The initialization is made in KX_PythonComponent::Start when the member m_init is checked to false in Update(). But the components have to know which blender component they are using and which game object is owning them. To answer this problem the new setter SetBlenderPythonComponent is called in the same time of SetGameObject when the components are converted. The initialization is now independent of libloading but not about object duplication. To avoid issues iterating on the list of objects which can be modified, a second list is made in KX_Scene::LogicUpdateFrame containing all the objects using a component at the begining of the frame update. Added object will be update in the next frame as before. Reported by Maujoe in #335. --- .../Converter/BL_BlenderDataConversion.cpp | 115 ++++++++++++++---- source/gameengine/Ketsji/KX_GameObject.cpp | 104 +++------------- source/gameengine/Ketsji/KX_GameObject.h | 16 +-- .../gameengine/Ketsji/KX_PythonComponent.cpp | 96 ++++++++++----- source/gameengine/Ketsji/KX_PythonComponent.h | 22 +++- source/gameengine/Ketsji/KX_Scene.cpp | 25 ++-- 6 files changed, 204 insertions(+), 174 deletions(-) diff --git a/source/gameengine/Converter/BL_BlenderDataConversion.cpp b/source/gameengine/Converter/BL_BlenderDataConversion.cpp index 6cd33ff517c1..be62664d8891 100644 --- a/source/gameengine/Converter/BL_BlenderDataConversion.cpp +++ b/source/gameengine/Converter/BL_BlenderDataConversion.cpp @@ -77,6 +77,7 @@ #include "KX_EmptyObject.h" #include "KX_FontObject.h" #include "KX_LodManager.h" +#include "KX_PythonComponent.h" #include "RAS_ICanvas.h" #include "RAS_Polygon.h" @@ -92,6 +93,7 @@ #include "BKE_main.h" #include "BKE_global.h" #include "BKE_object.h" +#include "BKE_python_component.h" #include "BL_ModifierDeformer.h" #include "BL_ShapeDeformer.h" #include "BL_SkinDeformer.h" @@ -137,6 +139,7 @@ #include "DNA_action_types.h" #include "DNA_object_force.h" #include "DNA_constraint_types.h" +#include "DNA_python_component_types.h" #include "MEM_guardedalloc.h" @@ -1207,6 +1210,84 @@ static KX_GameObject* getGameOb(STR_String busc,CListValue* sumolist) } +static void BL_ConvertComponentsObject(KX_GameObject *gameobj, Object *blenderobj) +{ + PythonComponent *pc = (PythonComponent *)blenderobj->components.first; + PyObject *arg_dict = NULL, *args = NULL, *mod = NULL, *cls = NULL, *pycomp = NULL, *ret = NULL; + + if (!pc) { + return; + } + + CListValue *components = new CListValue(); + + while (pc) { + // Make sure to clean out anything from previous loops + Py_XDECREF(args); + Py_XDECREF(arg_dict); + Py_XDECREF(mod); + Py_XDECREF(cls); + Py_XDECREF(ret); + Py_XDECREF(pycomp); + args = arg_dict = mod = cls = pycomp = ret = NULL; + + // Grab the module + mod = PyImport_ImportModule(pc->module); + + if (mod == NULL) { + if (PyErr_Occurred()) { + PyErr_Print(); + } + CM_Error("coulding import the module '" << pc->module << "'"); + pc = pc->next; + continue; + } + + // Grab the class object + cls = PyObject_GetAttrString(mod, pc->name); + if (cls == NULL) { + if (PyErr_Occurred()) { + PyErr_Print(); + } + CM_Error("python module found, but failed to find the component '" << pc->name << "'"); + pc = pc->next; + continue; + } + + // Lastly make sure we have a class and it's an appropriate sub type + if (!PyType_Check(cls) || !PyObject_IsSubclass(cls, (PyObject*)&KX_PythonComponent::Type)) { + CM_Error(pc->module << "." << pc->name << " is not a KX_PythonComponent subclass"); + pc = pc->next; + continue; + } + + // Every thing checks out, now generate the args dictionary and init the component + args = PyTuple_Pack(1, gameobj->GetProxy()); + + pycomp = PyObject_Call(cls, args, NULL); + + if (PyErr_Occurred()) { + // The component is invalid, drop it + PyErr_Print(); + } + else { + KX_PythonComponent *comp = static_cast(BGE_PROXY_REF(pycomp)); + comp->SetBlenderPythonComponent(pc); + comp->SetGameObject(gameobj); + components->Add(comp); + } + + pc = pc->next; + } + + Py_XDECREF(args); + Py_XDECREF(mod); + Py_XDECREF(cls); + Py_XDECREF(pycomp); + + gameobj->SetComponents(components); +} + /* helper for BL_ConvertBlenderObjects, avoids code duplication * note: all var names match args are passed from the caller */ static void bl_ConvertBlenderObject_Single( @@ -1852,14 +1933,6 @@ void BL_ConvertBlenderObjects(struct Main* maggie, } } - /* cleanup converted set of group objects */ - convertedlist->Release(); - sumolist->Release(); - - // Set the physics environment so KX_PythonComponent.start() can use bge.constraints - KX_Scene *currentScene = KX_GetActiveScene(); - PHY_IPhysicsEnvironment *currentEnv = PHY_GetActiveEnvironment(); - KX_SetActiveScene(kxscene); PHY_SetActiveEnvironment(kxscene->GetPhysicsEnvironment()); @@ -1877,13 +1950,6 @@ void BL_ConvertBlenderObjects(struct Main* maggie, } } - /* Restore the current scene and physics engine yet it was changed to - * allow python components using the current scene and physics engine. - */ - - KX_SetActiveScene(currentScene); - PHY_SetActiveEnvironment(currentEnv); - //process navigation mesh objects for (CListValue::iterator it = objectlist->GetBegin(), end = objectlist->GetEnd(); it != end; ++it) { KX_GameObject *gameobj = *it; @@ -1937,6 +2003,16 @@ void BL_ConvertBlenderObjects(struct Main* maggie, gameobj->ResetState(); } + // Convert the python components of each object. + for (CListValue::iterator it = sumolist->GetBegin(), end = sumolist->GetEnd(); it != end; ++it) { + KX_GameObject *gameobj = *it; + Object *blenderobj = gameobj->GetBlenderObject(); + BL_ConvertComponentsObject(gameobj, blenderobj); + } + + // cleanup converted set of group objects + convertedlist->Release(); + sumolist->Release(); logicbrick_conversionlist->Release(); // Calculate the scene btree - @@ -1955,14 +2031,5 @@ void BL_ConvertBlenderObjects(struct Main* maggie, kxscene->DupliGroupRecurse(gameobj, 0); } } - - /* Initialize python components, use a fixed size because some component can add object - * and these objects are only at the end of the list. Never use iterator here because the - * beginning iterator can be changed and then pointed to a fake game object. - */ - for (unsigned int i = 0, size = objectlist->GetCount(); i < size; ++i) { - KX_GameObject *gameobj = (KX_GameObject *)objectlist->GetValue(i); - gameobj->InitComponents(); - } } diff --git a/source/gameengine/Ketsji/KX_GameObject.cpp b/source/gameengine/Ketsji/KX_GameObject.cpp index 4366fa01ba1a..549991a10973 100644 --- a/source/gameengine/Ketsji/KX_GameObject.cpp +++ b/source/gameengine/Ketsji/KX_GameObject.cpp @@ -70,7 +70,6 @@ #include "KX_CollisionContactPoints.h" #include "BKE_object.h" -#include "BKE_python_component.h" #include "BL_ActionManager.h" #include "BL_Action.h" @@ -535,14 +534,19 @@ void KX_GameObject::ProcessReplica() } #ifdef WITH_PYTHON + if (m_attr_dict) m_attr_dict= PyDict_Copy(m_attr_dict); -#endif if (m_components) { - m_components->Release(); + m_components = (CListValue *)m_components->GetReplica(); + for (CListValue::iterator it = m_components->GetBegin(), end = m_components->GetEnd(); it != end; ++it) { + KX_PythonComponent *component = *it; + component->SetGameObject(this); + } } - m_components = NULL; + +#endif } static void setGraphicController_recursive(SG_Node* node) @@ -1605,92 +1609,14 @@ CListValue* KX_GameObject::GetChildrenRecursive() return list; } -CListValue *KX_GameObject::GetComponents() +CListValue *KX_GameObject::GetComponents() const { return m_components; } -void KX_GameObject::InitComponents() +void KX_GameObject::SetComponents(CListValue *components) { -#ifdef WITH_PYTHON - PythonComponent *pc = (PythonComponent *)GetBlenderObject()->components.first; - PyObject *arg_dict = NULL, *args = NULL, *mod = NULL, *cls = NULL, *pycomp = NULL, *ret = NULL; - - if (!pc) { - return; - } - - m_components = new CListValue(); - - while (pc) { - // Make sure to clean out anything from previous loops - Py_XDECREF(args); - Py_XDECREF(arg_dict); - Py_XDECREF(mod); - Py_XDECREF(cls); - Py_XDECREF(ret); - Py_XDECREF(pycomp); - args = arg_dict = mod = cls = pycomp = ret = NULL; - - // Grab the module - mod = PyImport_ImportModule(pc->module); - - if (mod == NULL) { - if (PyErr_Occurred()) { - PyErr_Print(); - } - CM_Error("coulding import the module '" << pc->module << "'"); - pc = pc->next; - continue; - } - - // Grab the class object - cls = PyObject_GetAttrString(mod, pc->name); - if (cls == NULL) { - if (PyErr_Occurred()) { - PyErr_Print(); - } - CM_Error("python module found, but failed to find the component '" << pc->name << "'"); - pc = pc->next; - continue; - } - - // Lastly make sure we have a class and it's an appropriate sub type - if (!PyType_Check(cls) || !PyObject_IsSubclass(cls, (PyObject*)&KX_PythonComponent::Type)) { - CM_Error(pc->module << "." << pc->name << " is not a KX_PythonComponent subclass"); - pc = pc->next; - continue; - } - - // Every thing checks out, now generate the args dictionary and init the component - arg_dict = (PyObject *)BKE_python_component_argument_dict_new(pc); - args = PyTuple_New(1); - PyTuple_SetItem(args, 0, GetProxy()); - - pycomp = PyObject_Call(cls, args, NULL); - - ret = PyObject_CallMethod(pycomp, "start", "O", arg_dict); - - if (PyErr_Occurred()) { - // The component is invalid, drop it - PyErr_Print(); - } - else { - KX_PythonComponent *comp = static_cast(BGE_PROXY_REF(pycomp)); - m_components->Add(comp); - } - - pc = pc->next; - } - - Py_XDECREF(args); - Py_XDECREF(arg_dict); - Py_XDECREF(mod); - Py_XDECREF(cls); - Py_XDECREF(ret); - Py_XDECREF(pycomp); - -#endif // WITH_PYTHON + m_components = components; } void KX_GameObject::UpdateComponents() @@ -1700,11 +1626,9 @@ void KX_GameObject::UpdateComponents() return; } - for (CListValue::baseIterator it = m_components->GetBegin(), end = m_components->GetEnd(); it != end; ++it) { - PyObject *pycomp = (*it)->GetProxy(); - if (!PyObject_CallMethod(pycomp, "update", "")) { - PyErr_Print(); - } + for (CListValue::iterator it = m_components->GetBegin(), end = m_components->GetEnd(); it != end; ++it) { + KX_PythonComponent *comp = *it; + comp->Update(); } #endif // WITH_PYTHON diff --git a/source/gameengine/Ketsji/KX_GameObject.h b/source/gameengine/Ketsji/KX_GameObject.h index 1827721505dc..59e9b7b50c44 100644 --- a/source/gameengine/Ketsji/KX_GameObject.h +++ b/source/gameengine/Ketsji/KX_GameObject.h @@ -911,19 +911,13 @@ class KX_GameObject : public SCA_IObject CListValue* GetChildren(); CListValue* GetChildrenRecursive(); - /** - * Returns the component list - */ - CListValue *GetComponents(); - /** - * Initializes the components - */ - void InitComponents(); + /// Returns the component list. + CListValue *GetComponents() const; + /// Add a components. + void SetComponents(CListValue *components); - /** - * Updates the components - */ + /// Updates the components. void UpdateComponents(); KX_Scene* GetScene(); diff --git a/source/gameengine/Ketsji/KX_PythonComponent.cpp b/source/gameengine/Ketsji/KX_PythonComponent.cpp index bdc9c6f6e4a9..d40dcb1b617d 100644 --- a/source/gameengine/Ketsji/KX_PythonComponent.cpp +++ b/source/gameengine/Ketsji/KX_PythonComponent.cpp @@ -24,12 +24,18 @@ #include "KX_PythonComponent.h" #include "KX_GameObject.h" -// #include "BLI_string.h" -KX_PythonComponent::KX_PythonComponent(STR_String name) - :CValue(), +#include "CM_Message.h" + +#include "DNA_python_component_types.h" + +#include "BKE_python_component.h" + +KX_PythonComponent::KX_PythonComponent(const STR_String& name) + :m_pc(NULL), m_gameobj(NULL), - m_name(name) + m_name(name), + m_init(false) { } @@ -37,7 +43,35 @@ KX_PythonComponent::~KX_PythonComponent() { } -KX_GameObject *KX_PythonComponent::GetGameObject() +STR_String KX_PythonComponent::GetName() +{ + return m_name; +} + +CValue *KX_PythonComponent::GetReplica() +{ + KX_PythonComponent *replica = new KX_PythonComponent(*this); + replica->ProcessReplica(); + + // Subclass the python component. + PyTypeObject *type = Py_TYPE(GetProxy()); + if (!py_base_new(type, PyTuple_Pack(1, replica->GetProxy()), NULL)) { + CM_Error("failed replicate component: \"" << m_name << "\""); + delete replica; + return NULL; + } + + return replica; +} + +void KX_PythonComponent::ProcessReplica() +{ + CValue::ProcessReplica(); + m_gameobj = NULL; + m_init = false; +} + +KX_GameObject *KX_PythonComponent::GetGameObject() const { return m_gameobj; } @@ -47,43 +81,49 @@ void KX_PythonComponent::SetGameObject(KX_GameObject *gameobj) m_gameobj = gameobj; } -STR_String KX_PythonComponent::GetName() +void KX_PythonComponent::SetBlenderPythonComponent(PythonComponent *pc) { - return m_name; + m_pc = pc; } -PyObject *KX_PythonComponent::py_component_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +void KX_PythonComponent::Start() { - KX_PythonComponent *comp = new KX_PythonComponent(STR_String(type->tp_name)); + PyObject *arg_dict = (PyObject *)BKE_python_component_argument_dict_new(m_pc); - PyObject *proxy = py_base_new(type, PyTuple_Pack(1, comp->GetProxy()), kwds); - if (!proxy) { - delete comp; - return NULL; + PyObject *ret = PyObject_CallMethod(GetProxy(), "start", "O", arg_dict); + + if (PyErr_Occurred()) { + PyErr_Print(); } - return proxy; + Py_XDECREF(arg_dict); + Py_XDECREF(ret); } -int KX_PythonComponent::py_component_init(PyObjectPlus_Proxy *self, PyObject *args, PyObject *kwds) +void KX_PythonComponent::Update() { - PyObject *pyobj; - - if (!PyArg_ParseTuple(args, "O", &pyobj)) { - return -1; + if (!m_init) { + Start(); + m_init = true; } - if (!PyObject_IsInstance(pyobj, (PyObject*)&KX_GameObject::Type)) { - PyErr_SetString(PyExc_TypeError, "expected a KX_GameObject for first argument"); - return -1; + PyObject *pycomp = GetProxy(); + if (!PyObject_CallMethod(pycomp, "update", "")) { + PyErr_Print(); } +} - KX_GameObject *gameobj = static_cast(BGE_PROXY_REF(pyobj)); - KX_PythonComponent *kxpycomp = static_cast(BGE_PROXY_REF(self)); +PyObject *KX_PythonComponent::py_component_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + KX_PythonComponent *comp = new KX_PythonComponent(STR_String(type->tp_name)); - kxpycomp->SetGameObject(gameobj); + PyObject *proxy = py_base_new(type, PyTuple_Pack(1, comp->GetProxy()), kwds); + if (!proxy) { + delete comp; + return NULL; + } - return 0; + return proxy; } PyTypeObject KX_PythonComponent::Type = { @@ -104,9 +144,7 @@ PyTypeObject KX_PythonComponent::Type = { 0, 0, &PyObjectPlus::Type, - 0,0,0,0, - (initproc)py_component_init, - 0, + 0,0,0,0,0,0, py_component_new }; diff --git a/source/gameengine/Ketsji/KX_PythonComponent.h b/source/gameengine/Ketsji/KX_PythonComponent.h index 0ee1c61f9296..8d00de5aafe5 100644 --- a/source/gameengine/Ketsji/KX_PythonComponent.h +++ b/source/gameengine/Ketsji/KX_PythonComponent.h @@ -28,27 +28,37 @@ #include "EXP_Value.h" class KX_GameObject; +struct PythonComponent; class KX_PythonComponent : public CValue { Py_Header + private: - // member vars + PythonComponent *m_pc; KX_GameObject *m_gameobj; STR_String m_name; + bool m_init; public: - KX_PythonComponent(STR_String name); + KX_PythonComponent(const STR_String& name); virtual ~KX_PythonComponent(); - KX_GameObject *GetGameObject(); + // stuff for cvalue related things + virtual STR_String GetName(); + virtual CValue *GetReplica(); + + void ProcessReplica(); + + KX_GameObject *GetGameObject() const; void SetGameObject(KX_GameObject *gameobj); - // stuff for cvalue related things - STR_String GetName(); + void SetBlenderPythonComponent(PythonComponent *pc); + + void Start(); + void Update(); static PyObject *py_component_new(PyTypeObject *type, PyObject *args, PyObject *kwds); - static int py_component_init(PyObjectPlus_Proxy *self, PyObject *args, PyObject *kwds); // Attributes static PyObject *pyattr_get_object(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef); diff --git a/source/gameengine/Ketsji/KX_Scene.cpp b/source/gameengine/Ketsji/KX_Scene.cpp index 3bfc0a8501b6..64b23c879f08 100644 --- a/source/gameengine/Ketsji/KX_Scene.cpp +++ b/source/gameengine/Ketsji/KX_Scene.cpp @@ -973,16 +973,6 @@ SCA_IObject* KX_Scene::AddReplicaObject(class CValue* originalobject, DupliGroupRecurse(*git, 0); } - // Initialize component in root parent object. - replica->InitComponents(); - // Initialize components recursively. - CListValue *childrecursive = replica->GetChildrenRecursive(); - for (CListValue::iterator it = childrecursive->GetBegin(), end = childrecursive->GetEnd(); it != end; ++it) { - KX_GameObject *gameobj = *it; - gameobj->InitComponents(); - } - childrecursive->Release(); - // don't release replica here because we are returning it, not done with it... return replica; } @@ -1632,12 +1622,19 @@ void KX_Scene::UpdateAnimations(double curtime) void KX_Scene::LogicUpdateFrame(double curtime, bool frame) { - /* Update object components, don't use iterator in loop because component can add objects and then make - * iterators invalid. + /* Update object components, we copy the object pointer in a second list to make sure that we iterate on a list + * which will not be modified, indeed components can add objects in theirs initialization. */ - for (int i = 0; i < m_objectlist->GetCount(); ++i) { - ((KX_GameObject*)m_objectlist->GetValue(i))->UpdateComponents(); + + std::vector objects; + for (CListValue::iterator it = m_objectlist->GetBegin(), end = m_objectlist->GetEnd(); it != end; ++it) { + objects.push_back(*it); + } + + for (std::vector::iterator it = objects.begin(), end = objects.end(); it != end; ++it) { + (*it)->UpdateComponents(); } + m_logicmgr->UpdateFrame(curtime, frame); }