Compare commits

..

No commits in common. "1.20" and "eol/1.16" have entirely different histories.

584 changed files with 12414 additions and 13597 deletions

View File

@ -4,75 +4,51 @@ body:
- type: markdown
attributes:
value: >-
**Need help?** Ask on [Discord](https://discord.gg/rN9Y7caguP) instead of opening an issue.
**Note: This issue tracker is not intended for support requests!** If you need help with crashes or other issues, then
you should [ask on our Discord server](https://discord.gg/rN9Y7caguP) instead. Unless you are certain that you
have found a defect, and you are able to point to where the problem is, you should not open an issue.
<br><br>
Additionally, please make sure you have done the following:
**Issues that do not meet the requirements below (or are otherwise impossible to address with the given info) will be closed without investigation.**
- type: checkboxes
id: confirmations
attributes:
label: Checklist
options:
- label: I am reporting a defect, not asking for help
required: true
- label: I have searched existing issues and this has not been reported
required: true
- label: I have reduced my mod list to the minimum required to reproduce this issue (see below)
required: true
- **Have you ensured that all of your mods (including ModernFix) are up-to-date?** The latest version of ModernFix
can always be found [on Modrinth](https://modrinth.com/mod/modernfix).
- **Have you used the [search tool](https://github.com/embeddedt/ModernFix/issues) to check whether your issue
has already been reported?** If it has been, then consider adding more information to the existing issue instead.
- **Have you determined the minimum set of instructions to reproduce the issue?** If your problem only occurs
with other mods installed, then you should narrow down exactly which mods are causing the issue. Please do not
provide your entire list of mods to us and expect that we will be able to figure out the problem.
- type: textarea
id: description
attributes:
label: Bug Description
description: >-
Describe the issue in detail. Be sure to include what you expected to happen and what actually happened.
validations:
required: true
- type: textarea
id: minimal-mods
attributes:
label: Minimal Mod List
description: >-
List ONLY the mods required to reproduce this issue. Maintainers have debugging tools that help them
locate problems quickly, but these generally don't work well in modpacks or large mod sets.
A minimal list should typically contain fewer than 10 mods.
Use this section to describe the issue you are experiencing in as much depth as possible. The description should
explain what behavior you were expecting, and why you believe the issue to be a bug. If the issue you are reporting
only occurs with specific mods installed, then provide the name and version of each mod.
Reports with large mod lists will likely be closed without investigation, unless the problem is very clear.
If you don't know which mods are causing your problem, use binary search:
1. Remove half your mods
2. Test if the issue still occurs
3. If yes, remove half again. If no, restore the last removed half and repeat from step 1.
4. Repeat until only the necessary mods remain
placeholder: "- ModernFix 5.x.x\n- SomeMod 1.2.3"
validations:
required: true
**Hint:** If you have any screenshots, videos, or other information that you feel is necessary to
explain the issue, you can attach them here.
- type: textarea
id: description-reproduction-steps
attributes:
label: Reproduction Steps
description: >-
Provide clear steps to reproduce the bug. Each step should be a single concrete action.
Maintainers are busy and need to be able to quickly replicate your problem. Your reproduction steps should be
clear enough for someone who is unfamiliar with your mods to follow in 5 minutes or less (not counting time
to launch the game).
Providing vague steps is likely to result in the issue being closed.
placeholder: "1. \n2. \n3. "
Provide as much information as possible on how to reproduce this bug. Make sure your instructions are as clear and
concise as possible, because other people will need to be able to follow your guide in order to re-create the issue.
**Hint:** A common way to fill this section out is to write a step-by-step guide.
validations:
required: true
- type: textarea
id: diagnostic-info
id: log-file
attributes:
label: Diagnostic Info
label: Log File
description: >-
Drag and drop `latest.log` from `.minecraft/logs/` for the session where the issue occurred.
Do not paste log text inline. Issues without a valid `latest.log` will be closed.
If a crash occurred, also attach the relevant file from `.minecraft/crash-reports/`.
**Hint:** You can usually find the log files within the folder `.minecraft/logs`. Most often, you will want the `latest.log`
file, since that file belongs to the last played session of the game.
placeholder: >-
Drag-and-drop the log file here.
validations:
required: true

View File

@ -11,124 +11,31 @@ on:
jobs:
build:
runs-on: ubuntu-22.04
permissions:
issues: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up JDK 21
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 21
java-version: 17
check-latest: true
- name: Check if release branch
id: check_branch
if: github.event_name == 'push'
run: |
if [[ "${{ github.ref }}" =~ ^refs/heads/[0-9]+\. ]]; then
echo "is_release=true" >> $GITHUB_OUTPUT
else
echo "is_release=false" >> $GITHUB_OUTPUT
fi
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ steps.check_branch.outputs.is_release != 'true' }}
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/1.') }}
gradle-home-cache-cleanup: true
- name: Remove tags for release on other versions
if: steps.check_branch.outputs.is_release == 'true'
run: ./scripts/tagcleaner.sh
- name: Setup project Loom cache
uses: actions/cache@v4
with:
path: |
.gradle/loom-cache
key: ${{ runner.os }}-gradle-${{ hashFiles('**/gradle.properties', '**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: ${{ runner.os }}-gradle-
- name: Build ModernFix using Gradle
run: ./gradlew build
- name: Run mixin audit
run: timeout 60 xvfb-run ./gradlew runAuditClient
- name: Publish mod to CurseForge & Modrinth
if: steps.check_branch.outputs.is_release == 'true'
run: ./gradlew publishMods copyJarToBin
env:
CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
- name: Capture mod version
if: steps.check_branch.outputs.is_release == 'true'
run: |
echo "MOD_VERSION=$(./gradlew properties -q | grep '^version:' | awk '{print $2}')" >> $GITHUB_ENV
echo "MC_VERSION=$(grep '^minecraft_version=' gradle.properties | cut -d= -f2)" >> $GITHUB_ENV
- name: Comment on fixed issues
if: steps.check_branch.outputs.is_release == 'true'
uses: actions/github-script@v7
with:
script: |
const { execSync } = require('child_process');
const branch = context.ref.replace('refs/heads/', '');
const { data: runs } = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'gradle.yml',
branch,
status: 'success',
per_page: 1
});
const logArgs = runs.workflow_runs.length > 0
? `${runs.workflow_runs[0].head_sha}..${context.sha}`
: `-1 ${context.sha}`;
const log = execSync(`git log ${logArgs} --format=%s%n%b`, { encoding: 'utf8' });
const issueNumbers = new Set();
const pattern = /(?:fix(?:es|ed)?|close[sd]?|resolve[sd]?)\s+#(\d+)/gi;
let match;
while ((match = pattern.exec(log)) !== null) {
issueNumbers.add(parseInt(match[1]));
}
if (issueNumbers.size === 0) {
console.log('No fixed issues found in commits');
return;
}
const MARKER = '<!-- modernfix-fix-tracker -->';
const modVersion = process.env.MOD_VERSION;
const mcVersion = process.env.MC_VERSION;
const newLine = `- ${modVersion} for Minecraft ${mcVersion}`;
for (const issueNumber of issueNumbers) {
try {
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
per_page: 100
});
const existing = comments.find(c => c.body.includes(MARKER));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: existing.body + `\n${newLine}`
});
console.log(`Updated comment on issue #${issueNumber}`);
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: `${MARKER}\nThe fix for this issue has been released in the following versions of ModernFix:\n${newLine}`
});
console.log(`Created comment on issue #${issueNumber}`);
}
} catch (e) {
console.log(`Could not comment on #${issueNumber}: ${e.message}`);
}
}
- name: Upload Artifacts to GitHub
uses: actions/upload-artifact@v4
with:

41
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: Release ModernFix Artifacts
on:
release:
types:
- published
jobs:
release:
if: github.repository_owner == 'embeddedt'
runs-on: ubuntu-22.04
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
check-latest: true
- name: Remove tags for release on other versions
run: ./scripts/tagcleaner.sh
- name: Build and publish mod to CurseForge & Modrinth
run: ./gradlew publishToModSites copyJarToBin
env:
CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
- name: Upload assets to GitHub
uses: AButler/upload-release-assets@v3.0
with:
files: 'bin/*'
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Add changelog to release
uses: irongut/EditRelease@v1.2.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
id: ${{ github.event.release.id }}
replacebody: true
files: "CHANGELOG.md"

View File

@ -2,15 +2,16 @@ ModernFix is a standard Minecraft-style Gradle project powered by Architectury L
run the `build` task (e.g. via `./gradlew build`). You can also use `./gradlew forge:build` or `./gradlew fabric:build`
to build for just one loader (e.g. when debugging and wanting to rebuild quickly).
You must use Java 21 to develop ModernFix as the toolchain requires it. Nonetheless, the built 1.20.1 JAR is still
compatible with Java 17.
You must use Java 17 to develop ModernFix as the toolchain requires it. Nonetheless, the 1.16 mod JARs will work on
a Minecraft instance with Java 8.
## Submitting pull requests
Code or documentation contributions are welcome. Please keep the following points in mind:
* This project supports many Minecraft versions. Ideally, contributions should be made to the oldest relevant MC version (currently 1.20)
and then they will be ported forward.
* This project supports many Minecraft versions. Ideally, contributions should be made to the oldest relevant MC version.
For instance, a PR optimizing new worldgen should be made to 1.18 (not 1.19 or 1.20) while a PR optimizing something
like recipes should be made to 1.16 (the oldest supported version).
This somewhat unconventional policy ensures that all supported versions are treated equal when it comes to development,
rather than the onus being on other modders and players to backport changes that are needed. Changes to older versions are

View File

