Compare commits
468 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3eaf157365 | ||
|
|
e753953cba | ||
|
|
4a4ef3b2cd | ||
|
|
9dea4a3832 | ||
|
|
433a0e5b70 | ||
|
|
5ab71150f4 | ||
|
|
842f804f7c | ||
|
|
a154f02e24 | ||
|
|
ac62231416 | ||
|
|
422f570ddd | ||
|
|
d70e51d796 | ||
|
|
7a5e2cfb67 | ||
|
|
51f273fae4 | ||
|
|
334a91f3b0 | ||
|
|
564d607db1 | ||
|
|
cae2af8bfc | ||
|
|
502857229d | ||
|
|
6e4074ba3a | ||
|
|
a25e37b968 | ||
|
|
40e8f7ccec | ||
|
|
18734563d6 | ||
|
|
d9fb13a805 | ||
|
|
8e2d6968a0 | ||
|
|
7a8f40d0f9 | ||
|
|
757fdad7e0 | ||
|
|
003bfb46e5 | ||
|
|
be491e29ea | ||
|
|
c45f063bfb | ||
|
|
3da80a6d11 | ||
|
|
565fe3d53e | ||
|
|
f449fcb899 | ||
|
|
a24037a9d5 | ||
|
|
6a4e2810b4 | ||
|
|
9f56c913fa | ||
|
|
10f8be3d93 | ||
|
|
c8a43ff7d6 | ||
|
|
9f11af14b5 | ||
|
|
b9832b076b | ||
|
|
49a88c8bba | ||
|
|
334683fef6 | ||
|
|
7a8beea66e | ||
|
|
97c4b35c82 | ||
|
|
bfc099472f | ||
|
|
b3d1e9bcb0 | ||
|
|
0068f72631 | ||
|
|
9f7c65fc67 | ||
|
|
76a2a97d2d | ||
|
|
3a8172c1c4 | ||
|
|
4927b21f3f | ||
|
|
8eda3ea196 | ||
|
|
58b86a9852 | ||
|
|
f14bfa56e1 | ||
|
|
f9cce166d1 | ||
|
|
0f99216417 | ||
|
|
e9836ceac6 | ||
|
|
dbf343bf91 | ||
|
|
617c50ffff | ||
|
|
0a68a6923a | ||
|
|
a631e17aab | ||
|
|
df58c05d75 | ||
|
|
01fb138c8a | ||
|
|
a0e8d0b012 | ||
|
|
07f4e2c6a3 | ||
|
|
3e349d71d1 | ||
|
|
bbc2e4d898 | ||
|
|
683d6674d4 | ||
|
|
a8785e654f | ||
|
|
10b665ca33 | ||
|
|
2cc683f39d | ||
|
|
da82e38aa7 | ||
|
|
d9b003b04f | ||
|
|
59bb46fd36 | ||
|
|
48ce6a686e | ||
|
|
fa1553e61a | ||
|
|
00de7e1c4a | ||
|
|
6e07ecf91a | ||
|
|
7840a86e91 | ||
|
|
523cf8a67c | ||
|
|
57544803b4 | ||
|
|
9b35236b85 | ||
|
|
23a5f2985e | ||
|
|
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 |
6
TODO.txt
Normal file
6
TODO.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
- Reimplement mixin.bugfix.entity_pose_stack
|
||||
- Reimplement dynamic_resources for items & Neo standalone models
|
||||
- Investigate if cache_strongholds is still needed in 26.1
|
||||
- Check if BlockStateData patch is still needed in compact_mojang_registries
|
||||
- Check if faster_texture_stitching is still worthwhile with 21.x changes to the stitcher
|
||||
- Sculk deadlock fix looks unnecessary since Mojang is careful about when they send the event
|
||||
|
|
@ -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.140")
|
||||
id("me.modmuss50.mod-publish-plugin") version("1.1.0")
|
||||
}
|
||||
|
||||
|
|
@ -16,22 +16,25 @@ 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"
|
||||
}
|
||||
|
||||
rootProject.properties["parchment_version"]?.let { parchmentVer ->
|
||||
parchment {
|
||||
minecraftVersion = minecraft_version
|
||||
minecraftVersion = rootProject.properties["parchment_mc_version"].toString()
|
||||
mappingsVersion = parchmentVer.toString()
|
||||
}
|
||||
}
|
||||
|
||||
runs {
|
||||
configureEach {
|
||||
systemProperty("modernfix.auditMixinsAtStart", "true")
|
||||
}
|
||||
create("client") {
|
||||
client()
|
||||
}
|
||||
|
|
@ -51,14 +54,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 +63,7 @@ tasks.named<Jar>("jar") {
|
|||
}
|
||||
|
||||
java {
|
||||
val curSourceCompatLevel = JavaVersion.VERSION_17
|
||||
val curSourceCompatLevel = JavaVersion.VERSION_25
|
||||
sourceCompatibility = curSourceCompatLevel
|
||||
targetCompatibility = curSourceCompatLevel
|
||||
}
|
||||
|
|
@ -109,26 +106,18 @@ val embed by configurations.creating {
|
|||
dependencies {
|
||||
implementation(project(":annotations"))
|
||||
embed(project(":annotations"))
|
||||
"additionalRuntimeClasspath"(project(":annotations"))
|
||||
annotationProcessor(project(path = ":annotation-processor", configuration = "shadow"))
|
||||
|
||||
val mixinextrasVersion = rootProject.properties["mixinextras_version"].toString()
|
||||
implementation("io.github.llamalad7:mixinextras-common:${mixinextrasVersion}")
|
||||
annotationProcessor("net.fabricmc:sponge-mixin:0.12.5+mixin.0.8.5")
|
||||
annotationProcessor("io.github.llamalad7:mixinextras-common:${mixinextrasVersion}")
|
||||
implementation("io.github.llamalad7:mixinextras-forge:${mixinextrasVersion}")
|
||||
"jarJar"("io.github.llamalad7:mixinextras-forge:${mixinextrasVersion}")
|
||||
|
||||
val jei_version = rootProject.properties["jei_version"].toString()
|
||||
modCompileOnly("mezz.jei:jei-${minecraft_version}-forge:${jei_version}")
|
||||
modCompileOnly("curse.maven:spark-361579:${rootProject.properties["spark_version"].toString()}")
|
||||
modCompileOnly("curse.maven:ctm-267602:${rootProject.properties["ctm_version"].toString()}")
|
||||
modCompileOnly("curse.maven:ldlib-626676:${rootProject.properties["ldlib_version"].toString()}")
|
||||
modCompileOnly("curse.maven:supermartijncore-454372:4455391")
|
||||
modCompileOnly("curse.maven:patchouli-306770:6164575")
|
||||
modCompileOnly("curse.maven:cofhcore-69162:5374122")
|
||||
modCompileOnly("curse.maven:resourcefullib-570073:5659871")
|
||||
modCompileOnly("curse.maven:kubejs-238086:5853326")
|
||||
val jei_minecraft_version = rootProject.properties["jei_minecraft_version"]?.toString() ?: minecraft_version
|
||||
compileOnly("mezz.jei: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:cofhcore-69162:5374122")
|
||||
compileOnly("curse.maven:resourcefullib-570073:5659871")
|
||||
compileOnly("curse.maven:kubejs-238086:5853326")
|
||||
}
|
||||
|
||||
tasks.named<Jar>("jar") {
|
||||
|
|
@ -145,6 +134,8 @@ tasks.withType<JavaCompile>().configureEach {
|
|||
)
|
||||
)
|
||||
}
|
||||
// Show more errors when porting
|
||||
options.compilerArgs.addAll(listOf("-Xmaxerrs", "1000"))
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
|
@ -160,12 +151,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)
|
||||
|
|
@ -189,7 +180,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,30 @@ 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=26.1.2
|
||||
enabled_platforms=neoforge
|
||||
forge_version=26.1.2.73
|
||||
parchment_version=2025.12.20
|
||||
parchment_mc_version=1.21.11
|
||||
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=27.3.0.12
|
||||
jei_minecraft_version=1.21.11
|
||||
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=26.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:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
package org.embeddedt.modernfix;
|
||||
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.TracingExecutor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.Util;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
|
|
@ -16,7 +17,6 @@ import org.embeddedt.modernfix.util.ClassInfoManager;
|
|||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
// The value here should match an entry in the META-INF/mods.toml file
|
||||
public class ModernFix {
|
||||
|
|
@ -33,17 +33,17 @@ public class ModernFix {
|
|||
// Used to skip computing the blockstate caches twice
|
||||
public static boolean runningFirstInjection = false;
|
||||
|
||||
private static ExecutorService resourceReloadService = null;
|
||||
private static TracingExecutor resourceReloadService = null;
|
||||
|
||||
static {
|
||||
if(ModernFixMixinPlugin.instance.isOptionEnabled("perf.dedicated_reload_executor.ReloadExecutor")) {
|
||||
resourceReloadService = ReloadExecutor.createCustomResourceReloadExecutor();
|
||||
resourceReloadService = new TracingExecutor(ReloadExecutor.createCustomResourceReloadExecutor());
|
||||
} else {
|
||||
resourceReloadService = Util.backgroundExecutor();
|
||||
}
|
||||
}
|
||||
|
||||
public static ExecutorService resourceReloadExecutor() {
|
||||
public static TracingExecutor resourceReloadExecutor() {
|
||||
return resourceReloadService;
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ public class ModernFix {
|
|||
|
||||
public ModernFix() {
|
||||
INSTANCE = this;
|
||||
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.snapshot_easter_egg.NameChange") && !SharedConstants.getCurrentVersion().isStable())
|
||||
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.snapshot_easter_egg.NameChange") && !SharedConstants.getCurrentVersion().stable())
|
||||
NAME = "PreemptiveFix";
|
||||
ModernFixPlatformHooks.INSTANCE.onServerCommandRegister(ModernFixCommands::register);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,12 +1,5 @@
|
|||
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;
|
||||
|
||||
|
||||
/**
|
||||
* Implement this interface in a mod class and add it to "modernfix:integration_v1" in your mod metadata file
|
||||
* to integrate with ModernFix's features.
|
||||
|
|
@ -20,58 +13,4 @@ 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
|
||||
* with dynamic resources on
|
||||
* @param textureGetter function to retrieve textures for this model
|
||||
* @return the model which should actually be loaded for this resource location
|
||||
*/
|
||||
default BakedModel onBakedModelLoad(ResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery, Function<Material, TextureAtlasSprite> textureGetter) {
|
||||
return onBakedModelLoad(location, baseModel, originalModel, state, bakery);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
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 {
|
||||
/**
|
||||
* Allows converting a ModelResourceLocation back into the corresponding BlockState(s). Try to avoid calling this
|
||||
* multiple times if possible.
|
||||
* @param location the location of the model
|
||||
* @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()));
|
||||
if(blockOpt.isPresent())
|
||||
return ModelBakeryHelpers.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), location);
|
||||
else
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows converting a ModelResourceLocation back into the corresponding BlockState(s). Faster version of its
|
||||
* companion function if and only if you know the corresponding Block already for some reason.
|
||||
* @param definition the state definition for the Block
|
||||
* @param location the location of the model
|
||||
* @return a list of all blockstates related to the model
|
||||
*/
|
||||
public static ImmutableList<BlockState> getBlockStateForLocation(StateDefinition<Block, BlockState> definition, ModelResourceLocation location) {
|
||||
return ModelBakeryHelpers.getBlockStatesForMRL(definition, location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compatibility helper for mods to use to get a map-like view of the model bakery.
|
||||
* @param modelGetter the model getter function supplied by the integration class
|
||||
* @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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a ModelBaker for mods to use.
|
||||
* @param bakery the ModelBakery supplied to your integration
|
||||
* @return an appropriate ModelBaker
|
||||
*/
|
||||
public static ModelBaker adaptBakery(ModelBakery bakery) {
|
||||
return new ModelBaker() {
|
||||
@Override
|
||||
public UnbakedModel getModel(ResourceLocation resourceLocation) {
|
||||
return bakery.getModel(resourceLocation);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BakedModel bake(ResourceLocation resourceLocation, ModelState modelState) {
|
||||
return ((IExtendedModelBakery)bakery).bakeDefault(resourceLocation, modelState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BakedModel bake(ResourceLocation location, ModelState state, Function<Material, TextureAtlasSprite> sprites) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<Material, TextureAtlasSprite> getModelTextureGetter() {
|
||||
return Material::sprite;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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,59 +3,16 @@ 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 net.minecraft.server.permissions.Permissions;
|
||||
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))
|
||||
.then(literal("mcfunctions").requires(source -> source.permissions().hasPermission(Permissions.COMMANDS_ADMIN))
|
||||
.executes(context -> {
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
if(level == null) {
|
||||
|
|
|
|||
|
|
@ -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,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,10 @@ 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"));
|
||||
mainThreadExecutor.schedule(() -> {
|
||||
throw exc;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
surrogate.complete(either);
|
||||
|
|
@ -100,64 +80,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$0", 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,30 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.gameevent.GameEvent;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public class EntityMixin {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason When an entity is added to the world via the worldgen load path (ChunkMap#postLoadProtoChunk calling
|
||||
* ServerLevel#addWorldGenChunkEntities), attempts to add a passenger result in a deadlock when the sculk event
|
||||
* 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) {
|
||||
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());
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -40,7 +36,7 @@ public abstract class ReloadableResourceManagerMixin {
|
|||
ModernFix.LOGGER.error("A mod is calling registerReloadListener at the wrong time. This will cause random concurrency crashes when ModernFix is not installed. Please report this to them. If you are a modder, refer to https://github.com/embeddedt/ModernFix/wiki/registerReloadListener-called-on-wrong-thread for more information.", new Exception("registerReloadListener called on wrong thread"));
|
||||
// Defer the call onto the main client thread. There is a decent chance the mod's listener will be
|
||||
// ignored in this case, but it is more predictable than allowing them to randomly crash the game.
|
||||
Minecraft.getInstance().tell(() -> this.registerReloadListener(listener));
|
||||
Minecraft.getInstance().schedule(() -> this.registerReloadListener(listener));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.ender_dragon_leak;
|
||||
|
||||
import net.minecraft.client.renderer.entity.EnderDragonRenderer;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(EnderDragonRenderer.class)
|
||||
@ClientOnlyMixin
|
||||
public abstract class EnderDragonRendererMixin {
|
||||
@Shadow @Final private EnderDragonRenderer.DragonModel model;
|
||||
|
||||
/**
|
||||
* Prevent leaking the client world through the entity reference.
|
||||
*/
|
||||
@Inject(method = "render(Lnet/minecraft/world/entity/boss/enderdragon/EnderDragon;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At("RETURN"))
|
||||
private void clearDragonEntityReference(CallbackInfo ci) {
|
||||
this.model.entity = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.entity_pose_stack;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.renderer.entity.player.AvatarRenderer;
|
||||
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;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(AvatarRenderer.class)
|
||||
@ClientOnlyMixin
|
||||
public class AvatarRendererMixin {
|
||||
@Redirect(method = "submit(Lnet/minecraft/client/renderer/entity/state/AvatarRenderState;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;Lnet/minecraft/client/renderer/state/level/CameraRenderState;)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).mfix$getLastIndex();
|
||||
instance.post(event);
|
||||
if (((RenderPlayerEvent.Pre)event).isCanceled()) {
|
||||
// Pop the stack if someone pushed it in the event
|
||||
while (((PoseStackAccessor)stack).mfix$getLastIndex() > size) {
|
||||
stack.popPose();
|
||||
}
|
||||
}
|
||||
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.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 = "submit(Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;Lnet/minecraft/client/renderer/state/level/CameraRenderState;)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)) {
|
||||
int size = ((PoseStackAccessor)stack).mfix$getLastIndex();
|
||||
instance.post(event);
|
||||
if (((RenderLivingEvent.Pre)event).isCanceled()) {
|
||||
// Pop the stack if someone pushed it in the event
|
||||
while (((PoseStackAccessor)stack).getPoseStack().size() > size) {
|
||||
while (((PoseStackAccessor)stack).mfix$getLastIndex() > size) {
|
||||
stack.popPose();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
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 org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(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) {
|
||||
PoseStack stack = ((RenderPlayerEvent)event).getPoseStack();
|
||||
int size = ((PoseStackAccessor)stack).getPoseStack().size();
|
||||
if (instance.post(event)) {
|
||||
// Pop the stack if someone pushed it in the event
|
||||
while (((PoseStackAccessor)stack).getPoseStack().size() > size) {
|
||||
stack.popPose();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,11 +5,9 @@ import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
|||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.Deque;
|
||||
|
||||
@Mixin(PoseStack.class)
|
||||
@ClientOnlyMixin
|
||||
public interface PoseStackAccessor {
|
||||
@Accessor
|
||||
Deque<PoseStack.Pose> getPoseStack();
|
||||
@Accessor("lastIndex")
|
||||
int mfix$getLastIndex();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package org.embeddedt.modernfix.common.mixin.bugfix.extra_experimental_screen;
|
|||
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen;
|
||||
import net.minecraft.world.level.storage.LevelDataAndDimensions;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import net.minecraft.world.level.storage.WorldData;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
|
@ -16,11 +16,11 @@ public class CreateWorldScreenMixin {
|
|||
* Fix experimental world dialog still being shown the first time you reopen a world that was created
|
||||
* as experimental.
|
||||
*/
|
||||
@ModifyArg(method = "createNewWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/worldselection/WorldOpenFlows;createLevelFromExistingSettings(Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;Lnet/minecraft/server/ReloadableServerResources;Lnet/minecraft/core/LayeredRegistryAccess;Lnet/minecraft/world/level/storage/WorldData;)V"), index = 3)
|
||||
private WorldData setExperimentalFlag(WorldData data) {
|
||||
if(data instanceof PrimaryLevelData pld && data.worldGenSettingsLifecycle() != Lifecycle.stable()) {
|
||||
@ModifyArg(method = "createNewWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/worldselection/WorldOpenFlows;createLevelFromExistingSettings(Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;Lnet/minecraft/server/ReloadableServerResources;Lnet/minecraft/core/LayeredRegistryAccess;Lnet/minecraft/world/level/storage/LevelDataAndDimensions$WorldDataAndGenSettings;Ljava/util/Optional;)V"), index = 3)
|
||||
private LevelDataAndDimensions.WorldDataAndGenSettings setExperimentalFlag(LevelDataAndDimensions.WorldDataAndGenSettings settings) {
|
||||
if(settings.data() instanceof PrimaryLevelData pld && settings.data().worldGenSettingsLifecycle() != Lifecycle.stable()) {
|
||||
pld.withConfirmedWarning(true);
|
||||
}
|
||||
return data;
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,17 +2,16 @@ package org.embeddedt.modernfix.common.mixin.bugfix.missing_block_entities;
|
|||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.PalettedContainerFactory;
|
||||
import net.minecraft.world.level.chunk.UpgradeData;
|
||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
|
|
@ -38,14 +37,14 @@ public abstract class LevelChunkMixin extends ChunkAccess {
|
|||
|
||||
@Shadow @Nullable public abstract BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType);
|
||||
|
||||
public LevelChunkMixin(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry<Biome> biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sections, @Nullable BlendingData blendingData) {
|
||||
super(chunkPos, upgradeData, levelHeightAccessor, biomeRegistry, inhabitedTime, sections, blendingData);
|
||||
public LevelChunkMixin(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, PalettedContainerFactory palettedContainerFactory, long inhabitedTime, LevelChunkSection @org.jspecify.annotations.Nullable [] sections, @org.jspecify.annotations.Nullable BlendingData blendingData) {
|
||||
super(chunkPos, upgradeData, levelHeightAccessor, palettedContainerFactory, inhabitedTime, sections, blendingData);
|
||||
}
|
||||
|
||||
@Inject(method = "replaceWithPacketData", at = @At("RETURN"))
|
||||
private void validateBlockEntitiesInChunk(CallbackInfo ci) {
|
||||
// No reason to check in singleplayer or on the integrated server
|
||||
if (this.level.isClientSide && !Minecraft.getInstance().isLocalServer()) {
|
||||
if (this.level.isClientSide() && !Minecraft.getInstance().isLocalServer()) {
|
||||
for (int i = 0; i < this.sections.length; i++) {
|
||||
var section = this.sections[i];
|
||||
try {
|
||||
|
|
@ -62,8 +61,8 @@ public abstract class LevelChunkMixin extends ChunkAccess {
|
|||
|
||||
@Unique
|
||||
private void scanSectionForBlockEntities(LevelChunkSection section, int i) {
|
||||
int chunkXOff = this.chunkPos.x * 16;
|
||||
int chunkZOff = this.chunkPos.z * 16;
|
||||
int chunkXOff = this.chunkPos.x() * 16;
|
||||
int chunkZOff = this.chunkPos.z() * 16;
|
||||
BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos();
|
||||
int sectionYOff = this.getSectionYFromSectionIndex(i) * 16;
|
||||
for (int y = 0; y < 16; y++) {
|
||||
|
|
|
|||
|
|
@ -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;ZZ)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,22 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.core;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
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(Bootstrap.class)
|
||||
@ClientOnlyMixin
|
||||
public class BootstrapClientMixin {
|
||||
/**
|
||||
* Hack to workaround RenderStateShard deadlock (by loading it early on a single thread). We use validate
|
||||
* here to ensure Forge registries are initialized.
|
||||
*/
|
||||
@Inject(method = "validate", at = @At("HEAD"))
|
||||
private static void loadClientClasses(CallbackInfo ci) {
|
||||
RenderType.solid();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
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;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
|
|
@ -25,14 +23,10 @@ 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();
|
||||
|
||||
if (Boolean.getBoolean("modernfix.auditMixinsAtStart")) {
|
||||
MixinEnvironment.getCurrentEnvironment().audit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package org.embeddedt.modernfix.common.mixin.core;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.util.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;
|
||||
|
|
@ -36,7 +36,7 @@ public class MinecraftServerMixin implements ITimeTrackingServer {
|
|||
return mfix$lastTickStartTime;
|
||||
}
|
||||
|
||||
@Inject(method = "runServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;tickServer(Ljava/util/function/BooleanSupplier;)V"))
|
||||
@Inject(method = "runServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;processPacketsAndTick(Z)V"))
|
||||
private void trackTickTime(CallbackInfo ci) {
|
||||
mfix$lastTickStartTime = Util.getMillis();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<>());
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
package org.embeddedt.modernfix.common.mixin.feature.cause_lag_by_disabling_threads;
|
||||
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.TracingExecutor;
|
||||
import net.minecraft.util.Util;
|
||||
import org.embeddedt.modernfix.util.SingleThreadedWorkerService;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@Mixin(Util.class)
|
||||
public class UtilMixin {
|
||||
@Shadow @Final @Mutable
|
||||
private static final ExecutorService BACKGROUND_EXECUTOR = new SingleThreadedWorkerService();
|
||||
private static final TracingExecutor BACKGROUND_EXECUTOR = new TracingExecutor(new SingleThreadedWorkerService());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ 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.resources.ResourceLocation;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.functions.CommandFunction;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.ServerFunctionManager;
|
||||
import org.embeddedt.modernfix.duck.IProfilingServerFunctionManager;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
|
|
@ -24,27 +25,27 @@ import java.util.Map;
|
|||
|
||||
@Mixin(ServerFunctionManager.class)
|
||||
public class ServerFunctionManagerMixin implements IProfilingServerFunctionManager {
|
||||
@Shadow @Final private static ResourceLocation TICK_FUNCTION_TAG;
|
||||
@Shadow @Final private static Identifier TICK_FUNCTION_TAG;
|
||||
|
||||
private final Map<ResourceLocation, Stopwatch> mfix$functionWatches = new Object2ObjectOpenHashMap<>();
|
||||
private final Map<Identifier, 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, Identifier 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, Identifier 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, Identifier identifier, CallbackInfo ci, @Share("stopwatch") LocalRef<Stopwatch> watchRef) {
|
||||
var watch = watchRef.get();
|
||||
if (watch != null && watch.isRunning()) {
|
||||
watch.stop();
|
||||
|
|
@ -52,14 +53,14 @@ 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, Identifier identifier, CallbackInfo ci) {
|
||||
mfix$functionWatches.values().removeIf(watch -> watch.elapsed().isZero());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mfix$getProfilingResults() {
|
||||
var list = new ArrayList<>(mfix$functionWatches.entrySet());
|
||||
list.sort(Comparator.<Map.Entry<ResourceLocation, Stopwatch>, Duration>comparing(e -> e.getValue().elapsed()).reversed());
|
||||
list.sort(Comparator.<Map.Entry<Identifier, Stopwatch>, Duration>comparing(e -> e.getValue().elapsed()).reversed());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (var entry : list) {
|
||||
sb.append(entry.getKey().toString());
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.feature.measure_time;
|
||||
|
||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
||||
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")
|
||||
public abstract class AddReloadListenerEventWrapperMixin implements PreparableReloadListener {
|
||||
@Shadow @Final private PreparableReloadListener wrapped;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason make a proper name show up in ProfiledReloadInstance
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.wrapped.getClass().getName();
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ public class ProfiledReloadInstanceMixin {
|
|||
@ModifyVariable(method = "finish", ordinal = 0, argsOnly = true, at = @At("HEAD"))
|
||||
private List<ProfiledReloadInstance.State> sortStates(List<ProfiledReloadInstance.State> datapoints) {
|
||||
datapoints = new ArrayList<>(datapoints);
|
||||
datapoints.sort(Comparator.<ProfiledReloadInstance.State>comparingLong(s -> s.preparationNanos.get() + s.reloadNanos.get()).reversed());
|
||||
datapoints.sort(Comparator.<ProfiledReloadInstance.State>comparingLong(s -> s.preparationNanos().get() + s.reloadNanos().get()).reversed());
|
||||
return datapoints;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package org.embeddedt.modernfix.common.mixin.feature.registry_event_progress;
|
||||
|
||||
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.ReferenceLinkedOpenHashSet;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.neoforged.fml.loading.progress.ProgressMeter;
|
||||
import net.neoforged.fml.loading.progress.StartupNotificationManager;
|
||||
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.Set;
|
||||
|
||||
@Mixin(targets = {"net/neoforged/neoforge/registries/NeoForgeRegistryCallbacks$BlockCallbacks"})
|
||||
public class BlockCallbacksMixin {
|
||||
@Shadow @Final @Mutable
|
||||
private Set<Block> addedBlocks;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Use an ordered set to make the baking order more predictable for users watching the splash screen
|
||||
*/
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void useOrderedSet(CallbackInfo ci) {
|
||||
this.addedBlocks = new ReferenceLinkedOpenHashSet<>(this.addedBlocks);
|
||||
}
|
||||
|
||||
@Inject(method = "onBake", at = @At("HEAD"))
|
||||
private void startBakeProgress(CallbackInfo ci, @Share("meter") LocalRef<ProgressMeter> meter) {
|
||||
meter.set(StartupNotificationManager.prependProgressBar("Build blockstate caches", addedBlocks.size()));
|
||||
}
|
||||
|
||||
@Inject(method = "onBake", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/Block;getStateDefinition()Lnet/minecraft/world/level/block/state/StateDefinition;", ordinal = 0))
|
||||
private void showBakeProgressPerBlock(CallbackInfo ci, @Local(ordinal = 0) Block block, @Share("meter") LocalRef<ProgressMeter> meter) {
|
||||
var id = block.builtInRegistryHolder().getKey().identifier();
|
||||
meter.get().label("Build blockstate caches - " + id.toString());
|
||||
meter.get().increment();
|
||||
}
|
||||
|
||||
@Inject(method = "onBake", at = @At(value = "INVOKE", target = "Ljava/util/Set;clear()V", ordinal = 0))
|
||||
private void stopBakeProgress(CallbackInfo ci, @Share("meter") LocalRef<ProgressMeter> meter) {
|
||||
meter.get().complete();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
@Inject(method = "postRegisterEvents", at = @At(value = "INVOKE", target = "Ljava/lang/RuntimeException;getSuppressed()[Ljava/lang/Throwable;", ordinal = 0))
|
||||
private static void closeAsyncScreen(CallbackInfo ci) {
|
||||
mfix$asyncScreen.close();
|
||||
mfix$asyncScreen = null;
|
||||
}
|
||||
|
||||
@Redirect(method = "postRegisterEvents", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/fml/ModLoader;postEventWrapContainerInModOrder(Lnet/minecraftforge/eventbus/api/Event;)V"))
|
||||
private static <T extends Event & IModBusEvent> void swapThreadAndPost(ModLoader loader, T event) {
|
||||
RegisterEvent registryEvent = (RegisterEvent)event;
|
||||
var pb = StartupMessageManager.addProgressBar(registryEvent.getRegistryKey().location().toString(), ModList.get().size());
|
||||
try {
|
||||
loader.postEventWithWrapInModOrder(event, (mc, e) -> {
|
||||
ModLoadingContext.get().setActiveContainer(mc);
|
||||
pb.label(pb.name() + " - " + mc.getModInfo().getDisplayName());
|
||||
pb.increment();
|
||||
}, (mc, e) -> {
|
||||
ModLoadingContext.get().setActiveContainer(null);
|
||||
});
|
||||
} finally {
|
||||
pb.complete();
|
||||
@Redirect(method = "postRegisterEvents", at = @At(value = "INVOKE", target = "Lnet/neoforged/fml/ModLoader;postEventWrapContainerInModOrder(Lnet/neoforged/bus/api/Event;)V"))
|
||||
private static <T extends Event & IModBusEvent> void postWithProgressBar(T event) {
|
||||
if(ModLoader.hasErrors()) {
|
||||
return;
|
||||
}
|
||||
RegisterEvent registryEvent = (RegisterEvent)event;
|
||||
// We control phases ourselves so we can make a separate progress bar for each phase.
|
||||
String registryName = registryEvent.getRegistryKey().identifier().toString();
|
||||
for(EventPriority phase : EventPriority.values()) {
|
||||
// FIXME need to use prepend rather than append for it to be visible for now
|
||||
var pb = StartupNotificationManager.prependProgressBar(registryName, ModList.get().size());
|
||||
try {
|
||||
ModList.get().forEachModInOrder(mc -> {
|
||||
ModLoadingContext.get().setActiveContainer(mc);
|
||||
pb.label(pb.name() + " - " + mc.getModInfo().getDisplayName());
|
||||
pb.increment();
|
||||
mc.acceptEvent(phase, event);
|
||||
ModLoadingContext.get().setActiveContainer(null);
|
||||
});
|
||||
} finally {
|
||||
pb.complete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
|
|||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.resources.RegistryOps;
|
||||
import net.minecraft.util.Util;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||
|
|
@ -77,7 +77,7 @@ public class ChunkGeneratorMixin implements IChunkGenerator {
|
|||
|
||||
private String mfix$makeCacheKey(ConcentricRingsStructurePlacement placement) {
|
||||
RegistryOps<Tag> ops = RegistryOps.create(NbtOps.INSTANCE, this.mfix$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);
|
||||
|
|
@ -126,11 +126,10 @@ 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)) {
|
||||
int[] data = root.getIntArray(key);
|
||||
for (String key : root.keySet()) {
|
||||
root.getIntArray(key).ifPresent(data -> {
|
||||
if (data.length >= 2 && data.length % 2 == 0) {
|
||||
List<ChunkPos> positions = new ArrayList<>(data.length / 2);
|
||||
for (int i = 0; i < data.length; i += 2) {
|
||||
|
|
@ -138,7 +137,7 @@ public class ChunkGeneratorMixin implements IChunkGenerator {
|
|||
}
|
||||
result.put(key, positions);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
|
|
@ -154,14 +153,14 @@ public class ChunkGeneratorMixin implements IChunkGenerator {
|
|||
int[] data = new int[positions.size() * 2];
|
||||
for (int i = 0; i < positions.size(); i++) {
|
||||
ChunkPos pos = positions.get(i);
|
||||
data[i * 2] = pos.x;
|
||||
data[i * 2 + 1] = pos.z;
|
||||
data[i * 2] = pos.x();
|
||||
data[i * 2 + 1] = pos.z();
|
||||
}
|
||||
root.putIntArray(entry.getKey(), data);
|
||||
}
|
||||
Path file = mfix$dimensionPath.resolve(CACHE_FILENAME);
|
||||
try {
|
||||
NbtIo.writeCompressed(root, file.toFile());
|
||||
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,6 +1,7 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.compact_bit_storage;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.level.chunk.PaletteResize;
|
||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
|
@ -40,7 +41,7 @@ public abstract class PalettedContainerMixin<T> {
|
|||
return;
|
||||
}
|
||||
this.data = this.createOrReuseData(null, 0);
|
||||
this.data.palette().idFor(value);
|
||||
this.data.palette().idFor(value, (PaletteResize<T>)this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ public class BlockStateDataMixin {
|
|||
* @author embeddedt
|
||||
* @reason Reduce memory use of these constant CompoundTags via aggressive interning.
|
||||
*/
|
||||
/*
|
||||
@ModifyExpressionValue(method = "parse", at = @At(value = "INVOKE", target = "Lnet/minecraft/nbt/TagParser;parseTag(Ljava/lang/String;)Lnet/minecraft/nbt/CompoundTag;"))
|
||||
private static CompoundTag compactTag(CompoundTag tag) {
|
||||
if (TAG_INTERNER == null) {
|
||||
|
|
@ -40,6 +41,8 @@ public class BlockStateDataMixin {
|
|||
return new CompoundTag(Map.ofEntries(entries));
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@Inject(method = "<clinit>", at = @At("RETURN"))
|
||||
private static void clearInterner(CallbackInfo ci) {
|
||||
if (TAG_INTERNER != null) {
|
||||
|
|
|
|||
|
|
@ -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<>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public class CreateWorldScreenMixin {
|
|||
return ModernFix.resourceReloadExecutor();
|
||||
}
|
||||
|
||||
@ModifyArg(method = "openFresh", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/WorldLoader;load(Lnet/minecraft/server/WorldLoader$InitConfig;Lnet/minecraft/server/WorldLoader$WorldDataSupplier;Lnet/minecraft/server/WorldLoader$ResultFactory;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 3)
|
||||
@ModifyArg(method = "openCreateWorldScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/WorldLoader;load(Lnet/minecraft/server/WorldLoader$InitConfig;Lnet/minecraft/server/WorldLoader$WorldDataSupplier;Lnet/minecraft/server/WorldLoader$ResultFactory;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 3)
|
||||
private static Executor getCreationExecutorService(Executor e) {
|
||||
return ModernFix.resourceReloadExecutor();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dedicated_reload_executor;
|
||||
|
||||
import net.minecraft.TracingExecutor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
|
|
@ -7,13 +8,11 @@ import org.spongepowered.asm.mixin.Mixin;
|
|||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
@ClientOnlyMixin
|
||||
public class MinecraftMixin {
|
||||
@Redirect(method = { "<init>", "reloadResourcePacks(Z)Ljava/util/concurrent/CompletableFuture;" }, at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;backgroundExecutor()Ljava/util/concurrent/ExecutorService;", ordinal = 0))
|
||||
private ExecutorService getResourceReloadExecutor() {
|
||||
@Redirect(method = { "<init>", "reloadResourcePacks(ZLnet/minecraft/client/Minecraft$GameLoadCookie;)Ljava/util/concurrent/CompletableFuture;" }, at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Util;backgroundExecutor()Lnet/minecraft/TracingExecutor;", ordinal = 0))
|
||||
private TracingExecutor 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;Ljava/util/List;Lnet/minecraft/world/flag/FeatureFlagSet;Lnet/minecraft/commands/Commands$CommandSelection;Lnet/minecraft/server/permissions/PermissionSet;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 6)
|
||||
private Executor getReloadExecutor(Executor asyncExecutor) {
|
||||
return ModernFix.resourceReloadExecutor();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +1,54 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.deduplicate_wall_shapes;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.block.WallBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.Property;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import org.joml.Vector3d;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Most wall blocks use the default set of vanilla properties, and the default sizes for their shapes. This means
|
||||
* there is no need to reconstruct a separate VoxelShape instance for each wall, we can just repurpose the
|
||||
* same shape instances. To do this we can cache a mapping between a state (represented only as its prop->value map)
|
||||
* and the desired shape, and generate the BlockState->VoxelShape map from this for each block.
|
||||
* @author embeddedt
|
||||
* @reason Avoid excessive memory usage in modpacks that add many variations of wall blocks. The strategy here requires
|
||||
* multiple memoization maps, but is also the simplest way to do it without having to literally replicate the vanilla
|
||||
* logic (as that uses blockstates rather than property maps).
|
||||
*/
|
||||
@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<>();
|
||||
public class WallBlockMixin {
|
||||
@Unique
|
||||
private static final ConcurrentHashMap<Vector3d, VoxelShape> COLUMN_CACHE = new ConcurrentHashMap<>();
|
||||
@Unique
|
||||
private static final ConcurrentHashMap<List<Double>, VoxelShape> BOX_Z_CACHE = new ConcurrentHashMap<>();
|
||||
@Unique
|
||||
private static final ConcurrentHashMap<VoxelShape, Map<Direction, VoxelShape>> ROTATE_HORZ_CACHE = new ConcurrentHashMap<>();
|
||||
@Unique
|
||||
private static final ConcurrentHashMap<Pair<VoxelShape, VoxelShape>, VoxelShape> OR_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
public WallBlockMixin(Properties properties) {
|
||||
super(properties);
|
||||
@WrapOperation(method = "makeShapes", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/Block;column(DDD)Lnet/minecraft/world/phys/shapes/VoxelShape;"))
|
||||
private VoxelShape memoizeColumn(double sizeXZ, double minY, double postHeight, Operation<VoxelShape> original) {
|
||||
return COLUMN_CACHE.computeIfAbsent(new Vector3d(sizeXZ, minY, postHeight), l -> original.call(sizeXZ, minY, postHeight));
|
||||
}
|
||||
|
||||
@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);
|
||||
// require the properties to be identical
|
||||
if(cache == null || !cache.getSecond().getProperties().equals(this.stateDefinition.getProperties()))
|
||||
return;
|
||||
ImmutableMap.Builder<BlockState, VoxelShape> builder = ImmutableMap.builder();
|
||||
for(BlockState state : this.stateDefinition.getPossibleStates()) {
|
||||
VoxelShape shape = cache.getFirst().get(state.getValues());
|
||||
if(shape == null)
|
||||
return; // fallback to vanilla logic
|
||||
builder.put(state, shape);
|
||||
}
|
||||
cir.setReturnValue(builder.build());
|
||||
@WrapOperation(method = "makeShapes", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/Block;boxZ(DDDDD)Lnet/minecraft/world/phys/shapes/VoxelShape;"))
|
||||
private VoxelShape memoizeBoxZ(double sizeX, double minY, double maxY, double minZ, double maxZ, Operation<VoxelShape> original) {
|
||||
return BOX_Z_CACHE.computeIfAbsent(List.of(sizeX, minY, maxY, minZ, maxZ), l -> original.call(sizeX, minY, maxY, minZ, maxZ));
|
||||
}
|
||||
|
||||
@Inject(method = "makeShapes", at = @At("RETURN"))
|
||||
private synchronized void storeCachedShapesByProperty(float f1, float f2, float f3, float f4, float f5, float f6, CallbackInfoReturnable<Map<BlockState, VoxelShape>> cir) {
|
||||
// never populate cache as a non-vanilla block
|
||||
if((Class<?>)this.getClass() != WallBlock.class)
|
||||
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<BlockState, VoxelShape> shapeMap = cir.getReturnValue();
|
||||
for(Map.Entry<BlockState, VoxelShape> entry : shapeMap.entrySet()) {
|
||||
cacheByProperties.put(entry.getKey().getValues(), entry.getValue());
|
||||
}
|
||||
CACHE_BY_SHAPE_VALS.put(key, Pair.of(cacheByProperties, this.stateDefinition));
|
||||
}
|
||||
@WrapOperation(method = "makeShapes", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/shapes/Shapes;rotateHorizontal(Lnet/minecraft/world/phys/shapes/VoxelShape;)Ljava/util/Map;"))
|
||||
private Map<Direction, VoxelShape> memoizeRotateHorizontal(VoxelShape north, Operation<Map<Direction, VoxelShape>> original) {
|
||||
return ROTATE_HORZ_CACHE.computeIfAbsent(north, l -> original.call(north));
|
||||
}
|
||||
|
||||
@WrapOperation(method = "lambda$makeShapes$0", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/shapes/Shapes;or(Lnet/minecraft/world/phys/shapes/VoxelShape;Lnet/minecraft/world/phys/shapes/VoxelShape;)Lnet/minecraft/world/phys/shapes/VoxelShape;"))
|
||||
private static VoxelShape memoizeOr(VoxelShape one, VoxelShape two, Operation<VoxelShape> original) {
|
||||
return OR_CACHE.computeIfAbsent(Pair.of(one, two), l -> original.call(one, two));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.ModifyExpressionValue;
|
||||
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 {
|
||||
@ModifyExpressionValue(method = "<clinit>", at = @At(value = "INVOKE", target = "Lcom/mojang/datafixers/DataFixerBuilder;build()Lcom/mojang/datafixers/DataFixerBuilder$Result;"))
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -28,9 +28,11 @@ public class ClientLanguageMixin {
|
|||
* @reason collect the list of all known language resources
|
||||
*/
|
||||
@WrapOperation(method = "loadFrom", at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/resources/language/ClientLanguage;appendFrom(Ljava/lang/String;Ljava/util/List;Ljava/util/Map;)V"))
|
||||
target = "Lnet/minecraft/client/resources/language/ClientLanguage;appendFrom(Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;)V"))
|
||||
private static void collectResources(String languageName, List<Resource> resources,
|
||||
Map<String, String> destinationMap, Operation<Void> original,
|
||||
Map<String, String> destinationMap,
|
||||
Map<String, net.minecraft.network.chat.Component> componentMap,
|
||||
Operation<Void> original,
|
||||
@Share("usedResources") LocalRef<List<Resource>> usedResources) {
|
||||
List<Resource> collected = usedResources.get();
|
||||
if (collected == null) {
|
||||
|
|
@ -38,14 +40,14 @@ public class ClientLanguageMixin {
|
|||
usedResources.set(collected);
|
||||
}
|
||||
collected.addAll(resources);
|
||||
original.call(languageName, resources, destinationMap);
|
||||
original.call(languageName, resources, destinationMap, componentMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason figure out which keys are dynamically loaded and which are injected by mixins
|
||||
*/
|
||||
@ModifyArg(method = "loadFrom", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/language/ClientLanguage;<init>(Ljava/util/Map;Z)V"), index = 0)
|
||||
@ModifyArg(method = "loadFrom", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/language/ClientLanguage;<init>(Ljava/util/Map;ZLjava/util/Map;)V"), index = 0)
|
||||
private static Map<String, String> modifyLanguageMap(Map<String, String> storage, @Share("usedResources") LocalRef<List<Resource>> usedResources) {
|
||||
List<Resource> collected = Objects.requireNonNullElse(usedResources.get(), List.of());
|
||||
return DynamicLanguageMap.forVanillaData(storage, collected);
|
||||
|
|
|
|||
|
|
@ -1,74 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import net.minecraft.client.renderer.block.BlockModelShaper;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.duck.IModelHoldingBlockState;
|
||||
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
|
||||
import org.embeddedt.modernfix.util.DynamicOverridableMap;
|
||||
import org.spongepowered.asm.mixin.*;
|
||||
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(BlockModelShaper.class)
|
||||
@ClientOnlyMixin
|
||||
public class BlockModelShaperMixin {
|
||||
@Shadow @Final private ModelManager modelManager;
|
||||
|
||||
@Shadow
|
||||
private Map<BlockState, BakedModel> modelByStateCache;
|
||||
|
||||
@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)));
|
||||
// Clear the cached models on blockstate objects
|
||||
for(Block block : BuiltInRegistries.BLOCK) {
|
||||
for(BlockState state : block.getStateDefinition().getPossibleStates()) {
|
||||
if(state instanceof IModelHoldingBlockState modelHolder) {
|
||||
modelHolder.mfix$setModel(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BakedModel cacheBlockModel(BlockState state) {
|
||||
// Do all model system accesses in the unlocked path
|
||||
ModelResourceLocation mrl = ModelLocationCache.get(state);
|
||||
BakedModel model = mrl == null ? null : modelManager.getModel(mrl);
|
||||
if (model == null) {
|
||||
model = modelManager.getMissingModel();
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason get the model from the dynamic model provider
|
||||
*/
|
||||
@Overwrite
|
||||
public BakedModel getBlockModel(BlockState state) {
|
||||
if(state instanceof IModelHoldingBlockState modelHolder) {
|
||||
BakedModel model = modelHolder.mfix$getModel();
|
||||
|
||||
if(model != null) {
|
||||
return model;
|
||||
}
|
||||
|
||||
model = this.cacheBlockModel(state);
|
||||
modelHolder.mfix$setModel(model);
|
||||
return model;
|
||||
} else {
|
||||
return this.cacheBlockModel(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import net.minecraft.client.resources.model.BlockStateDefinitions;
|
||||
import net.minecraft.resources.Identifier;
|
||||
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.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(BlockStateDefinitions.class)
|
||||
@ClientOnlyMixin
|
||||
public interface BlockStateDefinitionsAccessor {
|
||||
@Accessor("STATIC_DEFINITIONS")
|
||||
static Map<Identifier, StateDefinition<Block, BlockState>> getStaticDefinitions() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
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 org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.dynamicresources.DynamicBakedModelProvider;
|
||||
import org.embeddedt.modernfix.forge.dynresources.ModelBakeEventHelper;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Mixin(ForgeHooksClient.class)
|
||||
public class ForgeHooksClientMixin {
|
||||
/**
|
||||
* Generate a more realistic keySet that contains every item and block model location, to help with mod compat.
|
||||
*/
|
||||
@Redirect(method = "onModifyBakingResult", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/fml/ModLoader;postEvent(Lnet/minecraftforge/eventbus/api/Event;)V"), remap = false)
|
||||
private static void postNamespacedKeySetEvent(ModLoader loader, Event event) {
|
||||
if(!ModLoader.isLoadingStateValid())
|
||||
return;
|
||||
ModelEvent.ModifyBakingResult bakeEvent = ((ModelEvent.ModifyBakingResult)event);
|
||||
Stopwatch globalTimer = Stopwatch.createStarted();
|
||||
Stopwatch selfTimer = Stopwatch.createStarted();
|
||||
ModelBakeEventHelper helper = new ModelBakeEventHelper(bakeEvent.getModels());
|
||||
selfTimer.stop();
|
||||
Method acceptEv = ObfuscationReflectionHelper.findMethod(ModContainer.class, "acceptEvent", Event.class);
|
||||
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());
|
||||
Stopwatch timer = times.computeIfAbsent(id, $ -> Stopwatch.createUnstarted());
|
||||
timer.start();
|
||||
try {
|
||||
acceptEv.invoke(mc, postedEvent);
|
||||
} catch(ReflectiveOperationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
timer.stop();
|
||||
});
|
||||
globalTimer.stop();
|
||||
if (globalTimer.elapsed(TimeUnit.SECONDS) >= 1) {
|
||||
ModernFix.LOGGER.warn("Posting dynamic ModelEvent.ModifyBakingResult to mods took {}, breakdown below:", globalTimer);
|
||||
times.entrySet().stream()
|
||||
.sorted(Comparator.<Map.Entry<String, Stopwatch>, Duration>comparing(e -> e.getValue().elapsed()).reversed())
|
||||
.filter(e -> e.getValue().elapsed(TimeUnit.MILLISECONDS) > 50)
|
||||
.forEach(entry -> {
|
||||
ModernFix.LOGGER.warn(" {}: {}", entry.getKey(), entry.getValue().toString());
|
||||
});
|
||||
}
|
||||
if (bakeEvent.getModels() instanceof DynamicBakedModelProvider dynamicProvider) {
|
||||
dynamicProvider.dumpStats();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import net.minecraft.core.IdMapper;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(IdMapper.class)
|
||||
public interface IdMapperAccessor<T> {
|
||||
@Accessor("tToId")
|
||||
Reference2IntMap<T> getReferenceMap();
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
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 org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.dynamicresources.DynamicModelCache;
|
||||
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
|
||||
import org.embeddedt.modernfix.util.ItemMesherMap;
|
||||
import org.spongepowered.asm.mixin.*;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ForgeItemModelShaper.class)
|
||||
@ClientOnlyMixin
|
||||
public abstract class ItemModelMesherForgeMixin extends ItemModelShaper {
|
||||
@Shadow(remap = false) @Final @Mutable private Map<Holder.Reference<Item>, ModelResourceLocation> locations;
|
||||
|
||||
private Map<Holder.Reference<Item>, ModelResourceLocation> overrideLocations;
|
||||
|
||||
private final DynamicModelCache<Holder.Reference<Item>> mfix$modelCache = new DynamicModelCache<>(k -> this.mfix$getModelSlow((Holder.Reference<Item>)k), true);
|
||||
|
||||
public ItemModelMesherForgeMixin(ModelManager arg) {
|
||||
super(arg);
|
||||
}
|
||||
|
||||
private static final ModelResourceLocation SENTINEL = new ModelResourceLocation(new ResourceLocation("modernfix", "sentinel"), "sentinel");
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void replaceLocationMap(CallbackInfo ci) {
|
||||
overrideLocations = new HashMap<>();
|
||||
// need to replace this map because mods query locations through it
|
||||
locations = new ItemMesherMap<>(this::mfix$getLocationForge);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private ModelResourceLocation mfix$getLocationForge(Holder.Reference<Item> item) {
|
||||
ModelResourceLocation map = overrideLocations.getOrDefault(item, SENTINEL);
|
||||
if(map == SENTINEL) {
|
||||
/* generate the appropriate location from our cache */
|
||||
map = ModelLocationCache.get(item.get());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private BakedModel mfix$getModelSlow(Holder.Reference<Item> key) {
|
||||
ModelResourceLocation map = mfix$getLocationForge(key);
|
||||
return map == null ? null : getModelManager().getModel(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Get the stored location for that item and meta, and get the model
|
||||
* from that location from the model manager.
|
||||
**/
|
||||
@Overwrite
|
||||
@Override
|
||||
public BakedModel getItemModel(Item item) {
|
||||
return this.mfix$modelCache.get(ForgeRegistries.ITEMS.getDelegateOrThrow(item));
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Don't get all models during init (with dynamic loading, that would
|
||||
* generate them all). Just store location instead.
|
||||
**/
|
||||
@Overwrite
|
||||
@Override
|
||||
public void register(Item item, ModelResourceLocation location) {
|
||||
overrideLocations.put(ForgeRegistries.ITEMS.getDelegateOrThrow(item), location);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Disable cache rebuilding (with dynamic loading, that would generate
|
||||
* all models).
|
||||
**/
|
||||
@Overwrite
|
||||
@Override
|
||||
public void rebuildCache() {
|
||||
this.mfix$modelCache.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import net.minecraft.client.renderer.ItemModelShaper;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.dynamicresources.DynamicModelCache;
|
||||
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
|
||||
import org.embeddedt.modernfix.util.DynamicInt2ObjectMap;
|
||||
import org.spongepowered.asm.mixin.*;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ItemModelShaper.class)
|
||||
@ClientOnlyMixin
|
||||
public abstract class ItemModelShaperMixin {
|
||||
|
||||
@Shadow public abstract ModelManager getModelManager();
|
||||
|
||||
@Shadow @Final @Mutable private Int2ObjectMap<BakedModel> shapesCache;
|
||||
|
||||
private Map<Item, ModelResourceLocation> overrideLocationsVanilla;
|
||||
|
||||
public ItemModelShaperMixin() {
|
||||
super();
|
||||
}
|
||||
|
||||
private static final ModelResourceLocation SENTINEL_VANILLA = new ModelResourceLocation(new ResourceLocation("modernfix", "sentinel"), "sentinel");
|
||||
|
||||
private final DynamicModelCache<Item> mfix$itemModelCache = new DynamicModelCache<>(k -> this.mfix$getModelForItem((Item)k), true);
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void replaceLocationMap(CallbackInfo ci) {
|
||||
overrideLocationsVanilla = new HashMap<>();
|
||||
this.shapesCache = new DynamicInt2ObjectMap<>(index -> getModelManager().getModel(ModelLocationCache.get(Item.byId(index))));
|
||||
}
|
||||
|
||||
@Unique
|
||||
private ModelResourceLocation mfix$getLocation(Item item) {
|
||||
ModelResourceLocation map = overrideLocationsVanilla.getOrDefault(item, SENTINEL_VANILLA);
|
||||
if(map == SENTINEL_VANILLA) {
|
||||
/* generate the appropriate location from our cache */
|
||||
map = ModelLocationCache.get(item);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
private BakedModel mfix$getModelForItem(Item item) {
|
||||
ModelResourceLocation map = mfix$getLocation(item);
|
||||
return map == null ? null : getModelManager().getModel(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Get the stored location for that item and meta, and get the model
|
||||
* from that location from the model manager.
|
||||
**/
|
||||
@Overwrite
|
||||
public BakedModel getItemModel(Item item) {
|
||||
return this.mfix$itemModelCache.get(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Don't get all models during init (with dynamic loading, that would
|
||||
* generate them all). Just store location instead.
|
||||
**/
|
||||
@Overwrite
|
||||
public void register(Item item, ModelResourceLocation location) {
|
||||
overrideLocationsVanilla.put(item, location);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Disable cache rebuilding (with dynamic loading, that would generate
|
||||
* all models).
|
||||
**/
|
||||
@Overwrite
|
||||
public void rebuildCache() {
|
||||
this.mfix$itemModelCache.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import net.minecraft.client.renderer.ItemModelShaper;
|
||||
import net.minecraft.client.renderer.entity.ItemRenderer;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(ItemRenderer.class)
|
||||
@ClientOnlyMixin
|
||||
public class ItemRendererMixin {
|
||||
/**
|
||||
* Don't waste space putting all these locations into the cache, compute them on demand later.
|
||||
*/
|
||||
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemModelShaper;register(Lnet/minecraft/world/item/Item;Lnet/minecraft/client/resources/model/ModelResourceLocation;)V"))
|
||||
private void skipDefaultRegistration(ItemModelShaper shaper, Item item, ModelResourceLocation mrl) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.renderer.block.dispatch.BlockStateModel;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.duck.IModelHoldingBlockState;
|
||||
|
|
@ -10,17 +10,17 @@ import java.lang.ref.SoftReference;
|
|||
|
||||
@Mixin(BlockBehaviour.BlockStateBase.class)
|
||||
@ClientOnlyMixin
|
||||
public class BlockStateBaseMixin implements IModelHoldingBlockState {
|
||||
private volatile SoftReference<BakedModel> mfix$model;
|
||||
public class MixinBlockState implements IModelHoldingBlockState {
|
||||
private volatile SoftReference<BlockStateModel> mfix$model;
|
||||
|
||||
@Override
|
||||
public BakedModel mfix$getModel() {
|
||||
public BlockStateModel mfix$getModel() {
|
||||
var ref = mfix$model;
|
||||
return ref != null ? ref.get() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mfix$setModel(BakedModel model) {
|
||||
public void mfix$setModel(BlockStateModel model) {
|
||||
mfix$model = model != null ? new SoftReference<>(model) : null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.resources.model.BlockStateModelLoader;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
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.dynresources.DynamicModelSystem;
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(BlockStateModelLoader.class)
|
||||
@ClientOnlyMixin
|
||||
public abstract class MixinBlockStateModelLoader {
|
||||
@Shadow
|
||||
protected static BlockStateModelLoader.LoadedModels lambda$loadBlockStates$2(Map.Entry<Identifier, List<Resource>> entry, Function<Identifier, StateDefinition<Block, BlockState>> locationToBlockStateMapper) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Load blockstate model definitions dynamically instead of all at once
|
||||
*/
|
||||
@ModifyArg(method = "loadBlockStates", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenCompose(Ljava/util/function/Function;)Ljava/util/concurrent/CompletableFuture;"))
|
||||
private static Function<Map<Identifier, List<Resource>>, ? extends CompletionStage<BlockStateModelLoader.LoadedModels>> skipAOTBlockStateLoad(Function<Map<Identifier, List<Resource>>, ? extends CompletionStage<Map<Identifier, BlockStateModelLoader.LoadedModels>>> original, @Local(ordinal = 0) Function<Identifier, StateDefinition<Block, BlockState>> mapper) {
|
||||
return resourceMap -> CompletableFuture.completedFuture(DynamicModelSystem.createDynamicBlockStateLoadedModels(resourceMap, (id, resources) -> {
|
||||
return lambda$loadBlockStates$2(Map.entry(id, resources), mapper);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.multiplayer.ClientRegistryLayer;
|
||||
import net.minecraft.client.resources.model.ClientItemInfoLoader;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.dynresources.DynamicModelSystem;
|
||||
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.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(ClientItemInfoLoader.class)
|
||||
@ClientOnlyMixin
|
||||
public abstract class MixinClientItemInfoLoader {
|
||||
@Shadow
|
||||
private static ClientItemInfoLoader.PendingLoad lambda$scheduleLoad$3(Identifier resourceId, Resource resource, RegistryAccess.Frozen registries) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Load client item infos dynamically instead of all at once
|
||||
*/
|
||||
@ModifyArg(method = "scheduleLoad", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenCompose(Ljava/util/function/Function;)Ljava/util/concurrent/CompletableFuture;"))
|
||||
private static Function<Map<Identifier, Resource>, ? extends CompletionStage<ClientItemInfoLoader.LoadedClientInfos>> skipAOTClientItemLoad(
|
||||
Function<Map<Identifier, Resource>, ? extends CompletionStage<ClientItemInfoLoader.LoadedClientInfos>> original,
|
||||
@Local(ordinal = 0) RegistryAccess.Frozen staticRegistries) {
|
||||
return resourceMap -> CompletableFuture.completedFuture(DynamicModelSystem.createDynamicClientInfos(resourceMap, (resourceId, resource) -> {
|
||||
ClientItemInfoLoader.PendingLoad load = lambda$scheduleLoad$3(resourceId, resource, staticRegistries);
|
||||
return load.clientItemInfo();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.neoforged.neoforge.client.ClientNeoForgeMod;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(ClientNeoForgeMod.class)
|
||||
@ClientOnlyMixin
|
||||
public class MixinClientNeoForgeMod {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason avoid triggering eager load of every item model
|
||||
*/
|
||||
@Redirect(method = "lambda$new$7", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelManager;getItemModel(Lnet/minecraft/resources/Identifier;)Lnet/minecraft/client/renderer/item/ItemModel;"))
|
||||
private static ItemModel checkExistenceWithoutLoadingModel(ModelManager instance, Identifier id) {
|
||||
if (!((ModelManagerAccessor)instance).mfix$getBakedItemModels().containsKey(id)) {
|
||||
ModernFix.LOGGER.warn("Missing item model '{}'", id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
|
||||
import net.minecraft.client.renderer.item.ClientItem;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.dynresources.DynamicModelSystem;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Constant;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyConstant;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.Slice;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@Mixin(ModelBakery.class)
|
||||
@ClientOnlyMixin
|
||||
public class MixinModelBakery {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Create dynamic baked registry with cache instead of baking all entries from the input map at once
|
||||
*/
|
||||
@Redirect(method = "bakeModels", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/thread/ParallelMapTransform;schedule(Ljava/util/Map;Ljava/util/function/BiFunction;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
|
||||
private <K, U, V> CompletableFuture<Map<K, V>> dynamicallyBake(Map<K, U> input, BiFunction<K, U, V> baker, Executor executor) {
|
||||
return CompletableFuture.completedFuture(DynamicModelSystem.createDynamicBakedRegistry(input, baker));
|
||||
}
|
||||
|
||||
@Redirect(method = "bakeModels",
|
||||
slice = @Slice(from = @At(value = "FIELD", target = "Lnet/minecraft/client/resources/model/ModelBakery;clientInfos:Ljava/util/Map;", opcode = Opcodes.GETFIELD, ordinal = 2)),
|
||||
at = @At(value = "INVOKE", target = "Ljava/util/Map;forEach(Ljava/util/function/BiConsumer;)V", ordinal = 0))
|
||||
private void dynamicItemProperties(Map<Identifier, ClientItem> clientItems, BiConsumer<? super Identifier, ? super ClientItem> action,
|
||||
@Local(name = "itemStackModelProperties") LocalRef<Map<Identifier, ClientItem.Properties>> modelProperties) {
|
||||
modelProperties.set(Maps.asMap(clientItems.keySet(), id -> {
|
||||
var item = clientItems.get(id);
|
||||
var props = ClientItem.Properties.DEFAULT;
|
||||
if (item != null && !props.equals(item.properties())) {
|
||||
props = item.properties();
|
||||
}
|
||||
return props;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason We want log4j to print the stacktrace and not just the exception message
|
||||
*/
|
||||
@ModifyConstant(method = "lambda$bakeModels$0", constant = @Constant(stringValue = "Unable to bake model: '{}': {}"))
|
||||
private static String showFullException(String prefix) {
|
||||
return "Unable to bake model: '{}'";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.minecraft.client.color.block.BlockColors;
|
||||
import net.minecraft.client.renderer.block.dispatch.BlockStateModel;
|
||||
import net.minecraft.client.resources.model.BlockStateModelLoader;
|
||||
import net.minecraft.client.resources.model.ClientItemInfoLoader;
|
||||
import net.minecraft.client.resources.model.ModelDiscovery;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.client.resources.model.ResolvedModel;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.client.resources.model.cuboid.ItemModelGenerator;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.neoforged.neoforge.client.model.standalone.StandaloneModelLoader;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.dynresources.BlockStateModelMap;
|
||||
import org.embeddedt.modernfix.dynresources.DynamicModelSystem;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(ModelManager.class)
|
||||
@ClientOnlyMixin
|
||||
public class MixinModelManager {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Instead of loading all unbaked models from the resource packs at once, create a dynamic map backed by
|
||||
* a cache that loads them on demand
|
||||
*/
|
||||
@ModifyArg(method = "loadBlockModels", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenCompose(Ljava/util/function/Function;)Ljava/util/concurrent/CompletableFuture;"))
|
||||
private static Function<Map<Identifier, Resource>, ? extends CompletionStage<Map<Identifier, UnbakedModel>>> skipAOTUnbakedModelLoad(Function<Map<Identifier, Resource>, ? extends CompletionStage<Map<Identifier, UnbakedModel>>> original) {
|
||||
return resourceMap -> CompletableFuture.completedFuture(DynamicModelSystem.createDynamicUnbakedModelMap(resourceMap));
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Stop all models from being loaded at startup by the model resolution logic.
|
||||
*/
|
||||
@Redirect(
|
||||
method = "discoverModelDependencies(Ljava/util/Map;Lnet/minecraft/client/resources/model/BlockStateModelLoader$LoadedModels;Lnet/minecraft/client/resources/model/ClientItemInfoLoader$LoadedClientInfos;Lnet/neoforged/neoforge/client/model/standalone/StandaloneModelLoader$LoadedModels;)Lnet/minecraft/client/resources/model/ModelManager$ResolvedModels;",
|
||||
at = @At(value = "INVOKE", target = "Ljava/util/Collection;forEach(Ljava/util/function/Consumer;)V"))
|
||||
private static <T> void skipAddingRoot(Collection<T> instance, Consumer<T> consumer) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt, coredex
|
||||
* @reason Divert to our dynamic resolver. It is cleaner to overwrite the whole method, but this seems to cause
|
||||
* a conflict with Sodium.
|
||||
*/
|
||||
@Redirect(
|
||||
method = "discoverModelDependencies(Ljava/util/Map;Lnet/minecraft/client/resources/model/BlockStateModelLoader$LoadedModels;Lnet/minecraft/client/resources/model/ClientItemInfoLoader$LoadedClientInfos;Lnet/neoforged/neoforge/client/model/standalone/StandaloneModelLoader$LoadedModels;)Lnet/minecraft/client/resources/model/ModelManager$ResolvedModels;",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelDiscovery;resolve()Ljava/util/Map;")
|
||||
)
|
||||
private static Map<Identifier, ResolvedModel> useDynamicResolverMap(
|
||||
ModelDiscovery discovery,
|
||||
Map<Identifier, UnbakedModel> allModels,
|
||||
BlockStateModelLoader.LoadedModels blockStateModels,
|
||||
ClientItemInfoLoader.LoadedClientInfos itemInfos,
|
||||
StandaloneModelLoader.LoadedModels standaloneModels
|
||||
) {
|
||||
UnbakedModel generatedItemModel;
|
||||
var generatedItemWrapper = ((ModelDiscoveryAccessor) discovery).mfix$getModelWrappers().get(ItemModelGenerator.GENERATED_ITEM_MODEL_ID);
|
||||
if (generatedItemWrapper != null) {
|
||||
generatedItemModel = generatedItemWrapper.wrapped();
|
||||
} else {
|
||||
generatedItemModel = new ItemModelGenerator();
|
||||
}
|
||||
return new DynamicModelSystem.DynamicResolver(allModels, blockStateModels, itemInfos, standaloneModels, generatedItemModel).resolvedModelsMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Build the model groups dynamically
|
||||
*/
|
||||
@Overwrite
|
||||
private static Object2IntMap<BlockState> buildModelGroups(BlockColors blockColors, BlockStateModelLoader.LoadedModels loadedModels) {
|
||||
return new DynamicModelSystem.BlockGroupingMap(blockColors, loadedModels);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason avoid copying inner map
|
||||
*/
|
||||
@Overwrite
|
||||
private static Map<BlockState, BlockStateModel> createBlockStateToModelDispatch(Map<BlockState, BlockStateModel> blockStateModels, BlockStateModel missingModel) {
|
||||
BlockStateModelMap.resetCache();
|
||||
return new BlockStateModelMap(blockStateModels, missingModel);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.*;
|
||||
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.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.function.Function;
|
||||
|
||||
@Mixin(value = ModelBakery.ModelBakerImpl.class, priority = 600)
|
||||
@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;
|
||||
|
||||
private boolean mfix$ignoreCache = false;
|
||||
|
||||
@Shadow @Final private Function<Material, TextureAtlasSprite> modelTextureGetter;
|
||||
|
||||
@Override
|
||||
public void mfix$ignoreCache() {
|
||||
mfix$ignoreCache = true;
|
||||
}
|
||||
|
||||
private boolean throwIfMissing;
|
||||
|
||||
@Override
|
||||
public boolean throwOnMissingModel(boolean flag) {
|
||||
boolean old = throwIfMissing;
|
||||
throwIfMissing = flag;
|
||||
return old;
|
||||
}
|
||||
|
||||
@Inject(method = "getModel", at = @At("HEAD"), cancellable = true)
|
||||
private void obtainModel(ResourceLocation arg, CallbackInfoReturnable<UnbakedModel> cir) {
|
||||
if(debugDynamicModelLoading)
|
||||
ModernFix.LOGGER.info("Baking {}", arg);
|
||||
IExtendedModelBakery extendedBakery = (IExtendedModelBakery)this.field_40571;
|
||||
if(arg instanceof ModelResourceLocation && arg != ModelBakery.MISSING_MODEL_LOCATION) {
|
||||
// synchronized because we use topLevelModels
|
||||
synchronized (this.field_40571) {
|
||||
this.field_40571.loadTopLevel((ModelResourceLocation)arg);
|
||||
cir.setReturnValue(this.field_40571.topLevelModels.getOrDefault(arg, extendedBakery.mfix$getUnbakedMissingModel()));
|
||||
// avoid leaks
|
||||
this.field_40571.topLevelModels.clear();
|
||||
}
|
||||
} else
|
||||
cir.setReturnValue(this.field_40571.getModel(arg));
|
||||
UnbakedModel toReplace = cir.getReturnValue();
|
||||
if(true) {
|
||||
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
|
||||
try {
|
||||
toReplace = integration.onUnbakedModelPreBake(arg, toReplace, this.field_40571);
|
||||
} catch(RuntimeException e) {
|
||||
ModernFix.LOGGER.error("Exception firing model pre-bake event for {}", arg, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
cir.setReturnValue(toReplace);
|
||||
cir.getReturnValue().resolveParents(this.field_40571::getModel);
|
||||
if(cir.getReturnValue() == extendedBakery.mfix$getUnbakedMissingModel()) {
|
||||
if(arg != ModelBakery.MISSING_MODEL_LOCATION) {
|
||||
if(debugDynamicModelLoading)
|
||||
ModernFix.LOGGER.warn("Model {} not present", arg);
|
||||
if(throwIfMissing)
|
||||
throw new ModelMissingException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;Ljava/util/function/Function;)Lnet/minecraft/client/resources/model/BakedModel;", at = @At(value = "INVOKE", target = "Ljava/util/Map;get(Ljava/lang/Object;)Ljava/lang/Object;", ordinal = 0), remap = false)
|
||||
private Object ignoreCacheIfRequested(Object o) {
|
||||
return mfix$ignoreCache ? null : o;
|
||||
}
|
||||
|
||||
@WrapOperation(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;Ljava/util/function/Function;)Lnet/minecraft/client/resources/model/BakedModel;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/UnbakedModel;bake(Lnet/minecraft/client/resources/model/ModelBaker;Ljava/util/function/Function;Lnet/minecraft/client/resources/model/ModelState;Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/resources/model/BakedModel;"))
|
||||
private BakedModel callBakedModelIntegration(UnbakedModel unbakedModel, ModelBaker baker, Function<Material, TextureAtlasSprite> spriteGetter, ModelState state, ResourceLocation location, Operation<BakedModel> operation) {
|
||||
BakedModel model = operation.call(unbakedModel, baker, spriteGetter, state, location);
|
||||
|
||||
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
|
||||
model = integration.onBakedModelLoad(location, unbakedModel, model, state, this.field_40571, spriteGetter);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,412 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.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.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.client.Minecraft;
|
||||
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.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.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.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.*;
|
||||
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;
|
||||
|
||||
/* low priority so that our injectors are added after other mods' */
|
||||
@Mixin(value = ModelBakery.class, priority = 1100)
|
||||
@ClientOnlyMixin
|
||||
public abstract class ModelBakeryMixin implements IExtendedModelBakery {
|
||||
|
||||
private static final boolean debugDynamicModelLoading = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
|
||||
|
||||
@Shadow @Final @Mutable public Map<ResourceLocation, UnbakedModel> unbakedCache;
|
||||
|
||||
@Shadow @Final public static ModelResourceLocation MISSING_MODEL_LOCATION;
|
||||
|
||||
@Shadow protected abstract BlockModel loadBlockModel(ResourceLocation location) throws IOException;
|
||||
|
||||
@Shadow @Final private Set<ResourceLocation> loadingStack;
|
||||
|
||||
@Shadow protected abstract void loadModel(ResourceLocation blockstateLocation) throws Exception;
|
||||
|
||||
@Shadow @Final @Mutable
|
||||
private Map<ResourceLocation, BakedModel> bakedTopLevelModels;
|
||||
|
||||
@Shadow @Final @Mutable private 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;
|
||||
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@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)
|
||||
return missingModel;
|
||||
return unbakedCache.get(rl);
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "cacheAndQueueDependencies", at = @At("HEAD"), argsOnly = true)
|
||||
private UnbakedModel fireUnbakedEvent(UnbakedModel model, ResourceLocation location) {
|
||||
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
|
||||
try {
|
||||
model = integration.onUnbakedModelLoad(location, model, (ModelBakery)(Object)this);
|
||||
} catch(RuntimeException e) {
|
||||
ModernFix.LOGGER.error("Exception firing model load event for {}", location, e);
|
||||
}
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
@Inject(method = "cacheAndQueueDependencies", at = @At("RETURN"))
|
||||
private void addToSmallLoadingCache(ResourceLocation location, UnbakedModel model, CallbackInfo ci) {
|
||||
smallLoadingCache.put(location, model);
|
||||
}
|
||||
|
||||
private int mfix$nestedLoads = 0;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason synchronize
|
||||
*/
|
||||
@Inject(method = "getModel", at = @At("HEAD"), cancellable = true)
|
||||
public void getOrLoadModelDynamic(ResourceLocation modelLocation, CallbackInfoReturnable<UnbakedModel> cir) {
|
||||
if(modelLocation.equals(MISSING_MODEL_LOCATION)) {
|
||||
cir.setReturnValue(missingModel);
|
||||
return;
|
||||
}
|
||||
UnbakedModel existing = this.unbakedCache.get(modelLocation);
|
||||
if (existing != null) {
|
||||
cir.setReturnValue(existing);
|
||||
} else {
|
||||
synchronized(this) {
|
||||
// CIT Resewn adds dependencies to loadingStack outside of getModel(), so we must silently
|
||||
// ignore existing things in the stack for the outermost model
|
||||
if (mfix$nestedLoads > 0 && this.loadingStack.contains(modelLocation)) {
|
||||
throw new IllegalStateException("Circular reference while loading " + modelLocation);
|
||||
} else {
|
||||
this.loadingStack.add(modelLocation);
|
||||
UnbakedModel iunbakedmodel = missingModel;
|
||||
|
||||
while(!this.loadingStack.isEmpty()) {
|
||||
ResourceLocation resourcelocation = this.loadingStack.iterator().next();
|
||||
|
||||
mfix$nestedLoads++;
|
||||
try {
|
||||
existing = this.unbakedCache.get(resourcelocation);
|
||||
if (existing == null) {
|
||||
if(debugDynamicModelLoading)
|
||||
LOGGER.info("Loading {}", resourcelocation);
|
||||
this.loadModel(resourcelocation);
|
||||
} else
|
||||
smallLoadingCache.put(resourcelocation, existing);
|
||||
} catch (ModelBakery.BlockStateDefinitionException var9) {
|
||||
LOGGER.warn(var9.getMessage());
|
||||
this.unbakedCache.put(resourcelocation, iunbakedmodel);
|
||||
smallLoadingCache.put(resourcelocation, iunbakedmodel);
|
||||
} catch (Exception var10) {
|
||||
LOGGER.warn("Unable to load model: '{}' referenced from: {}: {}", resourcelocation, modelLocation, var10);
|
||||
this.unbakedCache.put(resourcelocation, iunbakedmodel);
|
||||
smallLoadingCache.put(resourcelocation, iunbakedmodel);
|
||||
} finally {
|
||||
mfix$nestedLoads--;
|
||||
this.loadingStack.remove(resourcelocation);
|
||||
}
|
||||
}
|
||||
|
||||
// We have to get the result from the temporary cache used for a model load
|
||||
// As in pathological cases (e.g. Pedestals on 1.19) unbakedCache can lose
|
||||
// the model immediately
|
||||
UnbakedModel result = smallLoadingCache.getOrDefault(modelLocation, iunbakedmodel);
|
||||
try {
|
||||
// required as some mods (e.g. EBE) call bake directly on the returned model, without resolving parents themselves
|
||||
result.resolveParents(this::getModel);
|
||||
} catch(RuntimeException ignored) {}
|
||||
// We are done with loading, so clear this cache to allow GC of any unneeded models
|
||||
if(mfix$nestedLoads == 0)
|
||||
smallLoadingCache.clear();
|
||||
cir.setReturnValue(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "loadModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;"))
|
||||
private ImmutableList<BlockState> loadOnlyRelevantBlockState(StateDefinition<Block, BlockState> stateDefinition, ResourceLocation location) {
|
||||
var allStates = stateDefinition.getPossibleStates();
|
||||
|
||||
if(!(location instanceof ModelResourceLocation mrl)) {
|
||||
return allStates;
|
||||
}
|
||||
|
||||
// Load a batch of models at once in certain initialization phases to speed up the loading process.
|
||||
// This is disabled when in-game as it will cause stutters when blocks are placed.
|
||||
boolean shouldLoadBatch = (Minecraft.getInstance().getOverlay() != null || Minecraft.getInstance().level == null);
|
||||
int batchSize = ModelBakeryHelpers.MAX_UNBAKED_MODEL_COUNT - 1000;
|
||||
|
||||
// If loading a batch and all the states are smaller than the max batch size, just use them
|
||||
// This is hoisted above the computation of desiredStates for performance reasons
|
||||
if (shouldLoadBatch && allStates.size() <= batchSize) {
|
||||
return allStates;
|
||||
}
|
||||
|
||||
var desiredStates = ModelBakeryHelpers.getBlockStatesForMRL(stateDefinition, mrl);
|
||||
|
||||
// If not loading a batch, load only the desired states
|
||||
if (!shouldLoadBatch) {
|
||||
return desiredStates;
|
||||
}
|
||||
|
||||
// At this point we want to load a batch if possible, but loading every state is too much. If desiredStates
|
||||
// is a single state (should almost always be the case), then we choose a sublist starting from it and extending
|
||||
// batchSize entries (or less if the list ends). If it's multiple states, a single sublist may not include
|
||||
// everything, so we bail.
|
||||
if (desiredStates.size() != 1) {
|
||||
return desiredStates;
|
||||
}
|
||||
|
||||
var desiredState = desiredStates.get(0);
|
||||
int indexInAllStates = allStates.indexOf(desiredState);
|
||||
if (indexInAllStates == -1) {
|
||||
return desiredStates;
|
||||
}
|
||||
return allStates.subList(indexInAllStates, Math.min(indexInAllStates + batchSize, allStates.size()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import net.minecraft.client.resources.model.ModelDiscovery;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(ModelDiscovery.class)
|
||||
@ClientOnlyMixin
|
||||
public interface ModelDiscoveryAccessor {
|
||||
@Accessor("modelWrappers")
|
||||
Object2ObjectMap<Identifier, ModelDiscovery.ModelWrapper> mfix$getModelWrappers();
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ModelManager.class)
|
||||
@ClientOnlyMixin
|
||||
public interface ModelManagerAccessor {
|
||||
@Accessor("bakedItemStackModels")
|
||||
Map<Identifier, ItemModel> mfix$getBakedItemModels();
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
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.BakedModel;
|
||||
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.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.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.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mixin(ModelManager.class)
|
||||
@ClientOnlyMixin
|
||||
public class ModelManagerMixin {
|
||||
@Shadow private Map<ResourceLocation, BakedModel> bakedRegistry;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void injectDummyBakedRegistry(CallbackInfo ci) {
|
||||
if(this.bakedRegistry == null) {
|
||||
this.bakedRegistry = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
@ModifyArg(method = "loadBlockModels", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenCompose(Ljava/util/function/Function;)Ljava/util/concurrent/CompletableFuture;", ordinal = 0), index = 0)
|
||||
private static Function<Map<ResourceLocation, Resource>, ? extends CompletionStage<Map<ResourceLocation, BlockModel>>> deferBlockModelLoad(Function<Map<ResourceLocation, Resource>, ? extends CompletionStage<Map<ResourceLocation, BlockModel>>> fn, @Local(ordinal = 0, argsOnly = true) ResourceManager manager) {
|
||||
return resourceMap -> {
|
||||
var fallbackModel = BlockModel.fromString(ModelBakery.MISSING_MODEL_MESH);
|
||||
var cache = CacheUtil.<ResourceLocation, BlockModel>simpleCacheForLambda(location -> loadSingleBlockModel(manager, location, fallbackModel), 100L);
|
||||
return CompletableFuture.completedFuture(Maps.asMap(Set.copyOf(resourceMap.keySet()), location -> cache.getUnchecked(location)));
|
||||
};
|
||||
}
|
||||
|
||||
@Redirect(method = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelManager;loadBlockStates(Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
|
||||
private CompletableFuture<Map<ResourceLocation, List<ModelBakery.LoadedJson>>> deferBlockStateLoad(ResourceManager manager, Executor executor) {
|
||||
var cache = CacheUtil.<ResourceLocation, List<ModelBakery.LoadedJson>>simpleCacheForLambda(location -> loadSingleBlockState(manager, location), 100L);
|
||||
return CompletableFuture.completedFuture(new LambdaMap<>(location -> cache.getUnchecked(location)));
|
||||
}
|
||||
|
||||
@Redirect(method = "loadModels", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/StateDefinition;getPossibleStates()Lcom/google/common/collect/ImmutableList;"))
|
||||
private ImmutableList<BlockState> skipCollection(StateDefinition<Block, BlockState> definition) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
private static BlockModel loadSingleBlockModel(ResourceManager manager, ResourceLocation location, BlockModel fallbackModel) {
|
||||
return manager.getResource(location).map(resource -> {
|
||||
try (BufferedReader reader = resource.openAsReader()) {
|
||||
return BlockModel.fromStream(reader);
|
||||
} catch (Exception e) {
|
||||
// We must return some nonnull value to avoid breaking the map convention. The easiest solution
|
||||
// is to just return a missing model template.
|
||||
ModernFix.LOGGER.error("Couldn't load model {}, substituting missing", location, e);
|
||||
return fallbackModel;
|
||||
}
|
||||
}).orElse(null);
|
||||
}
|
||||
|
||||
private List<ModelBakery.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));
|
||||
} catch(IOException e) {
|
||||
ModernFix.LOGGER.error("Couldn't load blockstate", e);
|
||||
return null;
|
||||
}
|
||||
}).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources.ctm;
|
||||
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ModelBakery.class)
|
||||
@ClientOnlyMixin
|
||||
public interface CTMModelBakeryAccessor {
|
||||
@Accessor("bakedCache")
|
||||
Map<ModelBakery.BakedCacheKey, BakedModel> mfix$getBakedCache();
|
||||
}
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
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;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import team.chisel.ctm.CTM;
|
||||
import team.chisel.ctm.api.model.IModelCTM;
|
||||
import team.chisel.ctm.client.model.AbstractCTMBakedModel;
|
||||
import team.chisel.ctm.client.model.ModelCTM;
|
||||
import team.chisel.ctm.client.texture.IMetadataSectionCTM;
|
||||
import team.chisel.ctm.client.util.ResourceUtil;
|
||||
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")
|
||||
@ClientOnlyMixin
|
||||
public abstract class TextureMetadataHandlerMixin implements ModernFixClientIntegration {
|
||||
|
||||
@Shadow(remap = false) @Nonnull protected abstract BakedModel wrap(UnbakedModel model, BakedModel object) throws IOException;
|
||||
|
||||
@Shadow(remap = false) @Final private Multimap<ResourceLocation, Material> scrapedTextures;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void subscribeDynamic(CallbackInfo ci) {
|
||||
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)
|
||||
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()) {
|
||||
Deque<ResourceLocation> dependencies = new ArrayDeque<>();
|
||||
Set<ResourceLocation> seenModels = new HashSet<>();
|
||||
dependencies.push(rl);
|
||||
seenModels.add(rl);
|
||||
boolean shouldWrap = false;
|
||||
Set<Pair<String, String>> errors = new HashSet<>();
|
||||
// Breadth-first loop through dependencies, exiting as soon as a CTM texture is found, and skipping duplicates/cycles
|
||||
while (!shouldWrap && !dependencies.isEmpty()) {
|
||||
ResourceLocation dep = dependencies.pop();
|
||||
UnbakedModel model;
|
||||
try {
|
||||
model = dep == rl ? rootModel : bakery.getModel(dep);
|
||||
} catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Collection<Material> textures = Sets.newHashSet(scrapedTextures.get(dep));
|
||||
Collection<ResourceLocation> newDependencies = model.getDependencies();
|
||||
for (Material tex : textures) {
|
||||
IMetadataSectionCTM meta = null;
|
||||
// Cache all dependent texture metadata
|
||||
try {
|
||||
meta = ResourceUtil.getMetadata(ResourceUtil.spriteToAbsolute(tex.texture())).orElse(null); // TODO, lazy
|
||||
} catch (IOException e) {} // Fallthrough
|
||||
if (meta != null) {
|
||||
// At least one texture has CTM metadata, so we should wrap this model
|
||||
shouldWrap = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (ResourceLocation newDep : newDependencies) {
|
||||
if (seenModels.add(newDep)) {
|
||||
dependencies.push(newDep);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shouldWrap) {
|
||||
try {
|
||||
baked = wrap(rootModel, baked);
|
||||
handleInit(rl, baked, bakery);
|
||||
dependencies.clear();
|
||||
} catch (IOException e) {
|
||||
CTM.logger.error("Could not wrap model " + rl + ". Aborting...", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return baked;
|
||||
}
|
||||
|
||||
private void handleInit(ResourceLocation key, BakedModel wrappedModel, ModelBakery bakery) {
|
||||
if(wrappedModel instanceof AbstractCTMBakedModel baked) {
|
||||
IModelCTM var10 = baked.getModel();
|
||||
if (var10 instanceof ModelCTM ctmModel) {
|
||||
if (!ctmModel.isInitialized()) {
|
||||
// Clear the baked cache as upstream CTM does
|
||||
((CTMModelBakeryAccessor)bakery).mfix$getBakedCache().clear();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
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.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;
|
||||
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.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.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.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(ClientProxyImpl.class)
|
||||
@ClientOnlyMixin
|
||||
@RequiresMod("ldlib")
|
||||
public abstract class ClientProxyImplMixin implements ModernFixClientIntegration {
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void registerIntegration(CallbackInfo ci) {
|
||||
ModernFixClient.CLIENT_INTEGRATIONS.add(this);
|
||||
}
|
||||
|
||||
@Redirect(method = "modelBake", at = @At(value = "INVOKE", target = "Ljava/util/Map;entrySet()Ljava/util/Set;", ordinal = 0), remap = false)
|
||||
private Set<?> disableLoop(Map<?, ?> map) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel onBakedModelLoad(ResourceLocation rl, UnbakedModel rootModel, BakedModel baked, ModelState state, ModelBakery bakery, Function<Material, TextureAtlasSprite> textureGetter) {
|
||||
if (baked == null) {
|
||||
return null;
|
||||
}
|
||||
if (rl instanceof ModelResourceLocation && rootModel != null) {
|
||||
if (baked instanceof LDLRendererModel) {
|
||||
return baked;
|
||||
}
|
||||
if (baked.isCustomRenderer()) { // Nothing we can add to builtin models
|
||||
return baked;
|
||||
}
|
||||
Deque<ResourceLocation> dependencies = new ArrayDeque<>();
|
||||
Set<ResourceLocation> seenModels = new HashSet<>();
|
||||
dependencies.push(rl);
|
||||
seenModels.add(rl);
|
||||
boolean shouldWrap = ClientProxy.WRAPPED_MODELS.getOrDefault(rl, false);
|
||||
// Breadth-first loop through dependencies, exiting as soon as a CTM texture is found, and skipping duplicates/cycles
|
||||
while (!shouldWrap && !dependencies.isEmpty()) {
|
||||
ResourceLocation dep = dependencies.pop();
|
||||
UnbakedModel model;
|
||||
try {
|
||||
model = dep == rl ? rootModel : bakery.getModel(dep);
|
||||
} catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Set<Material> textures = new HashSet<>(ClientProxy.SCRAPED_TEXTURES.get(dep));
|
||||
for (Material tex : textures) {
|
||||
// Cache all dependent texture metadata
|
||||
// At least one texture has CTM metadata, so we should wrap this baked
|
||||
if (!LDLMetadataSection.getMetadata(LDLMetadataSection.spriteToAbsolute(tex.texture())).isMissing()) { // TODO lazy
|
||||
shouldWrap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!shouldWrap) {
|
||||
for (ResourceLocation newDep : model.getDependencies()) {
|
||||
if (seenModels.add(newDep)) {
|
||||
dependencies.push(newDep);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LDLib.LOGGER.error("Error loading baked dependency {} for baked {}. Skipping...", dep, rl, e);
|
||||
}
|
||||
}
|
||||
ClientProxy.WRAPPED_MODELS.put(rl, shouldWrap);
|
||||
if (shouldWrap) {
|
||||
return new CustomBakedModelImpl(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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import com.google.common.cache.Cache;
|
|||
import com.google.common.cache.CacheBuilder;
|
||||
import com.mojang.datafixers.DataFixer;
|
||||
import net.minecraft.core.HolderGetter;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
||||
|
|
@ -24,12 +24,12 @@ import java.util.Optional;
|
|||
@Mixin(StructureTemplateManager.class)
|
||||
public class StructureManagerMixin {
|
||||
@Shadow @Final @Mutable
|
||||
private Map<ResourceLocation, Optional<StructureTemplate>> structureRepository;
|
||||
private Map<Identifier, Optional<StructureTemplate>> structureRepository;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void makeStructuresSafe(ResourceManager arg, LevelStorageSource.LevelStorageAccess arg2, DataFixer dataFixer, HolderGetter<Block> arg3, CallbackInfo ci) {
|
||||
/* Structures needing to be reloaded is not a huge issue since we optimize loading them already */
|
||||
Cache<ResourceLocation, Optional<StructureTemplate>> structureCache = CacheBuilder.newBuilder()
|
||||
Cache<Identifier, Optional<StructureTemplate>> structureCache = CacheBuilder.newBuilder()
|
||||
.softValues()
|
||||
.build();
|
||||
this.structureRepository = structureCache.asMap();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user