diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/control/IRow.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/control/IRow.java index b859db7..80099d4 100644 --- a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/control/IRow.java +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/control/IRow.java @@ -5,8 +5,7 @@ import org.gtk.gtk.*; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.PropertyKey; -import java.util.function.Consumer; -import java.util.function.IntConsumer; +import java.util.function.*; public class IRow extends Box { public IRow(@PropertyKey(resourceBundle = I18n.BUNDLE) String title, @PropertyKey(resourceBundle = I18n.BUNDLE) @Nullable String subtitle, Object... args) { @@ -30,51 +29,58 @@ public class IRow extends Box { append(head); } - public void setButton(@PropertyKey(resourceBundle = I18n.BUNDLE) String text, Button.Clicked action) { - firstChild.hexpand = true; + public Button setButton(@PropertyKey(resourceBundle = I18n.BUNDLE) String text, Button.Clicked action) { Button btn = Button.newWithLabel(I18n.get(text)); - btn.valign = Align.CENTER; - btn.halign = Align.END; + packSmallEnd(btn); btn.onClicked(action); - append(btn); + return btn; } public DropDown setDropdown(String[] options, int defaultIndex, IntConsumer changed) { - firstChild.hexpand = true; DropDown btn = new DropDown(new StringList(options), null); - btn.valign = Align.CENTER; - btn.halign = Align.END; + packSmallEnd(btn); btn.selected = defaultIndex; btn.onNotify("selected", pspec -> { changed.accept(btn.selected); }); btn.expression = new PropertyExpression(StringObject.type, null, "string"); - append(btn); return btn; } - public Switch setCheckbox(boolean value, Consumer changed) { - firstChild.hexpand = true; + public Switch setSwitch(boolean value, Consumer changed) { Switch btn = new Switch(); - btn.valign = Align.CENTER; - btn.halign = Align.END; + packSmallEnd(btn); btn.active = value; btn.onStateSet(state -> { changed.accept(state); return false; }); - append(btn); return btn; } - public Entry setEntry(String value, Consumer onChanged) { + public SpinButton setSpinButton(double value, double min, double max, double step, DoubleConsumer changed) { + SpinButton btn = SpinButton.newWithRange(min, max, step); + packSmallEnd(btn); + btn.value = value; + btn.onValueChanged(() -> changed.accept(btn.value)); + return btn; + } + + public Entry setEntry(String value, Consumer changed) { Entry entry = new Entry(); entry.text = value == null ? "" : value; entry.hexpand = true; entry.valign = Align.CENTER; entry.halign = Align.FILL; - entry.onChanged(() -> onChanged.accept(entry.text)); + entry.onChanged(() -> changed.accept(entry.text)); append(entry); return entry; } + + private void packSmallEnd(Widget widget) { + firstChild.hexpand = true; + widget.valign = Align.CENTER; + widget.halign = Align.END; + append(widget); + } } diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/util/Memory.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/util/Memory.java new file mode 100644 index 0000000..5b45916 --- /dev/null +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/util/Memory.java @@ -0,0 +1,103 @@ +package io.gitlab.jfronny.inceptum.gtk.util; + +import io.gitlab.jfronny.commons.LazySupplier; +import io.gitlab.jfronny.commons.OSUtils; +import io.gitlab.jfronny.inceptum.common.Utils; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public class Memory { + public static final long KB = 1024; + public static final long MB = KB * 1024; + public static final long GB = MB * 1024; + private static final MI impl = switch (OSUtils.TYPE) { + case LINUX -> new LinuxMI(); + case WINDOWS -> new WindowsMI(); + case MAC_OS -> new MacOsMI(); + }; + + private static final LazySupplier totalMemory = new LazySupplier<>(impl::getTotalMemory); + + public static long getMaxMBForInstance() { + return Math.max(totalMemory.get() / MB - 1024, 1024); + } + + private interface MI { + long getTotalMemory(); + } + + private static class LinuxMI implements MI { + @Override + public long getTotalMemory() { + try (Stream stream = Files.lines(Path.of("/proc/meminfo"))) { + var memTotal = stream + .filter(s -> s.startsWith("MemTotal:")) + .map(s -> s.substring("MemTotal:".length())) + .map(String::trim) + .findFirst(); + if (memTotal.isPresent()) { + return parseDecimalMemorySizeToBinary(memTotal.get()); + } else { + Utils.LOGGER.error("Could not find total memory"); + return 32 * GB; + } + } catch (IOException e) { + Utils.LOGGER.error("Could not get total memory", e); + return 32 * GB; + } + } + + // Taken from oshi + private static final Pattern BYTES_PATTERN = Pattern.compile("(\\d+) ?([kKMGT]?B?).*"); + private static final Pattern WHITESPACES = Pattern.compile("\\s+"); + private static long parseDecimalMemorySizeToBinary(String size) { + String[] mem = WHITESPACES.split(size); + if (mem.length < 2) { + // If no spaces, use regexp + Matcher matcher = BYTES_PATTERN.matcher(size.trim()); + if (matcher.find() && matcher.groupCount() == 2) { + mem = new String[2]; + mem[0] = matcher.group(1); + mem[1] = matcher.group(2); + } + } + long capacity = parseLongOrDefault(mem[0], 0L); + if (mem.length == 2 && mem[1].length() > 1) { + switch (mem[1].charAt(0)) { + case 'T' -> capacity <<= 40; + case 'G' -> capacity <<= 30; + case 'M' -> capacity <<= 20; + case 'K', 'k' -> capacity <<= 10; + default -> {} + } + } + return capacity; + } + private static long parseLongOrDefault(String s, long defaultLong) { + try { + return Long.parseLong(s); + } catch (NumberFormatException e) { + return defaultLong; + } + } + } + + private static class WindowsMI implements MI { + @Override + public long getTotalMemory() { + return 32 * GB; // This is currently unsupported, but any implementations by Windows user using panama are welcome + } + } + + private static class MacOsMI implements MI { + @Override + public long getTotalMemory() { + return 32 * GB; // This is currently unsupported, but any implementations by MacOS user using panama are welcome + } + } +} diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/LauncherSettingsWindow.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/LauncherSettingsWindow.java index df4911c..1dc756b 100644 --- a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/LauncherSettingsWindow.java +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/LauncherSettingsWindow.java @@ -23,7 +23,7 @@ public class LauncherSettingsWindow extends Window { { IRow row = new IRow("settings.snapshots", "settings.snapshots.subtitle"); listBox.append(row); - row.setCheckbox(InceptumConfig.snapshots, b -> { + row.setSwitch(InceptumConfig.snapshots, b -> { InceptumConfig.snapshots = b; InceptumConfig.saveConfig(); }); diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/GeneralTab.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/GeneralTab.java index 7eabbaf..17893e1 100644 --- a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/GeneralTab.java +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/GeneralTab.java @@ -2,11 +2,11 @@ package io.gitlab.jfronny.inceptum.gtk.window.edit; import io.github.jwharm.javagi.GErrorException; import io.gitlab.jfronny.commons.ArgumentsTokenizer; -import io.gitlab.jfronny.commons.OSUtils; import io.gitlab.jfronny.commons.io.JFiles; import io.gitlab.jfronny.inceptum.common.*; import io.gitlab.jfronny.inceptum.gtk.control.ILabel; import io.gitlab.jfronny.inceptum.gtk.util.I18n; +import io.gitlab.jfronny.inceptum.gtk.util.Memory; import io.gitlab.jfronny.inceptum.gtk.window.InstanceSettingsWindow; import io.gitlab.jfronny.inceptum.launcher.api.FabricMetaApi; import io.gitlab.jfronny.inceptum.launcher.api.McApi; @@ -78,7 +78,7 @@ public class GeneralTab extends SettingsTab { var fabricRow = section.row("instance.settings.general.game.fabric.enabled", "instance.settings.general.game.fabric.enabled.subtitle"); var loaderRow = section.row("instance.settings.general.game.fabric.version", "instance.settings.general.game.fabric.version.subtitle"); loaderRow.visible = instance.isFabric; - ref.fabricEnabled = fabricRow.setCheckbox(instance.isFabric, bl -> { + ref.fabricEnabled = fabricRow.setSwitch(instance.isFabric, bl -> { if (bl) { if (ref.fabricVersions != null && ref.fabricVersions.length != 0 && ref.defaultFabric != null) { instance.meta.gameVersion = GameVersionParser.createVersionWithFabric(instance.gameVersion, ref.defaultFabric); @@ -151,7 +151,22 @@ public class GeneralTab extends SettingsTab { }); row.append(btn); } - //TODO minMem/maxMem (slider?) + { + var row = section.row("instance.settings.general.game.memory.min", "instance.settings.general.game.memory.min.subtitle"); + row.setSpinButton(instance.meta.minMem == null ? 512 : instance.meta.minMem / Memory.MB, 512, Memory.maxMBForInstance, 128, v -> { + instance.meta.minMem = (long) (v * Memory.MB); + if (instance.meta.minMem == Memory.GB / 2) instance.meta.minMem = null; + instance.writeMeta(); + }); + } + { + var row = section.row("instance.settings.general.game.memory.max", "instance.settings.general.game.memory.max.subtitle"); + row.setSpinButton(instance.meta.maxMem == null ? 1024 : instance.meta.maxMem / Memory.MB, 1024, Memory.maxMBForInstance, 128, v -> { + instance.meta.maxMem = (long) (v * Memory.MB); + if (instance.meta.maxMem == Memory.GB) instance.meta.maxMem = null; + instance.writeMeta(); + }); + } }); section("instance.settings.general.args", section -> { if (instance.meta.arguments == null) instance.meta.arguments = new InstanceMeta.Arguments(List.of(), List.of(), List.of()); diff --git a/launcher-gtk/src/main/resources/inceptum.properties b/launcher-gtk/src/main/resources/inceptum.properties index 86961ca..382530c 100644 --- a/launcher-gtk/src/main/resources/inceptum.properties +++ b/launcher-gtk/src/main/resources/inceptum.properties @@ -90,4 +90,8 @@ instance.settings.general.game.fabric.enabled.subtitle=Whether the Fabric Loader instance.settings.general.game.fabric.version=Fabric Version instance.settings.general.game.fabric.version.subtitle=Version of the Fabric Loader to use instance.settings.general.game.java=Java -instance.settings.general.game.java.subtitle=The path of the custom Java binary to use (or empty for default) \ No newline at end of file +instance.settings.general.game.java.subtitle=The path of the custom Java binary to use (or empty for default) +instance.settings.general.game.memory.min=Minimum Memory +instance.settings.general.game.memory.min.subtitle=The minimum amount of Memory Minecraft will allocate (in MiB) +instance.settings.general.game.memory.max=Maximum Memory +instance.settings.general.game.memory.max.subtitle=The maximum amount of Memory Minecraft will allocate (in MiB) \ No newline at end of file diff --git a/launcher-gtk/src/main/resources/inceptum_de.properties b/launcher-gtk/src/main/resources/inceptum_de.properties index ff1d694..29a727c 100644 --- a/launcher-gtk/src/main/resources/inceptum_de.properties +++ b/launcher-gtk/src/main/resources/inceptum_de.properties @@ -90,4 +90,8 @@ instance.settings.general.game.fabric.enabled.subtitle=Ob Fabric-Loader f instance.settings.general.game.fabric.version=Fabric-Version instance.settings.general.game.fabric.version.subtitle=Zu verwendende Version des Fabric-Loaders instance.settings.general.game.java=Java -instance.settings.general.game.java.subtitle=Pfad der Java-Binärdatei, die diese Instanz verwenden soll. Leer lassen um die Standard-Binärdatei zu nutzen. \ No newline at end of file +instance.settings.general.game.java.subtitle=Pfad der Java-Binärdatei, die diese Instanz verwenden soll. Leer lassen um die Standard-Binärdatei zu nutzen. +instance.settings.general.game.memory.min=Minimale Speichernutzung +instance.settings.general.game.memory.min.subtitle=Die minimale Speichernutzung dieser Instanz (in MiB) +instance.settings.general.game.memory.max=Maximale Speichernutzung +instance.settings.general.game.memory.max.subtitle=Die maximale Speichernutzung dieser Instanz (in MiB) \ No newline at end of file