Compare commits
414 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b702a4003e | ||
|
|
f484823c04 | ||
|
|
12afd99b83 | ||
|
|
5d862d0de3 | ||
|
|
5ab71150f4 | ||
|
|
a154f02e24 | ||
|
|
422f570ddd | ||
|
|
51f273fae4 | ||
|
|
334a91f3b0 | ||
|
|
cae2af8bfc | ||
|
|
a25e37b968 | ||
|
|
d9fb13a805 | ||
|
|
003bfb46e5 | ||
|
|
c45f063bfb | ||
|
|
9f56c913fa | ||
|
|
10f8be3d93 | ||
|
|
9f11af14b5 | ||
|
|
b9832b076b | ||
|
|
49a88c8bba | ||
|
|
334683fef6 | ||
|
|
7a8beea66e | ||
|
|
97c4b35c82 | ||
|
|
bfc099472f | ||
|
|
b3d1e9bcb0 | ||
|
|
0068f72631 | ||
|
|
3a8172c1c4 | ||
|
|
59bb46fd36 | ||
|
|
c63b9de971 | ||
|
|
d0fe9d6002 | ||
|
|
1b26be735b | ||
|
|
bd9494a4a2 | ||
|
|
c3e5ddc450 | ||
|
|
35f81bae3d | ||
|
|
0a469c09a3 | ||
|
|
935365604d | ||
|
|
b4024f696d | ||
|
|
9b7a174af8 | ||
|
|
1f15c277ab | ||
|
|
a39fb0a082 | ||
|
|
d2b2334807 | ||
|
|
eb6700eaf5 | ||
|
|
a287375522 | ||
|
|
a8abed1d56 | ||
|
|
07592fb708 | ||
|
|
e9cc6caad5 | ||
|
|
6a7b18cc6b | ||
|
|
3fd3fce262 | ||
|
|
d47e412011 | ||
|
|
39a43398ba | ||
|
|
96092c7189 | ||
|
|
1501fe29e6 | ||
|
|
2f25bb4bae | ||
|
|
528d9c80c8 | ||
|
|
5ea3880e80 | ||
|
|
387c94296b | ||
|
|
2811af7112 | ||
|
|
470d30cdb5 | ||
|
|
f59d4adf92 | ||
|
|
d23b38f1be | ||
|
|
fbf4a533c2 | ||
|
|
ae7df998bf | ||
|
|
619e15e62d | ||
|
|
dbdb7c37a6 | ||
|
|
aa31256655 | ||
|
|
7dcaa6b641 | ||
|
|
6f3f75416f | ||
|
|
8a3b7f7935 | ||
|
|
3194d30a09 | ||
|
|
b1b42b9dd9 | ||
|
|
17a9f122b1 | ||
|
|
048e7f7e07 | ||
|
|
18dac0d949 | ||
|
|
508e62b160 | ||
|
|
2dae858652 | ||
|
|
21fc44716b | ||
|
|
75f65535f8 | ||
|
|
757d8b6762 | ||
|
|
de804c3aa6 | ||
|
|
8def366676 | ||
|
|
92e8234240 | ||
|
|
2673ae46ae | ||
|
|
60d3026ea6 | ||
|
|
653b901180 | ||
|
|
13820f7bbf | ||
|
|
c66987887c | ||
|
|
7ab3f3bc97 | ||
|
|
c7c866fde5 | ||
|
|
f6683a77ce | ||
|
|
ed2ad51523 | ||
|
|
ff6b687d5a | ||
|
|
aaaa8ad48a | ||
|
|
e2ac3bb97a | ||
|
|
6eb82e1325 | ||
|
|
ad6425f7e9 | ||
|
|
960c394073 | ||
|
|
e8320a3d8f | ||
|
|
74a339bc2c | ||
|
|
6706656623 | ||
|
|
db5363a429 | ||
|
|
fc96643a89 | ||
|
|
eeb842332b | ||
|
|
6531b69fb9 | ||
|
|
0f16e159f9 | ||
|
|
1d4ddc302a | ||
|
|
de5b79fe7c | ||
|
|
86f06b2f36 | ||
|
|
520de2c12b | ||
|
|
a29bdb2f82 | ||
|
|
cdfe53589e | ||
|
|
5463ccc3e6 | ||
|
|
dee2627df9 | ||
|
|
d8e937720f | ||
|
|
9df79d8c8c | ||
|
|
1572bd705d | ||
|
|
2abc4fa56b | ||
|
|
489136d048 | ||
|
|
820169667a | ||
|
|
512a7e237c | ||
|
|
7db5d6a1da | ||
|
|
631ad0528b | ||
|
|
1daea1f5e3 | ||
|
|
b2eb14b766 | ||
|
|
70fba2e0af | ||
|
|
30e91f2056 | ||
|
|
beccbef151 | ||
|
|
c3b17b2927 | ||
|
|
783627f4c5 | ||
|
|
d7bfeedc62 | ||
|
|
212f139bf1 | ||
|
|
85740f83af | ||
|
|
d46d24542f | ||
|
|
700ccc25b7 | ||
|
|
1b6075562c | ||
|
|
2697a8f358 | ||
|
|
f056fe4d0c | ||
|
|
ef07197345 | ||
|
|
180606eea1 | ||
|
|
a6e0785252 | ||
|
|
4673293039 | ||
|
|
f2379dc9e3 | ||
|
|
d4cc7664f6 | ||
|
|
1dce8d4ccf | ||
|
|
6fd3dde1a2 | ||
|
|
f586522dfe | ||
|
|
168ab8effa | ||
|
|
6e9dfaf0c6 | ||
|
|
9d584d13d2 | ||
|
|
a0f391e258 | ||
|
|
8c51ccc022 | ||
|
|
390404d1d5 | ||
|
|
57af3c6491 | ||
|
|
99da801c3f | ||
|
|
c292f0ddce | ||
|
|
aa2a3817ba | ||
|
|
4473a4ffb4 | ||
|
|
b4398565a6 | ||
|
|
9cfccb58fa | ||
|
|
a9f4ce72f1 | ||
|
|
bc3af0450e | ||
|
|
d0598055c0 | ||
|
|
ab8810b7fe | ||
|
|
2ef7d5fc85 | ||
|
|
d8b207ff10 | ||
|
|
4e8427545f | ||
|
|
f85d31d24d | ||
|
|
6a7b6abb23 | ||
|
|
a443972d28 | ||
|
|
b6865eff6c | ||
|
|
cc653249e4 | ||
|
|
e75810db5b | ||
|
|
a4359e1ad5 | ||
|
|
95a5a1b7b1 | ||
|
|
d846a077ac | ||
|
|
2f98fadc9e | ||
|
|
de70fbafad | ||
|
|
a66a1dab9d | ||
|
|
4d251e61ca | ||
|
|
57d5f08b1d | ||
|
|
f13910a6ed | ||
|
|
bebf3ccec2 | ||
|
|
a5b1cbdc13 | ||
|
|
89592a2c42 | ||
|
|
3f688148d0 | ||
|
|
c8f9b1cd84 | ||
|
|
7ee968f719 | ||
|
|
2cce200e4d | ||
|
|
2e9a6f27e0 | ||
|
|
730932b018 | ||
|
|
c6338f9736 | ||
|
|
0277d80ce7 | ||
|
|
b1c0b0f813 | ||
|
|
0476cdc35a | ||
|
|
23bbf7b092 | ||
|
|
0061e1de8e | ||
|
|
159f27a8bf | ||
|
|
c5dd43f2a1 | ||
|
|
da065350d0 | ||
|
|
9bf4faa4a5 | ||
|
|
7bd88b4f04 | ||
|
|
3bedde4ebe | ||
|
|
2e1bed7cfb | ||
|
|
73f706164f | ||
|
|
5b5cbf9380 | ||
|
|
50556ab005 | ||
|
|
c3d78c3d09 | ||
|
|
ad07fb3f0c | ||
|
|
a9efc62867 | ||
|
|
4b741da54d | ||
|
|
60c2b02e37 | ||
|
|
042a7caa9e | ||
|
|
02e4cb6cf1 | ||
|
|
b2e3ae82eb | ||
|
|
180f4eaf8d | ||
|
|
95cd576c68 | ||
|
|
0ea4284846 | ||
|
|
8fc9c0b3c1 | ||
|
|
60fa5d4afe | ||
|
|
95a8e4fe21 | ||
|
|
add7dd4609 | ||
|
|
827550e8af | ||
|
|
9d7ef772a0 | ||
|
|
a47a61a923 | ||
|
|
5e20e25c4d | ||
|
|
e6b28de740 | ||
|
|
61c2116946 | ||
|
|
72845d8952 | ||
|
|
70eaabe756 | ||
|
|
e22f5caec7 | ||
|
|
8a872fed27 | ||
|
|
0a2299ee33 | ||
|
|
826e5a4c20 | ||
|
|
a62c0635a5 | ||
|
|
4584457a79 | ||
|
|
8f663a0b07 | ||
|
|
b65a63896b | ||
|
|
fdc98b2600 | ||
|
|
8806dffaea | ||
|
|
761703b4ab | ||
|
|
967d39997f | ||
|
|
48b492b906 | ||
|
|
96db279d58 | ||
|
|
8d2d3d8e15 | ||
|
|
b82942fe75 | ||
|
|
e6ca5f633c | ||
|
|
611dc4a266 | ||
|
|
eacab89181 | ||
|
|
139b967ce6 | ||
|
|
d52725012a | ||
|
|
23e6091747 | ||
|
|
3f7af1c2e0 | ||
|
|
f38a06ecc1 | ||
|
|
c8485eaef8 | ||
|
|
fc86d0eee0 | ||
|
|
6143d8a783 | ||
|
|
90cf39e9d8 | ||
|
|
739b1663cd | ||
|
|
4dfba0cab4 | ||
|
|
46fc1e8539 | ||
|
|
2699fe448e | ||
|
|
5c72a527ad | ||
|
|
25f13a9701 | ||
|
|
16ef9253e6 | ||
|
|
f25ecf337d | ||
|
|
675ce1cd43 | ||
|
|
c4855e0b70 | ||
|
|
6c206c47e9 | ||
|
|
daa84565a6 | ||
|
|
8658784906 | ||
|
|
d3758eb4f6 | ||
|
|
e9bfd965cd | ||
|
|
c2a2fd876b | ||
|
|
649c25d0d2 | ||
|
|
ad4bfc79ab | ||
|
|
a4bb17d2af | ||
|
|
b55ed00748 | ||
|
|
806fb7dcfe | ||
|
|
b5becf6ba3 | ||
|
|
c65fdbccc0 | ||
|
|
dcee8b4169 | ||
|
|
d6fc939f41 | ||
|
|
a012b60ae3 | ||
|
|
97a2c0e0f5 | ||
|
|
34bc295b3c | ||
|
|
e08531c228 | ||
|
|
456bca47b6 | ||
|
|
b25bb14d3d | ||
|
|
d4fcc80db0 | ||
|
|
64ea7eab19 | ||
|
|
432b137edb | ||
|
|
991ec339b2 | ||
|
|
1bf98ced86 | ||
|
|
7646bfa153 | ||
|
|
7be97e61e8 | ||
|
|
b23502c32b | ||
|
|
d03571cb05 | ||
|
|
e1c91f13ac | ||
|
|
324fb3af97 | ||
|
|
585fdb9e99 | ||
|
|
fa9d39bd31 | ||
|
|
501db5b84a | ||
|
|
8c46b4629d | ||
|
|
9dcc87b227 | ||
|
|
0eb70468a1 | ||
|
|
ba2b740075 | ||
|
|
063289faac | ||
|
|
d3ed56a1c1 | ||
|
|
45a7f1a63e | ||
|
|
04aac43db0 | ||
|
|
0ea5139315 | ||
|
|
06bb9f9545 | ||
|
|
adf169c3fa | ||
|
|
8d5e66218e | ||
|
|
45b734dead | ||
|
|
2ec1000ae8 | ||
|
|
1f66c7becd | ||
|
|
ccc21d76d8 | ||
|
|
4fec9f53c3 | ||
|
|
b8cd5adbb1 | ||
|
|
bf69e58169 | ||
|
|
6592ac5cac | ||
|
|
1f447b689f | ||
|
|
8df6fab0e7 | ||
|
|
328507cea3 | ||
|
|
c6b38f340a | ||
|
|
69a9aa76da | ||
|
|
87ee1017ae | ||
|
|
fcf21283d8 | ||
|
|
bfdd1f913d | ||
|
|
3187c80d48 | ||
|
|
f594ec6c5b | ||
|
|
efa46c3842 | ||
|
|
473597a915 | ||
|
|
84061b197f | ||
|
|
dc86d3e137 | ||
|
|
4d111cb381 | ||
|
|
29d1f88539 | ||
|
|
0ff032bd1a | ||
|
|
e34e9fcf8b | ||
|
|
9861926f1d | ||
|
|
acb6809459 | ||
|
|
fd975388ca | ||
|
|
13ee6b3523 | ||
|
|
af8e23f41a | ||
|
|
19ba30280c | ||
|
|
33609d234c | ||
|
|
6e3134161a | ||
|
|
27142d2c42 | ||
|
|
c672948d8c | ||
|
|
0a418bc55d | ||
|
|
d8263c55f8 | ||
|
|
a212a3fc7e | ||
|
|
7af609b56f | ||
|
|
45c11ea7e0 | ||
|
|
894173cf1d | ||
|
|
fc52570951 | ||
|
|
207521f778 | ||
|
|
9efe912090 | ||
|
|
15d16ffa9a | ||
|
|
95f2dc95d3 | ||
|
|
86c6e90436 | ||
|
|
915de8187e | ||
|
|
181be3cf80 | ||
|
|
79751dac21 | ||
|
|
184dec0739 | ||
|
|
1339fc7af6 | ||
|
|
624a4483eb | ||
|
|
3b7d2f1e2e | ||
|
|
91a084f860 | ||
|
|
546a362d9c | ||
|
|
87b984bfe9 | ||
|
|
7a2b71381c | ||
|
|
850fdcbf1b | ||
|
|
3add97202e | ||
|
|
5d481334a8 | ||
|
|
6d096e8ae0 | ||
|
|
c09b530a8c | ||
|
|
fbacc85f86 | ||
|
|
0e9142e68f | ||
|
|
f6f7badde8 | ||
|
|
2c5b664c68 | ||
|
|
2a099d8537 | ||
|
|
472ed42f22 | ||
|
|
9830d4d6f7 | ||
|
|
348a2bcf3d | ||
|
|
43da9ffcdc | ||
|
|
7f80f38862 | ||
|
|
231c3f41e5 | ||
|
|
cc54b6d67d | ||
|
|
e8fa483917 | ||
|
|
dc3cb998b0 | ||
|
|
972121fa50 | ||
|
|
93554d1854 | ||
|
|
a00efe62c7 | ||
|
|
1267a25db1 | ||
|
|
0149a113a8 | ||
|
|
97d4f6bbc2 | ||
|
|
dfb0c52fa3 | ||
|
|
02fa2be42b | ||
|
|
45c29216a8 | ||
|
|
5e8e7649e5 | ||
|
|
0a2cda0814 | ||
|
|
5952fa2178 | ||
|
|
9bf0017aeb | ||
|
|
cb286c0bca | ||
|
|
b56a65c192 | ||
|
|
8d1058cc3f | ||
|
|
eb15718023 | ||
|
|
861474d635 | ||
|
|
f3bda91ebf | ||
|
|
84c72f8da8 | ||
|
|
3716912a53 | ||
|
|
42688757a7 | ||
|
|
23b473f85d | ||
|
|
7fcaf716d8 |
|
|
@ -161,6 +161,7 @@ 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_17",
|
||||
this(true, "0.8", packageName, "org.embeddedt.modernfix.core.ModernFixMixinPlugin", "JAVA_21",
|
||||
commonMixins, clientMixins, InjectorOptions.DEFAULT, OverwriteOptions.DEFAULT);
|
||||
}
|
||||
public record InjectorOptions(int defaultRequire) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
plugins {
|
||||
id("net.neoforged.moddev.legacyforge") version("2.0.134")
|
||||
id("net.neoforged.moddev") version("2.0.134")
|
||||
id("me.modmuss50.mod-publish-plugin") version("1.1.0")
|
||||
}
|
||||
|
||||
|
|
@ -16,11 +16,11 @@ val gitVersion = providers.of(GitVersionSource::class) {
|
|||
|
||||
version = gitVersion.get()
|
||||
|
||||
base.archivesName = "modernfix-forge"
|
||||
base.archivesName = "modernfix-neoforge"
|
||||
|
||||
legacyForge {
|
||||
neoForge {
|
||||
enable {
|
||||
forgeVersion = rootProject.properties["forge_version"].toString()
|
||||
version = rootProject.properties["forge_version"].toString()
|
||||
isDisableRecompilation = System.getenv("CI") == "true"
|
||||
}
|
||||
|
||||
|
|
@ -51,14 +51,8 @@ legacyForge {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -66,7 +60,7 @@ tasks.named<Jar>("jar") {
|
|||
}
|
||||
|
||||
java {
|
||||
val curSourceCompatLevel = JavaVersion.VERSION_17
|
||||
val curSourceCompatLevel = JavaVersion.VERSION_21
|
||||
sourceCompatibility = curSourceCompatLevel
|
||||
targetCompatibility = curSourceCompatLevel
|
||||
}
|
||||
|
|
@ -112,24 +106,17 @@ 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()
|
||||
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")
|
||||
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")
|
||||
compileOnly("curse.maven:terrablender-neoforge-940057:6054947")
|
||||
}
|
||||
|
||||
tasks.named<Jar>("jar") {
|
||||
|
|
@ -161,12 +148,12 @@ tasks.named<ProcessResources>("processResources") {
|
|||
|
||||
inputs.property("version", project.version)
|
||||
|
||||
filesMatching("META-INF/mods.toml") {
|
||||
filesMatching("META-INF/neoforge.mods.toml") {
|
||||
expand("version" to project.version)
|
||||
}
|
||||
}
|
||||
|
||||
val finalJarTask = "reobfJar"
|
||||
val finalJarTask = "jar"
|
||||
|
||||
tasks.register<Copy>("copyJarNameConsistent") {
|
||||
from(tasks.named<Jar>(finalJarTask).get().outputs.files)
|
||||
|
|
@ -190,7 +177,7 @@ publishMods {
|
|||
changelog = "Please check the [GitHub wiki](https://github.com/embeddedt/ModernFix/wiki/Changelog) for major changes."
|
||||
type = STABLE
|
||||
|
||||
modLoaders.add("forge")
|
||||
modLoaders.add("neoforge")
|
||||
|
||||
curseforge {
|
||||
projectId = "790626"
|
||||
|
|
|
|||
|
|
@ -5,28 +5,29 @@ junit_version=5.10.0-M1
|
|||
mixinextras_version=0.4.1
|
||||
|
||||
mod_id=modernfix
|
||||
minecraft_version=1.20.1
|
||||
enabled_platforms=forge
|
||||
forge_version=1.20.1-47.4.0
|
||||
parchment_version=2023.07.09
|
||||
minecraft_version=1.21.1
|
||||
enabled_platforms=neoforge
|
||||
forge_version=21.1.111
|
||||
parchment_version=2024.11.17
|
||||
parchment_mc_version=1.21.1
|
||||
refined_storage_version=4392788
|
||||
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
|
||||
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
|
||||
|
||||
fabric_loader_version=0.16.10
|
||||
fabric_api_version=0.86.0+1.20.1
|
||||
fabric_api_version=0.102.1+1.21.1
|
||||
|
||||
continuity_version=3.0.0-beta.2+1.19.3
|
||||
continuity_version=3.0.0-beta.4+1.20.2
|
||||
|
||||
modmenu_version=7.0.0-beta.2
|
||||
modmenu_version=11.0.3
|
||||
diagonal_fences_version=4558828
|
||||
|
||||
spark_version=4587310
|
||||
spark_version=6225208
|
||||
|
||||
use_fabric_api_at_runtime=true
|
||||
|
||||
|
|
|
|||
|
|
@ -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", "forge" ]:
|
||||
for platform in [ "common", "fabric", "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:
|
||||
|
|
|
|||
|
|
@ -7,14 +7,12 @@ 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.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class ModernFixClient {
|
||||
|
|
@ -40,7 +38,6 @@ 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,11 +1,10 @@
|
|||
package org.embeddedt.modernfix.api.entrypoint;
|
||||
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Implement this interface in a mod class and add it to "modernfix:integration_v1" in your mod metadata file
|
||||
|
|
@ -21,49 +20,10 @@ 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
|
||||
|
|
@ -71,7 +31,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(ResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery, Function<Material, TextureAtlasSprite> textureGetter) {
|
||||
return onBakedModelLoad(location, baseModel, originalModel, state, bakery);
|
||||
default BakedModel onBakedModelLoad(ModelResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter textureGetter) {
|
||||
return originalModel;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,18 @@
|
|||
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 {
|
||||
|
|
@ -28,7 +23,7 @@ public final class ModelHelpers {
|
|||
* @return a list of all blockstates related to the model
|
||||
*/
|
||||
public static ImmutableList<BlockState> getBlockStateForLocation(ModelResourceLocation location) {
|
||||
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(new ResourceLocation(location.getNamespace(), location.getPath()));
|
||||
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(location.id());
|
||||
if(blockOpt.isPresent())
|
||||
return ModelBakeryHelpers.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), location);
|
||||
else
|
||||
|
|
@ -52,7 +47,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<>(location -> modelGetter.apply(location, BlockModelRotation.X0_Y0));
|
||||
return new DynamicMap<>(ResourceLocation.class, location -> modelGetter.apply(location, BlockModelRotation.X0_Y0));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -61,6 +56,8 @@ 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) {
|
||||
|
|
@ -72,16 +69,8 @@ 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;
|
||||
}
|
||||
};
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,185 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,58 +3,14 @@ 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();
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +1,28 @@
|
|||
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.server.MinecraftServer;
|
||||
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 net.minecraft.world.level.chunk.status.ChunkStatusTasks;
|
||||
import net.minecraft.world.level.chunk.status.WorldGenContext;
|
||||
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;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(ChunkMap.class)
|
||||
@Mixin(ChunkStatusTasks.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<>();
|
||||
private static final ThreadLocal<CompletableFuture<ChunkAccess>> MFIX_SURROGATE_FUTURE = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
|
|
@ -59,25 +37,24 @@ public abstract class ChunkMapLoadMixin {
|
|||
* <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>>();
|
||||
@Redirect(method = "full", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;supplyAsync(Ljava/util/function/Supplier;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", ordinal = 0))
|
||||
private static CompletableFuture<ChunkAccess> createSurrogateFuture(Supplier<ChunkAccess> supplier, Executor executor,
|
||||
@Local(ordinal = 0, argsOnly = true) WorldGenContext worldGenContext) {
|
||||
var surrogate = new CompletableFuture<ChunkAccess>();
|
||||
// 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 -> {
|
||||
var mainThreadExecutor = ((ServerChunkCacheAccessor) worldGenContext.level().getChunkSource()).mfix$getMainThreadProcessor();
|
||||
CompletableFuture.runAsync(() -> {}, executor).thenApplyAsync($ -> {
|
||||
// running on thread that executes lambda body
|
||||
MFIX_SURROGATE_FUTURE.set(surrogate);
|
||||
try {
|
||||
return fn.apply(either);
|
||||
return supplier.get();
|
||||
} finally {
|
||||
MFIX_SURROGATE_FUTURE.remove();
|
||||
}
|
||||
}, this.mainThreadExecutor).whenComplete((either, throwable) -> {
|
||||
}, mainThreadExecutor).whenComplete((either, throwable) -> {
|
||||
if (throwable != null) {
|
||||
if (!surrogate.isDone()) {
|
||||
surrogate.completeExceptionally(throwable);
|
||||
|
|
@ -85,7 +62,8 @@ public abstract class ChunkMapLoadMixin {
|
|||
// 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);
|
||||
var exc = new ReportedException(CrashReport.forThrowable(throwable, "Exception during promotion of chunk to FULL status"));
|
||||
MinecraftServer.setFatalException(exc);
|
||||
}
|
||||
} else {
|
||||
surrogate.complete(either);
|
||||
|
|
@ -100,64 +78,11 @@ public abstract class ChunkMapLoadMixin {
|
|||
* @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) {
|
||||
@Inject(method = "lambda$full$2", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/LevelChunk;runPostLoad()V"))
|
||||
private static void completeSurrogateFuture(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);
|
||||
future.complete(levelChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
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;
|
||||
|
|
@ -17,8 +18,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/world/level/gameevent/GameEvent;Lnet/minecraft/world/entity/Entity;)V"))
|
||||
private boolean onlyAddIfSelfChunkLoaded(Entity instance, GameEvent event, Entity entity) {
|
||||
@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) {
|
||||
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,11 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
|
||||
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(ServerChunkCache.class)
|
||||
public interface ServerChunkCacheAccessor {
|
||||
@Accessor("mainThreadProcessor")
|
||||
ServerChunkCache.MainThreadExecutor mfix$getMainThreadProcessor();
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,13 +6,9 @@ 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.embeddedt.modernfix.neoforge.init.ModernFixForge;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
|
|
|||
|
|
@ -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.minecraftforge.client.event.RenderLivingEvent;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.neoforged.neoforge.client.event.RenderLivingEvent;
|
||||
import net.neoforged.bus.api.Event;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
|
@ -13,18 +13,17 @@ 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/minecraftforge/eventbus/api/IEventBus;post(Lnet/minecraftforge/eventbus/api/Event;)Z", ordinal = 0))
|
||||
private boolean 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/neoforged/bus/api/IEventBus;post(Lnet/neoforged/bus/api/Event;)Lnet/neoforged/bus/api/Event;", ordinal = 0))
|
||||
private Event fireCheckingPoseStack(IEventBus instance, Event event) {
|
||||
PoseStack stack = ((RenderLivingEvent)event).getPoseStack();
|
||||
int size = ((PoseStackAccessor)stack).getPoseStack().size();
|
||||
if (instance.post(event)) {
|
||||
instance.post(event);
|
||||
if (((RenderLivingEvent.Pre)event).isCanceled()) {
|
||||
// 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.minecraftforge.client.event.RenderPlayerEvent;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.neoforged.bus.api.Event;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.neoforge.client.event.RenderPlayerEvent;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
|
@ -13,18 +13,17 @@ 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/minecraftforge/eventbus/api/IEventBus;post(Lnet/minecraftforge/eventbus/api/Event;)Z", ordinal = 0))
|
||||
private boolean 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/neoforged/bus/api/IEventBus;post(Lnet/neoforged/bus/api/Event;)Lnet/neoforged/bus/api/Event;", ordinal = 0))
|
||||
private Event fireCheckingPoseStack(IEventBus instance, Event event) {
|
||||
PoseStack stack = ((RenderPlayerEvent)event).getPoseStack();
|
||||
int size = ((PoseStackAccessor)stack).getPoseStack().size();
|
||||
if (instance.post(event)) {
|
||||
instance.post(event);
|
||||
if (((RenderPlayerEvent.Pre)event).isCanceled()) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
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,15 +1,9 @@
|
|||
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;
|
||||
|
|
@ -36,12 +30,14 @@ 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) && NetworkUtils.isCurrentlyVanilla) {
|
||||
if(type.ordinal() >= (mfix$maxVanillaOrdinal + 1)) {
|
||||
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();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
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 = "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;"))
|
||||
@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;"))
|
||||
private void clearLevelDataForLeaks(CallbackInfo ci) {
|
||||
if(this.level != null) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package org.embeddedt.modernfix.common.mixin.core;
|
||||
|
||||
import net.minecraft.server.Bootstrap;
|
||||
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;
|
||||
|
|
@ -25,14 +23,7 @@ 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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package org.embeddedt.modernfix.common.mixin.core;
|
||||
|
||||
import net.minecraftforge.registries.GameData;
|
||||
import org.embeddedt.modernfix.forge.init.ModernFixForge;
|
||||
import net.neoforged.neoforge.registries.GameData;
|
||||
import org.embeddedt.modernfix.neoforge.init.ModernFixForge;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
|
|
|
|||
|
|
@ -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.forge.load.MinecraftServerReloadTracker;
|
||||
import org.embeddedt.modernfix.neoforge.load.MinecraftServerReloadTracker;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.forge.load.MinecraftServerReloadTracker;
|
||||
import org.embeddedt.modernfix.neoforge.load.MinecraftServerReloadTracker;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
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.minecraftforge.internal.BrandingControl;
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import net.neoforged.fml.ModContainer;
|
||||
import net.neoforged.fml.ModList;
|
||||
import net.neoforged.neoforge.internal.BrandingControl;
|
||||
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/minecraftforge/fml/ModList;get()Lnet/minecraftforge/fml/ModList;"), locals = LocalCapture.CAPTURE_FAILHARD, require = 0)
|
||||
@Inject(method = "computeBranding", at = @At(value = "INVOKE", target = "Lnet/neoforged/fml/ModList;get()Lnet/neoforged/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.ChunkRenderDispatcher;
|
||||
import net.minecraft.client.renderer.chunk.SectionRenderDispatcher;
|
||||
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(ChunkRenderDispatcher.class)
|
||||
@Mixin(SectionRenderDispatcher.class)
|
||||
@ClientOnlyMixin
|
||||
public class ChunkRenderDispatcherMixin {
|
||||
private static final Executor MFIX_CHUNK_BUILD_EXECUTOR = new ThreadPoolExecutor(1, computeNumThreads(), 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ 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.CommandFunction;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.functions.CommandFunction;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.ServerFunctionManager;
|
||||
import org.embeddedt.modernfix.duck.IProfilingServerFunctionManager;
|
||||
|
|
@ -29,22 +30,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> functionObjects, ResourceLocation identifier, CallbackInfo ci) {
|
||||
private void resetWatches(Collection<CommandFunction<CommandSourceStack>> 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/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) {
|
||||
@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) {
|
||||
watchRef.set(null);
|
||||
if (identifier == TICK_FUNCTION_TAG) {
|
||||
var watch = mfix$functionWatches.computeIfAbsent(function.getId(), i -> Stopwatch.createUnstarted());
|
||||
var watch = mfix$functionWatches.computeIfAbsent(function.id(), i -> Stopwatch.createUnstarted());
|
||||
watch.start();
|
||||
watchRef.set(watch);
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
@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) {
|
||||
var watch = watchRef.get();
|
||||
if (watch != null && watch.isRunning()) {
|
||||
watch.stop();
|
||||
|
|
@ -52,7 +53,7 @@ public class ServerFunctionManagerMixin implements IProfilingServerFunctionManag
|
|||
}
|
||||
|
||||
@Inject(method = "executeTagFunctions", at = @At("RETURN"))
|
||||
private void pruneUnusedWatches(Collection<CommandFunction> functionObjects, ResourceLocation identifier, CallbackInfo ci) {
|
||||
private void pruneUnusedWatches(Collection<CommandFunction<CommandSourceStack>> 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/minecraftforge/event/AddReloadListenerEvent$WrappedStateAwareListener")
|
||||
@Mixin(targets = "net/neoforged/neoforge/event/AddReloadListenerEvent$WrappedStateAwareListener")
|
||||
public abstract class AddReloadListenerEventWrapperMixin implements PreparableReloadListener {
|
||||
@Shadow @Final private PreparableReloadListener wrapped;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,52 +1,46 @@
|
|||
package org.embeddedt.modernfix.common.mixin.feature.registry_event_progress;
|
||||
|
||||
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 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 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 {
|
||||
|
||||
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();
|
||||
@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;
|
||||
}
|
||||
|
||||
@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());
|
||||
// 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 {
|
||||
loader.postEventWithWrapInModOrder(event, (mc, e) -> {
|
||||
ModList.get().forEachModInOrder(mc -> {
|
||||
ModLoadingContext.get().setActiveContainer(mc);
|
||||
pb.label(pb.name() + " - " + mc.getModInfo().getDisplayName());
|
||||
pb.increment();
|
||||
}, (mc, e) -> {
|
||||
mc.acceptEvent(phase, event);
|
||||
ModLoadingContext.get().setActiveContainer(null);
|
||||
});
|
||||
} finally {
|
||||
pb.complete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.attribute_supplier_dedup;
|
||||
|
||||
import net.minecraft.core.Holder;
|
||||
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.embeddedt.modernfix.neoforge.init.ModernFixForge;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
|
@ -18,7 +19,7 @@ import java.util.Map;
|
|||
public class AttributeSupplierBuilderMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private Map<Attribute, AttributeInstance> builder;
|
||||
private Map<Holder<Attribute>, AttributeInstance> builder;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.attribute_supplier_dedup;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.world.entity.ai.attributes.Attribute;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||||
|
|
@ -19,7 +20,7 @@ public class AttributeSupplierMixin {
|
|||
@Shadow
|
||||
@Final
|
||||
@Mutable
|
||||
private Map<Attribute, AttributeInstance> instances;
|
||||
private Map<Holder<Attribute>, AttributeInstance> instances;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
|
|
@ -27,7 +28,7 @@ public class AttributeSupplierMixin {
|
|||
* care about insertion order in this context
|
||||
*/
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void useCompactJavaMap(Map<Attribute, AttributeInstance> instances, CallbackInfo ci) {
|
||||
private void useCompactJavaMap(Map<Holder<Attribute>, AttributeInstance> instances, CallbackInfo ci) {
|
||||
this.instances = new Object2ObjectOpenHashMap<>(this.instances);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
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,7 +3,6 @@ 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;
|
||||
|
|
@ -13,7 +12,7 @@ import org.spongepowered.asm.mixin.injection.Redirect;
|
|||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Mixin(SkinManager.class)
|
||||
@Mixin(targets = {"net/minecraft/client/resources/SkinManager$TextureCache" })
|
||||
@ClientOnlyMixin
|
||||
public class SkinManagerMixin {
|
||||
@Unique
|
||||
|
|
@ -22,7 +21,7 @@ public class SkinManagerMixin {
|
|||
.concurrencyLevel(1)
|
||||
.build();
|
||||
|
||||
@Redirect(method = "registerTexture(Lcom/mojang/authlib/minecraft/MinecraftProfileTexture;Lcom/mojang/authlib/minecraft/MinecraftProfileTexture$Type;Lnet/minecraft/client/resources/SkinManager$SkinTextureCallback;)Lnet/minecraft/resources/ResourceLocation;",
|
||||
@Redirect(method = { "getOrLoad", "registerTexture" },
|
||||
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
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ public class ChunkGeneratorMixin implements IChunkGenerator {
|
|||
|
||||
private String mfix$makeCacheKey(ConcentricRingsStructurePlacement placement) {
|
||||
RegistryOps<Tag> ops = RegistryOps.create(NbtOps.INSTANCE, this.mfix$server.registryAccess());
|
||||
String placementKey = ConcentricRingsStructurePlacement.CODEC.encodeStart(ops, placement)
|
||||
String placementKey = ConcentricRingsStructurePlacement.CODEC.codec().encodeStart(ops, placement)
|
||||
.result().map(Tag::toString).orElse(null);
|
||||
String biomeSourceKey = BiomeSource.CODEC.encodeStart(ops, this.biomeSource)
|
||||
.result().map(Tag::toString).orElse(null);
|
||||
|
|
@ -155,7 +155,7 @@ public class ChunkGeneratorMixin implements IChunkGenerator {
|
|||
return new HashMap<>();
|
||||
}
|
||||
try {
|
||||
CompoundTag root = NbtIo.readCompressed(file.toFile());
|
||||
CompoundTag root = NbtIo.readCompressed(file, NbtAccounter.unlimitedHeap());
|
||||
Map<String, List<ChunkPos>> result = new HashMap<>();
|
||||
for (String key : root.getAllKeys()) {
|
||||
if (root.contains(key, Tag.TAG_INT_ARRAY)) {
|
||||
|
|
@ -190,7 +190,7 @@ public class ChunkGeneratorMixin implements IChunkGenerator {
|
|||
}
|
||||
Path file = mfix$dimensionPath.resolve(CACHE_FILENAME);
|
||||
try {
|
||||
NbtIo.writeCompressed(root, file.toFile());
|
||||
NbtIo.writeCompressed(root, file);
|
||||
} catch (Exception e) {
|
||||
ModernFix.LOGGER.warn("Failed to write stronghold cache", e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
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,9 +1,7 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.chunk_meshing;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.renderer.chunk.RenderChunkRegion;
|
||||
import net.minecraft.client.renderer.chunk.SectionCompiler;
|
||||
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;
|
||||
|
|
@ -11,7 +9,7 @@ import org.spongepowered.asm.mixin.Mixin;
|
|||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(targets = { "net/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk$RebuildTask"}, priority = 2000)
|
||||
@Mixin(value = SectionCompiler.class, priority = 2000)
|
||||
@ClientOnlyMixin
|
||||
@RequiresMod("!fluidlogged")
|
||||
public class RebuildTaskMixin {
|
||||
|
|
@ -23,13 +21,4 @@ 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.compact_mojang_registries;
|
||||
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.core.RegistrationInfo;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import org.embeddedt.modernfix.annotation.IgnoreOutsideDev;
|
||||
import org.embeddedt.modernfix.registry.LifecycleMap;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
|
|
@ -20,10 +21,10 @@ public abstract class MappedRegistryMixin<T> {
|
|||
@Shadow
|
||||
@Final
|
||||
@Mutable
|
||||
private Map<T, Lifecycle> lifecycles;
|
||||
private Map<ResourceKey<T>, RegistrationInfo> registrationInfos;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void replaceStorage(CallbackInfo ci) {
|
||||
this.lifecycles = new LifecycleMap<>();
|
||||
this.registrationInfos = new LifecycleMap<>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import java.util.concurrent.ExecutorService;
|
|||
@Mixin(Minecraft.class)
|
||||
@ClientOnlyMixin
|
||||
public class MinecraftMixin {
|
||||
@Redirect(method = { "<init>", "reloadResourcePacks(Z)Ljava/util/concurrent/CompletableFuture;" }, at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;backgroundExecutor()Ljava/util/concurrent/ExecutorService;", ordinal = 0))
|
||||
@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))
|
||||
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/RegistryAccess$Frozen;Lnet/minecraft/world/flag/FeatureFlagSet;Lnet/minecraft/commands/Commands$CommandSelection;ILjava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 5)
|
||||
@ModifyArg(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ReloadableServerResources;loadResources(Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/core/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)
|
||||
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<ImmutableMap<Property<?>, Comparable<?>>, VoxelShape>, StateDefinition<Block, BlockState>>> CACHE_BY_SHAPE_VALS = new HashMap<>();
|
||||
private static Map<ImmutableList<Float>, Pair<Map<Map<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<ImmutableMap<Property<?>, Comparable<?>>, VoxelShape>, StateDefinition<Block, BlockState>> cache = CACHE_BY_SHAPE_VALS.get(key);
|
||||
Pair<Map<Map<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<ImmutableMap<Property<?>, Comparable<?>>, VoxelShape> cacheByProperties = new HashMap<>();
|
||||
Map<Map<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());
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
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,33 +1,17 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_dfu;
|
||||
|
||||
import com.mojang.datafixers.DSL;
|
||||
import com.mojang.datafixers.DataFixer;
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import com.mojang.datafixers.DataFixerBuilder;
|
||||
import net.minecraft.util.datafix.DataFixers;
|
||||
import org.embeddedt.modernfix.dfu.LazyDataFixer;
|
||||
import org.embeddedt.modernfix.dfu.DFUBlaster;
|
||||
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 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);
|
||||
}
|
||||
public class DataFixersMixin {
|
||||
@ModifyReturnValue(method = "createFixerUpper", at = @At("RETURN"))
|
||||
private static DataFixerBuilder.Result setupMapBlasting(DataFixerBuilder.Result original) {
|
||||
DFUBlaster.blastMaps();
|
||||
return original;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<>(state -> modelManager.getModel(ModelLocationCache.get(state)));
|
||||
this.modelByStateCache = new DynamicOverridableMap<>(BlockState.class, state -> modelManager.getModel(ModelLocationCache.get(state)));
|
||||
// Clear the cached models on blockstate objects
|
||||
for(Block block : BuiltInRegistries.BLOCK) {
|
||||
for(BlockState state : block.getStateDefinition().getPossibleStates()) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
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,17 +3,16 @@ 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.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 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 org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.dynamicresources.DynamicBakedModelProvider;
|
||||
import org.embeddedt.modernfix.forge.dynresources.ModelBakeEventHelper;
|
||||
import org.embeddedt.modernfix.neoforge.dynresources.ModelBakeEventHelper;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
|
@ -24,14 +23,14 @@ import java.util.Comparator;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Mixin(ForgeHooksClient.class)
|
||||
@Mixin(ClientHooks.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/minecraftforge/fml/ModLoader;postEvent(Lnet/minecraftforge/eventbus/api/Event;)V"), remap = false)
|
||||
private static void postNamespacedKeySetEvent(ModLoader loader, Event event) {
|
||||
if(!ModLoader.isLoadingStateValid())
|
||||
@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())
|
||||
return;
|
||||
ModelEvent.ModifyBakingResult bakeEvent = ((ModelEvent.ModifyBakingResult)event);
|
||||
Stopwatch globalTimer = Stopwatch.createStarted();
|
||||
|
|
@ -42,8 +41,8 @@ public class ForgeHooksClientMixin {
|
|||
Map<String, Stopwatch> times = new Object2ObjectOpenHashMap<>();
|
||||
times.put("modernfix", selfTimer);
|
||||
ModList.get().forEachModContainer((id, mc) -> {
|
||||
Map<ResourceLocation, BakedModel> newRegistry = helper.wrapRegistry(id);
|
||||
ModelEvent.ModifyBakingResult postedEvent = new ModelEvent.ModifyBakingResult(newRegistry, bakeEvent.getModelBakery());
|
||||
Map<ModelResourceLocation, BakedModel> newRegistry = helper.wrapRegistry(id);
|
||||
ModelEvent.ModifyBakingResult postedEvent = new ModelEvent.ModifyBakingResult(newRegistry, bakeEvent.getTextureGetter(), bakeEvent.getModelBakery());
|
||||
Stopwatch timer = times.computeIfAbsent(id, $ -> Stopwatch.createUnstarted());
|
||||
timer.start();
|
||||
try {
|
||||
|
|
@ -63,8 +62,5 @@ public class ForgeHooksClientMixin {
|
|||
ModernFix.LOGGER.warn(" {}: {}", entry.getKey(), entry.getValue().toString());
|
||||
});
|
||||
}
|
||||
if (bakeEvent.getModels() instanceof DynamicBakedModelProvider dynamicProvider) {
|
||||
dynamicProvider.dumpStats();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,9 @@ 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.minecraftforge.client.model.ForgeItemModelShaper;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import net.neoforged.neoforge.client.model.RegistryAwareItemModelShaper;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.dynamicresources.DynamicModelCache;
|
||||
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
|
||||
|
|
@ -21,20 +19,20 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ForgeItemModelShaper.class)
|
||||
@Mixin(RegistryAwareItemModelShaper.class)
|
||||
@ClientOnlyMixin
|
||||
public abstract class ItemModelMesherForgeMixin extends ItemModelShaper {
|
||||
@Shadow(remap = false) @Final @Mutable private Map<Holder.Reference<Item>, ModelResourceLocation> locations;
|
||||
@Shadow(remap = false) @Final @Mutable private Map<Item, ModelResourceLocation> locations;
|
||||
|
||||
private Map<Holder.Reference<Item>, ModelResourceLocation> overrideLocations;
|
||||
private Map<Item, ModelResourceLocation> overrideLocations;
|
||||
|
||||
private final DynamicModelCache<Holder.Reference<Item>> mfix$modelCache = new DynamicModelCache<>(k -> this.mfix$getModelSlow((Holder.Reference<Item>)k), true);
|
||||
private final DynamicModelCache<Item> mfix$modelCache = new DynamicModelCache<>(k -> this.mfix$getModelSlow((Item)k), true);
|
||||
|
||||
public ItemModelMesherForgeMixin(ModelManager arg) {
|
||||
super(arg);
|
||||
}
|
||||
|
||||
private static final ModelResourceLocation SENTINEL = new ModelResourceLocation(new ResourceLocation("modernfix", "sentinel"), "sentinel");
|
||||
private static final ModelResourceLocation SENTINEL = new ModelResourceLocation(ResourceLocation.fromNamespaceAndPath("modernfix", "sentinel"), "sentinel");
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void replaceLocationMap(CallbackInfo ci) {
|
||||
|
|
@ -44,16 +42,16 @@ public abstract class ItemModelMesherForgeMixin extends ItemModelShaper {
|
|||
}
|
||||
|
||||
@Unique
|
||||
private ModelResourceLocation mfix$getLocationForge(Holder.Reference<Item> item) {
|
||||
private ModelResourceLocation mfix$getLocationForge(Item item) {
|
||||
ModelResourceLocation map = overrideLocations.getOrDefault(item, SENTINEL);
|
||||
if(map == SENTINEL) {
|
||||
/* generate the appropriate location from our cache */
|
||||
map = ModelLocationCache.get(item.get());
|
||||
map = ModelLocationCache.get(item);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private BakedModel mfix$getModelSlow(Holder.Reference<Item> key) {
|
||||
private BakedModel mfix$getModelSlow(Item key) {
|
||||
ModelResourceLocation map = mfix$getLocationForge(key);
|
||||
return map == null ? null : getModelManager().getModel(map);
|
||||
}
|
||||
|
|
@ -66,7 +64,7 @@ public abstract class ItemModelMesherForgeMixin extends ItemModelShaper {
|
|||
@Overwrite
|
||||
@Override
|
||||
public BakedModel getItemModel(Item item) {
|
||||
return this.mfix$modelCache.get(ForgeRegistries.ITEMS.getDelegateOrThrow(item));
|
||||
return this.mfix$modelCache.get(item);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -77,7 +75,7 @@ public abstract class ItemModelMesherForgeMixin extends ItemModelShaper {
|
|||
@Overwrite
|
||||
@Override
|
||||
public void register(Item item, ModelResourceLocation location) {
|
||||
overrideLocations.put(ForgeRegistries.ITEMS.getDelegateOrThrow(item), location);
|
||||
overrideLocations.put(item, location);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public abstract class ItemModelShaperMixin {
|
|||
super();
|
||||
}
|
||||
|
||||
private static final ModelResourceLocation SENTINEL_VANILLA = new ModelResourceLocation(new ResourceLocation("modernfix", "sentinel"), "sentinel");
|
||||
private static final ModelResourceLocation SENTINEL_VANILLA = new ModelResourceLocation(ResourceLocation.fromNamespaceAndPath("modernfix", "sentinel"), "sentinel");
|
||||
|
||||
private final DynamicModelCache<Item> mfix$itemModelCache = new DynamicModelCache<>(k -> this.mfix$getModelForItem((Item)k), true);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
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,102 +1,85 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
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.*;
|
||||
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.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(value = ModelBakery.ModelBakerImpl.class, priority = 600)
|
||||
@Mixin(ModelBakery.ModelBakerImpl.class)
|
||||
@ClientOnlyMixin
|
||||
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;
|
||||
public abstract class ModelBakerImplMixin {
|
||||
@Shadow public abstract UnbakedModel getModel(ResourceLocation location);
|
||||
|
||||
private boolean mfix$ignoreCache = false;
|
||||
@Shadow(aliases = {"this$0"}) @Final private ModelBakery field_40571;
|
||||
@Unique
|
||||
private int mfix$getDepth = 0;
|
||||
|
||||
@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) {
|
||||
/**
|
||||
* @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 {
|
||||
toReplace = integration.onUnbakedModelPreBake(arg, toReplace, this.field_40571);
|
||||
} catch(RuntimeException e) {
|
||||
ModernFix.LOGGER.error("Exception firing model pre-bake event for {}", arg, e);
|
||||
model.resolveParents(this::getModel);
|
||||
} catch(Exception e) {
|
||||
ModernFix.LOGGER.warn("Exception encountered resolving parents", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
mfix$getDepth--;
|
||||
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,412 +1,274 @@
|
|||
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 net.minecraft.client.Minecraft;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import net.minecraft.client.color.block.BlockColors;
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.*;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.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.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 net.minecraft.util.profiling.ProfilerFiller;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.ModernFixClient;
|
||||
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
|
||||
import org.embeddedt.modernfix.duck.IExtendedModelBaker;
|
||||
import org.embeddedt.modernfix.dynamicresources.DynamicBakedModelProvider;
|
||||
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.dynamicresources.ModelBakeryHelpers;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.slf4j.Logger;
|
||||
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
|
||||
import org.embeddedt.modernfix.duck.IBlockStateModelLoader;
|
||||
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
|
||||
import org.embeddedt.modernfix.util.DynamicOverridableMap;
|
||||
import org.embeddedt.modernfix.util.LRUMap;
|
||||
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.injection.*;
|
||||
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.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;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/* low priority so that our injectors are added after other mods' */
|
||||
@Mixin(value = ModelBakery.class, priority = 1100)
|
||||
@Mixin(ModelBakery.class)
|
||||
@ClientOnlyMixin
|
||||
public abstract class ModelBakeryMixin implements IExtendedModelBakery {
|
||||
@Unique
|
||||
private BlockStateModelLoader dynamicLoader;
|
||||
|
||||
private static final boolean debugDynamicModelLoading = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
|
||||
@Unique
|
||||
private final ReentrantLock modelBakeryLock = new ReentrantLock();
|
||||
|
||||
@Shadow @Final @Mutable public Map<ResourceLocation, UnbakedModel> unbakedCache;
|
||||
@Unique
|
||||
private ModelBakery.TextureGetter textureGetter;
|
||||
|
||||
@Shadow @Final public static ModelResourceLocation MISSING_MODEL_LOCATION;
|
||||
@Unique
|
||||
private BakedModel bakedMissingModel;
|
||||
|
||||
@Shadow protected abstract BlockModel loadBlockModel(ResourceLocation location) throws IOException;
|
||||
@Shadow abstract UnbakedModel getModel(ResourceLocation resourceLocation);
|
||||
|
||||
@Shadow @Final private Set<ResourceLocation> loadingStack;
|
||||
@Shadow @Final private UnbakedModel missingModel;
|
||||
|
||||
@Shadow protected abstract void loadModel(ResourceLocation blockstateLocation) throws Exception;
|
||||
@Unique
|
||||
private static final boolean DEBUG_MODEL_LOADS = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
|
||||
|
||||
@Shadow @Final @Mutable
|
||||
private Map<ResourceLocation, BakedModel> bakedTopLevelModels;
|
||||
/**
|
||||
* 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<ModelBakery.BakedCacheKey, BakedModel> bakedCache;
|
||||
@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 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 protected abstract void loadItemModelAndDependencies(ResourceLocation resourceLocation);
|
||||
|
||||
|
||||
@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;
|
||||
}
|
||||
@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 put(ResourceLocation key, UnbakedModel value) {
|
||||
smallLoadingCache.put(key, value);
|
||||
return super.put(key, value);
|
||||
}
|
||||
};
|
||||
this.bakedTopLevelModels = new DynamicBakedModelProvider((ModelBakery)(Object)this, bakedCache);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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 don't actually load most models
|
||||
*/
|
||||
@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)
|
||||
public UnbakedModel mfix$loadUnbakedModelDynamic(ModelResourceLocation location) {
|
||||
if(location.equals(MISSING_MODEL_VARIANT)) {
|
||||
return missingModel;
|
||||
return unbakedCache.get(rl);
|
||||
}
|
||||
modelBakeryLock.lock();
|
||||
try {
|
||||
UnbakedModel existing = this.topLevelModels.get(location);
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
if(DEBUG_MODEL_LOADS) {
|
||||
ModernFix.LOGGER.info("Loading model {}", location);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "cacheAndQueueDependencies", at = @At("HEAD"), argsOnly = true)
|
||||
private UnbakedModel fireUnbakedEvent(UnbakedModel model, ResourceLocation location) {
|
||||
@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.onUnbakedModelLoad(location, model, (ModelBakery)(Object)this);
|
||||
model = integration.onBakedModelLoad(location, prototype, model, BlockModelRotation.X0_Y0, (ModelBakery)(Object)this, this.textureGetter);
|
||||
} catch (RuntimeException e) {
|
||||
ModernFix.LOGGER.error("Exception firing model load event for {}", location, e);
|
||||
ModernFix.LOGGER.error("Exception encountered running dynamic resources integration", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
modelBakeryLock.unlock();
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
@Inject(method = "cacheAndQueueDependencies", at = @At("RETURN"))
|
||||
private void addToSmallLoadingCache(ResourceLocation location, UnbakedModel model, CallbackInfo ci) {
|
||||
smallLoadingCache.put(location, 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);
|
||||
return original;
|
||||
}
|
||||
|
||||
private int mfix$nestedLoads = 0;
|
||||
@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) {
|
||||
return;
|
||||
}
|
||||
tickCount++;
|
||||
if((tickCount % 200) == 0) {
|
||||
if(modelBakeryLock.tryLock()) {
|
||||
try {
|
||||
runCleanup();
|
||||
} finally {
|
||||
modelBakeryLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason synchronize
|
||||
* @reason We provide a fake baked registry to the rest of Minecraft, that dynamically loads models.
|
||||
*/
|
||||
@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()));
|
||||
@Overwrite
|
||||
public Map<ModelResourceLocation, BakedModel> getBakedTopLevelModels() {
|
||||
return this.mfix$emulatedBakedRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
public ReentrantLock mfix$getLock() {
|
||||
return this.modelBakeryLock;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,27 +4,34 @@ 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;
|
||||
|
|
@ -41,9 +48,12 @@ import java.util.stream.Collectors;
|
|||
|
||||
@Mixin(ModelManager.class)
|
||||
@ClientOnlyMixin
|
||||
public class ModelManagerMixin {
|
||||
public class ModelManagerMixin implements IExtendedModelManager {
|
||||
@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) {
|
||||
|
|
@ -61,9 +71,10 @@ public class ModelManagerMixin {
|
|||
}
|
||||
|
||||
@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<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)));
|
||||
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)));
|
||||
}
|
||||
|
||||
@Redirect(method = "loadModels", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;"))
|
||||
|
|
@ -84,14 +95,29 @@ public class ModelManagerMixin {
|
|||
}).orElse(null);
|
||||
}
|
||||
|
||||
private List<ModelBakery.LoadedJson> loadSingleBlockState(ResourceManager manager, ResourceLocation location) {
|
||||
private List<BlockStateModelLoader.LoadedJson> loadSingleBlockState(ResourceManager manager, ResourceLocation location) {
|
||||
return manager.getResourceStack(location).stream().map(resource -> {
|
||||
try (BufferedReader reader = resource.openAsReader()) {
|
||||
return new ModelBakery.LoadedJson(resource.sourcePackId(), GsonHelper.parse(reader));
|
||||
return new BlockStateModelLoader.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,15 +3,12 @@ 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;
|
||||
|
|
@ -29,7 +26,6 @@ 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")
|
||||
|
|
@ -45,18 +41,18 @@ public abstract class TextureMetadataHandlerMixin implements ModernFixClientInte
|
|||
ModernFixClient.CLIENT_INTEGRATIONS.add(this);
|
||||
}
|
||||
|
||||
@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)
|
||||
@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)
|
||||
private void noIteration(CallbackInfo ci) {
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel onBakedModelLoad(ResourceLocation rl, UnbakedModel rootModel, BakedModel baked, ModelState state, ModelBakery bakery) {
|
||||
if (rl instanceof ModelResourceLocation && !(baked instanceof AbstractCTMBakedModel) && !baked.isCustomRenderer()) {
|
||||
public BakedModel onBakedModelLoad(ModelResourceLocation mrl, UnbakedModel rootModel, BakedModel baked, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter getter) {
|
||||
if (!(baked instanceof AbstractCTMBakedModel) && !baked.isCustomRenderer()) {
|
||||
Deque<ResourceLocation> dependencies = new ArrayDeque<>();
|
||||
Set<ResourceLocation> seenModels = new HashSet<>();
|
||||
dependencies.push(rl);
|
||||
seenModels.add(rl);
|
||||
dependencies.push(mrl.id());
|
||||
seenModels.add(mrl.id());
|
||||
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
|
||||
|
|
@ -64,7 +60,7 @@ public abstract class TextureMetadataHandlerMixin implements ModernFixClientInte
|
|||
ResourceLocation dep = dependencies.pop();
|
||||
UnbakedModel model;
|
||||
try {
|
||||
model = dep == rl ? rootModel : bakery.getModel(dep);
|
||||
model = dep == mrl.id() ? rootModel : bakery.getModel(dep);
|
||||
} catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -92,32 +88,25 @@ public abstract class TextureMetadataHandlerMixin implements ModernFixClientInte
|
|||
if (shouldWrap) {
|
||||
try {
|
||||
baked = wrap(rootModel, baked);
|
||||
handleInit(rl, baked, bakery);
|
||||
handleInit(mrl, baked, bakery, getter);
|
||||
dependencies.clear();
|
||||
} catch (IOException e) {
|
||||
CTM.logger.error("Could not wrap model " + rl + ". Aborting...", e);
|
||||
CTM.logger.error("Could not wrap model " + mrl + ". Aborting...", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return baked;
|
||||
}
|
||||
|
||||
private void handleInit(ResourceLocation key, BakedModel wrappedModel, ModelBakery bakery) {
|
||||
private void handleInit(ModelResourceLocation key, BakedModel wrappedModel, ModelBakery bakery, ModelBakery.TextureGetter spriteGetter) {
|
||||
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();
|
||||
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);
|
||||
ModelBakery.ModelBakerImpl baker = bakery.new ModelBakerImpl(spriteGetter, key);
|
||||
ctmModel.bake(baker, m -> spriteGetter.get(key, m), BlockModelRotation.X0_Y0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,9 @@ 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.forge.ClientProxyImpl;
|
||||
import com.lowdragmc.lowdraglib.client.model.custommodel.CustomBakedModel;
|
||||
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;
|
||||
|
|
@ -29,9 +27,8 @@ import java.util.Deque;
|
|||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(ClientProxyImpl.class)
|
||||
@Mixin(ClientProxy.class)
|
||||
@ClientOnlyMixin
|
||||
@RequiresMod("ldlib")
|
||||
public abstract class ClientProxyImplMixin implements ModernFixClientIntegration {
|
||||
|
|
@ -46,11 +43,11 @@ public abstract class ClientProxyImplMixin implements ModernFixClientIntegration
|
|||
}
|
||||
|
||||
@Override
|
||||
public BakedModel onBakedModelLoad(ResourceLocation rl, UnbakedModel rootModel, BakedModel baked, ModelState state, ModelBakery bakery, Function<Material, TextureAtlasSprite> textureGetter) {
|
||||
public BakedModel onBakedModelLoad(ModelResourceLocation mrl, UnbakedModel rootModel, BakedModel baked, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter textureGetter) {
|
||||
if (baked == null) {
|
||||
return null;
|
||||
}
|
||||
if (rl instanceof ModelResourceLocation && rootModel != null) {
|
||||
if (rootModel != null) {
|
||||
if (baked instanceof LDLRendererModel) {
|
||||
return baked;
|
||||
}
|
||||
|
|
@ -59,9 +56,10 @@ 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(rl, false);
|
||||
boolean shouldWrap = ClientProxy.WRAPPED_MODELS.getOrDefault(mrl, 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();
|
||||
|
|
@ -92,9 +90,9 @@ public abstract class ClientProxyImplMixin implements ModernFixClientIntegration
|
|||
LDLib.LOGGER.error("Error loading baked dependency {} for baked {}. Skipping...", dep, rl, e);
|
||||
}
|
||||
}
|
||||
ClientProxy.WRAPPED_MODELS.put(rl, shouldWrap);
|
||||
ClientProxy.WRAPPED_MODELS.put(mrl, shouldWrap);
|
||||
if (shouldWrap) {
|
||||
return new CustomBakedModelImpl(baked);
|
||||
return new CustomBakedModel<>(baked);
|
||||
}
|
||||
}
|
||||
return baked;
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.encoder_cache_leak;
|
||||
|
||||
import net.minecraft.client.multiplayer.ClientConfigurationPacketListenerImpl;
|
||||
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(ClientConfigurationPacketListenerImpl.class)
|
||||
@ClientOnlyMixin
|
||||
public class ClientConfigurationPacketListenerImplMixin {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Reset the encoder cache after configuration finishes as the registries are now changing.
|
||||
*/
|
||||
@Inject(method = "handleConfigurationFinished", at = @At("RETURN"))
|
||||
private void resetEncoderCache(CallbackInfo ci) {
|
||||
((EncoderCacheAccessor)DataComponentsAccessor.mfix$getCache()).mfix$getCache().invalidateAll();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
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();
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.encoder_cache_leak;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
@ClientOnlyMixin
|
||||
public class MinecraftMixin {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Make sure the encoder cache is cleared when the client disconnects, as it retains strong references
|
||||
* to registries.
|
||||
*/
|
||||
@Inject(method = {
|
||||
"disconnect(Lnet/minecraft/client/gui/screens/Screen;Z)V",
|
||||
"clearClientLevel(Lnet/minecraft/client/gui/screens/Screen;)V"
|
||||
}, at = @At("RETURN"))
|
||||
private void clearEncoderCache(CallbackInfo ci) {
|
||||
((EncoderCacheAccessor)DataComponentsAccessor.mfix$getCache()).mfix$getCache().invalidateAll();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.fast_registry_validation;
|
||||
|
||||
import net.minecraftforge.registries.ForgeRegistry;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
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;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Mixin(value = ForgeRegistry.class, remap = false)
|
||||
public class ForgeRegistryMixin<V> {
|
||||
private int expectedNextBit = -1;
|
||||
|
||||
/**
|
||||
* Avoid calling nextClearBit and scanning the whole registry for every block registration.
|
||||
*/
|
||||
@Redirect(method = "add(ILnet/minecraft/resources/ResourceLocation;Ljava/lang/Object;Ljava/lang/String;)I", at = @At(value = "INVOKE", target = "Ljava/util/BitSet;nextClearBit(I)I"))
|
||||
private int useCachedBit(BitSet availabilityMap, int minimum) {
|
||||
int bit = availabilityMap.nextClearBit(expectedNextBit != -1 ? expectedNextBit : minimum);
|
||||
expectedNextBit = bit + 1;
|
||||
return bit;
|
||||
}
|
||||
|
||||
@Inject(method = { "sync", "clear", "block" }, at = @At("HEAD"))
|
||||
private void clearBitCache(CallbackInfo ci) {
|
||||
expectedNextBit = -1;
|
||||
}
|
||||
|
||||
@Redirect(method = "add(ILnet/minecraft/resources/ResourceLocation;Ljava/lang/Object;Ljava/lang/String;)I", at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;trace(Lorg/apache/logging/log4j/Marker;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V"))
|
||||
private void skipTrace(Logger logger, Marker marker, String s, Object o, Object o1, Object o2, Object o3, Object o4) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.faster_capabilities;
|
||||
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.capabilities.CapabilityDispatcher;
|
||||
import net.minecraftforge.common.capabilities.CapabilityProvider;
|
||||
import net.minecraftforge.common.capabilities.ICapabilityProvider;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import org.embeddedt.modernfix.forge.capability.CapabilityProviderDispatcherGenerator;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(CapabilityDispatcher.class)
|
||||
public class CapabilityDispatcherMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private ICapabilityProvider[] caps;
|
||||
private ICapabilityProvider mfix$turboDispatcher;
|
||||
|
||||
@Inject(method = "<init>(Ljava/util/Map;Ljava/util/List;Lnet/minecraftforge/common/capabilities/ICapabilityProvider;)V", at = @At("RETURN"))
|
||||
private void createTurboDispatcher(Map list, List listeners, ICapabilityProvider parent, CallbackInfo ci) {
|
||||
this.mfix$turboDispatcher = CapabilityProviderDispatcherGenerator.getOrGenerateDispatcher(this.caps);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason use ASM-generated dispatcher
|
||||
*/
|
||||
@Overwrite(remap = false)
|
||||
public <T> LazyOptional<T> getCapability(Capability<T> cap, @Nullable Direction side) {
|
||||
return this.mfix$turboDispatcher.getCapability(cap, side);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.faster_capabilities.bytecode_analysis;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.common.capabilities.ICapabilityProvider;
|
||||
import net.minecraftforge.event.AttachCapabilitiesEvent;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.eventbus.api.EventPriority;
|
||||
import org.embeddedt.modernfix.duck.IBatchingCapEvent;
|
||||
import org.embeddedt.modernfix.forge.capability.analysis.CapabilityAnalysisResult;
|
||||
import org.embeddedt.modernfix.forge.capability.analysis.CapabilityAnalyzer;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(AttachCapabilitiesEvent.class)
|
||||
public abstract class AttachCapabilitiesEventMixin extends Event implements IBatchingCapEvent {
|
||||
@Shadow @Final
|
||||
private Map<ResourceLocation, ICapabilityProvider> caps;
|
||||
|
||||
@Unique
|
||||
private final Map<ResourceLocation, EventPriority> mfix$phaseMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason record the current dispatch phase so we can sort within phase groups later
|
||||
*/
|
||||
@WrapMethod(method = "addCapability", remap = false)
|
||||
private void mfix$trackPhase(ResourceLocation key, ICapabilityProvider cap, Operation<Void> original) {
|
||||
original.call(key, cap);
|
||||
mfix$phaseMap.put(key, this.getPhase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mfix$sortCaps() {
|
||||
if (caps.size() < 2) {
|
||||
return;
|
||||
}
|
||||
var entries = new ArrayList<>(caps.entrySet());
|
||||
entries.sort(Comparator.comparingInt(e -> {
|
||||
EventPriority phase = mfix$phaseMap.getOrDefault(e.getKey(), EventPriority.NORMAL);
|
||||
var result = CapabilityAnalyzer.analyze(e.getValue().getClass());
|
||||
// Primary: preserve phase ordering (HIGHEST=0 .. LOWEST=4)
|
||||
// Secondary: Known/AlwaysEmpty before Indeterminate within each phase
|
||||
int capKey = result instanceof CapabilityAnalysisResult.Indeterminate ? 1 : 0;
|
||||
return phase.ordinal() * 2 + capKey;
|
||||
}));
|
||||
caps.clear();
|
||||
entries.forEach(e -> caps.put(e.getKey(), e.getValue()));
|
||||
mfix$phaseMap.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.faster_capabilities.bytecode_analysis;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraftforge.event.ForgeEventFactory;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import org.embeddedt.modernfix.duck.IBatchingCapEvent;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(value = ForgeEventFactory.class, remap = false)
|
||||
public class ForgeEventFactoryMixin {
|
||||
@WrapOperation(method = "gatherCapabilities(Lnet/minecraftforge/event/AttachCapabilitiesEvent;Lnet/minecraftforge/common/capabilities/ICapabilityProvider;)Lnet/minecraftforge/common/capabilities/CapabilityDispatcher;", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/eventbus/api/IEventBus;post(Lnet/minecraftforge/eventbus/api/Event;)Z"))
|
||||
private static boolean modernfix$sortAfterPost(IEventBus instance, Event event, Operation<Boolean> original) {
|
||||
boolean result = original.call(instance, event);
|
||||
((IBatchingCapEvent) event).mfix$sortCaps();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.faster_ingredients;
|
||||
|
||||
import cofh.lib.util.crafting.IngredientWithCount;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import org.embeddedt.modernfix.annotation.RequiresMod;
|
||||
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 java.util.stream.Stream;
|
||||
|
||||
@Mixin(IngredientWithCount.class)
|
||||
@RequiresMod("cofh_core")
|
||||
public class CofhIngredientWithCountMixin extends Ingredient {
|
||||
@Shadow @Final private Ingredient wrappedIngredient;
|
||||
@Shadow @Final private int count;
|
||||
|
||||
@Unique
|
||||
private ItemStack[] mfix$itemStacksWithAdjustedCount;
|
||||
|
||||
protected CofhIngredientWithCountMixin(Stream<? extends Value> values) {
|
||||
super(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason reimplement in a way that doesn't rely on the itemStacks implementation detail of the wrapped ingredient
|
||||
*/
|
||||
@Overwrite
|
||||
public ItemStack[] getItems() {
|
||||
if (this.mfix$itemStacksWithAdjustedCount == null) {
|
||||
ItemStack[] originalItems = this.wrappedIngredient.getItems();
|
||||
var newItems = new ItemStack[originalItems.length];
|
||||
for (int i = 0; i < originalItems.length; i++) {
|
||||
newItems[i] = originalItems[i].copy();
|
||||
newItems[i].setCount(this.count);
|
||||
}
|
||||
this.mfix$itemStacksWithAdjustedCount = newItems;
|
||||
}
|
||||
return this.mfix$itemStacksWithAdjustedCount;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.faster_ingredients;
|
||||
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import org.embeddedt.modernfix.forge.recipe.ExtendedIngredient;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(value = ForgeHooks.class, priority = 900)
|
||||
public class ForgeHooksMixin {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Exploding the stack list is entirely unnecessary to compute this
|
||||
*/
|
||||
@Inject(method = "hasNoElements", at = @At("HEAD"), cancellable = true, remap = false)
|
||||
private static void modernfix$fastHasNoElements(Ingredient ingredient, CallbackInfoReturnable<Boolean> cir) {
|
||||
if (ingredient.isVanilla()) {
|
||||
cir.setReturnValue(((ExtendedIngredient)ingredient).mfix$hasNoElements());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,12 @@ import it.unimi.dsi.fastutil.ints.IntComparators;
|
|||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.ItemStackLinkedSet;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import org.embeddedt.modernfix.forge.load.MinecraftServerReloadTracker;
|
||||
import org.embeddedt.modernfix.forge.recipe.ExtendedIngredient;
|
||||
import org.embeddedt.modernfix.forge.recipe.IngredientItemStacksSoftReference;
|
||||
import net.neoforged.neoforge.common.crafting.ICustomIngredient;
|
||||
import org.embeddedt.modernfix.neoforge.load.MinecraftServerReloadTracker;
|
||||
import org.embeddedt.modernfix.neoforge.recipe.ExtendedIngredient;
|
||||
import org.embeddedt.modernfix.neoforge.recipe.IngredientItemStacksSoftReference;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
|
@ -17,16 +19,13 @@ 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mixin(value = Ingredient.class, priority = 700)
|
||||
public abstract class IngredientMixin implements ExtendedIngredient {
|
||||
@Shadow
|
||||
public abstract boolean isVanilla();
|
||||
|
||||
@Shadow @Final
|
||||
private Ingredient.Value[] values;
|
||||
|
||||
|
|
@ -34,6 +33,15 @@ public abstract class IngredientMixin implements ExtendedIngredient {
|
|||
|
||||
@Shadow @Nullable private ItemStack[] itemStacks;
|
||||
|
||||
@Shadow public abstract boolean isCustom();
|
||||
|
||||
@Shadow private ICustomIngredient customIngredient;
|
||||
|
||||
@Unique
|
||||
private boolean isVanilla() {
|
||||
return !this.isCustom();
|
||||
}
|
||||
|
||||
private volatile IngredientItemStacksSoftReference mfix$cachedItemStacks;
|
||||
|
||||
/**
|
||||
|
|
@ -62,13 +70,19 @@ public abstract class IngredientMixin implements ExtendedIngredient {
|
|||
@Inject(method = "test(Lnet/minecraft/world/item/ItemStack;)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/crafting/Ingredient;getItems()[Lnet/minecraft/world/item/ItemStack;"), cancellable = true)
|
||||
private void modernfix$fasterTagIngredientTest(ItemStack stack, CallbackInfoReturnable<Boolean> cir) {
|
||||
if (this.isVanilla() && this.values.length == 1 && this.values[0] instanceof Ingredient.TagValue tagValue && mfix$areTagsAvailable()) {
|
||||
cir.setReturnValue(stack.getItemHolder().is(tagValue.tag));
|
||||
cir.setReturnValue(stack.getItemHolder().is(tagValue.tag()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mfix$hasNoElements() {
|
||||
return !this.containsItems();
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason exploding the stack list is unnecessary
|
||||
*/
|
||||
@Inject(method = "hasNoItems", at = @At("HEAD"), cancellable = true, remap = false)
|
||||
public void hasNoItems(CallbackInfoReturnable<Boolean> cir) {
|
||||
if (this.isVanilla()) {
|
||||
cir.setReturnValue(!this.containsItems());
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
|
|
@ -82,7 +96,7 @@ public abstract class IngredientMixin implements ExtendedIngredient {
|
|||
if (value instanceof Ingredient.ItemValue) {
|
||||
return true;
|
||||
} else if (value instanceof Ingredient.TagValue tagValue && mfix$areTagsAvailable()) {
|
||||
var holderSetOpt = BuiltInRegistries.ITEM.getTag(tagValue.tag);
|
||||
var holderSetOpt = BuiltInRegistries.ITEM.getTag(tagValue.tag());
|
||||
if (holderSetOpt.isPresent() && holderSetOpt.get().size() > 0) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -106,7 +120,7 @@ public abstract class IngredientMixin implements ExtendedIngredient {
|
|||
@Inject(method = "getStackingIds", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/crafting/Ingredient;getItems()[Lnet/minecraft/world/item/ItemStack;"), cancellable = true)
|
||||
private void modernfix$fasterTagIngredientStacking(CallbackInfoReturnable<IntList> cir) {
|
||||
if (this.isVanilla() && this.values.length == 1 && this.values[0] instanceof Ingredient.TagValue tagValue && mfix$areTagsAvailable()) {
|
||||
var tag = BuiltInRegistries.ITEM.getTag(tagValue.tag);
|
||||
var tag = BuiltInRegistries.ITEM.getTag(tagValue.tag());
|
||||
if (!tag.isPresent() || tag.get().size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -128,6 +142,11 @@ public abstract class IngredientMixin implements ExtendedIngredient {
|
|||
if (this.itemStacks != null) {
|
||||
return this.itemStacks;
|
||||
}
|
||||
if (this.customIngredient != null) {
|
||||
// We probably have to cache this as mods won't make it fast if they expect Neo to cache it
|
||||
this.itemStacks = this.customIngredient.getItems().collect(Collectors.toCollection(ItemStackLinkedSet::createTypeAndComponentsSet)).toArray(ItemStack[]::new);
|
||||
return this.itemStacks;
|
||||
}
|
||||
var cache = this.mfix$cachedItemStacks;
|
||||
if (cache != null) {
|
||||
var stacks = cache.get();
|
||||
|
|
@ -145,7 +164,7 @@ public abstract class IngredientMixin implements ExtendedIngredient {
|
|||
// Fast path for case with one item
|
||||
if (this.values.length == 1) {
|
||||
if (this.values[0] instanceof Ingredient.TagValue tagValue && mfix$areTagsAvailable()) {
|
||||
var tag = BuiltInRegistries.ITEM.getTag(tagValue.tag);
|
||||
var tag = BuiltInRegistries.ITEM.getTag(tagValue.tag());
|
||||
if (tag.isPresent() && tag.get().size() > 0) {
|
||||
var holderSet = tag.get();
|
||||
ItemStack[] result = new ItemStack[holderSet.size()];
|
||||
|
|
@ -171,9 +190,4 @@ public abstract class IngredientMixin implements ExtendedIngredient {
|
|||
public void mfix$clearReference() {
|
||||
this.mfix$cachedItemStacks = null;
|
||||
}
|
||||
|
||||
@Inject(method = "invalidate", at = @At("RETURN"), remap = false)
|
||||
private void invalidateSoftReference(CallbackInfo ci) {
|
||||
mfix$clearReference();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||
@Mixin(GameRenderer.class)
|
||||
@ClientOnlyMixin
|
||||
public class GameRendererMixin {
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;renderLevel(FJLcom/mojang/blaze3d/vertex/PoseStack;)V", shift = At.Shift.BEFORE))
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;renderLevel(Lnet/minecraft/client/DeltaTracker;)V", shift = At.Shift.BEFORE))
|
||||
private void markRenderingLevel(CallbackInfo ci) {
|
||||
RenderState.IS_RENDERING_LEVEL = true;
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;renderLevel(FJLcom/mojang/blaze3d/vertex/PoseStack;)V", shift = At.Shift.AFTER))
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;renderLevel(Lnet/minecraft/client/DeltaTracker;)V", shift = At.Shift.AFTER))
|
||||
private void markNotRenderingLevel(CallbackInfo ci) {
|
||||
RenderState.IS_RENDERING_LEVEL = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.faster_loot_loading;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.world.level.storage.loot.LootTable;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import org.apache.commons.lang3.function.TriFunction;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
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.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static net.minecraftforge.common.ForgeHooks.loadLootTable;
|
||||
|
||||
@Mixin(value = ForgeHooks.class, remap = false)
|
||||
@RequiresFeatureLevel(FeatureLevel.BETA)
|
||||
public class ForgeHooksMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private static Logger LOGGER;
|
||||
|
||||
private static boolean mfix$isVanillaTable(JsonElement data) {
|
||||
if (!(data instanceof JsonObject obj)) {
|
||||
return false;
|
||||
}
|
||||
var vanillaMarker = obj.getAsJsonPrimitive("mfix$isVanillaTable");
|
||||
if (vanillaMarker == null) {
|
||||
return false;
|
||||
}
|
||||
return vanillaMarker.getAsBoolean();
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason avoid getResource() call per loot table by using injected marker
|
||||
*/
|
||||
@Overwrite
|
||||
public static TriFunction<ResourceLocation, JsonElement, ResourceManager, Optional<LootTable>> getLootTableDeserializer(Gson gson, String directory) {
|
||||
return (location, data, resourceManager) -> {
|
||||
try {
|
||||
boolean custom = !mfix$isVanillaTable(data);
|
||||
return Optional.ofNullable(loadLootTable(gson, location, data, custom));
|
||||
} catch (Exception exception) {
|
||||
LOGGER.error("Couldn't parse element {}:{}", directory, location, exception);
|
||||
return Optional.empty();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.faster_loot_loading;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.resources.FileToIdConverter;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.world.level.storage.loot.LootDataManager;
|
||||
import net.minecraft.world.level.storage.loot.LootDataType;
|
||||
import org.embeddedt.modernfix.annotation.FeatureLevel;
|
||||
import org.embeddedt.modernfix.annotation.RequiresFeatureLevel;
|
||||
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.Map;
|
||||
|
||||
@Mixin(LootDataManager.class)
|
||||
@RequiresFeatureLevel(FeatureLevel.BETA)
|
||||
public class LootDataManagerMixin {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason inject a marker for vanilla loot tables into the JSON so that we can retrieve it from the deserializer
|
||||
*/
|
||||
@Inject(method = "lambda$scheduleElementParse$5", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/packs/resources/SimpleJsonResourceReloadListener;scanDirectory(Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/lang/String;Lcom/google/gson/Gson;Ljava/util/Map;)V", shift = At.Shift.AFTER))
|
||||
private static void mfix$scanAndCapture(ResourceManager resourceManager, LootDataType lootDataType, Map map, CallbackInfo ci,
|
||||
@Local(ordinal = 1) Map<ResourceLocation, JsonElement> lootTables) {
|
||||
FileToIdConverter converter = FileToIdConverter.json(lootDataType.directory());
|
||||
var lootTableResourceMap = converter.listMatchingResources(resourceManager);
|
||||
for (var entry : lootTableResourceMap.entrySet()) {
|
||||
if (lootTables.get(converter.fileToId(entry.getKey())) instanceof JsonObject obj) {
|
||||
var resource = entry.getValue();
|
||||
if (resource != null && !resource.isBuiltin()) {
|
||||
obj.addProperty("mfix$isVanillaTable", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.faster_structure_location;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureCheck;
|
||||
import org.embeddedt.modernfix.duck.IStructureCheck;
|
||||
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;
|
||||
|
||||
@Mixin(ServerLevel.class)
|
||||
public class ServerLevelMixin {
|
||||
@Shadow @Final private ServerChunkCache chunkSource;
|
||||
|
||||
@ModifyExpressionValue(method = "<init>", at = @At(value = "NEW", target = "(Lnet/minecraft/world/level/chunk/storage/ChunkScanAccess;Lnet/minecraft/core/RegistryAccess;Lnet/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager;Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/world/level/chunk/ChunkGenerator;Lnet/minecraft/world/level/levelgen/RandomState;Lnet/minecraft/world/level/LevelHeightAccessor;Lnet/minecraft/world/level/biome/BiomeSource;JLcom/mojang/datafixers/DataFixer;)Lnet/minecraft/world/level/levelgen/structure/StructureCheck;", ordinal = 0))
|
||||
private StructureCheck attachGeneratorState(StructureCheck check) {
|
||||
((IStructureCheck)check).mfix$setStructureState(this.chunkSource.getGeneratorState());
|
||||
return check;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.faster_structure_location;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureCheck;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureCheckResult;
|
||||
import org.embeddedt.modernfix.duck.IStructureCheck;
|
||||
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;
|
||||
|
||||
@Mixin(StructureCheck.class)
|
||||
public class StructureCheckMixin implements IStructureCheck {
|
||||
@Shadow @Final private Registry<Structure> structureConfigs;
|
||||
|
||||
private ChunkGeneratorStructureState mfix$structureState;
|
||||
|
||||
@Override
|
||||
public void mfix$setStructureState(ChunkGeneratorStructureState state) {
|
||||
mfix$structureState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt (inspired by 24w04a and Bytzo's comment on https://bugs.mojang.com/browse/MC-249136)
|
||||
* @reason Avoid running the canCreateStructure method (which can be expensive) if the structure placement already
|
||||
* forbids placing the structure in this chunk.
|
||||
*/
|
||||
@ModifyExpressionValue(method = "checkStart", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/structure/StructureCheck;tryLoadFromStorage(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/levelgen/structure/Structure;ZJ)Lnet/minecraft/world/level/levelgen/structure/StructureCheckResult;"))
|
||||
private StructureCheckResult mfix$checkForValidPosition(StructureCheckResult storageResult, ChunkPos chunkPos, Structure structure, boolean skipKnownStructures) {
|
||||
if (storageResult != null) {
|
||||
return storageResult;
|
||||
} else if(mfix$structureState != null) {
|
||||
// Check if any of the placements allow for this structure to be in this chunk
|
||||
var structureHolder = this.structureConfigs.wrapAsHolder(structure);
|
||||
for (var placement : mfix$structureState.getPlacementsForStructure(structureHolder)) {
|
||||
if (placement.isStructureChunk(mfix$structureState, chunkPos.x, chunkPos.z)) {
|
||||
// Allowed - return null so regular check runs
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Not allowed - early exit by returning a non-null value
|
||||
return StructureCheckResult.START_NOT_PRESENT;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.fix_handshake_stall;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraftforge.network.HandshakeHandler;
|
||||
import net.minecraftforge.network.NetworkRegistry;
|
||||
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.Slice;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(value = HandshakeHandler.class, remap = false)
|
||||
public class HandshakeHandlerMixin {
|
||||
@Shadow
|
||||
private int packetPosition;
|
||||
|
||||
@Shadow
|
||||
private List<NetworkRegistry.LoginPayload> messageList;
|
||||
|
||||
@Shadow
|
||||
private List<Integer> sentMessages;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason we must synchronize sentMessages because it is modified from both the Netty thread and the
|
||||
* server thread
|
||||
*/
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void synchronizeSentMessages(CallbackInfo ci) {
|
||||
this.sentMessages = Collections.synchronizedList(this.sentMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Forge only sends one login payload per tick. It takes many seconds to send all the payloads at this rate.
|
||||
* During this time, the game remains frozen on the chunk loading screen with almost zero CPU usage.
|
||||
* To fix this, we re-tick the handshake handler until the packetPosition stops advancing or the handler indicates
|
||||
* it no longer needs ticking.
|
||||
*/
|
||||
@WrapMethod(method = "tickServer")
|
||||
private boolean modernfix$retick(Operation<Boolean> original) {
|
||||
boolean isDoneTicking;
|
||||
int prevPacketPosition;
|
||||
do {
|
||||
prevPacketPosition = this.packetPosition;
|
||||
isDoneTicking = original.call();
|
||||
} while(!isDoneTicking && this.packetPosition > prevPacketPosition);
|
||||
return isDoneTicking;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason The original HandshakeHandler has an off-by-one error in its completion check. We patch this to prevent
|
||||
* our optimization from potentially triggering it more often due to the timing change.
|
||||
*/
|
||||
@WrapOperation(method = "tickServer", at = @At(value = "INVOKE", target = "Ljava/util/List;isEmpty()Z", ordinal = 0), slice = @Slice(from = @At(value = "INVOKE", target = "Ljava/util/List;removeIf(Ljava/util/function/Predicate;)Z", ordinal = 0)))
|
||||
private boolean preventEarlyExit(List<?> instance, Operation<Boolean> original) {
|
||||
if (instance != this.sentMessages) {
|
||||
throw new AssertionError("Injector is misplaced");
|
||||
}
|
||||
return original.call(instance) && this.packetPosition >= this.messageList.size();
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ import java.util.function.BooleanSupplier;
|
|||
|
||||
@Mixin(value = MinecraftServer.class, priority = 500)
|
||||
public abstract class MinecraftServerMixin extends BlockableEventLoop<Runnable> {
|
||||
@Shadow private long nextTickTime;
|
||||
@Shadow private long nextTickTimeNanos;
|
||||
|
||||
protected MinecraftServerMixin(String name) {
|
||||
super(name);
|
||||
|
|
@ -37,12 +37,12 @@ public abstract class MinecraftServerMixin extends BlockableEventLoop<Runnable>
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void waitForTasks() {
|
||||
@WrapOperation(method = "waitForTasks", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/thread/ReentrantBlockableEventLoop;waitForTasks()V"))
|
||||
private void waitLongerForTasks(MinecraftServer instance, Operation<Void> original) {
|
||||
if (this.mfix$isWaitingForNextTick) {
|
||||
LockSupport.parkNanos("waiting for tasks", (this.nextTickTime * 1000000L) - Util.getNanos());
|
||||
LockSupport.parkNanos("waiting for tasks", this.nextTickTimeNanos - Util.getNanos());
|
||||
} else {
|
||||
super.waitForTasks();
|
||||
original.call(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.forge_cap_retrieval;
|
||||
|
||||
import net.minecraftforge.event.AttachCapabilitiesEvent;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(AttachCapabilitiesEvent.class)
|
||||
public abstract class AttachCapabilitiesEventMixin extends Event {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason EventSubclassTransformer is supposed to inject an override returning a constant on the class to avoid the
|
||||
* {@link net.minecraftforge.eventbus.api.EventListenerHelper#isCancelable(Class)} slow path.
|
||||
* However, the false case is only done for direct subclasses of Event (the true case is done for
|
||||
* any cancelable event). This works for normal events because they must subclass Event directly, or be a subclass
|
||||
* of an event that does. However, AttachCapabilitiesEvent subclasses GenericEvent, which does not pass through
|
||||
* the EventSubclassTransformer as it comes from the EventBus library (where transformers are not run) rather than
|
||||
* Forge which is on the GAME layer. The transformer on AttachCapabilitiesEvent then does not add the override as
|
||||
* it expects it to be present on GenericEvent already.
|
||||
* <p>
|
||||
* The simplest workaround to that whole mess is to just inject the override ourselves.
|
||||
*/
|
||||
@Override
|
||||
public boolean isCancelable() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.forge_cap_retrieval;
|
||||
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.capabilities.ForgeCapabilities;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Mixin(LivingEntity.class)
|
||||
public class LivingEntityMixin {
|
||||
/**
|
||||
* @author embeddedt (issue noted by XFactHD)
|
||||
* @reason check capability equality before checking that entity is alive, the latter requires a lot more
|
||||
* indirection
|
||||
*/
|
||||
@Redirect(method = "getCapability", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;isAlive()Z"))
|
||||
private <T> boolean checkAliveAfterCap(LivingEntity entity, Capability<T> capability, @Nullable Direction facing) {
|
||||
return capability == ForgeCapabilities.ITEM_HANDLER && entity.isAlive();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.forge_registry_alloc;
|
||||
|
||||
import net.minecraft.world.level.levelgen.DebugLevelSource;
|
||||
import net.minecraftforge.registries.GameData;
|
||||
import net.neoforged.neoforge.registries.GameData;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.forge_registry_alloc;
|
||||
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import org.embeddedt.modernfix.forge.registry.DelegateHolder;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin({Block.class, Item.class})
|
||||
public class DelegateHolderMixin<T> implements DelegateHolder<T> {
|
||||
private Holder.Reference<T> mfix$delegate;
|
||||
private ResourceKey<Registry<T>> mfix$key;
|
||||
|
||||
@Override
|
||||
public Holder.Reference<T> mfix$getDelegate(ResourceKey<Registry<T>> registryKey) {
|
||||
if(mfix$key == registryKey) {
|
||||
return mfix$delegate;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mfix$setDelegate(ResourceKey<Registry<T>> registryKey, Holder.Reference<T> holder) {
|
||||
this.mfix$delegate = holder;
|
||||
this.mfix$key = registryKey;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.forge_registry_alloc;
|
||||
|
||||
import it.unimi.dsi.fastutil.Hash;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.registries.ForgeRegistry;
|
||||
import net.minecraftforge.registries.RegistryManager;
|
||||
import org.embeddedt.modernfix.forge.registry.DelegateHolder;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(value = ForgeRegistry.class, remap = false)
|
||||
public abstract class ForgeRegistryMixin<V> {
|
||||
// Replace the backing maps with fastutil maps for a bit more speed, since value->holder lookups in particular
|
||||
// are a bottleneck in many areas (e.g. render type lookup)
|
||||
@Shadow @Final private Map<ResourceLocation, Holder.Reference<V>> delegatesByName = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
@Shadow @Final private Map<V, Holder.Reference<V>> delegatesByValue = new Object2ObjectOpenHashMap<>(Hash.DEFAULT_INITIAL_SIZE, 0.5F);
|
||||
|
||||
@Shadow public abstract ResourceKey<Registry<V>> getRegistryKey();
|
||||
|
||||
@Shadow @Final private RegistryManager stage;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason stop allocating so many unneeded objects. stop.
|
||||
*/
|
||||
@Overwrite
|
||||
public Holder.Reference<V> getDelegateOrThrow(ResourceLocation location) {
|
||||
Holder.Reference<V> holder = delegatesByName.get(location);
|
||||
|
||||
if (holder == null) {
|
||||
throw new IllegalArgumentException(String.format(Locale.ENGLISH, "No delegate exists for location %s", location));
|
||||
}
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason see above
|
||||
*/
|
||||
@Overwrite
|
||||
public Holder.Reference<V> getDelegateOrThrow(ResourceKey<V> rkey) {
|
||||
Holder.Reference<V> holder = delegatesByName.get(rkey.location());
|
||||
|
||||
if (holder == null) {
|
||||
throw new IllegalArgumentException(String.format(Locale.ENGLISH, "No delegate exists for key %s", rkey));
|
||||
}
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason store delegates that are accessed extremely regularly on the registry entry itself, rather than
|
||||
* going through a map lookup
|
||||
*/
|
||||
@Inject(method = "bindDelegate", at = @At("RETURN"))
|
||||
private void attachDelegate(ResourceKey<V> rkey, V value, CallbackInfoReturnable<Holder.Reference<V>> cir) {
|
||||
// Only attach delegates for the ACTIVE registry. The Forge registry system is weird and seems to keep multiple
|
||||
// copies of itself at once.
|
||||
if(this.stage == RegistryManager.ACTIVE && value instanceof DelegateHolder<?>) {
|
||||
((DelegateHolder<V>)value).mfix$setDelegate(this.getRegistryKey(), cir.getReturnValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason skip map lookup for hot delegates, avoid allocations otherwise
|
||||
*/
|
||||
@Overwrite
|
||||
public Holder.Reference<V> getDelegateOrThrow(V value) {
|
||||
Holder.Reference<V> holder = null;
|
||||
if (this.stage == RegistryManager.ACTIVE && value instanceof DelegateHolder<?>) {
|
||||
holder = ((DelegateHolder<V>)value).mfix$getDelegate(this.getRegistryKey());
|
||||
}
|
||||
|
||||
if (holder == null) {
|
||||
holder = delegatesByValue.get(value);
|
||||
if (holder == null) {
|
||||
throw new IllegalArgumentException(String.format(Locale.ENGLISH, "No delegate exists for value %s", value));
|
||||
}
|
||||
}
|
||||
|
||||
return holder;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.forge_registry_lambda;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(value = RegistryObject.class, remap = false)
|
||||
public class RegistryObjectMixin<T> {
|
||||
@Shadow private @Nullable T value;
|
||||
|
||||
@Shadow @Final private ResourceLocation name;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason avoid lambda allocation on every call
|
||||
*/
|
||||
@Overwrite
|
||||
public T get() {
|
||||
T ret = this.value;
|
||||
if(ret == null) {
|
||||
throw new NullPointerException("Registry Object not present: " + this.name);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.ingredient_item_deduplication;
|
||||
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import org.embeddedt.modernfix.forge.recipe.IngredientValueDeduplicator;
|
||||
import org.embeddedt.modernfix.neoforge.recipe.IngredientValueDeduplicator;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
|
|
@ -10,8 +10,20 @@ import java.util.stream.Stream;
|
|||
|
||||
@Mixin(Ingredient.class)
|
||||
public class IngredientMixin {
|
||||
@ModifyVariable(method = "<init>", at = @At("HEAD"), argsOnly = true, ordinal = 0)
|
||||
@ModifyVariable(method = "<init>(Ljava/util/stream/Stream;)V", at = @At("HEAD"), argsOnly = true, ordinal = 0)
|
||||
private static Stream<? extends Ingredient.Value> injectDeduplicationPass(Stream<? extends Ingredient.Value> stream) {
|
||||
return stream.map(IngredientValueDeduplicator::deduplicate);
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "<init>([Lnet/minecraft/world/item/crafting/Ingredient$Value;)V", at = @At("HEAD"), argsOnly = true, ordinal = 0)
|
||||
private static Ingredient.Value[] injectDeduplicationPassArray(Ingredient.Value[] values) {
|
||||
if (values.length == 0) {
|
||||
return values;
|
||||
}
|
||||
Ingredient.Value[] newValues = new Ingredient.Value[values.length];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
newValues[i] = IngredientValueDeduplicator.deduplicate(values[i]);
|
||||
}
|
||||
return newValues;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.ingredient_item_deduplication;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
|
||||
import net.minecraft.core.component.DataComponentMap;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.core.component.PatchedDataComponentMap;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Mixin(PatchedDataComponentMap.class)
|
||||
public interface PatchedDataComponentMapAccessor {
|
||||
@Accessor("prototype")
|
||||
DataComponentMap mfix$getPrototype();
|
||||
@Accessor("patch")
|
||||
Reference2ObjectMap<DataComponentType<?>, Optional<?>> mfix$getPatch();
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.kubejs;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import dev.latvian.mods.kubejs.recipe.RecipeJS;
|
||||
import dev.latvian.mods.kubejs.recipe.RecipesEventJS;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.crafting.Recipe;
|
||||
import net.minecraft.world.item.crafting.RecipeManager;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.annotation.FeatureLevel;
|
||||
import org.embeddedt.modernfix.annotation.RequiresMod;
|
||||
import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig;
|
||||
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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(RecipesEventJS.class)
|
||||
@RequiresMod("kubejs")
|
||||
public class RecipeEventJSMixin {
|
||||
|
||||
/**
|
||||
* The recipe event object can be leaked in scripts and this wastes 40MB of memory.
|
||||
*/
|
||||
@Inject(method = "post", at = @At("RETURN"), remap = false)
|
||||
private void clearRecipeLists(CallbackInfo ci) {
|
||||
ModernFix.LOGGER.info("Clearing KubeJS recipe lists...");
|
||||
// Even though we are a mixin class, use reflection so this works across a variety of versions
|
||||
Field[] fields = RecipesEventJS.class.getDeclaredFields();
|
||||
for(Field f : fields) {
|
||||
try {
|
||||
if(!Modifier.isStatic(f.getModifiers())
|
||||
&& (Collection.class.isAssignableFrom(f.getType())
|
||||
|| Map.class.isAssignableFrom(f.getType()))
|
||||
) {
|
||||
f.setAccessible(true);
|
||||
Object collection = f.get(this);
|
||||
int size;
|
||||
if(collection instanceof Map) {
|
||||
size = ((Map<?, ?>)collection).size();
|
||||
((Map<?, ?>)collection).clear();
|
||||
} else if(collection instanceof Collection) {
|
||||
size = ((Collection<?>)collection).size();
|
||||
((Collection<?>)collection).clear();
|
||||
} else
|
||||
throw new IllegalStateException();
|
||||
ModernFix.LOGGER.debug("Cleared {} with {} entries", f.getName(), size);
|
||||
}
|
||||
} catch(RuntimeException | ReflectiveOperationException e) {
|
||||
ModernFix.LOGGER.debug("Uh oh, couldn't clear field", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason once datapackRecipeMap is iterated, it is never referenced again, so clear it to avoid retaining
|
||||
* references to the JSON objects
|
||||
*/
|
||||
@Inject(method = "post", at = @At(value = "NEW", target = "()Ljava/util/concurrent/ConcurrentLinkedQueue;", ordinal = 0), remap = false)
|
||||
private void modernfix$clearDatapackRecipeMap(RecipeManager recipeManager, Map<ResourceLocation, JsonElement> datapackRecipeMap, CallbackInfo ci) {
|
||||
if (ModernFixEarlyConfig.ACTIVE_FEATURE_LEVEL.isAtLeast(FeatureLevel.BETA)) {
|
||||
datapackRecipeMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason As we start materializing the final recipe objects, null out the JSON references so we avoid having
|
||||
* to keep both in memory at the same time
|
||||
*/
|
||||
@Inject(method = "createRecipe", at = @At("RETURN"), remap = false)
|
||||
private void modernfix$clearJson(RecipeJS r, CallbackInfoReturnable<Recipe<?>> cir) {
|
||||
if (!ModernFixEarlyConfig.ACTIVE_FEATURE_LEVEL.isAtLeast(FeatureLevel.BETA)) {
|
||||
return;
|
||||
}
|
||||
r.json = null;
|
||||
r.originalJson = null;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user