Compare commits

..

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

510 changed files with 8039 additions and 13320 deletions

View File

@ -1,78 +0,0 @@
name: Bug Report
description: "For reporting bugs and other defects"
body:
- type: markdown
attributes:
value: >-
**Need help?** Ask on [Discord](https://discord.gg/rN9Y7caguP) instead of opening an issue.
**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
- 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.
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
- 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. "
validations:
required: true
- type: textarea
id: diagnostic-info
attributes:
label: Diagnostic Info
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/`.
validations:
required: true

View File

@ -1,6 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: For help with other issues, join our Discord community
url: https://discord.gg/rN9Y7caguP
about: This is the best option for getting help with mod installation, performance issues, and any other support inquiries
# Copied from https://github.com/CaffeineMC/sodium-fabric#community

View File

@ -10,127 +10,35 @@ on:
jobs:
build:
runs-on: ubuntu-22.04
permissions:
issues: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 21
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
java-version: 17
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
uses: gradle/gradle-build-action@v2
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: 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
- name: Setup project Loom cache
uses: actions/cache@v3
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}`);
}
}
path: |
.gradle/loom-cache
key: ${{ runner.os }}-gradle-${{ hashFiles('**/gradle.properties', '**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: ${{ runner.os }}-gradle-
- name: Build ModernFix
run: |
chmod +x gradlew
./gradlew build
- name: Upload Artifacts to GitHub
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: Package
path: bin

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

@ -0,0 +1,42 @@
name: Release ModernFix Artifacts
on:
release:
types:
- published
jobs:
release:
if: github.repository_owner == 'embeddedt'
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 17
- name: Remove tags for release on other versions
run: ./scripts/tagcleaner.sh
- name: Build and publish mod to CurseForge & Modrinth
run: |
chmod +x gradlew
./gradlew forge:publishToModSites fabric:publishToModSites forge:copyJarToBin fabric:copyJarToBin
env:
CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
- name: Upload assets to GitHub
uses: AButler/upload-release-assets@v2.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

@ -8,10 +8,10 @@ on:
jobs:
wikigen:
if: github.repository_owner == 'embeddedt'
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Generate Markdown Patch-List
@ -24,4 +24,4 @@ jobs:
with:
path: "doc/generated"
env:
GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.WIKI_TOKEN }}
GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.WIKI_TOKEN }}

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

@ -6,11 +6,11 @@ Some fixes are based on prior work in various Forge PRs (check commit history an
is directly derived from Sodium and used under the terms of the LGPL-3.0 license.
## Development builds (generally stable, but may occasionally have bugs)
- 1.16.5: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.16/Package.zip
- 1.18.2: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.18/Package.zip
- 1.19.2: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.19.2/Package.zip
- 1.20.1: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.20/Package.zip
- 1.20.2: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.20.2/Package.zip
- 1.16.5, both modloaders: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.16/Package.zip
- 1.18.2, both modloaders: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.18/Package.zip
- 1.19.2, both modloaders: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.19.2/Package.zip
- 1.19.4, both modloaders: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.19.4/Package.zip
- 1.20.1, both modloaders: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.20/Package.zip
------------

View File

@ -1,54 +0,0 @@
plugins {
id 'com.gradleup.shadow' version '8.3.9'
id 'java-library'
}
repositories {
mavenCentral()
maven { url uri("https://maven.fabricmc.net") }
maven { url "https://maven.neoforged.net/releases" }
}
dependencies {
annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
compileOnly 'com.google.auto.service:auto-service:1.1.1'
implementation 'com.google.code.gson:gson:2.10.1'
shadow 'com.google.code.gson:gson:2.10.1'
implementation 'com.google.auto:auto-common:1.2.1'
shadow 'com.google.auto:auto-common:1.2.1'
implementation 'com.google.guava:guava:21.0'
shadow 'com.google.guava:guava:21.0'
implementation project(":annotations")
shadow project(":annotations")
// Shadow annotations
implementation 'net.fabricmc:sponge-mixin:0.12.5+'
implementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
implementation 'net.minecraftforge:mergetool:1.1.7'
implementation 'net.neoforged:mergetool:2.0.2'
}
tasks.withType(JavaCompile) {
options.release = 17
}
shadowJar {
dependencies {
include(dependency('net.fabricmc:sponge-mixin:'))
include(dependency('net.fabricmc:fabric-loader:'))
include(dependency(':mergetool:'))
}
// 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'}
include {it.getName() == 'Dist.class'}
include {it.getName() == 'Environment.class'}
include {it.getName() == 'EnvType.class'}
}
version = '1.1.4'

View File

@ -1,184 +0,0 @@
package org.fury_phoenix.mixinAp.annotation;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import net.fabricmc.api.Environment;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.annotation.IgnoreMixin;
import org.fury_phoenix.mixinAp.util.TypedAccessorMap;
import org.spongepowered.asm.mixin.Mixin;
import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
import static java.util.AbstractMap.SimpleImmutableEntry;
public class ClientMixinValidator {
private final Messager messager;
private final Elements elemUtils;
private final Types types;
private final boolean debug;
private static final TypedAccessorMap<Annotation> markers = new TypedAccessorMap<>();
private static final Map.Entry<Class<Environment>, Function<? super Environment, ?>>
FabricAccessor = new SimpleImmutableEntry<>(Environment.class, Environment::value);
private static final Map.Entry<
Class<net.minecraftforge.api.distmarker.OnlyIn>,
Function<? super net.minecraftforge.api.distmarker.OnlyIn, ?>>
ForgeAccessor = new SimpleImmutableEntry<>(
net.minecraftforge.api.distmarker.OnlyIn.class,
net.minecraftforge.api.distmarker.OnlyIn::value
);
private static final Map.Entry<
Class<net.neoforged.api.distmarker.OnlyIn>,
Function<? super net.neoforged.api.distmarker.OnlyIn, ?>>
NeoForgeAccessor = new SimpleImmutableEntry<>(
net.neoforged.api.distmarker.OnlyIn.class,
net.neoforged.api.distmarker.OnlyIn::value
);
static {
markers.put(FabricAccessor);
markers.put(ForgeAccessor);
markers.put(NeoForgeAccessor);
}
private static final Collection<String> unannotatedClasses = new HashSet<>();
public ClientMixinValidator(ProcessingEnvironment env) {
debug = Boolean.valueOf(env.getOptions().get("org.fury_phoenix.mixinAp.validator.debug"));
messager = env.getMessager();
elemUtils = env.getElementUtils();
types = env.getTypeUtils();
}
public boolean validateMixin(TypeElement annotatedMixinClass) {
return targetsClient(annotatedMixinClass) &&
(annotatedMixinClass.getAnnotation(ClientOnlyMixin.class) == null);
}
public boolean targetsClient(TypeElement annotatedMixinClass) {
return targetsClient(getTargets(annotatedMixinClass)) &&
!isIgnored(annotatedMixinClass);
}
private boolean targetsClient(Collection<?> classTargets) {
return classTargets.stream().anyMatch(this::targetsClient);
}
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: "
+ classTarget.getClass() + "\n" + "Stringified contents: "
+ classTarget.toString());
}
}
private boolean isClientMarked(TypeElement te) {
for (var entry : markers.entrySet()) {
var marker = te.getAnnotation(entry.getKey());
if(marker == null) continue;
return entry.getValue().apply(marker).toString().equals("CLIENT");
}
if(debug && unannotatedClasses.add(te.toString())) {
messager.printMessage(Diagnostic.Kind.WARNING,
"No marker annotations present on " + te + "!");
}
return false;
}
private boolean isIgnored(TypeElement te) {
if(te.getAnnotation(IgnoreMixin.class) != null) {
messager.printMessage(Diagnostic.Kind.WARNING,
toSourceString(te.toString()) + " is ignored!");
return true;
}
return false;
}
private boolean warn(Object o) {
messager.printMessage(Diagnostic.Kind.WARNING,
toSourceString(o.toString()) + " can't be loaded, so it is skipped!");
return false;
}
public Map.Entry<? extends CharSequence, ? extends CharSequence>
getClientMixinEntry(TypeElement annotatedMixinClass) {
return new SimpleImmutableEntry<>(
annotatedMixinClass.getQualifiedName(),
getTargets(annotatedMixinClass)
.stream()
.filter(this::targetsClient)
.map(Object::toString)
.map(ClientMixinValidator::toSourceString)
.collect(Collectors.joining(", "))
);
}
private Collection<Object> getTargets(TypeElement annotatedMixinClass) {
Collection<? extends TypeMirror> clzsses = Set.of();
Collection<? extends String> imaginaries = Set.of();
TypeMirror MixinElement = elemUtils.getTypeElement(Mixin.class.getName()).asType();
for (var mirror : annotatedMixinClass.getAnnotationMirrors()) {
if(!types.isSameType(mirror.getAnnotationType(), MixinElement))
continue;
@SuppressWarnings("unchecked")
var wrappedClzss = (List<? extends AnnotationValue>)
getAnnotationValue(mirror, "value").getValue();
clzsses = wrappedClzss.stream()
.map(AnnotationValue::getValue)
.map(TypeMirror.class::cast)
.collect(Collectors.toSet());
@SuppressWarnings("unchecked")
var wrappedStrings = (List<? extends AnnotationValue>)
getAnnotationValue(mirror, "targets").getValue();
imaginaries = wrappedStrings.stream()
.map(AnnotationValue::getValue)
.map(String.class::cast)
.collect(Collectors.toSet());
}
return Stream.of(clzsses, imaginaries)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
}
public static String toSourceString(String bytecodeName) {
return bytecodeName.replaceAll("\\/", ".");
}
}

