Design/Runtimes.md

367 lines
12 KiB
Markdown
Raw Permalink Normal View History

2021-12-19 21:22:18 -07:00
# JRE system overhaul
The idea is to do a complete rework of Java runtime management.
The game now requires different versions of the JRE and our current approach no longer works.
The complete shape of the system is undecided at this point.
Format decisions should aim for future extensibility - don't close doors that don't need to be closed forever.
## Runtime references
Runtime references would be used in version metadata files.
2021-12-19 21:22:18 -07:00
Along those, a hierarchy of options / variable defaults
Currently, vanilla hardcodes these for `x86`:
```
-Xmx800M -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=16M
2021-12-19 21:22:18 -07:00
```
And these for `amd64`:
```
-Xmx2G -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M
2021-12-19 21:22:18 -07:00
```
2021-12-20 13:37:28 -07:00
### Runtime reference in version metadata
This is how a Java runtime could be referenced in each Minecraft version:
2021-12-19 21:22:18 -07:00
```json
{
"runtime": {
2021-12-20 13:37:28 -07:00
"minVersion": 7,
2021-12-19 21:22:18 -07:00
"variables": {
2021-12-20 13:37:28 -07:00
"all": {
"x86": {
"maxHeap": 800,
"heapRegionSize": 16
},
"amd64": {
"maxHeap": 2048,
"heapRegionSize": 32,
"minStack": 1
}
2021-12-19 21:22:18 -07:00
}
}
}
}
```
2021-12-20 13:37:28 -07:00
This is how a Java runtime could be referenced in each Minecraft version, with slightly different defaults:
2021-12-19 21:22:18 -07:00
```json
{
"runtime": {
2021-12-20 13:37:28 -07:00
"minVersion": 17,
2021-12-19 21:22:18 -07:00
"variables": {
"windows": {
"x86": {
2021-12-20 13:37:28 -07:00
"minHeap": 512,
"maxHeap": 1450
2021-12-19 21:22:18 -07:00
}
},
"all": {
"x86": {
2021-12-20 13:37:28 -07:00
"minHeap": 512,
"maxHeap": 2048
2021-12-19 21:22:18 -07:00
},
"amd64": {
2021-12-20 13:37:28 -07:00
"minHeap": 512,
"maxHeap": 2048,
"minStack": 1
2021-12-19 21:22:18 -07:00
}
}
}
}
}
```
2021-12-20 13:37:28 -07:00
`minVersion` and `maxVersion` together define the range of Java versions a particular component can accept:
- `"minVersion": 17, "maxVersion": 17` means only version 17.
- `"minVersion": 16, "maxVersion": 17` means either 16 or 17. Highest will be preferred when resolving versions.
- `"minVersion": 7` means anything above and including version 7. Highest will be preferred.
- `"maxVersion": 8` means anything below and including version 8. Highest will be preferred.
If there are multiple components being composed together, range intersection operation is used to resolve the final set of eligible Java versions.
**Hypothetical example:**
- Minecraft X has `"minVersion": 6`
- Minecraft Forge for X can only work with `"minVersion": 7, "maxVersion": 8`
- When the ranges are composed together, the eligible versions are 7 and 8.
- MultiMC will try to use these, in order:
- Mojang Java 8, as long as there is one for this platform/architecture)
- System Java 8, if present
- System Java 7, if present
- FAIL to resolve and show an error
### Runtime reference in a modpack
2021-12-19 21:22:18 -07:00
In a modpack (or instance), we could say there is an optimal heap size and minimal heap size:
```json
{
"runtime": {
"minHeap": "2048M",
"optimalHeap": "4096M"
}
}
```
With this, we could:
- Warn the user if they don't have enough memory for optimal use
- Warn them harder if they have below minimum available memory
- Determine the actual heap size dynamically based on the runtime, hardware, architecture and the instance/modpack variables.
2021-12-20 13:37:28 -07:00
Actually specifying a Java version is probably not needed, but could be a thing.
2021-12-19 21:22:18 -07:00
2021-12-20 13:37:28 -07:00
## Runtimes
2021-12-19 21:22:18 -07:00
2021-12-20 13:37:28 -07:00
Every major Java version has some properties.
2021-12-19 21:22:18 -07:00
2021-12-20 13:37:28 -07:00
Some versions can be mapped to a Mojang runtime, which is ready to use and available.
2021-12-19 21:22:18 -07:00
2021-12-20 13:37:28 -07:00
Every runtime has a major version - even locally installed ones - and it can be probed.
2021-12-19 21:22:18 -07:00
2021-12-20 13:37:28 -07:00
We would have a simple runtime registry that ties major versions of Java to piston coordinates and various other properties.
2021-12-19 21:22:18 -07:00
2021-12-20 13:37:28 -07:00
The piston components can be discovered from:
https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json
2021-12-19 21:22:18 -07:00
2021-12-20 13:37:28 -07:00
This can be internally hardcoded, and only the name of the component needs to be in the MultiMC metadata.
2021-12-19 21:22:18 -07:00
2021-12-20 13:37:28 -07:00
On launch, we would load the piston index and runtime metadata files from local cache and then attempt to fetch updated ones (with usual online/offline/missing states).
2021-12-19 21:22:18 -07:00
2021-12-20 13:37:28 -07:00
### Example of a possible metadata file describing all runtimes:
```json
[
{
"majorVersion": 7,
"managedArgs": {
"minHeap": "-Xms${value}m",
"maxHeap": "-Xmx${value}m",
"minStack": "-Xss${value}m",
2021-12-20 13:37:28 -07:00
"permSize": "-XX:PermSize=${value}m",
"startOnFirstThread": "-XstartOnFirstThread"
},
"hardArgs": [
"-XX:+UseG1GC",
"-XX:G1NewSizePercent=20",
"-XX:G1ReservePercent=20",
"-XX:MaxGCPauseMillis=50",
"-XX:G1HeapRegionSize=${heapRegionSize}m",
]
},
{
"majorVersion": 8,
"pistonComponent": "jre-legacy",
"managedArgs": {
"minHeap": "-Xms${value}M",
"maxHeap": "-Xmx${value}M",
"minStack": "-Xss${value}M",
2021-12-20 13:37:28 -07:00
"startOnFirstThread": "-XstartOnFirstThread"
},
"hardArgs": [
"-XX:+UseG1GC",
"-XX:G1NewSizePercent=20",
"-XX:G1ReservePercent=20",
"-XX:MaxGCPauseMillis=50",
"-XX:G1HeapRegionSize=${heapRegionSize}m",
]
},
{
"majorVersion": 16,
"pistonComponent": "java-runtime-alpha",
"managedArgs": {
"minHeap": "-Xms${value}m",
"maxHeap": "-Xmx${value}m",
"minStack": "-Xss${value}m",
2021-12-20 13:37:28 -07:00
"startOnFirstThread": "-XstartOnFirstThread"
},
"hardArgs": [
"-XX:+UseZGC"
]
},
{
"majorVersion": 17,
"pistonComponent": "java-runtime-beta",
"managedArgs": {
"minHeap": "-Xms${value}m",
"maxHeap": "-Xmx${value}m",
"minStack": "-Xss${value}m",
2021-12-20 13:37:28 -07:00
"startOnFirstThread": "-XstartOnFirstThread"
},
"hardArgs": [
"-XX:+UseZGC"
]
}
]
```
2021-12-19 21:22:18 -07:00
2021-12-20 13:37:28 -07:00
Anything below lowest major version (currently Java 7) is not supported by MultiMC.
Args from a major version propagate to any higher version that lacks a definition in the metadata, with the exception of `pistonComponent`.
2021-12-19 21:22:18 -07:00
2021-12-20 13:37:28 -07:00
So, for example, when JRE overrides are enabled:
- User selects system Java 7.
- Args from `7` are used.
2021-12-19 21:22:18 -07:00
2021-12-20 13:37:28 -07:00
- User selects system Java 11.
- Args from `8` are used.
2021-12-19 21:22:18 -07:00
2021-12-20 13:37:28 -07:00
- User selects system Java 18.
- Args from `17` are used.
Java older than 7 will not be supported (Java code in MultiMC is built for Java 7 at the minimum). When detected, we will simply present an error.
2021-12-19 21:22:18 -07:00
`managedArgs` are managed by MultiMC and not directly visible to the user. They depend on the left hand side (variable by name) being set in order to be passed to Java. If there is no defined value of `minHeap`, `-Xms${value}m` is not passed to the JRE.
2021-12-20 14:21:34 -07:00
`startOnFirstThread` is then only specified on macOS, and replaces the current trait-based logic.
`hardArgs` are hardcoded, but do show up in the UI as 'default'/'ghost' and can be customized. Variables can be substituted into hardcoded args (like `heapRegionSize` for G1GC).
How the values for the variables are determined is internal to MultiMC and tuned to work well on all machines over time.
## Java settings in MultiMC
Existing Java settings in MultiMC have two layers -- global and per-instance.
The settings poorly accomodate resolving which Java to use, and/or determining the heap size and other java args dynamically.
For best user experience, the JRE settings should never have to be touched.
### Global settings
These affect every instance, unless overriden, and are present in `multimc.cfg`:
```
JavaArchitecture=64
JavaPath=java
JavaTimestamp=1635003862000
JavaVersion=17.0.1
JvmArgs=-verbose:gc
LastHostname=peterix
MaxMemAlloc=8192
MinMemAlloc=4096
PermGen=128
```
This allows only selecting one version of Java tied to one specificy JRE binary.
**Caching:**
When going between machines, or when the selected Java runtime disappears/changes, MultiMC asks you which Java to use again.
These variables stored in global settings are used to support that functionality.
- `JavaArchitecture` records the selected JRE's CPU architecture.
- `JavaTimestamp` records the last changed timestamp of the selected JRE's binary.
- `JavaVersion` records the selected JRE's version.
- `LastHostname` is used as a factor to pop up the JRE selection wizard.
**Actual options:**
- `JavaPath` is an absolute or relative path to the JRE binary. MultiMC can use a JRE stored in its data folder via the relative path, making it slightly more portable.
- `JvmArgs` are extra args added to the JRE args, by default empty.
- `MinMemAlloc` directly translates to minimum heap size (`-Xms${value}m` argument).
- `MinMemAlloc` directly translates to maximum heap size (`-Xmx${value}m` argument).
- `PermGen` directly translates to Java 7 and older 'Permanent Generation' size (`-Xmx${value}m` argument).
The default for PermGen in MultiMC is 128m.
```
if (permgen != 64)
{
args << QString("-XX:PermSize=%1m").arg(permgen);
}
```
### Per-Instance settings
Per-instance settings mirror the global settings, but need to be toggled on with override settings.
With `OverrideLocation`, the instance uses its own Java runtime:
```
OverrideJavaLocation=true
JavaPath=/usr/lib/jvm/java-17-openjdk/bin/java
JavaArchitecture=64
JavaTimestamp=1635003862000
JavaVersion=17.0.1
```
With `OverrideMemory`, the instance will start with different memory amounts than specified in global settings:
```
OverrideMemory=true
MaxMemAlloc=8192
MinMemAlloc=4096
PermGen=128
```
With `OverrideJavaArgs`, the instance has its own extra runtime arguments (they are **not combined** with global):
```
OverrideJavaArgs=true
JvmArgs=-verbose:gc
```
There is also a legacy setting that overrides both location and args, but not memory.
This:
```
OverrideJava=true
JavaPath=/usr/lib/jvm/java-17-openjdk/bin/java
JavaArchitecture=64
JavaTimestamp=1635003862000
JavaVersion=17.0.1
JvmArgs=-verbose:gc
```
Is equivalent to this:
```
OverrideJavaArgs=true
OverrideJavaLocation=true
JavaPath=/usr/lib/jvm/java-17-openjdk/bin/java
JavaArchitecture=64
JavaTimestamp=1635003862000
JavaVersion=17.0.1
JvmArgs=-verbose:gc
```
The `OverrideJava` turns into `OverrideJavaArgs` and `OverrideJavaLocation` on instance settings save.
2021-12-22 18:25:23 -07:00
## Approach
- Remove the globals. They could be reintroduced later, but let's not delve into that at this point.
- Add a global JRE manager
- It scans for local JREs
- It downloads and caches the JRE metadata file
- It downloads and caches the piston metadata for available/references Mojang JREs
- It determines the state of installation for the Mojang JREs
- Each instance determines its Java runtime major version range based on dependency constraints
- Then this can be fed into a model for selecting a JRE from the JRE manager
- Instance needs to track the selected JRE
- If there is no selected JRE, determine it dynamically
- Mojang JREs are prefered over local JREs, regardless of JRE version
- Highest possible version of the range is picked otherwise
- If nothing is picked, raise an error
- Needs UI
- If the use selected JRE becomes unavailable or no longer fits in the JRE version range, raise an error
- Needs UI
- In the instance settings, the user can:
- Select a JRE out of one of the detected ones, or manually browse for a binary
- Mojang = essentially nothing selected - you get that when you keep this full auto
- Manual selection of a path needs to run a check on the JRE
- Set/unset the target heap size
- Set/unset the target permgen size for Java 7
- Add to or override the args
### Questions:
- What is a reference to a JRE in the instance settings?
- Absolute/relative binary path?
- UUID -> JRE manager?
- Optional<T> of some sort?
- All of the above?
- How do we preserve (if at all) the global args?
- Maybe just tell user that these are no longer used?
- Maybe turn it into something that's added extra to args of all instances?
- Think `-verbose:gc` - this is generally useful, but you don't want to type it in everywhere...
- How do we handle the global memory settings?
- Keeping the current system for this?