Add API framework to handle temporary layers generated by previous runs

We need special handling to transfer temporary layers generated
by a model execution when running a partial model from a previous state
This commit is contained in:
Nyall Dawson 2024-04-22 12:05:24 +10:00
parent 20d9657f19
commit 83c7ec07d6
4 changed files with 228 additions and 0 deletions

View File

@ -0,0 +1,42 @@
/***************************************************************************
qgsprocessingmodelconfig.cpp
----------------------
begin : April 2024
copyright : (C) 2024 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#include "qgsprocessingmodelconfig.h"
#include "qgsmaplayerstore.h"
QgsProcessingModelInitialRunConfig::QgsProcessingModelInitialRunConfig() = default;
QgsProcessingModelInitialRunConfig::~QgsProcessingModelInitialRunConfig() = default;
QgsMapLayerStore *QgsProcessingModelInitialRunConfig::previousLayerStore()
{
return mModelInitialLayerStore.get();
}
std::unique_ptr<QgsMapLayerStore> QgsProcessingModelInitialRunConfig::takePreviousLayerStore()
{
return std::move( mModelInitialLayerStore );
}
void QgsProcessingModelInitialRunConfig::setPreviousLayerStore( std::unique_ptr<QgsMapLayerStore> store )
{
if ( store )
{
Q_ASSERT_X( !store->thread(), "QgsProcessingModelInitialRunConfig::setPreviousLayerStore", "store must have been pushed to a nullptr thread prior to calling this method" );
}
mModelInitialLayerStore = std::move( store );
}

View File

@ -0,0 +1,160 @@
/***************************************************************************
qgsprocessingmodelconfig.h
----------------------
begin : April 2024
copyright : (C) 2024 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef QGSPROCESSINGMODELCONFIG_H
#define QGSPROCESSINGMODELCONFIG_H
#include "qgis_core.h"
#include "qgis.h"
#include <QSet>
#define SIP_NO_FILE
class QgsMapLayerStore;
/**
* \ingroup core
* \brief Configuration settings which control how a Processing model is executed.
*
* \note Not available in Python bindings.
*
* \since QGIS 3.38
*/
class CORE_EXPORT QgsProcessingModelInitialRunConfig
{
public:
QgsProcessingModelInitialRunConfig();
~QgsProcessingModelInitialRunConfig();
/**
* Returns the subset of child algorithms to run (by child ID).
*
* An empty set indicates the entire model should be run.
*
* \see setChildAlgorithmSubset()
*/
QSet<QString> childAlgorithmSubset() const { return mChildAlgorithmSubset; }
/**
* Sets the \a subset of child algorithms to run (by child ID).
*
* An empty set indicates the entire model should be run.
*
* \see childAlgorithmSubset()
*/
void setChildAlgorithmSubset( const QSet<QString> &subset ) { mChildAlgorithmSubset = subset; }
/**
* Returns the map of child algorithm inputs to use as the initial state when running the model.
*
* Map keys refer to the child algorithm IDs.
*
* \see setInitialChildInputs()
*/
QVariantMap initialChildInputs() { return mInitialChildInputs; }
/**
* Sets the map of child algorithm \a inputs to use as the initial state when running the model.
*
* Map keys refer to the child algorithm IDs.
*
* \see initialChildInputs()
*/
void setInitialChildInputs( const QVariantMap &inputs ) { mInitialChildInputs = inputs; }
/**
* Returns the map of child algorithm outputs to use as the initial state when running the model.
*
* Map keys refer to the child algorithm IDs.
*
* \see setInitialChildOutputs()
*/
QVariantMap initialChildOutputs() { return mInitialChildOutputs; }
/**
* Sets the map of child algorithm \a outputs to use as the initial state when running the model.
*
* Map keys refer to the child algorithm IDs.
*
* \see initialChildOutputs()
*/
void setInitialChildOutputs( const QVariantMap &outputs ) { mInitialChildOutputs = outputs; }
/**
* Returns the set of previously executed child algorithm IDs to use as the initial state
* when running the model.
*
* \see setPreviouslyExecutedChildAlgorithms()
*/
QSet< QString > previouslyExecutedChildAlgorithms() const { return mPreviouslyExecutedChildren; }
/**
* Sets the previously executed child algorithm IDs to use as the initial state
* when running the model.
*
* \see previouslyExecutedChildAlgorithms()
*/
void setPreviouslyExecutedChildAlgorithms( const QSet< QString > &children ) { mPreviouslyExecutedChildren = children; }
/**
* Returns a reference to a map store containing copies of temporary layers generated
* during previous model executions.
*
* This may be NULLPTR.
*
* \see setPreviousLayerStore()
* \see takePreviousLayerStore()
*/
QgsMapLayerStore *previousLayerStore();
/**
* Takes the map store containing copies of temporary layers generated
* during previous model executions.
*
* May return NULLPTR if this is not available.
*
* \see previousLayerStore()
* \see setPreviousLayerStore()
*/
std::unique_ptr< QgsMapLayerStore > takePreviousLayerStore();
/**
* Sets the map store containing copies of temporary layers generated
* during previous model executions.
*
* \warning \a store must have previous been moved to a NULLPTR thread via a call
* to QObject::moveToThread. An assert will be triggered if this condition is not met.
*
* \see previousLayerStore()
* \see takePreviousLayerStore()
*/
void setPreviousLayerStore( std::unique_ptr< QgsMapLayerStore > store );
private:
QSet<QString> mChildAlgorithmSubset;
QVariantMap mInitialChildInputs;
QVariantMap mInitialChildOutputs;
QSet< QString > mPreviouslyExecutedChildren;
std::unique_ptr< QgsMapLayerStore > mModelInitialLayerStore;
};
#endif // QGSPROCESSINGMODELCONFIG_H