View File

@ -1,118 +0,0 @@
package org.fury_phoenix.mixinAp.annotation;
import com.google.auto.service.AutoService;
import com.google.common.base.Throwables;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
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"})
@AutoService(Processor.class)
public class MixinProcessor extends AbstractProcessor {
// Remember to call toString when using aliases
private static final Map<String, String> aliases = Map.of(
"Mixin", "mixins",
"ClientOnlyMixin", "client"
);
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 {
if(roundEnv.processingOver()){
filterMixinSets();
// create record for serialization, compute package name
String packageName = Optional.ofNullable(mixinConfigList.get("mixins"))
.orElse(mixinConfigList.get("client"))
.get(0).split("(?<=mixin)")[0];
finalizeMixinConfig();
new MixinConfig(packageName,
mixinConfigList.get("mixins"),
mixinConfigList.get("client")
).generateMixinConfig(processingEnv);
} else {
processMixins(annotations, roundEnv);
}
} catch (Exception e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Fatal error:" +
Throwables.getStackTraceAsString(e));
throw new RuntimeException(e);
// Halt the AP to prevent nonsense errors
}
return false;
}
private void processMixins(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
Set<? extends Element> annotatedMixins = roundEnv.getElementsAnnotatedWith(annotation);
Stream<TypeElement> mixinStream =
annotatedMixins.stream()
.map(TypeElement.class::cast);
validateCommonMixins(annotation, mixinStream);
List<String> mixins =
annotatedMixins.stream()
.map(TypeElement.class::cast)
.map(e -> processingEnv.getElementUtils().getBinaryName(e).toString())
.collect(Collectors.toList());
mixinConfigList.putIfAbsent(aliases.get(annotation.getSimpleName().toString()), mixins);
}
}
private void filterMixinSets() {
List<String> commonSet = mixinConfigList.get("mixins");
if(commonSet == null) return;
commonSet.removeAll(mixinConfigList.get("client"));
}
private void validateCommonMixins(TypeElement annotation, Stream<TypeElement> mixins) {
if(!annotation.getSimpleName().toString().equals("Mixin"))
return;
ClientMixinValidator validator = new ClientMixinValidator(processingEnv);
// The implementation may throw a CME
mixins.sequential()
.filter(validator::validateMixin)
.map(validator::getClientMixinEntry)
.forEach(this::logClientClassTarget);
}
private void logClientClassTarget(Map.Entry<? extends CharSequence, ? extends CharSequence> mixin) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Mixin " + mixin.getKey() + " targets client-side classes: " + mixin.getValue());
}
private void finalizeMixinConfig() {
// relativize class names
for(var list : mixinConfigList.values()) {
list.replaceAll(className -> className.split("(?<=mixin.)")[1]);
}
}
}

View File

@ -1,65 +0,0 @@
package org.fury_phoenix.mixinAp.config;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.Optional;
import javax.annotation.processing.ProcessingEnvironment;
import javax.tools.StandardLocation;
public record MixinConfig(
boolean required,
String minVersion,
@SerializedName("package")
String packageName,
String plugin,
String compatibilityLevel,
@SerializedName("mixins")
List<String> commonMixins,
@SerializedName("client")
List<String> clientMixins,
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",
commonMixins, clientMixins, InjectorOptions.DEFAULT, OverwriteOptions.DEFAULT);
}
public record InjectorOptions(int defaultRequire) {
public static final InjectorOptions DEFAULT = new InjectorOptions(1);
}
public record OverwriteOptions(boolean conformVisibility) {
public static final OverwriteOptions DEFAULT = new OverwriteOptions(true);
}
public void generateMixinConfig(ProcessingEnvironment env) throws IOException {
try (
Writer mixinConfigWriter = env.getFiler()
.createResource(StandardLocation.SOURCE_OUTPUT, "",
MixinConfig.computeMixinConfigPath(
Optional.of(env.getOptions().get("rootProject.name")),
Optional.ofNullable(env.getOptions().get("project.name"))
)
).openWriter()
) {
String mixinConfig = new GsonBuilder()
.setPrettyPrinting()
.create()
.toJson(this);
mixinConfigWriter.write(mixinConfig);
mixinConfigWriter.write("\n");
} catch (IOException e) { throw e; }
}
private static String computeMixinConfigPath(Optional<String> rootProjectName, Optional<String> projectName) {
return "resources/" +
rootProjectName.get() +
(projectName.isPresent() ? "-" : "") +
projectName.orElse("") +
".mixins.json";
}
}

