mirror of
https://github.com/OPM/ResInsight.git
synced 2025-02-25 18:55:39 -06:00
#9588 Accumulate N cells for AABB tree
This commit is contained in:
@@ -104,7 +104,7 @@ void RiaConsoleApplication::initialize()
|
||||
RiaApplication::initialize();
|
||||
|
||||
RiaLogging::setLoggerInstance( std::make_unique<RiaStdOutLogger>() );
|
||||
RiaLogging::loggerInstance()->setLevel( int( RILogLevel::RI_LL_DEBUG ) );
|
||||
RiaLogging::loggerInstance()->setLevel( int( RiaLogging::logLevelBasedOnPreferences() ) );
|
||||
|
||||
m_socketServer = new RiaSocketServer( this );
|
||||
}
|
||||
|
@@ -434,7 +434,8 @@ void RiaGuiApplication::initialize()
|
||||
logger->addMessagePanel( m_mainWindow->messagePanel() );
|
||||
logger->addMessagePanel( m_mainPlotWindow->messagePanel() );
|
||||
RiaLogging::setLoggerInstance( std::move( logger ) );
|
||||
RiaLogging::loggerInstance()->setLevel( int( RILogLevel::RI_LL_DEBUG ) );
|
||||
|
||||
RiaLogging::loggerInstance()->setLevel( int( RiaLogging::logLevelBasedOnPreferences() ) );
|
||||
}
|
||||
m_socketServer = new RiaSocketServer( this );
|
||||
}
|
||||
|
@@ -187,6 +187,16 @@ void RiaLogging::setLoggerInstance( std::unique_ptr<RiaLogger> loggerInstance )
|
||||
sm_logger = std::move( loggerInstance );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RILogLevel RiaLogging::logLevelBasedOnPreferences()
|
||||
{
|
||||
if ( RiaApplication::enableDevelopmentFeatures() ) return RILogLevel::RI_LL_DEBUG;
|
||||
|
||||
return RILogLevel::RI_LL_INFO;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
@@ -63,6 +63,8 @@ public:
|
||||
static RiaLogger* loggerInstance();
|
||||
static void setLoggerInstance( std::unique_ptr<RiaLogger> loggerInstance );
|
||||
|
||||
static RILogLevel logLevelBasedOnPreferences();
|
||||
|
||||
static void error( const QString& message );
|
||||
static void warning( const QString& message );
|
||||
static void info( const QString& message );
|
||||
|
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "RimEclipseCase.h"
|
||||
|
||||
#include "RiaApplication.h"
|
||||
#include "RiaColorTables.h"
|
||||
#include "RiaDefines.h"
|
||||
#include "RiaFieldHandleTools.h"
|
||||
@@ -655,7 +656,7 @@ void RimEclipseCase::computeCachedData()
|
||||
std::string aabbTreeInfo;
|
||||
rigEclipseCase->mainGrid()->computeCachedData( &aabbTreeInfo );
|
||||
|
||||
RiaLogging::info( QString::fromStdString( aabbTreeInfo ) );
|
||||
RiaLogging::debug( QString::fromStdString( aabbTreeInfo ) );
|
||||
}
|
||||
|
||||
{
|
||||
|
@@ -267,8 +267,18 @@ void RigMainGrid::computeCachedData( std::string* aabbTreeInfo )
|
||||
initAllSubCellsMainGridCellIndex();
|
||||
|
||||
m_cellSearchTree = nullptr;
|
||||
buildCellSearchTree();
|
||||
if ( aabbTreeInfo ) *aabbTreeInfo = m_cellSearchTree->info();
|
||||
|
||||
const double maxNumberOfLeafNodes = 4000000;
|
||||
const double factor = std::ceil( cellCount() / maxNumberOfLeafNodes );
|
||||
const size_t cellsPerBoundingBox = std::max( size_t( 1 ), static_cast<size_t>( factor ) );
|
||||
|
||||
buildCellSearchTreeOptimized( cellsPerBoundingBox );
|
||||
|
||||
if ( aabbTreeInfo )
|
||||
{
|
||||
*aabbTreeInfo += "Cells per bounding box : " + std::to_string( cellsPerBoundingBox ) + "\n";
|
||||
*aabbTreeInfo += m_cellSearchTree->info();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -779,14 +789,10 @@ void RigMainGrid::buildCellSearchTree()
|
||||
const std::array<size_t, 8>& cellIndices = m_cells[cIdx].cornerIndices();
|
||||
|
||||
cvf::BoundingBox cellBB;
|
||||
cellBB.add( m_nodes[cellIndices[0]] );
|
||||
cellBB.add( m_nodes[cellIndices[1]] );
|
||||
cellBB.add( m_nodes[cellIndices[2]] );
|
||||
cellBB.add( m_nodes[cellIndices[3]] );
|
||||
cellBB.add( m_nodes[cellIndices[4]] );
|
||||
cellBB.add( m_nodes[cellIndices[5]] );
|
||||
cellBB.add( m_nodes[cellIndices[6]] );
|
||||
cellBB.add( m_nodes[cellIndices[7]] );
|
||||
for ( size_t i : cellIndices )
|
||||
{
|
||||
cellBB.add( m_nodes[i] );
|
||||
}
|
||||
|
||||
if ( cellBB.isValid() )
|
||||
{
|
||||
@@ -812,6 +818,97 @@ void RigMainGrid::buildCellSearchTree()
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RigMainGrid::buildCellSearchTreeOptimized( size_t cellsPerBoundingBox )
|
||||
{
|
||||
int threadCount = RiaOpenMPTools::availableThreadCount();
|
||||
|
||||
std::vector<std::vector<std::vector<int>>> threadCellIndicesForBoundingBoxes( threadCount );
|
||||
std::vector<std::vector<cvf::BoundingBox>> threadCellBoundingBoxes( threadCount );
|
||||
|
||||
#pragma omp parallel
|
||||
{
|
||||
int myThread = RiaOpenMPTools::currentThreadIndex();
|
||||
|
||||
#pragma omp for
|
||||
for ( int i = 0; i < static_cast<int>( cellCountI() ); i++ )
|
||||
{
|
||||
for ( size_t j = 0; j < cellCountJ(); j++ )
|
||||
{
|
||||
size_t k = 0;
|
||||
while ( k < cellCountK() )
|
||||
{
|
||||
size_t kCount = 0;
|
||||
|
||||
std::vector<int> aggregatedCellIndices;
|
||||
cvf::BoundingBox accumulatedBB;
|
||||
|
||||
while ( ( kCount < cellsPerBoundingBox ) && ( k + kCount < cellCountK() ) )
|
||||
{
|
||||
size_t cellIdx = cellIndexFromIJK( i, j, k + kCount );
|
||||
const auto& rigCell = cell( cellIdx );
|
||||
|
||||
if ( !rigCell.isInvalid() )
|
||||
{
|
||||
aggregatedCellIndices.push_back( static_cast<int>( cellIdx ) );
|
||||
|
||||
// Add all cells in sub grid contained in this main grid cell
|
||||
if ( auto subGrid = rigCell.subGrid() )
|
||||
{
|
||||
for ( size_t localIdx = 0; localIdx < subGrid->cellCount(); localIdx++ )
|
||||
{
|
||||
const auto& localCell = subGrid->cell( localIdx );
|
||||
if ( localCell.mainGridCellIndex() == cellIdx )
|
||||
{
|
||||
aggregatedCellIndices.push_back(
|
||||
static_cast<int>( subGrid->reservoirCellIndex( localIdx ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::array<size_t, 8>& cellIndices = rigCell.cornerIndices();
|
||||
|
||||
cvf::BoundingBox cellBB;
|
||||
for ( size_t i : cellIndices )
|
||||
{
|
||||
cellBB.add( m_nodes[i] );
|
||||
}
|
||||
|
||||
if ( cellBB.isValid() ) accumulatedBB.add( cellBB );
|
||||
}
|
||||
kCount++;
|
||||
}
|
||||
|
||||
k += kCount;
|
||||
kCount = 0;
|
||||
|
||||
threadCellIndicesForBoundingBoxes[myThread].emplace_back( aggregatedCellIndices );
|
||||
threadCellBoundingBoxes[myThread].emplace_back( accumulatedBB );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<int>> cellIndicesForBoundingBoxes;
|
||||
std::vector<cvf::BoundingBox> cellBoundingBoxes;
|
||||
|
||||
for ( auto i = 0; i < threadCount; i++ )
|
||||
{
|
||||
cellIndicesForBoundingBoxes.insert( cellIndicesForBoundingBoxes.end(),
|
||||
threadCellIndicesForBoundingBoxes[i].begin(),
|
||||
threadCellIndicesForBoundingBoxes[i].end() );
|
||||
|
||||
cellBoundingBoxes.insert( cellBoundingBoxes.end(),
|
||||
threadCellBoundingBoxes[i].begin(),
|
||||
threadCellBoundingBoxes[i].end() );
|
||||
}
|
||||
|
||||
m_cellSearchTree = new cvf::BoundingBoxTree;
|
||||
m_cellSearchTree->buildTreeFromBoundingBoxesOptimized( cellBoundingBoxes, cellIndicesForBoundingBoxes );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
@@ -116,6 +116,7 @@ public:
|
||||
private:
|
||||
void initAllSubCellsMainGridCellIndex();
|
||||
void buildCellSearchTree();
|
||||
void buildCellSearchTreeOptimized( size_t cellsPerBoundingBox );
|
||||
bool hasFaultWithName( const QString& name ) const;
|
||||
|
||||
static std::array<double, 6> defaultMapAxes();
|
||||
|
@@ -753,14 +753,11 @@ private:
|
||||
const std::array<size_t, 8>& cellIndices = wellCell.cornerIndices();
|
||||
|
||||
cvf::BoundingBox& cellBB = m_cellBoundingBoxes[cIdx];
|
||||
cellBB.add( nodes[cellIndices[0]] );
|
||||
cellBB.add( nodes[cellIndices[1]] );
|
||||
cellBB.add( nodes[cellIndices[2]] );
|
||||
cellBB.add( nodes[cellIndices[3]] );
|
||||
cellBB.add( nodes[cellIndices[4]] );
|
||||
cellBB.add( nodes[cellIndices[5]] );
|
||||
cellBB.add( nodes[cellIndices[6]] );
|
||||
cellBB.add( nodes[cellIndices[7]] );
|
||||
|
||||
for ( size_t i : cellIndices )
|
||||
{
|
||||
cellBB.add( nodes[i] );
|
||||
}
|
||||
}
|
||||
|
||||
m_cellSearchTree.buildTreeFromBoundingBoxes( m_cellBoundingBoxes, nullptr );
|
||||
|
@@ -134,10 +134,11 @@ namespace cvf {
|
||||
public:
|
||||
AABBTreeNodeLeaf();
|
||||
|
||||
size_t index() const;
|
||||
void setIndex(size_t index);
|
||||
std::vector<int> ids() const;
|
||||
void setIds(const std::vector<int>& ids);
|
||||
|
||||
private:
|
||||
size_t m_index; ///< An index of the leaf node. The interpretation of this index is depending on which tree the node is in.
|
||||
std::vector<int> m_ids; ///< An list of IDs of the leaf node. The interpretation of these values depends on which tree the node is in.
|
||||
};
|
||||
|
||||
//=================================================================================================================================
|
||||
@@ -185,7 +186,7 @@ namespace cvf {
|
||||
bool intersect(const AABBTreeNode* pA, const AABBTreeNode* pB) const;
|
||||
|
||||
AABBTreeNodeInternal* createNode();
|
||||
AABBTreeNodeLeaf* createOrAssignLeaf(size_t leafIndex, size_t bbId);
|
||||
AABBTreeNodeLeaf* createOrAssignLeaf(size_t leafIndex, const std::vector<int>& bbIds);
|
||||
|
||||
private:
|
||||
static void deleteInternalNodesBottomUp(AABBTreeNode* node);
|
||||
@@ -218,6 +219,8 @@ namespace cvf {
|
||||
class BoundingBoxTreeImpl : public AABBTree
|
||||
{
|
||||
BoundingBoxTreeImpl() {}
|
||||
|
||||
void buildTree(const std::vector<cvf::BoundingBox>& boundingBoxes, const std::vector<std::vector<int>>& ids);
|
||||
|
||||
private:
|
||||
friend class BoundingBoxTree;
|
||||
@@ -225,10 +228,10 @@ namespace cvf {
|
||||
cvf::BoundingBox createLeaves();
|
||||
void findIntersections(const cvf::BoundingBox& bb, std::vector<size_t>& bbIds) const;
|
||||
|
||||
void findIntersections(const cvf::BoundingBox& bb, const AABBTreeNode* node, std::vector<size_t>& indices) const;
|
||||
void findIntersections(const cvf::BoundingBox& bb, const AABBTreeNode* node, std::vector<size_t>& ids) const;
|
||||
|
||||
std::vector<cvf::BoundingBox> m_validBoundingBoxes;
|
||||
std::vector<size_t> m_validOptionalBoundingBoxIds;
|
||||
std::vector<std::vector<int>> m_validOptionalBoundingBoxIds;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -411,25 +414,23 @@ void AABBTreeNodeInternal::setRight(AABBTreeNode* right)
|
||||
AABBTreeNodeLeaf::AABBTreeNodeLeaf()
|
||||
{
|
||||
m_type = AB_LEAF;
|
||||
|
||||
m_index = std::numeric_limits<size_t>::max();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
size_t AABBTreeNodeLeaf::index() const
|
||||
std::vector<int> AABBTreeNodeLeaf::ids() const
|
||||
{
|
||||
return m_index;
|
||||
return m_ids;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void AABBTreeNodeLeaf::setIndex(size_t index)
|
||||
void AABBTreeNodeLeaf::setIds(const std::vector<int>& ids)
|
||||
{
|
||||
m_index = index;
|
||||
m_ids = ids;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -733,11 +734,6 @@ std::string AABBTree::treeInfo() const
|
||||
text += " Avg : " + std::to_string(iAvgHeigth) + "\n";
|
||||
text += " Ideal : " + std::to_string(iIdealHeigth) + "\n";
|
||||
|
||||
|
||||
cvf::BoundingBox bb;
|
||||
boundingBox(&bb);
|
||||
text += bb.debugString().toStdString();
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -782,10 +778,10 @@ cvf::AABBTreeNodeInternal* AABBTree::createNode()
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
cvf::AABBTreeNodeLeaf* AABBTree::createOrAssignLeaf(size_t leafIndex, size_t bbId)
|
||||
cvf::AABBTreeNodeLeaf* AABBTree::createOrAssignLeaf(size_t leafIndex, const std::vector<int>& bbIds)
|
||||
{
|
||||
cvf::AABBTreeNodeLeaf* leaf = &m_leafPool[leafIndex];
|
||||
leaf->setIndex(bbId);
|
||||
leaf->setIds(bbIds);
|
||||
return leaf;
|
||||
}
|
||||
|
||||
@@ -807,6 +803,22 @@ void AABBTree::deleteInternalNodesBottomUp(AABBTreeNode* node)
|
||||
delete internalNode;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void BoundingBoxTreeImpl::buildTree(const std::vector<cvf::BoundingBox>& boundingBoxes, const std::vector<std::vector<int>>& ids)
|
||||
{
|
||||
// Assign data used in the tree construction
|
||||
m_validBoundingBoxes = boundingBoxes;
|
||||
m_validOptionalBoundingBoxIds = ids;
|
||||
|
||||
AABBTree::buildTree();
|
||||
|
||||
// Release the memory used by the bounding boxes and ids, as this information is now distributed in the tree
|
||||
m_validBoundingBoxes.clear();
|
||||
m_validOptionalBoundingBoxIds.clear();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Creates leafs for the supplied valid bounding boxes, keeping the original index
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -823,10 +835,10 @@ cvf::BoundingBox BoundingBoxTreeImpl::createLeaves()
|
||||
#pragma omp for
|
||||
for (int i = 0; i < (int)m_validBoundingBoxes.size(); i++)
|
||||
{
|
||||
size_t bbId = i;
|
||||
if (!m_validOptionalBoundingBoxIds.empty()) bbId = m_validOptionalBoundingBoxIds[i];
|
||||
std::vector<int> bbIds = {i};
|
||||
if (!m_validOptionalBoundingBoxIds.empty()) bbIds = m_validOptionalBoundingBoxIds[i];
|
||||
|
||||
AABBTreeNodeLeaf* leaf = createOrAssignLeaf(i, bbId);
|
||||
AABBTreeNodeLeaf* leaf = createOrAssignLeaf(i, bbIds);
|
||||
|
||||
leaf->setBoundingBox(m_validBoundingBoxes[i]);
|
||||
|
||||
@@ -858,7 +870,7 @@ void BoundingBoxTreeImpl::findIntersections(const cvf::BoundingBox& bb, std::vec
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void BoundingBoxTreeImpl::findIntersections(const cvf::BoundingBox& bb, const AABBTreeNode* node, std::vector<size_t>& cvIndices) const
|
||||
void BoundingBoxTreeImpl::findIntersections(const cvf::BoundingBox& bb, const AABBTreeNode* node, std::vector<size_t>& ids) const
|
||||
{
|
||||
CVF_TIGHT_ASSERT(bb.isValid());
|
||||
|
||||
@@ -868,7 +880,9 @@ void BoundingBoxTreeImpl::findIntersections(const cvf::BoundingBox& bb, const AA
|
||||
{
|
||||
const AABBTreeNodeLeaf* leaf = static_cast<const AABBTreeNodeLeaf*>(node);
|
||||
{
|
||||
cvIndices.push_back(leaf->index());
|
||||
auto leafIds = leaf->ids();
|
||||
|
||||
ids.insert(ids.end(), leafIds.begin(), leafIds.end());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -876,8 +890,8 @@ void BoundingBoxTreeImpl::findIntersections(const cvf::BoundingBox& bb, const AA
|
||||
{
|
||||
const AABBTreeNodeInternal* internalNode = static_cast<const AABBTreeNodeInternal*>(node);
|
||||
|
||||
findIntersections(bb, internalNode->left(), cvIndices);
|
||||
findIntersections(bb, internalNode->right(), cvIndices);
|
||||
findIntersections(bb, internalNode->left(), ids);
|
||||
findIntersections(bb, internalNode->right(), ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -908,23 +922,50 @@ void BoundingBoxTree::buildTreeFromBoundingBoxes(const std::vector<cvf::Bounding
|
||||
{
|
||||
if (optionalBoundingBoxIds) CVF_ASSERT(boundingBoxes.size() == optionalBoundingBoxIds->size());
|
||||
|
||||
m_implTree->m_validBoundingBoxes.clear();
|
||||
m_implTree->m_validBoundingBoxes.reserve(boundingBoxes.size());
|
||||
if (optionalBoundingBoxIds)
|
||||
m_implTree->m_validOptionalBoundingBoxIds.reserve(optionalBoundingBoxIds->size());
|
||||
std::vector<cvf::BoundingBox> validBoundingBoxes;
|
||||
std::vector<std::vector<int>> validOptionalBoundingBoxIds;
|
||||
|
||||
validBoundingBoxes.reserve(boundingBoxes.size());
|
||||
if (optionalBoundingBoxIds)
|
||||
validOptionalBoundingBoxIds.reserve(optionalBoundingBoxIds->size());
|
||||
|
||||
for (size_t i = 0; i < boundingBoxes.size(); ++i)
|
||||
{
|
||||
if (boundingBoxes[i].isValid())
|
||||
{
|
||||
validBoundingBoxes.push_back(boundingBoxes[i]);
|
||||
if (optionalBoundingBoxIds)
|
||||
{
|
||||
const auto& id = (*optionalBoundingBoxIds)[i];
|
||||
|
||||
std::vector<int> ids = {static_cast<int>(id)};
|
||||
validOptionalBoundingBoxIds.push_back(ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_implTree->buildTree(validBoundingBoxes, validOptionalBoundingBoxIds);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void BoundingBoxTree::buildTreeFromBoundingBoxesOptimized(const std::vector<cvf::BoundingBox>& boundingBoxes,
|
||||
const std::vector<std::vector<int>>& optionalBoundingBoxIds)
|
||||
{
|
||||
std::vector<cvf::BoundingBox> validBoundingBoxes;
|
||||
std::vector<std::vector<int>> validOptionalBoundingBoxIds;
|
||||
|
||||
for (int i = 0; i < (int)boundingBoxes.size(); ++i)
|
||||
{
|
||||
if (boundingBoxes[i].isValid())
|
||||
{
|
||||
m_implTree->m_validBoundingBoxes.push_back(boundingBoxes[i]);
|
||||
if (optionalBoundingBoxIds)
|
||||
{
|
||||
m_implTree->m_validOptionalBoundingBoxIds.push_back((*optionalBoundingBoxIds)[i]);
|
||||
}
|
||||
validBoundingBoxes.push_back(boundingBoxes[i]);
|
||||
validOptionalBoundingBoxIds.push_back(optionalBoundingBoxIds[i]);
|
||||
}
|
||||
}
|
||||
m_implTree->buildTree();
|
||||
|
||||
m_implTree->buildTree(validBoundingBoxes, validOptionalBoundingBoxIds);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
@@ -57,7 +57,10 @@ public:
|
||||
|
||||
void buildTreeFromBoundingBoxes(const std::vector<cvf::BoundingBox>& boundingBoxes,
|
||||
const std::vector<size_t>* optionalBoundingBoxIds);
|
||||
|
||||
|
||||
void buildTreeFromBoundingBoxesOptimized(const std::vector<cvf::BoundingBox>& boundingBoxes,
|
||||
const std::vector<std::vector<int>>& optionalBoundingBoxIds);
|
||||
|
||||
void findIntersections(const cvf::BoundingBox& inputBB, std::vector<size_t>* bbIdsOrIndexesIntersected) const;
|
||||
|
||||
std::string info() const;
|
||||
|
@@ -17,8 +17,6 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma once
|
||||
|
||||
#include "RiaLogging.h"
|
||||
|
||||
#include <QString>
|
||||
#include <grpc/support/log.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
|
Reference in New Issue
Block a user