Skip to content

Commit e1ea342

Browse files
committed
Enable Python in background threads. Fixed drawing of plugin layers.
1 parent 2bece09 commit e1ea342

File tree

4 files changed

+60
-0
lines changed

4 files changed

+60
-0
lines changed

python/core/qgspluginlayer.sip

+11
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,15 @@ class QgsPluginLayer : QgsMapLayer
1717
//! (defult implementation returns nothing)
1818
//! @note Added in v2.1
1919
virtual QgsLegendSymbologyList legendSymbologyItems( const QSize& iconSize );
20+
21+
/** Return new instance of QgsMapLayerRenderer that will be used for rendering of given context
22+
*
23+
* The default implementation returns map layer renderer which just calls draw().
24+
* This may work, but it is unsafe for multi-threaded rendering because of the run
25+
* conditions that may happen (e.g. something is changed in the layer while it is
26+
* being rendered).
27+
*
28+
* @note added in 2.1
29+
*/
30+
virtual QgsMapLayerRenderer* createMapRenderer( QgsRenderContext& rendererContext ) /Factory/;
2031
};

src/core/qgspluginlayer.cpp

+27
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
***************************************************************************/
1515
#include "qgspluginlayer.h"
1616

17+
#include "qgsmaplayerrenderer.h"
18+
1719
QgsPluginLayer::QgsPluginLayer( QString layerType, QString layerName )
1820
: QgsMapLayer( PluginLayer, layerName ), mPluginLayerType( layerType )
1921
{
@@ -34,3 +36,28 @@ QgsLegendSymbologyList QgsPluginLayer::legendSymbologyItems( const QSize& iconSi
3436
Q_UNUSED( iconSize );
3537
return QgsLegendSymbologyList();
3638
}
39+
40+
/** Fallback layer renderer implementation for layer that do not support map renderer yet. */
41+
class QgsPluginLayerRenderer : public QgsMapLayerRenderer
42+
{
43+
public:
44+
QgsPluginLayerRenderer( QgsPluginLayer* layer, QgsRenderContext& rendererContext )
45+
: QgsMapLayerRenderer( layer->id() )
46+
, mLayer( layer )
47+
, mRendererContext( rendererContext )
48+
{}
49+
50+
virtual bool render()
51+
{
52+
return mLayer->draw( mRendererContext );
53+
}
54+
55+
protected:
56+
QgsPluginLayer* mLayer;
57+
QgsRenderContext& mRendererContext;
58+
};
59+
60+
QgsMapLayerRenderer* QgsPluginLayer::createMapRenderer( QgsRenderContext& rendererContext )
61+
{
62+
return new QgsPluginLayerRenderer( this, rendererContext );
63+
}

src/core/qgspluginlayer.h

+11
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ class CORE_EXPORT QgsPluginLayer : public QgsMapLayer
4646
//! @note Added in v2.1
4747
virtual QgsLegendSymbologyList legendSymbologyItems( const QSize& iconSize );
4848

49+
/** Return new instance of QgsMapLayerRenderer that will be used for rendering of given context
50+
*
51+
* The default implementation returns map layer renderer which just calls draw().
52+
* This may work, but it is unsafe for multi-threaded rendering because of the run
53+
* conditions that may happen (e.g. something is changed in the layer while it is
54+
* being rendered).
55+
*
56+
* @note added in 2.1
57+
*/
58+
virtual QgsMapLayerRenderer* createMapRenderer( QgsRenderContext& rendererContext );
59+
4960
protected:
5061
QString mPluginLayerType;
5162
};

src/python/qgspythonutilsimpl.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <QStringList>
3434
#include <QDir>
3535

36+
PyThreadState* _mainState;
3637

3738
QgsPythonUtilsImpl::QgsPythonUtilsImpl()
3839
{
@@ -53,6 +54,9 @@ void QgsPythonUtilsImpl::initPython( QgisInterface* interface )
5354
// initialize python
5455
Py_Initialize();
5556

57+
// initialize threading AND acquire GIL
58+
PyEval_InitThreads();
59+
5660
mPythonEnabled = true;
5761

5862
mMainModule = PyImport_AddModule( "__main__" ); // borrowed reference
@@ -167,6 +171,13 @@ void QgsPythonUtilsImpl::initPython( QgisInterface* interface )
167171

168172
QString startuppath = homePythonPath() + " + \"/startup.py\"";
169173
runString( "if os.path.exists(" + startuppath + "): from startup import *\n" );
174+
175+
// release GIL!
176+
// Later on, we acquire GIL just before doing some Python calls and
177+
// release GIL again when the work with Python API is done.
178+
// (i.e. there must be PyGILState_Ensure + PyGILState_Release pair
179+
// around any calls to Python API, otherwise we may segfault!)
180+
_mainState = PyEval_SaveThread();
170181
}
171182

172183
void QgsPythonUtilsImpl::exitPython()

0 commit comments

Comments
 (0)