View File

@ -1,37 +0,0 @@
package org.fury_phoenix.mixinAp.util;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import static java.util.Map.Entry;
/**
* Type-safe heterogenous map of accessors
* @author Fury_Phoenix
* @reason Type-safety since K, V of Map are non-identical
* @param <SuperType> The supertype of desired types.
* This is useful in cases such as <A extends Annotation>.
*/
public class TypedAccessorMap<SuperType> {
private final Map<Class<? extends SuperType>, Function<Object, ?>> typedAccessors = new HashMap<>();
public <T extends SuperType> void put(Class<T> key, Function<? super T, ?> func) {
Objects.requireNonNull(func);
typedAccessors.put(Objects.requireNonNull(key), o -> func.apply(key.cast(o)));
}
public <T extends SuperType> void put(Entry<Class<T>, Function<? super T, ?>> entry) {
put(entry.getKey(), entry.getValue());
}
public <T extends SuperType> Function<Object, ?> get(Class<T> key) {
return typedAccessors.get(key);
}
public Set<Entry<Class<? extends SuperType>, Function<Object, ?>>> entrySet() {
return typedAccessors.entrySet();
}
}

View File

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

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,11 +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.SOURCE)
@Target(ElementType.TYPE)
public @interface IgnoreMixin {
}

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;
}

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.3-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,81 @@
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_17
repositories {
maven { url 'https://modmaven.dev/' }
maven {
url "https://cursemaven.com"
content {
includeGroup "curse.maven"
}
}
maven {
name = 'ParchmentMC'
url = 'https://maven.parchmentmc.org'
}
maven {
// Shedaniel's maven (Architectury API)
url = "https://maven.architectury.dev"
content {
includeGroup "me.shedaniel"
}
}
maven {
// saps.dev Maven (KubeJS and Rhino)
url = "https://maven.saps.dev/minecraft"
content {
includeGroup "dev.latvian.mods"
}
}
maven { // CTM
url "https://maven.tterrag.com/"
}
maven { url 'https://maven.blamejared.com' }
repositories {
maven {
name = "Fuzs Mod Resources"
url = "https://raw.githubusercontent.com/Fuzss/modresources/main/maven/"
}
}
maven {
url 'https://maven.terraformersmc.com/releases'
}
maven { url = "https://jitpack.io" }
}

View File

@ -0,0 +1,52 @@
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")
}
}
}
tasks {
processResources {
def mixinFileList = []
def mixinDirectory = file("src/main/java/org/embeddedt/modernfix/" + project.name + "/mixin")
fileTree(mixinDirectory).visit { FileVisitDetails details ->
if(details.file.isFile()) {
def fileName = mixinDirectory.relativePath(details.file).toString().replaceFirst(/\.java$/, "").replace('/', '.')
mixinFileList << fileName
}
}
def mixinClassesStringB = new StringBuilder()
for(int i = 0; i < mixinFileList.size(); i++) {
mixinClassesStringB.append(" \"")
mixinClassesStringB.append(mixinFileList.get(i))
mixinClassesStringB.append('"')
if(i < (mixinFileList.size() - 1))
mixinClassesStringB.append(',')
mixinClassesStringB.append('\n')
}
def replacements = [
mixin_classes: mixinClassesStringB.toString()
]
inputs.properties replacements
def filePattern = "modernfix-" + project.name + ".mixins.json"
filesMatching(filePattern) {
expand replacements
}
}
}

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("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)
}
}

54
common/build.gradle Normal file
View File

@ -0,0 +1,54 @@
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("com.github.llamalad7.mixinextras:mixinextras-common:${rootProject.mixinextras_version}"))
modCompileOnly("dev.latvian.mods:kubejs:${kubejs_version}") {
transitive = false
}
modApi("dev.latvian.mods:rhino:${rhino_version}") {
transitive = false
}
modApi("me.shedaniel:RoughlyEnoughItems-api:${rei_version}") {
transitive = false
}
modCompileOnly("me.shedaniel:RoughlyEnoughItems-fabric:${rei_version}") {
transitive = false
}
modCompileOnly("curse.maven:diagonal-fences-458048:${diagonal_fences_version}")
modCompileOnly "curse.maven:spark-361579:${rootProject.spark_version}"
// compile against the JEI API but do not include it at runtime
modCompileOnly("mezz.jei:jei-${minecraft_version}-common:${jei_version}")
modCompileOnly("mezz.jei:jei-${minecraft_version}-gui:${jei_version}")
modCompileOnly("mezz.jei:jei-${minecraft_version}-lib:${jei_version}")
// 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,7 +12,6 @@ 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;
@ -47,17 +45,6 @@ public class ModernFix {
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.util.MemoryReserve;
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.searchtree.JEIBackedSearchTree;
import org.embeddedt.modernfix.searchtree.REIBackedSearchTree;
import org.embeddedt.modernfix.searchtree.SearchTreeProviderRegistry;
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
MemoryReserve.release();
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.branding.F3Screen")) {
brandingString = ModernFix.NAME + " " + ModernFixPlatformHooks.INSTANCE.getVersionString();
}
SearchTreeProviderRegistry.register(JEIBackedSearchTree.PROVIDER);
SearchTreeProviderRegistry.register(REIBackedSearchTree.PROVIDER);
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);
}
}
}
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

