Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitchell Stokes <mogurijin@gmail.com>2012-12-22 09:38:32 +0400
committerMitchell Stokes <mogurijin@gmail.com>2012-12-22 09:38:32 +0400
commit84966c3d0ae2d39fa4e691ed0131d8b94f8785e4 (patch)
tree96f1fbaac1e4b2f151c7726c90b8491a718666c1 /source/gameengine/Converter
parent8062bcd16f9e91d76dbd074f32b889ce2e0cba84 (diff)
BGE: Committing async LibLoad from Swiss. This does the lib loading in a separate thread to keep the BGE from freezing. Here is an example from the docs:
# Print a message when an async LibLoad is done import bge def finished_cb(status): print("Library (%s) loaded in %.2fms." % (status.libraryName, status.timeTaken)) bge.logic.LibLoad('myblend.blend', 'Scene', async=True).onFinish = finished_cb LibLoad() now returns a KX_LibLoadStatus object for information on the library loading. LibNew() and LibFree() are unaffected by this commit. In other words, the async option only works for LibLoad(). Furthermore it only works for Scenes, not Actions or Meshes.
Diffstat (limited to 'source/gameengine/Converter')
-rw-r--r--source/gameengine/Converter/BL_BlenderDataConversion.cpp10
-rw-r--r--source/gameengine/Converter/CMakeLists.txt2
-rw-r--r--source/gameengine/Converter/KX_BlenderSceneConverter.cpp136
-rw-r--r--source/gameengine/Converter/KX_BlenderSceneConverter.h17
-rw-r--r--source/gameengine/Converter/KX_LibLoadStatus.cpp251
-rw-r--r--source/gameengine/Converter/KX_LibLoadStatus.h85
6 files changed, 484 insertions, 17 deletions
diff --git a/source/gameengine/Converter/BL_BlenderDataConversion.cpp b/source/gameengine/Converter/BL_BlenderDataConversion.cpp
index f9586e32816..874bf614413 100644
--- a/source/gameengine/Converter/BL_BlenderDataConversion.cpp
+++ b/source/gameengine/Converter/BL_BlenderDataConversion.cpp
@@ -192,6 +192,8 @@ extern "C" {
}
#endif
+#include "BLI_threads.h"
+
static bool default_light_mode = 0;
static std::map<int, SCA_IInputDevice::KX_EnumInputs> create_translate_table()
@@ -1342,7 +1344,13 @@ static float my_boundbox_mesh(Mesh *me, float *loc, float *size)
float radius=0.0f, vert_radius, *co;
int a;
- if (me->bb==0) me->bb= (struct BoundBox *)MEM_callocN(sizeof(BoundBox), "boundbox");
+ if (me->bb==0) {
+ // This can be called in a seperate (not main) thread when doing async libload,
+ // so lets try to be safe...
+ BLI_begin_threaded_malloc();
+ me->bb= (struct BoundBox *)MEM_callocN(sizeof(BoundBox), "boundbox");
+ BLI_end_threaded_malloc();
+ }
bb= me->bb;
INIT_MINMAX(min, max);
diff --git a/source/gameengine/Converter/CMakeLists.txt b/source/gameengine/Converter/CMakeLists.txt
index e9dd97f3821..e01729e156f 100644
--- a/source/gameengine/Converter/CMakeLists.txt
+++ b/source/gameengine/Converter/CMakeLists.txt
@@ -83,6 +83,7 @@ set(SRC
KX_ConvertProperties.cpp
KX_ConvertSensors.cpp
KX_IpoConvert.cpp
+ KX_LibLoadStatus.cpp
KX_SoftBodyDeformer.cpp
BL_ActionActuator.h
@@ -105,6 +106,7 @@ set(SRC
KX_ConvertProperties.h
KX_ConvertSensors.h
KX_IpoConvert.h
+ KX_LibLoadStatus.h
KX_SoftBodyDeformer.h
)
diff --git a/source/gameengine/Converter/KX_BlenderSceneConverter.cpp b/source/gameengine/Converter/KX_BlenderSceneConverter.cpp
index 260ca9ede96..ceaa0a5f5a8 100644
--- a/source/gameengine/Converter/KX_BlenderSceneConverter.cpp
+++ b/source/gameengine/Converter/KX_BlenderSceneConverter.cpp
@@ -60,6 +60,7 @@
#endif
#include "KX_BlenderSceneConverter.h"
+#include "KX_LibLoadStatus.h"
#include "KX_BlenderScalarInterpolator.h"
#include "BL_BlenderDataConversion.h"
#include "BlenderWorldInfo.h"
@@ -113,6 +114,14 @@ extern "C" {
#include "../../blender/blenlib/BLI_linklist.h"
}
+#include <pthread.h>
+
+/* This is used to avoid including pthread.h in KX_BlenderSceneConverter.h */
+typedef struct ThreadInfo {
+ vector<pthread_t> threads;
+ pthread_mutex_t merge_lock;
+} ThreadInfo;
+
KX_BlenderSceneConverter::KX_BlenderSceneConverter(
struct Main* maggie,
class KX_KetsjiEngine* engine
@@ -126,6 +135,8 @@ KX_BlenderSceneConverter::KX_BlenderSceneConverter(
{
tag_main(maggie, 0); /* avoid re-tagging later on */
m_newfilename = "";
+ m_threadinfo = new ThreadInfo();
+ pthread_mutex_init(&m_threadinfo->merge_lock, NULL);
}
@@ -135,6 +146,16 @@ KX_BlenderSceneConverter::~KX_BlenderSceneConverter()
int i;
// delete sumoshapes
+ if (m_threadinfo) {
+ vector<pthread_t>::iterator pit = m_threadinfo->threads.begin();
+ while (pit != m_threadinfo->threads.end()) {
+ pthread_join((*pit), NULL);
+ pit++;
+ }
+
+ pthread_mutex_destroy(&m_threadinfo->merge_lock);
+ delete m_threadinfo;
+ }
int numAdtLists = m_map_blender_to_gameAdtList.size();
for (i=0; i<numAdtLists; i++) {
@@ -299,7 +320,10 @@ void KX_BlenderSceneConverter::ConvertScene(class KX_Scene* destinationscene,
// hook for registration function during conversion.
m_currentScene = destinationscene;
destinationscene->SetSceneConverter(this);
- SG_SetActiveStage(SG_STAGE_CONVERTER);
+
+ // This doesn't really seem to do anything except cause potential issues
+ // when doing threaded conversion, so it's disabled for now.
+ // SG_SetActiveStage(SG_STAGE_CONVERTER);
if (blenderscene)
{
@@ -945,7 +969,67 @@ Main* KX_BlenderSceneConverter::GetMainDynamicPath(const char *path)
return NULL;
}
-bool KX_BlenderSceneConverter::LinkBlendFileMemory(void *data, int length, const char *path, char *group, KX_Scene *scene_merge, char **err_str, short options)
+void KX_BlenderSceneConverter::MergeAsyncLoads()
+{
+ vector<KX_Scene*> *merge_scenes;
+
+ vector<KX_LibLoadStatus*>::iterator mit;
+ vector<KX_Scene*>::iterator sit;
+
+ pthread_mutex_lock(&m_threadinfo->merge_lock);
+
+ for (mit=m_mergequeue.begin(); mit!=m_mergequeue.end(); ++mit) {
+ merge_scenes = (vector<KX_Scene*>*)(*mit)->GetData();
+
+ for (sit=merge_scenes->begin(); sit!=merge_scenes->end(); ++sit) {
+ (*mit)->GetMergeScene()->MergeScene(*sit);
+ delete (*sit);
+ }
+
+
+ delete merge_scenes;
+ (*mit)->SetData(NULL);
+
+ (*mit)->Finish();
+ }
+
+ m_mergequeue.clear();
+
+ pthread_mutex_unlock(&m_threadinfo->merge_lock);
+}
+
+void KX_BlenderSceneConverter::AddScenesToMergeQueue(KX_LibLoadStatus *status)
+{
+ pthread_mutex_lock(&m_threadinfo->merge_lock);
+ m_mergequeue.push_back(status);
+ pthread_mutex_unlock(&m_threadinfo->merge_lock);
+}
+
+static void *async_convert(void *ptr)
+{
+ KX_Scene *new_scene = NULL;
+ KX_LibLoadStatus *status = (KX_LibLoadStatus*)ptr;
+ vector<Scene*> *scenes = (vector<Scene*>*)status->GetData();
+ vector<KX_Scene*> *merge_scenes = new vector<KX_Scene*>(); // Deleted in MergeAsyncLoads
+
+ for (unsigned int i=0; i<scenes->size(); ++i) {
+ new_scene = status->GetEngine()->CreateScene((*scenes)[i], true);
+
+ if (new_scene)
+ merge_scenes->push_back(new_scene);
+
+ status->AddProgress((1.f/scenes->size())*0.9f); // We'll call conversion 90% and merging 10% for now
+ }
+
+ delete scenes;
+ status->SetData(merge_scenes);
+
+ status->GetConverter()->AddScenesToMergeQueue(status);
+
+ return NULL;
+}
+
+KX_LibLoadStatus *KX_BlenderSceneConverter::LinkBlendFileMemory(void *data, int length, const char *path, char *group, KX_Scene *scene_merge, char **err_str, short options)
{
BlendHandle *bpy_openlib = BLO_blendhandle_from_memory(data, length);
@@ -953,7 +1037,7 @@ bool KX_BlenderSceneConverter::LinkBlendFileMemory(void *data, int length, const
return LinkBlendFile(bpy_openlib, path, group, scene_merge, err_str, options);
}
-bool KX_BlenderSceneConverter::LinkBlendFilePath(const char *path, char *group, KX_Scene *scene_merge, char **err_str, short options)
+KX_LibLoadStatus *KX_BlenderSceneConverter::LinkBlendFilePath(const char *path, char *group, KX_Scene *scene_merge, char **err_str, short options)
{
BlendHandle *bpy_openlib = BLO_blendhandle_from_file((char *)path, NULL);
@@ -985,7 +1069,7 @@ static void load_datablocks(Main *main_newlib, BlendHandle *bpy_openlib, const c
BLO_library_append_end(NULL, main_tmp, &bpy_openlib, idcode, flag);
}
-bool KX_BlenderSceneConverter::LinkBlendFile(BlendHandle *bpy_openlib, const char *path, char *group, KX_Scene *scene_merge, char **err_str, short options)
+KX_LibLoadStatus *KX_BlenderSceneConverter::LinkBlendFile(BlendHandle *bpy_openlib, const char *path, char *group, KX_Scene *scene_merge, char **err_str, short options)
{
Main *main_newlib; /* stored as a dynamic 'main' until we free it */
const int idcode = BKE_idcode_from_name(group);
@@ -994,25 +1078,27 @@ bool KX_BlenderSceneConverter::LinkBlendFile(BlendHandle *bpy_openlib, const cha
// TIMEIT_START(bge_link_blend_file);
+ KX_LibLoadStatus *status;
+
/* only scene and mesh supported right now */
if (idcode!=ID_SCE && idcode!=ID_ME &&idcode!=ID_AC) {
snprintf(err_local, sizeof(err_local), "invalid ID type given \"%s\"\n", group);
*err_str= err_local;
BLO_blendhandle_close(bpy_openlib);
- return false;
+ return NULL;
}
if (GetMainDynamicPath(path)) {
snprintf(err_local, sizeof(err_local), "blend file already open \"%s\"\n", path);
*err_str= err_local;
BLO_blendhandle_close(bpy_openlib);
- return false;
+ return NULL;
}
if (bpy_openlib==NULL) {
snprintf(err_local, sizeof(err_local), "could not open blendfile \"%s\"\n", path);
*err_str= err_local;
- return false;
+ return NULL;
}
main_newlib= (Main *)MEM_callocN( sizeof(Main), "BgeMain");
@@ -1039,6 +1125,8 @@ bool KX_BlenderSceneConverter::LinkBlendFile(BlendHandle *bpy_openlib, const cha
strncpy(main_newlib->name, path, sizeof(main_newlib->name));
+ status = new KX_LibLoadStatus(this, m_ketsjiEngine, scene_merge, path);
+
if (idcode==ID_ME) {
/* Convert all new meshes into BGE meshes */
ID* mesh;
@@ -1063,16 +1151,30 @@ bool KX_BlenderSceneConverter::LinkBlendFile(BlendHandle *bpy_openlib, const cha
else if (idcode==ID_SCE) {
/* Merge all new linked in scene into the existing one */
ID *scene;
+ // scenes gets deleted by the thread when it's done using it (look in async_convert())
+ vector<Scene*> *scenes = (options & LIB_LOAD_ASYNC) ? new vector<Scene*>() : NULL;
+
for (scene= (ID *)main_newlib->scene.first; scene; scene= (ID *)scene->next ) {
if (options & LIB_LOAD_VERBOSE)
printf("SceneName: %s\n", scene->name+2);
- /* merge into the base scene */
- KX_Scene* other= m_ketsjiEngine->CreateScene((Scene *)scene, true);
- scene_merge->MergeScene(other);
+ if (options & LIB_LOAD_ASYNC) {
+ scenes->push_back((Scene*)scene);
+ } else {
+ /* merge into the base scene */
+ KX_Scene* other= m_ketsjiEngine->CreateScene((Scene *)scene, true);
+ scene_merge->MergeScene(other);
- // RemoveScene(other); // Don't run this, it frees the entire scene converter data, just delete the scene
- delete other;
+ // RemoveScene(other); // Don't run this, it frees the entire scene converter data, just delete the scene
+ delete other;
+ }
+ }
+
+ if (options & LIB_LOAD_ASYNC) {
+ pthread_t id;
+ status->SetData(scenes);
+ pthread_create(&id, NULL, &async_convert, (void*)status);
+ m_threadinfo->threads.push_back(id);
}
#ifdef WITH_PYTHON
@@ -1093,9 +1195,14 @@ bool KX_BlenderSceneConverter::LinkBlendFile(BlendHandle *bpy_openlib, const cha
}
}
+ if (!(options & LIB_LOAD_ASYNC))
+ status->Finish();
+
+
// TIMEIT_END(bge_link_blend_file);
- return true;
+ m_status_map[main_newlib->name] = status;
+ return status;
}
/* Note m_map_*** are all ok and don't need to be freed
@@ -1382,6 +1489,9 @@ bool KX_BlenderSceneConverter::FreeBlendFile(struct Main *maggie)
removeImportMain(maggie);
#endif
+ delete m_status_map[maggie->name];
+ m_status_map.erase(maggie->name);
+
free_main(maggie);
return true;
diff --git a/source/gameengine/Converter/KX_BlenderSceneConverter.h b/source/gameengine/Converter/KX_BlenderSceneConverter.h
index eddb377dbc7..f7723350eee 100644
--- a/source/gameengine/Converter/KX_BlenderSceneConverter.h
+++ b/source/gameengine/Converter/KX_BlenderSceneConverter.h
@@ -52,6 +52,7 @@ class BL_InterpolatorList;
class BL_Material;
struct Main;
struct Scene;
+struct ThreadInfo;
class KX_BlenderSceneConverter : public KX_ISceneConverter
{
@@ -61,10 +62,16 @@ class KX_BlenderSceneConverter : public KX_ISceneConverter
vector<pair<KX_Scene*,RAS_MeshObject*> > m_meshobjects;
vector<pair<KX_Scene*,BL_Material *> > m_materials;
+ vector<class KX_LibLoadStatus*> m_mergequeue;
+ ThreadInfo *m_threadinfo;
+
// Cached material conversions
map<struct Material*, BL_Material*> m_mat_cache;
map<struct Material*, RAS_IPolyMaterial*> m_polymat_cache;
+ // Saved KX_LibLoadStatus objects
+ map<char *, class KX_LibLoadStatus*> m_status_map;
+
// Should also have a list of collision shapes.
// For the time being this is held in KX_Scene::m_shapes
@@ -159,13 +166,16 @@ public:
struct Main* GetMainDynamicPath(const char *path);
vector<struct Main*> &GetMainDynamic();
- bool LinkBlendFileMemory(void *data, int length, const char *path, char *group, KX_Scene *scene_merge, char **err_str, short options);
- bool LinkBlendFilePath(const char *path, char *group, KX_Scene *scene_merge, char **err_str, short options);
- bool LinkBlendFile(struct BlendHandle *bpy_openlib, const char *path, char *group, KX_Scene *scene_merge, char **err_str, short options);
+ class KX_LibLoadStatus *LinkBlendFileMemory(void *data, int length, const char *path, char *group, KX_Scene *scene_merge, char **err_str, short options);
+ class KX_LibLoadStatus *LinkBlendFilePath(const char *path, char *group, KX_Scene *scene_merge, char **err_str, short options);
+ class KX_LibLoadStatus *LinkBlendFile(struct BlendHandle *bpy_openlib, const char *path, char *group, KX_Scene *scene_merge, char **err_str, short options);
bool MergeScene(KX_Scene *to, KX_Scene *from);
RAS_MeshObject *ConvertMeshSpecial(KX_Scene* kx_scene, Main *maggie, const char *name);
bool FreeBlendFile(struct Main *maggie);
bool FreeBlendFile(const char *path);
+
+ virtual void MergeAsyncLoads();
+ void AddScenesToMergeQueue(class KX_LibLoadStatus *status);
void PrintStats() {
printf("BGE STATS!\n");
@@ -195,6 +205,7 @@ public:
LIB_LOAD_LOAD_ACTIONS = 1,
LIB_LOAD_VERBOSE = 2,
LIB_LOAD_LOAD_SCRIPTS = 4,
+ LIB_LOAD_ASYNC = 8,
};
diff --git a/source/gameengine/Converter/KX_LibLoadStatus.cpp b/source/gameengine/Converter/KX_LibLoadStatus.cpp
new file mode 100644
index 00000000000..fb36f232f30
--- /dev/null
+++ b/source/gameengine/Converter/KX_LibLoadStatus.cpp
@@ -0,0 +1,251 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor(s): Mitchell Stokes
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file KX_LibLoadStatus.cpp
+ * \ingroup bgeconv
+ */
+
+#include "KX_LibLoadStatus.h"
+#include "PIL_time.h"
+
+KX_LibLoadStatus::KX_LibLoadStatus(class KX_BlenderSceneConverter* kx_converter,
+ class KX_KetsjiEngine* kx_engine,
+ class KX_Scene* merge_scene,
+ const char *path) :
+ m_converter(kx_converter),
+ m_engine(kx_engine),
+ m_mergescene(merge_scene),
+ m_data(NULL),
+ m_libname(path),
+ m_progress(0.f),
+#ifdef WITH_PYTHON
+ m_finish_cb(NULL),
+ m_progress_cb(NULL)
+#endif
+{
+ m_endtime = m_starttime = PIL_check_seconds_timer();
+}
+
+void KX_LibLoadStatus::Finish()
+{
+ m_progress = 1.f;
+ m_endtime = PIL_check_seconds_timer();
+
+ RunFinishCallback();
+ RunProgressCallback();
+}
+
+void KX_LibLoadStatus::RunFinishCallback()
+{
+#ifdef WITH_PYTHON
+ if (m_finish_cb) {
+ PyObject* args = Py_BuildValue("(O)", GetProxy());
+
+ if (!PyObject_Call(m_finish_cb, args, NULL)) {
+ PyErr_Print();
+ PyErr_Clear();
+ }
+
+ Py_DECREF(args);
+ }
+#endif
+}
+
+void KX_LibLoadStatus::RunProgressCallback()
+{
+// Progess callbacks are causing threading problems with Python, so they're disabled for now
+#if 0
+#ifdef WITH_PYTHON
+ if (m_progress_cb) {
+ //PyGILState_STATE gstate = PyGILState_Ensure();
+ PyObject* args = Py_BuildValue("(O)", GetProxy());
+
+ if (!PyObject_Call(m_progress_cb, args, NULL)) {
+ PyErr_Print();
+ PyErr_Clear();
+ }
+
+ Py_DECREF(args);
+ //PyGILState_Release(gstate);
+ }
+#endif
+#endif
+}
+
+class KX_BlenderSceneConverter *KX_LibLoadStatus::GetConverter()
+{
+ return m_converter;
+}
+
+class KX_KetsjiEngine *KX_LibLoadStatus::GetEngine()
+{
+ return m_engine;
+}
+
+class KX_Scene *KX_LibLoadStatus::GetMergeScene()
+{
+ return m_mergescene;
+}
+
+void KX_LibLoadStatus::SetLibName(const char *name)
+{
+ m_libname = name;
+}
+
+const char *KX_LibLoadStatus::GetLibName()
+{
+ return m_libname;
+}
+
+void KX_LibLoadStatus::SetData(void *data)
+{
+ m_data = data;
+}
+
+void *KX_LibLoadStatus::GetData()
+{
+ return m_data;
+}
+
+void KX_LibLoadStatus::SetProgress(float progress)
+{
+ m_progress = progress;
+ RunProgressCallback();
+}
+
+float KX_LibLoadStatus::GetProgress()
+{
+ return m_progress;
+}
+
+void KX_LibLoadStatus::AddProgress(float progress)
+{
+ m_progress += progress;
+ RunProgressCallback();
+}
+
+#ifdef WITH_PYTHON
+
+PyMethodDef KX_LibLoadStatus::Methods[] =
+{
+ {NULL} //Sentinel
+};
+
+PyAttributeDef KX_LibLoadStatus::Attributes[] = {
+ KX_PYATTRIBUTE_RW_FUNCTION("onFinish", KX_LibLoadStatus, pyattr_get_onfinish, pyattr_set_onfinish),
+ // KX_PYATTRIBUTE_RW_FUNCTION("onProgress", KX_LibLoadStatus, pyattr_get_onprogress, pyattr_set_onprogress),
+ KX_PYATTRIBUTE_FLOAT_RO("progress", KX_LibLoadStatus, m_progress),
+ KX_PYATTRIBUTE_STRING_RO("libraryName", KX_LibLoadStatus, m_libname),
+ KX_PYATTRIBUTE_RO_FUNCTION("timeTaken", KX_LibLoadStatus, pyattr_get_timetaken),
+ { NULL } //Sentinel
+};
+
+PyTypeObject KX_LibLoadStatus::Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "KX_LibLoadStatus",
+ sizeof(PyObjectPlus_Proxy),
+ 0,
+ py_base_dealloc,
+ 0,
+ 0,
+ 0,
+ 0,
+ py_base_repr,
+ 0,0,0,0,0,0,0,0,0,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ 0,0,0,0,0,0,0,
+ Methods,
+ 0,
+ 0,
+ &PyObjectPlus::Type,
+ 0,0,0,0,0,0,
+ py_base_new
+};
+
+
+PyObject* KX_LibLoadStatus::pyattr_get_onfinish(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+ KX_LibLoadStatus* self = static_cast<KX_LibLoadStatus*>(self_v);
+
+ if (self->m_finish_cb) {
+ Py_INCREF(self->m_finish_cb);
+ return self->m_finish_cb;
+ }
+
+ Py_RETURN_NONE;
+}
+
+int KX_LibLoadStatus::pyattr_set_onfinish(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef, PyObject *value)
+{
+ KX_LibLoadStatus* self = static_cast<KX_LibLoadStatus*>(self_v);
+
+ if (!PyCallable_Check(value)) {
+ PyErr_SetString(PyExc_TypeError, "KX_LibLoadStatus.onFinished requires a callable object");
+ return PY_SET_ATTR_FAIL;
+ }
+
+ if (self->m_finish_cb)
+ Py_DECREF(self->m_finish_cb);
+
+ Py_INCREF(value);
+ self->m_finish_cb = value;
+
+ return PY_SET_ATTR_SUCCESS;
+}
+
+PyObject* KX_LibLoadStatus::pyattr_get_onprogress(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+ KX_LibLoadStatus* self = static_cast<KX_LibLoadStatus*>(self_v);
+
+ if (self->m_progress_cb) {
+ Py_INCREF(self->m_progress_cb);
+ return self->m_progress_cb;
+ }
+
+ Py_RETURN_NONE;
+}
+
+int KX_LibLoadStatus::pyattr_set_onprogress(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef, PyObject *value)
+{
+ KX_LibLoadStatus* self = static_cast<KX_LibLoadStatus*>(self_v);
+
+ if (!PyCallable_Check(value)) {
+ PyErr_SetString(PyExc_TypeError, "KX_LibLoadStatus.onProgress requires a callable object");
+ return PY_SET_ATTR_FAIL;
+ }
+
+ if (self->m_progress_cb)
+ Py_DECREF(self->m_progress_cb);
+
+ Py_INCREF(value);
+ self->m_progress_cb = value;
+
+ return PY_SET_ATTR_SUCCESS;
+}
+
+PyObject* KX_LibLoadStatus::pyattr_get_timetaken(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+ KX_LibLoadStatus* self = static_cast<KX_LibLoadStatus*>(self_v);
+
+ return PyFloat_FromDouble(self->m_endtime - self->m_starttime);
+}
+#endif // WITH_PYTHON
diff --git a/source/gameengine/Converter/KX_LibLoadStatus.h b/source/gameengine/Converter/KX_LibLoadStatus.h
new file mode 100644
index 00000000000..3da7329213b
--- /dev/null
+++ b/source/gameengine/Converter/KX_LibLoadStatus.h
@@ -0,0 +1,85 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor(s): Mitchell Stokes
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file KX_LibLoadStatus.h
+ * \ingroup bgeconv
+ */
+
+#ifndef __KX_LIBLOADSTATUS_H__
+#define __KX_LIBLOADSTATUS_H__
+
+#include "PyObjectPlus.h"
+
+class KX_LibLoadStatus : public PyObjectPlus
+{
+ Py_Header
+private:
+ class KX_BlenderSceneConverter* m_converter;
+ class KX_KetsjiEngine* m_engine;
+ class KX_Scene* m_mergescene;
+ void* m_data;
+ STR_String m_libname;
+
+ float m_progress;
+ double m_starttime;
+ double m_endtime;
+
+#ifdef WITH_PYTHON
+ PyObject* m_finish_cb;
+ PyObject* m_progress_cb;
+#endif
+
+public:
+ KX_LibLoadStatus(class KX_BlenderSceneConverter* kx_converter,
+ class KX_KetsjiEngine* kx_engine,
+ class KX_Scene* merge_scene,
+ const char *path);
+
+ void Finish(); // Called when the libload is done
+ void RunFinishCallback();
+ void RunProgressCallback();
+
+ class KX_BlenderSceneConverter *GetConverter();
+ class KX_KetsjiEngine *GetEngine();
+ class KX_Scene *GetMergeScene();
+
+ void SetLibName(const char *name);
+ const char *GetLibName();
+
+ void SetData(void *data);
+ void *GetData();
+
+ void SetProgress(float progress);
+ float GetProgress();
+ void AddProgress(float progress);
+
+#ifdef WITH_PYTHON
+ static PyObject* pyattr_get_onfinish(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef);
+ static int pyattr_set_onfinish(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef, PyObject *value);
+ static PyObject* pyattr_get_onprogress(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef);
+ static int pyattr_set_onprogress(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef, PyObject *value);
+
+ static PyObject* pyattr_get_timetaken(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef);
+#endif
+};
+
+#endif // __KX_LIBLOADSTATUS_H__