Compare commits
No commits in common. "1.20" and "3.6.0" have entirely different histories.
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
|
@ -1 +0,0 @@
|
||||||
ko_fi: embeddedt
|
|
||||||
78
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
78
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -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
|
|
||||||
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -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
|
|
||||||
141
.github/workflows/gradle.yml
vendored
141
.github/workflows/gradle.yml
vendored
|
|
@ -1,136 +1,27 @@
|
||||||
name: Build ModernFix using Gradle
|
# This workflow will build a Java project with Gradle
|
||||||
|
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
|
||||||
|
|
||||||
on:
|
name: Build mod
|
||||||
push:
|
|
||||||
branches:
|
on: [push, pull_request]
|
||||||
- '**'
|
|
||||||
tags-ignore:
|
|
||||||
- '**'
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
concurrency:
|
|
||||||
group: release-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- uses: actions/checkout@v2
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up JDK 21
|
- uses: actions/setup-java@v3
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: 21
|
java-version: '17'
|
||||||
check-latest: true
|
cache: 'gradle'
|
||||||
- name: Check if release branch
|
- name: Grant execute permission for gradlew
|
||||||
id: check_branch
|
run: chmod +x gradlew
|
||||||
if: github.event_name == 'push'
|
- name: Build the mod
|
||||||
run: |
|
run: ./gradlew --no-daemon build
|
||||||
if [[ "${{ github.ref }}" =~ ^refs/heads/[0-9]+\. ]]; then
|
- uses: actions/upload-artifact@v2
|
||||||
echo "is_release=true" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "is_release=false" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
- name: Setup Gradle
|
|
||||||
uses: gradle/actions/setup-gradle@v4
|
|
||||||
with:
|
|
||||||
cache-read-only: ${{ steps.check_branch.outputs.is_release != 'true' }}
|
|
||||||
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
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const { execSync } = require('child_process');
|
|
||||||
|
|
||||||
const branch = context.ref.replace('refs/heads/', '');
|
|
||||||
const { data: runs } = await github.rest.actions.listWorkflowRuns({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
workflow_id: 'gradle.yml',
|
|
||||||
branch,
|
|
||||||
status: 'success',
|
|
||||||
per_page: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
const logArgs = runs.workflow_runs.length > 0
|
|
||||||
? `${runs.workflow_runs[0].head_sha}..${context.sha}`
|
|
||||||
: `-1 ${context.sha}`;
|
|
||||||
const log = execSync(`git log ${logArgs} --format=%s%n%b`, { encoding: 'utf8' });
|
|
||||||
|
|
||||||
const issueNumbers = new Set();
|
|
||||||
const pattern = /(?:fix(?:es|ed)?|close[sd]?|resolve[sd]?)\s+#(\d+)/gi;
|
|
||||||
let match;
|
|
||||||
while ((match = pattern.exec(log)) !== null) {
|
|
||||||
issueNumbers.add(parseInt(match[1]));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (issueNumbers.size === 0) {
|
|
||||||
console.log('No fixed issues found in commits');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MARKER = '<!-- modernfix-fix-tracker -->';
|
|
||||||
const modVersion = process.env.MOD_VERSION;
|
|
||||||
const mcVersion = process.env.MC_VERSION;
|
|
||||||
const newLine = `- ${modVersion} for Minecraft ${mcVersion}`;
|
|
||||||
|
|
||||||
for (const issueNumber of issueNumbers) {
|
|
||||||
try {
|
|
||||||
const { data: comments } = await github.rest.issues.listComments({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: issueNumber,
|
|
||||||
per_page: 100
|
|
||||||
});
|
|
||||||
|
|
||||||
const existing = comments.find(c => c.body.includes(MARKER));
|
|
||||||
if (existing) {
|
|
||||||
await github.rest.issues.updateComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
comment_id: existing.id,
|
|
||||||
body: existing.body + `\n${newLine}`
|
|
||||||
});
|
|
||||||
console.log(`Updated comment on issue #${issueNumber}`);
|
|
||||||
} else {
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: issueNumber,
|
|
||||||
body: `${MARKER}\nThe fix for this issue has been released in the following versions of ModernFix:\n${newLine}`
|
|
||||||
});
|
|
||||||
console.log(`Created comment on issue #${issueNumber}`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Could not comment on #${issueNumber}: ${e.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
- name: Upload Artifacts to GitHub
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
with:
|
||||||
name: Package
|
name: Package
|
||||||
path: bin
|
path: bin
|
||||||
27
.github/workflows/wiki_update.yml
vendored
27
.github/workflows/wiki_update.yml
vendored
|
|
@ -1,27 +0,0 @@
|
||||||
name: Update wiki using WikiGen
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- '1.**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
wikigen:
|
|
||||||
if: github.repository_owner == 'embeddedt'
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Generate Markdown Patch-List
|
|
||||||
run: python3 scripts/gen-markdown-patchlist.py
|
|
||||||
- name: Very legitimate hack for wiki push race condition
|
|
||||||
run: sleep $((1 + (RANDOM % 30)))
|
|
||||||
shell: bash
|
|
||||||
- name: Upload generated file to wiki
|
|
||||||
uses: SwiftDocOrg/github-wiki-publish-action@v1
|
|
||||||
with:
|
|
||||||
path: "doc/generated"
|
|
||||||
env:
|
|
||||||
GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.WIKI_TOKEN }}
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -2,12 +2,8 @@ eclipse
|
||||||
run
|
run
|
||||||
libs
|
libs
|
||||||
media
|
media
|
||||||
__pycache__
|
|
||||||
*.pyc
|
|
||||||
classes/
|
classes/
|
||||||
.architectury-transformer/
|
.architectury-transformer/
|
||||||
fabric/fabricloader.log
|
|
||||||
fabric/test_run
|
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
ModernFix is a standard Minecraft-style Gradle project powered by Architectury Loom. To build the mod for all platforms,
|
|
||||||
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.
|
|
||||||
|
|
||||||
## 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 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
|
|
||||||
quickly ported up to the latest one as part of the regular development cycle. You are still welcome to open PRs against
|
|
||||||
a newer branch if desired - but the change will likely be applied manually and not merged as a regular PR.
|
|
||||||
|
|
||||||
* Please ensure your code is reasonably neat and sufficiently documented. Remember that self-documenting code is always
|
|
||||||
better.
|
|
||||||
|
|
@ -6,11 +6,10 @@ 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.
|
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)
|
## Development builds (generally stable, but may occasionally have bugs)
|
||||||
- 1.16.5: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.16/Package.zip
|
- 1.16.5, both modloaders: https://nightly.link/embeddedt/ModernFix/workflows/gradle/main/Package.zip
|
||||||
- 1.18.2: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.18/Package.zip
|
- 1.18.2, both modloaders: 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.19.2, both modloaders: 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.19.4, both modloaders: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.19.4/Package.zip
|
||||||
- 1.20.2: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.20.2/Package.zip
|
|
||||||
|
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'
|
|
||||||
|
|
@ -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("\\/", ".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
org.fury_phoenix.mixinAp.annotation.MixinProcessor,aggregating
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
plugins {
|
|
||||||
id("java")
|
|
||||||
}
|
|
||||||
|
|
||||||
version = "1.1.0"
|
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package org.embeddedt.modernfix.annotation;
|
|
||||||
|
|
||||||
public enum FeatureLevel {
|
|
||||||
GA, BETA;
|
|
||||||
|
|
||||||
public boolean isAtLeast(FeatureLevel required) {
|
|
||||||
return this.ordinal() >= required.ordinal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,15 +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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The config system will ignore mixins with this annotation when generating config options unless running
|
|
||||||
* in a dev environment.
|
|
||||||
*/
|
|
||||||
@Retention(RetentionPolicy.CLASS)
|
|
||||||
@Target(ElementType.TYPE)
|
|
||||||
public @interface IgnoreOutsideDev {
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
167
build.gradle
Normal file
167
build.gradle
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
plugins {
|
||||||
|
id "architectury-plugin" version "3.4-SNAPSHOT"
|
||||||
|
id "dev.architectury.loom" version "1.1-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 '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
|
||||||
|
}
|
||||||
|
|
||||||
|
architectury {
|
||||||
|
minecraft = rootProject.minecraft_version
|
||||||
|
}
|
||||||
|
|
||||||
|
ext.archives_base_name = 'modernfix-mc' + minecraft_version
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
apply plugin: "java"
|
||||||
|
apply plugin: "architectury-plugin"
|
||||||
|
apply plugin: "maven-publish"
|
||||||
|
apply plugin: "com.diffplug.spotless"
|
||||||
|
|
||||||
|
spotless {
|
||||||
|
java {
|
||||||
|
removeUnusedImports()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'org.embeddedt'
|
||||||
|
version = gitVersion()
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
apply plugin: "dev.architectury.loom"
|
||||||
|
|
||||||
|
loom {
|
||||||
|
silentMojangMappingsLicense()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
|
||||||
|
mappings loom.layered() {
|
||||||
|
officialMojangMappings()
|
||||||
|
parchment("org.parchmentmc.data:parchment-${minecraft_version}:${parchment_version}@zip")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile) {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
task generateChangelog(type: se.bjurr.gitchangelog.plugin.gradle.GitChangelogTask) {
|
||||||
|
def details = versionDetails();
|
||||||
|
if(details.commitDistance > 0) {
|
||||||
|
fromRef = details.lastTag;
|
||||||
|
} else {
|
||||||
|
def secondLastTagCmd = "git describe --abbrev=0 " + details.lastTag + "^"
|
||||||
|
def secondLastTag = secondLastTagCmd.execute().text.trim()
|
||||||
|
fromRef = secondLastTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
file = new File("CHANGELOG.md");
|
||||||
|
def otherTemplateContent = new File('gradle/changelog.mustache').getText('UTF-8');
|
||||||
|
templateContent = "## Changes since " + fromRef + "\n" + otherTemplateContent;
|
||||||
|
toCommit = "HEAD";
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('checkCleanTag') {
|
||||||
|
doLast {
|
||||||
|
def details = versionDetails()
|
||||||
|
if (!details.isCleanTag || versionDetails().commitDistance != 0) {
|
||||||
|
throw new GradleException('Not a clean tree.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
210
build.gradle.kts
210
build.gradle.kts
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
plugins {
|
|
||||||
`kotlin-dsl`
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
46
common/build.gradle
Normal file
46
common/build.gradle
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
architectury {
|
||||||
|
common(rootProject.enabled_platforms.split(","))
|
||||||
|
}
|
||||||
|
|
||||||
|
loom {
|
||||||
|
accessWidenerPath = file("src/main/resources/modernfix.accesswidener")
|
||||||
|
}
|
||||||
|
|
||||||
|
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}"
|
||||||
|
|
||||||
|
modApi("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
|
||||||
|
}
|
||||||
|
// 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}"
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
}
|
||||||
21
common/src/main/java/org/embeddedt/modernfix/FileWalker.java
Normal file
21
common/src/main/java/org/embeddedt/modernfix/FileWalker.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
package org.embeddedt.modernfix;
|
package org.embeddedt.modernfix;
|
||||||
|
|
||||||
import net.minecraft.SharedConstants;
|
|
||||||
import net.minecraft.Util;
|
import net.minecraft.Util;
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.level.ChunkMap;
|
import net.minecraft.server.level.ChunkMap;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
|
@ -13,7 +11,6 @@ import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
|
||||||
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||||
import org.embeddedt.modernfix.resources.ReloadExecutor;
|
import org.embeddedt.modernfix.resources.ReloadExecutor;
|
||||||
import org.embeddedt.modernfix.util.ClassInfoManager;
|
import org.embeddedt.modernfix.util.ClassInfoManager;
|
||||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
|
||||||
|
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
@ -26,8 +23,6 @@ public class ModernFix {
|
||||||
|
|
||||||
public static final String MODID = "modernfix";
|
public static final String MODID = "modernfix";
|
||||||
|
|
||||||
public static String NAME = "ModernFix";
|
|
||||||
|
|
||||||
public static ModernFix INSTANCE;
|
public static ModernFix INSTANCE;
|
||||||
|
|
||||||
// Used to skip computing the blockstate caches twice
|
// Used to skip computing the blockstate caches twice
|
||||||
|
|
@ -47,48 +42,32 @@ public class ModernFix {
|
||||||
return resourceReloadService;
|
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() {
|
public ModernFix() {
|
||||||
INSTANCE = this;
|
INSTANCE = this;
|
||||||
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.snapshot_easter_egg.NameChange") && !SharedConstants.getCurrentVersion().isStable())
|
ModernFixPlatformHooks.onServerCommandRegister(ModernFixCommands::register);
|
||||||
NAME = "PreemptiveFix";
|
|
||||||
ModernFixPlatformHooks.INSTANCE.onServerCommandRegister(ModernFixCommands::register);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onServerStarted() {
|
public void onServerStarted() {
|
||||||
if(ModernFixPlatformHooks.INSTANCE.isDedicatedServer()) {
|
if(ModernFixPlatformHooks.isDedicatedServer()) {
|
||||||
float gameStartTime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
|
float gameStartTime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
|
||||||
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.measure_time.ServerLoad"))
|
ModernFix.LOGGER.warn("Dedicated server took " + gameStartTime + " seconds to load");
|
||||||
ModernFix.LOGGER.warn("Dedicated server took " + gameStartTime + " seconds to load");
|
|
||||||
ModernFixPlatformHooks.INSTANCE.onLaunchComplete();
|
|
||||||
}
|
}
|
||||||
ClassInfoManager.clear();
|
ClassInfoManager.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ConstantValue")
|
public void onLoadComplete() {
|
||||||
|
ClassInfoManager.clear();
|
||||||
|
}
|
||||||
|
|
||||||
public void onServerDead(MinecraftServer server) {
|
public void onServerDead(MinecraftServer server) {
|
||||||
/* Clear as much data from the integrated server as possible, in case a mod holds on to it */
|
/* Clear as much data from the integrated server as possible, in case a mod holds on to it */
|
||||||
try {
|
try {
|
||||||
for(ServerLevel level : server.getAllLevels()) {
|
for(ServerLevel level : server.getAllLevels()) {
|
||||||
ChunkMap chunkMap = level.getChunkSource().chunkMap;
|
ChunkMap chunkMap = level.getChunkSource().chunkMap;
|
||||||
// Null check for mods that replace chunk system
|
chunkMap.updatingChunkMap.clear();
|
||||||
if(chunkMap.updatingChunkMap != null)
|
chunkMap.visibleChunkMap.clear();
|
||||||
chunkMap.updatingChunkMap.clear();
|
chunkMap.pendingUnloads.clear();
|
||||||
if(chunkMap.visibleChunkMap != null)
|
|
||||||
chunkMap.visibleChunkMap.clear();
|
|
||||||
if(chunkMap.pendingUnloads != null)
|
|
||||||
chunkMap.pendingUnloads.clear();
|
|
||||||
}
|
}
|
||||||
} catch(RuntimeException e) {
|
} catch(RuntimeException e) {
|
||||||
ModernFix.LOGGER.error("Couldn't clear chunk data", e);
|
ModernFix.LOGGER.error("Couldn't clear chunk data", e);
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
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.client.gui.screens.ConnectScreen;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.client.gui.screens.TitleScreen;
|
||||||
|
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.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.world.IntegratedWatchdog;
|
||||||
|
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ModernFixClient {
|
||||||
|
public static long worldLoadStartTime;
|
||||||
|
private static int numRenderTicks;
|
||||||
|
|
||||||
|
public static float gameStartTimeSeconds = -1;
|
||||||
|
|
||||||
|
private static boolean recipesUpdated, tagsUpdated = false;
|
||||||
|
|
||||||
|
public String brandingString = null;
|
||||||
|
|
||||||
|
public ModernFixClient() {
|
||||||
|
// clear reserve as it's not needed
|
||||||
|
MemoryReserve.release();
|
||||||
|
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.branding.F3Screen")) {
|
||||||
|
brandingString = "ModernFix " + ModernFixPlatformHooks.getVersionString();
|
||||||
|
}
|
||||||
|
SearchTreeProviderRegistry.register(JEIBackedSearchTree.PROVIDER);
|
||||||
|
SearchTreeProviderRegistry.register(REIBackedSearchTree.PROVIDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetWorldLoadStateMachine() {
|
||||||
|
numRenderTicks = 0;
|
||||||
|
worldLoadStartTime = -1;
|
||||||
|
recipesUpdated = false;
|
||||||
|
tagsUpdated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onScreenOpening(Screen openingScreen) {
|
||||||
|
if(openingScreen instanceof ConnectScreen) {
|
||||||
|
worldLoadStartTime = System.nanoTime();
|
||||||
|
} else if (openingScreen instanceof TitleScreen && gameStartTimeSeconds < 0) {
|
||||||
|
gameStartTimeSeconds = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
|
||||||
|
ModernFix.LOGGER.warn("Game took " + gameStartTimeSeconds + " seconds to start");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
@Retention(RetentionPolicy.CLASS)
|
@Retention(RetentionPolicy.CLASS)
|
||||||
@Target({ElementType.TYPE, ElementType.PACKAGE})
|
@Target(ElementType.TYPE)
|
||||||
public @interface RequiresMod {
|
public @interface RequiresMod {
|
||||||
String value() default "";
|
String value() default "";
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||||
import org.embeddedt.modernfix.duck.IBlockState;
|
import org.embeddedt.modernfix.duck.IBlockState;
|
||||||
|
|
||||||
public class BlockStateCacheHandler {
|
public class BlockStateCacheHandler {
|
||||||
public static void invalidateCache() {
|
public static void rebuildParallel(boolean force) {
|
||||||
synchronized (BlockBehaviour.BlockStateBase.class) {
|
synchronized (BlockBehaviour.BlockStateBase.class) {
|
||||||
for (BlockState blockState : Block.BLOCK_STATE_REGISTRY) {
|
for (BlockState blockState : Block.BLOCK_STATE_REGISTRY) {
|
||||||
((IBlockState)blockState).clearCache();
|
((IBlockState)blockState).clearCache();
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
package org.embeddedt.modernfix.blockstate;
|
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 net.minecraft.world.level.block.state.properties.Property;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
@ -16,7 +14,6 @@ import java.util.*;
|
||||||
*/
|
*/
|
||||||
public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S> {
|
public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S> {
|
||||||
private final Map<Property<?>, Comparable<?>>[] keys;
|
private final Map<Property<?>, Comparable<?>>[] keys;
|
||||||
private Map<Map<Property<?>, Comparable<?>>, S> fastLookup;
|
|
||||||
private final Object[] values;
|
private final Object[] values;
|
||||||
private int usedSlots;
|
private int usedSlots;
|
||||||
public FakeStateMap(int numStates) {
|
public FakeStateMap(int numStates) {
|
||||||
|
|
@ -37,39 +34,22 @@ public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsKey(Object o) {
|
public boolean containsKey(Object o) {
|
||||||
return getFastLookup().containsKey(o);
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsValue(Object o) {
|
public boolean containsValue(Object o) {
|
||||||
return getFastLookup().containsValue(o);
|
throw new UnsupportedOperationException();
|
||||||
}
|
|
||||||
|
|
||||||
@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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public S get(Object o) {
|
public S get(Object o) {
|
||||||
return getFastLookup().get(o);
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public S put(Map<Property<?>, Comparable<?>> propertyComparableMap, S s) {
|
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;
|
keys[usedSlots] = propertyComparableMap;
|
||||||
values[usedSlots] = s;
|
values[usedSlots] = s;
|
||||||
usedSlots++;
|
usedSlots++;
|
||||||
|
|
@ -90,58 +70,49 @@ public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
for(int i = 0; i < usedSlots; i++) {
|
for(int i = 0; i < this.keys.length; i++) {
|
||||||
this.keys[i] = null;
|
this.keys[i] = null;
|
||||||
this.values[i] = null;
|
this.values[i] = null;
|
||||||
}
|
}
|
||||||
this.usedSlots = 0;
|
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
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Set<Map<Property<?>, Comparable<?>>> keySet() {
|
public Set<Map<Property<?>, Comparable<?>>> keySet() {
|
||||||
return new AbstractSet<>() {
|
throw new UnsupportedOperationException();
|
||||||
@Override
|
|
||||||
public Iterator<Map<Property<?>, Comparable<?>>> iterator() {
|
|
||||||
return keys.length == usedSlots ? Iterators.forArray(keys) : asList(keys).iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return usedSlots;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Collection<S> values() {
|
public Collection<S> values() {
|
||||||
return (Collection<S>)asList(values);
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Set<Entry<Map<Property<?>, Comparable<?>>, S>> entrySet() {
|
public Set<Entry<Map<Property<?>, Comparable<?>>, S>> entrySet() {
|
||||||
return new AbstractSet<>() {
|
return new Set<Entry<Map<Property<?>, Comparable<?>>, S>>() {
|
||||||
@Override
|
@Override
|
||||||
public int size() {
|
public int size() {
|
||||||
return usedSlots;
|
return usedSlots;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return FakeStateMap.this.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Entry<Map<Property<?>, Comparable<?>>, S>> iterator() {
|
public Iterator<Entry<Map<Property<?>, Comparable<?>>, S>> iterator() {
|
||||||
return new Iterator<>() {
|
return new Iterator<Entry<Map<Property<?>, Comparable<?>>, S>>() {
|
||||||
int currentIdx = 0;
|
int currentIdx = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasNext() {
|
public boolean hasNext() {
|
||||||
return currentIdx < usedSlots;
|
return currentIdx < usedSlots;
|
||||||
|
|
@ -149,14 +120,61 @@ public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Entry<Map<Property<?>, Comparable<?>>, S> next() {
|
public Entry<Map<Property<?>, Comparable<?>>, S> next() {
|
||||||
if (currentIdx >= usedSlots)
|
if(currentIdx >= usedSlots)
|
||||||
throw new IndexOutOfBoundsException();
|
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++;
|
currentIdx++;
|
||||||
return entry;
|
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();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,7 +8,6 @@ import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.packs.resources.Resource;
|
import net.minecraft.server.packs.resources.Resource;
|
||||||
import net.minecraft.server.packs.resources.ResourceManager;
|
import net.minecraft.server.packs.resources.ResourceManager;
|
||||||
import org.embeddedt.modernfix.ModernFix;
|
import org.embeddedt.modernfix.ModernFix;
|
||||||
import org.embeddedt.modernfix.duck.IProfilingServerFunctionManager;
|
|
||||||
import org.embeddedt.modernfix.structure.CachingStructureManager;
|
import org.embeddedt.modernfix.structure.CachingStructureManager;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
@ -43,37 +42,17 @@ public class ModernFixCommands {
|
||||||
ResourceLocation structureLocation = new ResourceLocation(found.getNamespace(), matcher.group(1));
|
ResourceLocation structureLocation = new ResourceLocation(found.getNamespace(), matcher.group(1));
|
||||||
try(InputStream resource = entry.getValue().open()) {
|
try(InputStream resource = entry.getValue().open()) {
|
||||||
CachingStructureManager.readStructureTag(structureLocation, level.getServer().getFixerUpper(), resource);
|
CachingStructureManager.readStructureTag(structureLocation, level.getServer().getFixerUpper(), resource);
|
||||||
Component msg = Component.literal("checked " + structureLocation + " (" + upgradedNum + "/" + structures.size() + ")");
|
context.getSource().sendSuccess(Component.literal("checked " + structureLocation + " (" + upgradedNum + "/" + structures.size() + ")"), false);
|
||||||
context.getSource().sendSuccess(() -> msg, false);
|
|
||||||
} catch(Throwable e) {
|
} catch(Throwable e) {
|
||||||
ModernFix.LOGGER.error("Couldn't upgrade structure " + found, e);
|
ModernFix.LOGGER.error("Couldn't upgrade structure " + found, e);
|
||||||
context.getSource().sendFailure(Component.literal("error reading " + structureLocation + " (" + upgradedNum + "/" + structures.size() + ")"));
|
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;
|
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;
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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.Registry;
|
||||||
|
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(Registry.BIOME_REGISTRY).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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.bugfix.edge_chunk_not_saved;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||||
|
import net.minecraft.world.level.chunk.ImposterProtoChunk;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
|
import net.minecraft.server.level.ChunkHolder;
|
||||||
|
import net.minecraft.server.level.ChunkMap;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/* https://github.com/SuperCoder7979/chunksavingfix-fabric/blob/main/src/main/java/supercoder79/chunksavingfix/mixin/MixinThreadedAnvilChunkStorage.java */
|
||||||
|
@Mixin(ChunkMap.class)
|
||||||
|
public class ChunkManagerMixin {
|
||||||
|
// TODO: hits both at the moment- check and re-evaluate
|
||||||
|
@ModifyArg(method = "saveAllChunks(Z)V", at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;filter(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;", ordinal = 0), require = 0)
|
||||||
|
private Predicate<ChunkHolder> alwaysAccessibleFlush(Predicate<ChunkHolder> chunkHolder) {
|
||||||
|
return c -> true;
|
||||||
|
}
|
||||||
|
@ModifyArg(method = "saveAllChunks(Z)V", at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;filter(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;", ordinal = 1), require = 0)
|
||||||
|
private Predicate<ChunkAccess> allowProtoChunkFlush(Predicate<ChunkAccess> chunk) {
|
||||||
|
return c -> c instanceof ProtoChunk || c instanceof ImposterProtoChunk || c instanceof LevelChunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "saveChunkIfNeeded", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkHolder;wasAccessibleSinceLastSave()Z"), require = 0)
|
||||||
|
private boolean alwaysAccessible(ChunkHolder chunkHolder) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.bugfix.paper_chunk_patches;
|
||||||
|
|
||||||
|
import com.mojang.datafixers.util.Either;
|
||||||
|
import net.minecraft.server.level.ChunkHolder;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||||
|
import org.embeddedt.modernfix.duck.IPaperChunkHolder;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
@Mixin(ChunkHolder.class)
|
||||||
|
public abstract class ChunkHolderMixin implements IPaperChunkHolder {
|
||||||
|
|
||||||
|
@Shadow public abstract CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresentUnchecked(ChunkStatus arg);
|
||||||
|
|
||||||
|
@Shadow @Final private static List<ChunkStatus> CHUNK_STATUSES;
|
||||||
|
|
||||||
|
public ChunkStatus mfix$getChunkHolderStatus() {
|
||||||
|
for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) {
|
||||||
|
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = this.getFutureIfPresentUnchecked(curr);
|
||||||
|
Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = future.getNow(null);
|
||||||
|
if (either == null || !either.left().isPresent()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return curr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkAccess mfix$getAvailableChunkNow() {
|
||||||
|
// TODO can we just getStatusFuture(EMPTY)?
|
||||||
|
for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) {
|
||||||
|
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = this.getFutureIfPresentUnchecked(curr);
|
||||||
|
Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = future.getNow(null);
|
||||||
|
if (either == null || !either.left().isPresent()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return either.left().get();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ChunkStatus mfix$getNextStatus(ChunkStatus status) {
|
||||||
|
if (status == ChunkStatus.FULL) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
return CHUNK_STATUSES.get(status.getIndex() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mfix$canAdvanceStatus() {
|
||||||
|
ChunkStatus status = mfix$getChunkHolderStatus();
|
||||||
|
ChunkAccess chunk = mfix$getAvailableChunkNow();
|
||||||
|
return chunk != null && (status == null || chunk.getStatus().isOrAfter(mfix$getNextStatus(status)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
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.embeddedt.modernfix.duck.IPaperChunkHolder;
|
||||||
|
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
|
||||||
|
@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;
|
||||||
|
private Executor mainInvokingExecutor;
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
|
private void setup(CallbackInfo ci) {
|
||||||
|
this.mainInvokingExecutor = (runnable) -> {
|
||||||
|
if(ModernFixPlatformHooks.getCurrentServer().isSameThread())
|
||||||
|
runnable.run();
|
||||||
|
else
|
||||||
|
this.mainThreadExecutor.execute(runnable);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* https://github.com/PaperMC/Paper/blob/ver/1.17.1/patches/server/0752-Fix-chunks-refusing-to-unload-at-low-TPS.patch */
|
||||||
|
@ModifyArg(method = "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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://github.com/PaperMC/Paper/blob/master/patches/removed/1.19.2-legacy-chunksystem/0482-Improve-Chunk-Status-Transition-Speed.patch */
|
||||||
|
@ModifyArg(method = "prepareEntityTickingChunk", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 1)
|
||||||
|
private Executor useMainInvokingExecutor(Executor executor) {
|
||||||
|
return this.mainInvokingExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
|
@Redirect(method = "scheduleChunkGeneration", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenComposeAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
|
||||||
|
private CompletableFuture skipWorkerIfPossible(CompletableFuture inputFuture, Function function, Executor executor, ChunkHolder holder) {
|
||||||
|
Executor targetExecutor = (runnable) -> {
|
||||||
|
if(((IPaperChunkHolder)holder).mfix$canAdvanceStatus()) {
|
||||||
|
this.mainInvokingExecutor.execute(runnable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
executor.execute(runnable);
|
||||||
|
};
|
||||||
|
return inputFuture.thenComposeAsync(function, targetExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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(!optional.isPresent())
|
||||||
|
return CompletableFuture.completedFuture(either);
|
||||||
|
|
||||||
|
if (requiredStatus == ChunkStatus.LIGHT) {
|
||||||
|
this.distanceManager.addTicket(TicketType.LIGHT, chunkpos, 33 + ChunkStatus.getDistance(ChunkStatus.LIGHT), chunkpos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// from original method
|
||||||
|
if (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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package org.embeddedt.modernfix.common.mixin.bugfix.paper_chunk_patches;
|
package org.embeddedt.modernfix.common.mixin.bugfix.paper_chunk_patches;
|
||||||
|
|
||||||
import net.minecraft.util.SortedArraySet;
|
import net.minecraft.util.SortedArraySet;
|
||||||
import org.embeddedt.modernfix.annotation.RequiresMod;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
|
@ -10,7 +9,6 @@ import java.util.Arrays;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
@Mixin(SortedArraySet.class)
|
@Mixin(SortedArraySet.class)
|
||||||
@RequiresMod("!moonrise")
|
|
||||||
public abstract class SortedArraySetMixin<T> extends AbstractSet<T> {
|
public abstract class SortedArraySetMixin<T> extends AbstractSet<T> {
|
||||||
@Shadow private int size;
|
@Shadow private int size;
|
||||||
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,10 +11,6 @@ import org.spongepowered.asm.mixin.Overwrite;
|
||||||
@Mixin(Minecraft.class)
|
@Mixin(Minecraft.class)
|
||||||
@ClientOnlyMixin
|
@ClientOnlyMixin
|
||||||
public class MinecraftMixin {
|
public class MinecraftMixin {
|
||||||
/**
|
|
||||||
* @author embeddedt
|
|
||||||
* @reason avoid exception stacktrace being printed in dev
|
|
||||||
*/
|
|
||||||
@Overwrite
|
@Overwrite
|
||||||
private UserApiService createUserApiService(YggdrasilAuthenticationService yggdrasilAuthenticationService, GameConfig arg) {
|
private UserApiService createUserApiService(YggdrasilAuthenticationService yggdrasilAuthenticationService, GameConfig arg) {
|
||||||
return UserApiService.OFFLINE;
|
return UserApiService.OFFLINE;
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.feature.measure_time;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
|
||||||
|
|
||||||
|
@Mixin(Minecraft.class)
|
||||||
|
@ClientOnlyMixin
|
||||||
|
public class MinecraftMixin {
|
||||||
|
/* not supported in 1.19
|
||||||
|
private long datapackReloadStartTime;
|
||||||
|
|
||||||
|
@Inject(method = "makeWorldStem(Lnet/minecraft/server/packs/repository/PackRepository;ZLnet/minecraft/server/WorldStem$DataPackConfigSupplier;Lnet/minecraft/server/WorldStem$WorldDataSupplier;)Lnet/minecraft/server/WorldStem;", at = @At(value = "HEAD"))
|
||||||
|
private void recordReloadStart(CallbackInfoReturnable<WorldStem> cir) {
|
||||||
|
datapackReloadStartTime = System.nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "makeWorldStem(Lnet/minecraft/server/packs/repository/PackRepository;ZLnet/minecraft/server/WorldStem$DataPackConfigSupplier;Lnet/minecraft/server/WorldStem$WorldDataSupplier;)Lnet/minecraft/server/WorldStem;", at = @At(value = "RETURN"))
|
||||||
|
private void recordReloadEnd(CallbackInfoReturnable<WorldStem> cir) {
|
||||||
|
float timeSpentReloading = ((float)(System.nanoTime() - datapackReloadStartTime) / 1000000000f);
|
||||||
|
ModernFix.LOGGER.warn("Datapack reload took " + timeSpentReloading + " seconds.");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.blast_search_trees;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.searchtree.SearchRegistry;
|
||||||
|
import org.embeddedt.modernfix.ModernFix;
|
||||||
|
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||||
|
import org.embeddedt.modernfix.searchtree.DummySearchTree;
|
||||||
|
import org.embeddedt.modernfix.searchtree.SearchTreeProviderRegistry;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(Minecraft.class)
|
||||||
|
@ClientOnlyMixin
|
||||||
|
public class MinecraftMixin {
|
||||||
|
@Shadow @Final private SearchRegistry searchRegistry;
|
||||||
|
|
||||||
|
@Inject(method = "createSearchTrees", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void replaceSearchTrees(CallbackInfo ci) {
|
||||||
|
SearchTreeProviderRegistry.Provider provider = SearchTreeProviderRegistry.getSearchTreeProvider();
|
||||||
|
if(provider == null)
|
||||||
|
return;
|
||||||
|
ModernFix.LOGGER.info("Replacing search trees with '{}' provider", provider.getName());
|
||||||
|
this.searchRegistry.register(SearchRegistry.CREATIVE_NAMES, list -> provider.getSearchTree(false));
|
||||||
|
this.searchRegistry.register(SearchRegistry.CREATIVE_TAGS, list -> provider.getSearchTree(true));
|
||||||
|
this.searchRegistry.register(SearchRegistry.RECIPE_COLLECTIONS, list -> new DummySearchTree<>());
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.cache_model_materials;
|
||||||
|
|
||||||
|
import com.mojang.datafixers.util.Either;
|
||||||
|
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||||
|
import net.minecraft.client.resources.model.Material;
|
||||||
|
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||||
|
import org.embeddedt.modernfix.duck.ICachedMaterialsModel;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Mutable;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Mixin(BlockModel.class)
|
||||||
|
@ClientOnlyMixin
|
||||||
|
public class BlockModelMixin {
|
||||||
|
@Shadow @Final @Mutable public Map<String, Either<Material, String>> textureMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author embeddedt
|
||||||
|
* @reason detect changes to the texture map, and clear the material cache as needed
|
||||||
|
*/
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
|
private void useTrackingTextureMap(CallbackInfo ci) {
|
||||||
|
Map<String, Either<Material, String>> backingMap = this.textureMap;
|
||||||
|
ICachedMaterialsModel cacheHolder = (ICachedMaterialsModel)this;
|
||||||
|
this.textureMap = new Map<String, Either<Material, String>>() {
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return backingMap.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return backingMap.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(Object o) {
|
||||||
|
return backingMap.containsKey(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(Object o) {
|
||||||
|
return backingMap.containsValue(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Either<Material, String> get(Object o) {
|
||||||
|
return backingMap.get(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Either<Material, String> put(String s, Either<Material, String> materialStringEither) {
|
||||||
|
Either<Material, String> old = backingMap.put(s, materialStringEither);
|
||||||
|
cacheHolder.clearMaterialsCache();
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Either<Material, String> remove(Object o) {
|
||||||
|
Either<Material, String> e = backingMap.remove(o);
|
||||||
|
cacheHolder.clearMaterialsCache();
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putAll(@NotNull Map<? extends String, ? extends Either<Material, String>> map) {
|
||||||
|
backingMap.putAll(map);
|
||||||
|
cacheHolder.clearMaterialsCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
backingMap.clear();
|
||||||
|
cacheHolder.clearMaterialsCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Set<String> keySet() {
|
||||||
|
cacheHolder.clearMaterialsCache();
|
||||||
|
return backingMap.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Collection<Either<Material, String>> values() {
|
||||||
|
cacheHolder.clearMaterialsCache();
|
||||||
|
return backingMap.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Set<Entry<String, Either<Material, String>>> entrySet() {
|
||||||
|
cacheHolder.clearMaterialsCache();
|
||||||
|
return backingMap.entrySet();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.cache_model_materials;
|
||||||
|
|
||||||
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||||
|
import net.minecraft.client.resources.model.UnbakedModel;
|
||||||
|
import net.minecraft.client.resources.model.Material;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||||
|
import org.embeddedt.modernfix.duck.ICachedMaterialsModel;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/* only cache BlockModel to prevent issues with CTM on Fabric */
|
||||||
|
@Mixin(value = {BlockModel.class})
|
||||||
|
@ClientOnlyMixin
|
||||||
|
public class VanillaModelMixin implements ICachedMaterialsModel {
|
||||||
|
private Collection<Material> materialsCache = null;
|
||||||
|
|
||||||
|
@Inject(method = "getMaterials", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void useCachedMaterials(Function<ResourceLocation, UnbakedModel> pModelGetter, Set<Pair<String, String>> pMissingTextureErrors, CallbackInfoReturnable<Collection<Material>> cir) {
|
||||||
|
if(materialsCache != null) {
|
||||||
|
cir.setReturnValue(materialsCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "getMaterials", at = @At("RETURN"))
|
||||||
|
private void storeCachedMaterials(Function<ResourceLocation, UnbakedModel> pModelGetter, Set<Pair<String, String>> pMissingTextureErrors, CallbackInfoReturnable<Collection<Material>> cir) {
|
||||||
|
if(materialsCache == null)
|
||||||
|
materialsCache = Collections.unmodifiableCollection(cir.getReturnValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearMaterialsCache() {
|
||||||
|
materialsCache = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
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.ChunkGenerator;
|
||||||
|
import net.minecraft.world.level.levelgen.RandomState;
|
||||||
|
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(ChunkGenerator.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, RandomState random, 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, RandomState random, 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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
|
||||||
|
|
||||||
|
import net.minecraft.core.Holder;
|
||||||
|
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.ChunkGenerator;
|
||||||
|
import net.minecraft.world.level.dimension.DimensionType;
|
||||||
|
import net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.RandomState;
|
||||||
|
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, Holder<DimensionType> arg3, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
|
||||||
|
super(arg, arg2, arg3, 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/ChunkGenerator;ensureStructuresGenerated(Lnet/minecraft/world/level/levelgen/RandomState;)V"))
|
||||||
|
private void hookStrongholdCache(ChunkGenerator generator, RandomState state) {
|
||||||
|
((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.getGenerator().ensureStructuresGenerated(this.chunkSource.randomState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StrongholdLocationCache mfix$getStrongholdCache() {
|
||||||
|
return mfix$strongholdCache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
package org.embeddedt.modernfix.common.mixin.perf.cache_upgraded_structures;
|
package org.embeddedt.modernfix.common.mixin.perf.cache_upgraded_structures;
|
||||||
|
|
||||||
import com.mojang.datafixers.DataFixer;
|
import com.mojang.datafixers.DataFixer;
|
||||||
import net.minecraft.core.HolderGetter;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.packs.resources.ResourceManager;
|
import net.minecraft.server.packs.resources.ResourceManager;
|
||||||
import net.minecraft.world.level.block.Block;
|
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||||
import org.embeddedt.modernfix.ModernFix;
|
import org.embeddedt.modernfix.ModernFix;
|
||||||
|
|
@ -16,7 +14,6 @@ import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Mixin(StructureTemplateManager.class)
|
@Mixin(StructureTemplateManager.class)
|
||||||
|
|
@ -25,8 +22,6 @@ public class StructureManagerMixin {
|
||||||
|
|
||||||
@Shadow private ResourceManager resourceManager;
|
@Shadow private ResourceManager resourceManager;
|
||||||
|
|
||||||
@Shadow @Final private HolderGetter<Block> blockLookup;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author embeddedt
|
* @author embeddedt
|
||||||
* @reason use our own manager to avoid needless DFU updates
|
* @reason use our own manager to avoid needless DFU updates
|
||||||
|
|
@ -34,8 +29,8 @@ public class StructureManagerMixin {
|
||||||
@Overwrite
|
@Overwrite
|
||||||
private Optional<StructureTemplate> loadFromResource(ResourceLocation id) {
|
private Optional<StructureTemplate> loadFromResource(ResourceLocation id) {
|
||||||
ResourceLocation arg = new ResourceLocation(id.getNamespace(), "structures/" + id.getPath() + ".nbt");
|
ResourceLocation arg = new ResourceLocation(id.getNamespace(), "structures/" + id.getPath() + ".nbt");
|
||||||
try(InputStream stream = this.resourceManager.open(arg)) {
|
try {
|
||||||
return Optional.of(CachingStructureManager.readStructure(id, this.fixerUpper, stream, this.blockLookup));
|
return Optional.of(CachingStructureManager.readStructure(id, this.fixerUpper, this.resourceManager.open(arg)));
|
||||||
} catch(FileNotFoundException e) {
|
} catch(FileNotFoundException e) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
|
|
@ -30,15 +30,8 @@ public abstract class PalettedContainerMixin<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(empty && storArray.length > 0) {
|
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 */
|
/* it means the chunk is oversized and wasting memory, take the ID out of the palette and recreate a smaller chunk */
|
||||||
try {
|
T value = this.data.palette().valueFor(0);
|
||||||
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;
|
|
||||||
}
|
|
||||||
this.data = this.createOrReuseData(null, 0);
|
this.data = this.createOrReuseData(null, 0);
|
||||||
this.data.palette().idFor(value);
|
this.data.palette().idFor(value);
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,6 @@ package org.embeddedt.modernfix.common.mixin.perf.dedicated_reload_executor;
|
||||||
|
|
||||||
import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen;
|
import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen;
|
||||||
import org.embeddedt.modernfix.ModernFix;
|
import org.embeddedt.modernfix.ModernFix;
|
||||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||||
|
|
@ -10,9 +9,8 @@ import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
@Mixin(CreateWorldScreen.class)
|
@Mixin(CreateWorldScreen.class)
|
||||||
@ClientOnlyMixin
|
|
||||||
public class CreateWorldScreenMixin {
|
public class CreateWorldScreenMixin {
|
||||||
@ModifyArg(method = "applyNewPackConfig", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/WorldLoader;load(Lnet/minecraft/server/WorldLoader$InitConfig;Lnet/minecraft/server/WorldLoader$WorldDataSupplier;Lnet/minecraft/server/WorldLoader$ResultFactory;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 3)
|
@ModifyArg(method = "tryApplyNewDataPacks", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/WorldLoader;load(Lnet/minecraft/server/WorldLoader$InitConfig;Lnet/minecraft/server/WorldLoader$WorldDataSupplier;Lnet/minecraft/server/WorldLoader$ResultFactory;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 3)
|
||||||
private Executor getReloadExecutorService(Executor e) {
|
private Executor getReloadExecutorService(Executor e) {
|
||||||
return ModernFix.resourceReloadExecutor();
|
return ModernFix.resourceReloadExecutor();
|
||||||
}
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ import java.util.concurrent.Executor;
|
||||||
|
|
||||||
@Mixin(MinecraftServer.class)
|
@Mixin(MinecraftServer.class)
|
||||||
public class MinecraftServerMixin {
|
public class MinecraftServerMixin {
|
||||||
@ModifyArg(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ReloadableServerResources;loadResources(Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/core/RegistryAccess$Frozen;Lnet/minecraft/world/flag/FeatureFlagSet;Lnet/minecraft/commands/Commands$CommandSelection;ILjava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 5)
|
@ModifyArg(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ReloadableServerResources;loadResources(Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/core/RegistryAccess$Frozen;Lnet/minecraft/commands/Commands$CommandSelection;ILjava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 4)
|
||||||
private Executor getReloadExecutor(Executor asyncExecutor) {
|
private Executor getReloadExecutor(Executor asyncExecutor) {
|
||||||
return ModernFix.resourceReloadExecutor();
|
return ModernFix.resourceReloadExecutor();
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,6 @@ package org.embeddedt.modernfix.common.mixin.perf.dedicated_reload_executor;
|
||||||
|
|
||||||
import net.minecraft.client.gui.screens.worldselection.WorldOpenFlows;
|
import net.minecraft.client.gui.screens.worldselection.WorldOpenFlows;
|
||||||
import org.embeddedt.modernfix.ModernFix;
|
import org.embeddedt.modernfix.ModernFix;
|
||||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||||
|
|
@ -10,9 +9,8 @@ import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
@Mixin(WorldOpenFlows.class)
|
@Mixin(WorldOpenFlows.class)
|
||||||
@ClientOnlyMixin
|
|
||||||
public class WorldOpenFlowsMixin {
|
public class WorldOpenFlowsMixin {
|
||||||
@ModifyArg(method = "loadWorldDataBlocking", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/WorldLoader;load(Lnet/minecraft/server/WorldLoader$InitConfig;Lnet/minecraft/server/WorldLoader$WorldDataSupplier;Lnet/minecraft/server/WorldLoader$ResultFactory;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 3)
|
@ModifyArg(method = "loadWorldStem(Lnet/minecraft/server/WorldLoader$PackConfig;Lnet/minecraft/server/WorldLoader$WorldDataSupplier;)Lnet/minecraft/server/WorldStem;", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/WorldStem;load(Lnet/minecraft/server/WorldLoader$InitConfig;Lnet/minecraft/server/WorldLoader$WorldDataSupplier;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 2)
|
||||||
private Executor getResourceReloadExecutor(Executor service) {
|
private Executor getResourceReloadExecutor(Executor service) {
|
||||||
return ModernFix.resourceReloadExecutor();
|
return ModernFix.resourceReloadExecutor();
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_dfu;
|
package org.embeddedt.modernfix.common.mixin.perf.dynamic_dfu;
|
||||||
|
|
||||||
import com.mojang.datafixers.DSL;
|
|
||||||
import com.mojang.datafixers.DataFixer;
|
import com.mojang.datafixers.DataFixer;
|
||||||
import net.minecraft.util.datafix.DataFixers;
|
import net.minecraft.util.datafix.DataFixers;
|
||||||
import org.embeddedt.modernfix.dfu.LazyDataFixer;
|
import org.embeddedt.modernfix.dfu.LazyDataFixer;
|
||||||
|
|
@ -10,11 +9,9 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@Mixin(DataFixers.class)
|
@Mixin(DataFixers.class)
|
||||||
public abstract class DataFixersMixin {
|
public abstract class DataFixersMixin {
|
||||||
@Shadow protected static DataFixer createFixerUpper(Set<DSL.TypeReference> set) {
|
@Shadow protected static DataFixer createFixerUpper() {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,9 +21,9 @@ public abstract class DataFixersMixin {
|
||||||
* Avoid classloading the DFU logic until we actually need it.
|
* Avoid classloading the DFU logic until we actually need it.
|
||||||
*/
|
*/
|
||||||
@Inject(method = "createFixerUpper", at = @At("HEAD"), cancellable = true)
|
@Inject(method = "createFixerUpper", at = @At("HEAD"), cancellable = true)
|
||||||
private static void createLazyFixerUpper(Set<DSL.TypeReference> set, CallbackInfoReturnable<DataFixer> cir) {
|
private static void createLazyFixerUpper(CallbackInfoReturnable<DataFixer> cir) {
|
||||||
if(lazyDataFixer == null) {
|
if(lazyDataFixer == null) {
|
||||||
lazyDataFixer = new LazyDataFixer(() -> createFixerUpper(set));
|
lazyDataFixer = new LazyDataFixer(DataFixersMixin::createFixerUpper);
|
||||||
cir.setReturnValue(lazyDataFixer);
|
cir.setReturnValue(lazyDataFixer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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))
|
||||||
|
private Object skipUvsForInitialLoad(JsonDeserializationContext context, JsonElement element, Type type) {
|
||||||
|
return UVController.useDummyUv.get() ? UVController.dummyUv : context.deserialize(element, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,12 +3,8 @@ package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||||
import net.minecraft.client.renderer.block.BlockModelShaper;
|
import net.minecraft.client.renderer.block.BlockModelShaper;
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
import net.minecraft.client.resources.model.ModelManager;
|
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 net.minecraft.world.level.block.state.BlockState;
|
||||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||||
import org.embeddedt.modernfix.duck.IModelHoldingBlockState;
|
|
||||||
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
|
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
|
||||||
import org.embeddedt.modernfix.util.DynamicOverridableMap;
|
import org.embeddedt.modernfix.util.DynamicOverridableMap;
|
||||||
import org.spongepowered.asm.mixin.*;
|
import org.spongepowered.asm.mixin.*;
|
||||||
|
|
@ -18,57 +14,35 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
@Mixin(BlockModelShaper.class)
|
@Mixin(BlockModelShaper.class)
|
||||||
@ClientOnlyMixin
|
@ClientOnlyMixin
|
||||||
public class BlockModelShaperMixin {
|
public class BlockModelShaperMixin {
|
||||||
@Shadow @Final private ModelManager modelManager;
|
@Shadow @Final private ModelManager modelManager;
|
||||||
|
|
||||||
@Shadow
|
@Shadow @Final @Mutable
|
||||||
private Map<BlockState, BakedModel> modelByStateCache;
|
private Map<BlockState, BakedModel> modelByStateCache;
|
||||||
|
|
||||||
@Inject(method = { "<init>", "replaceCache" }, at = @At("RETURN"))
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
private void replaceModelMap(CallbackInfo ci) {
|
private void replaceModelMap(CallbackInfo ci) {
|
||||||
// replace the backing map for mods which will access it
|
// replace the backing map for mods which will access it
|
||||||
this.modelByStateCache = new DynamicOverridableMap<>(state -> modelManager.getModel(ModelLocationCache.get(state)));
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author embeddedt
|
* @author embeddedt
|
||||||
* @reason get the model from the dynamic model provider
|
* @reason no need to rebuild model cache, and location cache is done elsewhere
|
||||||
*/
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public void rebuildCache() {
|
||||||
|
}
|
||||||
|
|
||||||
@Overwrite
|
@Overwrite
|
||||||
public BakedModel getBlockModel(BlockState state) {
|
public BakedModel getBlockModel(BlockState state) {
|
||||||
if(state instanceof IModelHoldingBlockState modelHolder) {
|
BakedModel model = modelManager.getModel(ModelLocationCache.get(state));
|
||||||
BakedModel model = modelHolder.mfix$getModel();
|
if (model == null) {
|
||||||
|
model = modelManager.getMissingModel();
|
||||||
if(model != null) {
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
model = this.cacheBlockModel(state);
|
|
||||||
modelHolder.mfix$setModel(model);
|
|
||||||
return model;
|
|
||||||
} else {
|
|
||||||
return this.cacheBlockModel(state);
|
|
||||||
}
|
}
|
||||||
|
return model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,10 +5,7 @@ import net.minecraft.client.renderer.ItemModelShaper;
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
import net.minecraft.client.resources.model.ModelManager;
|
import net.minecraft.client.resources.model.ModelManager;
|
||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.world.item.Item;
|
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.dynamicresources.ModelLocationCache;
|
||||||
import org.embeddedt.modernfix.util.DynamicInt2ObjectMap;
|
import org.embeddedt.modernfix.util.DynamicInt2ObjectMap;
|
||||||
import org.spongepowered.asm.mixin.*;
|
import org.spongepowered.asm.mixin.*;
|
||||||
|
|
@ -20,7 +17,6 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Mixin(ItemModelShaper.class)
|
@Mixin(ItemModelShaper.class)
|
||||||
@ClientOnlyMixin
|
|
||||||
public abstract class ItemModelShaperMixin {
|
public abstract class ItemModelShaperMixin {
|
||||||
|
|
||||||
@Shadow public abstract ModelManager getModelManager();
|
@Shadow public abstract ModelManager getModelManager();
|
||||||
|
|
@ -33,9 +29,7 @@ public abstract class ItemModelShaperMixin {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final ModelResourceLocation SENTINEL_VANILLA = new ModelResourceLocation(new ResourceLocation("modernfix", "sentinel"), "sentinel");
|
private static final ModelResourceLocation SENTINEL_VANILLA = new ModelResourceLocation("modernfix", "sentinel");
|
||||||
|
|
||||||
private final DynamicModelCache<Item> mfix$itemModelCache = new DynamicModelCache<>(k -> this.mfix$getModelForItem((Item)k), true);
|
|
||||||
|
|
||||||
@Inject(method = "<init>", at = @At("RETURN"))
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
private void replaceLocationMap(CallbackInfo ci) {
|
private void replaceLocationMap(CallbackInfo ci) {
|
||||||
|
|
@ -53,24 +47,17 @@ public abstract class ItemModelShaperMixin {
|
||||||
return map;
|
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
|
* @reason Get the stored location for that item and meta, and get the model
|
||||||
* from that location from the model manager.
|
* from that location from the model manager.
|
||||||
**/
|
**/
|
||||||
@Overwrite
|
@Overwrite
|
||||||
public BakedModel getItemModel(Item item) {
|
public BakedModel getItemModel(Item item) {
|
||||||
return this.mfix$itemModelCache.get(item);
|
ModelResourceLocation map = mfix$getLocation(item);
|
||||||
|
return map == null ? null : getModelManager().getModel(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author embeddedt
|
|
||||||
* @reason Don't get all models during init (with dynamic loading, that would
|
* @reason Don't get all models during init (with dynamic loading, that would
|
||||||
* generate them all). Just store location instead.
|
* generate them all). Just store location instead.
|
||||||
**/
|
**/
|
||||||
|
|
@ -80,12 +67,9 @@ public abstract class ItemModelShaperMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author embeddedt
|
|
||||||
* @reason Disable cache rebuilding (with dynamic loading, that would generate
|
* @reason Disable cache rebuilding (with dynamic loading, that would generate
|
||||||
* all models).
|
* all models).
|
||||||
**/
|
**/
|
||||||
@Overwrite
|
@Overwrite
|
||||||
public void rebuildCache() {
|
public void rebuildCache() {}
|
||||||
this.mfix$itemModelCache.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,10 +3,8 @@ package org.embeddedt.modernfix.common.mixin.perf.dynamic_structure_manager;
|
||||||
import com.google.common.cache.Cache;
|
import com.google.common.cache.Cache;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.mojang.datafixers.DataFixer;
|
import com.mojang.datafixers.DataFixer;
|
||||||
import net.minecraft.core.HolderGetter;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.packs.resources.ResourceManager;
|
import net.minecraft.server.packs.resources.ResourceManager;
|
||||||
import net.minecraft.world.level.block.Block;
|
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||||
|
|
@ -27,7 +25,7 @@ public class StructureManagerMixin {
|
||||||
private Map<ResourceLocation, Optional<StructureTemplate>> structureRepository;
|
private Map<ResourceLocation, Optional<StructureTemplate>> structureRepository;
|
||||||
|
|
||||||
@Inject(method = "<init>", at = @At("RETURN"))
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
private void makeStructuresSafe(ResourceManager arg, LevelStorageSource.LevelStorageAccess arg2, DataFixer dataFixer, HolderGetter<Block> arg3, CallbackInfo ci) {
|
private void makeStructuresSafe(ResourceManager arg, LevelStorageSource.LevelStorageAccess arg2, DataFixer dataFixer, CallbackInfo ci) {
|
||||||
/* Structures needing to be reloaded is not a huge issue since we optimize loading them already */
|
/* Structures needing to be reloaded is not a huge issue since we optimize loading them already */
|
||||||
Cache<ResourceLocation, Optional<StructureTemplate>> structureCache = CacheBuilder.newBuilder()
|
Cache<ResourceLocation, Optional<StructureTemplate>> structureCache = CacheBuilder.newBuilder()
|
||||||
.softValues()
|
.softValues()
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.faster_font_loading;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.NativeImage;
|
||||||
|
import net.minecraft.client.gui.font.providers.LegacyUnicodeBitmapsProvider;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Objective: avoid recomputing locations many times, as well as loading all the font sheets in the constructor
|
||||||
|
* only to do it again later.
|
||||||
|
*/
|
||||||
|
@Mixin(LegacyUnicodeBitmapsProvider.class)
|
||||||
|
@ClientOnlyMixin
|
||||||
|
public abstract class LegacyUnicodeBitmapsProviderMixin {
|
||||||
|
@Shadow protected abstract ResourceLocation getSheetLocation(int i);
|
||||||
|
|
||||||
|
@Shadow @Final private Map<ResourceLocation, NativeImage> textures;
|
||||||
|
private final ResourceLocation[] glyphLocations = new ResourceLocation[256];
|
||||||
|
|
||||||
|
private ResourceLocation currentCharIdx;
|
||||||
|
|
||||||
|
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/font/providers/LegacyUnicodeBitmapsProvider;getSheetLocation(I)Lnet/minecraft/resources/ResourceLocation;"))
|
||||||
|
private ResourceLocation storeCurrentCharIdx(LegacyUnicodeBitmapsProvider provider, int i) {
|
||||||
|
ResourceLocation location = getSheetLocation(i);
|
||||||
|
currentCharIdx = location;
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/NativeImage;read(Lcom/mojang/blaze3d/platform/NativeImage$Format;Ljava/io/InputStream;)Lcom/mojang/blaze3d/platform/NativeImage;"))
|
||||||
|
private NativeImage storeLoadedFontSheet(NativeImage.Format format, InputStream stream) throws IOException {
|
||||||
|
NativeImage image = NativeImage.read(format, stream);
|
||||||
|
textures.put(currentCharIdx, image);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/NativeImage;close()V"))
|
||||||
|
private void skipCloseNativeImage(NativeImage image) {
|
||||||
|
/* we can't close here, as the image has been stored for use later */
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
|
private void clearLocation(CallbackInfo ci) {
|
||||||
|
currentCharIdx = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "getSheetLocation", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void useCachedLocation(int idx, CallbackInfoReturnable<ResourceLocation> cir) {
|
||||||
|
int cachedIdx = idx / 256;
|
||||||
|
if(cachedIdx >= 0 && cachedIdx < glyphLocations.length && glyphLocations[cachedIdx] != null)
|
||||||
|
cir.setReturnValue(glyphLocations[cachedIdx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "getSheetLocation", at = @At("RETURN"))
|
||||||
|
private void saveCachedLocation(int idx, CallbackInfoReturnable<ResourceLocation> cir) {
|
||||||
|
int cachedIdx = idx / 256;
|
||||||
|
if(cachedIdx >= 0 && cachedIdx < glyphLocations.length)
|
||||||
|
glyphLocations[cachedIdx] = cir.getReturnValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package org.embeddedt.modernfix.common.mixin.perf.faster_item_rendering;
|
package org.embeddedt.modernfix.common.mixin.perf.faster_item_rendering;
|
||||||
|
|
||||||
import net.minecraft.client.renderer.GameRenderer;
|
import net.minecraft.client.renderer.GameRenderer;
|
||||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
|
||||||
import org.embeddedt.modernfix.render.RenderState;
|
import org.embeddedt.modernfix.render.RenderState;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
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;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
@Mixin(GameRenderer.class)
|
@Mixin(GameRenderer.class)
|
||||||
@ClientOnlyMixin
|
|
||||||
public class GameRendererMixin {
|
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))
|
@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) {
|
private void markRenderingLevel(CallbackInfo ci) {
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.faster_item_rendering;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
import net.minecraft.client.color.item.ItemColors;
|
||||||
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||||
|
import net.minecraft.client.renderer.block.model.ItemTransform;
|
||||||
|
import net.minecraft.client.renderer.block.model.ItemTransforms;
|
||||||
|
import net.minecraft.client.renderer.entity.ItemRenderer;
|
||||||
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
|
import net.minecraft.client.resources.model.SimpleBakedModel;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.util.RandomSource;
|
||||||
|
import net.minecraft.world.item.BlockItem;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import org.embeddedt.modernfix.render.FastItemRenderType;
|
||||||
|
import org.embeddedt.modernfix.render.RenderState;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mixin(ItemRenderer.class)
|
||||||
|
public abstract class ItemRendererMixin {
|
||||||
|
@Shadow @Final private ItemColors itemColors;
|
||||||
|
|
||||||
|
private final RandomSource dummyRandom = RandomSource.createNewThreadLocalInstance();
|
||||||
|
|
||||||
|
private static final float[] COLOR_MULTIPLIER = new float[]{1.0F, 1.0F, 1.0F, 1.0F};
|
||||||
|
|
||||||
|
private ItemTransforms.TransformType transformType;
|
||||||
|
|
||||||
|
@Inject(method = "render", at = @At("HEAD"))
|
||||||
|
private void markRenderingType(ItemStack itemStack, ItemTransforms.TransformType transformType, boolean leftHand, PoseStack matrixStack, MultiBufferSource buffer, int combinedLight, int combinedOverlay, BakedModel model, CallbackInfo ci) {
|
||||||
|
this.transformType = transformType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Direction[] ITEM_DIRECTIONS = new Direction[] { Direction.SOUTH };
|
||||||
|
private static final Direction[] BLOCK_DIRECTIONS = new Direction[] { Direction.UP, Direction.EAST, Direction.NORTH };
|
||||||
|
|
||||||
|
private boolean isCorrectDirectionForType(FastItemRenderType type, Direction direction) {
|
||||||
|
if(type == FastItemRenderType.SIMPLE_ITEM)
|
||||||
|
return direction == Direction.SOUTH;
|
||||||
|
else {
|
||||||
|
return direction == Direction.UP || direction == Direction.EAST || direction == Direction.NORTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a model
|
||||||
|
* - is a vanilla item model (SimpleBakedModel),
|
||||||
|
* - has no custom GUI transforms, and
|
||||||
|
* - is being rendered in 2D on a GUI
|
||||||
|
* we do not need to go through the process of rendering every quad. Just render the south ones (the ones facing the
|
||||||
|
* camera).
|
||||||
|
*/
|
||||||
|
@Inject(method = "renderModelLists", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void fasterItemRender(BakedModel model, ItemStack stack, int combinedLight, int combinedOverlay, PoseStack matrixStack, VertexConsumer buffer, CallbackInfo ci) {
|
||||||
|
if(!RenderState.IS_RENDERING_LEVEL && !stack.isEmpty() && model.getClass() == SimpleBakedModel.class && transformType == ItemTransforms.TransformType.GUI) {
|
||||||
|
FastItemRenderType type;
|
||||||
|
ItemTransform transform = model.getTransforms().gui;
|
||||||
|
if(transform == ItemTransform.NO_TRANSFORM)
|
||||||
|
type = FastItemRenderType.SIMPLE_ITEM;
|
||||||
|
else if(stack.getItem() instanceof BlockItem && isBlockTransforms(transform))
|
||||||
|
type = FastItemRenderType.SIMPLE_BLOCK;
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
ci.cancel();
|
||||||
|
PoseStack.Pose pose = matrixStack.last();
|
||||||
|
int[] combinedLights = new int[] {combinedLight, combinedLight, combinedLight, combinedLight};
|
||||||
|
Direction[] directions = type == FastItemRenderType.SIMPLE_ITEM ? ITEM_DIRECTIONS : BLOCK_DIRECTIONS;
|
||||||
|
for(Direction direction : directions) {
|
||||||
|
List<BakedQuad> culledFaces = model.getQuads(null, direction, dummyRandom);
|
||||||
|
/* check size to avoid instantiating iterator when the list is empty */
|
||||||
|
if(culledFaces.size() > 0) {
|
||||||
|
for(BakedQuad quad : culledFaces) {
|
||||||
|
render2dItemFace(quad, stack, buffer, pose, combinedLights, combinedOverlay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<BakedQuad> unculledFaces = model.getQuads(null, null, dummyRandom);
|
||||||
|
for(BakedQuad quad : unculledFaces) {
|
||||||
|
if(isCorrectDirectionForType(type, quad.getDirection()))
|
||||||
|
render2dItemFace(quad, stack, buffer, pose, combinedLights, combinedOverlay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBlockTransforms(ItemTransform transform) {
|
||||||
|
return transform.rotation.x() == 30f
|
||||||
|
&& transform.rotation.y() == 225f
|
||||||
|
&& transform.rotation.z() == 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void render2dItemFace(BakedQuad quad, ItemStack stack, VertexConsumer buffer, PoseStack.Pose pose, int[] combinedLights, int combinedOverlay) {
|
||||||
|
int i = -1;
|
||||||
|
if (quad.isTinted()) {
|
||||||
|
i = this.itemColors.getColor(stack, quad.getTintIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
float f = (float)(i >> 16 & 255) / 255.0F;
|
||||||
|
float f1 = (float)(i >> 8 & 255) / 255.0F;
|
||||||
|
float f2 = (float)(i & 255) / 255.0F;
|
||||||
|
buffer.putBulkData(pose, quad, COLOR_MULTIPLIER, f, f1, f2, combinedLights, combinedOverlay, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.faster_texture_loading;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.NativeImage;
|
||||||
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite;
|
||||||
|
import net.minecraft.client.renderer.texture.TextureAtlas;
|
||||||
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||||
|
import net.minecraft.client.resources.metadata.animation.AnimationMetadataSection;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.packs.resources.Resource;
|
||||||
|
import net.minecraft.server.packs.resources.ResourceManager;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.embeddedt.modernfix.ModernFix;
|
||||||
|
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||||
|
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
|
@Mixin(value = TextureAtlas.class, priority = 600)
|
||||||
|
@ClientOnlyMixin
|
||||||
|
public abstract class TextureAtlasMixin {
|
||||||
|
@Shadow protected abstract ResourceLocation getResourceLocation(ResourceLocation location);
|
||||||
|
|
||||||
|
@Shadow protected abstract Collection<TextureAtlasSprite.Info> getBasicSpriteInfos(ResourceManager resourceManager, Set<ResourceLocation> spriteLocations);
|
||||||
|
|
||||||
|
private Map<ResourceLocation, Pair<Resource, NativeImage>> loadedImages = new ConcurrentHashMap<>();
|
||||||
|
private boolean usingFasterLoad;
|
||||||
|
private Collection<TextureAtlasSprite.Info> storedResults;
|
||||||
|
/**
|
||||||
|
* @author embeddedt
|
||||||
|
* @reason simplify texture loading by loading whole image once, avoid slow PngInfo code
|
||||||
|
*/
|
||||||
|
@Inject(method = "getBasicSpriteInfos", at = @At("HEAD"))
|
||||||
|
private void loadImages(ResourceManager manager, Set<ResourceLocation> imageLocations, CallbackInfoReturnable<Collection<TextureAtlasSprite.Info>> cir) {
|
||||||
|
usingFasterLoad = ModernFixPlatformHooks.isLoadingNormally();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "getBasicSpriteInfos", at = @At(value = "INVOKE", target = "Ljava/util/Set;iterator()Ljava/util/Iterator;", ordinal = 0))
|
||||||
|
private Iterator<?> skipIteration(Set<?> instance, ResourceManager manager, Set<ResourceLocation> imageLocations) {
|
||||||
|
// bail if Forge is erroring to avoid AT crashes
|
||||||
|
if(!usingFasterLoad)
|
||||||
|
return instance.iterator();
|
||||||
|
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||||
|
ConcurrentLinkedQueue<TextureAtlasSprite.Info> results = new ConcurrentLinkedQueue<>();
|
||||||
|
for(ResourceLocation location : imageLocations) {
|
||||||
|
if(MissingTextureAtlasSprite.getLocation().equals(location))
|
||||||
|
continue;
|
||||||
|
futures.add(CompletableFuture.runAsync(() -> {
|
||||||
|
InputStream stream = null;
|
||||||
|
try {
|
||||||
|
ResourceLocation fileLocation = this.getResourceLocation(location);
|
||||||
|
Optional<Resource> resourceOpt = manager.getResource(fileLocation);
|
||||||
|
if(!resourceOpt.isPresent()) {
|
||||||
|
ModernFix.LOGGER.error("Using missing texture, unable to load {}", location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Resource resource = resourceOpt.get();
|
||||||
|
stream = resource.open();
|
||||||
|
NativeImage image = NativeImage.read(stream);
|
||||||
|
AnimationMetadataSection animData = resource.metadata().getSection(AnimationMetadataSection.SERIALIZER).orElse(AnimationMetadataSection.EMPTY);
|
||||||
|
Pair<Integer, Integer> dimensions = animData.getFrameSize(image.getWidth(), image.getHeight());
|
||||||
|
loadedImages.put(location, Pair.of(resource, image));
|
||||||
|
results.add(new TextureAtlasSprite.Info(location, dimensions.getFirst(), dimensions.getSecond(), animData));
|
||||||
|
stream.close();
|
||||||
|
stream = null;
|
||||||
|
} catch(IOException e) {
|
||||||
|
ModernFix.LOGGER.error("Using missing texture, unable to load {} : {}", location, e);
|
||||||
|
} catch(RuntimeException e) {
|
||||||
|
ModernFix.LOGGER.error("Unable to parse metadata from {} : {}", location, e);
|
||||||
|
}
|
||||||
|
if(stream != null)
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
|
}, ModernFix.resourceReloadExecutor()));
|
||||||
|
}
|
||||||
|
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||||
|
storedResults = results;
|
||||||
|
return Collections.emptyIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "getBasicSpriteInfos", at = @At("RETURN"))
|
||||||
|
private void injectFastSprites(ResourceManager resourceManager, Set<ResourceLocation> spriteLocations, CallbackInfoReturnable<Collection<TextureAtlasSprite.Info>> cir) {
|
||||||
|
if(usingFasterLoad)
|
||||||
|
cir.getReturnValue().addAll(storedResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "prepareToStitch", at = @At("HEAD"))
|
||||||
|
private void initMap(CallbackInfoReturnable<TextureAtlas.Preparations> cir) {
|
||||||
|
loadedImages = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "prepareToStitch", at = @At("RETURN"))
|
||||||
|
private void clearLoadedImages(CallbackInfoReturnable<TextureAtlas.Preparations> cir) {
|
||||||
|
loadedImages = Collections.emptyMap();
|
||||||
|
storedResults = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "load(Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite$Info;IIIII)Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;",
|
||||||
|
at = @At("HEAD"), cancellable = true)
|
||||||
|
private void loadFromExisting(ResourceManager resourceManager, TextureAtlasSprite.Info spriteInfo, int storageX, int storageY, int mipLevel, int x, int y, CallbackInfoReturnable<TextureAtlasSprite> cir) {
|
||||||
|
if(!usingFasterLoad)
|
||||||
|
return;
|
||||||
|
Pair<Resource, NativeImage> pair = loadedImages.get(spriteInfo.name());
|
||||||
|
if(pair == null) {
|
||||||
|
ModernFix.LOGGER.error("Texture {} was not loaded in early stage", spriteInfo.name());
|
||||||
|
cir.setReturnValue(null);
|
||||||
|
} else {
|
||||||
|
TextureAtlasSprite sprite = null;
|
||||||
|
try {
|
||||||
|
sprite = ModernFixPlatformHooks.loadTextureAtlasSprite((TextureAtlas)(Object)this, resourceManager, spriteInfo, pair.getFirst(), storageX, storageY, x, y, mipLevel, pair.getSecond());
|
||||||
|
} catch(RuntimeException | IOException e) {
|
||||||
|
ModernFix.LOGGER.error("Error loading texture {}: {}", spriteInfo.name(), e);
|
||||||
|
}
|
||||||
|
cir.setReturnValue(sprite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
package org.embeddedt.modernfix.common.mixin.perf.faster_texture_stitching;
|
package org.embeddedt.modernfix.common.mixin.perf.faster_texture_stitching;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
import com.mojang.datafixers.util.Pair;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
import net.minecraft.client.renderer.texture.Stitcher;
|
import net.minecraft.client.renderer.texture.Stitcher;
|
||||||
import net.minecraft.client.renderer.texture.StitcherException;
|
|
||||||
import org.embeddedt.modernfix.ModernFix;
|
import org.embeddedt.modernfix.ModernFix;
|
||||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||||
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||||
|
|
@ -18,22 +16,19 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
import java.util.Set;
|
||||||
|
|
||||||
@Mixin(Stitcher.class)
|
@Mixin(Stitcher.class)
|
||||||
@ClientOnlyMixin
|
@ClientOnlyMixin
|
||||||
public class StitcherMixin<T extends Stitcher.Entry> {
|
public class StitcherMixin {
|
||||||
@Shadow @Final private List<Stitcher.Holder<T>> texturesToBeStitched;
|
@Shadow @Final private Set<Stitcher.Holder> texturesToBeStitched;
|
||||||
|
|
||||||
@Shadow private int storageX;
|
@Shadow private int storageX;
|
||||||
|
|
||||||
@Shadow private int storageY;
|
@Shadow private int storageY;
|
||||||
|
|
||||||
@Shadow @Final private int maxWidth;
|
@Shadow @Final private static Comparator<Stitcher.Holder> HOLDER_COMPARATOR;
|
||||||
@Shadow @Final private int maxHeight;
|
private List<StbStitcher.LoadableSpriteInfo> loadableSpriteInfos;
|
||||||
|
|
||||||
@Shadow @Final private static Comparator<Stitcher.Holder<?>> HOLDER_COMPARATOR;
|
|
||||||
private List<StbStitcher.LoadableSpriteInfo<T>> loadableSpriteInfos;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author embeddedt, SuperCoder79
|
* @author embeddedt, SuperCoder79
|
||||||
|
|
@ -41,31 +36,18 @@ public class StitcherMixin<T extends Stitcher.Entry> {
|
||||||
*/
|
*/
|
||||||
@Inject(method = "stitch", at = @At("HEAD"), cancellable = true)
|
@Inject(method = "stitch", at = @At("HEAD"), cancellable = true)
|
||||||
private void stitchFast(CallbackInfo ci) {
|
private void stitchFast(CallbackInfo ci) {
|
||||||
this.loadableSpriteInfos = null;
|
if(!ModernFixPlatformHooks.isLoadingNormally()) {
|
||||||
if(!ModernFixPlatformHooks.INSTANCE.isLoadingNormally()) {
|
|
||||||
ModernFix.LOGGER.error("Using vanilla stitcher implementation due to invalid loading state");
|
ModernFix.LOGGER.error("Using vanilla stitcher implementation due to invalid loading state");
|
||||||
return;
|
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();
|
ci.cancel();
|
||||||
ObjectArrayList<Stitcher.Holder<T>> holderList = new ObjectArrayList<>(this.texturesToBeStitched);
|
ObjectArrayList<Stitcher.Holder> holderList = new ObjectArrayList<>(this.texturesToBeStitched);
|
||||||
holderList.sort(HOLDER_COMPARATOR);
|
holderList.sort(HOLDER_COMPARATOR);
|
||||||
Stitcher.Holder<T>[] aholder = holderList.toArray(new Stitcher.Holder[0]);
|
Stitcher.Holder[] aholder = holderList.toArray(new Stitcher.Holder[0]);
|
||||||
|
|
||||||
Pair<Pair<Integer, Integer>, List<StbStitcher.LoadableSpriteInfo<T>>> packingInfo = StbStitcher.packRects(aholder);
|
Pair<Pair<Integer, Integer>, List<StbStitcher.LoadableSpriteInfo>> packingInfo = StbStitcher.packRects(aholder);
|
||||||
this.storageX = packingInfo.getFirst().getFirst();
|
this.storageX = packingInfo.getFirst().getFirst();
|
||||||
this.storageY = packingInfo.getFirst().getSecond();
|
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();
|
this.loadableSpriteInfos = packingInfo.getSecond();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,12 +56,12 @@ public class StitcherMixin<T extends Stitcher.Entry> {
|
||||||
* @reason We setup the image ourselves in the StbStitcher, so we just feed this information back into the vanilla code
|
* @reason We setup the image ourselves in the StbStitcher, so we just feed this information back into the vanilla code
|
||||||
*/
|
*/
|
||||||
@Inject(method = "gatherSprites", at = @At("HEAD"), cancellable = true)
|
@Inject(method = "gatherSprites", at = @At("HEAD"), cancellable = true)
|
||||||
private void gatherSpritesFast(Stitcher.SpriteLoader<T> spriteLoader, CallbackInfo ci) {
|
private void gatherSpritesFast(Stitcher.SpriteLoader spriteLoader, CallbackInfo ci) {
|
||||||
if(this.loadableSpriteInfos == null)
|
if(!ModernFixPlatformHooks.isLoadingNormally())
|
||||||
return;
|
return;
|
||||||
ci.cancel();
|
ci.cancel();
|
||||||
for(StbStitcher.LoadableSpriteInfo<T> info : loadableSpriteInfos) {
|
for(StbStitcher.LoadableSpriteInfo info : loadableSpriteInfos) {
|
||||||
spriteLoader.load(info.info, info.x, info.y);
|
spriteLoader.load(info.info, info.width, info.height, info.x, info.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,22 +1,25 @@
|
||||||
package org.embeddedt.modernfix.common.mixin.perf.model_optimizations;
|
package org.embeddedt.modernfix.common.mixin.perf.model_optimizations;
|
||||||
|
|
||||||
import net.minecraft.world.level.block.state.properties.Property;
|
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.*;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
@Mixin(Property.class)
|
@Mixin(Property.class)
|
||||||
public class PropertyMixin {
|
public class PropertyMixin {
|
||||||
|
|
||||||
@Shadow @Final private String name;
|
@Shadow @Mutable
|
||||||
|
@Final private String name;
|
||||||
|
|
||||||
|
@Shadow private Integer hashCode;
|
||||||
|
|
||||||
@Shadow @Final private Class clazz;
|
@Shadow @Final private Class clazz;
|
||||||
|
|
||||||
@ModifyVariable(method = "<init>", at = @At("HEAD"), ordinal = 0, argsOnly = true)
|
@Redirect(method = "<init>", at = @At(value = "FIELD", target = "Lnet/minecraft/world/level/block/state/properties/Property;name:Ljava/lang/String;"))
|
||||||
private static String internName(String name) {
|
private void internName(Property instance, String name) {
|
||||||
return name.intern();
|
this.name = IdentifierCaches.PROPERTY.deduplicate(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author embeddedt
|
* @author embeddedt
|
||||||
* @reason compare hashcodes if generated, use reference equality for speed
|
* @reason compare hashcodes if generated, use reference equality for speed
|
||||||
|
|
@ -29,8 +32,7 @@ public class PropertyMixin {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
Property<?> property = (Property)p_equals_1_;
|
Property<?> property = (Property)p_equals_1_;
|
||||||
/* reference equality is safe here because of interning above */
|
/* reference equality is safe here because of deduplication */
|
||||||
//noinspection StringEquality
|
|
||||||
return this.clazz == property.getValueClass() && this.name == property.getName();
|
return this.clazz == property.getValueClass() && this.name == property.getName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package org.embeddedt.modernfix.common.mixin.perf.model_optimizations;
|
package org.embeddedt.modernfix.common.mixin.perf.model_optimizations;
|
||||||
|
|
||||||
|
import com.mojang.math.Matrix4f;
|
||||||
import com.mojang.math.Transformation;
|
import com.mojang.math.Transformation;
|
||||||
import org.joml.Matrix4f;
|
|
||||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||||
import org.spongepowered.asm.mixin.Final;
|
import org.spongepowered.asm.mixin.Final;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.nbt_memory_usage;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.Tag;
|
||||||
|
import org.embeddedt.modernfix.util.CanonizingStringMap;
|
||||||
|
import org.spongepowered.asm.mixin.*;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Mixin(CompoundTag.class)
|
||||||
|
public class CompoundTagMixin {
|
||||||
|
@Shadow @Final @Mutable
|
||||||
|
private Map<String, Tag> tags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the backing map is always a CanonizingStringMap.
|
||||||
|
*/
|
||||||
|
@Redirect(method = "<init>(Ljava/util/Map;)V", at = @At(value = "FIELD", target = "Lnet/minecraft/nbt/CompoundTag;tags:Ljava/util/Map;", ordinal = 0))
|
||||||
|
private void replaceTagMap(CompoundTag tag, Map<String, Tag> incomingMap) {
|
||||||
|
if(incomingMap instanceof CanonizingStringMap)
|
||||||
|
this.tags = incomingMap;
|
||||||
|
else {
|
||||||
|
this.tags = new CanonizingStringMap<>();
|
||||||
|
this.tags.putAll(incomingMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author embeddedt
|
||||||
|
* @reason use more efficient method when copying canonizing string map
|
||||||
|
*/
|
||||||
|
@Inject(method = "copy()Lnet/minecraft/nbt/CompoundTag;", at = @At("HEAD"), cancellable = true)
|
||||||
|
public void copyEfficient(CallbackInfoReturnable<Tag> cir) {
|
||||||
|
if(this.tags instanceof CanonizingStringMap) {
|
||||||
|
cir.setReturnValue(new CompoundTag(CanonizingStringMap.deepCopy((CanonizingStringMap<Tag>)this.tags, Tag::copy)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
|
import org.embeddedt.modernfix.duck.IBlockState;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.spongepowered.asm.mixin.Dynamic;
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
@Mixin(BlockBehaviour.BlockStateBase.class)
|
||||||
|
public abstract class BlockStateBaseMixin implements IBlockState {
|
||||||
|
@Shadow public abstract void initCache();
|
||||||
|
|
||||||
|
@Shadow private BlockBehaviour.BlockStateBase.Cache cache;
|
||||||
|
|
||||||
|
private volatile boolean cacheInvalid = false;
|
||||||
|
private static boolean buildingCache = false;
|
||||||
|
@Override
|
||||||
|
public void clearCache() {
|
||||||
|
cacheInvalid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockBehaviour.BlockStateBase.Cache generateCache(BlockBehaviour.BlockStateBase base) {
|
||||||
|
if(cacheInvalid) {
|
||||||
|
// Ensure that only one block's cache is built at a time
|
||||||
|
synchronized (BlockBehaviour.BlockStateBase.class) {
|
||||||
|
if(cacheInvalid) {
|
||||||
|
// Ensure that if we end up in here recursively, we just use the original cache
|
||||||
|
if(!buildingCache) {
|
||||||
|
buildingCache = true;
|
||||||
|
try {
|
||||||
|
this.initCache();
|
||||||
|
cacheInvalid = false;
|
||||||
|
} finally {
|
||||||
|
buildingCache = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "*", at = @At(
|
||||||
|
value = "FIELD",
|
||||||
|
opcode = Opcodes.GETFIELD,
|
||||||
|
target = "Lnet/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase;cache:Lnet/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase$Cache;",
|
||||||
|
ordinal = 0
|
||||||
|
))
|
||||||
|
private BlockBehaviour.BlockStateBase.Cache dynamicCacheGen(BlockBehaviour.BlockStateBase base) {
|
||||||
|
return generateCache(base);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Dynamic
|
||||||
|
@Inject(method = "getPathNodeType", at = @At("HEAD"), require = 0, remap = false)
|
||||||
|
private void generateCacheLithium(CallbackInfoReturnable<?> cir) {
|
||||||
|
generateCache((BlockBehaviour.BlockStateBase)(Object)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Dynamic
|
||||||
|
@Inject(method = "getNeighborPathNodeType", at = @At("HEAD"), require = 0, remap = false)
|
||||||
|
private void generateCacheLithium2(CallbackInfoReturnable<?> cir) {
|
||||||
|
generateCache((BlockBehaviour.BlockStateBase)(Object)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Dynamic
|
||||||
|
@Inject(method = "getAllFlags", at = @At("HEAD"), require = 0, remap = false)
|
||||||
|
private void generateCacheLithium3(CallbackInfoReturnable<?> cir) {
|
||||||
|
generateCache((BlockBehaviour.BlockStateBase)(Object)this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import org.embeddedt.modernfix.blockstate.BlockStateCacheHandler;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Mixin(value = Blocks.class, priority = 1100)
|
||||||
|
public class BlocksMixin {
|
||||||
|
@ModifyArg(method = "rebuildCache", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/IdMapper;forEach(Ljava/util/function/Consumer;)V"), index = 0)
|
||||||
|
private static Consumer getEmptyConsumer(Consumer original) {
|
||||||
|
BlockStateCacheHandler.rebuildParallel(true);
|
||||||
|
return o -> {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ public abstract class BiomeMixin {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Overwrite
|
@Overwrite
|
||||||
private float getTemperature(BlockPos pos) {
|
public final float getTemperature(BlockPos pos) {
|
||||||
return this.getHeightAdjustedTemperature(pos);
|
return this.getHeightAdjustedTemperature(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.remove_spawn_chunks;
|
||||||
|
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.level.ServerChunkCache;
|
||||||
|
import net.minecraft.server.level.TicketType;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
@Mixin(value = MinecraftServer.class, priority = 1100)
|
||||||
|
public class MinecraftServerMixin {
|
||||||
|
@Redirect(method = "prepareLevels", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V"))
|
||||||
|
private void addSpawnChunkTicket(ServerChunkCache cache, TicketType<?> type, ChunkPos pos, int distance, Object o) {
|
||||||
|
// load first chunk
|
||||||
|
cache.getChunk(pos.x, pos.z, ChunkStatus.FULL, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "prepareLevels", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;getTickingGenerated()I"), require = 0)
|
||||||
|
private int getGenerated(ServerChunkCache cache) {
|
||||||
|
return 441;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.remove_spawn_chunks;
|
||||||
|
|
||||||
|
import net.minecraft.server.level.DistanceManager;
|
||||||
|
import net.minecraft.server.level.ServerChunkCache;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
@Mixin(ServerChunkCache.class)
|
||||||
|
public interface ServerChunkCacheAccessor {
|
||||||
|
@Accessor("distanceManager")
|
||||||
|
DistanceManager getDistanceManager();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.perf.resourcepacks;
|
||||||
|
|
||||||
|
import net.minecraft.server.packs.resources.ReloadableResourceManager;
|
||||||
|
import org.embeddedt.modernfix.ModernFix;
|
||||||
|
import org.embeddedt.modernfix.resources.PackResourcesCacheEngine;
|
||||||
|
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(ReloadableResourceManager.class)
|
||||||
|
public class ReloadableResourceManagerMixin {
|
||||||
|
@Inject(method = "createReload", at = @At("HEAD"))
|
||||||
|
private void invalidateResourceCaches(CallbackInfoReturnable<?> cir) {
|
||||||
|
ModernFix.LOGGER.info("Invalidating pack caches");
|
||||||
|
PackResourcesCacheEngine.invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,17 +14,11 @@ import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
// This optimization requires FerriteCore to be worthwhile, otherwise the FakeStateMap degrades to hash internally
|
|
||||||
@Mixin(StateDefinition.class)
|
@Mixin(StateDefinition.class)
|
||||||
@RequiresMod("ferritecore")
|
@RequiresMod("ferritecore")
|
||||||
public class StateDefinitionMixin<O, S extends StateHolder<O, S>> {
|
public class StateDefinitionMixin<O, S extends StateHolder<O, S>> {
|
||||||
@Shadow @Final private ImmutableSortedMap<String, Property<?>> propertiesByName;
|
@Shadow @Final private ImmutableSortedMap<String, Property<?>> propertiesByName;
|
||||||
|
|
||||||
/**
|
|
||||||
* @author embeddedt
|
|
||||||
* @reason write states into a custom array map for fast iteration by FerriteCore, no need to waste time hashing
|
|
||||||
* and growing
|
|
||||||
*/
|
|
||||||
@ModifyVariable(method = "<init>", at = @At(value = "STORE", ordinal = 0), ordinal = 1, index = 8)
|
@ModifyVariable(method = "<init>", at = @At(value = "STORE", ordinal = 0), ordinal = 1, index = 8)
|
||||||
private Map<Map<Property<?>, Comparable<?>>, S> useArrayMap(Map<Map<Property<?>, Comparable<?>>, S> in) {
|
private Map<Map<Property<?>, Comparable<?>>, S> useArrayMap(Map<Map<Property<?>, Comparable<?>>, S> in) {
|
||||||
int numStates = 1;
|
int numStates = 1;
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package org.embeddedt.modernfix.common.mixin.safety;
|
||||||
|
|
||||||
|
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
|
||||||
|
import net.minecraft.client.renderer.entity.layers.RenderLayer;
|
||||||
|
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Mutable;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mixin(LivingEntityRenderer.class)
|
||||||
|
@ClientOnlyMixin
|
||||||
|
public class LivingEntityRendererMixin {
|
||||||
|
@Shadow @Final @Mutable
|
||||||
|
protected List<RenderLayer<?, ?>> layers;
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
|
private void synchronizeLayerList(CallbackInfo ci) {
|
||||||
|
/* allows buggy mods to call addLayer concurrently, order is not deterministic but can't fix that */
|
||||||
|
this.layers = Collections.synchronizedList(layers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
package org.embeddedt.modernfix.core;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig;
|
||||||
|
import org.embeddedt.modernfix.core.config.Option;
|
||||||
|
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||||
|
import org.objectweb.asm.tree.*;
|
||||||
|
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
|
||||||
|
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ModernFixMixinPlugin implements IMixinConfigPlugin {
|
||||||
|
private static final String MIXIN_PACKAGE_ROOT = "org.embeddedt.modernfix.mixin.";
|
||||||
|
|
||||||
|
public final Logger logger = LogManager.getLogger("ModernFix");
|
||||||
|
public ModernFixEarlyConfig config = null;
|
||||||
|
public static ModernFixMixinPlugin instance;
|
||||||
|
|
||||||
|
public ModernFixMixinPlugin() {
|
||||||
|
boolean firstConfig = instance == null;
|
||||||
|
if(firstConfig) {
|
||||||
|
instance = this;
|
||||||
|
try {
|
||||||
|
config = ModernFixEarlyConfig.load(new File("./config/modernfix-mixins.properties"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Could not load configuration file for ModernFix", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info("Loaded configuration file for ModernFix: {} options available, {} override(s) found",
|
||||||
|
config.getOptionCount(), config.getOptionOverrideCount());
|
||||||
|
|
||||||
|
if(ModernFixEarlyConfig.OPTIFINE_PRESENT)
|
||||||
|
this.logger.fatal("OptiFine detected. Use of ModernFix with OptiFine is not supported due to its impact on launch time and breakage of Forge features.");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class.forName("sun.misc.Unsafe").getDeclaredMethod("defineAnonymousClass", Class.class, byte[].class, Object[].class);
|
||||||
|
} catch(ReflectiveOperationException | NullPointerException e) {
|
||||||
|
this.logger.info("Applying Nashorn fix");
|
||||||
|
Properties properties = System.getProperties();
|
||||||
|
properties.setProperty("nashorn.args", properties.getProperty("nashorn.args", "") + " --anonymous-classes=false");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We abuse the constructor of a mixin plugin as a safe location to start modifying the classloader */
|
||||||
|
ModernFixPlatformHooks.injectPlatformSpecificHacks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad(String mixinPackage) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRefMapperConfig() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
|
||||||
|
mixinClassName = ModernFixEarlyConfig.sanitize(mixinClassName);
|
||||||
|
if (!mixinClassName.startsWith(MIXIN_PACKAGE_ROOT)) {
|
||||||
|
this.logger.error("Expected mixin '{}' to start with package root '{}', treating as foreign and " +
|
||||||
|
"disabling!", mixinClassName, MIXIN_PACKAGE_ROOT);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String mixin = mixinClassName.substring(MIXIN_PACKAGE_ROOT.length());
|
||||||
|
if(!instance.isOptionEnabled(mixin))
|
||||||
|
return false;
|
||||||
|
String disabledBecauseMod = instance.config.getPermanentlyDisabledMixins().get(mixin);
|
||||||
|
return disabledBecauseMod == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOptionEnabled(String mixin) {
|
||||||
|
Option option = instance.config.getEffectiveOptionForMixin(mixin);
|
||||||
|
|
||||||
|
if (option == null) {
|
||||||
|
this.logger.error("No rules matched mixin '{}', treating as foreign and disabling!", mixin);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.isOverridden()) {
|
||||||
|
String source = "[unknown]";
|
||||||
|
|
||||||
|
if (option.isUserDefined()) {
|
||||||
|
source = "user configuration";
|
||||||
|
} else if (option.isModDefined()) {
|
||||||
|
source = "mods [" + String.join(", ", option.getDefiningMods()) + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.isEnabled()) {
|
||||||
|
this.logger.warn("Force-enabling mixin '{}' as rule '{}' (added by {}) enables it", mixin,
|
||||||
|
option.getName(), source);
|
||||||
|
} else {
|
||||||
|
this.logger.warn("Force-disabling mixin '{}' as rule '{}' (added by {}) disables it and children", mixin,
|
||||||
|
option.getName(), source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return option.isEnabled();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getMixins() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
|
||||||
|
ModernFixPlatformHooks.applyASMTransformers(mixinClassName, targetClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,350 @@
|
||||||
|
package org.embeddedt.modernfix.core.config;
|
||||||
|
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.gson.*;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.embeddedt.modernfix.ModernFix;
|
||||||
|
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.tree.AnnotationNode;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class ModernFixEarlyConfig {
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger("ModernFixConfig");
|
||||||
|
|
||||||
|
private final Map<String, Option> options = new HashMap<>();
|
||||||
|
|
||||||
|
public static final boolean OPTIFINE_PRESENT;
|
||||||
|
|
||||||
|
private File configFile;
|
||||||
|
|
||||||
|
static {
|
||||||
|
boolean hasOfClass = false;
|
||||||
|
try {
|
||||||
|
Class.forName("optifine.OptiFineTransformationService");
|
||||||
|
hasOfClass = true;
|
||||||
|
} catch(Throwable e) {
|
||||||
|
}
|
||||||
|
OPTIFINE_PRESENT = hasOfClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean modPresent(String modId) {
|
||||||
|
if(modId.equals("optifine"))
|
||||||
|
return OPTIFINE_PRESENT;
|
||||||
|
else
|
||||||
|
return ModernFixPlatformHooks.modPresent(modId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String MIXIN_DESC = "Lorg/spongepowered/asm/mixin/Mixin;";
|
||||||
|
private static final String MIXIN_CLIENT_ONLY_DESC = "Lorg/embeddedt/modernfix/annotation/ClientOnlyMixin;";
|
||||||
|
private static final String MIXIN_REQUIRES_MOD_DESC = "Lorg/embeddedt/modernfix/annotation/RequiresMod;";
|
||||||
|
|
||||||
|
private static final Pattern PLATFORM_PREFIX = Pattern.compile("(forge|fabric|common)\\.");
|
||||||
|
|
||||||
|
public static String sanitize(String mixinClassName) {
|
||||||
|
return PLATFORM_PREFIX.matcher(mixinClassName).replaceFirst("");
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Set<String> mixinOptions = new ObjectOpenHashSet<>();
|
||||||
|
private final Map<String, String> mixinsMissingMods = new Object2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
public Map<String, String> getPermanentlyDisabledMixins() {
|
||||||
|
return mixinsMissingMods;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scanForAndBuildMixinOptions() {
|
||||||
|
List<String> configFiles = ImmutableList.of("modernfix-common.mixins.json", "modernfix-fabric.mixins.json", "modernfix-forge.mixins.json");
|
||||||
|
List<String> mixinPaths = new ArrayList<>();
|
||||||
|
for(String configFile : configFiles) {
|
||||||
|
InputStream stream = ModernFixEarlyConfig.class.getClassLoader().getResourceAsStream(configFile);
|
||||||
|
if(stream == null)
|
||||||
|
continue;
|
||||||
|
try(Reader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
|
||||||
|
JsonObject configObject = (JsonObject)new JsonParser().parse(reader);
|
||||||
|
JsonArray mixinList = configObject.getAsJsonArray("mixins");
|
||||||
|
String packageName = configObject.get("package").getAsString().replace('.', '/');
|
||||||
|
for(JsonElement mixin : mixinList) {
|
||||||
|
mixinPaths.add(packageName + "/" + mixin.getAsString().replace('.', '/') + ".class");
|
||||||
|
}
|
||||||
|
} catch(IOException | JsonParseException e) {
|
||||||
|
LOGGER.error("Error loading config " + configFile, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Splitter dotSplitter = Splitter.on('.');
|
||||||
|
for(String mixinPath : mixinPaths) {
|
||||||
|
try(InputStream stream = ModernFixEarlyConfig.class.getClassLoader().getResourceAsStream(mixinPath)) {
|
||||||
|
ClassReader reader = new ClassReader(stream);
|
||||||
|
ClassNode node = new ClassNode();
|
||||||
|
reader.accept(node, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
|
||||||
|
if(node.invisibleAnnotations == null)
|
||||||
|
return;
|
||||||
|
boolean isMixin = false, isClientOnly = false, requiredModPresent = true;
|
||||||
|
String requiredModId = "";
|
||||||
|
for(AnnotationNode annotation : node.invisibleAnnotations) {
|
||||||
|
if(Objects.equals(annotation.desc, MIXIN_DESC)) {
|
||||||
|
isMixin = true;
|
||||||
|
} else if(Objects.equals(annotation.desc, MIXIN_CLIENT_ONLY_DESC)) {
|
||||||
|
isClientOnly = true;
|
||||||
|
} else if(Objects.equals(annotation.desc, MIXIN_REQUIRES_MOD_DESC)) {
|
||||||
|
for(int i = 0; i < annotation.values.size(); i += 2) {
|
||||||
|
if(annotation.values.get(i).equals("value")) {
|
||||||
|
String modId = (String)annotation.values.get(i + 1);
|
||||||
|
if(modId != null) {
|
||||||
|
requiredModPresent = modPresent(modId);
|
||||||
|
requiredModId = modId;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(isMixin) {
|
||||||
|
String mixinClassName = sanitize(node.name.replace('/', '.')).replace("org.embeddedt.modernfix.mixin.", "");
|
||||||
|
if(!requiredModPresent)
|
||||||
|
mixinsMissingMods.put(mixinClassName, requiredModId);
|
||||||
|
else if(isClientOnly && !ModernFixPlatformHooks.isClient())
|
||||||
|
mixinsMissingMods.put(mixinClassName, "[not client]");
|
||||||
|
List<String> mixinOptionNames = dotSplitter.splitToList(mixinClassName);
|
||||||
|
StringBuilder optionBuilder = new StringBuilder(mixinClassName.length());
|
||||||
|
optionBuilder.append("mixin");
|
||||||
|
for(int i = 0; i < mixinOptionNames.size() - 1; i++) {
|
||||||
|
optionBuilder.append('.');
|
||||||
|
optionBuilder.append(mixinOptionNames.get(i));
|
||||||
|
mixinOptions.add(optionBuilder.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(IOException e) {
|
||||||
|
ModernFix.LOGGER.error("Error scanning file " + mixinPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final boolean isDevEnv = ModernFixPlatformHooks.isDevEnv();
|
||||||
|
|
||||||
|
private static final ImmutableMap<String, Boolean> DEFAULT_SETTING_OVERRIDES = ImmutableMap.<String, Boolean>builder()
|
||||||
|
.put("mixin.perf.dynamic_resources", false)
|
||||||
|
.put("mixin.feature.direct_stack_trace", false)
|
||||||
|
.put("mixin.perf.rewrite_registry", false)
|
||||||
|
.put("mixin.perf.clear_mixin_classinfo", false)
|
||||||
|
.put("mixin.perf.compress_blockstate", false)
|
||||||
|
.put("mixin.bugfix.packet_leak", false)
|
||||||
|
.put("mixin.perf.deduplicate_location", false)
|
||||||
|
.put("mixin.perf.dynamic_entity_renderers", false)
|
||||||
|
.put("mixin.feature.integrated_server_watchdog", true)
|
||||||
|
.put("mixin.perf.faster_item_rendering", false)
|
||||||
|
.put("mixin.devenv", isDevEnv)
|
||||||
|
.put("mixin.perf.remove_spawn_chunks", isDevEnv)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private ModernFixEarlyConfig(File file) {
|
||||||
|
this.configFile = file;
|
||||||
|
|
||||||
|
this.scanForAndBuildMixinOptions();
|
||||||
|
mixinOptions.addAll(DEFAULT_SETTING_OVERRIDES.keySet());
|
||||||
|
for(String optionName : mixinOptions) {
|
||||||
|
boolean defaultEnabled = DEFAULT_SETTING_OVERRIDES.getOrDefault(optionName, true);
|
||||||
|
this.options.putIfAbsent(optionName, new Option(optionName, defaultEnabled, false));
|
||||||
|
}
|
||||||
|
// Defines the default rules which can be configured by the user or other mods.
|
||||||
|
// You must manually add a rule for any new mixins not covered by an existing package rule.
|
||||||
|
this.addMixinRule("launch.class_search_cache", true);
|
||||||
|
/*
|
||||||
|
this.addMixinRule("perf.use_integrated_resources.jepb", modPresent("jepb"));
|
||||||
|
this.addMixinRule("perf.use_integrated_resources.jeresources", modPresent("jeresources"));
|
||||||
|
this.addMixinRule("perf.jeresources_startup", modPresent("jeresources"));
|
||||||
|
this.addMixinRule("perf.state_definition_construct", modPresent("ferritecore"));
|
||||||
|
this.addMixinRule("bugfix.starlight_emptiness", modPresent("starlight"));
|
||||||
|
this.addMixinRule("bugfix.chunk_deadlock.valhesia", modPresent("valhelsia_structures"));
|
||||||
|
this.addMixinRule("bugfix.tf_cme_on_load", modPresent("twilightforest"));
|
||||||
|
this.addMixinRule("bugfix.refinedstorage", modPresent("refinedstorage"));
|
||||||
|
this.addMixinRule("perf.async_jei", modPresent("jei"));
|
||||||
|
this.addMixinRule("perf.patchouli_deduplicate_books", modPresent("patchouli"));
|
||||||
|
this.addMixinRule("perf.kubejs", modPresent("kubejs"));
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Mod compat */
|
||||||
|
disableIfModPresent("mixin.perf.thread_priorities", "smoothboot");
|
||||||
|
disableIfModPresent("mixin.perf.boost_worker_count", "smoothboot");
|
||||||
|
disableIfModPresent("mixin.perf.async_jei", "modernui");
|
||||||
|
disableIfModPresent("mixin.perf.compress_biome_container", "chocolate", "betterendforge");
|
||||||
|
disableIfModPresent("mixin.bugfix.mc218112", "performant");
|
||||||
|
disableIfModPresent("mixin.bugfix.remove_block_chunkloading", "performant");
|
||||||
|
disableIfModPresent("mixin.bugfix.paper_chunk_patches", "c2me");
|
||||||
|
disableIfModPresent("mixin.perf.reuse_datapacks", "tac");
|
||||||
|
disableIfModPresent("mixin.launch.class_search_cache", "optifine");
|
||||||
|
disableIfModPresent("mixin.perf.datapack_reload_exceptions", "cyanide");
|
||||||
|
disableIfModPresent("mixin.perf.faster_texture_loading", "stitch");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableIfModPresent(String configName, String... ids) {
|
||||||
|
for(String id : ids) {
|
||||||
|
if(modPresent(id)) {
|
||||||
|
Option option = this.options.get(configName);
|
||||||
|
if(option != null)
|
||||||
|
option.addModOverride(false, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a Mixin rule which can be configured by users and other mods.
|
||||||
|
* @throws IllegalStateException If a rule with that name already exists
|
||||||
|
* @param mixin The name of the mixin package which will be controlled by this rule
|
||||||
|
* @param enabled True if the rule will be enabled by default, otherwise false
|
||||||
|
*/
|
||||||
|
private void addMixinRule(String mixin, boolean enabled) {
|
||||||
|
String name = getMixinRuleName(mixin);
|
||||||
|
|
||||||
|
if (this.options.putIfAbsent(name, new Option(name, enabled, false)) != null) {
|
||||||
|
throw new IllegalStateException("Mixin rule already defined: " + mixin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readProperties(Properties props) {
|
||||||
|
for (Map.Entry<Object, Object> entry : props.entrySet()) {
|
||||||
|
String key = (String) entry.getKey();
|
||||||
|
String value = (String) entry.getValue();
|
||||||
|
|
||||||
|
Option option = this.options.get(key);
|
||||||
|
|
||||||
|
if (option == null) {
|
||||||
|
LOGGER.warn("No configuration key exists with name '{}', ignoring", key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean enabled;
|
||||||
|
|
||||||
|
if (value.equalsIgnoreCase("true")) {
|
||||||
|
enabled = true;
|
||||||
|
} else if (value.equalsIgnoreCase("false")) {
|
||||||
|
enabled = false;
|
||||||
|
} else {
|
||||||
|
LOGGER.warn("Invalid value '{}' encountered for configuration key '{}', ignoring", value, key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
option.setEnabled(enabled, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the effective option for the specified class name. This traverses the package path of the given mixin
|
||||||
|
* and checks each root for configuration rules. If a configuration rule disables a package, all mixins located in
|
||||||
|
* that package and its children will be disabled. The effective option is that of the highest-priority rule, either
|
||||||
|
* a enable rule at the end of the chain or a disable rule at the earliest point in the chain.
|
||||||
|
*
|
||||||
|
* @return Null if no options matched the given mixin name, otherwise the effective option for this Mixin
|
||||||
|
*/
|
||||||
|
public Option getEffectiveOptionForMixin(String mixinClassName) {
|
||||||
|
int lastSplit = 0;
|
||||||
|
int nextSplit;
|
||||||
|
|
||||||
|
Option rule = null;
|
||||||
|
|
||||||
|
while ((nextSplit = mixinClassName.indexOf('.', lastSplit)) != -1) {
|
||||||
|
String key = getMixinRuleName(mixinClassName.substring(0, nextSplit));
|
||||||
|
|
||||||
|
Option candidate = this.options.get(key);
|
||||||
|
|
||||||
|
if (candidate != null) {
|
||||||
|
rule = candidate;
|
||||||
|
|
||||||
|
if (!rule.isEnabled()) {
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastSplit = nextSplit + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the configuration file from the specified location. If it does not exist, a new configuration file will be
|
||||||
|
* created. The file on disk will then be updated to include any new options.
|
||||||
|
*/
|
||||||
|
public static ModernFixEarlyConfig load(File file) {
|
||||||
|
ModernFixEarlyConfig config = new ModernFixEarlyConfig(file);
|
||||||
|
Properties props = new Properties();
|
||||||
|
if(file.exists()) {
|
||||||
|
try (FileInputStream fin = new FileInputStream(file)){
|
||||||
|
props.load(fin);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Could not load config file", e);
|
||||||
|
}
|
||||||
|
config.readProperties(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
config.save();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.warn("Could not write configuration file", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() throws IOException {
|
||||||
|
File dir = configFile.getParentFile();
|
||||||
|
|
||||||
|
if (!dir.exists()) {
|
||||||
|
if (!dir.mkdirs()) {
|
||||||
|
throw new IOException("Could not create parent directories");
|
||||||
|
}
|
||||||
|
} else if (!dir.isDirectory()) {
|
||||||
|
throw new IOException("The parent file is not a directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Writer writer = new FileWriter(configFile)) {
|
||||||
|
writer.write("# This is the configuration file for ModernFix.\n");
|
||||||
|
writer.write("#\n");
|
||||||
|
writer.write("# The following options can be enabled or disabled if there is a compatibility issue.\n");
|
||||||
|
writer.write("# Add a line mixin.example_name=true/false without the # sign to enable/disable a rule.\n");
|
||||||
|
List<String> keys = this.options.keySet().stream()
|
||||||
|
.filter(key -> !key.equals("mixin.core"))
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
for(String line : keys) {
|
||||||
|
if(!line.equals("mixin.core"))
|
||||||
|
writer.write("# " + line + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String key : keys) {
|
||||||
|
Option option = this.options.get(key);
|
||||||
|
if(option.isUserDefined())
|
||||||
|
writer.write(key + "=" + option.isEnabled() + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getMixinRuleName(String name) {
|
||||||
|
return "mixin." + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOptionCount() {
|
||||||
|
return this.options.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOptionOverrideCount() {
|
||||||
|
return (int) this.options.values()
|
||||||
|
.stream()
|
||||||
|
.filter(Option::isOverridden)
|
||||||
|
.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Option> getOptionMap() {
|
||||||
|
return Collections.unmodifiableMap(this.options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,6 @@ public class Option {
|
||||||
private Set<String> modDefined = null;
|
private Set<String> modDefined = null;
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private boolean userDefined;
|
private boolean userDefined;
|
||||||
private Option parent = null;
|
|
||||||
|
|
||||||
public Option(String name, boolean enabled, boolean userDefined) {
|
public Option(String name, boolean enabled, boolean userDefined) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
@ -38,33 +37,10 @@ public class Option {
|
||||||
this.modDefined.add(modId);
|
this.modDefined.add(modId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setParent(Option option) {
|
|
||||||
this.parent = option;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Option getParent() {
|
|
||||||
return this.parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getDepth() {
|
|
||||||
if(this.parent == null)
|
|
||||||
return 0;
|
|
||||||
else
|
|
||||||
return this.parent.getDepth() + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return this.enabled;
|
return this.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this option will effectively be disabled (regardless of its own status)
|
|
||||||
* by the parent rule being disabled.
|
|
||||||
*/
|
|
||||||
public boolean isEffectivelyDisabledByParent() {
|
|
||||||
return this.parent != null && (!this.parent.enabled || this.parent.isEffectivelyDisabledByParent());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOverridden() {
|
public boolean isOverridden() {
|
||||||
return this.isUserDefined() || this.isModDefined();
|
return this.isUserDefined() || this.isModDefined();
|
||||||
}
|
}
|
||||||
|
|
@ -81,13 +57,6 @@ public class Option {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSelfName() {
|
|
||||||
if(this.parent == null)
|
|
||||||
return this.name;
|
|
||||||
else
|
|
||||||
return this.name.substring(this.parent.getName().length() + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearModsDefiningValue() {
|
public void clearModsDefiningValue() {
|
||||||
this.modDefined = null;
|
this.modDefined = null;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.embeddedt.modernfix.dedup;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.Hash;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class DeduplicationCache<T> {
|
||||||
|
private final ObjectOpenCustomHashSet<T> pool;
|
||||||
|
|
||||||
|
private int attemptedInsertions = 0;
|
||||||
|
private int deduplicated = 0;
|
||||||
|
|
||||||
|
public DeduplicationCache(Hash.Strategy<T> strategy) {
|
||||||
|
this.pool = new ObjectOpenCustomHashSet<>(strategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeduplicationCache() {
|
||||||
|
this.pool = new ObjectOpenCustomHashSet<>(new Hash.Strategy<T>() {
|
||||||
|
@Override
|
||||||
|
public int hashCode(T o) {
|
||||||
|
return Objects.hashCode(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(T a, T b) {
|
||||||
|
return Objects.equals(a, b);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized T deduplicate(T item) {
|
||||||
|
this.attemptedInsertions++;
|
||||||
|
|
||||||
|
T result = this.pool.addOrGet(item);
|
||||||
|
|
||||||
|
if (result != item) {
|
||||||
|
this.deduplicated++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void clearCache() {
|
||||||
|
this.attemptedInsertions = 0;
|
||||||
|
this.deduplicated = 0;
|
||||||
|
|
||||||
|
this.pool.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized String toString() {
|
||||||
|
return String.format("DeduplicationCache ( %d/%d de-duplicated, %d pooled )",
|
||||||
|
this.deduplicated, this.attemptedInsertions, this.pool.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.embeddedt.modernfix.dedup;
|
||||||
|
|
||||||
|
|
||||||
|
import org.embeddedt.modernfix.ModernFix;
|
||||||
|
|
||||||
|
public class IdentifierCaches {
|
||||||
|
public static final DeduplicationCache<String> NAMESPACES = new DeduplicationCache<>();
|
||||||
|
public static final DeduplicationCache<String> PATH = new DeduplicationCache<>();
|
||||||
|
public static final DeduplicationCache<String> PROPERTY = new DeduplicationCache<>();
|
||||||
|
|
||||||
|
public static void printDebug() {
|
||||||
|
ModernFix.LOGGER.info("[[[ Identifier de-duplication statistics ]]]");
|
||||||
|
ModernFix.LOGGER.info("Namespace cache: {}", NAMESPACES);
|
||||||
|
ModernFix.LOGGER.info("Path cache: {}", PATH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package org.embeddedt.modernfix.dfu;
|
||||||
|
|
||||||
|
import com.google.common.cache.Cache;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.mojang.datafixers.RewriteResult;
|
||||||
|
import com.mojang.datafixers.TypeRewriteRule;
|
||||||
|
import com.mojang.datafixers.functions.PointFreeRule;
|
||||||
|
import com.mojang.datafixers.types.Type;
|
||||||
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import org.apache.commons.lang3.tuple.Triple;
|
||||||
|
import org.embeddedt.modernfix.ModernFix;
|
||||||
|
import sun.misc.Unsafe;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.IntFunction;
|
||||||
|
|
||||||
|
public class DFUBlaster {
|
||||||
|
private static final Cache<Pair<IntFunction<RewriteResult<?, ?>>, Integer>, RewriteResult<?, ?>> hmapApplyCache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(3, TimeUnit.MINUTES)
|
||||||
|
.build();
|
||||||
|
private static final Cache<Triple<Type<?>, TypeRewriteRule, PointFreeRule>, Optional<? extends RewriteResult<?, ?>>> rewriteCache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(3, TimeUnit.MINUTES)
|
||||||
|
.build();
|
||||||
|
public static void blastMaps() {
|
||||||
|
try {
|
||||||
|
Class<?> FOLD_CLASS = Class.forName("com.mojang.datafixers.functions.Fold");
|
||||||
|
Field hmapField = FOLD_CLASS.getDeclaredField("HMAP_APPLY_CACHE");
|
||||||
|
hmapField.setAccessible(true);
|
||||||
|
final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
|
||||||
|
theUnsafe.setAccessible(true);
|
||||||
|
Unsafe unsafe = (Unsafe)theUnsafe.get(null);
|
||||||
|
Object base = unsafe.staticFieldBase(hmapField);
|
||||||
|
long offset = unsafe.staticFieldOffset(hmapField);
|
||||||
|
unsafe.putObject(base, offset, hmapApplyCache.asMap());
|
||||||
|
Field rewriteCacheField = Type.class.getDeclaredField("REWRITE_CACHE");
|
||||||
|
rewriteCacheField.setAccessible(true);
|
||||||
|
base = unsafe.staticFieldBase(rewriteCacheField);
|
||||||
|
offset = unsafe.staticFieldOffset(rewriteCacheField);
|
||||||
|
unsafe.putObject(base, offset, rewriteCache.asMap());
|
||||||
|
new CleanerThread().start();
|
||||||
|
} catch(Throwable e) {
|
||||||
|
ModernFix.LOGGER.error("Could not replace DFU map", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CleanerThread extends Thread {
|
||||||
|
CleanerThread() {
|
||||||
|
this.setName("DFU cleaning thread");
|
||||||
|
this.setPriority(1);
|
||||||
|
this.setDaemon(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while(true) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(15000);
|
||||||
|
} catch(InterruptedException e){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rewriteCache.cleanUp();
|
||||||
|
hmapApplyCache.cleanUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
package org.embeddedt.modernfix.dfu;
|
||||||
|
|
||||||
|
import com.mojang.datafixers.DSL;
|
||||||
|
import com.mojang.datafixers.DataFixUtils;
|
||||||
|
import com.mojang.datafixers.DataFixer;
|
||||||
|
import com.mojang.datafixers.schemas.Schema;
|
||||||
|
import com.mojang.datafixers.types.Type;
|
||||||
|
import com.mojang.datafixers.types.constant.EmptyPart;
|
||||||
|
import com.mojang.datafixers.types.templates.TypeTemplate;
|
||||||
|
import com.mojang.serialization.Dynamic;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
|
import net.minecraft.SharedConstants;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class LazyDataFixer implements DataFixer {
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger("ModernFix");
|
||||||
|
private DataFixer backingDataFixer;
|
||||||
|
private final Supplier<DataFixer> dfuSupplier;
|
||||||
|
private static final Schema FAKE_SCHEMA = new EmptySchema();
|
||||||
|
|
||||||
|
public LazyDataFixer(Supplier<DataFixer> dfuSupplier) {
|
||||||
|
LOGGER.info("Bypassed Mojang DFU");
|
||||||
|
this.backingDataFixer = null;
|
||||||
|
this.dfuSupplier = dfuSupplier;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public <T> Dynamic<T> update(DSL.TypeReference type, Dynamic<T> input, int version, int newVersion) {
|
||||||
|
if(version >= newVersion)
|
||||||
|
return input;
|
||||||
|
synchronized (this) {
|
||||||
|
if(backingDataFixer == null) {
|
||||||
|
LOGGER.info("Instantiating Mojang DFU");
|
||||||
|
DFUBlaster.blastMaps();
|
||||||
|
backingDataFixer = dfuSupplier.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return backingDataFixer.update(type, input, version, newVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "getSchema is only there for checks that are not important" - fry, 2021
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Schema getSchema(int key) {
|
||||||
|
return FAKE_SCHEMA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty schema that also returns empty Type<?> instances to prevent crashes.
|
||||||
|
*/
|
||||||
|
static class EmptySchema extends Schema {
|
||||||
|
public EmptySchema() {
|
||||||
|
super(DataFixUtils.makeKey(SharedConstants.getCurrentVersion().getWorldVersion()), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Type<?> EMPTY_TYPE = new EmptyPart();
|
||||||
|
private static final TypeTemplate FAKE_TEMPLATE = EMPTY_TYPE.template();
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<String, Type<?>> buildTypes() {
|
||||||
|
Object2ObjectOpenHashMap<String, Type<?>> map = new Object2ObjectOpenHashMap<>();
|
||||||
|
map.defaultReturnValue(new EmptyPart());
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TypeTemplate resolveTemplate(String name) {
|
||||||
|
return FAKE_TEMPLATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type<?> getChoiceType(DSL.TypeReference type, String choiceName) {
|
||||||
|
return EMPTY_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerTypes(Schema schema, Map<String, Supplier<TypeTemplate>> entityTypes, Map<String, Supplier<TypeTemplate>> blockEntityTypes) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Supplier<TypeTemplate>> registerEntities(Schema schema) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Supplier<TypeTemplate>> registerBlockEntities(Schema schema) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,5 +3,4 @@ package org.embeddedt.modernfix.duck;
|
||||||
|
|
||||||
public interface IBlockState {
|
public interface IBlockState {
|
||||||
void clearCache();
|
void clearCache();
|
||||||
boolean isCacheInvalid();
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package org.embeddedt.modernfix.duck;
|
||||||
|
|
||||||
|
public interface ICachedMaterialsModel {
|
||||||
|
public void clearMaterialsCache();
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user