@ -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 "";
}

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,8 +1,6 @@
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.resources.ResourceLocation;
@ -17,7 +15,6 @@ 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 {
@ -72,16 +69,6 @@ public final class ModelHelpers {
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,62 @@
package org.embeddedt.modernfix.blockstate;
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.StateHolder;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
public class FerriteCorePostProcess {
private static final boolean willPostProcess;
private static final MethodHandle theTable, toKeyIndex;
static {
boolean success = true;
MethodHandle table = null, keyIndex = null;
try {
Class<?> fastMap = Class.forName("malte0811.ferritecore.fastmap.FastMap");
Field field = fastMap.getDeclaredField("toKeyIndex");
field.setAccessible(true);
keyIndex = MethodHandles.publicLookup().unreflectSetter(field);
field = StateHolder.class.getDeclaredField("ferritecore_globalTable");
field.setAccessible(true);
table = MethodHandles.publicLookup().unreflectGetter(field);
} catch(ReflectiveOperationException | RuntimeException e) {
e.printStackTrace();
success = false;
}
willPostProcess = success;
theTable = table;
toKeyIndex = keyIndex;
}
private static final Object2IntMap<?> EMPTY_MAP;
static {
Object2IntArrayMap<?> map = new Object2IntArrayMap<>();
map.defaultReturnValue(-1);
EMPTY_MAP = Object2IntMaps.unmodifiable(map);
}
public static <O, S extends StateHolder<O, S>> void postProcess(StateDefinition<O, S> state) {
if(!willPostProcess)
return;
try {
if(state.getProperties().size() == 0) {
for(S holder : state.getPossibleStates()) {
// deduplicate Object2IntMap objects from FerriteCore
// will probably be fixed upstream at some point, but likely not for older versions
Object table = theTable.invoke(holder);
toKeyIndex.invoke(table, EMPTY_MAP);
}
}
} catch(Throwable e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,78 @@
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();
}
@Override
public int getMinBuildHeight() {
return this.wrapped.getMinBuildHeight();
}
@Override
public int getHeight() {
return this.wrapped.getHeight();
}
@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

@ -8,7 +8,6 @@ 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.duck.IProfilingServerFunctionManager;
import org.embeddedt.modernfix.structure.CachingStructureManager;
import java.io.InputStream;
@ -43,37 +42,17 @@ public class ModernFixCommands {
ResourceLocation structureLocation = new ResourceLocation(found.getNamespace(), matcher.group(1));
try(InputStream resource = entry.getValue().open()) {
CachingStructureManager.readStructureTag(structureLocation, level.getServer().getFixerUpper(), resource);
Component msg = Component.literal("checked " + structureLocation + " (" + upgradedNum + "/" + structures.size() + ")");
context.getSource().sendSuccess(() -> msg, false);
context.getSource().sendSuccess(Component.literal("checked " + structureLocation + " (" + upgradedNum + "/" + structures.size() + ")"), false);
} catch(Throwable e) {
ModernFix.LOGGER.error("Couldn't upgrade structure " + found, e);
context.getSource().sendFailure(Component.literal("error reading " + structureLocation + " (" + upgradedNum + "/" + structures.size() + ")"));
}
}
context.getSource().sendSuccess(() -> Component.literal("All structures upgraded"), false);
context.getSource().sendSuccess(Component.literal("All structures upgraded"), false);
return 1;
}))
.then(literal("mcfunctions").requires(source -> source.hasPermission(3))
.executes(context -> {
ServerLevel level = context.getSource().getLevel();
if(level == null) {
context.getSource().sendFailure(Component.literal("Couldn't find server level"));
return 0;
}
if (level.getServer().getFunctions() instanceof IProfilingServerFunctionManager profiler) {
context.getSource().sendSuccess(() -> Component.literal("mcfunction runtime breakdown:"), false);
for(String line : profiler.mfix$getProfilingResults().split("\n")) {
context.getSource().sendSuccess(() -> Component.literal(line), false);
}
return 1;
} else {
context.getSource().sendFailure(Component.literal("ModernFix mcfunction profiling is not enabled on this server."));
return 0;
}
}))
);
}
}

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,73 @@
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
import com.mojang.datafixers.util.Either;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
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.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
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();
}
Holder<Biome> plains = this.level.registryAccess().registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.PLAINS);
cir.setReturnValue(new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ), plains));
} 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());
else
ModernFix.LOGGER.warn("Suspicious async chunkload, pass -Dmodernfix.debugBadChunkloading=true for more details");
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,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,49 @@
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;
}
/**
* @author embeddedt, Mojang
* @reason avoid getItem()
*/
@Redirect(method = "isEmpty", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;is(Lnet/minecraft/world/item/Item;)Z"))
private boolean checkIsDirect(ItemStack stack, Item item) {
return this.item == 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,75 @@
package org.embeddedt.modernfix.common.mixin.bugfix.paper_chunk_patches;
import com.mojang.datafixers.util.Either;
import net.minecraft.server.level.*;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.thread.BlockableEventLoop;
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.levelgen.structure.templatesystem.StructureTemplateManager;
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.callback.CallbackInfoReturnable;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@Mixin(ChunkMap.class)
public abstract class ChunkMapMixin {
@Shadow @Final private BlockableEventLoop<Runnable> mainThreadExecutor;
@Shadow @Final private ChunkMap.DistanceManager distanceManager;
@Shadow protected abstract CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> protoChunkToFullChunk(ChunkHolder arg);
@Shadow @Final private ServerLevel level;
@Shadow @Final private ThreadedLevelLightEngine lightEngine;
@Shadow @Final private ChunkProgressListener progressListener;
@Shadow protected abstract CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> scheduleChunkGeneration(ChunkHolder chunkHolder, ChunkStatus chunkStatus);
@Shadow @Final private StructureTemplateManager structureTemplateManager;
/* https://github.com/PaperMC/Paper/blob/ver/1.17.1/patches/server/0752-Fix-chunks-refusing-to-unload-at-low-TPS.patch */
@ModifyArg(method = "prepareAccessibleChunk", 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;
}
/**
* @author embeddedt
* @reason revert 1.17 chunk system changes, significantly reduces time and RAM needed to load chunks
*/
@Inject(method = "schedule", at = @At("HEAD"), cancellable = true)
private void useLegacySchedulingLogic(ChunkHolder holder, ChunkStatus requiredStatus, CallbackInfoReturnable<CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> cir) {
if(requiredStatus != ChunkStatus.EMPTY) {
ChunkPos chunkpos = holder.getPos();
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = holder.getOrScheduleFuture(requiredStatus.getParent(), (ChunkMap)(Object)this);
cir.setReturnValue(future.thenComposeAsync((either) -> {
Optional<ChunkAccess> optional = either.left();
if (requiredStatus == ChunkStatus.LIGHT) {
this.distanceManager.addTicket(TicketType.LIGHT, chunkpos, 33 + ChunkStatus.getDistance(ChunkStatus.LIGHT), chunkpos);
}
// from original method
if (optional.isPresent() && optional.get().getStatus().isOrAfter(requiredStatus)) {
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = requiredStatus.load(this.level, this.structureTemplateManager, this.lightEngine, (arg2) -> {
return this.protoChunkToFullChunk(holder);
}, (ChunkAccess)optional.get());
this.progressListener.onStatusChange(chunkpos, requiredStatus);
return completablefuture;
} else {
return this.scheduleChunkGeneration(holder, requiredStatus);
}
}, this.mainThreadExecutor).thenComposeAsync(CompletableFuture::completedFuture, this.mainThreadExecutor));
}
}
}

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

@ -5,7 +5,6 @@ import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.lighting.LevelLightEngine;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
@ -17,7 +16,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.concurrent.atomic.AtomicReferenceArray;
@Mixin(Minecraft.class)
@ClientOnlyMixin
public class MinecraftMixin {
@Shadow @Nullable public ClientLevel level;

View File

@ -3,7 +3,6 @@ package org.embeddedt.modernfix.common.mixin.bugfix.world_screen_skipped;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen;
import net.minecraft.client.gui.screens.worldselection.WorldSelectionList;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@ -12,7 +11,6 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(WorldSelectionList.WorldListEntry.class)
@ClientOnlyMixin
public class WorldSelectionListMixin {
@Shadow @Final private Minecraft minecraft;

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,17 @@
package org.embeddedt.modernfix.common.mixin.devenv;
import com.mojang.text2speech.Narrator;
import net.minecraft.client.GameNarrator;
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(GameNarrator.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 Narrator.EMPTY;
}
}

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

@ -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,20 @@
package org.embeddedt.modernfix.common.mixin.feature.measure_time;
import net.minecraft.server.packs.resources.ReloadableResourceManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
@Mixin(ReloadableResourceManager.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
*/
@ModifyArg(method = "createReload", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/packs/resources/SimpleReloadInstance;create(Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/List;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;Ljava/util/concurrent/CompletableFuture;Z)Lnet/minecraft/server/packs/resources/ReloadInstance;"), index = 5)
private boolean enableDebugReloader(boolean bl) {
return bl || ENABLE_DEBUG_RELOADER;
}
}

View File

@ -7,7 +7,7 @@ import net.minecraft.world.item.ItemStack;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
import org.embeddedt.modernfix.searchtree.RecipeBookSearchTree;
import org.embeddedt.modernfix.searchtree.DummySearchTree;
import org.embeddedt.modernfix.searchtree.SearchTreeProviderRegistry;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWErrorCallback;
@ -37,7 +37,7 @@ public abstract class MinecraftMixin {
SearchRegistry.TreeBuilderSupplier<ItemStack> tagSupplier = list -> provider.getSearchTree(true);
this.searchRegistry.register(SearchRegistry.CREATIVE_NAMES, nameSupplier);
this.searchRegistry.register(SearchRegistry.CREATIVE_TAGS, tagSupplier);
this.searchRegistry.register(SearchRegistry.RECIPE_COLLECTIONS, list -> new RecipeBookSearchTree(provider.getSearchTree(false), list));
this.searchRegistry.register(SearchRegistry.RECIPE_COLLECTIONS, list -> new DummySearchTree<>());
ModernFixPlatformHooks.INSTANCE.registerCreativeSearchTrees(this.searchRegistry, nameSupplier, tagSupplier, this::populateSearchTree);
// grab components for all key mappings in order to prevent them from being loaded off-thread later
// this populates the LazyLoadedValues

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,68 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
import net.minecraft.Util;
import net.minecraft.core.Holder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.duck.IChunkGenerator;
import org.embeddedt.modernfix.duck.IServerLevel;
import org.embeddedt.modernfix.world.StrongholdLocationCache;
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.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Mixin(ChunkGeneratorStructureState.class)
public class ChunkGeneratorMixin implements IChunkGenerator {
private WeakReference<ServerLevel> mfix$serverLevel;
@Override
public void mfix$setAssociatedServerLevel(ServerLevel level) {
mfix$serverLevel = new WeakReference<>(level);
}
@Inject(method = "generateRingPositions", at = @At("HEAD"), cancellable = true)
private void useCachedDataIfAvailable(Holder<StructureSet> structureSet, ConcentricRingsStructurePlacement placement, CallbackInfoReturnable<CompletableFuture<List<ChunkPos>>> cir) {
if(placement.count() == 0)
return;
ServerLevel level = searchLevel();
if(level == null)
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());
cir.setReturnValue(CompletableFuture.completedFuture(positions));
}
private ServerLevel searchLevel() {
if(mfix$serverLevel != null)
return mfix$serverLevel.get();
else
return null;
}
@Inject(method = "generateRingPositions", at = @At("RETURN"), cancellable = true)
private void saveCachedData(Holder<StructureSet> structureSet, ConcentricRingsStructurePlacement placement, CallbackInfoReturnable<CompletableFuture<List<ChunkPos>>> cir) {
cir.setReturnValue(cir.getReturnValue().thenApplyAsync(list -> {
if(list.size() == 0)
return list;
ServerLevel level = searchLevel();
if(level != null) {
StrongholdLocationCache cache = ((IServerLevel)level).mfix$getStrongholdCache();
cache.setChunkPosList(list);
ModernFix.LOGGER.debug("Saved stronghold cache for dimension {}", level.dimension().location());
}
return list;
}, Util.backgroundExecutor()));
}
}

View File

@ -0,0 +1,69 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerChunkCache;
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.ChunkGeneratorStructureState;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
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.IChunkGenerator;
import org.embeddedt.modernfix.duck.IServerLevel;
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.Redirect;
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, RegistryAccess arg3, Holder<DimensionType> arg4, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
super(arg, arg2, arg3, arg4, supplier, bl, bl2, l, i);
}
@Shadow public abstract DimensionDataStorage getDataStorage();
@Shadow @Final private ServerChunkCache chunkSource;
private StrongholdLocationCache mfix$strongholdCache;
/**
* Initialize the stronghold cache but don't force any structure generation yet.
*/
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/ChunkGeneratorStructureState;ensureStructuresGenerated()V"))
private void hookStrongholdCache(ChunkGeneratorStructureState generator) {
((IChunkGenerator)generator).mfix$setAssociatedServerLevel((ServerLevel)(Object)this);
}
/**
* Now start the stronghold generation process.
*/
@Inject(method = "<init>", at = @At("TAIL"))
private void ensureGeneration(MinecraftServer minecraftServer, Executor executor, LevelStorageSource.LevelStorageAccess arg, ServerLevelData arg2, ResourceKey<Level> arg3, LevelStem arg4, ChunkProgressListener arg5, boolean bl, long l, List<CustomSpawner> list, boolean bl2, CallbackInfo ci) {
mfix$strongholdCache = this.getDataStorage().computeIfAbsent(StrongholdLocationCache::load,
StrongholdLocationCache::new,
StrongholdLocationCache.getFileId(this.dimensionTypeRegistration()));
this.chunkSource.getGeneratorState().ensureStructuresGenerated();
}
@Override
public StrongholdLocationCache mfix$getStrongholdCache() {
return mfix$strongholdCache;
}
}

View File

@ -16,7 +16,6 @@ import org.spongepowered.asm.mixin.Shadow;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
@Mixin(StructureTemplateManager.class)
@ -34,8 +33,8 @@ public class StructureManagerMixin {
@Overwrite
private Optional<StructureTemplate> loadFromResource(ResourceLocation id) {
ResourceLocation arg = new ResourceLocation(id.getNamespace(), "structures/" + id.getPath() + ".nbt");
try(InputStream stream = this.resourceManager.open(arg)) {
return Optional.of(CachingStructureManager.readStructure(id, this.fixerUpper, stream, this.blockLookup));
try {
return Optional.of(CachingStructureManager.readStructure(id, this.fixerUpper, this.resourceManager.open(arg), this.blockLookup));
} catch(FileNotFoundException e) {
return Optional.empty();
} catch(IOException e) {

View File

@ -30,15 +30,8 @@ public abstract class PalettedContainerMixin<T> {
}
}
if(empty && storArray.length > 0) {
T value;
/* it means the chunk is oversized and wasting memory, take the ID out of the palette and recreate a smaller chunk */
try {
value = this.data.palette().valueFor(0);
} catch (RuntimeException e) {
// Some mods/servers seem to generate buggy palettes. This is not our fault (the game will likely crash later),
// but we catch it here to avoid receiving bug reports for an issue we didn't cause.
return;
}
T value = this.data.palette().valueFor(0);
this.data = this.createOrReuseData(null, 0);
this.data.palette().idFor(value);
}

View File

@ -0,0 +1,24 @@
package org.embeddedt.modernfix.common.mixin.perf.compact_mojang_registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import org.embeddedt.modernfix.annotation.IgnoreOutsideDev;
import org.embeddedt.modernfix.registry.DirectStorageRegistryObject;
import org.spongepowered.asm.mixin.Mixin;
@Mixin({ Block.class, Item.class })
@IgnoreOutsideDev
public class DirectObjectMixin implements DirectStorageRegistryObject {
private ResourceLocation mfix$resourceKey;
@Override
public ResourceLocation mfix$getResourceKey() {
return mfix$resourceKey;
}
@Override
public void mfix$setResourceKey(ResourceLocation key) {
mfix$resourceKey = key;
}
}

View File

@ -1,7 +1,9 @@
package org.embeddedt.modernfix.common.mixin.perf.compact_mojang_registries;
import com.google.common.collect.ImmutableSet;
import com.mojang.serialization.Lifecycle;
import net.minecraft.core.MappedRegistry;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.annotation.IgnoreOutsideDev;
import org.embeddedt.modernfix.registry.LifecycleMap;
import org.spongepowered.asm.mixin.Final;
@ -22,8 +24,17 @@ public abstract class MappedRegistryMixin<T> {
@Mutable
private Map<T, Lifecycle> lifecycles;
@Inject(method = "<init>", at = @At("RETURN"))
private static final ImmutableSet<ResourceLocation> MFIX$NEW_STORAGE_KEYS = ImmutableSet.of(new ResourceLocation("block"), new ResourceLocation("item"));
@Inject(method = "<init>(Lnet/minecraft/resources/ResourceKey;Lcom/mojang/serialization/Lifecycle;Z)V", at = @At("RETURN"))
private void replaceStorage(CallbackInfo ci) {
this.lifecycles = new LifecycleMap<>();
/*
if(MFIX$NEW_STORAGE_KEYS.contains(this.key().location())) {
ModernFixMixinPlugin.instance.logger.info("Using experimental registry storage for {}", this.key());
this.storage = (BiMap<ResourceLocation, T>) RegistryStorage.createStorage();
this.keyStorage = (BiMap<ResourceKey<T>, T>)RegistryStorage.createKeyStorage(this.key(), (BiMap<ResourceLocation, DirectStorageRegistryObject>)this.storage);
}
*/
}
}

View File

@ -0,0 +1,15 @@
package org.embeddedt.modernfix.common.mixin.perf.deduplicate_climate_parameters;
import net.minecraft.world.level.biome.Climate;
import org.embeddedt.modernfix.dedup.ClimateCache;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin({ Climate.Parameter.class, Climate.ParameterPoint.class })
public class ParameterMixin {
@Redirect(method = "*", at = @At(value = "NEW", target = "net/minecraft/world/level/biome/Climate$Parameter"), require = 0)
private static Climate.Parameter internParameterStatic(long min, long max) {
return ClimateCache.MFIX_INTERNER.intern(new Climate.Parameter(min, max));
}
}

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

@ -0,0 +1,26 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_block_codecs;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.level.block.state.properties.Property;
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.ModifyVariable;
import java.util.Map;
import java.util.function.Function;
@Mixin(StateDefinition.class)
public class StateDefinitionMixin<O, S extends StateHolder<O, S>> {
@Shadow @Final private O owner;
@ModifyVariable(method = "<init>", at = @At("HEAD"), ordinal = 0, argsOnly = true)
private static <O, S extends StateHolder<O, S>> StateDefinition.Factory<O, S> replaceMapCodec(StateDefinition.Factory<O, S> factory, Function<O, S> function, O object, StateDefinition.Factory<O, S> factory2, Map<String, Property<?>> map) {
if(object instanceof Block)
return (o, m, c) -> factory.create(o, m, null);
return factory;
}
}

View File

@ -0,0 +1,58 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_block_codecs;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.mojang.serialization.Codec;
import com.mojang.serialization.Decoder;
import com.mojang.serialization.Encoder;
import com.mojang.serialization.MapCodec;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.level.block.state.properties.Property;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import java.util.function.Supplier;
@Mixin(StateHolder.class)
public class StateHolderMixin {
private static final LoadingCache<Block, MapCodec<BlockState>> MODERNFIX_CODEC_CACHE = CacheBuilder.newBuilder()
.maximumSize(100000)
.build(new CacheLoader<Block, MapCodec<BlockState>>() {
@Override
public MapCodec<BlockState> load(Block block) throws Exception {
Supplier<BlockState> stateSupplier = block::defaultBlockState;
MapCodec<BlockState> mapCodec = MapCodec.of(Encoder.empty(), Decoder.unit(stateSupplier));
for(Property<?> property : block.getStateDefinition().getProperties()) {
mapCodec = StateDefinition.appendPropertyCodec(mapCodec, stateSupplier, property.getName(), property);
}
return mapCodec;
}
});
@Redirect(method = "codec", at = @At(value = "INVOKE", target = "Lcom/mojang/serialization/Codec;dispatch(Ljava/lang/String;Ljava/util/function/Function;Ljava/util/function/Function;)Lcom/mojang/serialization/Codec;", remap = false))
private static <O, S extends StateHolder<O, S>> Codec<S> obtainCodec(Codec<O> codec, String typeKey, Function<S, O> type, Function<O, ? extends Codec<S>> codecFn, Codec<O> codecMethodArg, Function<O, S> stateSupplier) {
return codec.dispatch(typeKey, type, block -> {
if(block instanceof Block) {
S state = stateSupplier.apply(block);
if(state.getValues().isEmpty())
return Codec.unit(state);
MapCodec<S> mapCodec;
try {
mapCodec = (MapCodec<S>)MODERNFIX_CODEC_CACHE.get((Block)block);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
return mapCodec.fieldOf("Properties").codec();
} else {
return codecFn.apply(block);
}
});
}
}

View File

@ -0,0 +1,38 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_entity_renderers;
import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.entity.EntityRendererMap;
import org.objectweb.asm.Opcodes;
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.util.Map;
@Mixin(EntityRenderDispatcher.class)
@ClientOnlyMixin
public class EntityRenderDispatcherMixin {
@Shadow private Map<EntityType<?>, EntityRenderer<?>> renderers;
private EntityRendererMap mfix$dynamicRenderers;
@Inject(method = "getRenderer", at = @At("RETURN"), cancellable = true)
private <T extends Entity> void checkNullness(T entity, CallbackInfoReturnable<EntityRenderer<? super T>> cir) {
// apparently some mods yeet the renderers map and cause issues
if(cir.getReturnValue() == null)
cir.setReturnValue((EntityRenderer<? super T>)mfix$dynamicRenderers.get(entity.getType()));
}
@Redirect(method = "onResourceManagerReload", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/renderer/entity/EntityRenderDispatcher;renderers:Ljava/util/Map;"))
private void setRendererField(EntityRenderDispatcher instance, Map<EntityType<?>, EntityRenderer<?>> incomingMap) {
this.renderers = incomingMap;
this.mfix$dynamicRenderers = (EntityRendererMap)incomingMap;
}
}

View File

@ -0,0 +1,29 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_entity_renderers;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.EntityRenderers;
import net.minecraft.world.entity.EntityType;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.entity.EntityRendererMap;
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.Map;
@Mixin(EntityRenderers.class)
@ClientOnlyMixin
public class EntityRenderersMixin {
@Shadow @Final private static Map<EntityType<?>, EntityRendererProvider<?>> PROVIDERS;
@Inject(method = "createEntityRenderers", at = @At("HEAD"), cancellable = true)
private static void createDynamicRendererLoader(EntityRendererProvider.Context context, CallbackInfoReturnable<Map<EntityType<?>, EntityRenderer<?>>> cir) {
cir.setReturnValue(new EntityRendererMap(PROVIDERS, context));
ModernFix.LOGGER.info("Dynamic entity renderer hook setup");
}
}

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

@ -4,11 +4,8 @@ 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,6 +15,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
@Mixin(BlockModelShaper.class)
@ClientOnlyMixin
public class BlockModelShaperMixin {
@ -30,25 +28,6 @@ public class BlockModelShaperMixin {
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);
}
}
}
}
private BakedModel cacheBlockModel(BlockState state) {
// Do all model system accesses in the unlocked path
ModelResourceLocation mrl = ModelLocationCache.get(state);
BakedModel model = mrl == null ? null : modelManager.getModel(mrl);
if (model == null) {
model = modelManager.getMissingModel();
}
return model;
}
/**
@ -57,18 +36,11 @@ public class BlockModelShaperMixin {
*/
@Overwrite
public BakedModel getBlockModel(BlockState state) {
if(state instanceof IModelHoldingBlockState modelHolder) {
BakedModel model = modelHolder.mfix$getModel();
if(model != null) {
return model;
}
model = this.cacheBlockModel(state);
modelHolder.mfix$setModel(model);
return model;
} else {
return this.cacheBlockModel(state);
ModelResourceLocation mrl = ModelLocationCache.get(state);
BakedModel model = mrl == null ? null : modelManager.getModel(mrl);
if (model == null) {
model = modelManager.getMissingModel();
}
return model;
}
}

View File

@ -7,8 +7,6 @@ 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;
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
import org.embeddedt.modernfix.util.DynamicInt2ObjectMap;
import org.spongepowered.asm.mixin.*;
@ -20,7 +18,6 @@ import java.util.HashMap;
import java.util.Map;
@Mixin(ItemModelShaper.class)
@ClientOnlyMixin
public abstract class ItemModelShaperMixin {
@Shadow public abstract ModelManager getModelManager();
@ -35,8 +32,6 @@ public abstract class ItemModelShaperMixin {
private static final ModelResourceLocation SENTINEL_VANILLA = new ModelResourceLocation(new ResourceLocation("modernfix", "sentinel"), "sentinel");
private final DynamicModelCache<Item> mfix$itemModelCache = new DynamicModelCache<>(k -> this.mfix$getModelForItem((Item)k), true);
@Inject(method = "<init>", at = @At("RETURN"))
private void replaceLocationMap(CallbackInfo ci) {
overrideLocationsVanilla = new HashMap<>();
@ -53,12 +48,6 @@ public abstract class ItemModelShaperMixin {
return map;
}
private BakedModel mfix$getModelForItem(Item item) {
ModelResourceLocation map = mfix$getLocation(item);
return map == null ? null : getModelManager().getModel(map);
}
/**
* @author embeddedt
* @reason Get the stored location for that item and meta, and get the model
@ -66,7 +55,8 @@ public abstract class ItemModelShaperMixin {
**/
@Overwrite
public BakedModel getItemModel(Item item) {
return this.mfix$itemModelCache.get(item);
ModelResourceLocation map = mfix$getLocation(item);
return map == null ? null : getModelManager().getModel(map);
}
/**
@ -85,7 +75,5 @@ public abstract class ItemModelShaperMixin {
* all models).
**/
@Overwrite
public void rebuildCache() {
this.mfix$itemModelCache.clear();
}
public void rebuildCache() {}
}

View File

@ -0,0 +1,21 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import net.minecraft.client.renderer.block.model.ItemOverrides;
import net.minecraft.client.resources.model.BakedModel;
import org.embeddedt.modernfix.dynamicresources.ItemOverrideBakedModel;
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(ItemOverrides.class)
public class ItemOverridesMixin {
@Inject(method = "resolve", at = @At("RETURN"), cancellable = true)
private void getRealModel(CallbackInfoReturnable<BakedModel> cir) {
BakedModel original = cir.getReturnValue();
if(original instanceof ItemOverrideBakedModel) {
ItemOverrideBakedModel override = (ItemOverrideBakedModel)original;
cir.setReturnValue(override.getRealModel());
}
}
}

View File

@ -0,0 +1,56 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources.diagonalfences;
import fuzs.diagonalfences.api.world.level.block.DiagonalBlock;
import fuzs.diagonalfences.client.model.MultipartAppender;
import fuzs.diagonalfences.mixin.client.accessor.ModelBakeryAccessor;
import net.minecraft.client.renderer.block.model.multipart.MultiPart;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.FenceBlock;
import net.minecraft.world.level.block.IronBarsBlock;
import org.apache.logging.log4j.util.BiConsumer;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.annotation.RequiresMod;
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
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;
@Mixin(MultipartAppender.class)
@RequiresMod("diagonalfences")
public abstract class MultipartAppenderMixin {
@Shadow(remap = false)
public static void appendDiagonalSelectors(BiConsumer<ResourceLocation, UnbakedModel> modelBakery, MultiPart multiPart, boolean rotateCenter) {
throw new AssertionError();
}
private static boolean handlerInjected = false;
@Inject(method = "onPrepareModelBaking", at = @At("HEAD"), cancellable = true)
private static void setupHelper(CallbackInfo ci) {
ci.cancel();
if(handlerInjected)
return;
handlerInjected = true;
ModernFixClient.CLIENT_INTEGRATIONS.add(new ModernFixClientIntegration() {
@Override
public UnbakedModel onUnbakedModelLoad(ResourceLocation location, UnbakedModel originalModel, ModelBakery bakery) {
if(originalModel instanceof MultiPart multipart) {
Block block = multipart.definition.getOwner();
if((block instanceof FenceBlock || block instanceof IronBarsBlock) && block instanceof DiagonalBlock diagonalBlock && diagonalBlock.hasProperties()) {
try {
appendDiagonalSelectors(((ModelBakeryAccessor)bakery)::diagonalfences$callCacheAndQueueDependencies, multipart, block instanceof IronBarsBlock);
} catch(RuntimeException e) {
e.printStackTrace();
}
}
}
return originalModel;
}
});
}
}

View File

@ -1,7 +1,6 @@
package org.embeddedt.modernfix.common.mixin.perf.faster_item_rendering;
import net.minecraft.client.renderer.GameRenderer;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.render.RenderState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@ -9,7 +8,6 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(GameRenderer.class)
@ClientOnlyMixin
public class GameRendererMixin {
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;renderLevel(FJLcom/mojang/blaze3d/vertex/PoseStack;)V", shift = At.Shift.BEFORE))
private void markRenderingLevel(CallbackInfo ci) {

View File

@ -1,6 +1,5 @@
package org.embeddedt.modernfix.common.mixin.perf.faster_item_rendering;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
@ -11,7 +10,6 @@ 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;
import org.embeddedt.modernfix.render.FastItemRenderType;
import org.embeddedt.modernfix.render.RenderState;
import org.embeddedt.modernfix.render.SimpleItemModelView;
@ -22,7 +20,6 @@ import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = ItemRenderer.class, priority = 600)
@ClientOnlyMixin
public abstract class ItemRendererMixin {
private ItemDisplayContext transformType;
private final SimpleItemModelView modelView = new SimpleItemModelView();
@ -41,13 +38,7 @@ public abstract class ItemRendererMixin {
* camera).
*/
@ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/ItemRenderer;renderModelLists(Lnet/minecraft/client/resources/model/BakedModel;Lnet/minecraft/world/item/ItemStack;IILcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;)V"), index = 0)
private BakedModel useSimpleWrappedItemModel(BakedModel model, ItemStack stack, int combinedLight, int combinedOverlay, PoseStack matrixStack, VertexConsumer buffer, @Local(ordinal = 0) BakedModel originalModel) {
// Forge composite models split themselves into a smaller simple model, we need to detect that the parent
// was not simple
if(originalModel != null && originalModel.getClass() != SimpleBakedModel.class) {
return model;
}
private BakedModel useSimpleWrappedItemModel(BakedModel model, ItemStack stack, int combinedLight, int combinedOverlay, PoseStack matrixStack, VertexConsumer buffer) {
if(!RenderState.IS_RENDERING_LEVEL && !stack.isEmpty() && model.getClass() == SimpleBakedModel.class && transformType == ItemDisplayContext.GUI) {
FastItemRenderType type;
ItemTransform transform = model.getTransforms().gui;

View File

@ -1,10 +1,8 @@
package org.embeddedt.modernfix.common.mixin.perf.faster_texture_stitching;
import com.google.common.collect.ImmutableList;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.client.renderer.texture.Stitcher;
import net.minecraft.client.renderer.texture.StitcherException;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
@ -18,7 +16,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;
@Mixin(Stitcher.class)
@ClientOnlyMixin
@ -29,9 +26,6 @@ public class StitcherMixin<T extends Stitcher.Entry> {
@Shadow private int storageY;
@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;
@ -41,16 +35,10 @@ public class StitcherMixin<T extends Stitcher.Entry> {
*/
@Inject(method = "stitch", at = @At("HEAD"), cancellable = true)
private void stitchFast(CallbackInfo ci) {
this.loadableSpriteInfos = null;
if(!ModernFixPlatformHooks.INSTANCE.isLoadingNormally()) {
ModernFix.LOGGER.error("Using vanilla stitcher implementation due to invalid loading state");
return;
}
if(this.texturesToBeStitched.size() < 100) {
// The vanilla implementation is fine for small atlases, and using it allows mods like JEI that depend on
// precise texture alignments to avoid bugs.
return;
}
ci.cancel();
ObjectArrayList<Stitcher.Holder<T>> holderList = new ObjectArrayList<>(this.texturesToBeStitched);
holderList.sort(HOLDER_COMPARATOR);
@ -59,13 +47,6 @@ public class StitcherMixin<T extends Stitcher.Entry> {
Pair<Pair<Integer, Integer>, List<StbStitcher.LoadableSpriteInfo<T>>> 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()));
}
this.loadableSpriteInfos = packingInfo.getSecond();
}
@ -75,7 +56,7 @@ public class StitcherMixin<T extends Stitcher.Entry> {
*/
@Inject(method = "gatherSprites", at = @At("HEAD"), cancellable = true)
private void gatherSpritesFast(Stitcher.SpriteLoader<T> spriteLoader, CallbackInfo ci) {
if(this.loadableSpriteInfos == null)
if(!ModernFixPlatformHooks.INSTANCE.isLoadingNormally())
return;
ci.cancel();
for(StbStitcher.LoadableSpriteInfo<T> info : loadableSpriteInfos) {

View File

@ -1,22 +1,25 @@
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
@ -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

@ -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);
}
}
}

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