diff options
-rw-r--r-- | doc/python_api/rst/bge.logic.rst | 10 | ||||
-rw-r--r-- | doc/python_api/rst/bge.types.rst | 38 | ||||
-rw-r--r-- | source/gameengine/Converter/BL_BlenderDataConversion.cpp | 10 | ||||
-rw-r--r-- | source/gameengine/Converter/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/gameengine/Converter/KX_BlenderSceneConverter.cpp | 136 | ||||
-rw-r--r-- | source/gameengine/Converter/KX_BlenderSceneConverter.h | 17 | ||||
-rw-r--r-- | source/gameengine/Converter/KX_LibLoadStatus.cpp | 251 | ||||
-rw-r--r-- | source/gameengine/Converter/KX_LibLoadStatus.h | 85 | ||||
-rw-r--r-- | source/gameengine/Ketsji/KX_ISceneConverter.h | 3 | ||||
-rw-r--r-- | source/gameengine/Ketsji/KX_KetsjiEngine.cpp | 2 | ||||
-rw-r--r-- | source/gameengine/Ketsji/KX_PythonInit.cpp | 20 | ||||
-rw-r--r-- | source/gameengine/Ketsji/KX_PythonInitTypes.cpp | 2 | ||||
-rw-r--r-- | source/gameengine/Ketsji/KX_Scene.cpp | 4 |
13 files changed, 553 insertions, 27 deletions
diff --git a/doc/python_api/rst/bge.logic.rst b/doc/python_api/rst/bge.logic.rst index ee39f7659ca..944b1ca06a0 100644 --- a/doc/python_api/rst/bge.logic.rst +++ b/doc/python_api/rst/bge.logic.rst @@ -176,7 +176,7 @@ General functions Restarts the current game by reloading the .blend file (the last saved version, not what is currently running). -.. function:: LibLoad(blend, type, data, load_actions=False, verbose=False, load_scripts=True) +.. function:: LibLoad(blend, type, data, load_actions=False, verbose=False, load_scripts=True, async=False) Converts the all of the datablocks of the given type from the given blend. @@ -191,7 +191,13 @@ General functions :arg verbose: Whether or not to print debugging information (e.g., "SceneName: Scene") :type verbose: bool :arg load_scripts: Whether or not to load text datablocks as well (can be disabled for some extra security) - :type load_scripts: bool + :type load_scripts: bool + :arg async: Whether or not to do the loading asynchronously (in another thread). Only the "Scene" type is currently supported for this feature. + :type async: bool + + :rtype: :class:`bge.types.KX_LibLoadStatus` + + .. note:: Asynchronously loaded libraries will not be available immediately after LibLoad() returns. Use the returned KX_LibLoadStatus to figure out when the libraries are ready. .. function:: LibNew(name, type, data) diff --git a/doc/python_api/rst/bge.types.rst b/doc/python_api/rst/bge.types.rst index 85429a00791..470e1c56bac 100644 --- a/doc/python_api/rst/bge.types.rst +++ b/doc/python_api/rst/bge.types.rst @@ -1929,6 +1929,44 @@ Types :type: boolean +.. class:: KX_LibLoadStatus(PyObjectPlus) + + An object providing information about a LibLoad() operation. + + .. code-block:: python + + # 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 + + .. attribute:: onFinish + + A callback that gets called when the lib load is done. + + :type: callable + + .. attribute:: progress + + The current progress of the lib load as a normalized value from 0.0 to 1.0. + + :type: float + + .. attribute:: libraryName + + The name of the library being loaded (the first argument to LibLoad). + + :type: string + + .. attribute:: timeTaken + + The amount of time, in seconds, the lib load took (0 until the operation is complete). + + :type: float + .. class:: KX_LightObject(KX_GameObject) A Light object. 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__ diff --git a/source/gameengine/Ketsji/KX_ISceneConverter.h b/source/gameengine/Ketsji/KX_ISceneConverter.h index 0dbfd7de2c6..7c1d593a81e 100644 --- a/source/gameengine/Ketsji/KX_ISceneConverter.h +++ b/source/gameengine/Ketsji/KX_ISceneConverter.h @@ -62,6 +62,9 @@ public: virtual void RemoveScene(class KX_Scene *scene)=0; + // handle any pending merges from asynchronous loads + virtual void MergeAsyncLoads()=0; + virtual void SetAlwaysUseExpandFraming(bool to_what) = 0; virtual void SetNewFileName(const STR_String& filename) = 0; diff --git a/source/gameengine/Ketsji/KX_KetsjiEngine.cpp b/source/gameengine/Ketsji/KX_KetsjiEngine.cpp index 890b9d4c472..6638b711a1b 100644 --- a/source/gameengine/Ketsji/KX_KetsjiEngine.cpp +++ b/source/gameengine/Ketsji/KX_KetsjiEngine.cpp @@ -592,6 +592,8 @@ bool KX_KetsjiEngine::NextFrame() m_frameTime += framestep; + m_sceneconverter->MergeAsyncLoads(); + for (sceneit = m_scenes.begin();sceneit != m_scenes.end(); ++sceneit) // for each scene, call the proceed functions { diff --git a/source/gameengine/Ketsji/KX_PythonInit.cpp b/source/gameengine/Ketsji/KX_PythonInit.cpp index df8f6eb44e8..0d39eb844b5 100644 --- a/source/gameengine/Ketsji/KX_PythonInit.cpp +++ b/source/gameengine/Ketsji/KX_PythonInit.cpp @@ -136,6 +136,7 @@ extern "C" { /* for converting new scenes */ #include "KX_BlenderSceneConverter.h" +#include "KX_LibLoadStatus.h" #include "KX_MeshProxy.h" /* for creating a new library of mesh objects */ extern "C" { #include "BKE_idcode.h" @@ -670,14 +671,15 @@ static PyObject *gLibLoad(PyObject *, PyObject *args, PyObject *kwds) Py_buffer py_buffer; py_buffer.buf = NULL; char *err_str= NULL; + KX_LibLoadStatus *status = NULL; short options=0; - int load_actions=0, verbose=0, load_scripts=1; + int load_actions=0, verbose=0, load_scripts=1, async=0; - static const char *kwlist[] = {"path", "group", "buffer", "load_actions", "verbose", "load_scripts", NULL}; + static const char *kwlist[] = {"path", "group", "buffer", "load_actions", "verbose", "load_scripts", "async", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss|y*iii:LibLoad", const_cast<char**>(kwlist), - &path, &group, &py_buffer, &load_actions, &verbose, &load_scripts)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss|y*iiIi:LibLoad", const_cast<char**>(kwlist), + &path, &group, &py_buffer, &load_actions, &verbose, &load_scripts, &async)) return NULL; /* setup options */ @@ -687,6 +689,8 @@ static PyObject *gLibLoad(PyObject *, PyObject *args, PyObject *kwds) options |= KX_BlenderSceneConverter::LIB_LOAD_VERBOSE; if (load_scripts != 0) options |= KX_BlenderSceneConverter::LIB_LOAD_LOAD_SCRIPTS; + if (async != 0) + options |= KX_BlenderSceneConverter::LIB_LOAD_ASYNC; if (!py_buffer.buf) { @@ -695,16 +699,16 @@ static PyObject *gLibLoad(PyObject *, PyObject *args, PyObject *kwds) BLI_strncpy(abs_path, path, sizeof(abs_path)); BLI_path_abs(abs_path, gp_GamePythonPath); - if (kx_scene->GetSceneConverter()->LinkBlendFilePath(abs_path, group, kx_scene, &err_str, options)) { - Py_RETURN_TRUE; + if ((status=kx_scene->GetSceneConverter()->LinkBlendFilePath(abs_path, group, kx_scene, &err_str, options))) { + return status->GetProxy(); } } else { - if (kx_scene->GetSceneConverter()->LinkBlendFileMemory(py_buffer.buf, py_buffer.len, path, group, kx_scene, &err_str, options)) { + if ((status=kx_scene->GetSceneConverter()->LinkBlendFileMemory(py_buffer.buf, py_buffer.len, path, group, kx_scene, &err_str, options))) { PyBuffer_Release(&py_buffer); - Py_RETURN_TRUE; + return status->GetProxy(); } PyBuffer_Release(&py_buffer); diff --git a/source/gameengine/Ketsji/KX_PythonInitTypes.cpp b/source/gameengine/Ketsji/KX_PythonInitTypes.cpp index 10c210cf16e..c6aa436537a 100644 --- a/source/gameengine/Ketsji/KX_PythonInitTypes.cpp +++ b/source/gameengine/Ketsji/KX_PythonInitTypes.cpp @@ -47,6 +47,7 @@ #include "KX_ConstraintActuator.h" #include "KX_ConstraintWrapper.h" #include "KX_GameActuator.h" +#include "KX_LibLoadStatus.h" #include "KX_Light.h" #include "KX_FontObject.h" #include "KX_MeshProxy.h" @@ -199,6 +200,7 @@ void initPyTypes(void) PyType_Ready_Attr(dict, KX_GameActuator, init_getset); PyType_Ready_Attr(dict, KX_GameObject, init_getset); PyType_Ready_Attr(dict, KX_IpoActuator, init_getset); + PyType_Ready_Attr(dict, KX_LibLoadStatus, init_getset); PyType_Ready_Attr(dict, KX_LightObject, init_getset); PyType_Ready_Attr(dict, KX_FontObject, init_getset); PyType_Ready_Attr(dict, KX_MeshProxy, init_getset); diff --git a/source/gameengine/Ketsji/KX_Scene.cpp b/source/gameengine/Ketsji/KX_Scene.cpp index 72be5f57b95..d298d1b4815 100644 --- a/source/gameengine/Ketsji/KX_Scene.cpp +++ b/source/gameengine/Ketsji/KX_Scene.cpp @@ -227,9 +227,13 @@ KX_Scene::KX_Scene(class SCA_IInputDevice* keyboarddevice, } #ifdef WITH_PYTHON + // We might be running in a separate thread (async libload) so + // try and grab the GIL to avoid issues + PyGILState_STATE gstate = PyGILState_Ensure(); m_attr_dict = PyDict_New(); /* new ref */ m_draw_call_pre = NULL; m_draw_call_post = NULL; + PyGILState_Release(gstate); #endif } |