1
Fork 0
mirror of https://github.com/redstrate/Novus.git synced 2025-06-07 21:57:46 +00:00

Move gar loading to a different thread and prevent unnecessary updates

This commit is contained in:
Joshua Goins 2023-09-25 23:48:03 -04:00
parent e67011ad71
commit d8d890bfbb
9 changed files with 254 additions and 129 deletions

View file

@ -18,6 +18,7 @@ public:
Q_SIGNALS:
void gearChanged();
void loadingChanged(bool loading);
public Q_SLOTS:
void clear();

View file

@ -24,6 +24,11 @@ struct GearInfo {
}
};
inline bool operator==(const GearInfo &a, const GearInfo &b)
{
return a.name == b.name && a.slot == b.slot;
}
struct GameData;
class GearView : public QWidget {
@ -51,6 +56,7 @@ public:
Q_SIGNALS:
void gearChanged();
void modelReloaded();
void loadingChanged(bool loading);
void raceChanged();
void subraceChanged();
@ -63,8 +69,8 @@ Q_SIGNALS:
void tailChanged();
public Q_SLOTS:
void clear();
void addGear(GearInfo& gear);
void removeGear(GearInfo &gear);
void setRace(Race race);
void setSubrace(Subrace subrace);
@ -76,7 +82,6 @@ public Q_SLOTS:
void setEar(int bodyVer);
void setTail(int bodyVer);
void reloadModel();
void reloadRaceDeforms();
private:
@ -84,11 +89,29 @@ private:
uint32_t maxLod = 0;
std::vector<GearInfo> gears;
struct LoadedGear {
GearInfo info;
physis_MDL mdl;
};
std::vector<LoadedGear> loadedGears;
std::vector<LoadedGear> queuedGearAdditions;
std::vector<LoadedGear> queuedGearRemovals;
bool gearDirty = false;
std::optional<int> face = 1, hair = 1, ear = 1, tail;
bool faceDirty = false, hairDirty = false, earDirty = false, tailDirty = false;
bool raceDirty = false;
MDLPart* mdlPart = nullptr;
GameData* data;
FileCache& cache;
bool updating = false;
void updatePart();
bool needsUpdate() const;
void gearUpdate(LoadedGear &gear);
void queueGearUpdate(LoadedGear &gear);
};

View file

@ -34,6 +34,8 @@ public Q_SLOTS:
void setGender(Gender gender);
void setLevelOfDetail(int lod);
void setFMVAvailable(bool available);
private Q_SLOTS:
void reloadGear();
@ -50,6 +52,7 @@ private:
QPushButton *addToFMVButton, *exportButton;
bool loadingComboData = false;
bool fmvAvailable = false;
GameData* data = nullptr;
};

View file

