Compare commits
108 Commits
5.26.0+1.2
...
1.20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
292a6aeab3 | ||
|
|
7fbfcf1a92 | ||
|
|
1bcb28a1ad | ||
|
|
d51b0f60a2 | ||
|
|
ab9880159e | ||
|
|
0f94634361 | ||
|
|
f1492cc829 | ||
|
|
0ecee529d7 | ||
|
|
e9bfd96dd9 | ||
|
|
fb9dcf77c6 | ||
|
|
33851c1cb6 | ||
|
|
494203ef5a | ||
|
|
74f76f7305 | ||
|
|
62dbbea083 | ||
|
|
538c52bc2a | ||
|
|
b62eb1845b | ||
|
|
7c45564979 | ||
|
|
f8d2425242 | ||
|
|
50cedfc699 | ||
|
|
f4f596ca0c | ||
|
|
85aab426c5 | ||
|
|
29ff5f152e | ||
|
|
8213a720a3 | ||
|
|
afe3e09a27 | ||
|
|
ae20fa17c9 | ||
|
|
a6c03e9928 | ||
|
|
864c751aea | ||
|
|
f931d5c442 | ||
|
|
55cec86e5f | ||
|
|
4ec8ef753a | ||
|
|
3f22e23565 | ||
|
|
a73dd5ef6a | ||
|
|
653a477060 | ||
|
|
44113d2536 | ||
|
|
1165d3bdd1 | ||
|
|
c73cdc49a4 | ||
|
|
4e3ecf9b6d | ||
|
|
a40363c1fb | ||
|
|
46dd5ecddd | ||
|
|
b765bcb51f | ||
|
|
26bd7116a1 | ||
|
|
4d2f0da1fc | ||
|
|
c2f585da95 | ||
|
|
327c3cd9ff | ||
|
|
c64ca2e54b | ||
|
|
85955ebf75 | ||
|
|
d749205427 | ||
|
|
438ceb1984 | ||
|
|
5acb5115b9 | ||
|
|
37dc9e60eb | ||
|
|
c2191df359 | ||
|
|
d08da1b3c8 | ||
|
|
36f425b8cd | ||
|
|
dc3c379049 | ||
|
|
4ff7d4c554 | ||
|
|
db13f39b30 | ||
|
|
5a9c49f8d4 | ||
|
|
8ee85f2c16 | ||
|
|
2081b63b56 | ||
|
|
94f1fbf4db | ||
|
|
ab8a8068e0 | ||
|
|
79d2b28d5b | ||
|
|
18dc488ab9 | ||
|
|
a9340b2642 | ||
|
|
670e06816b | ||
|
|
53349cbd1a | ||
|
|
1794c81b61 | ||
|
|
dbe9acb3d8 | ||
|
|
22915a91a1 | ||
|
|
1289897004 | ||
|
|
9692da12b4 | ||
|
|
e34a99b38c | ||
|
|
f79eae8b83 | ||
|
|
38288d5e6a | ||
|
|
2050516bf1 | ||
|
|
02f486ebf4 | ||
|
|
9edce9ad91 | ||
|
|
ac8d93d5b9 | ||
|
|
bee4536c1a | ||
|
|
da2206168b | ||
|
|
17f930ea6f | ||
|
|
f23348c6cb | ||
|
|
21cbcb0e04 | ||
|
|
925c7526ee | ||
|
|
30e3deb8e2 | ||
|
|
ee34dcf96e | ||
|
|
49d800ff27 | ||
|
|
15f30b532c | ||
|
|
df06010846 | ||
|
|
696b344ef5 | ||
|
|
e63d99763e | ||
|
|
60850610f9 | ||
|
|
e16179b797 | ||
|
|
784b914a43 | ||
|
|
b9933b1158 | ||
|
|
878b3798f3 | ||
|
|
bc0e9a09fc | ||
|
|
8c34c0de50 | ||
|
|
5a93bc6109 | ||
|
|
8125da7882 | ||
|
|
d699187006 | ||
|
|
cff29149db | ||
|
|
3926f27d33 | ||
|
|
9bc5f06a19 | ||
|
|
a70f76a34d | ||
|
|
4dcdf09a01 | ||
|
|
f26d35070e | ||
|
|
a04266df54 |
82
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
82
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -4,51 +4,75 @@ body:
|
|||
- type: markdown
|
||||
attributes:
|
||||
value: >-
|
||||
**Note: This issue tracker is not intended for support requests!** If you need help with crashes or other issues, then
|
||||
you should [ask on our Discord server](https://discord.gg/rN9Y7caguP) instead. Unless you are certain that you
|
||||
have found a defect, and you are able to point to where the problem is, you should not open an issue.
|
||||
<br><br>
|
||||
Additionally, please make sure you have done the following:
|
||||
**Need help?** Ask on [Discord](https://discord.gg/rN9Y7caguP) instead of opening an issue.
|
||||
|
||||
- **Have you ensured that all of your mods (including ModernFix) are up-to-date?** The latest version of ModernFix
|
||||
can always be found [on Modrinth](https://modrinth.com/mod/modernfix).
|
||||
|
||||
- **Have you used the [search tool](https://github.com/embeddedt/ModernFix/issues) to check whether your issue
|
||||
has already been reported?** If it has been, then consider adding more information to the existing issue instead.
|
||||
|
||||
- **Have you determined the minimum set of instructions to reproduce the issue?** If your problem only occurs
|
||||
with other mods installed, then you should narrow down exactly which mods are causing the issue. Please do not
|
||||
provide your entire list of mods to us and expect that we will be able to figure out the problem.
|
||||
**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: >-
|
||||
Use this section to describe the issue you are experiencing in as much depth as possible. The description should
|
||||
explain what behavior you were expecting, and why you believe the issue to be a bug. If the issue you are reporting
|
||||
only occurs with specific mods installed, then provide the name and version of each mod.
|
||||
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.
|
||||
|
||||
**Hint:** If you have any screenshots, videos, or other information that you feel is necessary to
|
||||
explain the issue, you can attach them here.
|
||||
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 as much information as possible on how to reproduce this bug. Make sure your instructions are as clear and
|
||||
concise as possible, because other people will need to be able to follow your guide in order to re-create the issue.
|
||||
|
||||
**Hint:** A common way to fill this section out is to write a step-by-step guide.
|
||||
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: log-file
|
||||
id: diagnostic-info
|
||||
attributes:
|
||||
label: Log File
|
||||
label: Diagnostic Info
|
||||
description: >-
|
||||
**Hint:** You can usually find the log files within the folder `.minecraft/logs`. Most often, you will want the `latest.log`
|
||||
file, since that file belongs to the last played session of the game.
|
||||
placeholder: >-
|
||||
Drag-and-drop the log file here.
|
||||
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
|
||||
|
|
|
|||
102
.github/workflows/gradle.yml
vendored
102
.github/workflows/gradle.yml
vendored
|
|
@ -11,6 +11,11 @@ on:
|
|||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
issues: write
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
|
@ -22,13 +27,108 @@ jobs:
|
|||
distribution: 'temurin'
|
||||
java-version: 21
|
||||
check-latest: true
|
||||
- name: Check if release branch
|
||||
id: check_branch
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
if [[ "${{ github.ref }}" =~ ^refs/heads/[0-9]+\. ]]; then
|
||||
echo "is_release=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "is_release=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/1.') }}
|
||||
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:
|
||||
|
|
|
|||
41
.github/workflows/release.yml
vendored
41
.github/workflows/release.yml
vendored
|
|
@ -1,41 +0,0 @@
|
|||
name: Release ModernFix Artifacts
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: github.repository_owner == 'embeddedt'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 21
|
||||
check-latest: true
|
||||
- name: Remove tags for release on other versions
|
||||
run: ./scripts/tagcleaner.sh
|
||||
- name: Build and publish mod to CurseForge & Modrinth
|
||||
run: ./gradlew publishMods copyJarToBin
|
||||
env:
|
||||
CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}
|
||||
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
|
||||
- name: Upload assets to GitHub
|
||||
uses: AButler/upload-release-assets@v3.0
|
||||
with:
|
||||
files: 'bin/*'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Add changelog to release
|
||||
uses: irongut/EditRelease@v1.2.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
id: ${{ github.event.release.id }}
|
||||
replacebody: true
|
||||
files: "CHANGELOG.md"
|
||||
|
|
@ -30,7 +30,7 @@ dependencies {
|
|||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.release = 21
|
||||
options.release = 17
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
|
|
|
|||
|
|
@ -90,24 +90,19 @@ public class ClientMixinValidator {
|
|||
}
|
||||
|
||||
private boolean targetsClient(Object classTarget) {
|
||||
return switch (classTarget) {
|
||||
case TypeElement te ->
|
||||
isClientMarked(te);
|
||||
case TypeMirror tm -> {
|
||||
var el = types.asElement(tm);
|
||||
yield el != null ? targetsClient(el) : warn("TypeMirror of " + tm);
|
||||
}
|
||||
// If you're using a dollar sign in class names you are insane
|
||||
case String s -> {
|
||||
var te =
|
||||
elemUtils.getTypeElement(toSourceString(s.split("\\$")[0]));
|
||||
yield te != null ? targetsClient(te) : warn(s);
|
||||
}
|
||||
default ->
|
||||
throw new IllegalArgumentException("Unhandled type: "
|
||||
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) {
|
||||
|
|
@ -166,7 +161,6 @@ public class ClientMixinValidator {
|
|||
|
||||
clzsses = wrappedClzss.stream()
|
||||
.map(AnnotationValue::getValue)
|
||||
.filter(o -> o instanceof TypeMirror)
|
||||
.map(TypeMirror.class::cast)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public record MixinConfig(
|
|||
InjectorOptions injectors, OverwriteOptions overwrites
|
||||
) {
|
||||
public MixinConfig(String packageName, List<String> commonMixins, List<String> clientMixins) {
|
||||
this(true, "0.8", packageName, "org.embeddedt.modernfix.core.ModernFixMixinPlugin", "JAVA_21",
|
||||
this(true, "0.8", packageName, "org.embeddedt.modernfix.core.ModernFixMixinPlugin", "JAVA_17",
|
||||
commonMixins, clientMixins, InjectorOptions.DEFAULT, OverwriteOptions.DEFAULT);
|
||||
}
|
||||
public record InjectorOptions(int defaultRequire) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package org.embeddedt.modernfix.annotation;
|
||||
|
||||
public enum FeatureLevel {
|
||||
GA, BETA;
|
||||
|
||||
public boolean isAtLeast(FeatureLevel required) {
|
||||
return this.ordinal() >= required.ordinal();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Target(ElementType.TYPE)
|
||||
@Target({ElementType.TYPE, ElementType.PACKAGE})
|
||||
public @interface RequiresMod {
|
||||
String value() default "";
|
||||
}
|
||||
|
|
|
|||
102
build.gradle.kts
102
build.gradle.kts
|
|
@ -1,7 +1,5 @@
|
|||
plugins {
|
||||
id("net.neoforged.moddev") version("2.0.134")
|
||||
id("org.ajoberstar.grgit") version("5.2.0")
|
||||
id("com.palantir.git-version") version("1.0.0")
|
||||
id("net.neoforged.moddev.legacyforge") version("2.0.134")
|
||||
id("me.modmuss50.mod-publish-plugin") version("1.1.0")
|
||||
}
|
||||
|
||||
|
|
@ -9,48 +7,20 @@ val minecraft_version = rootProject.properties["minecraft_version"].toString()
|
|||
|
||||
group = "org.embeddedt"
|
||||
|
||||
val versionDetails: groovy.lang.Closure<com.palantir.gradle.gitversion.VersionDetails> by extra
|
||||
// extract base version from tag, generate other metadata ourselves
|
||||
val details = versionDetails()
|
||||
|
||||
var plusIndex = details.lastTag.indexOf("+")
|
||||
if (plusIndex == -1) {
|
||||
plusIndex = details.lastTag.length
|
||||
val gitVersion = providers.of(GitVersionSource::class) {
|
||||
parameters {
|
||||
minecraftVersion.set(minecraft_version)
|
||||
projectDir.set(rootProject.layout.projectDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
var baseVersion = details.lastTag.substring(0, plusIndex)
|
||||
version = gitVersion.get()
|
||||
|
||||
val dirtyMarker = if (grgit.status().isClean) "" else ".dirty"
|
||||
base.archivesName = "modernfix-forge"
|
||||
|
||||
val commitHashMarker =
|
||||
if (details.commitDistance > 0)
|
||||
"." + details.gitHash.substring(0, minOf(4, details.gitHash.length))
|
||||
else
|
||||
""
|
||||
|
||||
var preMarker =
|
||||
if (details.commitDistance > 0 || !details.isCleanTag)
|
||||
"-beta.${details.commitDistance}"
|
||||
else
|
||||
""
|
||||
|
||||
if (preMarker.isNotEmpty()) {
|
||||
// bump to next patch release
|
||||
val versionParts = baseVersion.split(".")
|
||||
baseVersion =
|
||||
"${versionParts[0]}.${versionParts[1]}.${versionParts[2].toInt() + 1}"
|
||||
}
|
||||
|
||||
val versionString =
|
||||
"${baseVersion}${preMarker}+mc${minecraft_version}${commitHashMarker}${dirtyMarker}"
|
||||
|
||||
version = versionString
|
||||
|
||||
base.archivesName = "modernfix-neoforge"
|
||||
|
||||
neoForge {
|
||||
legacyForge {
|
||||
enable {
|
||||
version = rootProject.properties["forge_version"].toString()
|
||||
forgeVersion = rootProject.properties["forge_version"].toString()
|
||||
isDisableRecompilation = System.getenv("CI") == "true"
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +38,10 @@ neoForge {
|
|||
create("server") {
|
||||
server()
|
||||
}
|
||||
create("auditClient") {
|
||||
client()
|
||||
jvmArguments.addAll("-Dmodernfix.auditAndExit=true", "-Djava.awt.headless=true")
|
||||
}
|
||||
}
|
||||
|
||||
mods {
|
||||
|
|
@ -77,21 +51,22 @@ neoForge {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
))
|
||||
}
|
||||
|
||||
// We must force the Java 21 compiler to be used because our AP requires Java 21
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
val curSourceCompatLevel = JavaVersion.VERSION_21
|
||||
val curSourceCompatLevel = JavaVersion.VERSION_17
|
||||
sourceCompatibility = curSourceCompatLevel
|
||||
targetCompatibility = curSourceCompatLevel
|
||||
}
|
||||
|
|
@ -137,16 +112,24 @@ dependencies {
|
|||
"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()
|
||||
compileOnly("mezz.jei:jei-${minecraft_version}-neoforge:${jei_version}")
|
||||
compileOnly("curse.maven:spark-361579:${rootProject.properties["spark_version"].toString()}")
|
||||
compileOnly("curse.maven:ctm-267602:${rootProject.properties["ctm_version"].toString()}")
|
||||
compileOnly("curse.maven:ldlib-626676:${rootProject.properties["ldlib_version"].toString()}")
|
||||
compileOnly("curse.maven:supermartijncore-454372:4455391")
|
||||
compileOnly("curse.maven:patchouli-306770:6164575")
|
||||
compileOnly("curse.maven:cofhcore-69162:5374122")
|
||||
compileOnly("curse.maven:resourcefullib-570073:5659871")
|
||||
compileOnly("curse.maven:kubejs-238086:5853326")
|
||||
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") {
|
||||
|
|
@ -178,12 +161,12 @@ tasks.named<ProcessResources>("processResources") {
|
|||
|
||||
inputs.property("version", project.version)
|
||||
|
||||
filesMatching("META-INF/neoforge.mods.toml") {
|
||||
filesMatching("META-INF/mods.toml") {
|
||||
expand("version" to project.version)
|
||||
}
|
||||
}
|
||||
|
||||
val finalJarTask = "jar"
|
||||
val finalJarTask = "reobfJar"
|
||||
|
||||
tasks.register<Copy>("copyJarNameConsistent") {
|
||||
from(tasks.named<Jar>(finalJarTask).get().outputs.files)
|
||||
|
|
@ -203,10 +186,11 @@ tasks.named("build") {
|
|||
|
||||
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("neoforge")
|
||||
modLoaders.add("forge")
|
||||
|
||||
curseforge {
|
||||
projectId = "790626"
|
||||
|
|
@ -215,7 +199,7 @@ publishMods {
|
|||
minecraftVersions.add(minecraft_version)
|
||||
}
|
||||
modrinth {
|
||||
projectId = "modernfix"
|
||||
projectId = "nmDcB62a"
|
||||
accessToken = providers.environmentVariable("MODRINTH_TOKEN")
|
||||
minecraftVersions.add(minecraft_version)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
plugins {
|
||||
id "architectury-plugin" version "3.4-SNAPSHOT"
|
||||
id "dev.architectury.loom" version "1.9-SNAPSHOT" apply false
|
||||
id "maven-publish"
|
||||
id 'com.matthewprenger.cursegradle' version '1.4.0' apply false
|
||||
id 'com.palantir.git-version' version '1.0.0'
|
||||
id 'org.ajoberstar.grgit' version '5.2.0'
|
||||
id 'se.bjurr.gitchangelog.git-changelog-gradle-plugin' version '1.79.0'
|
||||
id "com.modrinth.minotaur" version "2.+" apply false
|
||||
id("com.diffplug.spotless") version "6.25.0" apply false
|
||||
id 'modernfix.common-conventions' apply false
|
||||
}
|
||||
|
||||
architectury {
|
||||
minecraft = rootProject.minecraft_version
|
||||
}
|
||||
|
||||
ext.archives_base_name = 'modernfix'
|
||||
|
||||
apply plugin: 'modernfix.common-conventions'
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
// ensure that the encoding is set to UTF-8, no matter what the system default is
|
||||
// this fixes some edge cases with special characters not displaying correctly
|
||||
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
|
||||
// If Javadoc is generated, this must be specified in that task too.
|
||||
options.encoding = "UTF-8"
|
||||
|
||||
// The Minecraft launcher currently installs Java 8 for users, so your mod probably wants to target Java 8 too
|
||||
// JDK 9 introduced a new way of specifying this that will make sure no newer classes or methods are used.
|
||||
// We'll use that if it's available, but otherwise we'll use the older option.
|
||||
def targetVersion = 8
|
||||
/*
|
||||
if (JavaVersion.current().isJava9Compatible()) {
|
||||
options.release = targetVersion
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
tasks.register('generateChangelog', se.bjurr.gitchangelog.plugin.gradle.GitChangelogTask) {
|
||||
def details = versionDetails();
|
||||
def theVersionRef
|
||||
if (details.commitDistance > 0) {
|
||||
theVersionRef = details.lastTag;
|
||||
} else {
|
||||
def secondLastTagCmd = "git describe --abbrev=0 " + details.lastTag + "^"
|
||||
def secondLastTag = secondLastTagCmd.execute().text.trim()
|
||||
theVersionRef = secondLastTag;
|
||||
}
|
||||
|
||||
fromRef = theVersionRef
|
||||
|
||||
file = new File("${rootDir}/CHANGELOG.md");
|
||||
templateContent = new File("${rootDir}/gradle/changelog.mustache").getText('UTF-8').replace("[[modernFixVersionRef]]", theVersionRef);
|
||||
toCommit = "HEAD";
|
||||
}
|
||||
|
||||
tasks.register('checkCleanTag') {
|
||||
doLast {
|
||||
def details = versionDetails()
|
||||
if (!details.isCleanTag || versionDetails().commitDistance != 0) {
|
||||
throw new GradleException('Not a clean tree.')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println "ModernFix: " + version
|
||||
7
buildSrc/build.gradle.kts
Normal file
7
buildSrc/build.gradle.kts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
61
buildSrc/src/main/kotlin/GitVersionSource.kt
Normal file
61
buildSrc/src/main/kotlin/GitVersionSource.kt
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,29 +5,28 @@ junit_version=5.10.0-M1
|
|||
mixinextras_version=0.4.1
|
||||
|
||||
mod_id=modernfix
|
||||
minecraft_version=1.21.1
|
||||
enabled_platforms=neoforge
|
||||
forge_version=21.1.111
|
||||
parchment_version=2024.11.17
|
||||
parchment_mc_version=1.21.1
|
||||
minecraft_version=1.20.1
|
||||
enabled_platforms=forge
|
||||
forge_version=1.20.1-47.4.0
|
||||
parchment_version=2023.07.09
|
||||
refined_storage_version=4392788
|
||||
jei_version=19.21.2.313
|
||||
rei_version=13.0.678
|
||||
ctm_version=5587515
|
||||
ldlib_version=5782845
|
||||
kubejs_version=2101.7.1-build.181
|
||||
rhino_version=2101.2.7-build.74
|
||||
supported_minecraft_versions=1.21.1
|
||||
jei_version=15.8.0.11
|
||||
rei_version=11.0.597
|
||||
ctm_version=5983309
|
||||
ldlib_version=5927130
|
||||
kubejs_version=2001.6.5-build.16
|
||||
rhino_version=2001.2.3-build.10
|
||||
supported_minecraft_versions=1.20.1
|
||||
|
||||
fabric_loader_version=0.16.10
|
||||
fabric_api_version=0.102.1+1.21.1
|
||||
fabric_api_version=0.86.0+1.20.1
|
||||
|
||||
continuity_version=3.0.0-beta.4+1.20.2
|
||||
continuity_version=3.0.0-beta.2+1.19.3
|
||||
|
||||
modmenu_version=11.0.3
|
||||
modmenu_version=7.0.0-beta.2
|
||||
diagonal_fences_version=4558828
|
||||
|
||||
spark_version=6225208
|
||||
spark_version=4587310
|
||||
|
||||
use_fabric_api_at_runtime=true
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
loom.platform=neoforge
|
||||
1
release_line.txt
Normal file
1
release_line.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
5.27
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
#!/bin/bash
|
||||
git ls-remote --heads origin | awk '{print $2}' | grep -E '^refs/heads/1\.' | sed 's:.*/::' | sort -V | grep -E '^1\.[0-9]*(\.[0-9]*)?$'
|
||||
git ls-remote --heads origin | awk '{print $2}' | grep -E '^refs/heads/[0-9]+\.' | sed 's:.*/::' | sort -V | grep -E '^[0-9]+\.[0-9]*(\.[0-9]*)?$'
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import re
|
|||
def get_valid_mixin_options():
|
||||
all_mixin_options = set()
|
||||
# gather all mixins in mixin folders
|
||||
for platform in [ "common", "fabric", "forge" ]:
|
||||
for platform in [ "common", "forge" ]:
|
||||
base_path = f"{platform}/src/main/java/org/embeddedt/modernfix/{platform}/mixin"
|
||||
for root, dirs, files in os.walk(base_path):
|
||||
for file in files:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package org.embeddedt.modernfix;
|
|||
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
|
|
@ -12,6 +13,7 @@ import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
|
|||
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||
import org.embeddedt.modernfix.resources.ReloadExecutor;
|
||||
import org.embeddedt.modernfix.util.ClassInfoManager;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
|
@ -45,6 +47,17 @@ public class ModernFix {
|
|||
return resourceReloadService;
|
||||
}
|
||||
|
||||
public static void runAuditIfRequested() {
|
||||
boolean auditAndExit = Boolean.getBoolean("modernfix.auditAndExit");
|
||||
if (auditAndExit || Boolean.getBoolean("modernfix.auditMixinsAtStart")) {
|
||||
MixinEnvironment.getCurrentEnvironment().audit();
|
||||
if (auditAndExit) {
|
||||
// Prevents Crash Assistant from treating mixin audit as a crash
|
||||
Minecraft.getInstance().stop();
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ModernFix() {
|
||||
INSTANCE = this;
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@ import org.embeddedt.modernfix.api.constants.IntegrationConstants;
|
|||
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
|
||||
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
|
||||
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||
import org.embeddedt.modernfix.searchtree.JEIBackedSearchTree;
|
||||
import org.embeddedt.modernfix.searchtree.SearchTreeProviderRegistry;
|
||||
import org.embeddedt.modernfix.spark.SparkLaunchProfiler;
|
||||
import org.embeddedt.modernfix.util.ClassInfoManager;
|
||||
import org.embeddedt.modernfix.world.IntegratedWatchdog;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class ModernFixClient {
|
||||
|
|
@ -38,6 +40,7 @@ public class ModernFixClient {
|
|||
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.branding.F3Screen")) {
|
||||
brandingString = ModernFix.NAME + " " + ModernFixPlatformHooks.INSTANCE.getVersionString();
|
||||
}
|
||||
SearchTreeProviderRegistry.register(JEIBackedSearchTree.PROVIDER);
|
||||
for(String className : ModernFixPlatformHooks.INSTANCE.getCustomModOptions().get(IntegrationConstants.CLIENT_INTEGRATION_CLASS)) {
|
||||
try {
|
||||
CLIENT_INTEGRATIONS.add((ModernFixClientIntegration)Class.forName(className).getDeclaredConstructor().newInstance());
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package org.embeddedt.modernfix.api.entrypoint;
|
||||
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.client.resources.model.ModelState;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
|
||||
/**
|
||||
* Implement this interface in a mod class and add it to "modernfix:integration_v1" in your mod metadata file
|
||||
|
|
@ -20,10 +21,49 @@ public interface ModernFixClientIntegration {
|
|||
default void onDynamicResourcesStatusChange(boolean enabled) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to allow mods to observe the loading of an unbaked model and either make changes to it or wrap it with their
|
||||
* own instance.
|
||||
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
|
||||
* @param originalModel the original model
|
||||
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
|
||||
* with dynamic resources on
|
||||
* @return the model which should actually be loaded for this resource location
|
||||
*/
|
||||
default UnbakedModel onUnbakedModelLoad(ResourceLocation location, UnbakedModel originalModel, ModelBakery bakery) {
|
||||
return originalModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to allow mods to observe the use of an unbaked model at bake time and either make changes to it or wrap it with their
|
||||
* own instance.
|
||||
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
|
||||
* @param originalModel the original model
|
||||
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
|
||||
* with dynamic resources on
|
||||
* @return the model which should actually be loaded for this resource location
|
||||
*/
|
||||
default UnbakedModel onUnbakedModelPreBake(ResourceLocation location, UnbakedModel originalModel, ModelBakery bakery) {
|
||||
return originalModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to allow mods to observe the loading of a baked model and either make changes to it or wrap it with their
|
||||
* own instance.
|
||||
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
|
||||
* @param originalModel the original model
|
||||
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
|
||||
* with dynamic resources on
|
||||
* @return the model which should actually be loaded for this resource location
|
||||
*/
|
||||
@Deprecated
|
||||
default BakedModel onBakedModelLoad(ResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery) {
|
||||
return originalModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to allow mods to observe the loading of a baked model and either make changes to it or wrap it with their
|
||||
* own instance.
|
||||
*
|
||||
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
|
||||
* @param originalModel the original model
|
||||
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
|
||||
|
|
@ -31,7 +71,7 @@ public interface ModernFixClientIntegration {
|
|||
* @param textureGetter function to retrieve textures for this model
|
||||
* @return the model which should actually be loaded for this resource location
|
||||
*/
|
||||
default BakedModel onBakedModelLoad(ModelResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter textureGetter) {
|
||||
return originalModel;
|
||||
default BakedModel onBakedModelLoad(ResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery, Function<Material, TextureAtlasSprite> textureGetter) {
|
||||
return onBakedModelLoad(location, baseModel, originalModel, state, bakery);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
package org.embeddedt.modernfix.api.helpers;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.*;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
|
||||
import org.embeddedt.modernfix.dynamicresources.ModelBakeryHelpers;
|
||||
import org.embeddedt.modernfix.util.DynamicMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class ModelHelpers {
|
||||
|
|
@ -23,7 +28,7 @@ public final class ModelHelpers {
|
|||
* @return a list of all blockstates related to the model
|
||||
*/
|
||||
public static ImmutableList<BlockState> getBlockStateForLocation(ModelResourceLocation location) {
|
||||
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(location.id());
|
||||
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(new ResourceLocation(location.getNamespace(), location.getPath()));
|
||||
if(blockOpt.isPresent())
|
||||
return ModelBakeryHelpers.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), location);
|
||||
else
|
||||
|
|
@ -47,7 +52,7 @@ public final class ModelHelpers {
|
|||
* @return a fake map of the top-level models
|
||||
*/
|
||||
public static Map<ResourceLocation, BakedModel> createFakeTopLevelMap(BiFunction<ResourceLocation, ModelState, BakedModel> modelGetter) {
|
||||
return new DynamicMap<>(ResourceLocation.class, location -> modelGetter.apply(location, BlockModelRotation.X0_Y0));
|
||||
return new DynamicMap<>(location -> modelGetter.apply(location, BlockModelRotation.X0_Y0));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -56,8 +61,6 @@ public final class ModelHelpers {
|
|||
* @return an appropriate ModelBaker
|
||||
*/
|
||||
public static ModelBaker adaptBakery(ModelBakery bakery) {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
/*
|
||||
return new ModelBaker() {
|
||||
@Override
|
||||
public UnbakedModel getModel(ResourceLocation resourceLocation) {
|
||||
|
|
@ -69,8 +72,16 @@ public final class ModelHelpers {
|
|||
public BakedModel bake(ResourceLocation resourceLocation, ModelState modelState) {
|
||||
return ((IExtendedModelBakery)bakery).bakeDefault(resourceLocation, modelState);
|
||||
}
|
||||
};
|
||||
|
||||
*/
|
||||
@Override
|
||||
public @Nullable BakedModel bake(ResourceLocation location, ModelState state, Function<Material, TextureAtlasSprite> sprites) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<Material, TextureAtlasSprite> getModelTextureGetter() {
|
||||
return Material::sprite;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,185 @@
|
|||
package org.embeddedt.modernfix.benchmark;
|
||||
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.*;
|
||||
import net.minecraft.util.Unit;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiManager;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.chunk.*;
|
||||
import net.minecraft.world.level.chunk.storage.ChunkSerializer;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class WorldgenBenchmark {
|
||||
|
||||
private static final TicketType<Unit> BENCHMARK_TICKET =
|
||||
TicketType.create("modernfix_benchmark", (a, b) -> 0);
|
||||
|
||||
private static final List<ChunkStatus> ALL_STATUSES = ChunkStatus.getStatusList().stream()
|
||||
.filter(s -> s.getIndex() > ChunkStatus.EMPTY.getIndex()
|
||||
&& s.getIndex() < ChunkStatus.INITIALIZE_LIGHT.getIndex())
|
||||
.toList();
|
||||
|
||||
private static final int REQUIRED_LOAD_RADIUS = ALL_STATUSES.stream().mapToInt(ChunkStatus::getRange).max().orElse(0);
|
||||
|
||||
public static String run(ServerLevel level, ChunkPos center, int testRadius, int iterations, ChunkStatus startStatus, ChunkStatus stopStatus) {
|
||||
int startIndex = ALL_STATUSES.indexOf(startStatus);
|
||||
if (startIndex < 0) {
|
||||
throw new IllegalArgumentException("Invalid start status: " + startStatus);
|
||||
}
|
||||
|
||||
int stopIndex = ALL_STATUSES.indexOf(stopStatus);
|
||||
if (stopIndex < 0) {
|
||||
throw new IllegalArgumentException("Invalid stop status:" + stopStatus);
|
||||
}
|
||||
|
||||
List<ChunkStatus> setupStatuses = ALL_STATUSES.subList(0, startIndex);
|
||||
List<ChunkStatus> timedStatuses = ALL_STATUSES.subList(startIndex, stopIndex + 1);
|
||||
|
||||
Context ctx = new Context(level, center, testRadius);
|
||||
long[] timings = new long[timedStatuses.size()];
|
||||
|
||||
int testDiameter = 2 * testRadius + 1;
|
||||
int numPositions = testDiameter * testDiameter;
|
||||
ChunkPos[] testPositions = new ChunkPos[numPositions];
|
||||
CompoundTag[] snapshots = new CompoundTag[numPositions];
|
||||
ChunkAccess[][] neighborArrays = new ChunkAccess[numPositions][];
|
||||
|
||||
int idx = 0;
|
||||
for (int tz = -testRadius; tz <= testRadius; tz++) {
|
||||
for (int tx = -testRadius; tx <= testRadius; tx++) {
|
||||
ChunkPos testPos = new ChunkPos(center.x + tx, center.z + tz);
|
||||
testPositions[idx] = testPos;
|
||||
neighborArrays[idx] = ctx.buildNeighborArray(testPos);
|
||||
|
||||
ProtoChunk setupProto = ctx.newProtoChunk(testPos);
|
||||
neighborArrays[idx][ctx.centerIndex] = setupProto;
|
||||
for (ChunkStatus status : setupStatuses) {
|
||||
status.generate(ctx.executor, level, ctx.generator, ctx.templates,
|
||||
ctx.lightEngine, ctx.noopPromotion, Arrays.asList(neighborArrays[idx])).join();
|
||||
}
|
||||
snapshots[idx] = ChunkSerializer.write(level, setupProto);
|
||||
idx++;
|
||||
ModernFix.LOGGER.info("worldgen benchmark setup progress: {}/{}", idx, numPositions);
|
||||
}
|
||||
}
|
||||
|
||||
ModernFix.LOGGER.info("worldgen benchmark setup complete");
|
||||
|
||||
for (int iter = 0; iter < iterations; iter++) {
|
||||
ModernFix.LOGGER.info("worldgen benchmark iteration: {}/{}", iter + 1, iterations);
|
||||
for (int p = 0; p < numPositions; p++) {
|
||||
ProtoChunk restored = ChunkSerializer.read(
|
||||
level, ctx.poiManager, testPositions[p], snapshots[p]);
|
||||
neighborArrays[p][ctx.centerIndex] = restored;
|
||||
List<ChunkAccess> neighborList = Arrays.asList(neighborArrays[p]);
|
||||
|
||||
for (int s = 0; s < timedStatuses.size(); s++) {
|
||||
long t0 = System.nanoTime();
|
||||
|
||||
timedStatuses.get(s).generate(ctx.executor, level, ctx.generator,
|
||||
ctx.templates, ctx.lightEngine, ctx.noopPromotion, neighborList).join();
|
||||
|
||||
timings[s] += System.nanoTime() - t0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModernFix.LOGGER.info("worldgen benchmark done");
|
||||
|
||||
ctx.cleanup();
|
||||
|
||||
return formatTimings(timedStatuses, timings, testRadius, iterations);
|
||||
}
|
||||
|
||||
private static String formatTimings(List<ChunkStatus> statuses, long[] timings, int testRadius, int iterations) {
|
||||
int totalChunks = (2 * testRadius + 1) * (2 * testRadius + 1) * iterations;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
long total = 0;
|
||||
for (int i = 0; i < timings.length; i++) {
|
||||
total += timings[i];
|
||||
String name = BuiltInRegistries.CHUNK_STATUS.getKey(statuses.get(i)).getPath();
|
||||
sb.append(String.format(" %-22s %8.1f ms (%6.2f ms/chunk)\n",
|
||||
name, timings[i] / 1e6, timings[i] / 1e6 / totalChunks));
|
||||
}
|
||||
sb.append(String.format(" %-22s %8.1f ms (%6.2f ms/chunk)\n",
|
||||
"TOTAL", total / 1e6, total / 1e6 / totalChunks));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static class Context {
|
||||
final ServerLevel level;
|
||||
final ServerChunkCache chunkSource;
|
||||
final ChunkPos center;
|
||||
final int loadRadius;
|
||||
final int loadDiameter;
|
||||
final ChunkAccess[] realChunks;
|
||||
final int neighborDiameter;
|
||||
final int centerIndex;
|
||||
final Executor executor;
|
||||
final ChunkGenerator generator;
|
||||
final ThreadedLevelLightEngine lightEngine;
|
||||
final StructureTemplateManager templates;
|
||||
final PoiManager poiManager;
|
||||
final Function<ChunkAccess, CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> noopPromotion;
|
||||
private final net.minecraft.core.Registry<Biome> biomeRegistry;
|
||||
|
||||
Context(ServerLevel level, ChunkPos center, int testRadius) {
|
||||
this.level = level;
|
||||
this.chunkSource = level.getChunkSource();
|
||||
this.center = center;
|
||||
this.loadRadius = testRadius + REQUIRED_LOAD_RADIUS;
|
||||
this.loadDiameter = 2 * loadRadius + 1;
|
||||
this.neighborDiameter = 2 * REQUIRED_LOAD_RADIUS + 1;
|
||||
this.centerIndex = neighborDiameter * neighborDiameter / 2;
|
||||
this.executor = MoreExecutors.directExecutor();
|
||||
this.generator = chunkSource.getGenerator();
|
||||
this.lightEngine = chunkSource.getLightEngine();
|
||||
this.templates = level.getStructureManager();
|
||||
this.poiManager = chunkSource.getPoiManager();
|
||||
this.noopPromotion = chunk -> CompletableFuture.completedFuture(Either.left(chunk));
|
||||
this.biomeRegistry = level.registryAccess().registryOrThrow(Registries.BIOME);
|
||||
|
||||
chunkSource.addRegionTicket(BENCHMARK_TICKET, center, loadRadius, Unit.INSTANCE);
|
||||
|
||||
realChunks = new ChunkAccess[loadDiameter * loadDiameter];
|
||||
for (int dz = -loadRadius; dz <= loadRadius; dz++) {
|
||||
for (int dx = -loadRadius; dx <= loadRadius; dx++) {
|
||||
LevelChunk real = level.getChunk(center.x + dx, center.z + dz);
|
||||
realChunks[(dz + loadRadius) * loadDiameter + (dx + loadRadius)] =
|
||||
new ImposterProtoChunk(real, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProtoChunk newProtoChunk(ChunkPos pos) {
|
||||
return new ProtoChunk(pos, UpgradeData.EMPTY, level, biomeRegistry, null);
|
||||
}
|
||||
|
||||
ChunkAccess[] buildNeighborArray(ChunkPos testPos) {
|
||||
int count = neighborDiameter * neighborDiameter;
|
||||
ChunkAccess[] array = new ChunkAccess[count];
|
||||
int baseX = (testPos.x - REQUIRED_LOAD_RADIUS) - (center.x - loadRadius);
|
||||
int baseZ = (testPos.z - REQUIRED_LOAD_RADIUS) - (center.z - loadRadius);
|
||||
for (int dz = 0; dz < neighborDiameter; dz++) {
|
||||
System.arraycopy(realChunks, (baseZ + dz) * loadDiameter + baseX,
|
||||
array, dz * neighborDiameter, neighborDiameter);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
chunkSource.removeRegionTicket(BENCHMARK_TICKET, center, loadRadius, Unit.INSTANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package org.embeddedt.modernfix.chunk;
|
||||
|
||||
import net.minecraft.world.level.chunk.Palette;
|
||||
|
||||
public interface ExtendedPalettedContainer<T> {
|
||||
Palette<T> mfix$getPalette();
|
||||
}
|
||||
|
|
@ -3,14 +3,58 @@ package org.embeddedt.modernfix.command;
|
|||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.duck.IProfilingServerFunctionManager;
|
||||
import org.embeddedt.modernfix.structure.CachingStructureManager;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static net.minecraft.commands.Commands.literal;
|
||||
|
||||
public class ModernFixCommands {
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
dispatcher.register(literal("modernfix")
|
||||
.then(literal("upgradeStructures")
|
||||
.requires(source -> source.hasPermission(3))
|
||||
.executes(context -> {
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
if(level == null) {
|
||||
context.getSource().sendFailure(Component.literal("Couldn't find server level"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
ResourceManager manager = level.getServer().resources.resourceManager();
|
||||
Map<ResourceLocation, Resource> structures = manager.listResources("structures", p -> p.getPath().endsWith(".nbt"));
|
||||
int upgradedNum = 0;
|
||||
Pattern pathPattern = Pattern.compile("^structures/(.*)\\.nbt$");
|
||||
for(Map.Entry<ResourceLocation, Resource> entry : structures.entrySet()) {
|
||||
upgradedNum++;
|
||||
ResourceLocation found = entry.getKey();
|
||||
Matcher matcher = pathPattern.matcher(found.getPath());
|
||||
if(!matcher.matches())
|
||||
continue;
|
||||
ResourceLocation structureLocation = new ResourceLocation(found.getNamespace(), matcher.group(1));
|
||||
try(InputStream resource = entry.getValue().open()) {
|
||||
CachingStructureManager.readStructureTag(structureLocation, level.getServer().getFixerUpper(), resource);
|
||||
Component msg = Component.literal("checked " + structureLocation + " (" + upgradedNum + "/" + structures.size() + ")");
|
||||
context.getSource().sendSuccess(() -> msg, false);
|
||||
} catch(Throwable e) {
|
||||
ModernFix.LOGGER.error("Couldn't upgrade structure " + found, e);
|
||||
context.getSource().sendFailure(Component.literal("error reading " + structureLocation + " (" + upgradedNum + "/" + structures.size() + ")"));
|
||||
}
|
||||
}
|
||||
|
||||
context.getSource().sendSuccess(() -> Component.literal("All structures upgraded"), false);
|
||||
|
||||
return 1;
|
||||
}))
|
||||
.then(literal("mcfunctions").requires(source -> source.hasPermission(3))
|
||||
.executes(context -> {
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.buffer_builder_leak;
|
||||
|
||||
import com.mojang.blaze3d.vertex.BufferBuilder;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.render.UnsafeBufferHelper;
|
||||
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.callback.CallbackInfo;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@Mixin(value = BufferBuilder.class, priority = 1500)
|
||||
@ClientOnlyMixin
|
||||
public class BufferBuilderMixin {
|
||||
@Shadow private ByteBuffer buffer;
|
||||
|
||||
private static boolean leakReported = false;
|
||||
|
||||
private boolean mfix$shouldFree = true;
|
||||
|
||||
@Dynamic
|
||||
@Inject(method = "flywheel$injectForRender", at = @At("RETURN"), remap = false, require = 0)
|
||||
private void preventFree(CallbackInfo ci) {
|
||||
mfix$shouldFree = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure UnsafeBufferHelper is classloaded early, to avoid Forge's event transformer showing an error in the log.
|
||||
*/
|
||||
@Inject(method = "<clinit>", at = @At(value = "RETURN"))
|
||||
private static void initUnsafeBufferHelper(CallbackInfo ci) {
|
||||
UnsafeBufferHelper.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
ByteBuffer buf = buffer;
|
||||
// can be null if a mod already tried to free the buffer
|
||||
if(buf != null && mfix$shouldFree) {
|
||||
if(!leakReported) {
|
||||
leakReported = true;
|
||||
ModernFix.LOGGER.warn("One or more BufferBuilders have been leaked, ModernFix will attempt to correct this.");
|
||||
}
|
||||
UnsafeBufferHelper.free(buf);
|
||||
buffer = null;
|
||||
}
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.buffer_builder_leak;
|
||||
|
||||
import com.mojang.blaze3d.vertex.BufferBuilder;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||
import net.minecraft.client.renderer.RenderBuffers;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(RenderBuffers.class)
|
||||
@ClientOnlyMixin
|
||||
public class RenderBuffersMixin {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason put() may be called for multiple instances of the same render type (e.g. signSheet and hangingSignSheet
|
||||
* in 1.20.1). This leaks the previous BufferBuilder if one is already in the map.
|
||||
*/
|
||||
@Inject(method = "put", at = @At("HEAD"), cancellable = true)
|
||||
private static void mfix$preventBufferLeak(Object2ObjectLinkedOpenHashMap<RenderType, BufferBuilder> mapBuilders, RenderType renderType, CallbackInfo ci) {
|
||||
if (mapBuilders.containsKey(renderType)) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import net.minecraft.CrashReport;
|
||||
import net.minecraft.ReportedException;
|
||||
import net.minecraft.server.level.ChunkHolder;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.util.thread.BlockableEventLoop;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
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.lang.reflect.Field;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(ChunkMap.class)
|
||||
public abstract class ChunkMapLoadMixin {
|
||||
@Shadow
|
||||
@Nullable
|
||||
protected abstract ChunkHolder getVisibleChunkIfPresent(long l);
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private BlockableEventLoop<Runnable> mainThreadExecutor;
|
||||
|
||||
@Unique
|
||||
private static final ThreadLocal<CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> MFIX_SURROGATE_FUTURE = new ThreadLocal<>();
|
||||
|
||||
@Unique
|
||||
private final ConcurrentLinkedQueue<Throwable> mfix$promotionExceptions = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason This redirect makes several changes to how full chunk promotion works. First of all, promotion runs
|
||||
* directly in the context of the main thread executor, rather than going through the priority sorter.
|
||||
* This change allows attempts to load other chunks from within the promotion lambda to succeed (important
|
||||
* for bad EntityJoinLevelEvent implementations to not deadlock the game). Second, it slightly alters the
|
||||
* semantics of protoChunkToFullChunk so that the FULL chunk future will be completed before postload
|
||||
* callbacks finish running. This change allows attempts to load the _same_ chunk in the promotion lambda to
|
||||
* succeed, as otherwise the future would block waiting for itself to complete.
|
||||
*
|
||||
* <p>This is a cleaner version of a similar trick used in ModernFix versions for 1.16, which deferred specifically
|
||||
* entity addition to happen outside the futures.
|
||||
*/
|
||||
@Redirect(method = "protoChunkToFullChunk", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", ordinal = 0))
|
||||
private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> createSurrogateFuture(
|
||||
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> previousFuture,
|
||||
Function<? super Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>, ? extends Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> fn,
|
||||
Executor executor) {
|
||||
var surrogate = new CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>();
|
||||
// Unlike vanilla, we execute the promotion lambda in mainThreadExecutor, rather than within the context
|
||||
// of the task sorter. Doing this avoids deadlocking the sorter if a blocking chunk load is attempted
|
||||
// during chunk promotion. We still initially compose the future through the sorter's executor to stop promotion
|
||||
// from running earlier than it would in vanilla.
|
||||
previousFuture.thenComposeAsync(CompletableFuture::completedFuture, executor).thenApplyAsync(either -> {
|
||||
// running on thread that executes lambda body
|
||||
MFIX_SURROGATE_FUTURE.set(surrogate);
|
||||
try {
|
||||
return fn.apply(either);
|
||||
} finally {
|
||||
MFIX_SURROGATE_FUTURE.remove();
|
||||
}
|
||||
}, this.mainThreadExecutor).whenComplete((either, throwable) -> {
|
||||
if (throwable != null) {
|
||||
if (!surrogate.isDone()) {
|
||||
surrogate.completeExceptionally(throwable);
|
||||
} else {
|
||||
// The chunk has already become visible at FULL status, so we
|
||||
// track the exception ourselves and manually rethrow it at the right point
|
||||
// to trigger a server crash
|
||||
this.mfix$promotionExceptions.add(throwable);
|
||||
}
|
||||
} else {
|
||||
surrogate.complete(either);
|
||||
}
|
||||
});
|
||||
// Return the surrogate
|
||||
return surrogate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Complete the surrogate future as soon as basic promotion is done, and before we start loading entities
|
||||
* & block entities. This allows EntityJoinLevelEvent to read the current chunk.
|
||||
*/
|
||||
@Inject(method = "lambda$protoChunkToFullChunk$34", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/LevelChunk;runPostLoad()V"))
|
||||
private void completeSurrogateFuture(ChunkHolder holder, ChunkAccess p_214856_, CallbackInfoReturnable<ChunkAccess> cir,
|
||||
@Local(ordinal = 0) LevelChunk levelChunk) {
|
||||
var future = MFIX_SURROGATE_FUTURE.get();
|
||||
if (future != null) {
|
||||
future.complete(Either.left(levelChunk));
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "tick()V", at = @At("HEAD"))
|
||||
private void reportDeferredPromotionException(CallbackInfo ci) {
|
||||
var throwable = this.mfix$promotionExceptions.poll();
|
||||
if (throwable == null) {
|
||||
return;
|
||||
}
|
||||
if (throwable instanceof ReportedException e) {
|
||||
throw e;
|
||||
} else {
|
||||
throw new ReportedException(CrashReport.forThrowable(throwable, "Exception during promotion of chunk to FULL status"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// we also preserve the legacy currentlyLoading field to keep Forge parity
|
||||
|
||||
private static final Field currentlyLoadingField = ObfuscationReflectionHelper.findField(ChunkHolder.class, "currentlyLoading");
|
||||
|
||||
private static void setCurrentlyLoading(ChunkHolder holder, LevelChunk value) {
|
||||
try {
|
||||
currentlyLoadingField.set(holder, value);
|
||||
} catch(ReflectiveOperationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set currentlyLoading before calling runPostLoad and restore its old value afterwards. We track the old value
|
||||
* to avoid conflicting with Forge if/when this feature is added.
|
||||
*/
|
||||
@WrapOperation(method = "lambda$protoChunkToFullChunk$34", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/LevelChunk;runPostLoad()V"))
|
||||
private void setCurrentLoadingThenPostLoad(LevelChunk chunk, Operation<Void> operation) {
|
||||
ChunkHolder holder = this.getVisibleChunkIfPresent(chunk.getPos().toLong());
|
||||
if(holder != null) {
|
||||
LevelChunk prevLoading = null;
|
||||
try {
|
||||
prevLoading = (LevelChunk)currentlyLoadingField.get(holder);
|
||||
} catch(ReflectiveOperationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
setCurrentlyLoading(holder, chunk);
|
||||
operation.call(chunk);
|
||||
} finally {
|
||||
setCurrentlyLoading(holder, prevLoading);
|
||||
}
|
||||
} else {
|
||||
ModernFix.LOGGER.warn("Unable to find chunk holder for loading chunk");
|
||||
operation.call(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.gameevent.GameEvent;
|
||||
|
|
@ -18,8 +17,8 @@ public class EntityMixin {
|
|||
* tries to raytrace blocks. To fix this, we skip firing the sculk event if the chunk the entity is within is not
|
||||
* loaded.
|
||||
*/
|
||||
@WrapWithCondition(method = "addPassenger", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;gameEvent(Lnet/minecraft/core/Holder;Lnet/minecraft/world/entity/Entity;)V"))
|
||||
private boolean onlyAddIfSelfChunkLoaded(Entity instance, Holder<GameEvent> gameEvent, Entity entity) {
|
||||
@WrapWithCondition(method = "addPassenger", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;gameEvent(Lnet/minecraft/world/level/gameevent/GameEvent;Lnet/minecraft/world/entity/Entity;)V"))
|
||||
private boolean onlyAddIfSelfChunkLoaded(Entity instance, GameEvent event, Entity entity) {
|
||||
var chunkPos = instance.chunkPosition();
|
||||
if (instance.level() instanceof ServerLevel serverLevel && serverLevel.getChunkSource().getChunkNow(chunkPos.x, chunkPos.z) == null) {
|
||||
ModernFix.LOGGER.warn("Skipped emitting ENTITY_MOUNT game event for entity {} as it would cause deadlock", instance.toString());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
|
||||
|
||||
import net.minecraft.server.level.ChunkHolder;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@Mixin(value = ServerChunkCache.class, priority = 1100)
|
||||
public abstract class ServerChunkCache_CurrentLoadingMixin {
|
||||
@Shadow @Nullable protected abstract ChunkHolder getVisibleChunkIfPresent(long l);
|
||||
|
||||
private static final MethodHandle CURRENTLY_LOADING;
|
||||
|
||||
static {
|
||||
try {
|
||||
Field currentlyLoadingField = ObfuscationReflectionHelper.findField(ChunkHolder.class, "currentlyLoading");
|
||||
currentlyLoadingField.setAccessible(true);
|
||||
CURRENTLY_LOADING = MethodHandles.lookup().unreflectGetter(currentlyLoadingField);
|
||||
} catch(Exception e) {
|
||||
throw new RuntimeException("Failed to get currentlyLoading field", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the currentlyLoading field before going to the future chain, as was done in 1.16. In 1.18 upstream seems
|
||||
* to have only applied this to getChunkNow().
|
||||
*/
|
||||
@Inject(method = "getChunk", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;getChunkFutureMainThread(IILnet/minecraft/world/level/chunk/ChunkStatus;Z)Ljava/util/concurrent/CompletableFuture;"), cancellable = true, require = 0)
|
||||
private void checkCurrentlyLoading(int chunkX, int chunkZ, ChunkStatus requiredStatus, boolean load, CallbackInfoReturnable<ChunkAccess> cir) {
|
||||
long i = ChunkPos.asLong(chunkX, chunkZ);
|
||||
ChunkHolder holder = this.getVisibleChunkIfPresent(i);
|
||||
if(holder != null) {
|
||||
LevelChunk c;
|
||||
try {
|
||||
c = (LevelChunk)CURRENTLY_LOADING.invokeExact(holder);
|
||||
} catch(Throwable e) {
|
||||
e.printStackTrace();
|
||||
c = null;
|
||||
}
|
||||
if(c != null)
|
||||
cir.setReturnValue(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.concurrency;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraftforge.registries.tags.ITag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(targets = {"net/minecraftforge/registries/ForgeRegistryTagManager"})
|
||||
public class ForgeRegistryTagManagerMixin<V> {
|
||||
@Shadow private volatile Map<TagKey<V>, ITag<V>> tags;
|
||||
|
||||
/**
|
||||
* @author embeddedt (issue found by Uncandango)
|
||||
* @reason vanilla does not use the correct double-checked locking paradigm, which leads to race conditions
|
||||
*/
|
||||
@WrapMethod(method = "getTag", remap = false)
|
||||
private ITag<V> getTagSafe(@NotNull TagKey<V> name, Operation<ITag<V>> original) {
|
||||
ITag<V> tag = this.tags.get(name);
|
||||
if (tag == null) {
|
||||
synchronized (this) {
|
||||
tag = original.call(name);
|
||||
}
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.concurrency;
|
||||
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(value = MappedRegistry.class, priority = 500)
|
||||
public abstract class MappedRegistryMixin<T> {
|
||||
@Shadow private volatile Map<TagKey<T>, HolderSet.Named<T>> tags;
|
||||
|
||||
@Shadow protected abstract HolderSet.Named<T> createTag(TagKey<T> key);
|
||||
|
||||
/**
|
||||
* @author embeddedt (issue found by Uncandango)
|
||||
* @reason vanilla does not use the correct double-checked locking paradigm, which leads to race conditions
|
||||
*/
|
||||
@Overwrite
|
||||
public HolderSet.Named<T> getOrCreateTag(TagKey<T> key) {
|
||||
HolderSet.Named<T> named = this.tags.get(key);
|
||||
if (named == null) {
|
||||
// synchronize and check again - this is the bugfix
|
||||
synchronized (this) {
|
||||
named = this.tags.get(key);
|
||||
if (named == null) {
|
||||
named = this.createTag(key);
|
||||
Map<TagKey<T>, HolderSet.Named<T>> map = new IdentityHashMap<>(this.tags);
|
||||
map.put(key, named);
|
||||
this.tags = map;
|
||||
}
|
||||
}
|
||||
}
|
||||
return named;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.concurrency;
|
||||
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(targets = {"net/minecraftforge/registries/NamespacedWrapper"}, priority = 500)
|
||||
public abstract class NamespacedWrapperMixin<T> {
|
||||
@Shadow(aliases = {"tags"}) private volatile Map<TagKey<T>, HolderSet.Named<T>> tags;
|
||||
|
||||
@Shadow(aliases = {"createTag"}) protected abstract HolderSet.Named<T> m_211067_(TagKey<T> key);
|
||||
|
||||
/**
|
||||
* @author embeddedt (issue found by Uncandango)
|
||||
* @reason vanilla does not use the correct double-checked locking paradigm, which leads to race conditions
|
||||
*/
|
||||
@Overwrite
|
||||
public HolderSet.Named<T> getOrCreateTag(TagKey<T> key) {
|
||||
HolderSet.Named<T> named = this.tags.get(key);
|
||||
if (named == null) {
|
||||
// synchronize and check again - this is the bugfix
|
||||
synchronized (this) {
|
||||
named = this.tags.get(key);
|
||||
if (named == null) {
|
||||
named = this.m_211067_(key);
|
||||
Map<TagKey<T>, HolderSet.Named<T>> map = new IdentityHashMap<>(this.tags);
|
||||
map.put(key, named);
|
||||
this.tags = map;
|
||||
}
|
||||
}
|
||||
}
|
||||
return named;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.concurrency;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.server.packs.PackType;
|
||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
||||
import net.minecraft.server.packs.resources.ReloadableResourceManager;
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import net.minecraftforge.fml.ModLoadingStage;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.forge.init.ModernFixForge;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(ReloadableResourceManager.class)
|
||||
@ClientOnlyMixin
|
||||
public abstract class ReloadableResourceManagerMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private PackType type;
|
||||
|
||||
@Shadow
|
||||
public abstract void registerReloadListener(PreparableReloadListener listener);
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason complain loudly when reload listeners are being registered too late in a way that would cause
|
||||
* concurrency issues, and prevent them from crashing the game
|
||||
*/
|
||||
@WrapMethod(method = "registerReloadListener")
|
||||
private void checkCallingThread(PreparableReloadListener listener, Operation<Void> original) {
|
||||
if (ModernFixForge.registryEventsFired && this.type == PackType.CLIENT_RESOURCES
|
||||
&& (Object)this == Minecraft.getInstance().getResourceManager()
|
||||
&& !Minecraft.getInstance().isSameThread()) {
|
||||
ModernFix.LOGGER.error("A mod is calling registerReloadListener at the wrong time. This will cause random concurrency crashes when ModernFix is not installed. Please report this to them. If you are a modder, refer to https://github.com/embeddedt/ModernFix/wiki/registerReloadListener-called-on-wrong-thread for more information.", new Exception("registerReloadListener called on wrong thread"));
|
||||
// Defer the call onto the main client thread. There is a decent chance the mod's listener will be
|
||||
// ignored in this case, but it is more predictable than allowing them to randomly crash the game.
|
||||
Minecraft.getInstance().tell(() -> this.registerReloadListener(listener));
|
||||
return;
|
||||
}
|
||||
|
||||
original.call(listener);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,9 @@ package org.embeddedt.modernfix.common.mixin.bugfix.entity_pose_stack;
|
|||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
|
||||
import net.neoforged.neoforge.client.event.RenderLivingEvent;
|
||||
import net.neoforged.bus.api.Event;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.minecraftforge.client.event.RenderLivingEvent;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
|
@ -13,17 +13,18 @@ import org.spongepowered.asm.mixin.injection.Redirect;
|
|||
@Mixin(LivingEntityRenderer.class)
|
||||
@ClientOnlyMixin
|
||||
public class LivingEntityRendererMixin {
|
||||
@Redirect(method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At(value = "INVOKE", target = "Lnet/neoforged/bus/api/IEventBus;post(Lnet/neoforged/bus/api/Event;)Lnet/neoforged/bus/api/Event;", ordinal = 0))
|
||||
private Event fireCheckingPoseStack(IEventBus instance, Event event) {
|
||||
@Redirect(method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/eventbus/api/IEventBus;post(Lnet/minecraftforge/eventbus/api/Event;)Z", ordinal = 0))
|
||||
private boolean fireCheckingPoseStack(IEventBus instance, Event event) {
|
||||
PoseStack stack = ((RenderLivingEvent)event).getPoseStack();
|
||||
int size = ((PoseStackAccessor)stack).getPoseStack().size();
|
||||
instance.post(event);
|
||||
if (((RenderLivingEvent.Pre)event).isCanceled()) {
|
||||
if (instance.post(event)) {
|
||||
// Pop the stack if someone pushed it in the event
|
||||
while (((PoseStackAccessor)stack).getPoseStack().size() > size) {
|
||||
stack.popPose();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ package org.embeddedt.modernfix.common.mixin.bugfix.entity_pose_stack;
|
|||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
|
||||
import net.neoforged.bus.api.Event;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.neoforge.client.event.RenderPlayerEvent;
|
||||
import net.minecraftforge.client.event.RenderPlayerEvent;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
|
@ -13,17 +13,18 @@ import org.spongepowered.asm.mixin.injection.Redirect;
|
|||
@Mixin(PlayerRenderer.class)
|
||||
@ClientOnlyMixin
|
||||
public class PlayerRendererMixin {
|
||||
@Redirect(method = "render(Lnet/minecraft/client/player/AbstractClientPlayer;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At(value = "INVOKE", target = "Lnet/neoforged/bus/api/IEventBus;post(Lnet/neoforged/bus/api/Event;)Lnet/neoforged/bus/api/Event;", ordinal = 0))
|
||||
private Event fireCheckingPoseStack(IEventBus instance, Event event) {
|
||||
@Redirect(method = "render(Lnet/minecraft/client/player/AbstractClientPlayer;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/eventbus/api/IEventBus;post(Lnet/minecraftforge/eventbus/api/Event;)Z", ordinal = 0))
|
||||
private boolean fireCheckingPoseStack(IEventBus instance, Event event) {
|
||||
PoseStack stack = ((RenderPlayerEvent)event).getPoseStack();
|
||||
int size = ((PoseStackAccessor)stack).getPoseStack().size();
|
||||
instance.post(event);
|
||||
if (((RenderPlayerEvent.Pre)event).isCanceled()) {
|
||||
if (instance.post(event)) {
|
||||
// Pop the stack if someone pushed it in the event
|
||||
while (((PoseStackAccessor)stack).getPoseStack().size() > size) {
|
||||
stack.popPose();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.forge_vehicle_packets;
|
||||
|
||||
import net.minecraft.network.protocol.game.ServerboundMoveVehiclePacket;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(ServerGamePacketListenerImpl.class)
|
||||
public class ServerGamePacketListenerImplMixin {
|
||||
@Shadow public ServerPlayer player;
|
||||
|
||||
@Redirect(method = "handleMoveVehicle", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;absMoveTo(DDDFF)V"), require = 0)
|
||||
private void movePlayerUsingPositionRider(ServerPlayer player, double x, double y, double z, float yRot, float xRot, ServerboundMoveVehiclePacket packet) {
|
||||
if(player == this.player) {
|
||||
// use positionRider
|
||||
Vec3 oldPos = this.player.position();
|
||||
yRot = this.player.getYRot();
|
||||
xRot = this.player.getXRot();
|
||||
float yHeadRot = this.player.getYHeadRot();
|
||||
this.player.getRootVehicle().positionRider(this.player);
|
||||
// keep old rotation
|
||||
this.player.setYRot(yRot);
|
||||
this.player.setXRot(xRot);
|
||||
this.player.setYHeadRot(yHeadRot);
|
||||
// save old position
|
||||
this.player.xo = oldPos.x;
|
||||
this.player.yo = oldPos.y;
|
||||
this.player.zo = oldPos.z;
|
||||
} else
|
||||
player.absMoveTo(x, y, z, yRot, xRot);
|
||||
}
|
||||
}
|
||||
|
|
@ -86,11 +86,9 @@ public abstract class LevelChunkMixin extends ChunkAccess {
|
|||
}
|
||||
|
||||
BlockEntity blockEntity = this.getBlockEntity(pos.immutable(), LevelChunk.EntityCreationType.IMMEDIATE);
|
||||
String blockName = state.getBlock().toString();
|
||||
if (blockEntity != null) {
|
||||
ModernFix.LOGGER.warn("Created missing block entity for {} at {}", blockName, pos.toShortString());
|
||||
} else {
|
||||
ModernFix.LOGGER.error("Block entity is missing for {} at {}, but could not be created", blockName, pos.toShortString());
|
||||
if (blockEntity != null && ModernFix.LOGGER.isDebugEnabled()) {
|
||||
String blockName = state.getBlock().toString();
|
||||
ModernFix.LOGGER.debug("Created missing block entity for {} at {}", blockName, pos.toShortString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.model_data_manager_cme;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraftforge.client.model.data.ModelDataManager;
|
||||
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.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Fix several concurrency issues in the default ModelDataManager.
|
||||
*/
|
||||
@Mixin(ModelDataManager.class)
|
||||
@ClientOnlyMixin
|
||||
public abstract class ModelDataManagerMixin {
|
||||
@Shadow protected abstract void refreshAt(ChunkPos chunk);
|
||||
|
||||
@Shadow @Final private Map<ChunkPos, Set<BlockPos>> needModelDataRefresh;
|
||||
|
||||
/**
|
||||
* Make the set of positions to refresh a real concurrent hash set rather than relying on synchronizedSet,
|
||||
* because the returned iterator won't be thread-safe otherwise. See https://github.com/AppliedEnergistics/Applied-Energistics-2/issues/7511
|
||||
*/
|
||||
@ModifyArg(method = "requestRefresh", at = @At(value = "INVOKE", target = "Ljava/util/Map;computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;", ordinal = 0), index = 1, remap = false)
|
||||
private Function<ChunkPos, Set<BlockPos>> changeTypeOfSetUsed(Function<ChunkPos, Set<BlockPos>> mappingFunction) {
|
||||
return pos -> ConcurrentHashMap.newKeySet();
|
||||
}
|
||||
|
||||
@Redirect(method = "getAt(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/Map;", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/client/model/data/ModelDataManager;refreshAt(Lnet/minecraft/world/level/ChunkPos;)V"), remap = false)
|
||||
private void onlyRefreshOnMainThread(ModelDataManager instance, ChunkPos pos) {
|
||||
// Only refresh model data on the main thread. This prevents calling getBlockEntity from worker threads
|
||||
// which could cause weird CMEs or other behavior.
|
||||
// Avoid the loop if no model data needs to be refreshed, to prevent unnecessary allocation.
|
||||
if(Minecraft.getInstance().isSameThread() && !needModelDataRefresh.isEmpty()) {
|
||||
// Refresh the given chunk, and all its neighbors. This is less efficient than the default code
|
||||
// but we have no choice since we need to not do refreshing on workers, and blocks might
|
||||
// try to access model data in neighboring chunks.
|
||||
for(int x = -1; x <= 1; x++) {
|
||||
for(int z = -1; z <= 1; z++) {
|
||||
refreshAt(new ChunkPos(pos.x + x, pos.z + z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.paper_chunk_patches;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import net.minecraft.server.level.*;
|
||||
import net.minecraft.util.thread.BlockableEventLoop;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
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.ModifyArg;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
|
||||
@Mixin(ChunkMap.class)
|
||||
public abstract class ChunkMapMixin {
|
||||
@Shadow @Final private BlockableEventLoop<Runnable> mainThreadExecutor;
|
||||
|
||||
/* https://github.com/PaperMC/Paper/blob/ver/1.17.1/patches/server/0752-Fix-chunks-refusing-to-unload-at-low-TPS.patch */
|
||||
@ModifyArg(method = "prepareAccessibleChunk", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 1)
|
||||
private Executor useMainThreadExecutor(Executor executor) {
|
||||
return this.mainThreadExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason 1.17+ uses getNow to check if the parent future is ready, and calls scheduleChunkGeneration as soon as
|
||||
* it is found to not be ready. In the latter scenario, a massive number of extra CompletableFutures are allocated
|
||||
* even if they are not actually necessary if the future is waited for. To prevent this, if the parent future
|
||||
* is not done, we wait for it to complete and then retry schedule(). This will either detect an adequate
|
||||
* status and return a loading future, or re-enter this injector with the parent future completed, in which case
|
||||
* we proceed to schedule generation as originally requested.
|
||||
*/
|
||||
@WrapOperation(method = "schedule", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkMap;scheduleChunkGeneration(Lnet/minecraft/server/level/ChunkHolder;Lnet/minecraft/world/level/chunk/ChunkStatus;)Ljava/util/concurrent/CompletableFuture;"))
|
||||
private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> mfix$avoidSchedulingGenerationPrematurely(ChunkMap map, ChunkHolder holder, ChunkStatus status, Operation<CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> original) {
|
||||
if (!status.hasLoadDependencies()) {
|
||||
var parentFuture = holder.getOrScheduleFuture(status.getParent(), map);
|
||||
if (!parentFuture.isDone()) {
|
||||
return parentFuture.thenComposeAsync(
|
||||
either -> map.schedule(holder, status),
|
||||
this.mainThreadExecutor
|
||||
);
|
||||
}
|
||||
}
|
||||
return original.call(map, holder, status);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,15 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.recipe_book_type_desync;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.stats.RecipeBookSettings;
|
||||
import net.minecraft.world.inventory.RecipeBookType;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.forge.packet.NetworkUtils;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
|
@ -30,14 +36,12 @@ public class RecipeBookSettingsMixin {
|
|||
}
|
||||
mfix$maxVanillaOrdinal = ord;
|
||||
}
|
||||
/*
|
||||
@Redirect(method = "read(Lnet/minecraft/network/FriendlyByteBuf;)Lnet/minecraft/stats/RecipeBookSettings;", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/FriendlyByteBuf;readBoolean()Z"))
|
||||
private static boolean useDefaultBooleanIfVanilla(FriendlyByteBuf buf, @Local(ordinal = 0) RecipeBookType type) {
|
||||
if(type.ordinal() >= (mfix$maxVanillaOrdinal + 1)) {
|
||||
if(type.ordinal() >= (mfix$maxVanillaOrdinal + 1) && NetworkUtils.isCurrentlyVanilla) {
|
||||
ModernFix.LOGGER.warn("Not reading recipe book data for type '{}' as we are using vanilla connection", type.name());
|
||||
return false; // skip actually reading buffer
|
||||
}
|
||||
return buf.readBoolean();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.registry_ops_cme;
|
||||
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.RegistryOps;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
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.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Mixin(targets = {"net/minecraft/resources/RegistryOps$1"})
|
||||
public class RegistryOpsMemoizedMixin {
|
||||
@Shadow @Final @Mutable
|
||||
private Map<ResourceKey<? extends Registry<?>>, Optional<? extends RegistryOps.RegistryInfo<?>>> lookups;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void useConcurrentMap(RegistryOps.RegistryInfoLookup registryInfoLookup, CallbackInfo ci) {
|
||||
this.lookups = new ConcurrentHashMap<>(this.lookups);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.removed_dimensions;
|
||||
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
|
||||
@Mixin(LevelStorageSource.class)
|
||||
public class LevelStorageSourceMixin {
|
||||
@ModifyArg(method = "*", at = @At(value = "INVOKE", target = "Lcom/mojang/serialization/DataResult;getOrThrow(ZLjava/util/function/Consumer;)Ljava/lang/Object;", ordinal = 0), index = 0)
|
||||
private static boolean alwaysAllowPartialDimensions(boolean flag) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ public class MinecraftMixin {
|
|||
/**
|
||||
* To mitigate the effect of leaked client worlds, clear most of the data structures that waste memory.
|
||||
*/
|
||||
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;Z)V", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/Minecraft;level:Lnet/minecraft/client/multiplayer/ClientLevel;"))
|
||||
@Inject(method = "clearLevel(Lnet/minecraft/client/gui/screens/Screen;)V", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/Minecraft;level:Lnet/minecraft/client/multiplayer/ClientLevel;"))
|
||||
private void clearLevelDataForLeaks(CallbackInfo ci) {
|
||||
if(this.level != null) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package org.embeddedt.modernfix.common.mixin.core;
|
||||
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import org.embeddedt.modernfix.util.TimeFormatter;
|
||||
import net.minecraftforge.network.NetworkConstants;
|
||||
import org.embeddedt.modernfix.forge.classloading.ManifestCompactor;
|
||||
import org.slf4j.Logger;
|
||||
import org.embeddedt.modernfix.forge.load.ModWorkManagerQueue;
|
||||
import org.embeddedt.modernfix.util.TimeFormatter;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
|
@ -22,6 +25,14 @@ public class BootstrapMixin {
|
|||
private static void doModernFixBootstrap(CallbackInfo ci) {
|
||||
if(!isBootstrapped) {
|
||||
LOGGER.info("ModernFix reached bootstrap stage ({} after launch)", TimeFormatter.formatNanos(ManagementFactory.getRuntimeMXBean().getUptime() * 1000L * 1000L));
|
||||
ModWorkManagerQueue.replace();
|
||||
ManifestCompactor.compactManifests();
|
||||
}
|
||||
}
|
||||
|
||||
/* for https://github.com/MinecraftForge/MinecraftForge/issues/9505 */
|
||||
@Inject(method = "bootStrap", at = @At("RETURN"))
|
||||
private static void doClassloadHack(CallbackInfo ci) {
|
||||
NetworkConstants.init();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package org.embeddedt.modernfix.common.mixin.core;
|
||||
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.logging.CrashReportAnalyser;
|
||||
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.Map;
|
||||
|
||||
@Mixin(CrashReportAnalyser.class)
|
||||
public class CrashReportAnalyserMixin {
|
||||
@Shadow @Final private static Map<IModInfo, String[]> SUSPECTED_MODS;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Remove ModernFix from the list of suspected mods when a crash happens. Otherwise, we get blamed
|
||||
* for "registry object not present" crashes if users don't interpret the crash before reporting
|
||||
* it.
|
||||
*
|
||||
* It seems unlikely ModernFix will simultaneously cause a crash while it's not obvious it caused it.
|
||||
*/
|
||||
@Inject(method = "buildSuspectedModsSection", at = @At("HEAD"), require = 0, remap = false)
|
||||
private static void removeOurselvesFromSuspectedMods(StringBuilder stringBuilder, CallbackInfo ci) {
|
||||
SUSPECTED_MODS.keySet().removeIf(iModInfo -> iModInfo.getModId().equals("modernfix"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package org.embeddedt.modernfix.common.mixin.core;
|
||||
|
||||
import net.minecraftforge.registries.GameData;
|
||||
import org.embeddedt.modernfix.forge.init.ModernFixForge;
|
||||
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(value = GameData.class, remap = false)
|
||||
public class GameDataMixin {
|
||||
@Inject(method = "postRegisterEvents", at = @At("RETURN"))
|
||||
private static void markPosted(CallbackInfo ci) {
|
||||
ModernFixForge.registryEventsFired = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
|||
import net.minecraft.Util;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.embeddedt.modernfix.duck.ITimeTrackingServer;
|
||||
import org.embeddedt.modernfix.neoforge.load.MinecraftServerReloadTracker;
|
||||
import org.embeddedt.modernfix.forge.load.MinecraftServerReloadTracker;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package org.embeddedt.modernfix.common.mixin.core;
|
||||
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraftforge.network.NetworkHooks;
|
||||
import org.embeddedt.modernfix.forge.packet.NetworkUtils;
|
||||
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(NetworkHooks.class)
|
||||
public abstract class NetworkHooksMixin {
|
||||
@Shadow public static boolean isVanillaConnection(Connection manager) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Inject(method = "handleClientLoginSuccess", at = @At("RETURN"), remap = false)
|
||||
private static void setVanillaGlobalFlag(Connection manager, CallbackInfo ci) {
|
||||
NetworkUtils.isCurrentlyVanilla = isVanillaConnection(manager);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package org.embeddedt.modernfix.common.mixin.core;
|
||||
|
||||
import net.minecraft.world.level.chunk.Palette;
|
||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||
import org.embeddedt.modernfix.chunk.ExtendedPalettedContainer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(PalettedContainer.class)
|
||||
public class PalettedContainerMixin<T> implements ExtendedPalettedContainer<T> {
|
||||
@Shadow
|
||||
private volatile PalettedContainer.Data<T> data;
|
||||
|
||||
@Override
|
||||
public Palette<T> mfix$getPalette() {
|
||||
return this.data.palette();
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package org.embeddedt.modernfix.common.mixin.core;
|
|||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.server.WorldLoader;
|
||||
import org.embeddedt.modernfix.neoforge.load.MinecraftServerReloadTracker;
|
||||
import org.embeddedt.modernfix.forge.load.MinecraftServerReloadTracker;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package org.embeddedt.modernfix.common.mixin.devenv;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.registries.ForgeRegistry;
|
||||
import net.minecraftforge.registries.GameData;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(GameData.class)
|
||||
public class GameDataMixin {
|
||||
|
||||
@Redirect(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/registries/ForgeRegistry;dump(Lnet/minecraft/resources/ResourceLocation;)V", remap = false))
|
||||
private static void noDump(ForgeRegistry<?> reg, ResourceLocation id) {
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package org.embeddedt.modernfix.common.mixin.feature.branding;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.neoforged.fml.ModContainer;
|
||||
import net.neoforged.fml.ModList;
|
||||
import net.neoforged.neoforge.internal.BrandingControl;
|
||||
import net.minecraftforge.internal.BrandingControl;
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
|
|
@ -14,7 +14,7 @@ import java.util.Optional;
|
|||
|
||||
@Mixin(value = BrandingControl.class, remap = false, priority = 1100)
|
||||
public class BrandingControlMixin {
|
||||
@Inject(method = "computeBranding", at = @At(value = "INVOKE", target = "Lnet/neoforged/fml/ModList;get()Lnet/neoforged/fml/ModList;"), locals = LocalCapture.CAPTURE_FAILHARD, require = 0)
|
||||
@Inject(method = "computeBranding", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/fml/ModList;get()Lnet/minecraftforge/fml/ModList;"), locals = LocalCapture.CAPTURE_FAILHARD, require = 0)
|
||||
private static void addModernFixBranding(CallbackInfo ci, ImmutableList.Builder<String> builder) {
|
||||
Optional<? extends ModContainer> mfContainer = ModList.get().getModContainerById("modernfix");
|
||||
if(mfContainer.isPresent())
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package org.embeddedt.modernfix.common.mixin.feature.cause_lag_by_disabling_threads;
|
||||
|
||||
import net.minecraft.client.renderer.chunk.SectionRenderDispatcher;
|
||||
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
|
@ -11,7 +11,7 @@ import java.util.concurrent.LinkedBlockingQueue;
|
|||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Mixin(SectionRenderDispatcher.class)
|
||||
@Mixin(ChunkRenderDispatcher.class)
|
||||
@ClientOnlyMixin
|
||||
public class ChunkRenderDispatcherMixin {
|
||||
private static final Executor MFIX_CHUNK_BUILD_EXECUTOR = new ThreadPoolExecutor(1, computeNumThreads(), 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package org.embeddedt.modernfix.common.mixin.feature.cause_lag_by_disabling_threads;
|
||||
|
||||
import net.minecraft.Util;
|
||||
import org.embeddedt.modernfix.util.DirectExecutorService;
|
||||
import org.embeddedt.modernfix.util.SingleThreadedWorkerService;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
|
|
@ -12,5 +12,5 @@ import java.util.concurrent.ExecutorService;
|
|||
@Mixin(Util.class)
|
||||
public class UtilMixin {
|
||||
@Shadow @Final @Mutable
|
||||
private static final ExecutorService BACKGROUND_EXECUTOR = new DirectExecutorService();
|
||||
private static final ExecutorService BACKGROUND_EXECUTOR = new SingleThreadedWorkerService();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ import com.llamalad7.mixinextras.sugar.Local;
|
|||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.functions.CommandFunction;
|
||||
import net.minecraft.commands.CommandFunction;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.ServerFunctionManager;
|
||||
import org.embeddedt.modernfix.duck.IProfilingServerFunctionManager;
|
||||
|
|
@ -30,22 +29,22 @@ public class ServerFunctionManagerMixin implements IProfilingServerFunctionManag
|
|||
private final Map<ResourceLocation, Stopwatch> mfix$functionWatches = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
@Inject(method = "executeTagFunctions", at = @At("HEAD"))
|
||||
private void resetWatches(Collection<CommandFunction<CommandSourceStack>> functionObjects, ResourceLocation identifier, CallbackInfo ci) {
|
||||
private void resetWatches(Collection<CommandFunction> functionObjects, ResourceLocation identifier, CallbackInfo ci) {
|
||||
mfix$functionWatches.values().forEach(Stopwatch::reset);
|
||||
}
|
||||
|
||||
@Inject(method = "executeTagFunctions", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ServerFunctionManager;execute(Lnet/minecraft/commands/functions/CommandFunction;Lnet/minecraft/commands/CommandSourceStack;)V"))
|
||||
private void startWatch(Collection<CommandFunction<CommandSourceStack>> functionObjects, ResourceLocation identifier, CallbackInfo ci, @Local(ordinal = 0) CommandFunction<CommandSourceStack> function, @Share("stopwatch") LocalRef<Stopwatch> watchRef) {
|
||||
@Inject(method = "executeTagFunctions", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ServerFunctionManager;execute(Lnet/minecraft/commands/CommandFunction;Lnet/minecraft/commands/CommandSourceStack;)I"))
|
||||
private void startWatch(Collection<CommandFunction> functionObjects, ResourceLocation identifier, CallbackInfo ci, @Local(ordinal = 0) CommandFunction function, @Share("stopwatch") LocalRef<Stopwatch> watchRef) {
|
||||
watchRef.set(null);
|
||||
if (identifier == TICK_FUNCTION_TAG) {
|
||||
var watch = mfix$functionWatches.computeIfAbsent(function.id(), i -> Stopwatch.createUnstarted());
|
||||
var watch = mfix$functionWatches.computeIfAbsent(function.getId(), i -> Stopwatch.createUnstarted());
|
||||
watch.start();
|
||||
watchRef.set(watch);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "executeTagFunctions", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ServerFunctionManager;execute(Lnet/minecraft/commands/functions/CommandFunction;Lnet/minecraft/commands/CommandSourceStack;)V", shift = At.Shift.AFTER))
|
||||
private void stopWatch(Collection<CommandFunction<CommandSourceStack>> functionObjects, ResourceLocation identifier, CallbackInfo ci, @Share("stopwatch") LocalRef<Stopwatch> watchRef) {
|
||||
@Inject(method = "executeTagFunctions", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ServerFunctionManager;execute(Lnet/minecraft/commands/CommandFunction;Lnet/minecraft/commands/CommandSourceStack;)I", shift = At.Shift.AFTER))
|
||||
private void stopWatch(Collection<CommandFunction> functionObjects, ResourceLocation identifier, CallbackInfo ci, @Share("stopwatch") LocalRef<Stopwatch> watchRef) {
|
||||
var watch = watchRef.get();
|
||||
if (watch != null && watch.isRunning()) {
|
||||
watch.stop();
|
||||
|
|
@ -53,7 +52,7 @@ public class ServerFunctionManagerMixin implements IProfilingServerFunctionManag
|
|||
}
|
||||
|
||||
@Inject(method = "executeTagFunctions", at = @At("RETURN"))
|
||||
private void pruneUnusedWatches(Collection<CommandFunction<CommandSourceStack>> functionObjects, ResourceLocation identifier, CallbackInfo ci) {
|
||||
private void pruneUnusedWatches(Collection<CommandFunction> functionObjects, ResourceLocation identifier, CallbackInfo ci) {
|
||||
mfix$functionWatches.values().removeIf(watch -> watch.elapsed().isZero());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import org.spongepowered.asm.mixin.Final;
|
|||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(targets = "net/neoforged/neoforge/event/AddReloadListenerEvent$WrappedStateAwareListener")
|
||||
@Mixin(targets = "net/minecraftforge/event/AddReloadListenerEvent$WrappedStateAwareListener")
|
||||
public abstract class AddReloadListenerEventWrapperMixin implements PreparableReloadListener {
|
||||
@Shadow @Final private PreparableReloadListener wrapped;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,46 +1,52 @@
|
|||
package org.embeddedt.modernfix.common.mixin.feature.registry_event_progress;
|
||||
|
||||
import net.neoforged.bus.api.Event;
|
||||
import net.neoforged.bus.api.EventPriority;
|
||||
import net.neoforged.fml.ModList;
|
||||
import net.neoforged.fml.ModLoader;
|
||||
import net.neoforged.fml.ModLoadingContext;
|
||||
import net.neoforged.fml.event.IModBusEvent;
|
||||
import net.neoforged.fml.loading.progress.StartupNotificationManager;
|
||||
import net.neoforged.neoforge.registries.GameData;
|
||||
import net.neoforged.neoforge.registries.RegisterEvent;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import net.minecraftforge.fml.ModLoader;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
import net.minecraftforge.fml.StartupMessageManager;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.registries.GameData;
|
||||
import net.minecraftforge.registries.RegisterEvent;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.forge.util.AsyncLoadingScreen;
|
||||
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.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(value = GameData.class, remap = false)
|
||||
@ClientOnlyMixin
|
||||
public class GameDataMixin {
|
||||
|
||||
@Redirect(method = "postRegisterEvents", at = @At(value = "INVOKE", target = "Lnet/neoforged/fml/ModLoader;postEventWrapContainerInModOrder(Lnet/neoforged/bus/api/Event;)V"))
|
||||
private static <T extends Event & IModBusEvent> void postWithProgressBar(T event) {
|
||||
if(ModLoader.hasErrors()) {
|
||||
return;
|
||||
}
|
||||
RegisterEvent registryEvent = (RegisterEvent)event;
|
||||
// We control phases ourselves so we can make a separate progress bar for each phase.
|
||||
String registryName = registryEvent.getRegistryKey().location().toString();
|
||||
for(EventPriority phase : EventPriority.values()) {
|
||||
// FIXME need to use prepend rather than append for it to be visible for now
|
||||
var pb = StartupNotificationManager.prependProgressBar(registryName, ModList.get().size());
|
||||
try {
|
||||
ModList.get().forEachModInOrder(mc -> {
|
||||
ModLoadingContext.get().setActiveContainer(mc);
|
||||
pb.label(pb.name() + " - " + mc.getModInfo().getDisplayName());
|
||||
pb.increment();
|
||||
mc.acceptEvent(phase, event);
|
||||
ModLoadingContext.get().setActiveContainer(null);
|
||||
});
|
||||
} finally {
|
||||
pb.complete();
|
||||
}
|
||||
}
|
||||
private static AsyncLoadingScreen mfix$asyncScreen;
|
||||
|
||||
@Inject(method = "postRegisterEvents", at = @At(value = "INVOKE", target = "Ljava/util/Set;iterator()Ljava/util/Iterator;", ordinal = 0))
|
||||
private static void createAsyncScreen(CallbackInfo ci) {
|
||||
mfix$asyncScreen = new AsyncLoadingScreen();
|
||||
}
|
||||
|
||||
@Inject(method = "postRegisterEvents", at = @At(value = "INVOKE", target = "Ljava/lang/RuntimeException;getSuppressed()[Ljava/lang/Throwable;", ordinal = 0))
|
||||
private static void closeAsyncScreen(CallbackInfo ci) {
|
||||
mfix$asyncScreen.close();
|
||||
mfix$asyncScreen = null;
|
||||
}
|
||||
|
||||
@Redirect(method = "postRegisterEvents", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/fml/ModLoader;postEventWrapContainerInModOrder(Lnet/minecraftforge/eventbus/api/Event;)V"))
|
||||
private static <T extends Event & IModBusEvent> void swapThreadAndPost(ModLoader loader, T event) {
|
||||
RegisterEvent registryEvent = (RegisterEvent)event;
|
||||
var pb = StartupMessageManager.addProgressBar(registryEvent.getRegistryKey().location().toString(), ModList.get().size());
|
||||
try {
|
||||
loader.postEventWithWrapInModOrder(event, (mc, e) -> {
|
||||
ModLoadingContext.get().setActiveContainer(mc);
|
||||
pb.label(pb.name() + " - " + mc.getModInfo().getDisplayName());
|
||||
pb.increment();
|
||||
}, (mc, e) -> {
|
||||
ModLoadingContext.get().setActiveContainer(null);
|
||||
});
|
||||
} finally {
|
||||
pb.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.attribute_supplier_dedup;
|
||||
|
||||
import net.minecraft.world.entity.ai.attributes.Attribute;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||||
import org.embeddedt.modernfix.entity.AttributeInstanceTemplates;
|
||||
import org.embeddedt.modernfix.forge.init.ModernFixForge;
|
||||
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(AttributeSupplier.Builder.class)
|
||||
public class AttributeSupplierBuilderMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private Map<Attribute, AttributeInstance> builder;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason canonicalize identical AttributeInstance templates, many entities are created with the same values
|
||||
*/
|
||||
@Inject(method = "build", at = @At(value = "NEW", target = "(Ljava/util/Map;)Lnet/minecraft/world/entity/ai/attributes/AttributeSupplier;"))
|
||||
private void deduplicateInstances(CallbackInfoReturnable<AttributeSupplier> cir) {
|
||||
// The interning has overhead, so we only apply it early during the launch, when mods are normally
|
||||
// registering the custom attribute suppliers.
|
||||
if (ModernFixForge.registryEventsFired) {
|
||||
return;
|
||||
}
|
||||
this.builder.replaceAll((a, i) -> AttributeInstanceTemplates.intern(i));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.attribute_supplier_dedup;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.world.entity.ai.attributes.Attribute;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||||
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.Map;
|
||||
|
||||
@Mixin(AttributeSupplier.class)
|
||||
public class AttributeSupplierMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
@Mutable
|
||||
private Map<Attribute, AttributeInstance> instances;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason more compact than ImmutableMap due to less wrapper objects, and we do not
|
||||
* care about insertion order in this context
|
||||
*/
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void useCompactJavaMap(Map<Attribute, AttributeInstance> instances, CallbackInfo ci) {
|
||||
this.instances = new Object2ObjectOpenHashMap<>(this.instances);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.blast_search_trees;
|
||||
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.searchtree.SearchRegistry;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||
import org.embeddedt.modernfix.searchtree.RecipeBookSearchTree;
|
||||
import org.embeddedt.modernfix.searchtree.SearchTreeProviderRegistry;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||
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(Minecraft.class)
|
||||
@ClientOnlyMixin
|
||||
public abstract class MinecraftMixin {
|
||||
@Shadow @Final private SearchRegistry searchRegistry;
|
||||
|
||||
@Shadow public abstract <T> void populateSearchTree(SearchRegistry.Key<T> key, List<T> list);
|
||||
|
||||
@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());
|
||||
SearchRegistry.TreeBuilderSupplier<ItemStack> nameSupplier = list -> provider.getSearchTree(false);
|
||||
SearchRegistry.TreeBuilderSupplier<ItemStack> tagSupplier = list -> provider.getSearchTree(true);
|
||||
this.searchRegistry.register(SearchRegistry.CREATIVE_NAMES, nameSupplier);
|
||||
this.searchRegistry.register(SearchRegistry.CREATIVE_TAGS, tagSupplier);
|
||||
this.searchRegistry.register(SearchRegistry.RECIPE_COLLECTIONS, list -> new RecipeBookSearchTree(provider.getSearchTree(false), list));
|
||||
ModernFixPlatformHooks.INSTANCE.registerCreativeSearchTrees(this.searchRegistry, nameSupplier, tagSupplier, this::populateSearchTree);
|
||||
// grab components for all key mappings in order to prevent them from being loaded off-thread later
|
||||
// this populates the LazyLoadedValues
|
||||
// we also need to suppress GLFW errors to prevent crashes if a key is missing
|
||||
GLFWErrorCallback oldCb = GLFW.glfwSetErrorCallback(null);
|
||||
for(KeyMapping mapping : KeyMapping.ALL.values()) {
|
||||
mapping.getTranslatedKeyMessage();
|
||||
}
|
||||
GLFW.glfwSetErrorCallback(oldCb);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package org.embeddedt.modernfix.common.mixin.perf.cache_profile_texture_url;
|
|||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
||||
import net.minecraft.client.resources.SkinManager;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
|
@ -12,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.Redirect;
|
|||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Mixin(targets = {"net/minecraft/client/resources/SkinManager$TextureCache" })
|
||||
@Mixin(SkinManager.class)
|
||||
@ClientOnlyMixin
|
||||
public class SkinManagerMixin {
|
||||
@Unique
|
||||
|
|
@ -21,7 +22,7 @@ public class SkinManagerMixin {
|
|||
.concurrencyLevel(1)
|
||||
.build();
|
||||
|
||||
@Redirect(method = { "getOrLoad", "registerTexture" },
|
||||
@Redirect(method = "registerTexture(Lcom/mojang/authlib/minecraft/MinecraftProfileTexture;Lcom/mojang/authlib/minecraft/MinecraftProfileTexture$Type;Lnet/minecraft/client/resources/SkinManager$SkinTextureCallback;)Lnet/minecraft/resources/ResourceLocation;",
|
||||
at = @At(value = "INVOKE", target = "Lcom/mojang/authlib/minecraft/MinecraftProfileTexture;getHash()Ljava/lang/String;", remap = false))
|
||||
private String useCachedHash(MinecraftProfileTexture texture) {
|
||||
// avoid lambda allocation for common case
|
||||
|
|
|
|||
|
|
@ -1,68 +1,198 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.resources.RegistryOps;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureSet;
|
||||
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.duck.IChunkGenerator;
|
||||
import org.embeddedt.modernfix.duck.IServerLevel;
|
||||
import org.embeddedt.modernfix.world.StrongholdLocationCache;
|
||||
import org.spongepowered.asm.mixin.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 org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@Mixin(ChunkGeneratorStructureState.class)
|
||||
public class ChunkGeneratorMixin implements IChunkGenerator {
|
||||
private WeakReference<ServerLevel> mfix$serverLevel;
|
||||
@Shadow
|
||||
@Final
|
||||
private long concentricRingsSeed;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private BiomeSource biomeSource;
|
||||
|
||||
private Path mfix$dimensionPath;
|
||||
private MinecraftServer mfix$server;
|
||||
|
||||
private SoftReference<Map<String, List<ChunkPos>>> mfix$cachedPositions = new SoftReference<>(null);
|
||||
|
||||
private static final String CACHE_FILENAME = "mfix_stronghold_cache_v2.nbt";
|
||||
|
||||
@Override
|
||||
public void mfix$setAssociatedServerLevel(ServerLevel level) {
|
||||
mfix$serverLevel = new WeakReference<>(level);
|
||||
public void mfix$setStrongholdCachePath(Path cachePath, MinecraftServer server) {
|
||||
this.mfix$dimensionPath = cachePath;
|
||||
this.mfix$server = server;
|
||||
}
|
||||
|
||||
@Inject(method = "generateRingPositions", at = @At("HEAD"), cancellable = true)
|
||||
private void useCachedDataIfAvailable(Holder<StructureSet> structureSet, ConcentricRingsStructurePlacement placement, CallbackInfoReturnable<CompletableFuture<List<ChunkPos>>> cir) {
|
||||
if(placement.count() == 0)
|
||||
return;
|
||||
ServerLevel level = searchLevel();
|
||||
if(level == null)
|
||||
return;
|
||||
StrongholdLocationCache cache = ((IServerLevel)level).mfix$getStrongholdCache();
|
||||
List<ChunkPos> positions = cache.getChunkPosList();
|
||||
if(positions.isEmpty())
|
||||
return;
|
||||
ModernFix.LOGGER.debug("Loaded stronghold cache for dimension {} with {} positions", level.dimension().location(), positions.size());
|
||||
cir.setReturnValue(CompletableFuture.completedFuture(positions));
|
||||
@WrapMethod(method = "generateRingPositions")
|
||||
private CompletableFuture<List<ChunkPos>> modernfix$cacheRingPositions(Holder<StructureSet> structureSet,
|
||||
ConcentricRingsStructurePlacement placement,
|
||||
Operation<CompletableFuture<List<ChunkPos>>> original,
|
||||
@Share("threadPool") LocalRef<ExecutorService> threadPoolRef) {
|
||||
if (this.mfix$server == null || this.mfix$dimensionPath == null) {
|
||||
return original.call(structureSet, placement);
|
||||
}
|
||||
|
||||
String cacheKey = mfix$makeCacheKey(placement);
|
||||
|
||||
// Try reading from cache
|
||||
List<ChunkPos> cached = mfix$readFromCache(cacheKey);
|
||||
if (cached != null) {
|
||||
ModernFix.LOGGER.debug("Using cached stronghold positions for {}", cacheKey);
|
||||
return CompletableFuture.completedFuture(List.copyOf(cached));
|
||||
}
|
||||
|
||||
var server = this.mfix$server;
|
||||
ExecutorService strongholdPool = Executors.newFixedThreadPool(Math.max(1, Runtime.getRuntime().availableProcessors() - 2));
|
||||
threadPoolRef.set(strongholdPool);
|
||||
try {
|
||||
return original.call(structureSet, placement).thenApplyAsync(positions -> {
|
||||
// Skip write if server exited before we finished
|
||||
if (server.isRunning()) {
|
||||
mfix$writeToCache(cacheKey, positions);
|
||||
}
|
||||
return positions;
|
||||
}, Util.ioPool());
|
||||
} finally {
|
||||
strongholdPool.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private ServerLevel searchLevel() {
|
||||
if(mfix$serverLevel != null)
|
||||
return mfix$serverLevel.get();
|
||||
else
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Ring position calculation is often not required for initial chunk generation, but the tasks still occupy
|
||||
* CPU time on the main worker pool and prevent higher priority work from progressing. To fix this we use a
|
||||
* dedicated pool.
|
||||
*/
|
||||
@Redirect(method = "generateRingPositions", at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;backgroundExecutor()Ljava/util/concurrent/ExecutorService;"))
|
||||
private ExecutorService useDedicatedService(@Share("threadPool") LocalRef<ExecutorService> threadPoolRef) {
|
||||
return threadPoolRef.get();
|
||||
}
|
||||
|
||||
private String mfix$makeCacheKey(ConcentricRingsStructurePlacement placement) {
|
||||
RegistryOps<Tag> ops = RegistryOps.create(NbtOps.INSTANCE, this.mfix$server.registryAccess());
|
||||
String placementKey = ConcentricRingsStructurePlacement.CODEC.encodeStart(ops, placement)
|
||||
.result().map(Tag::toString).orElse(null);
|
||||
String biomeSourceKey = BiomeSource.CODEC.encodeStart(ops, this.biomeSource)
|
||||
.result().map(Tag::toString).orElse(null);
|
||||
if (placementKey == null || biomeSourceKey == null) {
|
||||
ModernFix.LOGGER.warn("Failed to create cache key for concentric structure placement");
|
||||
return null;
|
||||
}
|
||||
String data = placementKey + ";biomes=" + biomeSourceKey + ";seed=" + this.concentricRingsSeed;
|
||||
try {
|
||||
byte[] hash = MessageDigest.getInstance("SHA-256").digest(data.getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder sb = new StringBuilder(64);
|
||||
for (byte b : hash) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "generateRingPositions", at = @At("RETURN"), cancellable = true)
|
||||
private void saveCachedData(Holder<StructureSet> structureSet, ConcentricRingsStructurePlacement placement, CallbackInfoReturnable<CompletableFuture<List<ChunkPos>>> cir) {
|
||||
cir.setReturnValue(cir.getReturnValue().thenApplyAsync(list -> {
|
||||
if(list.size() == 0)
|
||||
return list;
|
||||
ServerLevel level = searchLevel();
|
||||
if(level != null) {
|
||||
StrongholdLocationCache cache = ((IServerLevel)level).mfix$getStrongholdCache();
|
||||
cache.setChunkPosList(list);
|
||||
ModernFix.LOGGER.debug("Saved stronghold cache for dimension {}", level.dimension().location());
|
||||
private synchronized List<ChunkPos> mfix$readFromCache(String cacheKey) {
|
||||
Map<String, List<ChunkPos>> cache = mfix$getOrLoadCache();
|
||||
return cache.get(cacheKey);
|
||||
}
|
||||
|
||||
private synchronized void mfix$writeToCache(String cacheKey, List<ChunkPos> positions) {
|
||||
Map<String, List<ChunkPos>> cache = mfix$getOrLoadCache();
|
||||
cache.put(cacheKey, List.copyOf(positions));
|
||||
mfix$cachedPositions = new SoftReference<>(cache);
|
||||
mfix$saveCacheFile(cache);
|
||||
}
|
||||
|
||||
private Map<String, List<ChunkPos>> mfix$getOrLoadCache() {
|
||||
Map<String, List<ChunkPos>> cache = mfix$cachedPositions.get();
|
||||
if (cache != null) {
|
||||
return cache;
|
||||
}
|
||||
cache = mfix$loadCacheFile();
|
||||
mfix$cachedPositions = new SoftReference<>(cache);
|
||||
return cache;
|
||||
}
|
||||
|
||||
private Map<String, List<ChunkPos>> mfix$loadCacheFile() {
|
||||
Path file = mfix$dimensionPath.resolve(CACHE_FILENAME);
|
||||
if (!Files.exists(file)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
try {
|
||||
CompoundTag root = NbtIo.readCompressed(file.toFile());
|
||||
Map<String, List<ChunkPos>> result = new HashMap<>();
|
||||
for (String key : root.getAllKeys()) {
|
||||
if (root.contains(key, Tag.TAG_INT_ARRAY)) {
|
||||
int[] data = root.getIntArray(key);
|
||||
if (data.length >= 2 && data.length % 2 == 0) {
|
||||
List<ChunkPos> positions = new ArrayList<>(data.length / 2);
|
||||
for (int i = 0; i < data.length; i += 2) {
|
||||
positions.add(new ChunkPos(data[i], data[i + 1]));
|
||||
}
|
||||
result.put(key, positions);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}, Util.backgroundExecutor()));
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
ModernFix.LOGGER.warn("Failed to read stronghold cache, will recompute", e);
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
private void mfix$saveCacheFile(Map<String, List<ChunkPos>> cache) {
|
||||
CompoundTag root = new CompoundTag();
|
||||
for (var entry : cache.entrySet()) {
|
||||
List<ChunkPos> positions = entry.getValue();
|
||||
int[] data = new int[positions.size() * 2];
|
||||
for (int i = 0; i < positions.size(); i++) {
|
||||
ChunkPos pos = positions.get(i);
|
||||
data[i * 2] = pos.x;
|
||||
data[i * 2 + 1] = pos.z;
|
||||
}
|
||||
root.putIntArray(entry.getKey(), data);
|
||||
}
|
||||
Path file = mfix$dimensionPath.resolve(CACHE_FILENAME);
|
||||
try {
|
||||
NbtIo.writeCompressed(root, file.toFile());
|
||||
} catch (Exception e) {
|
||||
ModernFix.LOGGER.warn("Failed to write stronghold cache", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
|
||||
|
||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
|
||||
import org.embeddedt.modernfix.annotation.FeatureLevel;
|
||||
import org.embeddedt.modernfix.annotation.RequiresFeatureLevel;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(ConcentricRingsStructurePlacement.class)
|
||||
@RequiresFeatureLevel(FeatureLevel.BETA)
|
||||
public class ConcentricRingsStructurePlacementMixin {
|
||||
|
||||
@Shadow @Final private int distance;
|
||||
@Shadow @Final private int spread;
|
||||
@Shadow @Final private int count;
|
||||
|
||||
/**
|
||||
* Maximum per-axis section displacement from the initial ring chunk after biome snapping.
|
||||
*
|
||||
* Vanilla calls findBiomeHorizontal with radius=112 blocks. In quart space this is ±28,
|
||||
* and converting the selected quart back to section coordinates yields at most ±7 chunks
|
||||
* per axis from the original (initialX, initialZ).
|
||||
*/
|
||||
@Unique private static final int MFIX_MAX_BIOME_SNAP_SECTIONS_PER_AXIS = 7;
|
||||
/**
|
||||
* Worst-case Euclidean error introduced by rounding:
|
||||
* initialX/Z = round(cos(angle) * dist), round(sin(angle) * dist).
|
||||
*/
|
||||
@Unique private static final double MFIX_MAX_ROUNDING_ERROR = Math.sqrt(2.0) * 0.5;
|
||||
/**
|
||||
* Worst-case Euclidean biome-snap displacement when each axis can move by at most 7 chunks.
|
||||
*/
|
||||
@Unique private static final double MFIX_MAX_BIOME_SNAP_ERROR = MFIX_MAX_BIOME_SNAP_SECTIONS_PER_AXIS * Math.sqrt(2.0);
|
||||
/**
|
||||
* Total conservative positional slack (rounding + biome snap) applied to radial bounds.
|
||||
*/
|
||||
@Unique private static final double MFIX_MAX_POSITION_ERROR = MFIX_MAX_ROUNDING_ERROR + MFIX_MAX_BIOME_SNAP_ERROR;
|
||||
|
||||
/** Squared chunk-distance below which no ring position can ever land. */
|
||||
@Unique private long mfix$innerRadiusSq;
|
||||
/** Squared chunk-distance above which no ring position can ever land. */
|
||||
@Unique private long mfix$outerRadiusSq;
|
||||
|
||||
/**
|
||||
* Precomputes conservative radial bounds for vanilla's ring placement distance:
|
||||
* {@code dist = 4*i + i*i1*6 + noise}, where {@code i=distance} and {@code i1=circle}.
|
||||
*
|
||||
* - Inner bound uses the minimum possible base term ({@code i1=0} => {@code 4*i}).
|
||||
* - Outer bound uses the maximum reachable {@code i1} for this ({@code spread,count}) pair.
|
||||
*
|
||||
* Both bounds are expanded by {@link #MFIX_MAX_POSITION_ERROR} so we never reject a valid
|
||||
* chunk produced by rounding and biome snapping.
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>(Lnet/minecraft/core/Vec3i;Lnet/minecraft/world/level/levelgen/structure/placement/StructurePlacement$FrequencyReductionMethod;FILjava/util/Optional;IIILnet/minecraft/core/HolderSet;)V",
|
||||
at = @At("RETURN")
|
||||
)
|
||||
private void mfix$computeRadiusBounds(CallbackInfo ci) {
|
||||
double maxNoise = this.distance * 1.25; // (nextDouble() - 0.5) * (distance * 2.5)
|
||||
|
||||
// min(dist): 4*i + i*0*6 - maxNoise
|
||||
double minDist = 4.0 * this.distance - maxNoise;
|
||||
double safeInnerRadius = minDist - MFIX_MAX_POSITION_ERROR;
|
||||
this.mfix$innerRadiusSq = (long)Math.max(0.0, Math.floor(safeInnerRadius * safeInnerRadius));
|
||||
|
||||
if (this.spread == 0) {
|
||||
// Vanilla behavior becomes non-finite here (angle += 2π / 0), so keep only inner rejection.
|
||||
this.mfix$outerRadiusSq = Long.MAX_VALUE;
|
||||
return;
|
||||
}
|
||||
|
||||
int maxCircle = this.mfix$computeMaxCircleIndex();
|
||||
// max(dist): 4*i + i*maxCircle*6 + maxNoise
|
||||
double maxDist = 4.0 * this.distance + (double)this.distance * maxCircle * 6.0 + maxNoise;
|
||||
double safeOuterRadius = maxDist + MFIX_MAX_POSITION_ERROR;
|
||||
this.mfix$outerRadiusSq = (long)Math.ceil(safeOuterRadius * safeOuterRadius);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the highest ring index ({@code circle}) that vanilla can reach for this placement.
|
||||
*
|
||||
* This mirrors the spread/total update logic in
|
||||
* {@link net.minecraft.world.level.chunk.ChunkGeneratorStructureState#generateRingPositions},
|
||||
* but only tracks deterministic loop state (no RNG).
|
||||
*/
|
||||
@Unique
|
||||
private int mfix$computeMaxCircleIndex() {
|
||||
int ringSpread = this.spread;
|
||||
int total = 0;
|
||||
int circle = 0;
|
||||
|
||||
while (total + ringSpread < this.count) {
|
||||
total += ringSpread;
|
||||
circle++;
|
||||
ringSpread += 2 * ringSpread / (circle + 1);
|
||||
ringSpread = Math.min(ringSpread, this.count - total);
|
||||
}
|
||||
|
||||
return circle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt, GPT-5.3-Codex
|
||||
* @reason Avoid calling getRingPositionsFor() when we know the current chunk lies outside the region where
|
||||
* concentric placement can even happen. This is particularly helpful when creating new worlds, because we can
|
||||
* avoid blocking on the slow noise computations within the spawn region around (0, 0).
|
||||
*/
|
||||
@Inject(method = "isPlacementChunk", at = @At("HEAD"), cancellable = true)
|
||||
private void mfix$earlyRejectByRadius(ChunkGeneratorStructureState structureState, int x, int z,
|
||||
CallbackInfoReturnable<Boolean> cir) {
|
||||
long distSq = (long)x * x + (long)z * z;
|
||||
if (distSq < this.mfix$innerRadiusSq || distSq > this.mfix$outerRadiusSq) {
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +1,30 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
|
||||
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.storage.DimensionDataStorage;
|
||||
import net.minecraft.world.level.storage.WritableLevelData;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
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.function.Supplier;
|
||||
|
||||
@Mixin(ServerLevel.class)
|
||||
public abstract class ServerLevelMixin extends Level implements IServerLevel {
|
||||
protected ServerLevelMixin(WritableLevelData arg, ResourceKey<Level> arg2, RegistryAccess arg3, Holder<DimensionType> arg4, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
|
||||
super(arg, arg2, arg3, arg4, supplier, bl, bl2, l, i);
|
||||
}
|
||||
|
||||
@Shadow public abstract DimensionDataStorage getDataStorage();
|
||||
|
||||
@Shadow @Final private ServerChunkCache chunkSource;
|
||||
private StrongholdLocationCache mfix$strongholdCache;
|
||||
|
||||
public class ServerLevelMixin {
|
||||
/**
|
||||
* Initialize the stronghold cache but don't force any structure generation yet.
|
||||
* @author embeddedt
|
||||
* @reason Make the dimension path accessible to ChunkGeneratorStructureState.
|
||||
*/
|
||||
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/ChunkGeneratorStructureState;ensureStructuresGenerated()V"))
|
||||
private void hookStrongholdCache(ChunkGeneratorStructureState generator) {
|
||||
((IChunkGenerator)generator).mfix$setAssociatedServerLevel((ServerLevel)(Object)this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Now start the stronghold generation process.
|
||||
*/
|
||||
@Inject(method = "<init>", at = @At("TAIL"))
|
||||
private void ensureGeneration(CallbackInfo ci) {
|
||||
mfix$strongholdCache = this.getDataStorage().computeIfAbsent(
|
||||
StrongholdLocationCache.factory((ServerLevel)(Object)this),
|
||||
StrongholdLocationCache.getFileId(this.dimensionTypeRegistration()));
|
||||
this.chunkSource.getGeneratorState().ensureStructuresGenerated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StrongholdLocationCache mfix$getStrongholdCache() {
|
||||
return mfix$strongholdCache;
|
||||
@WrapOperation(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/ChunkGeneratorStructureState;ensureStructuresGenerated()V"))
|
||||
private void setCachePath(ChunkGeneratorStructureState instance, Operation<Void> original,
|
||||
@Local(ordinal = 0, argsOnly = true) LevelStorageSource.LevelStorageAccess levelStorageAccess,
|
||||
@Local(ordinal = 0, argsOnly = true) ResourceKey<Level> dimension,
|
||||
@Local(ordinal = 0, argsOnly = true) MinecraftServer server) {
|
||||
((IChunkGenerator)instance).mfix$setStrongholdCachePath(levelStorageAccess.getDimensionPath(dimension), server);
|
||||
original.call(instance);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.cache_upgraded_structures;
|
||||
|
||||
import com.mojang.datafixers.DataFixer;
|
||||
import net.minecraft.core.HolderGetter;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.structure.CachingStructureManager;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
|
||||
@Mixin(StructureTemplateManager.class)
|
||||
public class StructureManagerMixin {
|
||||
@Shadow @Final private DataFixer fixerUpper;
|
||||
|
||||
@Shadow private ResourceManager resourceManager;
|
||||
|
||||
@Shadow @Final private HolderGetter<Block> blockLookup;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason use our own manager to avoid needless DFU updates
|
||||
*/
|
||||
@Overwrite
|
||||
private Optional<StructureTemplate> loadFromResource(ResourceLocation id) {
|
||||
ResourceLocation arg = new ResourceLocation(id.getNamespace(), "structures/" + id.getPath() + ".nbt");
|
||||
try(InputStream stream = this.resourceManager.open(arg)) {
|
||||
return Optional.of(CachingStructureManager.readStructure(id, this.fixerUpper, stream, this.blockLookup));
|
||||
} catch(FileNotFoundException e) {
|
||||
return Optional.empty();
|
||||
} catch(IOException e) {
|
||||
ModernFix.LOGGER.error("Can't read structure", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.capability_list_compaction;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.neoforged.neoforge.capabilities.BaseCapability;
|
||||
import net.neoforged.neoforge.capabilities.CapabilityHooks;
|
||||
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
|
||||
import org.embeddedt.modernfix.neoforge.caps.CapProviderGetter;
|
||||
import org.embeddedt.modernfix.neoforge.caps.ITrackingCapEvent;
|
||||
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(value = CapabilityHooks.class, remap = false)
|
||||
public class CapabilityHooksMixin {
|
||||
// Must inject as late as possible to run after mixins that add their own capabilities
|
||||
// (e.g. https://github.com/SuperMartijn642/Entangled/blob/37f2489d8badc3f52401088d8a6e25d2a63a045c/src/main/java/com/supermartijn642/entangled/mixin/neoforge/CapabilityHooksMixin.java)
|
||||
@Inject(method = "init", at = @At(value = "RETURN"))
|
||||
private static void deduplicateCaps(CallbackInfo ci, @Local(ordinal = 0) RegisterCapabilitiesEvent event) {
|
||||
if(event instanceof ITrackingCapEvent) {
|
||||
//var stopwatch = Stopwatch.createStarted();
|
||||
for(BaseCapability<?, ?> cap : ((ITrackingCapEvent)event).mfix$getTrackedCaps()) {
|
||||
CapProviderGetter.deduplicateCap(cap);
|
||||
}
|
||||
//stopwatch.stop();
|
||||
//ModernFix.LOGGER.info("Deduplicated capability lists in {}", stopwatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.capability_list_compaction;
|
||||
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.ItemLike;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.neoforged.neoforge.capabilities.BaseCapability;
|
||||
import net.neoforged.neoforge.capabilities.BlockCapability;
|
||||
import net.neoforged.neoforge.capabilities.EntityCapability;
|
||||
import net.neoforged.neoforge.capabilities.IBlockCapabilityProvider;
|
||||
import net.neoforged.neoforge.capabilities.ICapabilityProvider;
|
||||
import net.neoforged.neoforge.capabilities.ItemCapability;
|
||||
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
|
||||
import org.embeddedt.modernfix.neoforge.caps.ITrackingCapEvent;
|
||||
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;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Mixin(value = RegisterCapabilitiesEvent.class, remap = false)
|
||||
public class RegisterCapabilitiesEventMixin implements ITrackingCapEvent {
|
||||
private final Set<BaseCapability<?, ?>> mfix$trackedCapabilities = new HashSet<>();
|
||||
|
||||
@Inject(method = "registerBlock", at = @At("HEAD"))
|
||||
private void trackBlockCap(BlockCapability<?, ?> capability, IBlockCapabilityProvider<?, ?> provider, Block[] blocks, CallbackInfo ci) {
|
||||
mfix$trackedCapabilities.add(capability);
|
||||
}
|
||||
|
||||
@Inject(method = "registerBlockEntity", at = @At("HEAD"))
|
||||
private void trackBlockEntityCap(BlockCapability<?, ?> capability, BlockEntityType<?> type, ICapabilityProvider<?, ?, ?> provider, CallbackInfo ci) {
|
||||
mfix$trackedCapabilities.add(capability);
|
||||
}
|
||||
|
||||
@Inject(method = "registerItem", at = @At("HEAD"))
|
||||
private void trackItemCap(ItemCapability<?, ?> capability, ICapabilityProvider<?, ?, ?> provider, ItemLike[] items, CallbackInfo ci) {
|
||||
mfix$trackedCapabilities.add(capability);
|
||||
}
|
||||
|
||||
@Inject(method = "registerEntity", at = @At("HEAD"))
|
||||
private void trackEntityCap(EntityCapability<?, ?> capability, EntityType<?> type, ICapabilityProvider<?, ?, ?> provider, CallbackInfo ci) {
|
||||
mfix$trackedCapabilities.add(capability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<BaseCapability<?, ?>> mfix$getTrackedCaps() {
|
||||
return mfix$trackedCapabilities;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.chunk_meshing;
|
||||
|
||||
import net.minecraft.client.renderer.chunk.SectionCompiler;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.renderer.chunk.RenderChunkRegion;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.annotation.RequiresMod;
|
||||
import org.embeddedt.modernfix.util.blockpos.SectionBlockPosIterator;
|
||||
|
|
@ -9,7 +11,7 @@ import org.spongepowered.asm.mixin.Mixin;
|
|||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(value = SectionCompiler.class, priority = 2000)
|
||||
@Mixin(targets = { "net/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk$RebuildTask"}, priority = 2000)
|
||||
@ClientOnlyMixin
|
||||
@RequiresMod("!fluidlogged")
|
||||
public class RebuildTaskMixin {
|
||||
|
|
@ -21,4 +23,13 @@ public class RebuildTaskMixin {
|
|||
private Iterable<BlockPos> fastBetweenClosed(BlockPos firstPos, BlockPos secondPos) {
|
||||
return () -> new SectionBlockPosIterator(firstPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason RenderChunkRegion.getBlockState is expensive, avoid calling it multiple times for the same position
|
||||
*/
|
||||
@Redirect(method = "compile", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/chunk/RenderChunkRegion;getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;", ordinal = 1), require = 0)
|
||||
private BlockState useExistingBlockState(RenderChunkRegion instance, BlockPos pos, @Local(ordinal = 0) BlockState state) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.compact_entity_models;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.model.geom.builders.CubeDefinition;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Mixin(CubeDefinition.class)
|
||||
@ClientOnlyMixin
|
||||
public class CubeDefinitionMixin {
|
||||
@Unique
|
||||
private static final ConcurrentHashMap<List<Object>, ModelPart.Cube> MFIX_CUBE_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason deduplicate creation of Cube objects
|
||||
*/
|
||||
@WrapOperation(method = "bake", at = @At(value = "NEW", target = "(IIFFFFFFFFFZFFLjava/util/Set;)Lnet/minecraft/client/model/geom/ModelPart$Cube;"))
|
||||
private ModelPart.Cube modernfix$deduplicateCube(int texCoordU, int texCoordV, float originX, float originY, float originZ,
|
||||
float dimensionX, float dimensionY, float dimensionZ, float gtowX,
|
||||
float growY, float growZ, boolean mirror, float texScaleU,
|
||||
float texScaleV, Set visibleFaces,
|
||||
Operation<ModelPart.Cube> original) {
|
||||
List<Object> cacheKey = List.of(texCoordU, texCoordV, originX, originY, originZ, dimensionX, dimensionY, dimensionZ, gtowX, growY, growZ, mirror, texScaleU, texScaleV, visibleFaces);
|
||||
var cube = MFIX_CUBE_CACHE.get(cacheKey);
|
||||
if (cube == null) {
|
||||
cube = original.call((Object[])cacheKey.toArray());
|
||||
MFIX_CUBE_CACHE.put(cacheKey, cube);
|
||||
}
|
||||
return cube;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.compact_imposterprotochunks;
|
||||
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.lighting.ChunkSkyLightSources;
|
||||
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;
|
||||
|
||||
@Mixin(ChunkAccess.class)
|
||||
public class ChunkAccessMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
@Mutable
|
||||
protected LevelChunkSection[] sections;
|
||||
|
||||
@Shadow
|
||||
protected ChunkSkyLightSources skyLightSources;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.compact_imposterprotochunks;
|
||||
|
||||
import net.minecraft.world.level.chunk.ImposterProtoChunk;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
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(ImposterProtoChunk.class)
|
||||
public abstract class ImposterProtoChunkMixin extends ChunkAccessMixin {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason ImposterProtoChunks allocate their own LevelChunkSection objects etc. which wastes quite
|
||||
* a bit of memory
|
||||
*/
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void replaceDuplicateObjects(LevelChunk wrapped, boolean allowWrites, CallbackInfo ci) {
|
||||
this.sections = wrapped.getSections();
|
||||
this.skyLightSources = wrapped.getSkyLightSources();
|
||||
}
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ public class BlockStateDataMixin {
|
|||
t = compactTag(ct);
|
||||
}
|
||||
t = TAG_INTERNER.addOrGet(t);
|
||||
entries[i++] = Map.entry(key, t);
|
||||
entries[i++] = Map.entry(key.intern(), t);
|
||||
}
|
||||
return new CompoundTag(Map.ofEntries(entries));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.compact_mojang_registries;
|
||||
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.core.RegistrationInfo;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import org.embeddedt.modernfix.annotation.IgnoreOutsideDev;
|
||||
import org.embeddedt.modernfix.registry.LifecycleMap;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
|
|
@ -21,10 +20,10 @@ public abstract class MappedRegistryMixin<T> {
|
|||
@Shadow
|
||||
@Final
|
||||
@Mutable
|
||||
private Map<ResourceKey<T>, RegistrationInfo> registrationInfos;
|
||||
private Map<T, Lifecycle> lifecycles;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void replaceStorage(CallbackInfo ci) {
|
||||
this.registrationInfos = new LifecycleMap<>();
|
||||
this.lifecycles = new LifecycleMap<>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.compress_unihex_font;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.client.gui.font.providers.GlyphProviderDefinition;
|
||||
import net.minecraft.client.gui.font.providers.GlyphProviderType;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.render.font.LazyGlyphProvider;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(GlyphProviderType.class)
|
||||
@ClientOnlyMixin
|
||||
public class GlyphProviderTypeMixin {
|
||||
@ModifyExpressionValue(method = "<clinit>", at = @At(value = "FIELD", opcode = Opcodes.GETSTATIC, target = "Lnet/minecraft/client/gui/font/providers/UnihexProvider$Definition;CODEC:Lcom/mojang/serialization/MapCodec;"))
|
||||
private static MapCodec<? extends GlyphProviderDefinition> lazyUnihex(MapCodec<? extends GlyphProviderDefinition> codec) {
|
||||
return LazyGlyphProvider.wrap(codec);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ import java.util.concurrent.ExecutorService;
|
|||
@Mixin(Minecraft.class)
|
||||
@ClientOnlyMixin
|
||||
public class MinecraftMixin {
|
||||
@Redirect(method = { "<init>", "reloadResourcePacks(ZLnet/minecraft/client/Minecraft$GameLoadCookie;)Ljava/util/concurrent/CompletableFuture;" }, at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;backgroundExecutor()Ljava/util/concurrent/ExecutorService;", ordinal = 0))
|
||||
@Redirect(method = { "<init>", "reloadResourcePacks(Z)Ljava/util/concurrent/CompletableFuture;" }, at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;backgroundExecutor()Ljava/util/concurrent/ExecutorService;", ordinal = 0))
|
||||
private ExecutorService getResourceReloadExecutor() {
|
||||
return ModernFix.resourceReloadExecutor();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import java.util.concurrent.Executor;
|
|||
|
||||
@Mixin(MinecraftServer.class)
|
||||
public class MinecraftServerMixin {
|
||||
@ModifyArg(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ReloadableServerResources;loadResources(Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/core/LayeredRegistryAccess;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/world/flag/FeatureFlagSet;Lnet/minecraft/commands/Commands$CommandSelection;ILjava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 5)
|
||||
private Executor getReloadExecutor(Executor asyncExecutor) {
|
||||
return ModernFix.resourceReloadExecutor();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import java.util.Map;
|
|||
*/
|
||||
@Mixin(WallBlock.class)
|
||||
public abstract class WallBlockMixin extends Block {
|
||||
private static Map<ImmutableList<Float>, Pair<Map<Map<Property<?>, Comparable<?>>, VoxelShape>, StateDefinition<Block, BlockState>>> CACHE_BY_SHAPE_VALS = new HashMap<>();
|
||||
private static Map<ImmutableList<Float>, Pair<Map<ImmutableMap<Property<?>, Comparable<?>>, VoxelShape>, StateDefinition<Block, BlockState>>> CACHE_BY_SHAPE_VALS = new HashMap<>();
|
||||
|
||||
public WallBlockMixin(Properties properties) {
|
||||
super(properties);
|
||||
|
|
@ -34,7 +34,7 @@ public abstract class WallBlockMixin extends Block {
|
|||
@Inject(method = "makeShapes", at = @At("HEAD"), cancellable = true)
|
||||
private synchronized void useCachedShapeMap(float f1, float f2, float f3, float f4, float f5, float f6, CallbackInfoReturnable<Map<BlockState, VoxelShape>> cir) {
|
||||
ImmutableList<Float> key = ImmutableList.of(f1, f2, f3, f4, f5, f6);
|
||||
Pair<Map<Map<Property<?>, Comparable<?>>, VoxelShape>, StateDefinition<Block, BlockState>> cache = CACHE_BY_SHAPE_VALS.get(key);
|
||||
Pair<Map<ImmutableMap<Property<?>, Comparable<?>>, VoxelShape>, StateDefinition<Block, BlockState>> cache = CACHE_BY_SHAPE_VALS.get(key);
|
||||
// require the properties to be identical
|
||||
if(cache == null || !cache.getSecond().getProperties().equals(this.stateDefinition.getProperties()))
|
||||
return;
|
||||
|
|
@ -55,7 +55,7 @@ public abstract class WallBlockMixin extends Block {
|
|||
return;
|
||||
ImmutableList<Float> key = ImmutableList.of(f1, f2, f3, f4, f5, f6);
|
||||
if(!CACHE_BY_SHAPE_VALS.containsKey(key)) {
|
||||
Map<Map<Property<?>, Comparable<?>>, VoxelShape> cacheByProperties = new HashMap<>();
|
||||
Map<ImmutableMap<Property<?>, Comparable<?>>, VoxelShape> cacheByProperties = new HashMap<>();
|
||||
Map<BlockState, VoxelShape> shapeMap = cir.getReturnValue();
|
||||
for(Map.Entry<BlockState, VoxelShape> entry : shapeMap.entrySet()) {
|
||||
cacheByProperties.put(entry.getKey().getValues(), entry.getValue());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_dfu;
|
||||
|
||||
import com.mojang.datafixers.DSL;
|
||||
import com.mojang.datafixers.types.Type;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/**
|
||||
* Prevent fetchChoiceType calls from loading DFU early. Vanilla doesn't need the return values here.
|
||||
*/
|
||||
@Mixin(BlockEntityType.class)
|
||||
public class BlockEntityTypeMixin {
|
||||
@Redirect(method = "register", at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;fetchChoiceType(Lcom/mojang/datafixers/DSL$TypeReference;Ljava/lang/String;)Lcom/mojang/datafixers/types/Type;"))
|
||||
private static Type<?> skipSchemaCheck(DSL.TypeReference ref, String s) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,33 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_dfu;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import com.mojang.datafixers.DataFixerBuilder;
|
||||
import com.mojang.datafixers.DSL;
|
||||
import com.mojang.datafixers.DataFixer;
|
||||
import net.minecraft.util.datafix.DataFixers;
|
||||
import org.embeddedt.modernfix.dfu.DFUBlaster;
|
||||
import org.embeddedt.modernfix.dfu.LazyDataFixer;
|
||||
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.Set;
|
||||
|
||||
@Mixin(DataFixers.class)
|
||||
public class DataFixersMixin {
|
||||
@ModifyReturnValue(method = "createFixerUpper", at = @At("RETURN"))
|
||||
private static DataFixerBuilder.Result setupMapBlasting(DataFixerBuilder.Result original) {
|
||||
DFUBlaster.blastMaps();
|
||||
return original;
|
||||
public abstract class DataFixersMixin {
|
||||
@Shadow protected static DataFixer createFixerUpper(Set<DSL.TypeReference> set) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
private static LazyDataFixer lazyDataFixer;
|
||||
|
||||
/**
|
||||
* Avoid classloading the DFU logic until we actually need it.
|
||||
*/
|
||||
@Inject(method = "createFixerUpper", at = @At("HEAD"), cancellable = true)
|
||||
private static void createLazyFixerUpper(Set<DSL.TypeReference> set, CallbackInfoReturnable<DataFixer> cir) {
|
||||
if(lazyDataFixer == null) {
|
||||
lazyDataFixer = new LazyDataFixer(() -> createFixerUpper(set));
|
||||
cir.setReturnValue(lazyDataFixer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_dfu;
|
||||
|
||||
import com.mojang.datafixers.DSL;
|
||||
import com.mojang.datafixers.types.Type;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/**
|
||||
* Prevent fetchChoiceType calls from loading DFU early. Vanilla doesn't need the return values here.
|
||||
*/
|
||||
@Mixin(EntityType.Builder.class)
|
||||
public class EntityTypeBuilderMixin {
|
||||
@Redirect(method = "build", at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;fetchChoiceType(Lcom/mojang/datafixers/DSL$TypeReference;Ljava/lang/String;)Lcom/mojang/datafixers/types/Type;"))
|
||||
private Type<?> skipSchemaCheck(DSL.TypeReference ref, String s) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_languages;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
|
||||
import net.minecraft.client.resources.language.ClientLanguage;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.dynamiclanguages.DynamicLanguageMap;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Modifies the language system to load/unload the contents of language entries based on GC pressure.
|
||||
*/
|
||||
@Mixin(value = ClientLanguage.class, priority = 2000)
|
||||
@ClientOnlyMixin
|
||||
public class ClientLanguageMixin {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason collect the list of all known language resources
|
||||
*/
|
||||
@WrapOperation(method = "loadFrom", at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/resources/language/ClientLanguage;appendFrom(Ljava/lang/String;Ljava/util/List;Ljava/util/Map;)V"))
|
||||
private static void collectResources(String languageName, List<Resource> resources,
|
||||
Map<String, String> destinationMap, Operation<Void> original,
|
||||
@Share("usedResources") LocalRef<List<Resource>> usedResources) {
|
||||
List<Resource> collected = usedResources.get();
|
||||
if (collected == null) {
|
||||
collected = new ArrayList<>();
|
||||
usedResources.set(collected);
|
||||
}
|
||||
collected.addAll(resources);
|
||||
original.call(languageName, resources, destinationMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason figure out which keys are dynamically loaded and which are injected by mixins
|
||||
*/
|
||||
@ModifyArg(method = "loadFrom", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/language/ClientLanguage;<init>(Ljava/util/Map;Z)V"), index = 0)
|
||||
private static Map<String, String> modifyLanguageMap(Map<String, String> storage, @Share("usedResources") LocalRef<List<Resource>> usedResources) {
|
||||
List<Resource> collected = Objects.requireNonNullElse(usedResources.get(), List.of());
|
||||
return DynamicLanguageMap.forVanillaData(storage, collected);
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ public class BlockModelShaperMixin {
|
|||
@Inject(method = { "<init>", "replaceCache" }, at = @At("RETURN"))
|
||||
private void replaceModelMap(CallbackInfo ci) {
|
||||
// replace the backing map for mods which will access it
|
||||
this.modelByStateCache = new DynamicOverridableMap<>(BlockState.class, 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()) {
|
||||
|
|
|
|||
|
|
@ -1,117 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceObjectImmutablePair;
|
||||
import net.minecraft.client.color.block.BlockColors;
|
||||
import net.minecraft.client.renderer.block.model.BlockModelDefinition;
|
||||
import net.minecraft.client.resources.model.BlockStateModelLoader;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.DefaultedRegistry;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.duck.IBlockStateModelLoader;
|
||||
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.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Mixin(BlockStateModelLoader.class)
|
||||
@ClientOnlyMixin
|
||||
public abstract class BlockStateModelLoaderMixin implements IBlockStateModelLoader {
|
||||
@Shadow protected abstract void loadBlockStateDefinitions(ResourceLocation resourceLocation, StateDefinition<Block, BlockState> stateDefinition);
|
||||
|
||||
@Shadow @Mutable @Final private Object2IntMap<BlockState> modelGroups;
|
||||
|
||||
private ImmutableList<BlockState> filteredStates;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void makeModelGroupsSynchronized(Map map, ProfilerFiller profilerFiller, UnbakedModel unbakedModel, BlockColors blockColors, BiConsumer biConsumer, CallbackInfo ci) {
|
||||
this.modelGroups = Object2IntMaps.synchronize(this.modelGroups);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadSpecificBlock(ModelResourceLocation location) {
|
||||
var optionalBlock = BuiltInRegistries.BLOCK.getOptional(location.id());
|
||||
if(optionalBlock.isPresent()) {
|
||||
// embeddedt note - filtering is currently disabled as it's quite inefficient to do vs. just loading
|
||||
// the extra models and letting LRU deal with it
|
||||
/*
|
||||
try {
|
||||
// Only filter states if we are in a world and not in the loading overlay
|
||||
filteredStates = (Minecraft.getInstance().getOverlay() == null && Minecraft.getInstance().level != null) ? ModelBakeryHelpers.getBlockStatesForMRL(optionalBlock.get().getStateDefinition(), location) : null;
|
||||
} catch(RuntimeException e) {
|
||||
ModernFix.LOGGER.error("Exception filtering states on {}", location, e);
|
||||
filteredStates = null;
|
||||
}
|
||||
*/
|
||||
try {
|
||||
this.loadBlockStateDefinitions(location.id(), optionalBlock.get().getStateDefinition());
|
||||
} finally {
|
||||
filteredStates = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "loadAllBlockStates", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/DefaultedRegistry;iterator()Ljava/util/Iterator;"))
|
||||
private Iterator<?> skipIteratingBlocks(DefaultedRegistry instance) {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
|
||||
@Redirect(method = "loadBlockStateDefinitions", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;"))
|
||||
private ImmutableList<BlockState> getFilteredStates(StateDefinition<Block, BlockState> instance) {
|
||||
return this.filteredStates != null ? this.filteredStates : instance.getPossibleStates();
|
||||
}
|
||||
|
||||
// Add some caching around key hot paths
|
||||
|
||||
private final Cache<ReferenceObjectImmutablePair<BlockStateModelLoader.LoadedJson, ResourceLocation>, BlockModelDefinition> cachedBlockModelDefs = CacheBuilder.newBuilder()
|
||||
.maximumSize(100)
|
||||
.build();
|
||||
|
||||
private static final Cache<Pair<StateDefinition<Block, BlockState>, String>, Predicate<BlockState>> cachedBlockStatePredicates = CacheBuilder.newBuilder()
|
||||
.maximumSize(100)
|
||||
.build();
|
||||
|
||||
@WrapOperation(method = "loadBlockStateDefinitions", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/BlockStateModelLoader$LoadedJson;parse(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/renderer/block/model/BlockModelDefinition$Context;)Lnet/minecraft/client/renderer/block/model/BlockModelDefinition;"))
|
||||
private BlockModelDefinition avoidMultipleParses(BlockStateModelLoader.LoadedJson instance, ResourceLocation blockStateId, BlockModelDefinition.Context context, Operation<BlockModelDefinition> original) {
|
||||
try {
|
||||
return cachedBlockModelDefs.get(ReferenceObjectImmutablePair.of(instance, blockStateId), () -> original.call(instance, blockStateId, context));
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@WrapMethod(method = "predicate")
|
||||
private static Predicate<BlockState> memoizePredicate(StateDefinition<Block, BlockState> stateDefentition, String properties, Operation<Predicate<BlockState>> original) {
|
||||
try {
|
||||
return cachedBlockStatePredicates.get(Pair.of(stateDefentition, properties), () -> original.call(stateDefentition, properties));
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,16 +3,17 @@ package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
|||
import com.google.common.base.Stopwatch;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.neoforged.bus.api.Event;
|
||||
import net.neoforged.fml.ModContainer;
|
||||
import net.neoforged.fml.ModList;
|
||||
import net.neoforged.fml.ModLoader;
|
||||
import net.neoforged.fml.util.ObfuscationReflectionHelper;
|
||||
import net.neoforged.neoforge.client.ClientHooks;
|
||||
import net.neoforged.neoforge.client.event.ModelEvent;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.client.ForgeHooksClient;
|
||||
import net.minecraftforge.client.event.ModelEvent;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import net.minecraftforge.fml.ModLoader;
|
||||
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.neoforge.dynresources.ModelBakeEventHelper;
|
||||
import org.embeddedt.modernfix.dynamicresources.DynamicBakedModelProvider;
|
||||
import org.embeddedt.modernfix.forge.dynresources.ModelBakeEventHelper;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
|
@ -23,14 +24,14 @@ import java.util.Comparator;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Mixin(ClientHooks.class)
|
||||
@Mixin(ForgeHooksClient.class)
|
||||
public class ForgeHooksClientMixin {
|
||||
/**
|
||||
* Generate a more realistic keySet that contains every item and block model location, to help with mod compat.
|
||||
*/
|
||||
@Redirect(method = "onModifyBakingResult", at = @At(value = "INVOKE", target = "Lnet/neoforged/fml/ModLoader;postEvent(Lnet/neoforged/bus/api/Event;)V"), remap = false)
|
||||
private static void postNamespacedKeySetEvent(Event event) {
|
||||
if(ModLoader.hasErrors())
|
||||
@Redirect(method = "onModifyBakingResult", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/fml/ModLoader;postEvent(Lnet/minecraftforge/eventbus/api/Event;)V"), remap = false)
|
||||
private static void postNamespacedKeySetEvent(ModLoader loader, Event event) {
|
||||
if(!ModLoader.isLoadingStateValid())
|
||||
return;
|
||||
ModelEvent.ModifyBakingResult bakeEvent = ((ModelEvent.ModifyBakingResult)event);
|
||||
Stopwatch globalTimer = Stopwatch.createStarted();
|
||||
|
|
@ -41,8 +42,8 @@ public class ForgeHooksClientMixin {
|
|||
Map<String, Stopwatch> times = new Object2ObjectOpenHashMap<>();
|
||||
times.put("modernfix", selfTimer);
|
||||
ModList.get().forEachModContainer((id, mc) -> {
|
||||
Map<ModelResourceLocation, BakedModel> newRegistry = helper.wrapRegistry(id);
|
||||
ModelEvent.ModifyBakingResult postedEvent = new ModelEvent.ModifyBakingResult(newRegistry, bakeEvent.getTextureGetter(), bakeEvent.getModelBakery());
|
||||
Map<ResourceLocation, BakedModel> newRegistry = helper.wrapRegistry(id);
|
||||
ModelEvent.ModifyBakingResult postedEvent = new ModelEvent.ModifyBakingResult(newRegistry, bakeEvent.getModelBakery());
|
||||
Stopwatch timer = times.computeIfAbsent(id, $ -> Stopwatch.createUnstarted());
|
||||
timer.start();
|
||||
try {
|
||||
|
|
@ -62,5 +63,8 @@ public class ForgeHooksClientMixin {
|
|||
ModernFix.LOGGER.warn(" {}: {}", entry.getKey(), entry.getValue().toString());
|
||||
});
|
||||
}
|
||||
if (bakeEvent.getModels() instanceof DynamicBakedModelProvider dynamicProvider) {
|
||||
dynamicProvider.dumpStats();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import net.minecraft.client.renderer.ItemModelShaper;
|
|||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.neoforged.neoforge.client.model.RegistryAwareItemModelShaper;
|
||||
import net.minecraftforge.client.model.ForgeItemModelShaper;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.dynamicresources.DynamicModelCache;
|
||||
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
|
||||
|
|
@ -19,20 +21,20 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(RegistryAwareItemModelShaper.class)
|
||||
@Mixin(ForgeItemModelShaper.class)
|
||||
@ClientOnlyMixin
|
||||
public abstract class ItemModelMesherForgeMixin extends ItemModelShaper {
|
||||
@Shadow(remap = false) @Final @Mutable private Map<Item, ModelResourceLocation> locations;
|
||||
@Shadow(remap = false) @Final @Mutable private Map<Holder.Reference<Item>, ModelResourceLocation> locations;
|
||||
|
||||
private Map<Item, ModelResourceLocation> overrideLocations;
|
||||
private Map<Holder.Reference<Item>, ModelResourceLocation> overrideLocations;
|
||||
|
||||
private final DynamicModelCache<Item> mfix$modelCache = new DynamicModelCache<>(k -> this.mfix$getModelSlow((Item)k), true);
|
||||
private final DynamicModelCache<Holder.Reference<Item>> mfix$modelCache = new DynamicModelCache<>(k -> this.mfix$getModelSlow((Holder.Reference<Item>)k), true);
|
||||
|
||||
public ItemModelMesherForgeMixin(ModelManager arg) {
|
||||
super(arg);
|
||||
}
|
||||
|
||||
private static final ModelResourceLocation SENTINEL = new ModelResourceLocation(ResourceLocation.fromNamespaceAndPath("modernfix", "sentinel"), "sentinel");
|
||||
private static final ModelResourceLocation SENTINEL = new ModelResourceLocation(new ResourceLocation("modernfix", "sentinel"), "sentinel");
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void replaceLocationMap(CallbackInfo ci) {
|
||||
|
|
@ -42,16 +44,16 @@ public abstract class ItemModelMesherForgeMixin extends ItemModelShaper {
|
|||
}
|
||||
|
||||
@Unique
|
||||
private ModelResourceLocation mfix$getLocationForge(Item item) {
|
||||
private ModelResourceLocation mfix$getLocationForge(Holder.Reference<Item> item) {
|
||||
ModelResourceLocation map = overrideLocations.getOrDefault(item, SENTINEL);
|
||||
if(map == SENTINEL) {
|
||||
/* generate the appropriate location from our cache */
|
||||
map = ModelLocationCache.get(item);
|
||||
map = ModelLocationCache.get(item.get());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private BakedModel mfix$getModelSlow(Item key) {
|
||||
private BakedModel mfix$getModelSlow(Holder.Reference<Item> key) {
|
||||
ModelResourceLocation map = mfix$getLocationForge(key);
|
||||
return map == null ? null : getModelManager().getModel(map);
|
||||
}
|
||||
|
|
@ -64,7 +66,7 @@ public abstract class ItemModelMesherForgeMixin extends ItemModelShaper {
|
|||
@Overwrite
|
||||
@Override
|
||||
public BakedModel getItemModel(Item item) {
|
||||
return this.mfix$modelCache.get(item);
|
||||
return this.mfix$modelCache.get(ForgeRegistries.ITEMS.getDelegateOrThrow(item));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -75,7 +77,7 @@ public abstract class ItemModelMesherForgeMixin extends ItemModelShaper {
|
|||
@Overwrite
|
||||
@Override
|
||||
public void register(Item item, ModelResourceLocation location) {
|
||||
overrideLocations.put(item, location);
|
||||
overrideLocations.put(ForgeRegistries.ITEMS.getDelegateOrThrow(item), location);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public abstract class ItemModelShaperMixin {
|
|||
super();
|
||||
}
|
||||
|
||||
private static final ModelResourceLocation SENTINEL_VANILLA = new ModelResourceLocation(ResourceLocation.fromNamespaceAndPath("modernfix", "sentinel"), "sentinel");
|
||||
private static final ModelResourceLocation SENTINEL_VANILLA = new ModelResourceLocation(new ResourceLocation("modernfix", "sentinel"), "sentinel");
|
||||
|
||||
private final DynamicModelCache<Item> mfix$itemModelCache = new DynamicModelCache<>(k -> this.mfix$getModelForItem((Item)k), true);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.client.renderer.block.model.ItemOverrides;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.client.resources.model.ModelState;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.duck.IExtendedModelBaker;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(ItemOverrides.class)
|
||||
@ClientOnlyMixin
|
||||
public class ItemOverridesForgeMixin {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason servers insist on generating invalid item overrides that have missing models
|
||||
*/
|
||||
@WrapOperation(method = "bakeModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelBaker;bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;Ljava/util/function/Function;)Lnet/minecraft/client/resources/model/BakedModel;"), remap = false)
|
||||
private BakedModel bake(ModelBaker instance, ResourceLocation resourceLocation, ModelState modelState, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, Operation<BakedModel> original) {
|
||||
boolean prevState = ((IExtendedModelBaker)instance).throwOnMissingModel(false);
|
||||
try {
|
||||
return original.call(instance, resourceLocation, modelState, spriteGetter);
|
||||
} finally {
|
||||
((IExtendedModelBaker)instance).throwOnMissingModel(prevState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.client.renderer.block.model.ItemOverrides;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.duck.IExtendedModelBaker;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(ItemOverrides.class)
|
||||
@ClientOnlyMixin
|
||||
public class ItemOverridesMixin {
|
||||
@WrapOperation(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelBaker;getModel(Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/resources/model/UnbakedModel;"))
|
||||
private UnbakedModel preventThrowForMissing(ModelBaker instance, ResourceLocation resourceLocation, Operation<UnbakedModel> original) {
|
||||
boolean prevState = ((IExtendedModelBaker)instance).throwOnMissingModel(false);
|
||||
try {
|
||||
return original.call(instance, resourceLocation);
|
||||
} finally {
|
||||
((IExtendedModelBaker)instance).throwOnMissingModel(prevState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.duck.IExtendedModelManager;
|
||||
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 abstract class MinecraftMixin_ModelTicking {
|
||||
@Shadow public abstract ModelManager getModelManager();
|
||||
|
||||
@Inject(method = "tick", at = @At(value = "RETURN"))
|
||||
private void tickModels(CallbackInfo ci) {
|
||||
((IExtendedModelManager)this.getModelManager()).mfix$tick();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +1,102 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.Material;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.client.resources.model.ModelState;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.client.resources.model.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.ModernFixClient;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
|
||||
import org.embeddedt.modernfix.duck.IExtendedModelBaker;
|
||||
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
|
||||
import org.embeddedt.modernfix.dynamicresources.ModelMissingException;
|
||||
import org.embeddedt.modernfix.forge.dynresources.IModelBakerImpl;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
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.function.Function;
|
||||
|
||||
@Mixin(ModelBakery.ModelBakerImpl.class)
|
||||
@Mixin(value = ModelBakery.ModelBakerImpl.class, priority = 600)
|
||||
@ClientOnlyMixin
|
||||
public abstract class ModelBakerImplMixin {
|
||||
@Shadow public abstract UnbakedModel getModel(ResourceLocation location);
|
||||
public abstract class ModelBakerImplMixin implements IModelBakerImpl, IExtendedModelBaker {
|
||||
private static final boolean debugDynamicModelLoading = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
|
||||
@Shadow(aliases = {"this$0","f_243927_"}) @Final private ModelBakery field_40571;
|
||||
|
||||
@Shadow(aliases = {"this$0"}) @Final private ModelBakery field_40571;
|
||||
@Unique
|
||||
private int mfix$getDepth = 0;
|
||||
private boolean mfix$ignoreCache = false;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason force parent resolution to happen before model gets baked
|
||||
*/
|
||||
@ModifyReturnValue(method = "getModel", at = @At("RETURN"))
|
||||
private UnbakedModel resolveParents(UnbakedModel model) {
|
||||
mfix$getDepth++;
|
||||
if(mfix$getDepth == 1) {
|
||||
try {
|
||||
model.resolveParents(this::getModel);
|
||||
} catch(Exception e) {
|
||||
ModernFix.LOGGER.warn("Exception encountered resolving parents", e);
|
||||
@Shadow @Final private Function<Material, TextureAtlasSprite> modelTextureGetter;
|
||||
|
||||
@Override
|
||||
public void mfix$ignoreCache() {
|
||||
mfix$ignoreCache = true;
|
||||
}
|
||||
|
||||
private boolean throwIfMissing;
|
||||
|
||||
@Override
|
||||
public boolean throwOnMissingModel(boolean flag) {
|
||||
boolean old = throwIfMissing;
|
||||
throwIfMissing = flag;
|
||||
return old;
|
||||
}
|
||||
|
||||
@Inject(method = "getModel", at = @At("HEAD"), cancellable = true)
|
||||
private void obtainModel(ResourceLocation arg, CallbackInfoReturnable<UnbakedModel> cir) {
|
||||
if(debugDynamicModelLoading)
|
||||
ModernFix.LOGGER.info("Baking {}", arg);
|
||||
IExtendedModelBakery extendedBakery = (IExtendedModelBakery)this.field_40571;
|
||||
if(arg instanceof ModelResourceLocation && arg != ModelBakery.MISSING_MODEL_LOCATION) {
|
||||
// synchronized because we use topLevelModels
|
||||
synchronized (this.field_40571) {
|
||||
this.field_40571.loadTopLevel((ModelResourceLocation)arg);
|
||||
cir.setReturnValue(this.field_40571.topLevelModels.getOrDefault(arg, extendedBakery.mfix$getUnbakedMissingModel()));
|
||||
// avoid leaks
|
||||
this.field_40571.topLevelModels.clear();
|
||||
}
|
||||
} else
|
||||
cir.setReturnValue(this.field_40571.getModel(arg));
|
||||
UnbakedModel toReplace = cir.getReturnValue();
|
||||
if(true) {
|
||||
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
|
||||
try {
|
||||
toReplace = integration.onUnbakedModelPreBake(arg, toReplace, this.field_40571);
|
||||
} catch(RuntimeException e) {
|
||||
ModernFix.LOGGER.error("Exception firing model pre-bake event for {}", arg, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
mfix$getDepth--;
|
||||
cir.setReturnValue(toReplace);
|
||||
cir.getReturnValue().resolveParents(this.field_40571::getModel);
|
||||
if(cir.getReturnValue() == extendedBakery.mfix$getUnbakedMissingModel()) {
|
||||
if(arg != ModelBakery.MISSING_MODEL_LOCATION) {
|
||||
if(debugDynamicModelLoading)
|
||||
ModernFix.LOGGER.warn("Model {} not present", arg);
|
||||
if(throwIfMissing)
|
||||
throw new ModelMissingException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;Ljava/util/function/Function;)Lnet/minecraft/client/resources/model/BakedModel;", at = @At(value = "INVOKE", target = "Ljava/util/Map;get(Ljava/lang/Object;)Ljava/lang/Object;", ordinal = 0), remap = false)
|
||||
private Object ignoreCacheIfRequested(Object o) {
|
||||
return mfix$ignoreCache ? null : o;
|
||||
}
|
||||
|
||||
@WrapOperation(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;Ljava/util/function/Function;)Lnet/minecraft/client/resources/model/BakedModel;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/UnbakedModel;bake(Lnet/minecraft/client/resources/model/ModelBaker;Ljava/util/function/Function;Lnet/minecraft/client/resources/model/ModelState;Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/resources/model/BakedModel;"))
|
||||
private BakedModel callBakedModelIntegration(UnbakedModel unbakedModel, ModelBaker baker, Function<Material, TextureAtlasSprite> spriteGetter, ModelState state, ResourceLocation location, Operation<BakedModel> operation) {
|
||||
BakedModel model = operation.call(unbakedModel, baker, spriteGetter, state, location);
|
||||
|
||||
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
|
||||
model = integration.onBakedModelLoad(location, unbakedModel, model, state, this.field_40571, spriteGetter);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
@WrapMethod(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;)Lnet/minecraft/client/resources/model/BakedModel;")
|
||||
private BakedModel mfix$lockWhenBaking(ResourceLocation location, ModelState transform, Operation<BakedModel> original) {
|
||||
var lock = ((IExtendedModelBakery)this.field_40571).mfix$getLock();
|
||||
lock.lock();
|
||||
try {
|
||||
return original.call(location, transform);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Handle dynamic model loading
|
||||
*/
|
||||
@Overwrite(remap = false)
|
||||
public UnbakedModel getTopLevelModel(ModelResourceLocation location) {
|
||||
IExtendedModelBakery bakery = (IExtendedModelBakery)this.field_40571;
|
||||
UnbakedModel model = bakery.mfix$loadUnbakedModelDynamic(location);
|
||||
return model == bakery.mfix$getMissingModel() ? null : model;
|
||||
}
|
||||
|
||||
@WrapMethod(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;Ljava/util/function/Function;)Lnet/minecraft/client/resources/model/BakedModel;", remap = false)
|
||||
private BakedModel mfix$lockWhenBaking(ResourceLocation location, ModelState transform, Function<Material, TextureAtlasSprite> textureGetter, Operation<BakedModel> original) {
|
||||
var lock = ((IExtendedModelBakery)this.field_40571).mfix$getLock();
|
||||
lock.lock();
|
||||
try {
|
||||
return original.call(location, transform, textureGetter);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,274 +1,412 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.RemovalCause;
|
||||
import com.google.common.cache.RemovalNotification;
|
||||
import com.google.common.collect.ForwardingMap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.color.block.BlockColors;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.BlockModelRotation;
|
||||
import net.minecraft.client.resources.model.BlockStateModelLoader;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.DefaultedRegistry;
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.ModernFixClient;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
|
||||
import org.embeddedt.modernfix.duck.IBlockStateModelLoader;
|
||||
import org.embeddedt.modernfix.duck.IExtendedModelBaker;
|
||||
import org.embeddedt.modernfix.dynamicresources.DynamicBakedModelProvider;
|
||||
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
|
||||
import org.embeddedt.modernfix.util.DynamicOverridableMap;
|
||||
import org.embeddedt.modernfix.util.LRUMap;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.dynamicresources.ModelBakeryHelpers;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
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.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@Mixin(ModelBakery.class)
|
||||
/* low priority so that our injectors are added after other mods' */
|
||||
@Mixin(value = ModelBakery.class, priority = 1100)
|
||||
@ClientOnlyMixin
|
||||
public abstract class ModelBakeryMixin implements IExtendedModelBakery {
|
||||
@Unique
|
||||
private BlockStateModelLoader dynamicLoader;
|
||||
|
||||
@Unique
|
||||
private final ReentrantLock modelBakeryLock = new ReentrantLock();
|
||||
private static final boolean debugDynamicModelLoading = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
|
||||
|
||||
@Unique
|
||||
private ModelBakery.TextureGetter textureGetter;
|
||||
@Shadow @Final @Mutable public Map<ResourceLocation, UnbakedModel> unbakedCache;
|
||||
|
||||
@Unique
|
||||
private BakedModel bakedMissingModel;
|
||||
@Shadow @Final public static ModelResourceLocation MISSING_MODEL_LOCATION;
|
||||
|
||||
@Shadow abstract UnbakedModel getModel(ResourceLocation resourceLocation);
|
||||
@Shadow protected abstract BlockModel loadBlockModel(ResourceLocation location) throws IOException;
|
||||
|
||||
@Shadow @Final private UnbakedModel missingModel;
|
||||
@Shadow @Final private Set<ResourceLocation> loadingStack;
|
||||
|
||||
@Unique
|
||||
private static final boolean DEBUG_MODEL_LOADS = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
|
||||
@Shadow protected abstract void loadModel(ResourceLocation blockstateLocation) throws Exception;
|
||||
|
||||
/**
|
||||
* Bake a model using the provided texture getter and location. The model is stored in {@link ModelBakeryMixin#bakedTopLevelModels}.
|
||||
*/
|
||||
@Shadow(aliases = "lambda$bakeModels$6") protected abstract void method_61072(ModelBakery.TextureGetter getter, ModelResourceLocation location, UnbakedModel model);
|
||||
@Shadow @Final @Mutable
|
||||
private Map<ResourceLocation, BakedModel> bakedTopLevelModels;
|
||||
|
||||
@Shadow @Mutable @Final private Map<ModelResourceLocation, BakedModel> bakedTopLevelModels;
|
||||
@Shadow @Mutable @Final public Map<ModelResourceLocation, UnbakedModel> topLevelModels;
|
||||
@Shadow @Mutable @Final private Map<ResourceLocation, UnbakedModel> unbakedCache;
|
||||
@Shadow @Mutable @Final public Map<ModelBakery.BakedCacheKey, BakedModel> bakedCache;
|
||||
@Shadow @Final @Mutable private Map<ModelBakery.BakedCacheKey, BakedModel> bakedCache;
|
||||
|
||||
@Shadow protected abstract void loadItemModelAndDependencies(ResourceLocation resourceLocation);
|
||||
@Shadow @Final @Mutable private BlockColors blockColors;
|
||||
|
||||
@Shadow @Final private static Logger LOGGER;
|
||||
|
||||
@Shadow
|
||||
public abstract void loadTopLevel(ModelResourceLocation modelResourceLocation);
|
||||
|
||||
@Shadow public abstract UnbakedModel getModel(ResourceLocation resourceLocation);
|
||||
|
||||
private Cache<ModelBakery.BakedCacheKey, BakedModel> loadedBakedModels;
|
||||
|
||||
private Cache<ResourceLocation, UnbakedModel> loadedModels;
|
||||
|
||||
private HashMap<ResourceLocation, UnbakedModel> smallLoadingCache = new HashMap<>();
|
||||
|
||||
private boolean ignoreModelLoad;
|
||||
|
||||
// disable fabric recursion
|
||||
@SuppressWarnings("unused")
|
||||
private boolean fabric_enableGetOrLoadModelGuard;
|
||||
|
||||
|
||||
@Shadow @Final public static ModelResourceLocation MISSING_MODEL_VARIANT;
|
||||
|
||||
@Shadow protected abstract void registerModelAndLoadDependencies(ModelResourceLocation modelLocation, UnbakedModel model);
|
||||
|
||||
private final Map<ModelResourceLocation, BakedModel> mfix$emulatedBakedRegistry = new DynamicOverridableMap<>(ModelResourceLocation.class, this::loadBakedModelDynamic);
|
||||
|
||||
@Override
|
||||
public UnbakedModel mfix$loadUnbakedModelDynamic(ModelResourceLocation location) {
|
||||
if(location.equals(MISSING_MODEL_VARIANT)) {
|
||||
return missingModel;
|
||||
}
|
||||
modelBakeryLock.lock();
|
||||
try {
|
||||
UnbakedModel existing = this.topLevelModels.get(location);
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
@Redirect(method = "<init>", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/resources/model/ModelBakery;blockColors:Lnet/minecraft/client/color/block/BlockColors;"))
|
||||
private void replaceTopLevelBakedModels(ModelBakery bakery, BlockColors val) {
|
||||
fabric_enableGetOrLoadModelGuard = false;
|
||||
this.blockColors = val;
|
||||
this.loadedBakedModels = CacheBuilder.newBuilder()
|
||||
.expireAfterAccess(ModelBakeryHelpers.MAX_MODEL_LIFETIME_SECS, TimeUnit.SECONDS)
|
||||
.maximumSize(ModelBakeryHelpers.MAX_BAKED_MODEL_COUNT)
|
||||
.concurrencyLevel(8)
|
||||
.removalListener(this::onModelRemoved)
|
||||
.softValues()
|
||||
.build();
|
||||
this.loadedModels = CacheBuilder.newBuilder()
|
||||
.expireAfterAccess(ModelBakeryHelpers.MAX_MODEL_LIFETIME_SECS, TimeUnit.SECONDS)
|
||||
.maximumSize(ModelBakeryHelpers.MAX_UNBAKED_MODEL_COUNT)
|
||||
.concurrencyLevel(8)
|
||||
.removalListener(this::onModelRemoved)
|
||||
.softValues()
|
||||
.build();
|
||||
this.bakedCache = loadedBakedModels.asMap();
|
||||
ConcurrentMap<ResourceLocation, UnbakedModel> unbakedCacheBackingMap = loadedModels.asMap();
|
||||
this.unbakedCache = new ForwardingMap<ResourceLocation, UnbakedModel>() {
|
||||
@Override
|
||||
protected Map<ResourceLocation, UnbakedModel> delegate() {
|
||||
return unbakedCacheBackingMap;
|
||||
}
|
||||
if(DEBUG_MODEL_LOADS) {
|
||||
ModernFix.LOGGER.info("Loading model {}", location);
|
||||
|
||||
@Override
|
||||
public UnbakedModel put(ResourceLocation key, UnbakedModel value) {
|
||||
smallLoadingCache.put(key, value);
|
||||
return super.put(key, value);
|
||||
}
|
||||
if(location.variant().equals("inventory")) {
|
||||
this.loadItemModelAndDependencies(location.id());
|
||||
} else if (location.variant().equals("fabric_resource") || location.variant().equals("standalone")) {
|
||||
UnbakedModel unbakedModel = this.getModel(location.id());
|
||||
this.registerModelAndLoadDependencies(location, unbakedModel);
|
||||
} else {
|
||||
((IBlockStateModelLoader)dynamicLoader).loadSpecificBlock(location);
|
||||
}
|
||||
return this.topLevelModels.getOrDefault(location, this.missingModel);
|
||||
} finally {
|
||||
modelBakeryLock.unlock();
|
||||
}
|
||||
};
|
||||
this.bakedTopLevelModels = new DynamicBakedModelProvider((ModelBakery)(Object)this, bakedCache);
|
||||
}
|
||||
|
||||
@WrapMethod(method = "getModel")
|
||||
private UnbakedModel mfix$lockWhenGettingModel(ResourceLocation modelLocation, Operation<UnbakedModel> original) {
|
||||
modelBakeryLock.lock();
|
||||
try {
|
||||
return original.call(modelLocation);
|
||||
} finally {
|
||||
modelBakeryLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnbakedModel mfix$getMissingModel() {
|
||||
return missingModel;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private BakedModel loadBakedModelDynamic(ModelResourceLocation location) {
|
||||
if(location.equals(MISSING_MODEL_VARIANT)) {
|
||||
return bakedMissingModel;
|
||||
}
|
||||
BakedModel model;
|
||||
modelBakeryLock.lock();
|
||||
try {
|
||||
model = bakedTopLevelModels.get(location);
|
||||
if(model == null) {
|
||||
UnbakedModel prototype = mfix$loadUnbakedModelDynamic(location);
|
||||
if(prototype == missingModel) {
|
||||
model = bakedMissingModel;
|
||||
} else {
|
||||
prototype.resolveParents(this::getModel);
|
||||
if(DEBUG_MODEL_LOADS) {
|
||||
ModernFix.LOGGER.info("Baking model {}", location);
|
||||
}
|
||||
this.method_61072(this.textureGetter, location, prototype);
|
||||
model = bakedTopLevelModels.remove(location);
|
||||
if(model == null) {
|
||||
ModernFix.LOGGER.error("Failed to load model " + location);
|
||||
model = bakedMissingModel;
|
||||
}
|
||||
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
|
||||
try {
|
||||
model = integration.onBakedModelLoad(location, prototype, model, BlockModelRotation.X0_Y0, (ModelBakery)(Object)this, this.textureGetter);
|
||||
} catch (RuntimeException e) {
|
||||
ModernFix.LOGGER.error("Exception encountered running dynamic resources integration", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
modelBakeryLock.unlock();
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(method = "<init>", at = @At(value = "CONSTANT", args = "stringValue=missing_model"))
|
||||
private String replaceBackingMaps(String original) {
|
||||
this.unbakedCache = new LRUMap<>(this.unbakedCache);
|
||||
this.bakedCache = new LRUMap<>(this.bakedCache);
|
||||
this.topLevelModels = new LRUMap<>(this.topLevelModels);
|
||||
this.bakedTopLevelModels = new LRUMap<>(this.bakedTopLevelModels);
|
||||
@ModifyExpressionValue(method = "<init>", at = @At(value = "FIELD", opcode = Opcodes.GETSTATIC, ordinal = 0, target = "Lnet/minecraft/client/resources/model/ModelBakery;STATIC_DEFINITIONS:Ljava/util/Map;"))
|
||||
private Map<ResourceLocation, StateDefinition<Block, BlockState>> ignoreFutureModelLoads(Map<ResourceLocation, StateDefinition<Block, BlockState>> original) {
|
||||
this.ignoreModelLoad = true;
|
||||
return original;
|
||||
}
|
||||
|
||||
@WrapOperation(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/BlockStateModelLoader;loadAllBlockStates()V"))
|
||||
private void noInitialBlockStateLoad(BlockStateModelLoader instance, Operation<Void> original) {
|
||||
dynamicLoader = instance;
|
||||
original.call(instance);
|
||||
}
|
||||
|
||||
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/DefaultedRegistry;keySet()Ljava/util/Set;"))
|
||||
private Set<?> skipLoadingItems(DefaultedRegistry instance) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
|
||||
@Inject(method = "bakeModels", at = @At("HEAD"))
|
||||
private void storeTextureGetterAndBakeMissing(ModelBakery.TextureGetter textureGetter, CallbackInfo ci) {
|
||||
this.textureGetter = textureGetter;
|
||||
this.method_61072(textureGetter, MISSING_MODEL_VARIANT, Objects.requireNonNull(this.topLevelModels.get(MISSING_MODEL_VARIANT)));
|
||||
this.bakedMissingModel = this.bakedTopLevelModels.get(MISSING_MODEL_VARIANT);
|
||||
}
|
||||
|
||||
private boolean inInitialLoad = true;
|
||||
|
||||
@Inject(method = "bakeModels", at = @At("RETURN"))
|
||||
private void onInitialBakeFinish(ModelBakery.TextureGetter textureGetter, CallbackInfo ci) {
|
||||
var permanentMRLs = new ObjectOpenHashSet<>(this.bakedTopLevelModels.keySet());
|
||||
((LRUMap<ModelResourceLocation, BakedModel>)this.bakedTopLevelModels).setPermanentEntries(permanentMRLs);
|
||||
ModernFix.LOGGER.info("Dynamic model bakery initial baking finished, with {} permanent top level baked models", this.bakedTopLevelModels.size());
|
||||
}
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void onInitialLoadFinish(BlockColors blockColors, ProfilerFiller profilerFiller, Map map, Map map2, CallbackInfo ci) {
|
||||
var permanentMRLs = new ObjectOpenHashSet<>(this.topLevelModels.keySet());
|
||||
((LRUMap<ModelResourceLocation, UnbakedModel>)this.topLevelModels).setPermanentEntries(permanentMRLs);
|
||||
ModernFix.LOGGER.info("Dynamic model bakery loading finished, with {} permanent top level models", this.topLevelModels.size());
|
||||
}
|
||||
|
||||
@Unique
|
||||
private int tickCount;
|
||||
|
||||
@Unique
|
||||
private static final int MAXIMUM_CACHE_SIZE = 1000;
|
||||
|
||||
private void runCleanup() {
|
||||
try {
|
||||
((LRUMap<?, ?>)this.unbakedCache).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
|
||||
} catch(RuntimeException e) {
|
||||
throw new IllegalStateException("Exception dropping entries in unbaked cache", e);
|
||||
}
|
||||
try {
|
||||
((LRUMap<?, ?>)this.bakedCache).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
|
||||
} catch(RuntimeException e) {
|
||||
throw new IllegalStateException("Exception dropping entries in baked cache", e);
|
||||
}
|
||||
try {
|
||||
((LRUMap<?, ?>)this.topLevelModels).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
|
||||
} catch(RuntimeException e) {
|
||||
throw new IllegalStateException("Exception dropping entries in top level models", e);
|
||||
}
|
||||
try {
|
||||
((LRUMap<?, ?>)this.bakedTopLevelModels).dropEntriesToMeetSize(MAXIMUM_CACHE_SIZE);
|
||||
} catch(RuntimeException e) {
|
||||
throw new IllegalStateException("Exception dropping entries in baked top level models", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mfix$finishLoading() {
|
||||
inInitialLoad = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mfix$tick() {
|
||||
if(inInitialLoad) {
|
||||
private <K, V> void onModelRemoved(RemovalNotification<K, V> notification) {
|
||||
if(!debugDynamicModelLoading)
|
||||
return;
|
||||
// If the entry was replaced (happens because of the Minecraft model loading code structure), or
|
||||
// was explicitly removed, we don't really care.
|
||||
var reason = notification.getCause();
|
||||
if (reason == RemovalCause.REPLACED || reason == RemovalCause.EXPLICIT) {
|
||||
return;
|
||||
}
|
||||
tickCount++;
|
||||
if((tickCount % 200) == 0) {
|
||||
if(modelBakeryLock.tryLock()) {
|
||||
try {
|
||||
runCleanup();
|
||||
} finally {
|
||||
modelBakeryLock.unlock();
|
||||
}
|
||||
}
|
||||
Object k = notification.getKey();
|
||||
if(k == null)
|
||||
return;
|
||||
ResourceLocation rl;
|
||||
boolean baked = false;
|
||||
if(k instanceof ResourceLocation) {
|
||||
rl = (ResourceLocation)k;
|
||||
} else {
|
||||
rl = ((ModelBakery.BakedCacheKey)k).id();
|
||||
baked = true;
|
||||
}
|
||||
/* can fire when a model is replaced */
|
||||
if(!baked && this.loadedModels.getIfPresent(rl) != null)
|
||||
return;
|
||||
ModernFix.LOGGER.warn("Evicted {} model {}", baked ? "baked" : "unbaked", rl);
|
||||
}
|
||||
|
||||
private UnbakedModel missingModel;
|
||||
|
||||
private Set<ResourceLocation> blockStateFiles;
|
||||
private Set<ResourceLocation> modelFiles;
|
||||
|
||||
@ModifyArg(method = "<init>", at = @At(value = "INVOKE", target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", ordinal = 0), index = 1)
|
||||
private Object captureMissingModel(Object model) {
|
||||
this.missingModel = (UnbakedModel)model;
|
||||
this.blockStateFiles = new HashSet<>();
|
||||
this.modelFiles = new HashSet<>();
|
||||
return this.missingModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason We provide a fake baked registry to the rest of Minecraft, that dynamically loads models.
|
||||
* @reason don't actually load most models
|
||||
*/
|
||||
@Overwrite
|
||||
public Map<ModelResourceLocation, BakedModel> getBakedTopLevelModels() {
|
||||
return this.mfix$emulatedBakedRegistry;
|
||||
@Redirect(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelBakery;loadTopLevel(Lnet/minecraft/client/resources/model/ModelResourceLocation;)V"))
|
||||
private void addTopLevelFile(ModelBakery bakery, ModelResourceLocation location) {
|
||||
if(location == MISSING_MODEL_LOCATION || !this.ignoreModelLoad) {
|
||||
loadTopLevel(location);
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Ljava/util/Map;forEach(Ljava/util/function/BiConsumer;)V", ordinal = 0))
|
||||
private void fetchStaticDefinitions(Map<ResourceLocation, StateDefinition<Block, BlockState>> map, BiConsumer<ResourceLocation, StateDefinition<Block, BlockState>> func) {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;", ordinal = 0))
|
||||
private ImmutableList<BlockState> fetchBlocks(StateDefinition<Block, BlockState> def) {
|
||||
/* no-op */
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Prevent the models provided by RegisterAdditional from being tracked, otherwise the unbaked models will
|
||||
* be loaded, baked, and added to permanent overrides unnecessarily.
|
||||
* We still need to fire the event, because there is a decent chance mods rely on it to set up state for their
|
||||
* model loaders, but we can ignore the return value.
|
||||
*/
|
||||
@WrapOperation(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/client/ForgeHooksClient;onRegisterAdditionalModels(Ljava/util/Set;)V"))
|
||||
private void preventLoadOfAdditionalModels(Set<ResourceLocation> additionalModels, Operation<Void> original) {
|
||||
original.call(additionalModels);
|
||||
// Immediately clear the set
|
||||
additionalModels.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a copy of the top-level model list to avoid CME if more models get loaded here.
|
||||
*/
|
||||
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Ljava/util/Map;values()Ljava/util/Collection;", ordinal = 0))
|
||||
private Collection<?> copyTopLevelModelList(Map<?, ?> map) {
|
||||
return new ArrayList<>(map.values());
|
||||
}
|
||||
|
||||
private BiFunction<ResourceLocation, Material, TextureAtlasSprite> textureGetter;
|
||||
|
||||
@Inject(method = "bakeModels", at = @At("HEAD"))
|
||||
private void captureGetter(BiFunction<ResourceLocation, Material, TextureAtlasSprite> getter, CallbackInfo ci) {
|
||||
this.ignoreModelLoad = false;
|
||||
textureGetter = getter;
|
||||
DynamicBakedModelProvider.currentInstance = (DynamicBakedModelProvider)this.bakedTopLevelModels;
|
||||
}
|
||||
|
||||
@Redirect(method = "bakeModels", at = @At(value = "INVOKE", target = "Ljava/util/Map;keySet()Ljava/util/Set;"))
|
||||
private Set<ResourceLocation> skipBake(Map<ResourceLocation, UnbakedModel> instance) {
|
||||
Set<ResourceLocation> modelSet = new HashSet<>(instance.keySet());
|
||||
if(modelSet.size() > 0)
|
||||
ModernFix.LOGGER.info("Early baking {} models", modelSet.size());
|
||||
return modelSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the already loaded missing model instead of the cache entry (which will probably get evicted).
|
||||
*/
|
||||
@Redirect(method = "loadModel", at = @At(value = "INVOKE", target = "Ljava/util/Map;get(Ljava/lang/Object;)Ljava/lang/Object;", ordinal = 1))
|
||||
private Object getMissingModel(Map map, Object rl) {
|
||||
if(rl == MISSING_MODEL_LOCATION && map == unbakedCache)
|
||||
return missingModel;
|
||||
return unbakedCache.get(rl);
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "cacheAndQueueDependencies", at = @At("HEAD"), argsOnly = true)
|
||||
private UnbakedModel fireUnbakedEvent(UnbakedModel model, ResourceLocation location) {
|
||||
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
|
||||
try {
|
||||
model = integration.onUnbakedModelLoad(location, model, (ModelBakery)(Object)this);
|
||||
} catch(RuntimeException e) {
|
||||
ModernFix.LOGGER.error("Exception firing model load event for {}", location, e);
|
||||
}
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
@Inject(method = "cacheAndQueueDependencies", at = @At("RETURN"))
|
||||
private void addToSmallLoadingCache(ResourceLocation location, UnbakedModel model, CallbackInfo ci) {
|
||||
smallLoadingCache.put(location, model);
|
||||
}
|
||||
|
||||
private int mfix$nestedLoads = 0;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason synchronize
|
||||
*/
|
||||
@Inject(method = "getModel", at = @At("HEAD"), cancellable = true)
|
||||
public void getOrLoadModelDynamic(ResourceLocation modelLocation, CallbackInfoReturnable<UnbakedModel> cir) {
|
||||
if(modelLocation.equals(MISSING_MODEL_LOCATION)) {
|
||||
cir.setReturnValue(missingModel);
|
||||
return;
|
||||
}
|
||||
UnbakedModel existing = this.unbakedCache.get(modelLocation);
|
||||
if (existing != null) {
|
||||
cir.setReturnValue(existing);
|
||||
} else {
|
||||
synchronized(this) {
|
||||
// CIT Resewn adds dependencies to loadingStack outside of getModel(), so we must silently
|
||||
// ignore existing things in the stack for the outermost model
|
||||
if (mfix$nestedLoads > 0 && this.loadingStack.contains(modelLocation)) {
|
||||
throw new IllegalStateException("Circular reference while loading " + modelLocation);
|
||||
} else {
|
||||
this.loadingStack.add(modelLocation);
|
||||
UnbakedModel iunbakedmodel = missingModel;
|
||||
|
||||
while(!this.loadingStack.isEmpty()) {
|
||||
ResourceLocation resourcelocation = this.loadingStack.iterator().next();
|
||||
|
||||
mfix$nestedLoads++;
|
||||
try {
|
||||
existing = this.unbakedCache.get(resourcelocation);
|
||||
if (existing == null) {
|
||||
if(debugDynamicModelLoading)
|
||||
LOGGER.info("Loading {}", resourcelocation);
|
||||
this.loadModel(resourcelocation);
|
||||
} else
|
||||
smallLoadingCache.put(resourcelocation, existing);
|
||||
} catch (ModelBakery.BlockStateDefinitionException var9) {
|
||||
LOGGER.warn(var9.getMessage());
|
||||
this.unbakedCache.put(resourcelocation, iunbakedmodel);
|
||||
smallLoadingCache.put(resourcelocation, iunbakedmodel);
|
||||
} catch (Exception var10) {
|
||||
LOGGER.warn("Unable to load model: '{}' referenced from: {}: {}", resourcelocation, modelLocation, var10);
|
||||
this.unbakedCache.put(resourcelocation, iunbakedmodel);
|
||||
smallLoadingCache.put(resourcelocation, iunbakedmodel);
|
||||
} finally {
|
||||
mfix$nestedLoads--;
|
||||
this.loadingStack.remove(resourcelocation);
|
||||
}
|
||||
}
|
||||
|
||||
// We have to get the result from the temporary cache used for a model load
|
||||
// As in pathological cases (e.g. Pedestals on 1.19) unbakedCache can lose
|
||||
// the model immediately
|
||||
UnbakedModel result = smallLoadingCache.getOrDefault(modelLocation, iunbakedmodel);
|
||||
try {
|
||||
// required as some mods (e.g. EBE) call bake directly on the returned model, without resolving parents themselves
|
||||
result.resolveParents(this::getModel);
|
||||
} catch(RuntimeException ignored) {}
|
||||
// We are done with loading, so clear this cache to allow GC of any unneeded models
|
||||
if(mfix$nestedLoads == 0)
|
||||
smallLoadingCache.clear();
|
||||
cir.setReturnValue(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "loadModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;"))
|
||||
private ImmutableList<BlockState> loadOnlyRelevantBlockState(StateDefinition<Block, BlockState> stateDefinition, ResourceLocation location) {
|
||||
var allStates = stateDefinition.getPossibleStates();
|
||||
|
||||
if(!(location instanceof ModelResourceLocation mrl)) {
|
||||
return allStates;
|
||||
}
|
||||
|
||||
// Load a batch of models at once in certain initialization phases to speed up the loading process.
|
||||
// This is disabled when in-game as it will cause stutters when blocks are placed.
|
||||
boolean shouldLoadBatch = (Minecraft.getInstance().getOverlay() != null || Minecraft.getInstance().level == null);
|
||||
int batchSize = ModelBakeryHelpers.MAX_UNBAKED_MODEL_COUNT - 1000;
|
||||
|
||||
// If loading a batch and all the states are smaller than the max batch size, just use them
|
||||
// This is hoisted above the computation of desiredStates for performance reasons
|
||||
if (shouldLoadBatch && allStates.size() <= batchSize) {
|
||||
return allStates;
|
||||
}
|
||||
|
||||
var desiredStates = ModelBakeryHelpers.getBlockStatesForMRL(stateDefinition, mrl);
|
||||
|
||||
// If not loading a batch, load only the desired states
|
||||
if (!shouldLoadBatch) {
|
||||
return desiredStates;
|
||||
}
|
||||
|
||||
// At this point we want to load a batch if possible, but loading every state is too much. If desiredStates
|
||||
// is a single state (should almost always be the case), then we choose a sublist starting from it and extending
|
||||
// batchSize entries (or less if the list ends). If it's multiple states, a single sublist may not include
|
||||
// everything, so we bail.
|
||||
if (desiredStates.size() != 1) {
|
||||
return desiredStates;
|
||||
}
|
||||
|
||||
var desiredState = desiredStates.get(0);
|
||||
int indexInAllStates = allStates.indexOf(desiredState);
|
||||
if (indexInAllStates == -1) {
|
||||
return desiredStates;
|
||||
}
|
||||
return allStates.subList(indexInAllStates, Math.min(indexInAllStates + batchSize, allStates.size()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReentrantLock mfix$getLock() {
|
||||
return this.modelBakeryLock;
|
||||
public BakedModel bakeDefault(ResourceLocation modelLocation, ModelState state) {
|
||||
ModelBakery.BakedCacheKey key = new ModelBakery.BakedCacheKey(modelLocation, BlockModelRotation.X0_Y0.getRotation(), BlockModelRotation.X0_Y0.isUvLocked());
|
||||
BakedModel m = loadedBakedModels.getIfPresent(key);
|
||||
if(m != null)
|
||||
return m;
|
||||
ModelBakery self = (ModelBakery) (Object) this;
|
||||
ModelBaker theBaker = self.new ModelBakerImpl(textureGetter, modelLocation);
|
||||
((IExtendedModelBaker)theBaker).throwOnMissingModel(true);
|
||||
synchronized(this) {
|
||||
// We intentionally use the 2-arg overload for better mixin compatibility, because we use the baker's default
|
||||
// texture getter anyway.
|
||||
//noinspection deprecation
|
||||
m = theBaker.bake(modelLocation, state);
|
||||
}
|
||||
if(m != null)
|
||||
loadedBakedModels.put(key, m);
|
||||
return m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableList<BlockState> getBlockStatesForMRL(StateDefinition<Block, BlockState> stateDefinition, ModelResourceLocation location) {
|
||||
return loadOnlyRelevantBlockState(stateDefinition, location);
|
||||
}
|
||||
|
||||
private BakedModel bakedMissingModel = null;
|
||||
|
||||
public void setBakedMissingModel(BakedModel m) {
|
||||
bakedMissingModel = m;
|
||||
}
|
||||
|
||||
public BakedModel getBakedMissingModel() {
|
||||
return bakedMissingModel;
|
||||
}
|
||||
|
||||
public UnbakedModel mfix$getUnbakedMissingModel() {
|
||||
return missingModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mfix$clearModels() {
|
||||
loadedModels.invalidateAll();
|
||||
loadedBakedModels.invalidateAll();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,34 +4,27 @@ import com.google.common.collect.ImmutableList;
|
|||
import com.google.common.collect.Maps;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.AtlasSet;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.BlockStateModelLoader;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
|
||||
import org.embeddedt.modernfix.duck.IExtendedModelManager;
|
||||
import org.embeddedt.modernfix.util.CacheUtil;
|
||||
import org.embeddedt.modernfix.util.LambdaMap;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Coerce;
|
||||
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.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
|
|
@ -48,12 +41,9 @@ import java.util.stream.Collectors;
|
|||
|
||||
@Mixin(ModelManager.class)
|
||||
@ClientOnlyMixin
|
||||
public class ModelManagerMixin implements IExtendedModelManager {
|
||||
public class ModelManagerMixin {
|
||||
@Shadow private Map<ResourceLocation, BakedModel> bakedRegistry;
|
||||
|
||||
@Unique
|
||||
private Runnable tickHandler = () -> {};
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void injectDummyBakedRegistry(CallbackInfo ci) {
|
||||
if(this.bakedRegistry == null) {
|
||||
|
|
@ -64,16 +54,16 @@ public class ModelManagerMixin implements IExtendedModelManager {
|
|||
@ModifyArg(method = "loadBlockModels", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenCompose(Ljava/util/function/Function;)Ljava/util/concurrent/CompletableFuture;", ordinal = 0), index = 0)
|
||||
private static Function<Map<ResourceLocation, Resource>, ? extends CompletionStage<Map<ResourceLocation, BlockModel>>> deferBlockModelLoad(Function<Map<ResourceLocation, Resource>, ? extends CompletionStage<Map<ResourceLocation, BlockModel>>> fn, @Local(ordinal = 0, argsOnly = true) ResourceManager manager) {
|
||||
return resourceMap -> {
|
||||
var cache = CacheUtil.<ResourceLocation, BlockModel>simpleCacheForLambda(location -> loadSingleBlockModel(manager, location), 100L);
|
||||
var fallbackModel = BlockModel.fromString(ModelBakery.MISSING_MODEL_MESH);
|
||||
var cache = CacheUtil.<ResourceLocation, BlockModel>simpleCacheForLambda(location -> loadSingleBlockModel(manager, location, fallbackModel), 100L);
|
||||
return CompletableFuture.completedFuture(Maps.asMap(Set.copyOf(resourceMap.keySet()), location -> cache.getUnchecked(location)));
|
||||
};
|
||||
}
|
||||
|
||||
@Redirect(method = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelManager;loadBlockStates(Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
|
||||
private CompletableFuture<Map<ResourceLocation, List<BlockStateModelLoader.LoadedJson>>> deferBlockStateLoad(ResourceManager manager, Executor executor) {
|
||||
var cache = CacheUtil.<ResourceLocation, List<BlockStateModelLoader.LoadedJson>>simpleCacheForLambda(location -> loadSingleBlockState(manager, location), 100L);
|
||||
var blockStateKeys = Set.copyOf(BlockStateModelLoader.BLOCKSTATE_LISTER.listMatchingResourceStacks(manager).keySet());
|
||||
return CompletableFuture.completedFuture(Maps.asMap(blockStateKeys, location -> cache.getUnchecked(location)));
|
||||
private CompletableFuture<Map<ResourceLocation, List<ModelBakery.LoadedJson>>> deferBlockStateLoad(ResourceManager manager, Executor executor) {
|
||||
var cache = CacheUtil.<ResourceLocation, List<ModelBakery.LoadedJson>>simpleCacheForLambda(location -> loadSingleBlockState(manager, location), 100L);
|
||||
return CompletableFuture.completedFuture(new LambdaMap<>(location -> cache.getUnchecked(location)));
|
||||
}
|
||||
|
||||
@Redirect(method = "loadModels", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;"))
|
||||
|
|
@ -81,40 +71,27 @@ public class ModelManagerMixin implements IExtendedModelManager {
|
|||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
private static BlockModel loadSingleBlockModel(ResourceManager manager, ResourceLocation location) {
|
||||
private static BlockModel loadSingleBlockModel(ResourceManager manager, ResourceLocation location, BlockModel fallbackModel) {
|
||||
return manager.getResource(location).map(resource -> {
|
||||
try (BufferedReader reader = resource.openAsReader()) {
|
||||
return BlockModel.fromStream(reader);
|
||||
} catch(IOException e) {
|
||||
ModernFix.LOGGER.error("Couldn't load model", e);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
// We must return some nonnull value to avoid breaking the map convention. The easiest solution
|
||||
// is to just return a missing model template.
|
||||
ModernFix.LOGGER.error("Couldn't load model {}, substituting missing", location, e);
|
||||
return fallbackModel;
|
||||
}
|
||||
}).orElse(null);
|
||||
}
|
||||
|
||||
private List<BlockStateModelLoader.LoadedJson> loadSingleBlockState(ResourceManager manager, ResourceLocation location) {
|
||||
private List<ModelBakery.LoadedJson> loadSingleBlockState(ResourceManager manager, ResourceLocation location) {
|
||||
return manager.getResourceStack(location).stream().map(resource -> {
|
||||
try (BufferedReader reader = resource.openAsReader()) {
|
||||
return new BlockStateModelLoader.LoadedJson(resource.sourcePackId(), GsonHelper.parse(reader));
|
||||
return new ModelBakery.LoadedJson(resource.sourcePackId(), GsonHelper.parse(reader));
|
||||
} catch(IOException e) {
|
||||
ModernFix.LOGGER.error("Couldn't load blockstate", e);
|
||||
return null;
|
||||
}
|
||||
}).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Inject(method = "loadModels", at = @At("RETURN"))
|
||||
private void storeTicker(ProfilerFiller profilerFiller, Map<ResourceLocation, AtlasSet.StitchResult> map, ModelBakery modelBakery, CallbackInfoReturnable<?> cir) {
|
||||
tickHandler = ((IExtendedModelBakery)modelBakery)::mfix$tick;
|
||||
}
|
||||
|
||||
@Inject(method = "apply", at = @At("RETURN"))
|
||||
private void freezeBakery(@Coerce Object reloadState, ProfilerFiller profilerFiller, CallbackInfo ci, @Local(ordinal = 0) ModelBakery bakery) {
|
||||
((IExtendedModelBakery)bakery).mfix$finishLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mfix$tick() {
|
||||
tickHandler.run();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@ package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources.ctm;
|
|||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.embeddedt.modernfix.ModernFixClient;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.annotation.RequiresMod;
|
||||
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
|
||||
import org.embeddedt.modernfix.forge.dynresources.IModelBakerImpl;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
|
@ -26,6 +29,7 @@ import team.chisel.ctm.client.util.TextureMetadataHandler;
|
|||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(TextureMetadataHandler.class)
|
||||
@RequiresMod("ctm")
|
||||
|
|
@ -41,18 +45,18 @@ public abstract class TextureMetadataHandlerMixin implements ModernFixClientInte
|
|||
ModernFixClient.CLIENT_INTEGRATIONS.add(this);
|
||||
}
|
||||
|
||||
@Inject(method = { "onModelBake(Lnet/neoforged/neoforge/client/event/ModelEvent$BakingCompleted;)V", "onModelBake(Lnet/neoforged/neoforge/client/event/ModelEvent$ModifyBakingResult;)V" }, at = @At("HEAD"), cancellable = true, remap = false)
|
||||
@Inject(method = { "onModelBake(Lnet/minecraftforge/client/event/ModelEvent$ModifyBakingResult;)V", "onModelBake(Lnet/minecraftforge/client/event/ModelEvent$BakingCompleted;)V" }, at = @At("HEAD"), cancellable = true, remap = false)
|
||||
private void noIteration(CallbackInfo ci) {
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel onBakedModelLoad(ModelResourceLocation mrl, UnbakedModel rootModel, BakedModel baked, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter getter) {
|
||||
if (!(baked instanceof AbstractCTMBakedModel) && !baked.isCustomRenderer()) {
|
||||
public BakedModel onBakedModelLoad(ResourceLocation rl, UnbakedModel rootModel, BakedModel baked, ModelState state, ModelBakery bakery) {
|
||||
if (rl instanceof ModelResourceLocation && !(baked instanceof AbstractCTMBakedModel) && !baked.isCustomRenderer()) {
|
||||
Deque<ResourceLocation> dependencies = new ArrayDeque<>();
|
||||
Set<ResourceLocation> seenModels = new HashSet<>();
|
||||
dependencies.push(mrl.id());
|
||||
seenModels.add(mrl.id());
|
||||
dependencies.push(rl);
|
||||
seenModels.add(rl);
|
||||
boolean shouldWrap = false;
|
||||
Set<Pair<String, String>> errors = new HashSet<>();
|
||||
// Breadth-first loop through dependencies, exiting as soon as a CTM texture is found, and skipping duplicates/cycles
|
||||
|
|
@ -60,7 +64,7 @@ public abstract class TextureMetadataHandlerMixin implements ModernFixClientInte
|
|||
ResourceLocation dep = dependencies.pop();
|
||||
UnbakedModel model;
|
||||
try {
|
||||
model = dep == mrl.id() ? rootModel : bakery.getModel(dep);
|
||||
model = dep == rl ? rootModel : bakery.getModel(dep);
|
||||
} catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -88,27 +92,34 @@ public abstract class TextureMetadataHandlerMixin implements ModernFixClientInte
|
|||
if (shouldWrap) {
|
||||
try {
|
||||
baked = wrap(rootModel, baked);
|
||||
handleInit(mrl, baked, bakery, getter);
|
||||
handleInit(rl, baked, bakery);
|
||||
dependencies.clear();
|
||||
} catch (IOException e) {
|
||||
CTM.logger.error("Could not wrap model " + mrl + ". Aborting...", e);
|
||||
CTM.logger.error("Could not wrap model " + rl + ". Aborting...", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return baked;
|
||||
}
|
||||
|
||||
private void handleInit(ModelResourceLocation key, BakedModel wrappedModel, ModelBakery bakery, ModelBakery.TextureGetter spriteGetter) {
|
||||
private void handleInit(ResourceLocation key, BakedModel wrappedModel, ModelBakery bakery) {
|
||||
if(wrappedModel instanceof AbstractCTMBakedModel baked) {
|
||||
IModelCTM var10 = baked.getModel();
|
||||
if (var10 instanceof ModelCTM ctmModel) {
|
||||
if (!ctmModel.isInitialized()) {
|
||||
// Clear the baked cache as upstream CTM does
|
||||
((CTMModelBakeryAccessor)bakery).mfix$getBakedCache().clear();
|
||||
ModelBakery.ModelBakerImpl baker = bakery.new ModelBakerImpl(spriteGetter, key);
|
||||
ctmModel.bake(baker, m -> spriteGetter.get(key, m), BlockModelRotation.X0_Y0);
|
||||
Function<Material, TextureAtlasSprite> spriteGetter = (m) -> {
|
||||
return Minecraft.getInstance().getModelManager().getAtlas(m.atlasLocation()).getSprite(m.texture());
|
||||
};
|
||||
ModelBakery.ModelBakerImpl baker = bakery.new ModelBakerImpl(($, m) -> {
|
||||
return spriteGetter.apply(m);
|
||||
}, key);
|
||||
// bypass bakedCache so that dependent models get re-baked and thus retrieve their sprites again
|
||||
((IModelBakerImpl)baker).mfix$ignoreCache();
|
||||
ctmModel.bake(baker, spriteGetter, BlockModelRotation.X0_Y0, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources.ldlib;
|
|||
|
||||
import com.lowdragmc.lowdraglib.LDLib;
|
||||
import com.lowdragmc.lowdraglib.client.ClientProxy;
|
||||
import com.lowdragmc.lowdraglib.client.model.custommodel.CustomBakedModel;
|
||||
import com.lowdragmc.lowdraglib.client.forge.ClientProxyImpl;
|
||||
import com.lowdragmc.lowdraglib.client.model.custommodel.LDLMetadataSection;
|
||||
import com.lowdragmc.lowdraglib.client.model.forge.CustomBakedModelImpl;
|
||||
import com.lowdragmc.lowdraglib.client.model.forge.LDLRendererModel;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.Material;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
|
|
@ -27,8 +29,9 @@ import java.util.Deque;
|
|||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(ClientProxy.class)
|
||||
@Mixin(ClientProxyImpl.class)
|
||||
@ClientOnlyMixin
|
||||
@RequiresMod("ldlib")
|
||||
public abstract class ClientProxyImplMixin implements ModernFixClientIntegration {
|
||||
|
|
@ -43,11 +46,11 @@ public abstract class ClientProxyImplMixin implements ModernFixClientIntegration
|
|||
}
|
||||
|
||||
@Override
|
||||
public BakedModel onBakedModelLoad(ModelResourceLocation mrl, UnbakedModel rootModel, BakedModel baked, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter textureGetter) {
|
||||
public BakedModel onBakedModelLoad(ResourceLocation rl, UnbakedModel rootModel, BakedModel baked, ModelState state, ModelBakery bakery, Function<Material, TextureAtlasSprite> textureGetter) {
|
||||
if (baked == null) {
|
||||
return null;
|
||||
}
|
||||
if (rootModel != null) {
|
||||
if (rl instanceof ModelResourceLocation && rootModel != null) {
|
||||
if (baked instanceof LDLRendererModel) {
|
||||
return baked;
|
||||
}
|
||||
|
|
@ -56,10 +59,9 @@ public abstract class ClientProxyImplMixin implements ModernFixClientIntegration
|
|||
}
|
||||
Deque<ResourceLocation> dependencies = new ArrayDeque<>();
|
||||
Set<ResourceLocation> seenModels = new HashSet<>();
|
||||
ResourceLocation rl = mrl.id();
|
||||
dependencies.push(rl);
|
||||
seenModels.add(rl);
|
||||
boolean shouldWrap = ClientProxy.WRAPPED_MODELS.getOrDefault(mrl, false);
|
||||
boolean shouldWrap = ClientProxy.WRAPPED_MODELS.getOrDefault(rl, false);
|
||||
// Breadth-first loop through dependencies, exiting as soon as a CTM texture is found, and skipping duplicates/cycles
|
||||
while (!shouldWrap && !dependencies.isEmpty()) {
|
||||
ResourceLocation dep = dependencies.pop();
|
||||
|
|
@ -90,9 +92,9 @@ public abstract class ClientProxyImplMixin implements ModernFixClientIntegration
|
|||
LDLib.LOGGER.error("Error loading baked dependency {} for baked {}. Skipping...", dep, rl, e);
|
||||
}
|
||||
}
|
||||
ClientProxy.WRAPPED_MODELS.put(mrl, shouldWrap);
|
||||
ClientProxy.WRAPPED_MODELS.put(rl, shouldWrap);
|
||||
if (shouldWrap) {
|
||||
return new CustomBakedModel<>(baked);
|
||||
return new CustomBakedModelImpl(baked);
|
||||
}
|
||||
}
|
||||
return baked;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources.supermartijncore;
|
||||
|
||||
import com.supermartijn642.core.registry.ClientRegistrationHandler;
|
||||
import com.supermartijn642.core.util.Pair;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
import net.minecraft.client.resources.model.ModelState;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.embeddedt.modernfix.ModernFixClient;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.annotation.RequiresMod;
|
||||
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
|
||||
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.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Mixin(ClientRegistrationHandler.class)
|
||||
@RequiresMod("supermartijn642corelib")
|
||||
@ClientOnlyMixin
|
||||
public class ClientRegistrationHandlerMixin {
|
||||
@Shadow(remap = false) @Final private List<Pair<Supplier<Stream<ResourceLocation>>, Function<BakedModel, BakedModel>>> modelOverwrites;
|
||||
|
||||
private Map<ResourceLocation, Function<BakedModel, BakedModel>> modelOverwritesByLocation = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
@Redirect(method = "handleModelBakeEvent", at = @At(value = "FIELD", target = "Lcom/supermartijn642/core/registry/ClientRegistrationHandler;modelOverwrites:Ljava/util/List;"), remap = false)
|
||||
private List<?> skipModelOverwrites(ClientRegistrationHandler h) {
|
||||
modelOverwritesByLocation.clear();
|
||||
for(Pair<Supplier<Stream<ResourceLocation>>, Function<BakedModel, BakedModel>> pair : this.modelOverwrites) {
|
||||
Stream<ResourceLocation> locationStream = pair.left().get();
|
||||
Function<BakedModel, BakedModel> swapper = pair.right();
|
||||
locationStream.forEach(l -> {
|
||||
modelOverwritesByLocation.put(l, swapper);
|
||||
});
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void registerDynBake(String modid, CallbackInfo ci) {
|
||||
ModernFixClient.CLIENT_INTEGRATIONS.add(new ModernFixClientIntegration() {
|
||||
@Override
|
||||
public BakedModel onBakedModelLoad(ResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery) {
|
||||
Function<BakedModel, BakedModel> replacer = modelOverwritesByLocation.get(location);
|
||||
if(replacer != null)
|
||||
return replacer.apply(originalModel);
|
||||
else
|
||||
return originalModel;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.encoder_cache_leak;
|
||||
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.util.EncoderCache;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(DataComponents.class)
|
||||
public interface DataComponentsAccessor {
|
||||
@Accessor("ENCODER_CACHE")
|
||||
static EncoderCache mfix$getCache() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.encoder_cache_leak;
|
||||
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import net.minecraft.util.EncoderCache;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(EncoderCache.class)
|
||||
public interface EncoderCacheAccessor {
|
||||
@Accessor("cache")
|
||||
LoadingCache<?, ?> mfix$getCache();
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.encoder_cache_leak;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.server.ReloadableServerResources;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Mixin(ReloadableServerResources.class)
|
||||
public class ReloadableServerResourcesMixin {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Some mods (e.g. KubeJS) may provide a custom DynamicOps instance during resource reload. This instance
|
||||
* can end up being strongly retained by an EncoderCache.Key entry even after the reload finishes. The simplest
|
||||
* fix is to invalidate all entries of the encoder cache after a server-side resource reload, which should not break
|
||||
* mods, as the cache is not guaranteed to persist entries for any length of time due to using both a maximum size
|
||||
* & soft values.
|
||||
*/
|
||||
@ModifyReturnValue(method = "loadResources", at = @At("RETURN"))
|
||||
private static CompletableFuture<ReloadableServerResources> resetEncoderCache(CompletableFuture<ReloadableServerResources> future) {
|
||||
return future.whenComplete((r, t) -> {
|
||||
((EncoderCacheAccessor)DataComponentsAccessor.mfix$getCache()).mfix$getCache().invalidateAll();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.fast_forge_dummies;
|
||||
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
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.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(targets = { "net/minecraftforge/registries/NamespacedWrapper" })
|
||||
public abstract class NamespacedHolderHelperMixin<T> extends MappedRegistry<T> {
|
||||
@Shadow(remap = false) private Map<ResourceLocation, Holder.Reference<T>> holdersByName;
|
||||
|
||||
public NamespacedHolderHelperMixin(ResourceKey<? extends Registry<T>> arg, Lifecycle lifecycle) {
|
||||
super(arg, lifecycle);
|
||||
}
|
||||
|
||||
@Inject(method = "freeze", at = @At(value = "FIELD", opcode = Opcodes.GETFIELD, target = "Lnet/minecraftforge/registries/NamespacedWrapper;holdersByName:Ljava/util/Map;", remap = false), cancellable = true)
|
||||
private void fastDummyCheck(CallbackInfoReturnable<Registry<T>> cir) {
|
||||
// Quickly iterate without making any streams, etc. to see if everything is fine
|
||||
// Use the slow path (by returning without cancelling) when there is an error
|
||||
for(Holder.Reference<T> ref : this.holdersByName.values()) {
|
||||
if(!ref.isBound())
|
||||
return;
|
||||
}
|
||||
if (this.unregisteredIntrusiveHolders != null) {
|
||||
for(Holder.Reference<T> ref : this.unregisteredIntrusiveHolders.values()) {
|
||||
if(ref.getType() == Holder.Reference.Type.INTRUSIVE && !ref.isBound())
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Skip the creation of streams
|
||||
cir.setReturnValue(this);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user