Skip to content

Commit

Permalink
UPBGE: Initialize components during first update.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
panzergame committed Dec 16, 2016
1 parent 20be643 commit 814afdf
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 174 deletions.
115 changes: 91 additions & 24 deletions source/gameengine/Converter/BL_BlenderDataConversion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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<KX_PythonComponent *>(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(
Expand Down Expand Up @@ -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());

Expand All @@ -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<KX_GameObject> it = objectlist->GetBegin(), end = objectlist->GetEnd(); it != end; ++it) {
KX_GameObject *gameobj = *it;
Expand Down Expand Up @@ -1937,6 +2003,16 @@ void BL_ConvertBlenderObjects(struct Main* maggie,
gameobj->ResetState();
}

// Convert the python components of each object.
for (CListValue::iterator<KX_GameObject> 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 -
Expand All @@ -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();
}
}

104 changes: 14 additions & 90 deletions source/gameengine/Ketsji/KX_GameObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<KX_PythonComponent> 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)
Expand Down Expand Up @@ -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<KX_PythonComponent *>(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()
Expand All @@ -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<KX_PythonComponent> it = m_components->GetBegin(), end = m_components->GetEnd(); it != end; ++it) {
KX_PythonComponent *comp = *it;
comp->Update();
}

#endif // WITH_PYTHON
Expand Down
16 changes: 5 additions & 11 deletions source/gameengine/Ketsji/KX_GameObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading

0 comments on commit 814afdf

Please sign in to comment.