Support for CurseForge recommended memory (#3711)

This commit is contained in:
TheKodeToad 2025-05-31 17:22:06 +00:00 committed by GitHub
commit 43dcafda60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 274 additions and 135 deletions

View File

@ -211,6 +211,7 @@ void InstanceImportTask::processZipPack()
progressStep->status = status;
stepProgress(*progressStep);
});
connect(zipTask.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); });
m_task.reset(zipTask);
zipTask->start();
}
@ -334,6 +335,8 @@ void InstanceImportTask::processFlame()
connect(inst_creation_task.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
connect(inst_creation_task.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); });
m_task.reset(inst_creation_task);
setAbortable(true);
m_task->start();
@ -431,6 +434,8 @@ void InstanceImportTask::processModrinth()
connect(inst_creation_task.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
connect(inst_creation_task.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); });
m_task.reset(inst_creation_task);
setAbortable(true);
m_task->start();

View File

@ -248,6 +248,7 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerSetting("ExportSummary", "");
m_settings->registerSetting("ExportAuthor", "");
m_settings->registerSetting("ExportOptionalFiles", true);
m_settings->registerSetting("ExportRecommendedRAM");
qDebug() << "Instance-type specific settings were loaded!";

View File

@ -54,6 +54,7 @@
#include "settings/INISettingsObject.h"
#include "sys.h"
#include "tasks/ConcurrentTask.h"
#include "ui/dialogs/BlockedModsDialog.h"
#include "ui/dialogs/CustomMessageBox.h"
@ -418,6 +419,24 @@ bool FlameCreationTask::createInstance()
}
}
int recommendedRAM = m_pack.minecraft.recommendedRAM;
// only set memory if this is a fresh instance
if (m_instance == nullptr && recommendedRAM > 0) {
const uint64_t sysMiB = Sys::getSystemRam() / Sys::mebibyte;
const uint64_t max = sysMiB * 0.9;
if (recommendedRAM > max) {
logWarning(tr("The recommended memory of the modpack exceeds 90% of your system RAM—reducing it from %1 MiB to %2 MiB!")
.arg(recommendedRAM)
.arg(max));
recommendedRAM = max;
}
instance.settings()->set("OverrideMemory", true);
instance.settings()->set("MaxMemAlloc", recommendedRAM);
}
QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods");
QFileInfo jarmodsInfo(jarmodsPath);
if (jarmodsInfo.isDir()) {

View File

@ -41,22 +41,8 @@
const QString FlamePackExportTask::TEMPLATE = "<li><a href=\"{url}\">{name}{authors}</a></li>\n";
const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" });
FlamePackExportTask::FlamePackExportTask(const QString& name,
const QString& version,
const QString& author,
bool optionalFiles,
InstancePtr instance,
const QString& output,
MMCZip::FilterFileFunction filter)
: name(name)
, version(version)
, author(author)
, optionalFiles(optionalFiles)
, instance(instance)
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
, gameRoot(instance->gameRoot())
, output(output)
, filter(filter)
FlamePackExportTask::FlamePackExportTask(FlamePackExportOptions&& options)
: m_options(std::move(options)), m_gameRoot(m_options.instance->gameRoot())
{}
void FlamePackExportTask::executeTask()
@ -81,7 +67,7 @@ void FlamePackExportTask::collectFiles()
QCoreApplication::processEvents();
files.clear();
if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) {
if (!MMCZip::collectFileListRecursively(m_options.instance->gameRoot(), nullptr, &files, m_options.filter)) {
emitFailed(tr("Could not search for files"));
return;
}
@ -89,11 +75,8 @@ void FlamePackExportTask::collectFiles()
pendingHashes.clear();
resolvedFiles.clear();
if (mcInstance != nullptr) {
mcInstance->loaderModList()->update();
connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, &FlamePackExportTask::collectHashes);
} else
collectHashes();
m_options.instance->loaderModList()->update();
connect(m_options.instance->loaderModList().get(), &ModFolderModel::updateFinished, this, &FlamePackExportTask::collectHashes);
}
void FlamePackExportTask::collectHashes()
@ -101,11 +84,11 @@ void FlamePackExportTask::collectHashes()
setAbortable(true);
setStatus(tr("Finding file hashes..."));
setProgress(1, 5);
auto allMods = mcInstance->loaderModList()->allMods();
auto allMods = m_options.instance->loaderModList()->allMods();
ConcurrentTask::Ptr hashingTask(new ConcurrentTask("MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
task.reset(hashingTask);
for (const QFileInfo& file : files) {
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
const QString relative = m_gameRoot.relativeFilePath(file.absoluteFilePath());
// require sensible file types
if (!std::any_of(FILE_EXTENSIONS.begin(), FILE_EXTENSIONS.end(), [&relative](const QString& extension) {
return relative.endsWith('.' + extension) || relative.endsWith('.' + extension + ".disabled");
@ -335,13 +318,13 @@ void FlamePackExportTask::buildZip()
setStatus(tr("Adding files..."));
setProgress(4, 5);
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true, false);
auto zipTask = makeShared<MMCZip::ExportToZipTask>(m_options.output, m_gameRoot, files, "overrides/", true, false);
zipTask->addExtraFile("manifest.json", generateIndex());
zipTask->addExtraFile("modlist.html", generateHTML());
QStringList exclude;
std::transform(resolvedFiles.keyBegin(), resolvedFiles.keyEnd(), std::back_insert_iterator(exclude),
[this](QString file) { return gameRoot.relativeFilePath(file); });
[this](QString file) { return m_gameRoot.relativeFilePath(file); });
zipTask->setExcludeFiles(exclude);
auto progressStep = std::make_shared<TaskStepProgress>();
@ -376,13 +359,14 @@ QByteArray FlamePackExportTask::generateIndex()
QJsonObject obj;
obj["manifestType"] = "minecraftModpack";
obj["manifestVersion"] = 1;
obj["name"] = name;
obj["version"] = version;
obj["author"] = author;
obj["name"] = m_options.name;
obj["version"] = m_options.version;
obj["author"] = m_options.author;
obj["overrides"] = "overrides";
if (mcInstance) {
QJsonObject version;
auto profile = mcInstance->getPackProfile();
auto profile = m_options.instance->getPackProfile();
// collect all supported components
const ComponentPtr minecraft = profile->getComponent("net.minecraft");
const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader");
@ -413,15 +397,18 @@ QByteArray FlamePackExportTask::generateIndex()
loader["primary"] = true;
version["modLoaders"] = QJsonArray({ loader });
}
if (m_options.recommendedRAM > 0)
version["recommendedRam"] = m_options.recommendedRAM;
obj["minecraft"] = version;
}
QJsonArray files;
for (auto mod : resolvedFiles) {
QJsonObject file;
file["projectID"] = mod.addonId;
file["fileID"] = mod.version;
file["required"] = mod.enabled || !optionalFiles;
file["required"] = mod.enabled || !m_options.optionalFiles;
files << file;
}
obj["files"] = files;

View File

@ -19,22 +19,26 @@
#pragma once
#include "BaseInstance.h"
#include "MMCZip.h"
#include "minecraft/MinecraftInstance.h"
#include "modplatform/flame/FlameAPI.h"
#include "tasks/Task.h"
struct FlamePackExportOptions {
QString name;
QString version;
QString author;
bool optionalFiles;
MinecraftInstancePtr instance;
QString output;
MMCZip::FilterFileFunction filter;
int recommendedRAM;
};
class FlamePackExportTask : public Task {
Q_OBJECT
public:
FlamePackExportTask(const QString& name,
const QString& version,
const QString& author,
bool optionalFiles,
InstancePtr instance,
const QString& output,
MMCZip::FilterFileFunction filter);
FlamePackExportTask(FlamePackExportOptions&& options);
protected:
void executeTask() override;
@ -45,13 +49,6 @@ class FlamePackExportTask : public Task {
static const QStringList FILE_EXTENSIONS;
// inputs
const QString name, version, author;
const bool optionalFiles;
const InstancePtr instance;
MinecraftInstance* mcInstance;
const QDir gameRoot;
const QString output;
const MMCZip::FilterFileFunction filter;
struct ResolvedFile {
int addonId;
@ -70,6 +67,9 @@ class FlamePackExportTask : public Task {
bool isMod;
};
FlamePackExportOptions m_options;
QDir m_gameRoot;
FlameAPI api;
QFileInfoList files;

View File

@ -27,6 +27,7 @@ static void loadMinecraftV1(Flame::Minecraft& m, QJsonObject& minecraft)
loadModloaderV1(loader, obj);
m.modLoaders.append(loader);
}
m.recommendedRAM = Json::ensureInteger(minecraft, "recommendedRam", 0);
}
static void loadManifestV1(Flame::Manifest& pack, QJsonObject& manifest)

View File

@ -67,6 +67,7 @@ struct Minecraft {
QString version;
QString libraries;
QList<Flame::Modloader> modLoaders;
int recommendedRAM;
};
struct Manifest {

View File

@ -196,6 +196,8 @@ void Task::logWarning(const QString& line)
{
qWarning() << line;
m_Warnings.append(line);
emit warningLogged(line);
}
QStringList Task::warnings() const

View File

@ -145,6 +145,7 @@ class Task : public QObject, public QRunnable {
void failed(QString reason);
void status(QString status);
void details(QString details);
void warningLogged(const QString& warning);
void stepProgress(TaskStepProgress const& task_progress);
//! Emitted when the canAbort() status has changed. */

View File

@ -71,6 +71,7 @@
#include <QToolButton>
#include <QWidget>
#include <QWidgetAction>
#include <memory>
#include <BaseInstance.h>
#include <BuildConfig.h>
@ -1439,15 +1440,18 @@ void MainWindow::on_actionExportInstanceZip_triggered()
void MainWindow::on_actionExportInstanceMrPack_triggered()
{
if (m_selectedInstance) {
ExportPackDialog dlg(m_selectedInstance, this);
auto instance = std::dynamic_pointer_cast<MinecraftInstance>(m_selectedInstance);
if (instance != nullptr) {
ExportPackDialog dlg(instance, this);
dlg.exec();
}
}
}
void MainWindow::on_actionExportInstanceFlamePack_triggered()
{
if (m_selectedInstance) {
auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
auto instance = std::dynamic_pointer_cast<MinecraftInstance>(m_selectedInstance);
if (instance) {
if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft");
cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") {
@ -1456,7 +1460,7 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered()
msgBox.exec();
return;
}
ExportPackDialog dlg(m_selectedInstance, this, ModPlatform::ResourceProvider::FLAME);
ExportPackDialog dlg(instance, this, ModPlatform::ResourceProvider::FLAME);
dlg.exec();
}
}

View File

@ -17,7 +17,7 @@
*/
#include "ExportPackDialog.h"
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
#include "modplatform/ModIndex.h"
#include "modplatform/flame/FlamePackExportTask.h"
#include "ui/dialogs/CustomMessageBox.h"
@ -33,7 +33,7 @@
#include "MMCZip.h"
#include "modplatform/modrinth/ModrinthPackExportTask.h"
ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider)
ExportPackDialog::ExportPackDialog(MinecraftInstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider)
: QDialog(parent), m_instance(instance), m_ui(new Ui::ExportPackDialog), m_provider(provider)
{
Q_ASSERT(m_provider == ModPlatform::ResourceProvider::MODRINTH || m_provider == ModPlatform::ResourceProvider::FLAME);
@ -44,12 +44,16 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
m_ui->version->setText(instance->settings()->get("ExportVersion").toString());
m_ui->optionalFiles->setChecked(instance->settings()->get("ExportOptionalFiles").toBool());
connect(m_ui->recommendedMemoryCheckBox, &QCheckBox::toggled, m_ui->recommendedMemory, &QWidget::setEnabled);
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
setWindowTitle(tr("Export Modrinth Pack"));
m_ui->authorLabel->hide();
m_ui->author->hide();
m_ui->recommendedMemoryWidget->hide();
m_ui->summary->setPlainText(instance->settings()->get("ExportSummary").toString());
} else {
setWindowTitle(tr("Export CurseForge Pack"));
@ -57,6 +61,19 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
m_ui->summaryLabel->hide();
m_ui->summary->hide();
const int recommendedRAM = instance->settings()->get("ExportRecommendedRAM").toInt();
if (recommendedRAM > 0) {
m_ui->recommendedMemoryCheckBox->setChecked(true);
m_ui->recommendedMemory->setValue(recommendedRAM);
} else {
m_ui->recommendedMemoryCheckBox->setChecked(false);
// recommend based on setting - limited to 12 GiB (CurseForge warns above this amount)
const int defaultRecommendation = qMin(m_instance->settings()->get("MaxMemAlloc").toInt(), 1024 * 12);
m_ui->recommendedMemory->setValue(defaultRecommendation);
}
m_ui->author->setText(instance->settings()->get("ExportAuthor").toString());
}
@ -120,9 +137,15 @@ void ExportPackDialog::done(int result)
if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
settings->set("ExportSummary", m_ui->summary->toPlainText());
else
else {
settings->set("ExportAuthor", m_ui->author->text());
if (m_ui->recommendedMemoryCheckBox->isChecked())
settings->set("ExportRecommendedRAM", m_ui->recommendedMemory->value());
else
settings->reset("ExportRecommendedRAM");
}
if (result == Accepted) {
const QString name = m_ui->name->text().isEmpty() ? m_instance->name() : m_ui->name->text();
const QString filename = FS::RemoveInvalidFilenameChars(name);
@ -149,8 +172,18 @@ void ExportPackDialog::done(int result)
task = new ModrinthPackExportTask(name, m_ui->version->text(), m_ui->summary->toPlainText(), m_ui->optionalFiles->isChecked(),
m_instance, output, std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1));
} else {
task = new FlamePackExportTask(name, m_ui->version->text(), m_ui->author->text(), m_ui->optionalFiles->isChecked(), m_instance,
output, std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1));
FlamePackExportOptions options{};
options.name = name;
options.version = m_ui->version->text();
options.author = m_ui->author->text();
options.optionalFiles = m_ui->optionalFiles->isChecked();
options.instance = m_instance;
options.output = output;
options.filter = std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1);
options.recommendedRAM = m_ui->recommendedMemoryCheckBox->isChecked() ? m_ui->recommendedMemory->value() : 0;
task = new FlamePackExportTask(std::move(options));
}
connect(task, &Task::failed,

View File

@ -22,6 +22,7 @@
#include "BaseInstance.h"
#include "FastFileIconProvider.h"
#include "FileIgnoreProxy.h"
#include "minecraft/MinecraftInstance.h"
#include "modplatform/ModIndex.h"
namespace Ui {
@ -32,7 +33,7 @@ class ExportPackDialog : public QDialog {
Q_OBJECT
public:
explicit ExportPackDialog(InstancePtr instance,
explicit ExportPackDialog(MinecraftInstancePtr instance,
QWidget* parent = nullptr,
ModPlatform::ResourceProvider provider = ModPlatform::ResourceProvider::MODRINTH);
~ExportPackDialog();
@ -44,7 +45,7 @@ class ExportPackDialog : public QDialog {
QString ignoreFileName();
private:
const InstancePtr m_instance;
const MinecraftInstancePtr m_instance;
Ui::ExportPackDialog* m_ui;
FileIgnoreProxy* m_proxy;
FastFileIconProvider m_icons;

View File

@ -19,37 +19,57 @@
<property name="title">
<string>&amp;Description</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<property name="labelAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>&amp;Name</string>
<string>&amp;Name:</string>
</property>
<property name="buddy">
<cstring>name</cstring>
</property>
</widget>
</item>
<item>
<item row="0" column="1">
<widget class="QLineEdit" name="name"/>
</item>
<item>
<item row="1" column="0">
<widget class="QLabel" name="versionLabel">
<property name="text">
<string>&amp;Version</string>
<string>&amp;Version:</string>
</property>
<property name="buddy">
<cstring>version</cstring>
</property>
</widget>
</item>
<item>
<item row="1" column="1">
<widget class="QLineEdit" name="version">
<property name="text">
<string>1.0.0</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="authorLabel">
<property name="text">
<string>&amp;Author:</string>
</property>
<property name="buddy">
<cstring>author</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="author"/>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="summaryLabel">
<property name="text">
@ -62,24 +82,29 @@
</item>
<item>
<widget class="QPlainTextEdit" name="summary">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
<property name="tabChangesFocus">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="authorLabel">
<property name="text">
<string>&amp;Author</string>
</property>
<property name="buddy">
<cstring>author</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="author"/>
</item>
</layout>
</widget>
</item>
@ -88,7 +113,70 @@
<property name="title">
<string>&amp;Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QWidget" name="recommendedMemoryWidget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="recommendedMemoryCheckBox">
<property name="text">
<string>&amp;Recommended Memory:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="recommendedMemory">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string> MiB</string>
</property>
<property name="minimum">
<number>8</number>
</property>
<property name="maximum">
<number>32768</number>
</property>
<property name="singleStep">
<number>128</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="filesLabel">
<property name="text">
@ -138,10 +226,6 @@
</layout>
</widget>
<tabstops>
<tabstop>name</tabstop>
<tabstop>version</tabstop>
<tabstop>summary</tabstop>
<tabstop>author</tabstop>
<tabstop>files</tabstop>
<tabstop>optionalFiles</tabstop>
</tabstops>