@ -1,6 +1,7 @@
plugins {
id 'com.gradleup.shadow' version '8.3.9'
id 'com.github.johnrengelman.shadow'
id 'java-library'
id 'com.diffplug.spotless'
}
repositories {
@ -30,6 +31,7 @@ dependencies {
}
tasks.withType(JavaCompile) {
options.compilerArgs += '--enable-preview'
options.release = 17
}
@ -42,7 +44,6 @@ shadowJar {
// shadowJar bug
include '*.jar'
include 'META-INF/services/javax.annotation.processing.Processor'
include 'META-INF/gradle/incremental.annotation.processors'
include 'org/spongepowered/asm/mixin/Mixin.class'
include 'org/fury_phoenix/**/*'
include {it.getName() == 'OnlyIn.class'}
@ -51,4 +52,9 @@ shadowJar {
include {it.getName() == 'EnvType.class'}
}
spotless {
java {
removeUnusedImports()
}
}
version = '1.1.4'

View File

@ -90,19 +90,24 @@ public class ClientMixinValidator {
}
private boolean targetsClient(Object classTarget) {
if (classTarget instanceof TypeElement te) {
return isClientMarked(te);
} else if (classTarget instanceof TypeMirror tm) {
var el = types.asElement(tm);
return el != null ? targetsClient(el) : warn("TypeMirror of " + tm);
} else if (classTarget instanceof String s) {
var te = elemUtils.getTypeElement(toSourceString(s.split("\\$")[0]));
return te != null ? targetsClient(te) : warn(s);
} else {
throw new IllegalArgumentException("Unhandled type: "
return switch (classTarget) {
case TypeElement te ->
isClientMarked(te);
case TypeMirror tm -> {
var el = types.asElement(tm);
yield el != null ? targetsClient(el) : warn("TypeMirror of " + tm);
}
// If you're using a dollar sign in class names you are insane
case String s -> {
var te =
elemUtils.getTypeElement(toSourceString(s.split("\\$")[0]));
yield te != null ? targetsClient(te) : warn(s);
}
default ->
throw new IllegalArgumentException("Unhandled type: "
+ classTarget.getClass() + "\n" + "Stringified contents: "
+ classTarget.toString());
}
};
}
private boolean isClientMarked(TypeElement te) {

View File

@ -16,6 +16,7 @@ import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
@ -25,6 +26,7 @@ import org.fury_phoenix.mixinAp.config.MixinConfig;
@SupportedAnnotationTypes({"org.spongepowered.asm.mixin.Mixin", "org.embeddedt.modernfix.annotation.ClientOnlyMixin"})
@SupportedOptions({"rootProject.name", "project.name", "org.fury_phoenix.mixinAp.validator.debug"})
@SupportedSourceVersion(SourceVersion.RELEASE_17)
@AutoService(Processor.class)
public class MixinProcessor extends AbstractProcessor {
@ -36,11 +38,6 @@ public class MixinProcessor extends AbstractProcessor {
private final Map<String, List<String>> mixinConfigList = new HashMap<>();
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
@ -80,7 +77,7 @@ public class MixinProcessor extends AbstractProcessor {
List<String> mixins =
annotatedMixins.stream()
.map(TypeElement.class::cast)
.map(e -> processingEnv.getElementUtils().getBinaryName(e).toString())
.map(TypeElement::toString)
.collect(Collectors.toList());
mixinConfigList.putIfAbsent(aliases.get(annotation.getSimpleName().toString()), mixins);

View File

@ -17,7 +17,7 @@ public record MixinConfig(
@SerializedName("package")
String packageName,
String plugin,
String compatibilityLevel,
String compatabilityLevel,
@SerializedName("mixins")
List<String> commonMixins,
@SerializedName("client")
@ -25,7 +25,7 @@ public record MixinConfig(
InjectorOptions injectors, OverwriteOptions overwrites
) {
public MixinConfig(String packageName, List<String> commonMixins, List<String> clientMixins) {
this(true, "0.8", packageName, "org.embeddedt.modernfix.core.ModernFixMixinPlugin", "JAVA_17",
this(true, "0.8", packageName, "org.embeddedt.modernfix.core.ModernFixMixinPlugin", "JAVA_8",
commonMixins, clientMixins, InjectorOptions.DEFAULT, OverwriteOptions.DEFAULT);
}
public record InjectorOptions(int defaultRequire) {

View File

@ -1 +0,0 @@
org.fury_phoenix.mixinAp.annotation.MixinProcessor,aggregating

6
annotations/build.gradle Normal file
View File

@ -0,0 +1,6 @@
plugins {
id 'modernfix.common-conventions'
id 'java-library'
}
version = '1.1.0'

View File

@ -1,10 +0,0 @@
plugins {
id("java")
}
version = "1.1.0"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@ -1,9 +0,0 @@
package org.embeddedt.modernfix.annotation;
public enum FeatureLevel {
GA, BETA;
public boolean isAtLeast(FeatureLevel required) {
return this.ordinal() >= required.ordinal();
}
}

View File

@ -1,12 +0,0 @@
package org.embeddedt.modernfix.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE, ElementType.PACKAGE})
public @interface RequiresFeatureLevel {
FeatureLevel value() default FeatureLevel.GA;
}

View File

@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE, ElementType.PACKAGE})
@Target(ElementType.TYPE)
public @interface RequiresMod {
String value() default "";
}

67
build.gradle Normal file
View File

@ -0,0 +1,67 @@
plugins {
id "architectury-plugin" version "3.4-SNAPSHOT"
id "dev.architectury.loom" version "1.6-SNAPSHOT" apply false
id "maven-publish"
id 'com.matthewprenger.cursegradle' version '1.4.0' apply false
id 'com.palantir.git-version' version '1.0.0'
id 'org.ajoberstar.grgit' version '5.2.0'
id 'se.bjurr.gitchangelog.git-changelog-gradle-plugin' version '1.79.0'
id "com.modrinth.minotaur" version "2.+" apply false
id("com.diffplug.spotless") version "6.18.0" apply false
id 'modernfix.common-conventions' apply false
}
architectury {
minecraft = rootProject.minecraft_version
}
ext.archives_base_name = 'modernfix'
apply plugin: 'modernfix.common-conventions'
tasks.withType(JavaCompile).configureEach {
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
// If Javadoc is generated, this must be specified in that task too.
options.encoding = "UTF-8"
// The Minecraft launcher currently installs Java 8 for users, so your mod probably wants to target Java 8 too
// JDK 9 introduced a new way of specifying this that will make sure no newer classes or methods are used.
// We'll use that if it's available, but otherwise we'll use the older option.
def targetVersion = 8
/*
if (JavaVersion.current().isJava9Compatible()) {
options.release = targetVersion
}
*/
}
tasks.register('generateChangelog', se.bjurr.gitchangelog.plugin.gradle.GitChangelogTask) {
def details = versionDetails();
def theVersionRef
if (details.commitDistance > 0) {
theVersionRef = details.lastTag;
} else {
def secondLastTagCmd = "git describe --abbrev=0 " + details.lastTag + "^"
def secondLastTag = secondLastTagCmd.execute().text.trim()
theVersionRef = secondLastTag;
}
fromRef = theVersionRef
file = new File("${rootDir}/CHANGELOG.md");
templateContent = new File("${rootDir}/gradle/changelog.mustache").getText('UTF-8').replace("[[modernFixVersionRef]]", theVersionRef);
toCommit = "HEAD";
}
tasks.register('checkCleanTag') {
doLast {
def details = versionDetails()
if (!details.isCleanTag || versionDetails().commitDistance != 0) {
throw new GradleException('Not a clean tree.')
}
}
}
println "ModernFix: " + version

View File

@ -1,210 +0,0 @@
plugins {
id("net.neoforged.moddev.legacyforge") version("2.0.134")
id("me.modmuss50.mod-publish-plugin") version("1.1.0")
}
val minecraft_version = rootProject.properties["minecraft_version"].toString()
group = "org.embeddedt"
val gitVersion = providers.of(GitVersionSource::class) {
parameters {
minecraftVersion.set(minecraft_version)
projectDir.set(rootProject.layout.projectDirectory)
}
}
version = gitVersion.get()
base.archivesName = "modernfix-forge"
legacyForge {
enable {
forgeVersion = rootProject.properties["forge_version"].toString()
isDisableRecompilation = System.getenv("CI") == "true"
}
rootProject.properties["parchment_version"]?.let { parchmentVer ->
parchment {
minecraftVersion = minecraft_version
mappingsVersion = parchmentVer.toString()
}
}
runs {
create("client") {
client()
}
create("server") {
server()
}
create("auditClient") {
client()
jvmArguments.addAll("-Dmodernfix.auditAndExit=true", "-Djava.awt.headless=true")
}
}
mods {
create("modernfix") {
sourceSet(sourceSets.main.get())
}
}
}
mixin {
add(sourceSets.main.get(), "modernfix.refmap.json")
config("modernfix-modernfix.mixins.json")
}
tasks.named<Jar>("jar") {
manifest.attributes(mapOf(
"MixinConfigs" to "modernfix-modernfix.mixins.json",
"Specification-Version" to "1",
"Implementation-Title" to project.name,
"Implementation-Version" to version
))
}
java {
val curSourceCompatLevel = JavaVersion.VERSION_17
sourceCompatibility = curSourceCompatLevel
targetCompatibility = curSourceCompatLevel
}
repositories {
exclusiveContent {
forRepository {
maven {
// location of the maven that hosts JEI files
name = "Progwml6 maven"
url = uri("https://dvs1.progwml6.com/files/maven/")
}
}
forRepository {
maven {
name = "ModMaven"
url = uri("https://modmaven.dev")
}
}
filter {
includeGroup("mezz.jei")
}
}
exclusiveContent {
forRepository {
maven("https://cursemaven.com")
}
filter {
includeGroup("curse.maven")
}
}
}
val embed by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
isTransitive = true
}
dependencies {
implementation(project(":annotations"))
embed(project(":annotations"))
"additionalRuntimeClasspath"(project(":annotations"))
annotationProcessor(project(path = ":annotation-processor", configuration = "shadow"))
val mixinextrasVersion = rootProject.properties["mixinextras_version"].toString()
implementation("io.github.llamalad7:mixinextras-common:${mixinextrasVersion}")
annotationProcessor("net.fabricmc:sponge-mixin:0.12.5+mixin.0.8.5")
annotationProcessor("io.github.llamalad7:mixinextras-common:${mixinextrasVersion}")
implementation("io.github.llamalad7:mixinextras-forge:${mixinextrasVersion}")
"jarJar"("io.github.llamalad7:mixinextras-forge:${mixinextrasVersion}")
val jei_version = rootProject.properties["jei_version"].toString()
modCompileOnly("mezz.jei:jei-${minecraft_version}-forge:${jei_version}")
modCompileOnly("curse.maven:spark-361579:${rootProject.properties["spark_version"].toString()}")
modCompileOnly("curse.maven:ctm-267602:${rootProject.properties["ctm_version"].toString()}")
modCompileOnly("curse.maven:ldlib-626676:${rootProject.properties["ldlib_version"].toString()}")
modCompileOnly("curse.maven:supermartijncore-454372:4455391")
modCompileOnly("curse.maven:patchouli-306770:6164575")
modCompileOnly("curse.maven:cofhcore-69162:5374122")
modCompileOnly("curse.maven:resourcefullib-570073:5659871")
modCompileOnly("curse.maven:kubejs-238086:5853326")
modCompileOnly("curse.maven:terrablender-563928:6290448")
}
tasks.named<Jar>("jar") {
from(embed.map { if (it.isDirectory) it else zipTree(it) })
}
// For the AP
tasks.withType<JavaCompile>().configureEach {
if (!name.lowercase().contains("test")) {
options.compilerArgs.addAll(
listOf(
"-ArootProject.name=${rootProject.name}",
"-Aproject.name=${project.name}"
)
)
}
}
sourceSets {
main {
resources.srcDir(
layout.buildDirectory.dir("generated/sources/annotationProcessor/java/main/resources")
)
}
}
tasks.named<ProcessResources>("processResources") {
dependsOn(tasks.named("compileJava"))
inputs.property("version", project.version)
filesMatching("META-INF/mods.toml") {
expand("version" to project.version)
}
}
val finalJarTask = "reobfJar"
tasks.register<Copy>("copyJarNameConsistent") {
from(tasks.named<Jar>(finalJarTask).get().outputs.files)
into(project.file("build/libs"))
rename { _ -> "modernfix-" + project.name + "-latest.jar" }
}
tasks.register<Copy>("copyJarToBin") {
from(tasks.named<Jar>(finalJarTask).get().outputs.files)
into(rootProject.file("bin"))
mustRunAfter(tasks.named("copyJarNameConsistent"))
}
tasks.named("build") {
dependsOn("copyJarToBin", "copyJarNameConsistent")
}
publishMods {
file.set(tasks.named<Jar>(finalJarTask).flatMap { it.archiveFile })
displayName.set(tasks.named<Jar>(finalJarTask).flatMap { it.archiveFileName })
changelog = "Please check the [GitHub wiki](https://github.com/embeddedt/ModernFix/wiki/Changelog) for major changes."
type = STABLE
modLoaders.add("forge")
curseforge {
projectId = "790626"
projectSlug = "modernfix"
accessToken = providers.environmentVariable("CURSEFORGE_TOKEN")
minecraftVersions.add(minecraft_version)
}
modrinth {
projectId = "nmDcB62a"
accessToken = providers.environmentVariable("MODRINTH_TOKEN")
minecraftVersions.add(minecraft_version)
}
}
tasks.named("publishMods") {
dependsOn(finalJarTask)
}

3
buildSrc/build.gradle Normal file
View File

@ -0,0 +1,3 @@
plugins {
id 'groovy-gradle-plugin'
}

View File

@ -1,7 +0,0 @@
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}

0
buildSrc/settings.gradle Normal file
View File

View File

@ -0,0 +1,137 @@
plugins {
id 'java'
id 'architectury-plugin'
id 'maven-publish'
id 'com.diffplug.spotless'
}
spotless {
java {
removeUnusedImports()
}
}
architectury {
compileOnly()
}
group = 'org.embeddedt'
// extract base version from tag, generate other metadata ourselves
def details = versionDetails()
def plusIndex = details.lastTag.indexOf("+")
if(plusIndex == -1) {
plusIndex = details.lastTag.length()
}
def baseVersion = details.lastTag.substring(0, plusIndex)
def dirtyMarker = grgit.status().clean ? "" : ".dirty"
def commitHashMarker = details.commitDistance > 0 ? ("." + details.gitHash.substring(0, Math.min(4, details.gitHash.length()))) : ""
def preMarker = (details.commitDistance > 0 || !details.isCleanTag) ? ("-beta." + details.commitDistance) : ""
if(preMarker.length() > 0) {
// bump to next patch release
def versionParts = baseVersion.tokenize(".")
baseVersion = "${versionParts[0]}.${versionParts[1]}.${versionParts[2].toInteger() + 1}"
}
def versionString = "${baseVersion}${preMarker}+mc${minecraft_version}${commitHashMarker}${dirtyMarker}"
version = versionString
archivesBaseName = rootProject.archives_base_name + '-' + project.name
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_8
repositories {
exclusiveContent {
forRepository {
maven {
url "https://modmaven.dev"
}
}
filter {
includeGroup "appeng"
includeGroup "vazkii.patchouli"
includeGroup "mezz.jei"
}
}
exclusiveContent {
forRepository {
maven {
url "https://cursemaven.com"
}
}
filter {
includeGroup "curse.maven"
}
}
exclusiveContent {
forRepository {
maven {
name = 'ParchmentMC'
url = 'https://maven.parchmentmc.org'
}
}
filter {
includeGroup "org.parchmentmc.data"
}
}
exclusiveContent {
forRepository {
maven {
url = 'https://maven.architectury.dev'
}
}
filter {
includeGroup "me.shedaniel"
}
}
exclusiveContent {
forRepository {
maven {
url = 'https://maven.saps.dev/minecraft'
}
}
filter {
includeGroup "dev.latvian.mods"
}
}
exclusiveContent {
forRepository {
maven {
name = "Fuzs Mod Resources"
url = "https://raw.githubusercontent.com/Fuzss/modresources/main/maven/"
}
}
filter {
includeGroup "fuzs"
}
}
exclusiveContent {
forRepository {
maven {
name = "Fabric maven"
url = "https://maven.fabricmc.net/"
}
}
filter {
includeGroup "net.fabricmc"
}
}
exclusiveContent {
forRepository {
maven {
name = "Mod Menu"
url = "https://maven.terraformersmc.com/releases/"
}
}
filter {
includeGroup "com.terraformersmc"
}
}
exclusiveContent {
forRepository {
maven {
url "https://maven.tterrag.com"
}
}
filter {
includeGroup "team.chisel.ctm"
}
}
}

View File

@ -0,0 +1,41 @@
plugins {
id 'modernfix.common-conventions'
id 'dev.architectury.loom'
}
loom {
silentMojangMappingsLicense()
accessWidenerPath = file("${rootDir}/common/src/main/resources/modernfix.accesswidener")
}
dependencies {
minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
mappings loom.layered() {
officialMojangMappings()
if(rootProject.hasProperty("parchment_version")) {
parchment("org.parchmentmc.data:parchment-${minecraft_version}:${parchment_version}@zip")
}
}
implementation project(":annotations")
annotationProcessor project(path: ":annotation-processor", configuration: 'shadow')
}
project.sourceSets {
main.resources.srcDirs += [layout.buildDirectory.dir("generated/sources/annotationProcessor/java/main/resources")]
}
// hack to shut up gradle about the hack to include generated resources
tasks {
processResources {
dependsOn compileJava
}
}
tasks.withType(JavaCompile) {
options.fork = true
options.forkOptions.jvmArgs << '--enable-preview'
configure(options) {
if (!name.toLowerCase().contains('test')) {
options.compilerArgs << "-ArootProject.name=${rootProject.name}" << "-Aproject.name=${project.name}"
}
}
}

View File

@ -0,0 +1,69 @@
plugins {
id 'com.matthewprenger.cursegradle'
id 'com.modrinth.minotaur'
}
loom {
mods {
main { // to match the default mod generated for Forge
sourceSet project.sourceSets.main
sourceSet project(':common').sourceSets.main
}
}
runs {
client {
vmArgs "-Xmx1G"
vmArgs "-Xms1G"
property("mixin.debug.export", "true")
}
}
}
def copyJarNameConsistent = tasks.register('copyJarNameConsistent', Copy) {
from remapJar // shortcut for createJar.outputs.files
into project.file("build/libs")
rename { name -> "modernfix-" + project.name + "-latest.jar" }
}
def copyJarToBin = tasks.register('copyJarToBin', Copy) {
from remapJar // shortcut for createJar.outputs.files
into rootProject.file("bin")
mustRunAfter "copyJarNameConsistent"
}
tasks.build.dependsOn(copyJarToBin, copyJarNameConsistent)
def isBeta = project.version.toString().contains("beta")
curseforge {
if (System.getenv("CURSEFORGE_TOKEN") != null) {
apiKey = System.getenv("CURSEFORGE_TOKEN")
project {
id = "790626"
changelog = file("${rootDir}/CHANGELOG.md")
changelogType = "markdown"
releaseType = isBeta ? "beta" : "release"
addGameVersion project.name.capitalize()
gameVersionStrings.addAll(supported_minecraft_versions.tokenize(","))
mainArtifact remapJar
}
}
}
modrinth {
token = System.getenv("MODRINTH_TOKEN")
projectId = "modernfix" // This can be the project ID or the slug. Either will work!
versionType = isBeta ? "beta" : "release" // This is the default -- can also be `beta` or `alpha`
uploadFile = remapJar
gameVersions = supported_minecraft_versions.tokenize(",")
loaders = [project.name]
changelog.set(provider { file("${rootDir}/CHANGELOG.md").getText('UTF-8') })
}
tasks.curseforge.dependsOn(rootProject.generateChangelog)
tasks.modrinth.dependsOn(rootProject.generateChangelog)
tasks.register('publishToModSites') {
publishToModSites.dependsOn(tasks.modrinth)
publishToModSites.dependsOn(tasks.curseforge)
}

View File

@ -1,61 +0,0 @@
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ValueSource
import org.gradle.api.provider.ValueSourceParameters
import org.gradle.process.ExecOperations
import java.io.ByteArrayOutputStream
import java.io.File
import javax.inject.Inject
abstract class GitVersionSource : ValueSource<String, GitVersionSource.Parameters> {
interface Parameters : ValueSourceParameters {
val minecraftVersion: Property<String>
val projectDir: DirectoryProperty
}
@get:Inject
abstract val execOperations: ExecOperations
override fun obtain(): String {
val minecraftVersion = parameters.minecraftVersion.get()
val workDir = parameters.projectDir.get().asFile
val releaseLine = workDir.resolve("release_line.txt").readText().trim()
val patch = try {
// Find the most recent first-parent commit that touched release_line.txt
val lineStartCommit = git(workDir,
"log", "--first-parent",
"-n", "1",
"--format=%H",
"--",
"release_line.txt"
).trim()
if (lineStartCommit.isEmpty()) {
// count all first-parent commits as a safe fallback
git(workDir, "rev-list", "--count", "--first-parent", "HEAD")
.trim().toIntOrNull() ?: 0
} else {
git(workDir, "rev-list", "--count", "--first-parent", "$lineStartCommit..HEAD")
.trim().toIntOrNull() ?: 0
}
} catch (_: Exception) {
// Git is unavailable or this is not a git repository
999
}
return "$releaseLine.$patch+mc$minecraftVersion"
}
private fun git(workDir: File, vararg args: String): String {
val output = ByteArrayOutputStream()
execOperations.exec {
commandLine("git", *args)
standardOutput = output
workingDir(workDir)
}
return output.toString(Charsets.UTF_8)
}
}

37
common/build.gradle Normal file
View File

@ -0,0 +1,37 @@
plugins {
id "modernfix.mod-common-conventions"
}
architectury {
common(rootProject.enabled_platforms.split(","))
}
dependencies {
// We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies
// Do NOT use other classes from fabric loader
modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
implementation(annotationProcessor("io.github.llamalad7:mixinextras-common:${rootProject.mixinextras_version}"))
modCompileOnly("dev.latvian.mods:kubejs:${kubejs_version}") {
transitive = false
}
// Remove the next line if you don't want to depend on the API
// modApi "me.shedaniel:architectury:${rootProject.architectury_version}"
}
// don't need remapped common jar
tasks.named('remapJar') { enabled = false }
publishing {
publications {
mavenCommon(MavenPublication) {
artifactId = rootProject.archives_base_name
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
}
}

View File

@ -0,0 +1,21 @@
package org.embeddedt.modernfix;
import com.google.common.cache.CacheLoader;
import org.apache.commons.lang3.tuple.Pair;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FileWalker extends CacheLoader<Pair<Path, Integer>, List<Path>> {
public static final FileWalker INSTANCE = new FileWalker();
@Override
public List<Path> load(Pair<Path, Integer> key) throws Exception {
try(Stream<Path> stream = Files.walk(key.getLeft(), key.getRight())) {
return stream.collect(Collectors.toList());
}
}
}

View File

@ -2,7 +2,6 @@ package org.embeddedt.modernfix;
import net.minecraft.SharedConstants;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
@ -13,10 +12,9 @@ import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
import org.embeddedt.modernfix.resources.ReloadExecutor;
import org.embeddedt.modernfix.util.ClassInfoManager;
import org.spongepowered.asm.mixin.MixinEnvironment;
import java.lang.management.ManagementFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executor;
// The value here should match an entry in the META-INF/mods.toml file
public class ModernFix {
@ -33,7 +31,7 @@ public class ModernFix {
// Used to skip computing the blockstate caches twice
public static boolean runningFirstInjection = false;
private static ExecutorService resourceReloadService = null;
private static Executor resourceReloadService = null;
static {
if(ModernFixMixinPlugin.instance.isOptionEnabled("perf.dedicated_reload_executor.ReloadExecutor")) {
@ -43,21 +41,10 @@ public class ModernFix {
}
}
public static ExecutorService resourceReloadExecutor() {
public static Executor resourceReloadExecutor() {
return resourceReloadService;
}
public static void runAuditIfRequested() {
boolean auditAndExit = Boolean.getBoolean("modernfix.auditAndExit");
if (auditAndExit || Boolean.getBoolean("modernfix.auditMixinsAtStart")) {
MixinEnvironment.getCurrentEnvironment().audit();
if (auditAndExit) {
// Prevents Crash Assistant from treating mixin audit as a crash
Minecraft.getInstance().stop();
System.exit(0);
}
}
}
public ModernFix() {
INSTANCE = this;
@ -76,19 +63,14 @@ public class ModernFix {
ClassInfoManager.clear();
}
@SuppressWarnings("ConstantValue")
public void onServerDead(MinecraftServer server) {
/* Clear as much data from the integrated server as possible, in case a mod holds on to it */
try {
for(ServerLevel level : server.getAllLevels()) {
ChunkMap chunkMap = level.getChunkSource().chunkMap;
// Null check for mods that replace chunk system
if(chunkMap.updatingChunkMap != null)
chunkMap.updatingChunkMap.clear();
if(chunkMap.visibleChunkMap != null)
chunkMap.visibleChunkMap.clear();
if(chunkMap.pendingUnloads != null)
chunkMap.pendingUnloads.clear();
chunkMap.updatingChunkMap.clear();
chunkMap.visibleChunkMap.clear();
chunkMap.pendingUnloads.clear();
}
} catch(RuntimeException e) {
ModernFix.LOGGER.error("Couldn't clear chunk data", e);

View File

@ -0,0 +1,205 @@
package org.embeddedt.modernfix;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.client.Minecraft;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.entity.Entity;
import org.embeddedt.modernfix.api.constants.IntegrationConstants;
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.packet.EntityIDSyncPacket;
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
import org.embeddedt.modernfix.util.ClassInfoManager;
import org.embeddedt.modernfix.world.IntegratedWatchdog;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ModernFixClient {
public static ModernFixClient INSTANCE;
public static long worldLoadStartTime = -1;
private static int numRenderTicks;
public static float gameStartTimeSeconds = -1;
public static boolean recipesUpdated, tagsUpdated = false;
public String brandingString = null;
/**
* The list of loaded client integrations.
*/
public static List<ModernFixClientIntegration> CLIENT_INTEGRATIONS = new CopyOnWriteArrayList<>();
public ModernFixClient() {
INSTANCE = this;
// clear reserve as it's not needed
Minecraft.reserve = new byte[0];
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.branding.F3Screen")) {
brandingString = ModernFix.NAME + " " + ModernFixPlatformHooks.INSTANCE.getVersionString();
}
for(String className : ModernFixPlatformHooks.INSTANCE.getCustomModOptions().get(IntegrationConstants.CLIENT_INTEGRATION_CLASS)) {
try {
CLIENT_INTEGRATIONS.add((ModernFixClientIntegration)Class.forName(className).getDeclaredConstructor().newInstance());
} catch(ReflectiveOperationException | ClassCastException e) {
ModernFix.LOGGER.error("Could not instantiate integration {}", className, e);
}
}
if(ModernFixMixinPlugin.instance.isOptionEnabled("perf.dynamic_resources.FireIntegrationHook")) {
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
integration.onDynamicResourcesStatusChange(true);
}
}
}
public void resetWorldLoadStateMachine() {
numRenderTicks = 0;
worldLoadStartTime = -1;
recipesUpdated = false;
tagsUpdated = false;
}
public void onGameLaunchFinish() {
if(gameStartTimeSeconds >= 0)
return;
gameStartTimeSeconds = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.measure_time.GameLoad"))
ModernFix.LOGGER.warn("Game took " + gameStartTimeSeconds + " seconds to start");
ModernFixPlatformHooks.INSTANCE.onLaunchComplete();
ClassInfoManager.clear();
}
public void onRecipesUpdated() {
recipesUpdated = true;
}
public void onTagsUpdated() {
tagsUpdated = true;
}
public void onRenderTickEnd() {
if(recipesUpdated
&& tagsUpdated
&& worldLoadStartTime != -1
&& Minecraft.getInstance().player != null
&& numRenderTicks++ >= 10) {
float timeSpentLoading = ((float)(System.nanoTime() - worldLoadStartTime) / 1000000000f);
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.measure_time.WorldLoad")) {
ModernFix.LOGGER.warn("Time from main menu to in-game was " + timeSpentLoading + " seconds");
ModernFix.LOGGER.warn("Total time to load game and open world was " + (timeSpentLoading + gameStartTimeSeconds) + " seconds");
}
resetWorldLoadStateMachine();
}
}
/**
* Check if the IDs match and remap them if not.
* @return true if ID remap was needed
*/
private static boolean compareAndSwitchIds(Class<? extends Entity> eClass, String fieldName, EntityDataAccessor<?> accessor, int newId) {
if(accessor.id != newId) {
ModernFix.LOGGER.warn("Corrected ID mismatch on {} field {}. Client had {} but server wants {}.",
eClass,
fieldName,
accessor.id,
newId);
accessor.id = newId;
return true;
} else {
ModernFix.LOGGER.debug("{} {} ID fine: {}", eClass, fieldName, newId);
return false;
}
}
/**
* Horrendous hack to allow tracking every synced entity data manager.
*
* This is to ensure we can perform ID fixup on already constructed managers.
*/
public static final Set<SynchedEntityData> allEntityDatas = Collections.newSetFromMap(new WeakHashMap<>());
private static final Field entriesArrayField;
static {
Field field;
try {
field = SynchedEntityData.class.getDeclaredField("entriesArray");
field.setAccessible(true);
} catch(ReflectiveOperationException e) {
field = null;
}
entriesArrayField = field;
}
/**
* Extremely hacky method to detect and correct mismatched entity data parameter IDs on the client and server.
*
* The technique is far from ideal, but it should detect reliably and also not break already constructed entities.
*/
public static void handleEntityIDSync(EntityIDSyncPacket packet) {
Map<Class<? extends Entity>, List<Pair<String, Integer>>> info = packet.getFieldInfo();
boolean fixNeeded = false;
for(Map.Entry<Class<? extends Entity>, List<Pair<String, Integer>>> entry : info.entrySet()) {
Class<? extends Entity> eClass = entry.getKey();
for(Pair<String, Integer> field : entry.getValue()) {
String fieldName = field.getFirst();
int newId = field.getSecond();
try {
Field f = eClass.getDeclaredField(fieldName);
f.setAccessible(true);
EntityDataAccessor<?> accessor = (EntityDataAccessor<?>)f.get(null);
if(compareAndSwitchIds(eClass, fieldName, accessor, newId))
fixNeeded = true;
} catch(NoSuchFieldException e) {
ModernFix.LOGGER.warn("Couldn't find field on {}: {}", eClass, fieldName);
} catch(ReflectiveOperationException e) {
throw new RuntimeException("Unexpected exception", e);
}
}
}
/* Now the ID mappings on synced entity data instances are probably all wrong. Fix that. */
List<SynchedEntityData> dataEntries;
synchronized (allEntityDatas) {
if(fixNeeded) {
dataEntries = new ArrayList<>(allEntityDatas);
for(SynchedEntityData manager : dataEntries) {
Int2ObjectOpenHashMap<SynchedEntityData.DataItem<?>> fixedMap = new Int2ObjectOpenHashMap<>();
List<SynchedEntityData.DataItem<?>> items = new ArrayList<>(manager.itemsById.values());
for(SynchedEntityData.DataItem<?> item : items) {
fixedMap.put(item.getAccessor().id, item);
}
manager.lock.writeLock().lock();
try {
manager.itemsById.replaceAll((id, parameter) -> fixedMap.get((int)id));
if(entriesArrayField != null) {
try {
SynchedEntityData.DataItem<?>[] dataArray = new SynchedEntityData.DataItem[items.size()];
for(int i = 0; i < dataArray.length; i++) {
dataArray[i] = fixedMap.get(i);
}
entriesArrayField.set(manager, dataArray);
} catch(ReflectiveOperationException e) {
ModernFix.LOGGER.error(e);
}
}
} finally {
manager.lock.writeLock().unlock();
}
}
}
allEntityDatas.clear();
}
}
public void onServerStarted(MinecraftServer server) {
if(!ModernFixMixinPlugin.instance.isOptionEnabled("feature.integrated_server_watchdog.IntegratedWatchdog"))
return;
IntegratedWatchdog watchdog = new IntegratedWatchdog(server);
watchdog.start();
}
}

View File

@ -1,11 +1,11 @@
package org.embeddedt.modernfix.api.entrypoint;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import java.util.function.Function;
/**
* Implement this interface in a mod class and add it to "modernfix:integration_v1" in your mod metadata file
@ -56,22 +56,7 @@ public interface ModernFixClientIntegration {
* with dynamic resources on
* @return the model which should actually be loaded for this resource location
*/
@Deprecated
default BakedModel onBakedModelLoad(ResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery) {
return originalModel;
}
/**
* Called to allow mods to observe the loading of a baked model and either make changes to it or wrap it with their
* own instance.
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
* @param originalModel the original model
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
* with dynamic resources on
* @param textureGetter function to retrieve textures for this model
* @return the model which should actually be loaded for this resource location
*/
default BakedModel onBakedModelLoad(ResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery, Function<Material, TextureAtlasSprite> textureGetter) {
return onBakedModelLoad(location, baseModel, originalModel, state, bakery);
}
}

View File

@ -1,23 +1,21 @@
package org.embeddedt.modernfix.api.helpers;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.BlockModelRotation;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.embeddedt.modernfix.dynamicresources.ModelBakeryHelpers;
import org.embeddedt.modernfix.util.DynamicMap;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
@SuppressWarnings("unused")
public final class ModelHelpers {
@ -28,7 +26,7 @@ public final class ModelHelpers {
* @return a list of all blockstates related to the model
*/
public static ImmutableList<BlockState> getBlockStateForLocation(ModelResourceLocation location) {
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(new ResourceLocation(location.getNamespace(), location.getPath()));
Optional<Block> blockOpt = Registry.BLOCK.getOptional(new ResourceLocation(location.getNamespace(), location.getPath()));
if(blockOpt.isPresent())
return ModelBakeryHelpers.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), location);
else
@ -54,34 +52,4 @@ public final class ModelHelpers {
public static Map<ResourceLocation, BakedModel> createFakeTopLevelMap(BiFunction<ResourceLocation, ModelState, BakedModel> modelGetter) {
return new DynamicMap<>(location -> modelGetter.apply(location, BlockModelRotation.X0_Y0));
}
/**
* Provides a ModelBaker for mods to use.
* @param bakery the ModelBakery supplied to your integration
* @return an appropriate ModelBaker
*/
public static ModelBaker adaptBakery(ModelBakery bakery) {
return new ModelBaker() {
@Override
public UnbakedModel getModel(ResourceLocation resourceLocation) {
return bakery.getModel(resourceLocation);
}
@Nullable
@Override
public BakedModel bake(ResourceLocation resourceLocation, ModelState modelState) {
return ((IExtendedModelBakery)bakery).bakeDefault(resourceLocation, modelState);
}
@Override
public @Nullable BakedModel bake(ResourceLocation location, ModelState state, Function<Material, TextureAtlasSprite> sprites) {
throw new UnsupportedOperationException();
}
@Override
public Function<Material, TextureAtlasSprite> getModelTextureGetter() {
return Material::sprite;
}
};
}
}

View File

@ -6,7 +6,7 @@ import net.minecraft.world.level.block.state.BlockState;
import org.embeddedt.modernfix.duck.IBlockState;
public class BlockStateCacheHandler {
public static void invalidateCache() {
public static void rebuildParallel(boolean force) {
synchronized (BlockBehaviour.BlockStateBase.class) {
for (BlockState blockState : Block.BLOCK_STATE_REGISTRY) {
((IBlockState)blockState).clearCache();

View File

@ -1,7 +1,5 @@
package org.embeddedt.modernfix.blockstate;
import com.google.common.collect.Iterators;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.world.level.block.state.properties.Property;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -16,7 +14,6 @@ import java.util.*;
*/
public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S> {
private final Map<Property<?>, Comparable<?>>[] keys;
private Map<Map<Property<?>, Comparable<?>>, S> fastLookup;
private final Object[] values;
private int usedSlots;
public FakeStateMap(int numStates) {
@ -37,39 +34,22 @@ public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S>
@Override
public boolean containsKey(Object o) {
return getFastLookup().containsKey(o);
throw new UnsupportedOperationException();
}
@Override
public boolean containsValue(Object o) {
return getFastLookup().containsValue(o);
}
@SuppressWarnings("unchecked")
private Map<Map<Property<?>, Comparable<?>>, S> getFastLookup() {
if(fastLookup == null) {
var map = new Object2ObjectOpenHashMap<Map<Property<?>, Comparable<?>>, S>(usedSlots);
Map<Property<?>, Comparable<?>>[] keys = this.keys;
Object[] values = this.values;
for(int i = 0; i < usedSlots; i++) {
map.put(keys[i], (S)values[i]);
}
fastLookup = map;
}
return fastLookup;
throw new UnsupportedOperationException();
}
@Override
public S get(Object o) {
return getFastLookup().get(o);
throw new UnsupportedOperationException();
}
@Nullable
@Override
public S put(Map<Property<?>, Comparable<?>> propertyComparableMap, S s) {
if(fastLookup != null) {
throw new IllegalStateException("Cannot populate map after fast lookup is built");
}
keys[usedSlots] = propertyComparableMap;
values[usedSlots] = s;
usedSlots++;
@ -90,58 +70,49 @@ public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S>
@Override
public void clear() {
for(int i = 0; i < usedSlots; i++) {
for(int i = 0; i < this.keys.length; i++) {
this.keys[i] = null;
this.values[i] = null;
}
this.usedSlots = 0;
}
private <T> List<T> asList(T... array) {
var list = Arrays.asList(array);
if(usedSlots < array.length) {
list = list.subList(0, usedSlots);
}
return list;
}
@NotNull
@Override
public Set<Map<Property<?>, Comparable<?>>> keySet() {
return new AbstractSet<>() {
@Override
public Iterator<Map<Property<?>, Comparable<?>>> iterator() {
return keys.length == usedSlots ? Iterators.forArray(keys) : asList(keys).iterator();
}
@Override
public int size() {
return usedSlots;
}
};
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Collection<S> values() {
return (Collection<S>)asList(values);
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<Entry<Map<Property<?>, Comparable<?>>, S>> entrySet() {
return new AbstractSet<>() {
return new Set<Entry<Map<Property<?>, Comparable<?>>, S>>() {
@Override
public int size() {
return usedSlots;
}
@Override
public boolean isEmpty() {
return FakeStateMap.this.isEmpty();
}
@Override
public boolean contains(Object o) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Iterator<Entry<Map<Property<?>, Comparable<?>>, S>> iterator() {
return new Iterator<>() {
return new Iterator<Entry<Map<Property<?>, Comparable<?>>, S>>() {
int currentIdx = 0;
@Override
public boolean hasNext() {
return currentIdx < usedSlots;
@ -149,14 +120,61 @@ public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S>
@Override
public Entry<Map<Property<?>, Comparable<?>>, S> next() {
if (currentIdx >= usedSlots)
if(currentIdx >= usedSlots)
throw new IndexOutOfBoundsException();
Entry<Map<Property<?>, Comparable<?>>, S> entry = new AbstractMap.SimpleImmutableEntry<>(keys[currentIdx], (S) values[currentIdx]);
Entry<Map<Property<?>, Comparable<?>>, S> entry = new AbstractMap.SimpleImmutableEntry<>(keys[currentIdx], (S)values[currentIdx]);
currentIdx++;
return entry;
}
};
}
@NotNull
@Override
public Object[] toArray() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public <T> T[] toArray(@NotNull T[] ts) {
throw new UnsupportedOperationException();
}
@Override
public boolean add(Entry<Map<Property<?>, Comparable<?>>, S> mapSEntry) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsAll(@NotNull Collection<?> collection) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(@NotNull Collection<? extends Entry<Map<Property<?>, Comparable<?>>, S>> collection) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(@NotNull Collection<?> collection) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(@NotNull Collection<?> collection) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
};
}
}

View File

@ -0,0 +1,68 @@
package org.embeddedt.modernfix.chunk;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import org.jetbrains.annotations.Nullable;
public class SafeBlockGetter implements BlockGetter {
private final ServerLevel wrapped;
private final Thread mainThread;
public SafeBlockGetter(ServerLevel wrapped) {
this.wrapped = wrapped;
this.mainThread = Thread.currentThread();
}
public boolean shouldUse() {
return Thread.currentThread() != this.mainThread;
}
@Nullable
private BlockGetter getChunkSafe(BlockPos pos) {
// can safely call getChunkForLighting off-thread
BlockGetter access = this.wrapped.getChunkSource().getChunkForLighting(pos.getX() >> 4, pos.getZ() >> 4);
if(!(access instanceof ChunkAccess))
return null;
ChunkAccess chunk = (ChunkAccess)access;
if(!chunk.getStatus().isOrAfter(ChunkStatus.FULL))
return null;
return chunk;
}
@Override
public int getMaxBuildHeight() {
return this.wrapped.getMaxBuildHeight();
}
@Override
public int getMaxLightLevel() {
return this.wrapped.getMaxLightLevel();
}
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos) {
BlockGetter g = getChunkSafe(pos);
return g == null ? null : g.getBlockEntity(pos);
}
@Override
public BlockState getBlockState(BlockPos pos) {
BlockGetter g = getChunkSafe(pos);
return g == null ? Blocks.AIR.defaultBlockState() : g.getBlockState(pos);
}
@Override
public FluidState getFluidState(BlockPos pos) {
BlockGetter g = getChunkSafe(pos);
return g == null ? Fluids.EMPTY.defaultFluidState() : g.getFluidState(pos);
}
}

View File

@ -0,0 +1,56 @@
package org.embeddedt.modernfix.command;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.structure.CachingStructureManager;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static net.minecraft.commands.Commands.*;
public class ModernFixCommands {
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(literal("modernfix")
.then(literal("upgradeStructures")
.requires(source -> source.hasPermission(3))
.executes(context -> {
ServerLevel level = context.getSource().getLevel();
if(level == null) {
context.getSource().sendFailure(new TextComponent("Couldn't find server level"));
return 0;
}
ResourceManager manager = level.getServer().resources.getResourceManager();
Collection<ResourceLocation> structures = manager.listResources("structures", p -> p.endsWith(".nbt"));
int upgradedNum = 0;
Pattern pathPattern = Pattern.compile("^structures/(.*)\\.nbt$");
for(ResourceLocation found : structures) {
upgradedNum++;
Matcher matcher = pathPattern.matcher(found.getPath());
if(!matcher.matches())
continue;
ResourceLocation structureLocation = new ResourceLocation(found.getNamespace(), matcher.group(1));
try(Resource resource = manager.getResource(found)) {
CachingStructureManager.readStructureTag(structureLocation, level.getServer().getFixerUpper(), resource.getInputStream());
context.getSource().sendSuccess(new TextComponent("checked " + structureLocation + " (" + upgradedNum + "/" + structures.size() + ")"), false);
} catch(Throwable e) {
ModernFix.LOGGER.error("Couldn't upgrade structure " + found, e);
context.getSource().sendFailure(new TextComponent("error reading " + structureLocation + " (" + upgradedNum + "/" + structures.size() + ")"));
}
}
context.getSource().sendSuccess(new TextComponent("All structures upgraded"), false);
return 1;
}))
);
}
}

View File

@ -0,0 +1,22 @@
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockBehaviour;
import org.embeddedt.modernfix.chunk.SafeBlockGetter;
import org.embeddedt.modernfix.duck.ISafeBlockGetter;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
@Mixin(value = BlockBehaviour.BlockStateBase.class, priority = 100)
public class BlockStateBaseMixin {
@ModifyVariable(method = "getOffset", at = @At("HEAD"), argsOnly = true, index = 1)
private BlockGetter useSafeGetter(BlockGetter g) {
if(g instanceof ISafeBlockGetter) {
SafeBlockGetter replacement = ((ISafeBlockGetter) g).mfix$getSafeBlockGetter();
if(replacement.shouldUse())
return replacement;
}
return g;
}
}

View File

@ -0,0 +1,18 @@
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
import net.minecraft.server.level.ServerLevel;
import org.embeddedt.modernfix.chunk.SafeBlockGetter;
import org.embeddedt.modernfix.duck.ISafeBlockGetter;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(ServerLevel.class)
public class ServerLevelMixin implements ISafeBlockGetter {
@Unique
private final SafeBlockGetter mfix$safeBlockGetter = new SafeBlockGetter((ServerLevel)(Object)this);
@Override
public SafeBlockGetter mfix$getSafeBlockGetter() {
return mfix$safeBlockGetter;
}
}

View File

@ -0,0 +1,36 @@
package org.embeddedt.modernfix.common.mixin.bugfix.concurrency;
import net.minecraft.client.Minecraft;
import net.minecraft.util.thread.BlockableEventLoop;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import java.util.function.BooleanSupplier;
@Mixin(Minecraft.class)
@ClientOnlyMixin
public abstract class MinecraftMixin<R extends Runnable> extends BlockableEventLoop<R> {
protected MinecraftMixin(String p_i50403_1_) {
super(p_i50403_1_);
}
@Override
public void managedBlock(BooleanSupplier pIsDone) {
if(!this.isSameThread()) {
ModernFix.LOGGER.warn("A mod is calling Minecraft.managedBlock from the wrong thread. This is most likely related to one of our parallelizations.");
ModernFix.LOGGER.warn("ModernFix will work around this, however ideally the issue should be patched in the other mod.");
ModernFix.LOGGER.warn("Stacktrace", new IllegalThreadStateException());
while(!pIsDone.getAsBoolean()) {
try {
Thread.sleep(100);
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} else {
super.managedBlock(pIsDone);
}
}
}

View File

@ -0,0 +1,19 @@
package org.embeddedt.modernfix.common.mixin.bugfix.concurrency;
import net.minecraft.client.renderer.RenderType;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.Objects;
@Mixin(targets = { "net/minecraft/client/renderer/RenderType$CompositeRenderType$EqualsStrategy"})
@ClientOnlyMixin
public class RenderTypeEqualsStrategyMixin {
@Redirect(method = "equals(Lnet/minecraft/client/renderer/RenderType$CompositeRenderType;Lnet/minecraft/client/renderer/RenderType$CompositeRenderType;)Z", at = @At(value = "INVOKE", target = "Ljava/util/Objects;equals(Ljava/lang/Object;Ljava/lang/Object;)Z", ordinal = 0))
private boolean alsoCheckName(Object a, Object b, RenderType.CompositeRenderType type1, RenderType.CompositeRenderType type2) {
boolean supposedlyEqual = Objects.equals(a, b);
return supposedlyEqual && Objects.equals(type1.name, type2.name);
}
}

View File

@ -0,0 +1,27 @@
package org.embeddedt.modernfix.common.mixin.bugfix.concurrency;
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
import net.minecraft.client.renderer.RenderType;
import com.mojang.blaze3d.vertex.VertexFormat;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(RenderType.CompositeRenderType.class)
@ClientOnlyMixin
public class RenderTypeMixin {
@Shadow @Final private static ObjectOpenCustomHashSet<RenderType.CompositeRenderType> INSTANCES;
/**
* @author embeddedt
* @reason synchronize, can be accessed by multiple mods during modloading
*/
@Overwrite
private static RenderType.CompositeRenderType memoize(String name, VertexFormat format, int drawMode, int bufferSize, boolean useDelegate, boolean needsSorting, RenderType.CompositeState renderState) {
synchronized (INSTANCES){
return INSTANCES.addOrGet(new RenderType.CompositeRenderType(name, format, drawMode, bufferSize, useDelegate, needsSorting, renderState));
}
}
}

View File

@ -0,0 +1,28 @@
package org.embeddedt.modernfix.common.mixin.bugfix.concurrency;
import net.minecraft.tags.StaticTagHelper;
import net.minecraft.tags.TagCollection;
import net.minecraft.tags.TagContainer;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
@Mixin(StaticTagHelper.class)
public class StaticTagHelperMixin<T> {
@SuppressWarnings("rawtypes")
@Shadow @Mutable
@Final private List wrappers;
@Inject(method = "<init>", at = @At("RETURN"))
private void useCOWArrayList(Function<TagContainer, TagCollection<T>> function, CallbackInfo ci) {
this.wrappers = new CopyOnWriteArrayList<>();
}
}

View File

@ -0,0 +1,40 @@
package org.embeddedt.modernfix.common.mixin.bugfix.item_cache_flag;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
/**
* Remove emptyCacheFlag from ItemStack, as Mojang did in 1.20 due to <a href="https://bugs.mojang.com/browse/MC-258939">MC-258939</a>.
*/
@Mixin(ItemStack.class)
public class ItemStackMixin {
@Shadow @Final @Deprecated private Item item;
/**
* @author embeddedt, Mojang
* @reason avoid getItem()
*/
@Redirect(method = "isEmpty", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;getItem()Lnet/minecraft/world/item/Item;"))
private Item getItemDirect(ItemStack stack) {
return this.item;
}
@Redirect(method = "*", at = @At(value = "FIELD", opcode = Opcodes.GETFIELD, target = "Lnet/minecraft/world/item/ItemStack;emptyCacheFlag:Z"))
private boolean checkEmptyDirect(ItemStack stack) {
return stack.isEmpty();
}
/**
* @author embeddedt, Mojang
* @reason flag is no longer used
*/
@Overwrite
private void updateEmptyCacheFlag() {}
}

View File

@ -0,0 +1,48 @@
package org.embeddedt.modernfix.common.mixin.bugfix.mc218112;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
@Mixin(SynchedEntityData.class)
@ClientOnlyMixin
public abstract class SynchedEntityDataMixin_Client {
@Shadow @Final private ReadWriteLock lock;
@Shadow private boolean isDirty;
@Shadow protected abstract <T> void assignValue(SynchedEntityData.DataItem<T> target, SynchedEntityData.DataItem<?> source);
@Shadow @Final private Entity entity;
@Shadow @Final private Map<Integer, SynchedEntityData.DataItem<?>> itemsById;
/**
* @author embeddedt
* @reason always unlock
*/
@Overwrite
public void assignValues(List<SynchedEntityData.DataItem<?>> entries) {
this.lock.writeLock().lock();
try {
for(SynchedEntityData.DataItem<?> dataentry : entries) {
SynchedEntityData.DataItem<?> dataentry1 = this.itemsById.get(dataentry.getAccessor().getId());
if (dataentry1 != null) {
this.assignValue(dataentry1, dataentry);
this.entity.onSyncedDataUpdated(dataentry.getAccessor());
}
}
} finally {
this.lock.writeLock().unlock();
}
this.isDirty = true;
}
}

View File

@ -0,0 +1,39 @@
package org.embeddedt.modernfix.common.mixin.bugfix.packet_leak;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientboundCustomPayloadPacket.class)
@ClientOnlyMixin
public class SCustomPayloadPlayPacketMixin {
@Shadow private FriendlyByteBuf data;
private boolean needsRelease;
@Inject(method = "<init>(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/network/FriendlyByteBuf;)V", at = @At("RETURN"))
private void markNotOwned(ResourceLocation pIdentifier, FriendlyByteBuf pData, CallbackInfo ci) {
this.needsRelease = false;
}
@Inject(method = "read", at = @At("RETURN"))
private void markOwned(FriendlyByteBuf p_148837_1_, CallbackInfo ci) {
this.needsRelease = true;
}
@Redirect(method = "handle(Lnet/minecraft/network/protocol/game/ClientGamePacketListener;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/protocol/game/ClientGamePacketListener;handleCustomPayload(Lnet/minecraft/network/protocol/game/ClientboundCustomPayloadPacket;)V"))
private void handleAndFree(ClientGamePacketListener instance, ClientboundCustomPayloadPacket sCustomPayloadPlayPacket) {
/* in 1.16, this method creates a copy inside it, but does not handle freeing correctly. fixing that breaks mods though */
instance.handleCustomPayload(sCustomPayloadPlayPacket);
if(this.needsRelease)
this.data.release(); /* free our own copy of the data if needed */
}
}

View File

@ -0,0 +1,61 @@
package org.embeddedt.modernfix.common.mixin.bugfix.paper_chunk_patches;
import com.mojang.datafixers.util.Either;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import org.embeddedt.modernfix.duck.IPaperChunkHolder;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Mixin(ChunkHolder.class)
public abstract class ChunkHolderMixin implements IPaperChunkHolder {
@Shadow public abstract CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresentUnchecked(ChunkStatus arg);
@Shadow @Final private static List<ChunkStatus> CHUNK_STATUSES;
public ChunkStatus mfix$getChunkHolderStatus() {
for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) {
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = this.getFutureIfPresentUnchecked(curr);
Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = future.getNow(null);
if (either == null || !either.left().isPresent()) {
continue;
}
return curr;
}
return null;
}
public ChunkAccess mfix$getAvailableChunkNow() {
// TODO can we just getStatusFuture(EMPTY)?
for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) {
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = this.getFutureIfPresentUnchecked(curr);
Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = future.getNow(null);
if (either == null || !either.left().isPresent()) {
continue;
}
return either.left().get();
}
return null;
}
private static ChunkStatus mfix$getNextStatus(ChunkStatus status) {
if (status == ChunkStatus.FULL) {
return status;
}
return CHUNK_STATUSES.get(status.getIndex() + 1);
}
@Override
public boolean mfix$canAdvanceStatus() {
ChunkStatus status = mfix$getChunkHolderStatus();
ChunkAccess chunk = mfix$getAvailableChunkNow();
return chunk != null && (status == null || chunk.getStatus().isOrAfter(mfix$getNextStatus(status)));
}
}

View File

@ -0,0 +1,66 @@
package org.embeddedt.modernfix.common.mixin.bugfix.paper_chunk_patches;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.thread.BlockableEventLoop;
import org.embeddedt.modernfix.duck.IPaperChunkHolder;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Function;
@Mixin(ChunkMap.class)
public class ChunkMapMixin {
@Shadow @Final private BlockableEventLoop<Runnable> mainThreadExecutor;
@Shadow @Final private ServerLevel level;
private Executor mainInvokingExecutor;
@Inject(method = "<init>", at = @At("RETURN"))
private void setup(CallbackInfo ci) {
MinecraftServer server = this.level.getServer();
this.mainInvokingExecutor = (runnable) -> {
if(server.isSameThread())
runnable.run();
else
this.mainThreadExecutor.execute(runnable);
};
}
/* https://github.com/PaperMC/Paper/blob/ver/1.17.1/patches/server/0752-Fix-chunks-refusing-to-unload-at-low-TPS.patch */
@ModifyArg(method = "unpackTicks", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 1)
private Executor useMainThreadExecutor(Executor executor) {
return this.mainThreadExecutor;
}
/* https://github.com/PaperMC/Paper/blob/master/patches/removed/1.19.2-legacy-chunksystem/0482-Improve-Chunk-Status-Transition-Speed.patch */
@ModifyArg(method = "getEntityTickingRangeFuture", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 1)
private Executor useMainInvokingExecutor(Executor executor) {
return this.mainInvokingExecutor;
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Redirect(method = "scheduleChunkGeneration", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenComposeAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
private CompletableFuture skipWorkerIfPossible(CompletableFuture inputFuture, Function function, Executor executor, ChunkHolder holder) {
Executor targetExecutor = (runnable) -> {
if(((IPaperChunkHolder)holder).mfix$canAdvanceStatus()) {
this.mainInvokingExecutor.execute(runnable);
return;
}
executor.execute(runnable);
};
return inputFuture.thenComposeAsync(function, targetExecutor);
}
}

View File

@ -1,7 +1,6 @@
package org.embeddedt.modernfix.common.mixin.bugfix.paper_chunk_patches;
import net.minecraft.util.SortedArraySet;
import org.embeddedt.modernfix.annotation.RequiresMod;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@ -10,7 +9,6 @@ import java.util.Arrays;
import java.util.function.Predicate;
@Mixin(SortedArraySet.class)
@RequiresMod("!moonrise")
public abstract class SortedArraySetMixin<T> extends AbstractSet<T> {
@Shadow private int size;

View File

@ -34,7 +34,7 @@ public class MinecraftMixin {
}
this.level.getChunkSource().lightEngine = new LevelLightEngine(this.level.getChunkSource(), false, false);
// clear BE list otherwise they will hold chunks
this.level.blockEntityTickers.clear();
this.level.blockEntityList.clear();
} catch(RuntimeException e) {
ModernFix.LOGGER.error("Exception clearing level data", e);
}

View File

@ -0,0 +1,24 @@
package org.embeddedt.modernfix.common.mixin.core;
import net.minecraft.Util;
import net.minecraft.server.MinecraftServer;
import org.embeddedt.modernfix.duck.ITimeTrackingServer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(MinecraftServer.class)
public class MinecraftServerMixin implements ITimeTrackingServer {
private long mfix$lastTickStartTime = -1L;
@Override
public long mfix$getLastTickStartTime() {
return mfix$lastTickStartTime;
}
@Inject(method = "runServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;tickServer(Ljava/util/function/BooleanSupplier;)V"))
private void trackTickTime(CallbackInfo ci) {
mfix$lastTickStartTime = Util.getMillis();
}
}

View File

@ -0,0 +1,26 @@
package org.embeddedt.modernfix.common.mixin.core;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(SynchedEntityData.class)
@ClientOnlyMixin
public class SynchedEntityDataMixin {
/**
* Store this in our set of all entity data objects.
*
* Not an ideal solution, but it should guarantee compatibility with mods.
*/
@Inject(method = "<init>", at = @At("RETURN"))
private void storeInSet(Entity arg, CallbackInfo ci) {
synchronized (ModernFixClient.allEntityDatas) {
ModernFixClient.allEntityDatas.add((SynchedEntityData)(Object)this);
}
}
}

View File

@ -0,0 +1,19 @@
package org.embeddedt.modernfix.common.mixin.devenv;
import com.mojang.authlib.minecraft.OfflineSocialInteractions;
import com.mojang.authlib.minecraft.SocialInteractionsService;
import net.minecraft.client.Minecraft;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Minecraft.class)
@ClientOnlyMixin
public class MinecraftMixin {
@Inject(method = "createSocialInteractions", at = @At("HEAD"), cancellable = true)
private void noSocialInteraction(CallbackInfoReturnable<SocialInteractionsService> cir) {
cir.setReturnValue(new OfflineSocialInteractions());
}
}

View File

@ -0,0 +1,18 @@
package org.embeddedt.modernfix.common.mixin.devenv;
import com.mojang.text2speech.Narrator;
import com.mojang.text2speech.NarratorDummy;
import net.minecraft.client.gui.chat.NarratorChatListener;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(NarratorChatListener.class)
@ClientOnlyMixin
public class NarratorMixin {
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lcom/mojang/text2speech/Narrator;getNarrator()Lcom/mojang/text2speech/Narrator;", remap = false))
private Narrator useDummyNarrator() {
return new NarratorDummy();
}
}

View File

@ -0,0 +1,22 @@
package org.embeddedt.modernfix.common.mixin.feature.direct_stack_trace;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(CrashReport.class)
public class CrashReportMixin {
@Shadow @Final private Throwable exception;
@Inject(method = "addCategory(Ljava/lang/String;I)Lnet/minecraft/CrashReportCategory;", at = @At(value = "INVOKE", target = "Ljava/io/PrintStream;println(Ljava/lang/String;)V"))
private void dumpStacktrace(String s, int i, CallbackInfoReturnable<CrashReportCategory> cir) {
new Exception("ModernFix crash stacktrace").printStackTrace();
if(this.exception != null)
this.exception.printStackTrace();
}
}

View File

@ -2,8 +2,8 @@ package org.embeddedt.modernfix.common.mixin.feature.measure_time;
import com.google.common.base.Stopwatch;
import net.minecraft.server.Bootstrap;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;

View File

@ -0,0 +1,47 @@
package org.embeddedt.modernfix.common.mixin.feature.measure_time;
import com.mojang.datafixers.util.Function4;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Overlay;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.world.level.DataPackConfig;
import net.minecraft.core.RegistryAccess;
import net.minecraft.world.level.storage.WorldData;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.function.Function;
@Mixin(Minecraft.class)
@ClientOnlyMixin
public class MinecraftMixin {
@Shadow @Nullable public Overlay overlay;
private long datapackReloadStartTime;
@Inject(method = "makeServerStem", at = @At(value = "HEAD"))
private void recordReloadStart(RegistryAccess.RegistryHolder p_238189_1_, Function<LevelStorageSource.LevelStorageAccess, DataPackConfig> p_238189_2_, Function4<LevelStorageSource.LevelStorageAccess, RegistryAccess.RegistryHolder, ResourceManager, DataPackConfig, WorldData> p_238189_3_, boolean p_238189_4_, LevelStorageSource.LevelStorageAccess p_238189_5_, CallbackInfoReturnable<Minecraft.ServerStem> cir) {
datapackReloadStartTime = System.nanoTime();
}
@Inject(method = "makeServerStem", at = @At(value = "RETURN"))
private void recordReloadEnd(RegistryAccess.RegistryHolder p_238189_1_, Function<LevelStorageSource.LevelStorageAccess, DataPackConfig> p_238189_2_, Function4<LevelStorageSource.LevelStorageAccess, RegistryAccess.RegistryHolder, ResourceManager, DataPackConfig, WorldData> p_238189_3_, boolean p_238189_4_, LevelStorageSource.LevelStorageAccess p_238189_5_, CallbackInfoReturnable<Minecraft.ServerStem> cir) {
float timeSpentReloading = ((float)(System.nanoTime() - datapackReloadStartTime) / 1000000000f);
ModernFix.LOGGER.warn("Datapack reload took " + timeSpentReloading + " seconds.");
}
@Inject(method = "tick", at = @At("HEAD"))
private void onClientTick(CallbackInfo ci) {
if(this.overlay == null && ModernFixClient.INSTANCE != null) {
ModernFixClient.INSTANCE.onGameLaunchFinish();
}
}
}

View File

@ -0,0 +1,23 @@
package org.embeddedt.modernfix.common.mixin.feature.measure_time;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ProfiledReloadInstance;
import org.embeddedt.modernfix.util.NamedPreparableResourceListener;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import java.util.ArrayList;
import java.util.List;
@Mixin(ProfiledReloadInstance.class)
public class ProfiledReloadInstanceMixin {
@ModifyVariable(method = "<init>", at = @At("HEAD"), argsOnly = true, ordinal = 0)
private static List<PreparableReloadListener> getWrappedListeners(List<PreparableReloadListener> listeners) {
List<PreparableReloadListener> newList = new ArrayList<>(listeners.size());
for(PreparableReloadListener listener : listeners) {
newList.add(new NamedPreparableResourceListener(listener));
}
return newList;
}
}

View File

@ -0,0 +1,21 @@
package org.embeddedt.modernfix.common.mixin.feature.measure_time;
import net.minecraft.server.packs.resources.SimpleReloadableResourceManager;
import org.apache.logging.log4j.Logger;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(SimpleReloadableResourceManager.class)
public class SimpleReloadableResourceManagerMixin {
// TODO maybe expose as a mixin config
private static final boolean ENABLE_DEBUG_RELOADER = Boolean.getBoolean("modernfix.debugReloader");
/**
* @author embeddedt
* @reason add ability to use this feature in modpacks
*/
@Redirect(method = "createReload", at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;isDebugEnabled()Z", remap = false))
private boolean enableDebugReloader(Logger logger) {
return logger.isDebugEnabled() || ENABLE_DEBUG_RELOADER;
}
}

View File

@ -0,0 +1,66 @@
package org.embeddedt.modernfix.common.mixin.feature.stalled_chunk_load_detection;
import com.mojang.datafixers.util.Either;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.EmptyLevelChunk;
import org.embeddedt.modernfix.ModernFix;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.concurrent.*;
@Mixin(value = ServerChunkCache.class, priority = 1100)
public abstract class ServerChunkCacheMixin {
@Shadow @Final private Thread mainThread;
@Shadow @Final public ServerLevel level;
@Shadow protected abstract CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getChunkFutureMainThread(int k, int l, ChunkStatus arg, boolean bl);
@Shadow @Final private ServerChunkCache.MainThreadExecutor mainThreadProcessor;
private final boolean debugDeadServerAccess = Boolean.getBoolean("modernfix.debugBadChunkloading");
@Inject(method = "getChunk", at = @At("HEAD"), cancellable = true)
private void bailIfServerDead(int chunkX, int chunkZ, ChunkStatus requiredStatus, boolean load, CallbackInfoReturnable<ChunkAccess> cir) {
if(!this.level.getServer().isRunning() && !this.mainThread.isAlive()) {
ModernFix.LOGGER.fatal("A mod is accessing chunks from a stopped server (this will also cause memory leaks)");
if(debugDeadServerAccess) {
new Exception().printStackTrace();
}
cir.setReturnValue(new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ)));
} else if(Thread.currentThread() != this.mainThread) {
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = CompletableFuture.supplyAsync(() -> this.getChunkFutureMainThread(chunkX, chunkZ, requiredStatus, false), this.mainThreadProcessor).join();
if(!future.isDone()) {
// Wait at least 500 milliseconds before printing anything
Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> resultingChunk = null;
try {
resultingChunk = future.get(500, TimeUnit.MILLISECONDS);
} catch(InterruptedException | ExecutionException | TimeoutException ignored) {
}
if(resultingChunk != null && resultingChunk.left().isPresent()) {
cir.setReturnValue(resultingChunk.left().get());
return;
}
if(debugDeadServerAccess)
ModernFix.LOGGER.warn("Async loading of a chunk was requested, this might not be desirable", new Exception());
try {
resultingChunk = future.get(10, TimeUnit.SECONDS);
if(resultingChunk.left().isPresent()) {
cir.setReturnValue(resultingChunk.left().get());
return;
}
} catch(InterruptedException | ExecutionException | TimeoutException e) {
ModernFix.LOGGER.error("Async chunk load took way too long, this needs to be reported to the appropriate mod.", e);
}
//cir.setReturnValue(new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ)));
}
}
}
}

View File

@ -0,0 +1,57 @@
package org.embeddedt.modernfix.common.mixin.perf.biome_zoomer;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.biome.FuzzyOffsetBiomeZoomer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(FuzzyOffsetBiomeZoomer.class)
public abstract class FuzzyOffsetBiomeZoomerMixin {
@Shadow protected static double getFiddledDistance(long seed, int x, int y, int z, double scaleX, double scaleY, double scaleZ) {
throw new AssertionError();
}
/**
* @author embeddedt
* @reason use the modern logic that doesn't allocate an array of 8 doubles every time
*/
@Overwrite
public Biome getBiome(long seed, int xIn, int yIn, int zIn, BiomeManager.NoiseBiomeSource biomeReader) {
int i = xIn - 2;
int j = yIn - 2;
int k = zIn - 2;
int l = i >> 2;
int i1 = j >> 2;
int j1 = k >> 2;
double d0 = (double)(i & 3) / 4.0D;
double d1 = (double)(j & 3) / 4.0D;
double d2 = (double)(k & 3) / 4.0D;
int k1 = 0;
double d3 = Double.POSITIVE_INFINITY;
for(int l1 = 0; l1 < 8; ++l1) {
boolean flag = (l1 & 4) == 0;
boolean flag1 = (l1 & 2) == 0;
boolean flag2 = (l1 & 1) == 0;
int i2 = flag ? l : l + 1;
int j2 = flag1 ? i1 : i1 + 1;
int k2 = flag2 ? j1 : j1 + 1;
double d4 = flag ? d0 : d0 - 1.0D;
double d5 = flag1 ? d1 : d1 - 1.0D;
double d6 = flag2 ? d2 : d2 - 1.0D;
double d7 = getFiddledDistance(seed, i2, j2, k2, d4, d5, d6);
if (d3 > d7) {
k1 = l1;
d3 = d7;
}
}
int l2 = (k1 & 4) == 0 ? l : l + 1;
int i3 = (k1 & 2) == 0 ? i1 : i1 + 1;
int j3 = (k1 & 1) == 0 ? j1 : j1 + 1;
return biomeReader.getNoiseBiome(l2, i3, j3);
}
}

View File

@ -0,0 +1,25 @@
package org.embeddedt.modernfix.common.mixin.perf.boost_worker_count;
import net.minecraft.Util;
import org.embeddedt.modernfix.ModernFix;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
@Mixin(Util.class)
public class UtilMixin {
@ModifyConstant(method = "makeExecutor", constant = @Constant(intValue = 7))
private static int useHigherThreadCount(int old) {
String requestedMax = System.getProperty("max.bg.threads");
if(requestedMax != null) {
try {
int newMax = Integer.parseInt(requestedMax);
if(newMax >= 1 && newMax <= 255)
return newMax;
} catch(NumberFormatException e) {
ModernFix.LOGGER.error("max.bg.threads is not a number");
}
}
return 255;
}
}

View File

@ -0,0 +1,110 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_model_materials;
import com.mojang.datafixers.util.Either;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.resources.model.Material;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.ICachedMaterialsModel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
@Mixin(BlockModel.class)
@ClientOnlyMixin
public class BlockModelMixin {
@Shadow @Final @Mutable public Map<String, Either<Material, String>> textureMap;
/**
* @author embeddedt
* @reason detect changes to the texture map, and clear the material cache as needed
*/
@Inject(method = "<init>", at = @At("RETURN"))
private void useTrackingTextureMap(CallbackInfo ci) {
Map<String, Either<Material, String>> backingMap = this.textureMap;
ICachedMaterialsModel cacheHolder = (ICachedMaterialsModel)this;
this.textureMap = new Map<String, Either<Material, String>>() {
@Override
public int size() {
return backingMap.size();
}
@Override
public boolean isEmpty() {
return backingMap.isEmpty();
}
@Override
public boolean containsKey(Object o) {
return backingMap.containsKey(o);
}
@Override
public boolean containsValue(Object o) {
return backingMap.containsValue(o);
}
@Override
public Either<Material, String> get(Object o) {
return backingMap.get(o);
}
@Nullable
@Override
public Either<Material, String> put(String s, Either<Material, String> materialStringEither) {
Either<Material, String> old = backingMap.put(s, materialStringEither);
cacheHolder.clearMaterialsCache();
return old;
}
@Override
public Either<Material, String> remove(Object o) {
Either<Material, String> e = backingMap.remove(o);
cacheHolder.clearMaterialsCache();
return e;
}
@Override
public void putAll(@NotNull Map<? extends String, ? extends Either<Material, String>> map) {
backingMap.putAll(map);
cacheHolder.clearMaterialsCache();
}
@Override
public void clear() {
backingMap.clear();
cacheHolder.clearMaterialsCache();
}
@NotNull
@Override
public Set<String> keySet() {
cacheHolder.clearMaterialsCache();
return backingMap.keySet();
}
@NotNull
@Override
public Collection<Either<Material, String>> values() {
cacheHolder.clearMaterialsCache();
return backingMap.values();
}
@NotNull
@Override
public Set<Entry<String, Either<Material, String>>> entrySet() {
cacheHolder.clearMaterialsCache();
return backingMap.entrySet();
}
};
}
}

View File

@ -0,0 +1,28 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_model_materials;
import net.minecraft.client.renderer.block.model.multipart.MultiPart;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Collection;
@Mixin(MultiPart.class)
@ClientOnlyMixin
public class MultipartMixin {
private Collection<ResourceLocation> dependencyCache = null;
@Inject(method = "getDependencies", at = @At("HEAD"), cancellable = true)
private void useDependencyCache(CallbackInfoReturnable<Collection<ResourceLocation>> cir) {
if(dependencyCache != null)
cir.setReturnValue(dependencyCache);
}
@Inject(method = "getDependencies", at = @At("RETURN"))
private void storeDependencyCache(CallbackInfoReturnable<Collection<ResourceLocation>> cir) {
if(dependencyCache == null)
dependencyCache = cir.getReturnValue();
}
}

View File

@ -0,0 +1,43 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_model_materials;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.client.resources.model.Material;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.ICachedMaterialsModel;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.function.Function;
/* only cache BlockModel to prevent issues with CTM on Fabric */
@Mixin(value = {BlockModel.class})
@ClientOnlyMixin
public class VanillaModelMixin implements ICachedMaterialsModel {
private Collection<Material> materialsCache = null;
@Inject(method = "getMaterials", at = @At("HEAD"), cancellable = true)
private void useCachedMaterials(Function<ResourceLocation, UnbakedModel> pModelGetter, Set<Pair<String, String>> pMissingTextureErrors, CallbackInfoReturnable<Collection<Material>> cir) {
if(materialsCache != null) {
cir.setReturnValue(materialsCache);
}
}
@Inject(method = "getMaterials", at = @At("RETURN"))
private void storeCachedMaterials(Function<ResourceLocation, UnbakedModel> pModelGetter, Set<Pair<String, String>> pMissingTextureErrors, CallbackInfoReturnable<Collection<Material>> cir) {
if(materialsCache == null)
materialsCache = Collections.unmodifiableCollection(cir.getReturnValue());
}
@Override
public void clearMaterialsCache() {
materialsCache = null;
}
}

View File

@ -0,0 +1,66 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkGenerator;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.duck.IServerLevel;
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
import org.embeddedt.modernfix.world.StrongholdLocationCache;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
@Mixin(ChunkGenerator.class)
public class ChunkGeneratorMixin {
@Shadow @Final private List<ChunkPos> strongholdPositions;
@Inject(method = "generateStrongholds", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Lists;newArrayList()Ljava/util/ArrayList;", ordinal = 0, remap = false), cancellable = true)
private void useCachedDataIfAvailable(CallbackInfo ci) {
ServerLevel level = searchLevel();
if(level == null) {
ModernFix.LOGGER.error("Can't find server level for " + this);
return;
}
StrongholdLocationCache cache = ((IServerLevel)level).mfix$getStrongholdCache();
List<ChunkPos> positions = cache.getChunkPosList();
if(positions.isEmpty())
return;
ModernFix.LOGGER.debug("Loaded stronghold cache for dimension {} with {} positions", level.dimension().location(), positions.size());
this.strongholdPositions.addAll(positions);
ci.cancel();
}
private ServerLevel searchLevel() {
MinecraftServer server = ModernFixPlatformHooks.INSTANCE.getCurrentServer();
if(server != null) {
ServerLevel ourLevel = null;
for (ServerLevel level : server.getAllLevels()) {
if (level.getChunkSource().getGenerator() == ((ChunkGenerator) (Object) this)) {
ourLevel = level;
break;
}
}
return ourLevel;
} else
return null;
}
@Inject(method = "generateStrongholds", at = @At("TAIL"))
private void saveCachedData(CallbackInfo ci) {
if(this.strongholdPositions.size() > 0) {
ServerLevel level = searchLevel();
if(level != null) {
StrongholdLocationCache cache = ((IServerLevel)level).mfix$getStrongholdCache();
cache.setChunkPosList(this.strongholdPositions);
ModernFix.LOGGER.debug("Saved stronghold cache for dimension {}", level.dimension().location());
}
}
}
}

View File

@ -0,0 +1,49 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.level.CustomSpawner;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.DimensionDataStorage;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.level.storage.WritableLevelData;
import org.embeddedt.modernfix.duck.IServerLevel;
import org.embeddedt.modernfix.world.StrongholdLocationCache;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
@Mixin(ServerLevel.class)
public abstract class ServerLevelMixin extends Level implements IServerLevel {
protected ServerLevelMixin(WritableLevelData arg, ResourceKey<Level> arg2, DimensionType arg3, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l) {
super(arg, arg2, arg3, supplier, bl, bl2, l);
}
@Shadow public abstract DimensionDataStorage getDataStorage();
private StrongholdLocationCache mfix$strongholdCache;
@Inject(method = "<init>", at = @At("RETURN"))
private void addStrongholdCache(MinecraftServer minecraftServer, Executor executor, LevelStorageSource.LevelStorageAccess arg,
ServerLevelData arg2, ResourceKey<Level> arg3, DimensionType arg4, ChunkProgressListener arg5,
ChunkGenerator arg6, boolean bl, long l, List<CustomSpawner> list, boolean bl2, CallbackInfo ci) {
mfix$strongholdCache = this.getDataStorage().computeIfAbsent(() -> new StrongholdLocationCache((ServerLevel)(Object)this), StrongholdLocationCache.getFileId(this.dimensionType()));
}
@Override
public StrongholdLocationCache mfix$getStrongholdCache() {
return mfix$strongholdCache;
}
}

View File

@ -0,0 +1,25 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_upgraded_structures;
import com.mojang.datafixers.DataFixer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import org.embeddedt.modernfix.structure.CachingStructureManager;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.io.IOException;
import java.io.InputStream;
@Mixin(StructureManager.class)
public class StructureManagerMixin {
@Shadow @Final private DataFixer fixerUpper;
@Redirect(method = "loadFromResource", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/structure/templatesystem/StructureManager;readStructure(Ljava/io/InputStream;)Lnet/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate;"))
private StructureTemplate readViaCache(StructureManager manager, InputStream stream, ResourceLocation arg) throws IOException {
return CachingStructureManager.readStructure(arg, this.fixerUpper, stream);
}
}

View File

@ -2,6 +2,8 @@ package org.embeddedt.modernfix.common.mixin.perf.compact_mojang_registries;
import com.mojang.serialization.Lifecycle;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import org.embeddedt.modernfix.annotation.IgnoreOutsideDev;
import org.embeddedt.modernfix.registry.LifecycleMap;
import org.spongepowered.asm.mixin.Final;
@ -16,12 +18,16 @@ import java.util.Map;
@Mixin(MappedRegistry.class)
@IgnoreOutsideDev
public abstract class MappedRegistryMixin<T> {
public abstract class MappedRegistryMixin<T> extends Registry<T> {
@Shadow
@Final
@Mutable
private Map<T, Lifecycle> lifecycles;
protected MappedRegistryMixin(ResourceKey<? extends Registry<T>> resourceKey, Lifecycle lifecycle) {
super(resourceKey, lifecycle);
}
@Inject(method = "<init>", at = @At("RETURN"))
private void replaceStorage(CallbackInfo ci) {
this.lifecycles = new LifecycleMap<>();

View File

@ -0,0 +1,148 @@
package org.embeddedt.modernfix.common.mixin.perf.compress_biome_container;
import it.unimi.dsi.fastutil.objects.Reference2ShortMap;
import it.unimi.dsi.fastutil.objects.Reference2ShortOpenHashMap;
import net.minecraft.util.BitStorage;
import net.minecraft.core.IdMap;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.util.Mth;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.chunk.ChunkBiomeContainer;
import net.minecraft.world.level.biome.BiomeSource;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ChunkBiomeContainer.class)
public class MixinBiomeContainer {
@Mutable
@Shadow
@Final
private Biome[] biomes;
@Shadow
@Final
private IdMap<Biome> biomeRegistry;
@Shadow
@Final
private static int WIDTH_BITS;
private Biome[] palette;
private BitStorage intArray;
@Inject(method = "<init>(Lnet/minecraft/core/IdMap;[I)V", at = @At("RETURN"), require = 0)
private void reinit1(IdMap p_i241970_1_, int[] p_i241970_2_, CallbackInfo ci) {
this.createCompact();
}
@Inject(method = "<init>(Lnet/minecraft/core/IdMap;[Lnet/minecraft/world/level/biome/Biome;)V", at = @At("RETURN"))
private void reinit2(IdMap p_i241971_1_, Biome[] p_i241971_2_, CallbackInfo ci) {
this.createCompact();
}
@Inject(method = "<init>(Lnet/minecraft/core/IdMap;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/biome/BiomeSource;)V", at = @At("RETURN"))
private void reinit3(IdMap p_i241968_1_, ChunkPos p_i241968_2_, BiomeSource p_i241968_3_, CallbackInfo ci) {
this.createCompact();
}
@Inject(method = "<init>(Lnet/minecraft/core/IdMap;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/biome/BiomeSource;[I)V", at = @At("RETURN"))
private void reinit4(IdMap p_i241969_1_, ChunkPos p_i241969_2_, BiomeSource p_i241969_3_, int[] p_i241969_4_, CallbackInfo ci) {
this.createCompact();
}
private void createCompact() {
if (this.intArray != null || this.biomes[0] == null) {
return;
}
Reference2ShortOpenHashMap<Biome> paletteTable = this.createPalette();
Biome[] paletteIndexed = new Biome[paletteTable.size()];
for (Reference2ShortMap.Entry<Biome> entry : paletteTable.reference2ShortEntrySet()) {
paletteIndexed[entry.getShortValue()] = entry.getKey();
}
int packedIntSize = Math.max(2, Mth.ceillog2(paletteTable.size()));
BitStorage integerArray = new BitStorage(packedIntSize, ChunkBiomeContainer.BIOMES_SIZE);
Biome prevBiome = null;
short prevId = -1;
for (int i = 0; i < this.biomes.length; i++) {
Biome biome = this.biomes[i];
short id;
if (prevBiome == biome) {
id = prevId;
} else {
id = paletteTable.getShort(biome);
if (id < 0) {
throw new IllegalStateException("Palette is missing entry: " + biome);
}
prevId = id;
prevBiome = biome;
}
integerArray.set(i, id);
}
this.palette = paletteIndexed;
this.intArray = integerArray;
this.biomes = null;
}
private Reference2ShortOpenHashMap<Biome> createPalette() {
Reference2ShortOpenHashMap<Biome> map = new Reference2ShortOpenHashMap<>();
map.defaultReturnValue(Short.MIN_VALUE);
Biome prevObj = null;
short id = 0;
for (Biome obj : this.biomes) {
if (obj == prevObj) {
continue;
}
if (map.getShort(obj) < 0) {
map.put(obj, id++);
}
prevObj = obj;
}
return map;
}
/**
* @author JellySquid
* @reason Use paletted lookup
*/
@Overwrite
public int[] writeBiomes() {
int size = this.intArray.getSize();
int[] array = new int[size];
for(int i = 0; i < size; ++i) {
array[i] = this.biomeRegistry.getId(this.palette[this.intArray.get(i)]);
}
return array;
}
/**
* @author JellySquid
* @reason Use paletted lookup
*/
@Overwrite
public Biome getNoiseBiome(int biomeX, int biomeY, int biomeZ) {
int x = biomeX & ChunkBiomeContainer.HORIZONTAL_MASK;
int y = Mth.clamp(biomeY, 0, ChunkBiomeContainer.VERTICAL_MASK);
int z = biomeZ & ChunkBiomeContainer.HORIZONTAL_MASK;
return this.palette[this.intArray.get(y << WIDTH_BITS + WIDTH_BITS | z << WIDTH_BITS | x)];
}
}

View File

@ -7,13 +7,13 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executor;
@Mixin(Minecraft.class)
@ClientOnlyMixin
public class MinecraftMixin {
@Redirect(method = { "<init>", "reloadResourcePacks(Z)Ljava/util/concurrent/CompletableFuture;" }, at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;backgroundExecutor()Ljava/util/concurrent/ExecutorService;", ordinal = 0))
private ExecutorService getResourceReloadExecutor() {
@Redirect(method = { "<init>", "makeServerStem", "reloadResourcePacks" }, at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;backgroundExecutor()Ljava/util/concurrent/Executor;", ordinal = 0))
private Executor getResourceReloadExecutor() {
return ModernFix.resourceReloadExecutor();
}
}

View File

@ -10,7 +10,7 @@ import java.util.concurrent.Executor;
@Mixin(MinecraftServer.class)
public class MinecraftServerMixin {
@ModifyArg(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ReloadableServerResources;loadResources(Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/core/RegistryAccess$Frozen;Lnet/minecraft/world/flag/FeatureFlagSet;Lnet/minecraft/commands/Commands$CommandSelection;ILjava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 5)
@ModifyArg(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ServerResources;loadResources(Ljava/util/List;Lnet/minecraft/commands/Commands$CommandSelection;ILjava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 3)
private Executor getReloadExecutor(Executor asyncExecutor) {
return ModernFix.resourceReloadExecutor();
}

View File

@ -0,0 +1,30 @@
package org.embeddedt.modernfix.common.mixin.perf.deduplicate_location;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.dedup.IdentifierCaches;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ResourceLocation.class)
public class MixinResourceLocation {
@Mutable
@Shadow
@Final
protected String namespace;
@Mutable
@Shadow
@Final
protected String path;
@Inject(method = "<init>([Ljava/lang/String;)V", at = @At("RETURN"))
private void reinit(String[] id, CallbackInfo ci) {
this.namespace = IdentifierCaches.NAMESPACES.deduplicate(this.namespace);
this.path = IdentifierCaches.PATH.deduplicate(this.path);
}
}

View File

@ -1,6 +1,5 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_dfu;
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.DataFixer;
import net.minecraft.util.datafix.DataFixers;
import org.embeddedt.modernfix.dfu.LazyDataFixer;
@ -10,11 +9,9 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Set;
@Mixin(DataFixers.class)
public abstract class DataFixersMixin {
@Shadow protected static DataFixer createFixerUpper(Set<DSL.TypeReference> set) {
@Shadow protected static DataFixer createFixerUpper() {
throw new AssertionError();
}
@ -24,9 +21,9 @@ public abstract class DataFixersMixin {
* Avoid classloading the DFU logic until we actually need it.
*/
@Inject(method = "createFixerUpper", at = @At("HEAD"), cancellable = true)
private static void createLazyFixerUpper(Set<DSL.TypeReference> set, CallbackInfoReturnable<DataFixer> cir) {
private static void createLazyFixerUpper(CallbackInfoReturnable<DataFixer> cir) {
if(lazyDataFixer == null) {
lazyDataFixer = new LazyDataFixer(() -> createFixerUpper(set));
lazyDataFixer = new LazyDataFixer(DataFixersMixin::createFixerUpper);
cir.setReturnValue(lazyDataFixer);
}
}

View File

@ -0,0 +1,23 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonElement;
import net.minecraft.client.renderer.block.model.BlockElementFace;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.dynamicresources.UVController;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.lang.reflect.Type;
@Mixin(BlockElementFace.Deserializer.class)
@ClientOnlyMixin
public class BlockElementFaceDeserializerMixin {
@Redirect(method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/renderer/block/model/BlockElementFace;",
at = @At(value = "INVOKE", target = "Lcom/google/gson/JsonDeserializationContext;deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;)Ljava/lang/Object;", ordinal = 0, remap = false))
private Object skipUvsForInitialLoad(JsonDeserializationContext context, JsonElement element, Type type) {
return UVController.useDummyUv.get() ? UVController.dummyUv : context.deserialize(element, type);
}
}

View File

@ -1,14 +1,12 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.IModelHoldingBlockState;
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
import org.embeddedt.modernfix.util.DynamicOverridableMap;
import org.spongepowered.asm.mixin.*;
@ -18,26 +16,30 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
@Mixin(BlockModelShaper.class)
@ClientOnlyMixin
public class BlockModelShaperMixin {
@Shadow @Final private ModelManager modelManager;
@Shadow
@Shadow @Final @Mutable
private Map<BlockState, BakedModel> modelByStateCache;
@Inject(method = { "<init>", "replaceCache" }, at = @At("RETURN"))
private ThreadLocal<Reference2ReferenceLinkedOpenHashMap<BlockState, BakedModel>> mfix$modelCache = ThreadLocal.withInitial(Reference2ReferenceLinkedOpenHashMap::new);
@Inject(method = "<init>", at = @At("RETURN"))
private void replaceModelMap(CallbackInfo ci) {
// replace the backing map for mods which will access it
this.modelByStateCache = new DynamicOverridableMap<>(state -> modelManager.getModel(ModelLocationCache.get(state)));
// Clear the cached models on blockstate objects
for(Block block : BuiltInRegistries.BLOCK) {
for(BlockState state : block.getStateDefinition().getPossibleStates()) {
if(state instanceof IModelHoldingBlockState modelHolder) {
modelHolder.mfix$setModel(null);
}
}
}
}
/**
* @author embeddedt
* @reason no need to rebuild vanilla model cache
*/
@Overwrite
public void rebuildCache() {
this.mfix$modelCache = ThreadLocal.withInitial(Reference2ReferenceLinkedOpenHashMap::new);
}
private BakedModel cacheBlockModel(BlockState state) {
@ -57,18 +59,18 @@ public class BlockModelShaperMixin {
*/
@Overwrite
public BakedModel getBlockModel(BlockState state) {
if(state instanceof IModelHoldingBlockState modelHolder) {
BakedModel model = modelHolder.mfix$getModel();
Reference2ReferenceLinkedOpenHashMap<BlockState, BakedModel> map = this.mfix$modelCache.get();
BakedModel model = map.get(state);
if(model != null) {
return model;
}
model = this.cacheBlockModel(state);
modelHolder.mfix$setModel(model);
if(model != null) {
return model;
} else {
return this.cacheBlockModel(state);
}
model = this.cacheBlockModel(state);
map.putAndMoveToFirst(state, model);
if(map.size() > 500) {
map.removeLast();
}
return model;
}
}

View File

@ -5,7 +5,6 @@ import net.minecraft.client.renderer.ItemModelShaper;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.dynamicresources.DynamicModelCache;
@ -33,7 +32,7 @@ public abstract class ItemModelShaperMixin {
super();
}
private static final ModelResourceLocation SENTINEL_VANILLA = new ModelResourceLocation(new ResourceLocation("modernfix", "sentinel"), "sentinel");
private static final ModelResourceLocation SENTINEL_VANILLA = new ModelResourceLocation("modernfix", "sentinel");
private final DynamicModelCache<Item> mfix$itemModelCache = new DynamicModelCache<>(k -> this.mfix$getModelForItem((Item)k), true);

View File

@ -0,0 +1,28 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.HashMap;
import java.util.Map;
@Mixin(ModelManager.class)
@ClientOnlyMixin
public class ModelManagerMixin {
@Shadow private Map<ResourceLocation, BakedModel> bakedRegistry;
@Inject(method = "<init>", at = @At("RETURN"))
private void injectDummyBakedRegistry(CallbackInfo ci) {
if(this.bakedRegistry == null) {
// Create a dummy baked registry. This prevents NPEs when mods query block models too early
this.bakedRegistry = new HashMap<>();
}
}
}

View File

@ -0,0 +1,47 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_sounds;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalNotification;
import com.mojang.blaze3d.audio.SoundBuffer;
import net.minecraft.client.sounds.SoundBufferLibrary;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.dynamicresources.DynamicSoundHelpers;
import org.embeddedt.modernfix.ModernFix;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.Map;
@Mixin(SoundBufferLibrary.class)
@ClientOnlyMixin
public abstract class SoundBufferLibraryMixin {
private static final boolean debugDynamicSoundLoading = Boolean.getBoolean("modernfix.debugDynamicSoundLoading");
@Shadow @Final @Mutable
private Map<ResourceLocation, CompletableFuture<SoundBuffer>> cache = CacheBuilder.newBuilder()
.expireAfterAccess(DynamicSoundHelpers.MAX_SOUND_LIFETIME_SECS, TimeUnit.SECONDS)
.concurrencyLevel(1)
// Excessive use of type hinting due to it assuming Object as the broadest correct type
.<ResourceLocation, CompletableFuture<SoundBuffer>>removalListener(this::onSoundRemoval)
.build()
.asMap();
private <K extends ResourceLocation, V extends CompletableFuture<SoundBuffer>> void onSoundRemoval(RemovalNotification<K, V> notification) {
if(notification.getCause() == RemovalCause.REPLACED && notification.getValue() == cache.get(notification.getKey()))
return;
notification.getValue().thenAccept(SoundBuffer::discardAlBuffer);
if(!debugDynamicSoundLoading)
return;
K k = notification.getKey();
if(k == null)
return;
ModernFix.LOGGER.warn("Evicted sound {}", k);
}
}

View File

@ -3,12 +3,10 @@ package org.embeddedt.modernfix.common.mixin.perf.dynamic_structure_manager;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.mojang.datafixers.DataFixer;
import net.minecraft.core.HolderGetter;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@ -19,17 +17,16 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
import java.util.Optional;
@Mixin(StructureTemplateManager.class)
@Mixin(StructureManager.class)
public class StructureManagerMixin {
@Shadow @Final @Mutable
private Map<ResourceLocation, Optional<StructureTemplate>> structureRepository;
private Map<ResourceLocation, StructureTemplate> structureRepository;
@Inject(method = "<init>", at = @At("RETURN"))
private void makeStructuresSafe(ResourceManager arg, LevelStorageSource.LevelStorageAccess arg2, DataFixer dataFixer, HolderGetter<Block> arg3, CallbackInfo ci) {
private void makeStructuresSafe(ResourceManager arg, LevelStorageSource.LevelStorageAccess arg2, DataFixer dataFixer, CallbackInfo ci) {
/* Structures needing to be reloaded is not a huge issue since we optimize loading them already */
Cache<ResourceLocation, Optional<StructureTemplate>> structureCache = CacheBuilder.newBuilder()
Cache<ResourceLocation, StructureTemplate> structureCache = CacheBuilder.newBuilder()
.softValues()
.build();
this.structureRepository = structureCache.asMap();

View File

@ -0,0 +1,71 @@
package org.embeddedt.modernfix.common.mixin.perf.faster_font_loading;
import com.mojang.blaze3d.platform.NativeImage;
import net.minecraft.client.gui.font.providers.LegacyUnicodeBitmapsProvider;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* Objective: avoid recomputing locations many times, as well as loading all the font sheets in the constructor
* only to do it again later.
*/
@Mixin(LegacyUnicodeBitmapsProvider.class)
@ClientOnlyMixin
public abstract class LegacyUnicodeBitmapsProviderMixin {
@Shadow protected abstract ResourceLocation getSheetLocation(int i);
@Shadow @Final private Map<ResourceLocation, NativeImage> textures;
private final ResourceLocation[] glyphLocations = new ResourceLocation[256];
private ResourceLocation currentCharIdx;
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/font/providers/LegacyUnicodeBitmapsProvider;getSheetLocation(I)Lnet/minecraft/resources/ResourceLocation;"))
private ResourceLocation storeCurrentCharIdx(LegacyUnicodeBitmapsProvider provider, int i) {
ResourceLocation location = getSheetLocation(i);
currentCharIdx = location;
return location;
}
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/NativeImage;read(Lcom/mojang/blaze3d/platform/NativeImage$Format;Ljava/io/InputStream;)Lcom/mojang/blaze3d/platform/NativeImage;"))
private NativeImage storeLoadedFontSheet(NativeImage.Format format, InputStream stream) throws IOException {
NativeImage image = NativeImage.read(format, stream);
textures.put(currentCharIdx, image);
return image;
}
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/NativeImage;close()V"))
private void skipCloseNativeImage(NativeImage image) {
/* we can't close here, as the image has been stored for use later */
}
@Inject(method = "<init>", at = @At("RETURN"))
private void clearLocation(CallbackInfo ci) {
currentCharIdx = null;
}
@Inject(method = "getSheetLocation", at = @At("HEAD"), cancellable = true)
private void useCachedLocation(int idx, CallbackInfoReturnable<ResourceLocation> cir) {
int cachedIdx = idx / 256;
if(cachedIdx >= 0 && cachedIdx < glyphLocations.length && glyphLocations[cachedIdx] != null)
cir.setReturnValue(glyphLocations[cachedIdx]);
}
@Inject(method = "getSheetLocation", at = @At("RETURN"))
private void saveCachedLocation(int idx, CallbackInfoReturnable<ResourceLocation> cir) {
int cachedIdx = idx / 256;
if(cachedIdx >= 0 && cachedIdx < glyphLocations.length)
glyphLocations[cachedIdx] = cir.getReturnValue();
}
}

View File

@ -5,10 +5,10 @@ import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.block.model.ItemTransform;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.SimpleBakedModel;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
@ -24,11 +24,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = ItemRenderer.class, priority = 600)
@ClientOnlyMixin
public abstract class ItemRendererMixin {
private ItemDisplayContext transformType;
private ItemTransforms.TransformType transformType;
private final SimpleItemModelView modelView = new SimpleItemModelView();
@Inject(method = "render", at = @At("HEAD"))
private void markRenderingType(ItemStack itemStack, ItemDisplayContext transformType, boolean leftHand, PoseStack matrixStack, MultiBufferSource buffer, int combinedLight, int combinedOverlay, BakedModel model, CallbackInfo ci) {
private void markRenderingType(ItemStack itemStack, ItemTransforms.TransformType transformType, boolean leftHand, PoseStack matrixStack, MultiBufferSource buffer, int combinedLight, int combinedOverlay, BakedModel model, CallbackInfo ci) {
this.transformType = transformType;
}
@ -48,7 +48,7 @@ public abstract class ItemRendererMixin {
return model;
}
if(!RenderState.IS_RENDERING_LEVEL && !stack.isEmpty() && model.getClass() == SimpleBakedModel.class && transformType == ItemDisplayContext.GUI) {
if(!RenderState.IS_RENDERING_LEVEL && !stack.isEmpty() && model.getClass() == SimpleBakedModel.class && transformType == ItemTransforms.TransformType.GUI) {
FastItemRenderType type;
ItemTransform transform = model.getTransforms().gui;
if(transform == ItemTransform.NO_TRANSFORM)

View File

@ -0,0 +1,123 @@
package org.embeddedt.modernfix.common.mixin.perf.faster_texture_loading;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.metadata.animation.AnimationMetadataSection;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
@Mixin(value = TextureAtlas.class, priority = 600)
@ClientOnlyMixin
public abstract class TextureAtlasMixin {
@Shadow protected abstract ResourceLocation getResourceLocation(ResourceLocation location);
@Shadow protected abstract Collection<TextureAtlasSprite.Info> getBasicSpriteInfos(ResourceManager resourceManager, Set<ResourceLocation> spriteLocations);
private Map<ResourceLocation, Pair<Resource, NativeImage>> loadedImages = new ConcurrentHashMap<>();
private boolean usingFasterLoad;
private Collection<TextureAtlasSprite.Info> storedResults;
/**
* @author embeddedt
* @reason simplify texture loading by loading whole image once, avoid slow PngInfo code
*/
@Inject(method = "getBasicSpriteInfos", at = @At("HEAD"))
private void loadImages(ResourceManager manager, Set<ResourceLocation> imageLocations, CallbackInfoReturnable<Collection<TextureAtlasSprite.Info>> cir) {
usingFasterLoad = ModernFixPlatformHooks.INSTANCE.isLoadingNormally();
}
@Redirect(method = "getBasicSpriteInfos", at = @At(value = "INVOKE", target = "Ljava/util/Set;iterator()Ljava/util/Iterator;", ordinal = 0))
private Iterator<?> skipIteration(Set<?> instance, ResourceManager manager, Set<ResourceLocation> imageLocations) {
// bail if Forge is erroring to avoid AT crashes
if(!usingFasterLoad)
return instance.iterator();
List<CompletableFuture<?>> futures = new ArrayList<>();
ConcurrentLinkedQueue<TextureAtlasSprite.Info> results = new ConcurrentLinkedQueue<>();
for(ResourceLocation location : imageLocations) {
if(MissingTextureAtlasSprite.getLocation().equals(location))
continue;
futures.add(CompletableFuture.runAsync(() -> {
try {
ResourceLocation fileLocation = this.getResourceLocation(location);
Resource resource = manager.getResource(fileLocation);
NativeImage image = NativeImage.read(resource.getInputStream());
AnimationMetadataSection animData = resource.getMetadata(AnimationMetadataSection.SERIALIZER);
if (animData == null) {
animData = AnimationMetadataSection.EMPTY;
}
Pair<Integer, Integer> dimensions = animData.getFrameSize(image.getWidth(), image.getHeight());
loadedImages.put(location, Pair.of(resource, image));
results.add(new TextureAtlasSprite.Info(location, dimensions.getFirst(), dimensions.getSecond(), animData));
} catch(IOException e) {
ModernFix.LOGGER.error("Using missing texture, unable to load {} : {}", location, e);
} catch(RuntimeException e) {
ModernFix.LOGGER.error("Unable to parse metadata from {} : {}", location, e);
}
}, ModernFix.resourceReloadExecutor()));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
storedResults = results;
return Collections.emptyIterator();
}
@Inject(method = "getBasicSpriteInfos", at = @At("RETURN"))
private void injectFastSprites(ResourceManager resourceManager, Set<ResourceLocation> spriteLocations, CallbackInfoReturnable<Collection<TextureAtlasSprite.Info>> cir) {
if(usingFasterLoad)
cir.getReturnValue().addAll(storedResults);
}
@Inject(method = "prepareToStitch", at = @At("HEAD"))
private void initMap(CallbackInfoReturnable<TextureAtlas.Preparations> cir) {
loadedImages = new ConcurrentHashMap<>();
}
@Inject(method = "prepareToStitch", at = @At("RETURN"))
private void clearLoadedImages(CallbackInfoReturnable<TextureAtlas.Preparations> cir) {
loadedImages = Collections.emptyMap();
storedResults = null;
}
@Inject(method = "load(Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite$Info;IIIII)Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/texture/TextureAtlas;getResourceLocation(Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/resources/ResourceLocation;"), cancellable = true)
private void loadFromExisting(ResourceManager resourceManager, TextureAtlasSprite.Info spriteInfo, int width, int height, int mipmapLevel, int originX, int originY, CallbackInfoReturnable<TextureAtlasSprite> cir) {
if(!usingFasterLoad)
return;
Pair<Resource, NativeImage> pair = loadedImages.get(spriteInfo.name());
if(pair == null) {
ModernFix.LOGGER.error("Texture {} was not loaded in early stage", spriteInfo.name());
cir.setReturnValue(null);
} else {
TextureAtlasSprite sprite = null;
try {
sprite = ModernFixPlatformHooks.INSTANCE.loadTextureAtlasSprite((TextureAtlas)(Object)this, resourceManager, spriteInfo, pair.getFirst(), width, height, originX, originY, mipmapLevel, pair.getSecond());
} catch(RuntimeException e) {
ModernFix.LOGGER.error("Error loading texture {}: {}", spriteInfo.name(), e);
} finally {
try {
pair.getFirst().close();
} catch(IOException ignored) {
// not much we can do
}
}
cir.setReturnValue(sprite);
}
}
}

View File

@ -18,22 +18,22 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
@Mixin(Stitcher.class)
@ClientOnlyMixin
public class StitcherMixin<T extends Stitcher.Entry> {
@Shadow @Final private List<Stitcher.Holder<T>> texturesToBeStitched;
public class StitcherMixin {
@Shadow @Final private Set<Stitcher.Holder> texturesToBeStitched;
@Shadow private int storageX;
@Shadow private int storageY;
@Shadow @Final private static Comparator<Stitcher.Holder> HOLDER_COMPARATOR;
@Shadow @Final private int maxWidth;
@Shadow @Final private int maxHeight;
@Shadow @Final private static Comparator<Stitcher.Holder<?>> HOLDER_COMPARATOR;
private List<StbStitcher.LoadableSpriteInfo<T>> loadableSpriteInfos;
private List<StbStitcher.LoadableSpriteInfo> loadableSpriteInfos;
/**
* @author embeddedt, SuperCoder79
@ -52,18 +52,18 @@ public class StitcherMixin<T extends Stitcher.Entry> {
return;
}
ci.cancel();
ObjectArrayList<Stitcher.Holder<T>> holderList = new ObjectArrayList<>(this.texturesToBeStitched);
ObjectArrayList<Stitcher.Holder> holderList = new ObjectArrayList<>(this.texturesToBeStitched);
holderList.sort(HOLDER_COMPARATOR);
Stitcher.Holder<T>[] aholder = holderList.toArray(new Stitcher.Holder[0]);
Stitcher.Holder[] aholder = holderList.toArray(new Stitcher.Holder[0]);
Pair<Pair<Integer, Integer>, List<StbStitcher.LoadableSpriteInfo<T>>> packingInfo = StbStitcher.packRects(aholder);
Pair<Pair<Integer, Integer>, List<StbStitcher.LoadableSpriteInfo>> packingInfo = StbStitcher.packRects(aholder);
this.storageX = packingInfo.getFirst().getFirst();
this.storageY = packingInfo.getFirst().getSecond();
// Detect an oversized atlas generated in the previous step.
if(this.storageX > this.maxWidth || this.storageY > this.maxHeight) {
ModernFix.LOGGER.error("Requested atlas size {}x{} exceeds maximum of {}x{}", this.storageX, this.storageY, this.maxWidth, this.maxHeight);
throw new StitcherException(aholder[0].entry(), Stream.of(aholder).map(arg -> arg.entry()).collect(ImmutableList.toImmutableList()));
throw new StitcherException(aholder[0].spriteInfo, Stream.of(aholder).map(arg -> arg.spriteInfo).collect(ImmutableList.toImmutableList()));
}
this.loadableSpriteInfos = packingInfo.getSecond();
@ -74,12 +74,12 @@ public class StitcherMixin<T extends Stitcher.Entry> {
* @reason We setup the image ourselves in the StbStitcher, so we just feed this information back into the vanilla code
*/
@Inject(method = "gatherSprites", at = @At("HEAD"), cancellable = true)
private void gatherSpritesFast(Stitcher.SpriteLoader<T> spriteLoader, CallbackInfo ci) {
private void gatherSpritesFast(Stitcher.SpriteLoader spriteLoader, CallbackInfo ci) {
if(this.loadableSpriteInfos == null)
return;
ci.cancel();
for(StbStitcher.LoadableSpriteInfo<T> info : loadableSpriteInfos) {
spriteLoader.load(info.info, info.x, info.y);
for(StbStitcher.LoadableSpriteInfo info : loadableSpriteInfos) {
spriteLoader.load(info.info, info.width, info.height, info.x, info.y);
}
}
}

View File

@ -0,0 +1,23 @@
package org.embeddedt.modernfix.common.mixin.perf.fix_loop_spin_waiting;
import net.minecraft.util.thread.BlockableEventLoop;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
// This should fix https://bugs.mojang.com/browse/MC-183518
@Mixin(value = BlockableEventLoop.class, priority = 500)
public class BlockableEventLoopMixin {
private static final long MFIX$TICK_WAIT_TIME = TimeUnit.MILLISECONDS.toNanos(2);
/**
* @author embeddedt
* @reason yielding the thread is pretty pointless if we're about to park anyway
*/
@Overwrite
protected void waitForTasks() {
LockSupport.parkNanos("waiting for tasks", MFIX$TICK_WAIT_TIME);
}
}

View File

@ -12,7 +12,7 @@ public class BooleanPropertyMixin {
* There is no point comparing the immutable sets in any two instances of this class, as they will always be
* the same.
*/
@Redirect(method = "equals", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableSet;equals(Ljava/lang/Object;)Z", remap = false), remap = false)
@Redirect(method = "equals", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableSet;equals(Ljava/lang/Object;)Z", remap = false))
private boolean skipEqualityCheck(ImmutableSet instance, Object object) {
return true;
}

View File

@ -1,27 +1,30 @@
package org.embeddedt.modernfix.common.mixin.perf.model_optimizations;
import net.minecraft.world.level.block.state.properties.Property;
import org.embeddedt.modernfix.dedup.IdentifierCaches;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(Property.class)
public class PropertyMixin {
@Shadow @Final private String name;
@Shadow @Mutable
@Final private String name;
@Shadow private Integer hashCode;
@Shadow @Final private Class clazz;
@ModifyVariable(method = "<init>", at = @At("HEAD"), ordinal = 0, argsOnly = true)
private static String internName(String name) {
return name.intern();
@Redirect(method = "<init>", at = @At(value = "FIELD", target = "Lnet/minecraft/world/level/block/state/properties/Property;name:Ljava/lang/String;"))
private void internName(Property instance, String name) {
this.name = IdentifierCaches.PROPERTY.deduplicate(name);
}
/**
* @author embeddedt
* @reason compare hashcodes if generated, use reference equality for speed
*/
@Overwrite(remap = false)
@Overwrite
public boolean equals(Object p_equals_1_) {
if (this == p_equals_1_) {
return true;
@ -29,8 +32,7 @@ public class PropertyMixin {
return false;
} else {
Property<?> property = (Property)p_equals_1_;
/* reference equality is safe here because of interning above */
//noinspection StringEquality
/* reference equality is safe here because of deduplication */
return this.clazz == property.getValueClass() && this.name == property.getName();
}
}

View File

@ -1,7 +1,7 @@
package org.embeddedt.modernfix.common.mixin.perf.model_optimizations;
import com.mojang.math.Matrix4f;
import com.mojang.math.Transformation;
import org.joml.Matrix4f;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@ -19,7 +19,7 @@ public class TransformationMatrixMixin {
* @author embeddedt
* @reason use cached hashcode if exists
*/
@Overwrite(remap = false)
@Overwrite
public int hashCode() {
int hash;
if(cachedHashCode != null) {

View File

@ -14,7 +14,7 @@ public class MappedRegistryMixin {
* The original behavior causes O(n) time complexity for registration.
*/
@Redirect(
method = "registerMapping(ILnet/minecraft/resources/ResourceKey;Ljava/lang/Object;Lcom/mojang/serialization/Lifecycle;)Lnet/minecraft/core/Holder$Reference;",
method = "registerMapping(ILnet/minecraft/resources/ResourceKey;Ljava/lang/Object;Lcom/mojang/serialization/Lifecycle;Z)Ljava/lang/Object;",
at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/objects/ObjectList;size(I)V", remap = false)
)
private void setSizeSmart(ObjectList<?> list, int size) {

View File

@ -0,0 +1,28 @@
package org.embeddedt.modernfix.common.mixin.perf.mojang_registry_size;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(ResourceKey.class)
public class ResourceKeyMixin<T> {
private static final Map<ResourceLocation, Map<ResourceLocation, ResourceKey<?>>> INTERNING_MAP = new Object2ObjectOpenHashMap<>();
@Inject(method = "create(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/resources/ResourceKey;", at = @At("HEAD"), cancellable = true)
private static <T> void createEfficient(ResourceLocation parent, ResourceLocation location, CallbackInfoReturnable<ResourceKey<T>> cir) {
synchronized (ResourceKey.class) {
Map<ResourceLocation, ResourceKey<?>> keys = INTERNING_MAP.computeIfAbsent(parent, k -> new Object2ObjectOpenHashMap<>());
ResourceKey<?> key = keys.get(location);
if(key == null) {
key = new ResourceKey<>(parent, location);
keys.put(location, key);
}
cir.setReturnValue((ResourceKey<T>)key);
}
}
}

View File

@ -0,0 +1,20 @@
package org.embeddedt.modernfix.common.mixin.perf.nbt_memory_usage;
import net.minecraft.nbt.Tag;
import org.embeddedt.modernfix.util.CanonizingStringMap;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import java.util.Map;
@Mixin(targets = "net/minecraft/nbt/CompoundTag$1")
public class CompoundTag1Mixin {
@ModifyVariable(method = "load(Ljava/io/DataInput;ILnet/minecraft/nbt/NbtAccounter;)Lnet/minecraft/nbt/CompoundTag;", at = @At(value = "INVOKE_ASSIGN", target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;", remap = false))
private Map<String, Tag> modifyMap(Map<String, Tag> map) {
CanonizingStringMap<Tag> newMap = new CanonizingStringMap<>();
if(map != null)
newMap.putAll(map);
return newMap;
}
}

View File

@ -0,0 +1,40 @@
package org.embeddedt.modernfix.common.mixin.perf.nbt_memory_usage;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import org.embeddedt.modernfix.util.CanonizingStringMap;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(CompoundTag.class)
public class CompoundTagMixin {
@Shadow @Final
private Map<String, Tag> tags;
/**
* Ensure that the default backing map is a CanonizingStringMap.
*/
@ModifyArg(method = "<init>()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/nbt/CompoundTag;<init>(Ljava/util/Map;)V"), index = 0)
private static Map<String, Tag> useCanonizingStringMap(Map<String, Tag> incoming) {
CanonizingStringMap<Tag> newMap = new CanonizingStringMap<>();
if(incoming != null)
newMap.putAll(incoming);
return newMap;
}
/**
* @author embeddedt
* @reason use more efficient method when copying canonizing string map
*/
@Inject(method = "copy()Lnet/minecraft/nbt/Tag;", at = @At("HEAD"), cancellable = true)
public void copyEfficient(CallbackInfoReturnable<Tag> cir) {
if(this.tags instanceof CanonizingStringMap) {
cir.setReturnValue(new CompoundTag(CanonizingStringMap.deepCopy((CanonizingStringMap<Tag>)this.tags, Tag::copy)));
}
}
}

Some files were not shown because too many files have changed in this diff Show More