1. 修改了模型热重载部分逻辑,实现了真正的异步模型热重载

This commit is contained in:
Misaki
2025-12-07 16:06:15 +08:00
parent 09a4492f07
commit c1d90c73b8
9 changed files with 192 additions and 76 deletions
-2
View File
@@ -239,10 +239,8 @@ void GLCore::setWindowSize(const int w, const int h)
if (this->width() == w && this->height() == h) {
return;
}
// 调用 QWidget::resize 或 setFixedSize 来改变窗口的实际尺寸
setFixedSize(w, h);
// 调用 setFixedSize 会自动触发 QOpenGLWidget 的 resizeEvent
// 进而调用 resizeGL(w, h),无需手动调用 resizeGL
}
+3 -13
View File
@@ -6,9 +6,7 @@
* @brief 模型页面
* 暂时只做最简单功能切换模型
*/
#ifndef YOSUGA_MODELPAGE_H
#define YOSUGA_MODELPAGE_H
#pragma once
#include "BasePage.h"
#include "ElaPushButton.h"
@@ -20,7 +18,7 @@
class ElaLineEdit;
class ElaPushButton;
class ModelPage : public BasePage
class ModelPage final : public BasePage
{
Q_OBJECT
public:
@@ -28,7 +26,7 @@ public:
std::pair<QString, QString> splitPath(const QString& fullPath);
~ModelPage();
~ModelPage() override;
private:
@@ -39,12 +37,4 @@ private:
QUrl modelFileUrl;
QString modelFilePathFirst;
QString modelFilePathSecond;
};
#endif //YOSUGA_MODELPAGE_H
+3 -7
View File
@@ -52,7 +52,7 @@ HomePage::HomePage(QWidget* parent)
urlCard1->setTitlePixelSize(17);
urlCard1->setTitleSpacing(25);
urlCard1->setSubTitleSpacing(13);
urlCard1->setUrl("https://github.com/Misakiotoha/Yosuga");
urlCard1->setUrl("https://github.com/Misakityan/Yosuga");
urlCard1->setCardPixmap(QPixmap("Resources/Pic/Others/img.png"));
urlCard1->setTitle("Yosuga Github");
urlCard1->setSubTitle("Star++!");
@@ -110,10 +110,10 @@ HomePage::HomePage(QWidget* parent)
Q_EMIT modelShopNavigation();
});
ModeShopCard->setCardPixmap(QPixmap("Resources/Pic/Others/Live2D.png"));
ModeShopCard->setTitle("模型商店");
ModeShopCard->setTitle("模型设置");
ModeShopCard->setSubTitle("属于你的Live2D模型");
ModeShopCard->setInteractiveTips("By Misaki");
ModeShopCard->setDetailedText("选择你喜欢的Live2D模型,模型来自多个作者,多个平台,有免费也有收费的");
ModeShopCard->setDetailedText("选择你喜欢的Live2D模型");
// 音频设置卡片
ElaPopularCard* AudioSettingCard = new ElaPopularCard(this);
connect(AudioSettingCard, &ElaPopularCard::popularCardButtonClicked, this, [=, this]() {
@@ -144,10 +144,6 @@ HomePage::HomePage(QWidget* parent)
centerVLayout->addLayout(flowLayout);
centerVLayout->addStretch();
addCentralWidget(centralWidget);
// 初始化提示
ElaMessageBar::success(ElaMessageBarType::BottomRight, "Success", "初始化成功!", 2000);
qDebug() << "初始化成功";
}
HomePage::~HomePage()
+105 -47
View File
@@ -3,21 +3,19 @@
//
#include "ModelPage.h"
#include <QFileInfo>
#include <QFileDialog>
#include <QDebug>
#include <QHBoxLayout>
#include <QtWidgets>
#include "ElaComboBox.h"
#include "ElaMessageBar.h"
#include "ElaScrollPageArea.h"
#include "ElaText.h"
#include <QtConcurrent>
#include <QOpenGLContext>
#include <QOffscreenSurface>
#include <QFutureWatcher>
#include "LAppLive2DManager.hpp"
ModelPage::ModelPage(QWidget* parent)
: BasePage(parent)
{
ModelPage::ModelPage(QWidget *parent)
: BasePage(parent) {
// 预览窗口标题
setWindowTitle("ModelPage");
@@ -29,9 +27,9 @@ ModelPage::ModelPage(QWidget* parent)
modelChoosePushButton->setToolTip("选择.model3.json结尾的文件");
modelUsePushButton = new ElaPushButton("使用模型", this);
modelUsePushButton->setToolTip("使用选择的模型或Url对应的模型");
ElaScrollPageArea* modelSetArea = new ElaScrollPageArea(this);
QHBoxLayout* modelSetLayout = new QHBoxLayout(modelSetArea);
ElaText* modelSetText = new ElaText("模型设置", this);
ElaScrollPageArea *modelSetArea = new ElaScrollPageArea(this);
QHBoxLayout *modelSetLayout = new QHBoxLayout(modelSetArea);
ElaText *modelSetText = new ElaText("模型设置", this);
modelSetText->setTextPixelSize(15);
modelSetLayout->addWidget(modelSetText);
modelSetLayout->addWidget(modelUrlEdit);
@@ -40,54 +38,116 @@ ModelPage::ModelPage(QWidget* parent)
modelSetLayout->addWidget(modelUsePushButton);
modelSetLayout->addSpacing(10);
connect(modelChoosePushButton, &ElaPushButton::clicked, this, [this]() {
// 获取当前exe所在目录的本地路径
QString exeDir = QCoreApplication::applicationDirPath();
// 转换为QUrl格式(自动处理路径分隔符)
QUrl initialDir = QUrl::fromLocalFile(exeDir);
// 创建对话框对象(使用 heap 分配,由 Qt 对象树管理内存)
auto *fileDialog = new QFileDialog(this);
fileDialog->setWindowTitle("选择模型文件");
fileDialog->setNameFilter("Live2D Model (*.model3.json)");
// 打开文件选择对话框
modelFileUrl = QFileDialog::getOpenFileUrl(
this,
"选择模型文件",
initialDir, // 初始目录为当前目录
"*.model3.json");
// 设置初始目录
const QString exeDir = QCoreApplication::applicationDirPath(); // 获取当前exe所在目录的本地路径
fileDialog->setDirectory(exeDir); // 设置初始目录为当前 exe 所在目录
// 检查url是否有效
if(!modelFileUrl.isEmpty()){
QString t = modelFileUrl.toLocalFile();
std::pair<QString, QString> path = this->splitPath(t);
this->modelFilePathFirst = path.first;
this->modelFilePathSecond = path.second;
this->modelUrlEdit->setText(t);
}
else{
ElaMessageBar::information(ElaMessageBarType::BottomRight, "模型设置", "似乎并没有选择模型", 800.0, this);
}
// 连接信号:当用户选中文件并点击打开时
connect(fileDialog, &QFileDialog::fileSelected, this, [this, fileDialog](const QString &file) {
modelFileUrl = QUrl::fromLocalFile(file);
if (!modelFileUrl.isEmpty()) {
const QString t = modelFileUrl.toLocalFile();
const std::pair<QString, QString> path = this->splitPath(t);
this->modelFilePathFirst = path.first;
this->modelFilePathSecond = path.second;
this->modelUrlEdit->setText(t);
ElaMessageBar::success(ElaMessageBarType::BottomRight, "模型设置", "模型选择成功", 2000, this);
}
// 用完即弃,自动清理内存
fileDialog->deleteLater();
});
// 处理取消的情况(防止内存泄漏)
connect(fileDialog, &QFileDialog::rejected, fileDialog, &QObject::deleteLater);
// 显示对话框(非阻塞,不会卡住主界面)
fileDialog->open();
});
connect(modelUsePushButton, &ElaPushButton::clicked, this, [this]() {
if(!modelFileUrl.isEmpty()){
LAppLive2DManager::GetInstance()->LoadModelFromPath(this->modelFilePathFirst.toStdString(), this->modelFilePathSecond.toStdString());
}
else{
// 模型使用
if (modelFileUrl.isEmpty()) {
ElaMessageBar::information(ElaMessageBarType::BottomRight, "模型设置", "似乎并没有选择模型", 800.0, this);
return;
}
// UI 状态设置为加载中
modelUsePushButton->setEnabled(false); // 禁用使用按钮
modelUsePushButton->setText("加载中"); // 修改按钮文本
modelChoosePushButton->setEnabled(false); // 禁用选择按钮
// 获取路径字符串 (必须按值传递给lambda)
std::string dir = this->modelFilePathFirst.toStdString();
std::string filename = this->modelFilePathSecond.toStdString();
// 启动异步任务
QFuture<LAppModel *> future = QtConcurrent::run([dir, filename]() -> LAppModel * {
// 以下代码在子线程执行
// 创建临时 OpenGL 上下文
auto *context = new QOpenGLContext();
// 关键点:设置与全局共享上下文共享 (这样主线程才能看到纹理)
context->setShareContext(QOpenGLContext::globalShareContext());
if (!context->create()) {
delete context;
return nullptr;
}
// 创建离屏表面 (因为子线程没有窗口,需要一个假的绘制表面)
auto *surface = new QOffscreenSurface();
surface->setFormat(context->format());
surface->create();
// 绑定上下文
if (!context->makeCurrent(surface)) {
delete surface;
delete context;
return nullptr;
}
// 执行真正的耗时加载
// 调用我们在 Manager 里新写的函数
LAppModel *model = LAppLive2DManager::GetInstance()->LoadModelInstance(dir, filename);
// 清理子线程资源
context->doneCurrent();
delete surface;
delete context;
return model;
});
// 监控任务结束
auto *watcher = new QFutureWatcher<LAppModel *>();
connect(watcher, &QFutureWatcher<LAppModel *>::finished, this, [this, watcher]() {
// 下面的代码在主线程中执行
LAppModel *newModel = watcher->result();
if (newModel) {
// 调用挂载函数,瞬间完成切换
LAppLive2DManager::GetInstance()->MountLoadedModel(newModel);
ElaMessageBar::success(ElaMessageBarType::BottomRight, "成功", "模型加载完成", 2000, this);
} else {
ElaMessageBar::error(ElaMessageBarType::BottomRight, "错误", "模型加载失败 (OpenGL环境异常)", 2000, this);
}
// 恢复 UI
modelUsePushButton->setEnabled(true);
modelUsePushButton->setText("使用模型");
modelChoosePushButton->setEnabled(true);
watcher->deleteLater();
});
// 开始监控
watcher->setFuture(future);
});
QWidget* centralWidget = new QWidget(this);
centralWidget->setWindowTitle("模型商店");
QVBoxLayout* centerLayout = new QVBoxLayout(centralWidget);
QWidget *centralWidget = new QWidget(this);
centralWidget->setWindowTitle("模型设置");
QVBoxLayout *centerLayout = new QVBoxLayout(centralWidget);
centerLayout->addWidget(modelSetArea);
centerLayout->addStretch();
centerLayout->setContentsMargins(0, 0, 0, 0);
addCentralWidget(centralWidget, true, true, 0);
}
// 返回 pair<目录路径, 文件名>
std::pair<QString, QString> ModelPage::splitPath(const QString& fullPath)
{
QFileInfo fileInfo(fullPath);
std::pair<QString, QString> ModelPage::splitPath(const QString &fullPath) {
const QFileInfo fileInfo(fullPath);
// 获取目录部分(自动处理末尾斜杠)
QString dirPath = fileInfo.dir().absolutePath() + "/";
@@ -98,7 +158,5 @@ std::pair<QString, QString> ModelPage::splitPath(const QString& fullPath)
return {dirPath, fileName};
}
ModelPage::~ModelPage()
{
}
ModelPage::~ModelPage() {
}
+1 -1
View File
@@ -79,7 +79,7 @@ void Setting::initNavigationBar()
addPageNode("主页", homePage, ElaIconType::House);
// 添加模型商店节点
addPageNode("模型商店", modelPage, ElaIconType::Shop);
addPageNode("模型设置", modelPage, ElaIconType::Shop);
// 添加网络连接设置节点
addPageNode("连接设置", networkPage, ElaIconType::NetworkWired);