View File

@ -616,6 +616,14 @@ QVariantMap QgsProcessingAlgorithm::runPrepared( const QVariantMap &parameters,
std::unique_ptr< QgsProcessingModelInitialRunConfig > modelConfig = context.takeModelInitialRunConfig();
if ( modelConfig )
{
std::unique_ptr< QgsMapLayerStore > modelPreviousLayerStore = modelConfig->takePreviousLayerStore();
if ( modelPreviousLayerStore )
{
// move layers from previous layer store to context's temporary layer store, in a thread-safe way
Q_ASSERT_X( !modelPreviousLayerStore->thread(), "QgsProcessingAlgorithm::runPrepared", "QgsProcessingModelConfig::modelPreviousLayerStore must have been pushed to a nullptr thread" );
modelPreviousLayerStore->moveToThread( QThread::currentThread() );
runContext->temporaryLayerStore()->transferLayersFromStore( modelPreviousLayerStore.get() );
}
runContext->setModelInitialRunConfig( std::move( modelConfig ) );
}

View File

@ -2475,6 +2475,24 @@ void TestQgsProcessingModelAlgorithm::modelExecuteWithPreviousState()
QCOMPARE( firstResult.childResults().value( "calculate2" ).outputs().value( "OUTPUT" ).toString(), QStringLiteral( "a different string_2" ) );
QCOMPARE( firstResult.rawChildInputs().value( "calculate2" ).toMap().value( "INPUT" ).toString(), QStringLiteral( "a different string_2" ) );
QCOMPARE( firstResult.rawChildOutputs().value( "calculate2" ).toMap().value( "OUTPUT" ).toString(), QStringLiteral( "a different string_2" ) );
QCOMPARE( context.temporaryLayerStore()->count(), 0 );
// test handling of temporary layers generated during earlier runs
modelConfig = std::make_unique< QgsProcessingModelInitialRunConfig >();
std::unique_ptr < QgsMapLayerStore > previousStore = std::make_unique< QgsMapLayerStore >();
QgsVectorLayer *layer = new QgsVectorLayer( "Point?crs=epsg:3111", "v1", "memory" );
previousStore->addMapLayer( layer );
previousStore->moveToThread( nullptr );
modelConfig->setPreviousLayerStore( std::move( previousStore ) );
context.setModelInitialRunConfig( std::move( modelConfig ) );
m.run( params, context, &feedback, &ok );
QVERIFY( ok );
// layer should have been transferred to context's temporary layer store as part of model execution
QCOMPARE( context.temporaryLayerStore()->count(), 1 );
QCOMPARE( context.temporaryLayerStore()->mapLayersByName( QStringLiteral( "v1" ) ).at( 0 ), layer );
}
void TestQgsProcessingModelAlgorithm::modelDependencies()