@ -124,6 +124,13 @@ FullModelViewer::FullModelViewer(GameData* data, FileCache& cache) : data(data)
}
connect(this, &FullModelViewer::gearChanged, this, &FullModelViewer::reloadGear);
connect(gearView, &GearView::loadingChanged, this, &FullModelViewer::loadingChanged);
connect(this, &FullModelViewer::loadingChanged, this, [this, tabWidget](const bool loading) {
raceCombo->setEnabled(!loading);
subraceCombo->setEnabled(!loading);
genderCombo->setEnabled(!loading);
tabWidget->setEnabled(!loading);
});
reloadGear();
}
@ -138,10 +145,14 @@ void FullModelViewer::clear() {
void FullModelViewer::addGear(GearInfo& info) {
switch (info.slot) {
case Slot::Body:
if (topSlot ? *topSlot != info : true) {
topSlot = info;
}
break;
case Slot::Legs:
if (bottomSlot ? *bottomSlot != info : true) {
bottomSlot = info;
}
break;
default:
break;
@ -150,9 +161,8 @@ void FullModelViewer::addGear(GearInfo& info) {
Q_EMIT gearChanged();
}
void FullModelViewer::reloadGear() {
gearView->clear();
void FullModelViewer::reloadGear()
{
if (topSlot.has_value()) {
gearView->addGear(*topSlot);
} else {

View file

@ -4,7 +4,9 @@
#include "gearview.h"
#include <QDebug>
#include <QThreadPool>
#include <QVBoxLayout>
#include <QtConcurrent>
#include "filecache.h"
#include "magic_enum.hpp"
@ -18,36 +20,30 @@ GearView::GearView(GameData* data, FileCache& cache) : data(data), cache(cache)
layout->addWidget(mdlPart);
setLayout(layout);
connect(this, &GearView::gearChanged, this, [=] {
reloadModel();
});
connect(this, &GearView::raceChanged, this, [=] {
reloadRaceDeforms();
reloadModel();
});
connect(this, &GearView::subraceChanged, this, [=] {
reloadRaceDeforms();
reloadModel();
});
connect(this, &GearView::genderChanged, this, [=] {
reloadRaceDeforms();
reloadModel();
});
connect(this, &GearView::levelOfDetailChanged, this, &GearView::reloadModel);
mdlPart->requestUpdate = [this] {
if (updating) {
return;
}
connect(this, &GearView::faceChanged, this, &GearView::reloadModel);
connect(this, &GearView::hairChanged, this, &GearView::reloadModel);
connect(this, &GearView::earChanged, this, &GearView::reloadModel);
connect(this, &GearView::tailChanged, this, &GearView::reloadModel);
if (needsUpdate()) {
updating = true;
Q_EMIT loadingChanged(true);
QtConcurrent::run(QThreadPool::globalInstance(), [this] {
updatePart();
Q_EMIT loadingChanged(false);
});
}
};
}
std::vector<std::pair<Race, Subrace>> GearView::supportedRaces() const {
std::vector<std::pair<Race, Subrace>> races;
for (const auto& gear : gears) {
for (const auto &gear : loadedGears) {
for (auto [race, race_name] : magic_enum::enum_entries<Race>()) {
for (auto subrace : physis_get_supported_subraces(race).subraces) {
auto equip_path =
physis_build_equipment_path(gear.modelInfo.primaryID, race, subrace, currentGender, gear.slot);
auto equip_path = physis_build_equipment_path(gear.info.modelInfo.primaryID, race, subrace, currentGender, gear.info.slot);
if (cache.fileExists(QLatin1String(equip_path)))
races.emplace_back(race, subrace);
@ -60,10 +56,9 @@ std::vector<std::pair<Race, Subrace>> GearView::supportedRaces() const {
std::vector<Gender> GearView::supportedGenders() const {
std::vector<Gender> genders;
for (const auto& gear : gears) {
for (const auto &gear : loadedGears) {
for (auto [gender, gender_name] : magic_enum::enum_entries<Gender>()) {
auto equip_path = physis_build_equipment_path(
gear.modelInfo.primaryID, currentRace, Subrace::Midlander, currentGender, gear.slot);
auto equip_path = physis_build_equipment_path(gear.info.modelInfo.primaryID, currentRace, Subrace::Midlander, currentGender, gear.info.slot);
if (cache.fileExists(QLatin1String(equip_path)))
genders.push_back(gender);
@ -81,16 +76,22 @@ void GearView::exportModel(const QString& fileName) {
mdlPart->exportModel(fileName);
}
void GearView::clear() {
gears.clear();
void GearView::addGear(GearInfo &gear)
{
qDebug() << "Adding gear" << gear.name.c_str();
queuedGearAdditions.emplace_back(gear);
gearDirty = true;
Q_EMIT gearChanged();
}
void GearView::addGear(GearInfo& gear) {
qDebug() << "Adding gear" << gear.name.c_str();
void GearView::removeGear(GearInfo &gear)
{
qDebug() << "Removing gear" << gear.name.c_str();
gears.push_back(gear);
queuedGearRemovals.emplace_back(gear);
gearDirty = true;
Q_EMIT gearChanged();
}
@ -102,9 +103,8 @@ void GearView::setRace(Race race) {
currentRace = race;
auto supportedSubraces = physis_get_supported_subraces(race);
if (supportedSubraces.subraces[0] == currentSubrace || supportedSubraces.subraces[1] == currentSubrace) {
} else {
const auto supportedSubraces = physis_get_supported_subraces(race);
if (supportedSubraces.subraces[0] != currentSubrace && supportedSubraces.subraces[1] != currentSubrace) {
setSubrace(supportedSubraces.subraces[0]);
}
@ -114,6 +114,8 @@ void GearView::setRace(Race race) {
setTail(-1);
}
raceDirty = true;
Q_EMIT raceChanged();
}
@ -123,6 +125,12 @@ void GearView::setSubrace(Subrace subrace) {
}
currentSubrace = subrace;
// Hyur is the only race that has two different subraces
if (currentRace == Race::Hyur) {
raceDirty = true;
}
Q_EMIT subraceChanged();
}
@ -132,6 +140,9 @@ void GearView::setGender(Gender gender) {
}
currentGender = gender;
raceDirty = true;
Q_EMIT genderChanged();
}
@ -141,81 +152,126 @@ void GearView::setLevelOfDetail(int lod) {
}
currentLod = lod;
Q_EMIT levelOfDetailChanged();
}
void GearView::setFace(int bodyVer) {
if (face == bodyVer) {
void GearView::setFace(const int faceCode)
{
if (face == faceCode) {
return;
}
if (bodyVer == -1) {
if (faceCode == -1) {
face = std::nullopt;
} else {
face = bodyVer;
face = faceCode;
}
faceDirty = true;
Q_EMIT faceChanged();
}
void GearView::setHair(int bodyVer) {
if (hair == bodyVer) {
void GearView::setHair(int hairCode)
{
if (hair == hairCode) {
return;
}
if (bodyVer == -1) {
if (hairCode == -1) {
hair = std::nullopt;
} else {
hair = bodyVer;
hair = hairCode;
}
hairDirty = true;
Q_EMIT hairChanged();
}
void GearView::setEar(int bodyVer) {
if (ear == bodyVer) {
void GearView::setEar(const int earCode)
{
if (ear == earCode) {
return;
}
if (bodyVer == -1) {
if (earCode == -1) {
ear = std::nullopt;
} else {
ear = bodyVer;
ear = earCode;
}
earDirty = true;
Q_EMIT earChanged();
}
void GearView::setTail(int bodyVer) {
if (tail == bodyVer) {
void GearView::setTail(const int tailCode)
{
if (tail == tailCode) {
return;
}
if (bodyVer == -1) {
if (tailCode == -1) {
tail = std::nullopt;
} else {
tail = bodyVer;
tail = tailCode;
}
tailDirty = true;
Q_EMIT tailChanged();
}
void GearView::reloadModel() {
mdlPart->clear();
void GearView::reloadRaceDeforms()
{
qDebug() << "Loading race deform matrices for " << magic_enum::enum_name(currentRace).data() << magic_enum::enum_name(currentSubrace).data()
<< magic_enum::enum_name(currentGender).data();
const int raceCode = physis_get_race_code(currentRace, currentSubrace, currentGender);
qDebug() << "Race code: " << raceCode;
maxLod = 0;
QString skelName = QString{"c%1b0001.skel"}.arg(raceCode, 4, 10, QLatin1Char{'0'});
mdlPart->setSkeleton(physis_skeleton_from_skel(physis_read_file(skelName.toStdString().c_str())));
for (const auto& gear : gears) {
auto mdl_data = cache.lookupFile(physis_build_equipment_path(
gear.modelInfo.primaryID, currentRace, currentSubrace, currentGender, gear.slot));
// racial deforms don't work on Hyur Midlander, not needed? TODO not sure
if (currentSubrace != Subrace::Midlander) {
QString deformName = QString{"c%1_deform.json"}.arg(raceCode, 4, 10, QLatin1Char{'0'});
mdlPart->loadRaceDeformMatrices(physis_read_file(deformName.toStdString().c_str()));
} else {
for (auto &data : mdlPart->boneData) {
data.deformRaceMatrix = glm::mat4(1.0f);
}
}
}
MDLPart &GearView::part() const
{
return *mdlPart;
}
void GearView::updatePart()
{
qInfo() << raceDirty << gearDirty << updating;
if (raceDirty) {
// if race changes, all of the models need to be reloaded.
// TODO: in the future, we can be a bit smarter about this, lots of races use the same model (hyur)
for (auto &part : loadedGears) {
mdlPart->removeModel(part.mdl);
}
queuedGearAdditions = loadedGears;
loadedGears.clear();
gearDirty = true;
}
if (gearDirty) {
for (auto &gearAddition : queuedGearAdditions) {
auto mdl_data = cache.lookupFile(
physis_build_equipment_path(gearAddition.info.modelInfo.primaryID, currentRace, currentSubrace, currentGender, gearAddition.info.slot));
// attempt to load the next best race
// currently hardcoded to hyur midlander
Race fallbackRace = currentRace;
Subrace fallbackSubrace = currentSubrace;
if (mdl_data.size == 0) {
mdl_data = cache.lookupFile(physis_build_equipment_path(
gear.modelInfo.primaryID, Race::Hyur, Subrace::Midlander, currentGender, gear.slot));
mdl_data = cache.lookupFile(
physis_build_equipment_path(gearAddition.info.modelInfo.primaryID, Race::Hyur, Subrace::Midlander, currentGender, gearAddition.info.slot));
fallbackRace = Race::Hyur;
fallbackSubrace = Subrace::Midlander;
}
@ -225,10 +281,11 @@ void GearView::reloadModel() {
std::vector<physis_Material> materials;
for (int i = 0; i < mdl.num_material_names; i++) {
const char* material_name = mdl.material_names[i];
const char *material_name = mdl.material_names[i];
const std::string mtrl_path = gear.getMtrlPath(material_name);
const std::string skinmtrl_path = physis_build_skin_material_path(physis_get_race_code(fallbackRace, fallbackSubrace, currentGender), 1, material_name);
const std::string mtrl_path = gearAddition.info.getMtrlPath(material_name);
const std::string skinmtrl_path =
physis_build_skin_material_path(physis_get_race_code(fallbackRace, fallbackSubrace, currentGender), 1, material_name);
if (cache.fileExists(QLatin1String(mtrl_path.c_str()))) {
auto mat = physis_material_parse(cache.lookupFile(mtrl_path.c_str()));
@ -244,12 +301,24 @@ void GearView::reloadModel() {
maxLod = std::max(mdl.num_lod, maxLod);
mdlPart->addModel(mdl, materials, currentLod);
gearAddition.mdl = mdl;
loadedGears.push_back(gearAddition);
}
}
for (auto &queuedRemoval : queuedGearRemovals) {
mdlPart->removeModel(queuedRemoval.mdl);
loadedGears.erase(std::remove_if(loadedGears.begin(),
loadedGears.end(),
[queuedRemoval](const LoadedGear other) {
return queuedRemoval.info == other.info;
}),
loadedGears.end());
}
}
if (face) {
auto mdl_data = cache.lookupFile(physis_build_character_path(
CharacterCategory::Face, *face, currentRace, currentSubrace, currentGender));
auto mdl_data = cache.lookupFile(physis_build_character_path(CharacterCategory::Face, *face, currentRace, currentSubrace, currentGender));
if (mdl_data.size > 0) {
auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data);
@ -270,8 +339,7 @@ void GearView::reloadModel() {
}
if (hair) {
auto mdl_data = cache.lookupFile(physis_build_character_path(
CharacterCategory::Hair, *hair, currentRace, currentSubrace, currentGender));
auto mdl_data = cache.lookupFile(physis_build_character_path(CharacterCategory::Hair, *hair, currentRace, currentSubrace, currentGender));
if (mdl_data.size > 0) {
auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data);
@ -292,8 +360,7 @@ void GearView::reloadModel() {
}
if (ear) {
auto mdl_data = cache.lookupFile(physis_build_character_path(
CharacterCategory::Hair, *ear, currentRace, currentSubrace, currentGender));
auto mdl_data = cache.lookupFile(physis_build_character_path(CharacterCategory::Hair, *ear, currentRace, currentSubrace, currentGender));
if (mdl_data.size > 0) {
auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data);
@ -314,8 +381,7 @@ void GearView::reloadModel() {
}
if (tail) {
auto mdl_data = cache.lookupFile(physis_build_character_path(
CharacterCategory::Tail, *tail, currentRace, currentSubrace, currentGender));
auto mdl_data = cache.lookupFile(physis_build_character_path(CharacterCategory::Tail, *tail, currentRace, currentSubrace, currentGender));
if (mdl_data.size > 0) {
auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data);
@ -330,31 +396,18 @@ void GearView::reloadModel() {
}
}
Q_EMIT modelReloaded();
raceDirty = false;
gearDirty = false;
updating = false;
faceDirty = false;
hairDirty = false;
earDirty = false;
tailDirty = false;
}
void GearView::reloadRaceDeforms() {
qDebug() << "Loading race deform matrices for " << magic_enum::enum_name(currentRace).data()
<< magic_enum::enum_name(currentSubrace).data() << magic_enum::enum_name(currentGender).data();
const int raceCode = physis_get_race_code(currentRace, currentSubrace, currentGender);
qDebug() << "Race code: " << raceCode;
QString skelName = QString{"c%1b0001.skel"}.arg(raceCode, 4, 10, QLatin1Char{'0'});
mdlPart->setSkeleton(physis_skeleton_from_skel(physis_read_file(skelName.toStdString().c_str())));
// racial deforms don't work on Hyur Midlander, not needed? TODO not sure
if (currentSubrace != Subrace::Midlander) {
QString deformName = QString{"c%1_deform.json"}.arg(raceCode, 4, 10, QLatin1Char{'0'});
mdlPart->loadRaceDeformMatrices(physis_read_file(deformName.toStdString().c_str()));
} else {
for (auto& data : mdlPart->boneData) {
data.deformRaceMatrix = glm::mat4(1.0f);
}
}
}
MDLPart& GearView::part() const {
return *mdlPart;
bool GearView::needsUpdate() const
{
return gearDirty || raceDirty || faceDirty || hairDirty || earDirty || tailDirty;
}
#include "moc_gearview.cpp"

View file

@ -83,6 +83,9 @@ MainWindow::MainWindow(GameData* in_data) : data(*in_data), cache(FileCache{*in_
connect(gearView, &SingleGearView::addToFullModelViewer, this, [=](GearInfo& info) {
fullModelViewer->addGear(info);
});
connect(fullModelViewer, &FullModelViewer::loadingChanged, this, [=](const bool loading) {
gearView->setFMVAvailable(!loading);
});
layout->addWidget(gearView);
fullModelViewer = new FullModelViewer(&data, cache);

View file

@ -100,15 +100,25 @@ SingleGearView::SingleGearView(GameData* data, FileCache& cache) : data(data) {
}
void SingleGearView::clear() {
if (currentGear) {
gearView->removeGear(*currentGear);
}
currentGear.reset();
Q_EMIT gearChanged();
}
void SingleGearView::setGear(const GearInfo& info) {
if (info != currentGear) {
if (currentGear) {
gearView->removeGear(*currentGear);
}
currentGear = info;
gearView->addGear(*currentGear);
Q_EMIT gearChanged();
}
}
void SingleGearView::setRace(Race race) {
@ -147,19 +157,16 @@ void SingleGearView::setLevelOfDetail(int lod) {
Q_EMIT levelOfDetailChanged();
}
void SingleGearView::reloadGear() {
gearView->clear();
void SingleGearView::reloadGear()
{
raceCombo->setEnabled(currentGear.has_value());
subraceCombo->setEnabled(currentGear.has_value());
genderCombo->setEnabled(currentGear.has_value());
lodCombo->setEnabled(currentGear.has_value());
addToFMVButton->setEnabled(currentGear.has_value());
addToFMVButton->setEnabled(currentGear.has_value() && fmvAvailable);
exportButton->setEnabled(currentGear.has_value());
if (currentGear.has_value()) {
gearView->addGear(*currentGear);
loadingComboData = true;
const auto oldRace = static_cast<Race>(raceCombo->itemData(raceCombo->currentIndex()).toInt());
@ -212,4 +219,12 @@ void SingleGearView::reloadGear() {
}
}
void SingleGearView::setFMVAvailable(const bool available)
{
if (fmvAvailable != available) {
fmvAvailable = available;
addToFMVButton->setEnabled(currentGear.has_value() && available);
}
}
#include "moc_singlegearview.cpp"

View file

@ -116,6 +116,9 @@ public:
}
void render() {
if (part->requestUpdate)
part->requestUpdate();
glm::vec3 position(
part->cameraDistance * sin(part->yaw),
part->cameraDistance * part->pitch,
@ -554,4 +557,14 @@ void MDLPart::calculateBone(physis_Skeleton& skeleton, physis_Bone& bone, const
}
}
void MDLPart::removeModel(const physis_MDL &mdl)
{
models.erase(std::remove_if(models.begin(),
models.end(),
[mdl](const RenderModel other) {
return mdl.lods == other.model.lods;
}),
models.end());
}
#include "moc_mdlpart.cpp"

View file

@ -47,6 +47,8 @@ public:
std::vector<BoneData> boneData;
std::function<void()> requestUpdate;
Q_SIGNALS:
void modelChanged();
void skeletonChanged();
@ -58,6 +60,8 @@ public Q_SLOTS:
/// Adds a new MDL with a list of materials used.
void addModel(physis_MDL mdl, std::vector<physis_Material> materials, int lod);
void removeModel(const physis_MDL &mdl);
/// Sets the skeleton any skinned MDLs should bind to.
void setSkeleton(physis_Skeleton skeleton);