协程架构重构成功, 下一步准备指针交换 无锁化重构
This commit is contained in:
@@ -77,6 +77,7 @@ struct AbsAxis::BuilderT : protected AbsAxis_Prop {
|
||||
SETTER_T(QString, layerName, "axis")
|
||||
protected:
|
||||
Plot *plot{};
|
||||
size_t pixelSize{};
|
||||
void set(AbsAxis* ret);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -135,19 +135,20 @@ namespace YSG {
|
||||
ret->init(plot, layerName);
|
||||
AbsAxisPrivate* pd = ret->d();
|
||||
AbsAxisRenderState* sc = pd->renderStateCache();
|
||||
PROP_RT(int, x)
|
||||
PROP_RT(int, y)
|
||||
PROP_RT(Qt::Orientation, orientation)
|
||||
PROP_RT(int, tickLength)
|
||||
PROP_RT(int, subTickLength)
|
||||
PROP_RT(QColor, color)
|
||||
PROP_RT(QChar, formatChar)
|
||||
PROP_RT(QLocale, locale)
|
||||
PROP_RT(QString, unitText)
|
||||
PROP_RT(QFont, unitTextFont)
|
||||
PROP_RT(QPen, unitTextPen)
|
||||
PROP_RT(QBrush, unitTextBackgroundBrush)
|
||||
PROP_RT(int, rotate) // 不旋转
|
||||
sc->x = this->x;
|
||||
sc->y = this->y;
|
||||
sc->orientation = this->orientation;
|
||||
sc->pixelSize = this->pixelSize;
|
||||
sc->tickLength = this->tickLength;
|
||||
sc->subTickLength = this->subTickLength;
|
||||
sc->color = this->color;
|
||||
sc->formatChar = this->formatChar;
|
||||
sc->locale = this->locale;
|
||||
sc->unitText = this->unitText;
|
||||
sc->unitTextFont = this->unitTextFont;
|
||||
sc->unitTextPen = this->unitTextPen;
|
||||
sc->unitTextBackgroundBrush = this->unitTextBackgroundBrush;
|
||||
sc->rotate = this->rotate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace YSG {
|
||||
bool selectTest(const QPointF& pos) override {return true;}
|
||||
void wheelEvent(QWheelEvent* event) override {
|
||||
if(useWheel) {
|
||||
SpinLockGuard _guard(&mBufferLock);
|
||||
RenderEditGuard _editGuard(this);
|
||||
const double delta = event->angleDelta().y();
|
||||
double wheelSteps = delta / 120.0;
|
||||
double factor = std::pow(0.8, wheelSteps);
|
||||
@@ -40,6 +42,7 @@ namespace YSG {
|
||||
if(!useDrag) return;
|
||||
if(event->button() != Qt::LeftButton) return;
|
||||
if(dragging) return;
|
||||
SpinLockGuard _guard(&mBufferLock);
|
||||
dragging = true;
|
||||
startGlobalPos = QCursor::pos();
|
||||
startPos = event->pos();
|
||||
@@ -51,6 +54,8 @@ namespace YSG {
|
||||
if(!useDrag) return;
|
||||
if(!(event->buttons() & Qt::LeftButton)) return;
|
||||
if(!dragging) return;
|
||||
SpinLockGuard _guard(&mBufferLock);
|
||||
RenderEditGuard _editGuard(this);
|
||||
QPoint delt = startGlobalPos - QCursor::pos();
|
||||
auto rc = renderStateCache();
|
||||
auto coordDelt = pixelPreCoord * (rc->orientation == Qt::Horizontal ? delt.x() :delt.y());
|
||||
|
||||
@@ -9,8 +9,11 @@ namespace YSG {
|
||||
PROP_P(TimeAxis, QFont, font)
|
||||
int TimeAxis::giveData(QTime time) {
|
||||
if(!ok()) return -1;
|
||||
std::pair<int, bool> ret = d()->mTimeTicker.getNewDataTickInfo();
|
||||
auto td = d()->tempDataCache();
|
||||
TimeAxisPrivate* pd = d();
|
||||
SpinLockGuard _guard(&pd->mBufferLock);
|
||||
RenderEditGuard _editGuard(pd);
|
||||
std::pair<int, bool> ret = pd->mTimeTicker.getNewDataTickInfo();
|
||||
auto td = pd->tempDataCache();
|
||||
td->mAllTime.push_back(time);
|
||||
if(ret.second) {
|
||||
td->mData.push_front({time, ret.first});
|
||||
|
||||
@@ -44,6 +44,50 @@ namespace YSG {
|
||||
SpinLockGuard::SpinLockGuard(SpinLock *lock) : mLock(lock) {
|
||||
if (mLock) mLock->lock();
|
||||
}
|
||||
RenderAble::RenderPipeline::RenderPipeline() {
|
||||
frontColor = &colorBuffers[0];
|
||||
readyColor = &colorBuffers[1];
|
||||
renderColor = &colorBuffers[2];
|
||||
}
|
||||
void RenderAble::RenderPipeline::markEditState() {
|
||||
editStateVersion.fetch_add(1, std::memory_order_release);
|
||||
}
|
||||
bool RenderAble::RenderPipeline::hasNewState() const {
|
||||
return RenderAble::version_newer(editStateVersion.load(std::memory_order_acquire), renderStateVersion);
|
||||
}
|
||||
RenderAble::ColorBuffer* RenderAble::RenderPipeline::currentRenderColor() const {
|
||||
return renderColor;
|
||||
}
|
||||
QRegion RenderAble::RenderPipeline::publishRenderColor() {
|
||||
QRegion updateRegion;
|
||||
SpinLockGuard guard(&colorLock);
|
||||
if(RenderAble::version_newer(renderColor->version, readyColor->version)) {
|
||||
std::swap(readyColor, renderColor);
|
||||
dirtyRegion += readyColor->dirtyRegion;
|
||||
}
|
||||
if(RenderAble::version_newer(readyColor->version, frontColor->version)) {
|
||||
if(dirtyRegion.isEmpty()) dirtyRegion = readyColor->dirtyRegion;
|
||||
if(!paintRequestPending) {
|
||||
paintRequestPending = true;
|
||||
updateRegion = dirtyRegion;
|
||||
}
|
||||
}
|
||||
return updateRegion;
|
||||
}
|
||||
RenderAble::ColorBuffer* RenderAble::RenderPipeline::consumeReadyColor() {
|
||||
SpinLockGuard guard(&colorLock);
|
||||
if(RenderAble::version_newer(readyColor->version, frontColor->version)) {
|
||||
std::swap(frontColor, readyColor);
|
||||
}
|
||||
paintRequestPending = false;
|
||||
dirtyRegion = QRegion();
|
||||
return frontColor;
|
||||
}
|
||||
RenderEditGuard::RenderEditGuard(RenderData* data) : mData(data) {
|
||||
}
|
||||
RenderEditGuard::~RenderEditGuard() {
|
||||
if(mData && mData->qPtr) mData->qPtr->markStateDirty();
|
||||
}
|
||||
|
||||
void RenderAble::init(Plot* plot, QString layerName) {
|
||||
if(layerName.isEmpty()) {
|
||||
@@ -51,7 +95,11 @@ namespace YSG {
|
||||
}
|
||||
plot->mLayerMap[layerName]->mRenderAbles.append(this);
|
||||
mPlot = plot;
|
||||
dPtr = createRenderData();
|
||||
if(!dPtr) dPtr = createRenderData();
|
||||
delete dPtr->mRenderState;
|
||||
delete dPtr->mRenderStateCache;
|
||||
delete dPtr->mTempData;
|
||||
delete dPtr->mTempDataCache;
|
||||
dPtr->mRenderState = createRenderState();
|
||||
dPtr->mRenderStateCache = createRenderState();
|
||||
dPtr->mTempData = createTempData();
|
||||
@@ -59,6 +107,12 @@ namespace YSG {
|
||||
dPtr->mRenderState->dPtr = dPtr;
|
||||
dPtr->mRenderStateCache->dPtr = dPtr;
|
||||
dPtr->qPtr = this;
|
||||
dPtr->mRenderStateCache->version = 1;
|
||||
if(mPlot) mPlot->markRenderStateDirty();
|
||||
}
|
||||
void RenderAble::markStateDirty() {
|
||||
if(dPtr && dPtr->mRenderStateCache) ++dPtr->mRenderStateCache->version;
|
||||
if(mPlot) mPlot->markRenderStateDirty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
+191
-134
@@ -1,125 +1,195 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <qlinkedlist.h>
|
||||
#include <QImage>
|
||||
#include <QRegion>
|
||||
#include <qscreen.h>
|
||||
|
||||
|
||||
#include "GlobalTypes.h"
|
||||
|
||||
|
||||
|
||||
namespace YSG {
|
||||
|
||||
#define INIT_GET(className) className##Private* privateData = d(); \
|
||||
className##RenderState* renderState = reinterpret_cast<className##RenderState*>(d()->getState(src)); \
|
||||
|
||||
|
||||
struct TempData {
|
||||
virtual ~TempData() = default;
|
||||
};
|
||||
struct RenderState {
|
||||
virtual ~RenderState() = default;
|
||||
RenderData *dPtr{};
|
||||
SRC src();
|
||||
};
|
||||
struct RenderData {
|
||||
SpinLock mBufferLock;
|
||||
RenderAble *qPtr{};
|
||||
bool mRenderStart = false;
|
||||
RenderState* getState(SRC src) const {
|
||||
switch (src) {
|
||||
case SRC::Render: {
|
||||
return mRenderState;
|
||||
}
|
||||
case SRC::Cache: {
|
||||
return mRenderStateCache;
|
||||
}
|
||||
case SRC::Auto: {
|
||||
return mRenderStart ? mRenderState : mRenderStateCache;
|
||||
}
|
||||
}
|
||||
qDebug() << "RenderState* RenderData::getState(SRC src) error! 不存在的枚举值" << static_cast<int>(src);
|
||||
return nullptr;
|
||||
#define INIT_GET(className) className##Private* privateData = d(); \
|
||||
className##RenderState* renderState = reinterpret_cast<className##RenderState*>(d()->getState(src));
|
||||
struct TempData {
|
||||
virtual ~TempData() = default;
|
||||
};
|
||||
struct RenderState {
|
||||
virtual ~RenderState() = default;
|
||||
std::uint64_t version = 0;
|
||||
RenderData* dPtr{};
|
||||
SRC src();
|
||||
};
|
||||
struct RenderData {
|
||||
SpinLock mBufferLock;
|
||||
RenderAble* qPtr{};
|
||||
bool mRenderStart = false;
|
||||
RenderState* getState(SRC src) const {
|
||||
switch (src) {
|
||||
case SRC::Render: {
|
||||
return mRenderState;
|
||||
}
|
||||
|
||||
RenderState *mRenderState{}, *mRenderStateCache{};
|
||||
TempData *mTempData{}, *mTempDataCache{};
|
||||
protected:
|
||||
virtual void loadCache() = 0;
|
||||
virtual RenderState* renderState() = 0;
|
||||
virtual RenderState* renderStateCache() = 0;
|
||||
virtual TempData* tempData() = 0;
|
||||
virtual TempData* tempDataCache() = 0;
|
||||
|
||||
virtual ~RenderData() {
|
||||
delete mTempData;
|
||||
delete mTempDataCache;
|
||||
delete mRenderState;
|
||||
delete mRenderStateCache;
|
||||
case SRC::Cache: {
|
||||
return mRenderStateCache;
|
||||
}
|
||||
virtual void draw(QPainter* painter){}
|
||||
virtual void prepareData() {
|
||||
SpinLockGuard g(&mBufferLock);
|
||||
//qDebug() << "qPtr->mObjectName " << qPtr->mObjectName;
|
||||
loadCache();
|
||||
case SRC::Auto: {
|
||||
return mRenderStart ? mRenderState : mRenderStateCache;
|
||||
}
|
||||
virtual bool selectTest(const QPointF& pos){return false;}
|
||||
virtual void plotEvent(QEvent* event){}
|
||||
virtual void mousePressEvent(QMouseEvent* event) {}
|
||||
virtual void mouseMoveEvent(QMouseEvent* event) {}
|
||||
virtual void mouseReleaseEvent(QMouseEvent* event) {}
|
||||
virtual void keyPressEvent(QKeyEvent* event){}
|
||||
virtual void keyReleaseEvent(QKeyEvent* event){}
|
||||
virtual void firstResizeEvent(QResizeEvent* event){}
|
||||
virtual void resizeEvent(QResizeEvent* event){}
|
||||
virtual void wheelEvent(QWheelEvent* event){}
|
||||
friend struct RenderAble;
|
||||
}
|
||||
qDebug() << "RenderState* RenderData::getState(SRC src) error! 不存在的枚举值" << static_cast<int>(src);
|
||||
return nullptr;
|
||||
}
|
||||
RenderState *mRenderState{}, *mRenderStateCache{};
|
||||
TempData *mTempData{}, *mTempDataCache{};
|
||||
protected:
|
||||
virtual void loadCache() = 0;
|
||||
virtual RenderState* renderState() = 0;
|
||||
virtual RenderState* renderStateCache() = 0;
|
||||
virtual TempData* tempData() = 0;
|
||||
virtual TempData* tempDataCache() = 0;
|
||||
virtual ~RenderData() {
|
||||
delete mTempData;
|
||||
delete mTempDataCache;
|
||||
delete mRenderState;
|
||||
delete mRenderStateCache;
|
||||
}
|
||||
virtual void draw(QPainter* painter) {}
|
||||
virtual void prepareData() {
|
||||
SpinLockGuard g(&mBufferLock);
|
||||
//qDebug() << "qPtr->mObjectName " << qPtr->mObjectName;
|
||||
loadCache();
|
||||
}
|
||||
virtual bool selectTest(const QPointF& pos) {
|
||||
return false;
|
||||
}
|
||||
virtual void plotEvent(QEvent* event) {}
|
||||
virtual void mousePressEvent(QMouseEvent* event) {}
|
||||
virtual void mouseMoveEvent(QMouseEvent* event) {}
|
||||
virtual void mouseReleaseEvent(QMouseEvent* event) {}
|
||||
virtual void keyPressEvent(QKeyEvent* event) {}
|
||||
virtual void keyReleaseEvent(QKeyEvent* event) {}
|
||||
virtual void firstResizeEvent(QResizeEvent* event) {}
|
||||
virtual void resizeEvent(QResizeEvent* event) {}
|
||||
virtual void wheelEvent(QWheelEvent* event) {}
|
||||
friend struct RenderAble;
|
||||
};
|
||||
struct BaseRenderData : RenderData {
|
||||
RenderState* renderState() override {
|
||||
return nullptr;
|
||||
}
|
||||
RenderState* renderStateCache() override {
|
||||
return nullptr;
|
||||
}
|
||||
TempData* tempData() override {
|
||||
return nullptr;
|
||||
}
|
||||
TempData* tempDataCache() override {
|
||||
return nullptr;
|
||||
}
|
||||
void loadCache() override {}
|
||||
};
|
||||
class Plot;
|
||||
struct LIB_DECL RenderEditGuard {
|
||||
explicit RenderEditGuard(RenderData* data);
|
||||
~RenderEditGuard();
|
||||
RenderData* mData{};
|
||||
};
|
||||
struct RenderAble {
|
||||
enum class JobState {
|
||||
Idle,
|
||||
Queued,
|
||||
Running
|
||||
};
|
||||
|
||||
struct BaseRenderData : RenderData {
|
||||
RenderState* renderState() override {return nullptr;}
|
||||
RenderState* renderStateCache() override {return nullptr;}
|
||||
TempData* tempData() override {return nullptr;}
|
||||
TempData* tempDataCache() override {return nullptr;}
|
||||
void loadCache() override{}
|
||||
struct ColorBuffer {
|
||||
std::uint64_t version = 0;
|
||||
QImage image;
|
||||
QRegion dirtyRegion;
|
||||
};
|
||||
|
||||
class Plot;
|
||||
struct RenderAble {
|
||||
virtual RenderData* createRenderData() {return new BaseRenderData;}
|
||||
virtual RenderState* createRenderState(){return new RenderState;}
|
||||
virtual TempData* createTempData() {return new TempData;}
|
||||
virtual void init(Plot *plot, QString layerName = "");
|
||||
virtual void draw(QPainter* painter){dPtr->draw(painter);}
|
||||
virtual void prepareData(){dPtr->prepareData();}
|
||||
virtual bool selectTest(const QPointF& pos){return dPtr->selectTest(pos);}
|
||||
virtual void plotEvent(QEvent* event){dPtr->plotEvent(event);}
|
||||
virtual void mousePressEvent(QMouseEvent* event) {dPtr->mousePressEvent(event);}
|
||||
virtual void mouseMoveEvent(QMouseEvent* event) {dPtr->mouseMoveEvent(event);}
|
||||
virtual void mouseReleaseEvent(QMouseEvent* event) {dPtr->mouseReleaseEvent(event);}
|
||||
virtual void keyPressEvent(QKeyEvent* event){dPtr->keyPressEvent(event);}
|
||||
virtual void keyReleaseEvent(QKeyEvent* event){dPtr->keyReleaseEvent(event);}
|
||||
virtual void firstResizeEvent(QResizeEvent* event){dPtr->firstResizeEvent(event);}
|
||||
virtual void resizeEvent(QResizeEvent* event){dPtr->resizeEvent(event);}
|
||||
virtual void wheelEvent(QWheelEvent* event){dPtr->wheelEvent(event);};
|
||||
|
||||
QString mObjectName;
|
||||
RenderData *dPtr{};
|
||||
Plot *mPlot{};
|
||||
[[nodiscard]] bool ok() const;
|
||||
bool mPrepared = false;
|
||||
// 这里面可以预先准备数据用来同步
|
||||
virtual ~RenderAble();
|
||||
virtual bool select(QPointF pos){return false;}
|
||||
bool mVisable = true;
|
||||
RenderAble* mParent{};
|
||||
QVector<RenderAble*> mBeRelyList;
|
||||
bool dectCycleRely();
|
||||
private:
|
||||
bool hasCycleRely(RenderAble* node, QSet<RenderAble*>& visited, QSet<RenderAble*>& recursionStack, QVector<RenderAble*>& currentPath);
|
||||
struct RenderPipeline {
|
||||
SpinLock colorLock;
|
||||
std::array<ColorBuffer, 3> colorBuffers;
|
||||
ColorBuffer* frontColor{};
|
||||
ColorBuffer* readyColor{};
|
||||
ColorBuffer* renderColor{};
|
||||
std::atomic<std::uint64_t> editStateVersion{1};
|
||||
std::uint64_t renderStateVersion = 0;
|
||||
JobState jobState = JobState::Idle;
|
||||
bool paintRequestPending = false;
|
||||
QRegion dirtyRegion;
|
||||
RenderPipeline();
|
||||
void markEditState();
|
||||
bool hasNewState() const;
|
||||
ColorBuffer* currentRenderColor() const;
|
||||
QRegion publishRenderColor();
|
||||
ColorBuffer* consumeReadyColor();
|
||||
};
|
||||
|
||||
|
||||
#define Q_Ptr2(ClassName) \
|
||||
virtual RenderData* createRenderData() {
|
||||
return new BaseRenderData;
|
||||
}
|
||||
virtual RenderState* createRenderState() {
|
||||
return new RenderState;
|
||||
}
|
||||
virtual TempData* createTempData() {
|
||||
return new TempData;
|
||||
}
|
||||
virtual void init(Plot* plot, QString layerName = "");
|
||||
virtual void draw(QPainter* painter) {
|
||||
dPtr->draw(painter);
|
||||
}
|
||||
virtual void prepareData() {
|
||||
dPtr->prepareData();
|
||||
}
|
||||
virtual bool selectTest(const QPointF& pos) {
|
||||
return dPtr->selectTest(pos);
|
||||
}
|
||||
virtual void plotEvent(QEvent* event) {
|
||||
dPtr->plotEvent(event);
|
||||
}
|
||||
virtual void mousePressEvent(QMouseEvent* event) {
|
||||
dPtr->mousePressEvent(event);
|
||||
}
|
||||
virtual void mouseMoveEvent(QMouseEvent* event) {
|
||||
dPtr->mouseMoveEvent(event);
|
||||
}
|
||||
virtual void mouseReleaseEvent(QMouseEvent* event) {
|
||||
dPtr->mouseReleaseEvent(event);
|
||||
}
|
||||
virtual void keyPressEvent(QKeyEvent* event) {
|
||||
dPtr->keyPressEvent(event);
|
||||
}
|
||||
virtual void keyReleaseEvent(QKeyEvent* event) {
|
||||
dPtr->keyReleaseEvent(event);
|
||||
}
|
||||
virtual void firstResizeEvent(QResizeEvent* event) {
|
||||
dPtr->firstResizeEvent(event);
|
||||
}
|
||||
virtual void resizeEvent(QResizeEvent* event) {
|
||||
dPtr->resizeEvent(event);
|
||||
}
|
||||
virtual void wheelEvent(QWheelEvent* event) {
|
||||
dPtr->wheelEvent(event);
|
||||
};
|
||||
static bool version_newer(std::uint64_t a, std::uint64_t b) {
|
||||
return static_cast<std::int64_t>(a - b) > 0;
|
||||
}
|
||||
void markStateDirty();
|
||||
QString mObjectName;
|
||||
RenderData* dPtr{};
|
||||
Plot* mPlot{};
|
||||
[[nodiscard]] bool ok() const;
|
||||
bool mPrepared = false;
|
||||
// 这里面可以预先准备数据用来同步
|
||||
virtual ~RenderAble();
|
||||
virtual bool select(QPointF pos) {
|
||||
return false;
|
||||
}
|
||||
bool mVisable = true;
|
||||
RenderAble* mParent{};
|
||||
QVector<RenderAble*> mBeRelyList;
|
||||
bool dectCycleRely();
|
||||
private:
|
||||
bool hasCycleRely(RenderAble* node, QSet<RenderAble*>& visited, QSet<RenderAble*>& recursionStack, QVector<RenderAble*>& currentPath);
|
||||
};
|
||||
#define Q_Ptr2(ClassName) \
|
||||
ClassName##Private* d() {return reinterpret_cast<ClassName##Private*>(dPtr);} \
|
||||
RenderData* createRenderData() override; \
|
||||
RenderState* createRenderState() override; \
|
||||
@@ -130,28 +200,21 @@ namespace YSG {
|
||||
friend struct ClassName##RenderState; \
|
||||
friend struct ClassName##TempData; \
|
||||
public:
|
||||
|
||||
|
||||
#define Q_Ptr(ClassName) \
|
||||
#define Q_Ptr(ClassName) \
|
||||
ClassName(); \
|
||||
ClassName##Private* d() {return reinterpret_cast<ClassName##Private*>(dPtr);} \
|
||||
RenderData* createRenderData() override; \
|
||||
RenderState* createRenderState() override; \
|
||||
TempData* createTempData() override; \
|
||||
|
||||
|
||||
|
||||
#define Q_Ptr_cpp(ClassName) \
|
||||
TempData* createTempData() override;
|
||||
#define Q_Ptr_cpp(ClassName) \
|
||||
ClassName::ClassName() { \
|
||||
dPtr = new ClassName##Private; \
|
||||
mObjectName = #ClassName; \
|
||||
} \
|
||||
RenderData* ClassName::createRenderData() {return new ClassName##Private;} \
|
||||
RenderState* ClassName::createRenderState() {return new ClassName##RenderState;} \
|
||||
TempData* ClassName::createTempData() {return new ClassName##TempData;} \
|
||||
|
||||
|
||||
#define D_Ptr_3(ClassName) \
|
||||
TempData* ClassName::createTempData() {return new ClassName##TempData;}
|
||||
#define D_Ptr_3(ClassName) \
|
||||
ClassName##RenderState* renderState() override {return reinterpret_cast<ClassName##RenderState*>(mRenderState);} \
|
||||
ClassName##RenderState* renderStateCache() override {return reinterpret_cast<ClassName##RenderState*>(mRenderStateCache);} \
|
||||
ClassName##TempData* tempData() override {return reinterpret_cast<ClassName##TempData*>(mTempData);} \
|
||||
@@ -162,10 +225,7 @@ namespace YSG {
|
||||
std::swap(mTempData, mTempDataCache); \
|
||||
if(!mRenderStart) mRenderStart = true; \
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define D_Ptr(ClassName) \
|
||||
#define D_Ptr(ClassName) \
|
||||
ClassName* q() {return reinterpret_cast<ClassName*>(qPtr);} \
|
||||
ClassName##RenderState* renderState() override {return reinterpret_cast<ClassName##RenderState*>(mRenderState);} \
|
||||
ClassName##RenderState* renderStateCache() override {return reinterpret_cast<ClassName##RenderState*>(mRenderStateCache);} \
|
||||
@@ -177,19 +237,16 @@ namespace YSG {
|
||||
std::swap(mTempData, mTempDataCache); \
|
||||
if(!mRenderStart) mRenderStart = true; \
|
||||
}
|
||||
|
||||
#define D_Ptr2(ClassName) \
|
||||
#define D_Ptr2(ClassName) \
|
||||
ClassName* q() {return reinterpret_cast<ClassName*>(qPtr);} \
|
||||
ClassName##RenderState* renderState() override {return reinterpret_cast<ClassName##RenderState*>(mRenderState);} \
|
||||
ClassName##RenderState* renderStateCache() override {return reinterpret_cast<ClassName##RenderState*>(mRenderStateCache);} \
|
||||
ClassName##TempData* tempData() override {return reinterpret_cast<ClassName##TempData*>(mTempData);} \
|
||||
ClassName##TempData* tempDataCache() override {return reinterpret_cast<ClassName##TempData*>(mTempDataCache);} \
|
||||
friend class ClassName;
|
||||
|
||||
#define INIT_SET(className) className##Private* pd = d(); \
|
||||
#define INIT_SET(className) className##Private* pd = d(); \
|
||||
className##RenderState* sc = pd->renderStateCache();\
|
||||
className##TempData* td = pd->tempDataCache(); \
|
||||
SpinLockGuard _guard(&pd->mBufferLock); \
|
||||
|
||||
|
||||
}
|
||||
RenderEditGuard _editGuard(pd);
|
||||
}
|
||||
|
||||
@@ -1,50 +1,81 @@
|
||||
#include "asio.hpp"
|
||||
#include "Global.h"
|
||||
#include <QApplication>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include "Plot_p.h"
|
||||
|
||||
namespace YSG {
|
||||
struct TimerThreadPrivate {
|
||||
asio::io_context mIoContext;
|
||||
std::unique_ptr<asio::executor_work_guard<asio::io_context::executor_type>> mWorkGuard;
|
||||
std::unique_ptr<asio::thread_pool> mCpuPool;
|
||||
std::thread::id mSchedulerThreadId;
|
||||
};
|
||||
|
||||
TimerThread::TimerThread() {
|
||||
connect(QApplication::instance(), &QApplication::aboutToQuit, [&]() {
|
||||
TimerThread::TimerThread() : d(std::make_unique<TimerThreadPrivate>()) {
|
||||
connect(QApplication::instance(), &QApplication::aboutToQuit, [this]() {
|
||||
if (isRunning()) {
|
||||
quit();
|
||||
d->mIoContext.stop();
|
||||
if(d->mCpuPool) d->mCpuPool->stop();
|
||||
wait();
|
||||
}
|
||||
delete this;
|
||||
});
|
||||
}
|
||||
TimerThread::~TimerThread() = default;
|
||||
|
||||
void TimerThread::run() {
|
||||
d->mSchedulerThreadId = std::this_thread::get_id();
|
||||
d->mIoContext.restart();
|
||||
d->mWorkGuard = std::make_unique<asio::executor_work_guard<asio::io_context::executor_type>>(d->mIoContext.get_executor());
|
||||
unsigned cpuCount = std::max(1u, std::thread::hardware_concurrency() > 1 ? std::thread::hardware_concurrency() - 1 : 1u);
|
||||
d->mCpuPool = std::make_unique<asio::thread_pool>(cpuCount);
|
||||
for (auto &plot: mPlots) {
|
||||
plot->d->mTimer = new QTimer;
|
||||
plot->d->mTimer->setTimerType(Qt::PreciseTimer);
|
||||
plot->d->mTimerThread = this;
|
||||
connect(plot->d->mTimer, &QTimer::timeout, plot, &Plot::render, Qt::DirectConnection);
|
||||
// show里的调用可能比这个 线程开启提前, 那是 timer是nullptr
|
||||
if (plot->isVisible()) plot->startRender();
|
||||
plot->d->mRenderTimer = std::make_unique<asio::steady_timer>(d->mIoContext);
|
||||
if(plot->isVisible()) plot->d->mRenderEnabled = true;
|
||||
if(plot->d->mRenderEnabled) plot->scheduleRenderTimer();
|
||||
plot->requestRender();
|
||||
}
|
||||
exec();
|
||||
d->mIoContext.run();
|
||||
for (auto &plot: mPlots) {
|
||||
plot->d->mTimer->stop();
|
||||
delete plot->d->mTimer;
|
||||
if(plot->d->mRenderTimer) {
|
||||
plot->d->mRenderTimer->cancel();
|
||||
plot->d->mRenderTimer.reset();
|
||||
}
|
||||
}
|
||||
if(d->mCpuPool) {
|
||||
d->mCpuPool->stop();
|
||||
d->mCpuPool->join();
|
||||
d->mCpuPool.reset();
|
||||
}
|
||||
d->mWorkGuard.reset();
|
||||
}
|
||||
void TimerThread::post(std::function<void()> task) {
|
||||
asio::post(d->mIoContext, std::move(task));
|
||||
}
|
||||
void TimerThread::postCpu(std::function<void()> task) {
|
||||
if(d->mCpuPool) {
|
||||
asio::post(*d->mCpuPool, std::move(task));
|
||||
} else {
|
||||
post(std::move(task));
|
||||
}
|
||||
}
|
||||
bool TimerThread::isSchedulerThread() const {
|
||||
return std::this_thread::get_id() == d->mSchedulerThreadId;
|
||||
}
|
||||
asio::io_context& TimerThread::ioContext() {
|
||||
return d->mIoContext;
|
||||
}
|
||||
|
||||
void Global::startAllTimeThread() {
|
||||
for (auto &thread: mTimerThreadMap) {
|
||||
//qDebug() << "startAllTimeThread == " << thread->objectName();
|
||||
thread->start();
|
||||
if(!thread->isRunning()) thread->start();
|
||||
}
|
||||
mTimerId = startTimer(10);
|
||||
}
|
||||
|
||||
void Global::timerEvent(QTimerEvent* event) {
|
||||
if (event->timerId() != mTimerId) return;
|
||||
for (TimerThread *t: mTimerThreadMap) {
|
||||
for (Plot *plot: t->mPlots) {
|
||||
if (plot->d->mPlotState.loadAcquire() != PlotPrivate::PlotState::NeedToPaint) continue;
|
||||
plot->repaint();
|
||||
}
|
||||
}
|
||||
Q_UNUSED(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,30 @@
|
||||
#include "../GlobalTypes.h"
|
||||
#include "Core/Base/global_include.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <QThread>
|
||||
#include <qrgb.h>
|
||||
|
||||
extern int qInitResources_resource();
|
||||
namespace asio {
|
||||
class io_context;
|
||||
}
|
||||
namespace YSG {
|
||||
struct TimerThreadPrivate;
|
||||
class LIB_DECL TimerThread : public QThread {
|
||||
Q_OBJECT
|
||||
public:
|
||||
QList<Plot*> mPlots;
|
||||
TimerThread();
|
||||
~TimerThread() override;
|
||||
void run() override;
|
||||
void post(std::function<void()> task);
|
||||
void postCpu(std::function<void()> task);
|
||||
bool isSchedulerThread() const;
|
||||
asio::io_context& ioContext();
|
||||
private:
|
||||
std::unique_ptr<TimerThreadPrivate> d;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -44,9 +44,10 @@ namespace YSG {
|
||||
// event->button():返回引发该事件的单个鼠标按钮。通常在 mousePressEvent 和 mouseReleaseEvent 中使用。
|
||||
// event->buttons():返回一个标志,指示当前按下的所有鼠标按钮。可以在任何鼠标事件中使用,特别是 mouseMoveEvent 中。
|
||||
void mousePressEvent(QMouseEvent* event) override {
|
||||
if (event->button() != Qt::LeftButton) return;
|
||||
MutiSelectRectRenderState* sc = renderStateCache();
|
||||
SpinLockGuard _guard(&mBufferLock);
|
||||
if (event->button() != Qt::LeftButton) return;
|
||||
RenderEditGuard _editGuard(this);
|
||||
QVector<QRectF> &rectList = sc->mRects;
|
||||
if (!(QApplication::keyboardModifiers() & Qt::ControlModifier)) {
|
||||
rectList.clear();
|
||||
@@ -69,6 +70,7 @@ namespace YSG {
|
||||
if(!(event->buttons() & Qt::LeftButton)) return;
|
||||
MutiSelectRectRenderState* sc = renderStateCache();
|
||||
SpinLockGuard _guard(&mBufferLock);
|
||||
RenderEditGuard _editGuard(this);
|
||||
QVector<QRectF> &rectList = sc->mRects;
|
||||
if (rectList.empty()) return;
|
||||
rectList.last().setBottomRight(event->pos());
|
||||
@@ -80,14 +82,14 @@ namespace YSG {
|
||||
void mouseReleaseEvent(QMouseEvent* event) override {}
|
||||
|
||||
void keyPressEvent(QKeyEvent* event) override {
|
||||
if (event->key() != Qt::Key_Escape || !mActive) return;
|
||||
MutiSelectRectRenderState* sc = renderStateCache();
|
||||
SpinLockGuard _guard(&mBufferLock);
|
||||
if (event->key() == Qt::Key_Escape && mActive) {
|
||||
mActive = false;
|
||||
QVector<QRectF> &rectList = sc->mRects;
|
||||
if (rectList.empty()) return;
|
||||
rectList.removeLast();
|
||||
}
|
||||
RenderEditGuard _editGuard(this);
|
||||
mActive = false;
|
||||
QVector<QRectF> &rectList = sc->mRects;
|
||||
if (rectList.empty()) return;
|
||||
rectList.removeLast();
|
||||
}
|
||||
|
||||
void keyReleaseEvent(QKeyEvent* event) override {
|
||||
|
||||
+115
-45
@@ -1,6 +1,8 @@
|
||||
#include "Plot_p.h"
|
||||
#include "PerformanceShower_p.h"
|
||||
#include "Global.h"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
namespace YSG {
|
||||
class PrePareMutiDataMutexGuard {
|
||||
@@ -31,7 +33,9 @@ namespace YSG {
|
||||
g->mTimerThreadMap[threadName] = newThread;
|
||||
newThread->setObjectName(threadName);
|
||||
}
|
||||
g->mTimerThreadMap[threadName]->mPlots.append(this);
|
||||
TimerThread* thread = g->mTimerThreadMap[threadName];
|
||||
thread->mPlots.append(this);
|
||||
d->mTimerThread = thread;
|
||||
}
|
||||
|
||||
Plot::Plot(){
|
||||
@@ -57,12 +61,12 @@ namespace YSG {
|
||||
}
|
||||
|
||||
Plot::~Plot() {
|
||||
delete d->mRenderBuffer;
|
||||
pauseRender();
|
||||
delete d;
|
||||
}
|
||||
|
||||
bool Plot::isRendering() {
|
||||
if(!d->mTimer) return false;
|
||||
return d->mTimer->isActive();
|
||||
return d->mRenderEnabled;
|
||||
}
|
||||
|
||||
void Plot::startRender(int refreshTimesPreSecond) {
|
||||
@@ -71,36 +75,57 @@ namespace YSG {
|
||||
}
|
||||
|
||||
void Plot::startRender() const {
|
||||
if(d->mTimer) QMetaObject::invokeMethod(d->mTimer, "start", Qt::AutoConnection, Q_ARG(int, qRound(1000.0 / (double) d->mRefreshTimesPreSecond)));
|
||||
d->mRenderEnabled = true;
|
||||
if(!d->mTimerThread) return;
|
||||
Plot* self = const_cast<Plot*>(this);
|
||||
d->mTimerThread->post([self]() {
|
||||
PlotPrivate* d = self->d;
|
||||
if(d->mRenderTimer) {
|
||||
d->mRenderTimer->cancel();
|
||||
self->scheduleRenderTimer();
|
||||
}
|
||||
self->submitRender();
|
||||
});
|
||||
}
|
||||
|
||||
void Plot::pauseRender() const {
|
||||
if(d->mTimer) QMetaObject::invokeMethod(d->mTimer, "stop", Qt::AutoConnection);
|
||||
d->mRenderEnabled = false;
|
||||
if(!d->mTimerThread) return;
|
||||
Plot* self = const_cast<Plot*>(this);
|
||||
d->mTimerThread->post([self]() {
|
||||
PlotPrivate* d = self->d;
|
||||
if(d->mRenderTimer) {
|
||||
d->mRenderTimer->cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Plot::paintEvent(QPaintEvent* event) {
|
||||
SpinLockGuard guard(&d->mBufferLock);
|
||||
Q_UNUSED(event)
|
||||
QPainter painter(this);
|
||||
painter.drawPixmap(QPoint(0, 0), d->mPaintBuffer);
|
||||
//painter.drawImage(QPoint(0, 0), d->mPaintBuffer);
|
||||
RenderAble::ColorBuffer* color = d->mPipeline.consumeReadyColor();
|
||||
if(color && !color->image.isNull()) painter.drawImage(QPoint(0, 0), color->image);
|
||||
else painter.fillRect(rect(), d->mBackground);
|
||||
painter.end();
|
||||
if(d->mShower) d->mShower->counterIncrease("plot paint");
|
||||
d->mPlotState.storeRelease(PlotPrivate::NeedToRender);
|
||||
}
|
||||
void Plot::render() {
|
||||
if (mFirstResize) return;
|
||||
if (d->mPlotState.loadAcquire() != PlotPrivate::NeedToRender) return;
|
||||
if(!d->mRenderBuffer) return;
|
||||
SpinLockGuard renderGuard(&d->mRenderLock);
|
||||
if(d->mRenderBuffer->isNull()) return;
|
||||
|
||||
submitRender();
|
||||
}
|
||||
void Plot::submitRender() {
|
||||
if(mFirstResize) return;
|
||||
if(!d->mRenderEnabled) return;
|
||||
QSize renderSize = d->mRenderSize;
|
||||
if(renderSize.width() <= 0 || renderSize.height() <= 0) return;
|
||||
RenderAble::RenderPipeline& pipeline = d->mPipeline;
|
||||
if(pipeline.jobState != RenderAble::JobState::Idle) return;
|
||||
if(!pipeline.hasNewState()) return;
|
||||
pipeline.jobState = RenderAble::JobState::Queued;
|
||||
pipeline.renderStateVersion = pipeline.editStateVersion.load(std::memory_order_acquire);
|
||||
if(d->mShower) {
|
||||
d->mShower->counterIncrease("渲染帧率");
|
||||
d->mShower->mInfos["interval"] = QString("渲染:%1, global:%2").arg(d->mTimer->interval()).arg(Global::instance()->mInterval);
|
||||
d->mShower->mInfos["interval"] = QString("渲染:%1, global:%2").arg(qRound(1000.0 / (double)d->mRefreshTimesPreSecond)).arg(Global::instance()->mInterval);
|
||||
}
|
||||
Cacl renderData("整体渲染耗时: %1 avg: %2", d->mShower);
|
||||
QPainter painter(d->mRenderBuffer);
|
||||
painter.fillRect(rect(), d->mBackground);
|
||||
{
|
||||
Cacl prepareData("准备数据耗时: %1 avg: %2", d->mShower);
|
||||
SpinLockGuard prepareDataLock(&d->mPrepareDataLock);
|
||||
@@ -125,10 +150,46 @@ namespace YSG {
|
||||
}
|
||||
if(mFirstPrepareData) mFirstPrepareData = false;
|
||||
}
|
||||
|
||||
|
||||
for(Layer* layer : mLayerList) {
|
||||
for(RenderAble* able : layer->mRenderAbles) {
|
||||
able->mPrepared = false;
|
||||
}
|
||||
}
|
||||
RenderAble::ColorBuffer* color = pipeline.currentRenderColor();
|
||||
QColor background = d->mBackground;
|
||||
std::uint64_t version = pipeline.renderStateVersion;
|
||||
TimerThread* thread = d->mTimerThread;
|
||||
pipeline.jobState = RenderAble::JobState::Running;
|
||||
if(thread) {
|
||||
thread->postCpu([this, color, renderSize, background, version, thread]() {
|
||||
renderColor(color, renderSize, background, version);
|
||||
thread->post([this]() {
|
||||
finishRender();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
renderColor(color, renderSize, background, version);
|
||||
finishRender();
|
||||
}
|
||||
}
|
||||
void Plot::finishRender() {
|
||||
RenderAble::RenderPipeline& pipeline = d->mPipeline;
|
||||
QRegion updateRegion = pipeline.publishRenderColor();
|
||||
pipeline.jobState = RenderAble::JobState::Idle;
|
||||
if(!updateRegion.isEmpty()) requestUpdate(updateRegion);
|
||||
if(d->mShower) d->mShower->refresh();
|
||||
if(pipeline.hasNewState()) submitRender();
|
||||
}
|
||||
void Plot::renderColor(RenderAble::ColorBuffer* color, QSize size, QColor background, std::uint64_t version) {
|
||||
Cacl renderData("整体渲染耗时: %1 avg: %2", d->mShower);
|
||||
if(color->image.size() != size || color->image.format() != QImage::Format_ARGB32) {
|
||||
color->image = QImage(size, QImage::Format_ARGB32);
|
||||
}
|
||||
color->image.detach();
|
||||
color->image.fill(background);
|
||||
QPainter painter(&color->image);
|
||||
{
|
||||
Cacl prepareData("draw耗时: %1 avg: %2", d->mShower);
|
||||
Cacl drawData("draw耗时: %1 avg: %2", d->mShower);
|
||||
for(Layer* layer : mLayerList) {
|
||||
for(RenderAble* cur : layer->mRenderAbles) {
|
||||
if(!cur->mVisable) continue;
|
||||
@@ -136,23 +197,35 @@ namespace YSG {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
SpinLockGuard bufferGuard(&d->mBufferLock);
|
||||
// d->mPaintBuffer = *d->mRenderBuffer;
|
||||
d->mPaintBuffer = QPixmap::fromImage(*d->mRenderBuffer);
|
||||
d->mPlotState.storeRelease(PlotPrivate::NeedToPaint);
|
||||
}
|
||||
|
||||
for(Layer* layer : mLayerList) {
|
||||
for(RenderAble* able : layer->mRenderAbles) {
|
||||
able->mPrepared = false;
|
||||
}
|
||||
}
|
||||
if(d->mShower) {
|
||||
d->mShower->refresh();
|
||||
}
|
||||
painter.end();
|
||||
color->version = version;
|
||||
color->dirtyRegion = QRegion(QRect(QPoint(0, 0), size));
|
||||
}
|
||||
void Plot::scheduleRenderTimer() {
|
||||
if(!d->mRenderTimer || !d->mRenderEnabled) return;
|
||||
int interval = std::max(1, qRound(1000.0 / (double)d->mRefreshTimesPreSecond));
|
||||
d->mRenderTimer->expires_after(std::chrono::milliseconds(interval));
|
||||
d->mRenderTimer->async_wait([this](const asio::error_code& error) {
|
||||
if(error) return;
|
||||
submitRender();
|
||||
scheduleRenderTimer();
|
||||
});
|
||||
}
|
||||
void Plot::markRenderStateDirty() {
|
||||
d->mPipeline.markEditState();
|
||||
requestRender();
|
||||
}
|
||||
void Plot::requestRender() {
|
||||
if(!d->mTimerThread) return;
|
||||
d->mTimerThread->post([this]() {
|
||||
submitRender();
|
||||
});
|
||||
}
|
||||
void Plot::requestUpdate(const QRegion& region) {
|
||||
QTimer::singleShot(0, this, [this, region]() {
|
||||
if(region.isEmpty()) update();
|
||||
else update(region);
|
||||
});
|
||||
}
|
||||
|
||||
QColor Plot::backgroundColor() {
|
||||
@@ -169,12 +242,8 @@ namespace YSG {
|
||||
case QEvent::Resize: {
|
||||
auto e = reinterpret_cast<QResizeEvent*>(event);
|
||||
{
|
||||
//qDebug() << resizeEvent->size();
|
||||
SpinLockGuard guard(&d->mRenderLock);
|
||||
delete d->mRenderBuffer;
|
||||
//d->mRenderBuffer = new QPixmap(e->size());
|
||||
d->mRenderBuffer = new QImage(e->size().width(), e->size().height(), QImage::Format_ARGB32);
|
||||
|
||||
d->mRenderSize = e->size();
|
||||
if(mFirstResize) {
|
||||
for(Layer* layer : mLayerList) {
|
||||
for(RenderAble* able : layer->mRenderAbles) {
|
||||
@@ -189,6 +258,7 @@ namespace YSG {
|
||||
}
|
||||
if(mFirstResize) mFirstResize = false;
|
||||
}
|
||||
markRenderStateDirty();
|
||||
break;
|
||||
}
|
||||
case QEvent::Show: {
|
||||
|
||||
@@ -57,6 +57,8 @@ namespace YSG {
|
||||
QColor backgroundColor();
|
||||
bool mFirstResize = true, mFirstPrepareData = true;
|
||||
void setBackgroundColor(const QColor& color) const;
|
||||
void markRenderStateDirty();
|
||||
void requestRender();
|
||||
struct Layer {
|
||||
QString mLayerName;
|
||||
QVector<RenderAble*> mRenderAbles;
|
||||
@@ -89,6 +91,11 @@ namespace YSG {
|
||||
QHash<QString, Layer*> mLayerMap;
|
||||
bool event(QEvent* event) final;
|
||||
void paintEvent(QPaintEvent* event) final;
|
||||
void scheduleRenderTimer();
|
||||
void submitRender();
|
||||
void finishRender();
|
||||
void requestUpdate(const QRegion& region);
|
||||
void renderColor(RenderAble::ColorBuffer* color, QSize size, QColor background, std::uint64_t version);
|
||||
friend class TimerThread;
|
||||
friend struct RenderAble;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "asio.hpp"
|
||||
#include "Plot.h"
|
||||
#include <QApplication>
|
||||
#include <QThread>
|
||||
#include <memory>
|
||||
|
||||
|
||||
namespace YSG {
|
||||
@@ -11,14 +13,11 @@ namespace YSG {
|
||||
Plot *q{};
|
||||
int mRefreshTimesPreSecond = 30;
|
||||
TimerThread* mTimerThread{};
|
||||
QTimer *mTimer{};
|
||||
enum PlotState { NeedToRender, NeedToPaint};
|
||||
QAtomicInteger<int> mPlotState = NeedToRender;
|
||||
std::unique_ptr<asio::steady_timer> mRenderTimer;
|
||||
bool mRenderEnabled = false;
|
||||
QSize mRenderSize;
|
||||
RenderAble::RenderPipeline mPipeline;
|
||||
SpinLock mBufferLock, mRenderLock, mPrepareDataLock, mMustPrepareDataLock;
|
||||
//QPixmap *mRenderBuffer{}, mPaintBuffer;
|
||||
//QImage *mRenderBuffer{}, mPaintBuffer;
|
||||
QImage *mRenderBuffer{};
|
||||
QPixmap mPaintBuffer{};
|
||||
QColor mBackground = Qt::black;
|
||||
PerformanceShower *mShower{};
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace YSG {
|
||||
AfterglowRenderState* sc = pd->renderStateCache();
|
||||
AfterglowTempData* td = pd->tempDataCache();
|
||||
SpinLockGuard _guard(&pd->mBufferLock);
|
||||
RenderEditGuard _editGuard(pd);
|
||||
sc->frequentPointSize = std::move(t);
|
||||
sc->update_PointSize(sc->frequentPointSize, sc->powerPointSize);
|
||||
}
|
||||
@@ -25,6 +26,7 @@ namespace YSG {
|
||||
AfterglowRenderState* sc = pd->renderStateCache();
|
||||
AfterglowTempData* td = pd->tempDataCache();
|
||||
SpinLockGuard _guard(&pd->mBufferLock);
|
||||
RenderEditGuard _editGuard(pd);
|
||||
sc->powerPointSize = std::move(t);
|
||||
sc->update_PointSize(sc->frequentPointSize, sc->powerPointSize);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user