Compare commits
349 Commits
1.20
...
5.20.0+1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d30de6398 | ||
|
|
06b1e8f5c3 | ||
|
|
fc96643a89 | ||
|
|
c66dd2382c | ||
|
|
2d263436f6 | ||
|
|
eeb842332b | ||
|
|
e0f941cfe5 | ||
|
|
a5af6e5c1f | ||
|
|
80070aa201 | ||
|
|
6531b69fb9 | ||
|
|
2f977822df | ||
|
|
9ed71dcdb4 | ||
|
|
46913ac8b2 | ||
|
|
a0cc8bfbd2 | ||
|
|
e7e065f809 | ||
|
|
19d2d8cfc0 | ||
|
|
0f16e159f9 | ||
|
|
1d4ddc302a | ||
|
|
de5b79fe7c | ||
|
|
e82f316216 | ||
|
|
2901b53424 | ||
|
|
b822f5ce87 | ||
|
|
145896cc99 | ||
|
|
359d1e81d6 | ||
|
|
1bb730b2b0 | ||
|
|
86f06b2f36 | ||
|
|
520de2c12b | ||
|
|
a29bdb2f82 | ||
|
|
c8ca6a5d53 | ||
|
|
103dea589f | ||
|
|
7645af1bab | ||
|
|
cdfe53589e | ||
|
|
1e0d4d9d07 | ||
|
|
5450a16aad | ||
|
|
d8b86708e5 | ||
|
|
6012626112 | ||
|
|
3d785cedb4 | ||
|
|
b1f5f6ebbe | ||
|
|
91ffdb1acf | ||
|
|
f374bcbb3a | ||
|
|
e0d24d4cdb | ||
|
|
545fb386a0 | ||
|
|
09ea5e1dc9 | ||
|
|
a841d20f8a | ||
|
|
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 |
|
|
@ -166,6 +166,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) {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ def versionString = "${baseVersion}${preMarker}+mc${minecraft_version}${commitHa
|
|||
version = versionString
|
||||
archivesBaseName = rootProject.archives_base_name + '-' + project.name
|
||||
|
||||
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17
|
||||
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_21
|
||||
|
||||
repositories {
|
||||
exclusiveContent {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ dependencies {
|
|||
mappings loom.layered() {
|
||||
officialMojangMappings()
|
||||
if(rootProject.hasProperty("parchment_version")) {
|
||||
parchment("org.parchmentmc.data:parchment-${minecraft_version}:${parchment_version}@zip")
|
||||
parchment("org.parchmentmc.data:parchment-${parchment_mc_version}:${parchment_version}@zip")
|
||||
}
|
||||
}
|
||||
implementation project(":annotations")
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ curseforge {
|
|||
changelog = file("${rootDir}/CHANGELOG.md")
|
||||
changelogType = "markdown"
|
||||
releaseType = isBeta ? "beta" : "release"
|
||||
addGameVersion project.name.capitalize()
|
||||
addGameVersion (project.name.equals("neoforge") ? "NeoForge" : project.name.capitalize())
|
||||
gameVersionStrings.addAll(supported_minecraft_versions.tokenize(","))
|
||||
mainArtifact remapJar
|
||||
}
|
||||
|
|
@ -66,4 +66,4 @@ tasks.modrinth.dependsOn(rootProject.generateChangelog)
|
|||
tasks.register('publishToModSites') {
|
||||
publishToModSites.dependsOn(tasks.modrinth)
|
||||
publishToModSites.dependsOn(tasks.curseforge)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ architectury {
|
|||
common(rootProject.enabled_platforms.split(","))
|
||||
}
|
||||
|
||||
ext.jei_minecraft_version = "1.20.1" /* temporary, till 1.20 releases */
|
||||
|
||||
dependencies {
|
||||
// We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies
|
||||
// Do NOT use other classes from fabric loader
|
||||
|
|
@ -20,18 +18,10 @@ dependencies {
|
|||
modApi("dev.latvian.mods:rhino:${rhino_version}") {
|
||||
transitive = false
|
||||
}
|
||||
modApi("me.shedaniel:RoughlyEnoughItems-api:${rei_version}") {
|
||||
transitive = false
|
||||
}
|
||||
modCompileOnly("me.shedaniel:RoughlyEnoughItems-fabric:${rei_version}") {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
modCompileOnly "curse.maven:spark-361579:${rootProject.spark_version}"
|
||||
// compile against the JEI API but do not include it at runtime
|
||||
modCompileOnly("mezz.jei:jei-${jei_minecraft_version}-common:${jei_version}")
|
||||
modCompileOnly("mezz.jei:jei-${jei_minecraft_version}-gui:${jei_version}")
|
||||
modCompileOnly("mezz.jei:jei-${jei_minecraft_version}-lib:${jei_version}")
|
||||
|
||||
modCompileOnly fabricApi.module("fabric-model-loading-api-v1", rootProject.fabric_api_version)
|
||||
// Remove the next line if you don't want to depend on the API
|
||||
// modApi "me.shedaniel:architectury:${rootProject.architectury_version}"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.embeddedt.modernfix;
|
||||
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.TracingExecutor;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
|
|
@ -14,7 +15,6 @@ import org.embeddedt.modernfix.resources.ReloadExecutor;
|
|||
import org.embeddedt.modernfix.util.ClassInfoManager;
|
||||
|
||||
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 {
|
||||
|
|
@ -31,7 +31,7 @@ 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")) {
|
||||
|
|
@ -41,7 +41,7 @@ public class ModernFix {
|
|||
}
|
||||
}
|
||||
|
||||
public static ExecutorService resourceReloadExecutor() {
|
||||
public static TracingExecutor resourceReloadExecutor() {
|
||||
return resourceReloadService;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,19 @@
|
|||
package org.embeddedt.modernfix;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.MemoryReserve;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.embeddedt.modernfix.api.constants.IntegrationConstants;
|
||||
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
|
||||
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
|
||||
import org.embeddedt.modernfix.packet.EntityIDSyncPacket;
|
||||
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||
import org.embeddedt.modernfix.searchtree.JEIBackedSearchTree;
|
||||
import org.embeddedt.modernfix.searchtree.REIBackedSearchTree;
|
||||
import org.embeddedt.modernfix.searchtree.SearchTreeProviderRegistry;
|
||||
import org.embeddedt.modernfix.util.ClassInfoManager;
|
||||
import org.embeddedt.modernfix.world.IntegratedWatchdog;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class ModernFixClient {
|
||||
|
|
@ -47,8 +39,6 @@ public class ModernFixClient {
|
|||
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.branding.F3Screen")) {
|
||||
brandingString = ModernFix.NAME + " " + ModernFixPlatformHooks.INSTANCE.getVersionString();
|
||||
}
|
||||
SearchTreeProviderRegistry.register(JEIBackedSearchTree.PROVIDER);
|
||||
SearchTreeProviderRegistry.register(REIBackedSearchTree.PROVIDER);
|
||||
for(String className : ModernFixPlatformHooks.INSTANCE.getCustomModOptions().get(IntegrationConstants.CLIENT_INTEGRATION_CLASS)) {
|
||||
try {
|
||||
CLIENT_INTEGRATIONS.add((ModernFixClientIntegration)Class.forName(className).getDeclaredConstructor().newInstance());
|
||||
|
|
@ -123,85 +113,6 @@ public class ModernFixClient {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Horrendous hack to allow tracking every synced entity data manager.
|
||||
*
|
||||
* This is to ensure we can perform ID fixup on already constructed managers.
|
||||
*/
|
||||
public static final Set<SynchedEntityData> allEntityDatas = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
|
||||
private static final Field entriesArrayField;
|
||||
static {
|
||||
Field field;
|
||||
try {
|
||||
field = SynchedEntityData.class.getDeclaredField("entriesArray");
|
||||
field.setAccessible(true);
|
||||
} catch(ReflectiveOperationException e) {
|
||||
field = null;
|
||||
}
|
||||
entriesArrayField = field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extremely hacky method to detect and correct mismatched entity data parameter IDs on the client and server.
|
||||
*
|
||||
* The technique is far from ideal, but it should detect reliably and also not break already constructed entities.
|
||||
*/
|
||||
public static void handleEntityIDSync(EntityIDSyncPacket packet) {
|
||||
Map<Class<? extends Entity>, List<Pair<String, Integer>>> info = packet.getFieldInfo();
|
||||
boolean fixNeeded = false;
|
||||
for(Map.Entry<Class<? extends Entity>, List<Pair<String, Integer>>> entry : info.entrySet()) {
|
||||
Class<? extends Entity> eClass = entry.getKey();
|
||||
for(Pair<String, Integer> field : entry.getValue()) {
|
||||
String fieldName = field.getFirst();
|
||||
int newId = field.getSecond();
|
||||
try {
|
||||
Field f = eClass.getDeclaredField(fieldName);
|
||||
f.setAccessible(true);
|
||||
EntityDataAccessor<?> accessor = (EntityDataAccessor<?>)f.get(null);
|
||||
if(compareAndSwitchIds(eClass, fieldName, accessor, newId))
|
||||
fixNeeded = true;
|
||||
} catch(NoSuchFieldException e) {
|
||||
ModernFix.LOGGER.warn("Couldn't find field on {}: {}", eClass, fieldName);
|
||||
} catch(ReflectiveOperationException e) {
|
||||
throw new RuntimeException("Unexpected exception", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Now the ID mappings on synced entity data instances are probably all wrong. Fix that. */
|
||||
List<SynchedEntityData> dataEntries;
|
||||
synchronized (allEntityDatas) {
|
||||
if(fixNeeded) {
|
||||
dataEntries = new ArrayList<>(allEntityDatas);
|
||||
for(SynchedEntityData manager : dataEntries) {
|
||||
Int2ObjectOpenHashMap<SynchedEntityData.DataItem<?>> fixedMap = new Int2ObjectOpenHashMap<>();
|
||||
List<SynchedEntityData.DataItem<?>> items = new ArrayList<>(manager.itemsById.values());
|
||||
for(SynchedEntityData.DataItem<?> item : items) {
|
||||
fixedMap.put(item.getAccessor().id, item);
|
||||
}
|
||||
manager.lock.writeLock().lock();
|
||||
try {
|
||||
manager.itemsById.replaceAll((id, parameter) -> fixedMap.get((int)id));
|
||||
if(entriesArrayField != null) {
|
||||
try {
|
||||
SynchedEntityData.DataItem<?>[] dataArray = new SynchedEntityData.DataItem[items.size()];
|
||||
for(int i = 0; i < dataArray.length; i++) {
|
||||
dataArray[i] = fixedMap.get(i);
|
||||
}
|
||||
entriesArrayField.set(manager, dataArray);
|
||||
} catch(ReflectiveOperationException e) {
|
||||
ModernFix.LOGGER.error(e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
manager.lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
allEntityDatas.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void onServerStarted(MinecraftServer server) {
|
||||
if(!ModernFixMixinPlugin.instance.isOptionEnabled("feature.integrated_server_watchdog.IntegratedWatchdog"))
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package org.embeddedt.modernfix.api.entrypoint;
|
||||
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.client.resources.model.ModelState;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
|
||||
/**
|
||||
* Implement this interface in a mod class and add it to "modernfix:integration_v1" in your mod metadata file
|
||||
|
|
@ -21,49 +20,10 @@ public interface ModernFixClientIntegration {
|
|||
default void onDynamicResourcesStatusChange(boolean enabled) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to allow mods to observe the loading of an unbaked model and either make changes to it or wrap it with their
|
||||
* own instance.
|
||||
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
|
||||
* @param originalModel the original model
|
||||
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
|
||||
* with dynamic resources on
|
||||
* @return the model which should actually be loaded for this resource location
|
||||
*/
|
||||
default UnbakedModel onUnbakedModelLoad(ResourceLocation location, UnbakedModel originalModel, ModelBakery bakery) {
|
||||
return originalModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to allow mods to observe the use of an unbaked model at bake time and either make changes to it or wrap it with their
|
||||
* own instance.
|
||||
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
|
||||
* @param originalModel the original model
|
||||
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
|
||||
* with dynamic resources on
|
||||
* @return the model which should actually be loaded for this resource location
|
||||
*/
|
||||
default UnbakedModel onUnbakedModelPreBake(ResourceLocation location, UnbakedModel originalModel, ModelBakery bakery) {
|
||||
return originalModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to allow mods to observe the loading of a baked model and either make changes to it or wrap it with their
|
||||
* own instance.
|
||||
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
|
||||
* @param originalModel the original model
|
||||
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
|
||||
* with dynamic resources on
|
||||
* @return the model which should actually be loaded for this resource location
|
||||
*/
|
||||
@Deprecated
|
||||
default BakedModel onBakedModelLoad(ResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery) {
|
||||
return originalModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to allow mods to observe the loading of a baked model and either make changes to it or wrap it with their
|
||||
* own instance.
|
||||
*
|
||||
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
|
||||
* @param originalModel the original model
|
||||
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
|
||||
|
|
@ -71,7 +31,7 @@ public interface ModernFixClientIntegration {
|
|||
* @param textureGetter function to retrieve textures for this model
|
||||
* @return the model which should actually be loaded for this resource location
|
||||
*/
|
||||
default BakedModel onBakedModelLoad(ResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery, Function<Material, TextureAtlasSprite> textureGetter) {
|
||||
return onBakedModelLoad(location, baseModel, originalModel, state, bakery);
|
||||
default BakedModel onBakedModelLoad(ModelResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter textureGetter) {
|
||||
return originalModel;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ 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;
|
||||
|
|
@ -25,7 +23,7 @@ public final class ModelHelpers {
|
|||
* @return a list of all blockstates related to the model
|
||||
*/
|
||||
public static ImmutableList<BlockState> getBlockStateForLocation(ModelResourceLocation location) {
|
||||
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(new ResourceLocation(location.getNamespace(), location.getPath()));
|
||||
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(location.id());
|
||||
if(blockOpt.isPresent())
|
||||
return ModelBakeryHelpers.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), location);
|
||||
else
|
||||
|
|
@ -49,7 +47,7 @@ public final class ModelHelpers {
|
|||
* @return a fake map of the top-level models
|
||||
*/
|
||||
public static Map<ResourceLocation, BakedModel> createFakeTopLevelMap(BiFunction<ResourceLocation, ModelState, BakedModel> modelGetter) {
|
||||
return new DynamicMap<>(location -> modelGetter.apply(location, BlockModelRotation.X0_Y0));
|
||||
return new DynamicMap<>(ResourceLocation.class, location -> modelGetter.apply(location, BlockModelRotation.X0_Y0));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -58,6 +56,8 @@ public final class ModelHelpers {
|
|||
* @return an appropriate ModelBaker
|
||||
*/
|
||||
public static ModelBaker adaptBakery(ModelBakery bakery) {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
/*
|
||||
return new ModelBaker() {
|
||||
@Override
|
||||
public UnbakedModel getModel(ResourceLocation resourceLocation) {
|
||||
|
|
@ -70,5 +70,7 @@ public final class ModelHelpers {
|
|||
return ((IExtendedModelBakery)bakery).bakeDefault(resourceLocation, modelState);
|
||||
}
|
||||
};
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import net.minecraft.world.level.block.Blocks;
|
|||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.world.level.material.Fluids;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
|
@ -32,24 +32,19 @@ public class SafeBlockGetter implements BlockGetter {
|
|||
if(!(access instanceof ChunkAccess))
|
||||
return null;
|
||||
ChunkAccess chunk = (ChunkAccess)access;
|
||||
if(!chunk.getStatus().isOrAfter(ChunkStatus.FULL))
|
||||
if(!chunk.getPersistedStatus().isOrAfter(ChunkStatus.FULL))
|
||||
return null;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxBuildHeight() {
|
||||
return this.wrapped.getMaxBuildHeight();
|
||||
public int getMaxY() {
|
||||
return this.wrapped.getMaxY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLightLevel() {
|
||||
return this.wrapped.getMaxLightLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinBuildHeight() {
|
||||
return this.wrapped.getMinBuildHeight();
|
||||
public int getMinY() {
|
||||
return this.wrapped.getMinY();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -2,58 +2,9 @@ package org.embeddedt.modernfix.command;
|
|||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.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;
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,22 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
|
||||
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import org.embeddedt.modernfix.chunk.SafeBlockGetter;
|
||||
import org.embeddedt.modernfix.duck.ISafeBlockGetter;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
|
||||
@Mixin(value = BlockBehaviour.BlockStateBase.class, priority = 100)
|
||||
public class BlockStateBaseMixin {
|
||||
@ModifyVariable(method = "getOffset", at = @At("HEAD"), argsOnly = true, index = 1)
|
||||
private BlockGetter useSafeGetter(BlockGetter g) {
|
||||
if(g instanceof ISafeBlockGetter) {
|
||||
SafeBlockGetter replacement = ((ISafeBlockGetter) g).mfix$getSafeBlockGetter();
|
||||
if(replacement.shouldUse())
|
||||
return replacement;
|
||||
}
|
||||
return g;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.bugfix.paper_chunk_patches;
|
||||
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import net.minecraft.server.level.*;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.util.thread.BlockableEventLoop;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
|
||||
@Mixin(ChunkMap.class)
|
||||
public abstract class ChunkMapMixin {
|
||||
@Shadow @Final private BlockableEventLoop<Runnable> mainThreadExecutor;
|
||||
|
||||
@Shadow @Final private ChunkMap.DistanceManager distanceManager;
|
||||
|
||||
@Shadow protected abstract CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> protoChunkToFullChunk(ChunkHolder arg);
|
||||
|
||||
@Shadow @Final private ServerLevel level;
|
||||
@Shadow @Final private ThreadedLevelLightEngine lightEngine;
|
||||
@Shadow @Final private ChunkProgressListener progressListener;
|
||||
|
||||
@Shadow protected abstract CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> scheduleChunkGeneration(ChunkHolder chunkHolder, ChunkStatus chunkStatus);
|
||||
|
||||
@Shadow @Final private StructureTemplateManager structureTemplateManager;
|
||||
|
||||
/* https://github.com/PaperMC/Paper/blob/ver/1.17.1/patches/server/0752-Fix-chunks-refusing-to-unload-at-low-TPS.patch */
|
||||
@ModifyArg(method = "prepareAccessibleChunk", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 1)
|
||||
private Executor useMainThreadExecutor(Executor executor) {
|
||||
return this.mainThreadExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason revert 1.17 chunk system changes, significantly reduces time and RAM needed to load chunks
|
||||
*/
|
||||
@Inject(method = "schedule", at = @At("HEAD"), cancellable = true)
|
||||
private void useLegacySchedulingLogic(ChunkHolder holder, ChunkStatus requiredStatus, CallbackInfoReturnable<CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> cir) {
|
||||
if(requiredStatus != ChunkStatus.EMPTY && !requiredStatus.hasLoadDependencies()) {
|
||||
ChunkPos chunkpos = holder.getPos();
|
||||
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = holder.getOrScheduleFuture(requiredStatus.getParent(), (ChunkMap)(Object)this);
|
||||
cir.setReturnValue(future.thenComposeAsync((either) -> {
|
||||
Optional<ChunkAccess> optional = either.left();
|
||||
|
||||
if (requiredStatus == ChunkStatus.LIGHT) {
|
||||
this.distanceManager.addTicket(TicketType.LIGHT, chunkpos, 33 + ChunkStatus.getDistance(ChunkStatus.LIGHT), chunkpos);
|
||||
}
|
||||
|
||||
// from original method
|
||||
if (optional.isPresent() && optional.get().getStatus().isOrAfter(requiredStatus)) {
|
||||
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = requiredStatus.load(this.level, this.structureTemplateManager, this.lightEngine, (arg2) -> {
|
||||
return this.protoChunkToFullChunk(holder);
|
||||
}, (ChunkAccess)optional.get());
|
||||
this.progressListener.onStatusChange(chunkpos, requiredStatus);
|
||||
return completablefuture;
|
||||
} else {
|
||||
return this.scheduleChunkGeneration(holder, requiredStatus);
|
||||
}
|
||||
}, this.mainThreadExecutor).thenComposeAsync(CompletableFuture::completedFuture, this.mainThreadExecutor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ public class MinecraftMixin {
|
|||
/**
|
||||
* To mitigate the effect of leaked client worlds, clear most of the data structures that waste memory.
|
||||
*/
|
||||
@Inject(method = "clearLevel(Lnet/minecraft/client/gui/screens/Screen;)V", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/Minecraft;level:Lnet/minecraft/client/multiplayer/ClientLevel;"))
|
||||
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;Z)V", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/Minecraft;level:Lnet/minecraft/client/multiplayer/ClientLevel;"))
|
||||
private void clearLevelDataForLeaks(CallbackInfo ci) {
|
||||
if(this.level != null) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package org.embeddedt.modernfix.fabric.mixin.core;
|
||||
package org.embeddedt.modernfix.common.mixin.core;
|
||||
|
||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||
import org.embeddedt.modernfix.ModernFixClientFabric;
|
||||
import org.embeddedt.modernfix.ModernFixClient;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
|
@ -13,11 +13,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||
public class ClientPlayNetHandlerMixin {
|
||||
@Inject(method = "handleUpdateRecipes", at = @At("RETURN"))
|
||||
private void signalRecipes(CallbackInfo ci) {
|
||||
ModernFixClientFabric.commonMod.onRecipesUpdated();
|
||||
}
|
||||
|
||||
@Inject(method = "handleUpdateTags", at = @At("RETURN"))
|
||||
private void signalTags(CallbackInfo ci) {
|
||||
ModernFixClientFabric.commonMod.onTagsUpdated();
|
||||
ModernFixClient.INSTANCE.onRecipesUpdated();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.core;
|
||||
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.embeddedt.modernfix.ModernFixClient;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(SynchedEntityData.class)
|
||||
@ClientOnlyMixin
|
||||
public class SynchedEntityDataMixin {
|
||||
/**
|
||||
* Store this in our set of all entity data objects.
|
||||
*
|
||||
* Not an ideal solution, but it should guarantee compatibility with mods.
|
||||
*/
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void storeInSet(Entity arg, CallbackInfo ci) {
|
||||
synchronized (ModernFixClient.allEntityDatas) {
|
||||
ModernFixClient.allEntityDatas.add((SynchedEntityData)(Object)this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,27 @@
|
|||
package org.embeddedt.modernfix.common.mixin.feature.cause_lag_by_disabling_threads;
|
||||
|
||||
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
|
||||
import net.minecraft.TracingExecutor;
|
||||
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;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
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<>());
|
||||
private static final TracingExecutor MFIX_CHUNK_BUILD_EXECUTOR = new TracingExecutor(new ThreadPoolExecutor(1,computeNumThreads(), 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()));
|
||||
|
||||
private static int computeNumThreads() {
|
||||
return Math.max(Math.min(Runtime.getRuntime().availableProcessors() / 4, 10), 1);
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "<init>*", at = @At("HEAD"), ordinal = 0, argsOnly = true)
|
||||
private static Executor replaceExecutor(Executor old) {
|
||||
private static TracingExecutor replaceExecutor(TracingExecutor old) {
|
||||
return MFIX_CHUNK_BUILD_EXECUTOR;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package org.embeddedt.modernfix.common.mixin.feature.cause_lag_by_disabling_threads;
|
||||
|
||||
import net.minecraft.TracingExecutor;
|
||||
import net.minecraft.Util;
|
||||
import org.embeddedt.modernfix.util.DirectExecutorService;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
|
|
@ -7,10 +8,8 @@ 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 DirectExecutorService();
|
||||
private static final TracingExecutor BACKGROUND_EXECUTOR = new TracingExecutor(new DirectExecutorService());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|||
public class CrashReportMixin {
|
||||
@Shadow @Final private Throwable exception;
|
||||
|
||||
@Inject(method = "addCategory(Ljava/lang/String;I)Lnet/minecraft/CrashReportCategory;", at = @At(value = "INVOKE", target = "Ljava/io/PrintStream;println(Ljava/lang/String;)V"))
|
||||
@Inject(method = "addCategory(Ljava/lang/String;I)Lnet/minecraft/CrashReportCategory;", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;error(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false))
|
||||
private void dumpStacktrace(String s, int i, CallbackInfoReturnable<CrashReportCategory> cir) {
|
||||
new Exception("ModernFix crash stacktrace").printStackTrace();
|
||||
if(this.exception != null)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
package org.embeddedt.modernfix.common.mixin.feature.stalled_chunk_load_detection;
|
||||
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.server.level.ChunkHolder;
|
||||
import net.minecraft.server.level.ChunkResult;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.EmptyLevelChunk;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
|
|
@ -26,7 +25,7 @@ public abstract class ServerChunkCacheMixin {
|
|||
@Shadow @Final private Thread mainThread;
|
||||
@Shadow @Final public ServerLevel level;
|
||||
|
||||
@Shadow protected abstract CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getChunkFutureMainThread(int k, int l, ChunkStatus arg, boolean bl);
|
||||
@Shadow protected abstract CompletableFuture<ChunkResult<ChunkAccess>> getChunkFutureMainThread(int k, int l, ChunkStatus arg, boolean bl);
|
||||
|
||||
@Shadow @Final private ServerChunkCache.MainThreadExecutor mainThreadProcessor;
|
||||
private final boolean debugDeadServerAccess = Boolean.getBoolean("modernfix.debugBadChunkloading");
|
||||
|
|
@ -38,27 +37,27 @@ public abstract class ServerChunkCacheMixin {
|
|||
if(debugDeadServerAccess) {
|
||||
new Exception().printStackTrace();
|
||||
}
|
||||
Holder<Biome> plains = this.level.registryAccess().registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.PLAINS);
|
||||
Holder<Biome> plains = this.level.registryAccess().lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.PLAINS);
|
||||
cir.setReturnValue(new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ), plains));
|
||||
} else if(Thread.currentThread() != this.mainThread) {
|
||||
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = CompletableFuture.supplyAsync(() -> this.getChunkFutureMainThread(chunkX, chunkZ, requiredStatus, false), this.mainThreadProcessor).join();
|
||||
CompletableFuture<ChunkResult<ChunkAccess>> future = CompletableFuture.supplyAsync(() -> this.getChunkFutureMainThread(chunkX, chunkZ, requiredStatus, false), this.mainThreadProcessor).join();
|
||||
if(!future.isDone()) {
|
||||
// Wait at least 500 milliseconds before printing anything
|
||||
Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> resultingChunk = null;
|
||||
ChunkResult<ChunkAccess> resultingChunk = null;
|
||||
try {
|
||||
resultingChunk = future.get(500, TimeUnit.MILLISECONDS);
|
||||
} catch(InterruptedException | ExecutionException | TimeoutException ignored) {
|
||||
}
|
||||
if(resultingChunk != null && resultingChunk.left().isPresent()) {
|
||||
cir.setReturnValue(resultingChunk.left().get());
|
||||
if(resultingChunk != null && resultingChunk.isSuccess()) {
|
||||
cir.setReturnValue(resultingChunk.orElse(null));
|
||||
return;
|
||||
}
|
||||
if(debugDeadServerAccess)
|
||||
ModernFix.LOGGER.warn("Async loading of a chunk was requested, this might not be desirable", new Exception());
|
||||
try {
|
||||
resultingChunk = future.get(10, TimeUnit.SECONDS);
|
||||
if(resultingChunk.left().isPresent()) {
|
||||
cir.setReturnValue(resultingChunk.left().get());
|
||||
if(resultingChunk.isSuccess()) {
|
||||
cir.setReturnValue(resultingChunk.orElse(null));
|
||||
return;
|
||||
}
|
||||
} catch(InterruptedException | ExecutionException | TimeoutException e) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.cache_model_materials;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.multipart.MultiPart;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@Mixin(MultiPart.class)
|
||||
@ClientOnlyMixin
|
||||
public class MultipartMixin {
|
||||
private Collection<ResourceLocation> dependencyCache = null;
|
||||
@Inject(method = "getDependencies", at = @At("HEAD"), cancellable = true)
|
||||
private void useDependencyCache(CallbackInfoReturnable<Collection<ResourceLocation>> cir) {
|
||||
if(dependencyCache != null)
|
||||
cir.setReturnValue(dependencyCache);
|
||||
}
|
||||
|
||||
@Inject(method = "getDependencies", at = @At("RETURN"))
|
||||
private void storeDependencyCache(CallbackInfoReturnable<Collection<ResourceLocation>> cir) {
|
||||
if(dependencyCache == null)
|
||||
dependencyCache = cir.getReturnValue();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import net.minecraft.core.RegistryAccess;
|
|||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
|
|
@ -22,12 +21,11 @@ import org.spongepowered.asm.mixin.injection.Inject;
|
|||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(ServerLevel.class)
|
||||
public abstract class ServerLevelMixin extends Level implements IServerLevel {
|
||||
protected ServerLevelMixin(WritableLevelData arg, ResourceKey<Level> arg2, RegistryAccess arg3, Holder<DimensionType> arg4, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
|
||||
super(arg, arg2, arg3, arg4, supplier, bl, bl2, l, i);
|
||||
protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey<Level> resourceKey, RegistryAccess registryAccess, Holder<DimensionType> holder, boolean bl, boolean bl2, long l, int i) {
|
||||
super(writableLevelData, resourceKey, registryAccess, holder, bl, bl2, l, i);
|
||||
}
|
||||
|
||||
@Shadow public abstract DimensionDataStorage getDataStorage();
|
||||
|
|
@ -48,8 +46,8 @@ public abstract class ServerLevelMixin extends Level implements IServerLevel {
|
|||
*/
|
||||
@Inject(method = "<init>", at = @At("TAIL"))
|
||||
private void ensureGeneration(CallbackInfo ci) {
|
||||
mfix$strongholdCache = this.getDataStorage().computeIfAbsent(StrongholdLocationCache::load,
|
||||
StrongholdLocationCache::new,
|
||||
mfix$strongholdCache = this.getDataStorage().computeIfAbsent(
|
||||
StrongholdLocationCache.factory((ServerLevel)(Object)this),
|
||||
StrongholdLocationCache.getFileId(this.dimensionTypeRegistration()));
|
||||
this.chunkSource.getGeneratorState().ensureStructuresGenerated();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.chunk_meshing;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.renderer.chunk.RenderChunkRegion;
|
||||
import net.minecraft.client.renderer.chunk.SectionCompiler;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.annotation.RequiresMod;
|
||||
import org.embeddedt.modernfix.util.blockpos.SectionBlockPosIterator;
|
||||
|
|
@ -11,7 +9,7 @@ import org.spongepowered.asm.mixin.Mixin;
|
|||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(targets = { "net/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk$RebuildTask"}, priority = 2000)
|
||||
@Mixin(value = SectionCompiler.class, priority = 2000)
|
||||
@ClientOnlyMixin
|
||||
@RequiresMod("!fluidlogged")
|
||||
public class RebuildTaskMixin {
|
||||
|
|
@ -23,13 +21,4 @@ public class RebuildTaskMixin {
|
|||
private Iterable<BlockPos> fastBetweenClosed(BlockPos firstPos, BlockPos secondPos) {
|
||||
return () -> new SectionBlockPosIterator(firstPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason RenderChunkRegion.getBlockState is expensive, avoid calling it multiple times for the same position
|
||||
*/
|
||||
@Redirect(method = "compile", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/chunk/RenderChunkRegion;getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;", ordinal = 1), require = 0)
|
||||
private BlockState useExistingBlockState(RenderChunkRegion instance, BlockPos pos, @Local(ordinal = 0) BlockState state) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.compact_mojang_registries;
|
||||
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.core.RegistrationInfo;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import org.embeddedt.modernfix.annotation.IgnoreOutsideDev;
|
||||
import org.embeddedt.modernfix.registry.LifecycleMap;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
|
|
@ -20,10 +21,10 @@ public abstract class MappedRegistryMixin<T> {
|
|||
@Shadow
|
||||
@Final
|
||||
@Mutable
|
||||
private Map<T, Lifecycle> lifecycles;
|
||||
private Map<ResourceKey<T>, RegistrationInfo> registrationInfos;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void replaceStorage(CallbackInfo ci) {
|
||||
this.lifecycles = new LifecycleMap<>();
|
||||
this.registrationInfos = new LifecycleMap<>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;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;ILjava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 6)
|
||||
private Executor getReloadExecutor(Executor asyncExecutor) {
|
||||
return ModernFix.resourceReloadExecutor();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,15 +15,15 @@ public class MixinResourceLocation {
|
|||
@Mutable
|
||||
@Shadow
|
||||
@Final
|
||||
protected String namespace;
|
||||
private String namespace;
|
||||
|
||||
@Mutable
|
||||
@Shadow
|
||||
@Final
|
||||
protected String path;
|
||||
private String path;
|
||||
|
||||
@Inject(method = "<init>([Ljava/lang/String;)V", at = @At("RETURN"))
|
||||
private void reinit(String[] id, CallbackInfo ci) {
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void reinit(String string, String string2, CallbackInfo ci) {
|
||||
this.namespace = IdentifierCaches.NAMESPACES.deduplicate(this.namespace);
|
||||
this.path = IdentifierCaches.PATH.deduplicate(this.path);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import java.util.Map;
|
|||
*/
|
||||
@Mixin(WallBlock.class)
|
||||
public abstract class WallBlockMixin extends Block {
|
||||
private static Map<ImmutableList<Float>, Pair<Map<ImmutableMap<Property<?>, Comparable<?>>, VoxelShape>, StateDefinition<Block, BlockState>>> CACHE_BY_SHAPE_VALS = new HashMap<>();
|
||||
private static Map<ImmutableList<Float>, Pair<Map<Map<Property<?>, Comparable<?>>, VoxelShape>, StateDefinition<Block, BlockState>>> CACHE_BY_SHAPE_VALS = new HashMap<>();
|
||||
|
||||
public WallBlockMixin(Properties properties) {
|
||||
super(properties);
|
||||
|
|
@ -34,7 +34,7 @@ public abstract class WallBlockMixin extends Block {
|
|||
@Inject(method = "makeShapes", at = @At("HEAD"), cancellable = true)
|
||||
private synchronized void useCachedShapeMap(float f1, float f2, float f3, float f4, float f5, float f6, CallbackInfoReturnable<Map<BlockState, VoxelShape>> cir) {
|
||||
ImmutableList<Float> key = ImmutableList.of(f1, f2, f3, f4, f5, f6);
|
||||
Pair<Map<ImmutableMap<Property<?>, Comparable<?>>, VoxelShape>, StateDefinition<Block, BlockState>> cache = CACHE_BY_SHAPE_VALS.get(key);
|
||||
Pair<Map<Map<Property<?>, Comparable<?>>, VoxelShape>, StateDefinition<Block, BlockState>> cache = CACHE_BY_SHAPE_VALS.get(key);
|
||||
// require the properties to be identical
|
||||
if(cache == null || !cache.getSecond().getProperties().equals(this.stateDefinition.getProperties()))
|
||||
return;
|
||||
|
|
@ -55,7 +55,7 @@ public abstract class WallBlockMixin extends Block {
|
|||
return;
|
||||
ImmutableList<Float> key = ImmutableList.of(f1, f2, f3, f4, f5, f6);
|
||||
if(!CACHE_BY_SHAPE_VALS.containsKey(key)) {
|
||||
Map<ImmutableMap<Property<?>, Comparable<?>>, VoxelShape> cacheByProperties = new HashMap<>();
|
||||
Map<Map<Property<?>, Comparable<?>>, VoxelShape> cacheByProperties = new HashMap<>();
|
||||
Map<BlockState, VoxelShape> shapeMap = cir.getReturnValue();
|
||||
for(Map.Entry<BlockState, VoxelShape> entry : shapeMap.entrySet()) {
|
||||
cacheByProperties.put(entry.getKey().getValues(), entry.getValue());
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_dfu;
|
||||
|
||||
import com.mojang.datafixers.DSL;
|
||||
import com.mojang.datafixers.types.Type;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/**
|
||||
* Prevent fetchChoiceType calls from loading DFU early. Vanilla doesn't need the return values here.
|
||||
*/
|
||||
@Mixin(BlockEntityType.class)
|
||||
public class BlockEntityTypeMixin {
|
||||
@Redirect(method = "register", at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;fetchChoiceType(Lcom/mojang/datafixers/DSL$TypeReference;Ljava/lang/String;)Lcom/mojang/datafixers/types/Type;"))
|
||||
private static Type<?> skipSchemaCheck(DSL.TypeReference ref, String s) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_dfu;
|
||||
|
||||
import com.mojang.datafixers.DSL;
|
||||
import com.mojang.datafixers.DataFixer;
|
||||
import net.minecraft.util.datafix.DataFixers;
|
||||
import org.embeddedt.modernfix.dfu.LazyDataFixer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Mixin(DataFixers.class)
|
||||
public 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -19,19 +19,19 @@ import java.util.Map;
|
|||
@Mixin(EntityRenderDispatcher.class)
|
||||
@ClientOnlyMixin
|
||||
public class EntityRenderDispatcherMixin {
|
||||
@Shadow private Map<EntityType<?>, EntityRenderer<?>> renderers;
|
||||
@Shadow private Map<EntityType<?>, EntityRenderer<?, ?>> renderers;
|
||||
|
||||
private EntityRendererMap mfix$dynamicRenderers;
|
||||
|
||||
@Inject(method = "getRenderer", at = @At("RETURN"), cancellable = true)
|
||||
private <T extends Entity> void checkNullness(T entity, CallbackInfoReturnable<EntityRenderer<? super T>> cir) {
|
||||
private <T extends Entity> void checkNullness(T entity, CallbackInfoReturnable<EntityRenderer<? super T, ?>> cir) {
|
||||
// apparently some mods yeet the renderers map and cause issues
|
||||
if(cir.getReturnValue() == null)
|
||||
cir.setReturnValue((EntityRenderer<? super T>)mfix$dynamicRenderers.get(entity.getType()));
|
||||
cir.setReturnValue((EntityRenderer<? super T, ?>)mfix$dynamicRenderers.get(entity.getType()));
|
||||
}
|
||||
|
||||
@Redirect(method = "onResourceManagerReload", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/renderer/entity/EntityRenderDispatcher;renderers:Ljava/util/Map;"))
|
||||
private void setRendererField(EntityRenderDispatcher instance, Map<EntityType<?>, EntityRenderer<?>> incomingMap) {
|
||||
private void setRendererField(EntityRenderDispatcher instance, Map<EntityType<?>, EntityRenderer<?, ?>> incomingMap) {
|
||||
this.renderers = incomingMap;
|
||||
this.mfix$dynamicRenderers = (EntityRendererMap)incomingMap;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public class EntityRenderersMixin {
|
|||
@Shadow @Final private static Map<EntityType<?>, EntityRendererProvider<?>> PROVIDERS;
|
||||
|
||||
@Inject(method = "createEntityRenderers", at = @At("HEAD"), cancellable = true)
|
||||
private static void createDynamicRendererLoader(EntityRendererProvider.Context context, CallbackInfoReturnable<Map<EntityType<?>, EntityRenderer<?>>> cir) {
|
||||
private static void createDynamicRendererLoader(EntityRendererProvider.Context context, CallbackInfoReturnable<Map<EntityType<?>, EntityRenderer<?, ?>>> cir) {
|
||||
cir.setReturnValue(new EntityRendererMap(PROVIDERS, context));
|
||||
ModernFix.LOGGER.info("Dynamic entity renderer hook setup");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonElement;
|
||||
import net.minecraft.client.renderer.block.model.BlockElementFace;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.dynamicresources.UVController;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
@Mixin(BlockElementFace.Deserializer.class)
|
||||
@ClientOnlyMixin
|
||||
public class BlockElementFaceDeserializerMixin {
|
||||
|
||||
@Redirect(method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/renderer/block/model/BlockElementFace;",
|
||||
at = @At(value = "INVOKE", target = "Lcom/google/gson/JsonDeserializationContext;deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;)Ljava/lang/Object;", ordinal = 0, remap = false))
|
||||
private Object skipUvsForInitialLoad(JsonDeserializationContext context, JsonElement element, Type type) {
|
||||
return UVController.useDummyUv.get() ? UVController.dummyUv : context.deserialize(element, type);
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ public class BlockModelShaperMixin {
|
|||
@Inject(method = { "<init>", "replaceCache" }, at = @At("RETURN"))
|
||||
private void replaceModelMap(CallbackInfo ci) {
|
||||
// replace the backing map for mods which will access it
|
||||
this.modelByStateCache = new DynamicOverridableMap<>(state -> modelManager.getModel(ModelLocationCache.get(state)));
|
||||
this.modelByStateCache = new DynamicOverridableMap<>(BlockState.class, state -> modelManager.getModel(ModelLocationCache.get(state)));
|
||||
// Clear the cached models on blockstate objects
|
||||
for(Block block : BuiltInRegistries.BLOCK) {
|
||||
for(BlockState state : block.getStateDefinition().getPossibleStates()) {
|
||||
|
|
@ -72,3 +72,4 @@ public class BlockModelShaperMixin {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,3 +24,4 @@ public class BlockStateBaseMixin implements IModelHoldingBlockState {
|
|||
mfix$model = model != null ? new SoftReference<>(model) : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
|
||||
|
||||
import net.minecraft.client.resources.model.ModelDiscovery;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(ModelDiscovery.class)
|
||||
@ClientOnlyMixin
|
||||
public class ModelDiscoveryMixin {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason We will show the warning ourselves later when loading the model dynamically, this is just spam since
|
||||
* the models don't exist during early loading
|
||||
*/
|
||||
@Redirect(method = "loadBlockModel", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V"))
|
||||
private void disableMissingModelWarning(Logger instance, String s, Object o) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +1,98 @@
|
|||
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.Minecraft;
|
||||
import net.minecraft.client.model.geom.EntityModelSet;
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.renderer.item.ClientItem;
|
||||
import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.resources.model.AtlasSet;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
import net.minecraft.client.resources.model.BlockStateModelLoader;
|
||||
import net.minecraft.client.resources.model.ClientItemInfoLoader;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
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.apache.commons.lang3.ArrayUtils;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.util.CacheUtil;
|
||||
import org.embeddedt.modernfix.util.LambdaMap;
|
||||
import org.embeddedt.modernfix.dynamicresources.DynamicModelProvider;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.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.lang.ref.WeakReference;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mixin(ModelManager.class)
|
||||
@ClientOnlyMixin
|
||||
public class ModelManagerMixin {
|
||||
@Shadow private Map<ResourceLocation, BakedModel> bakedRegistry;
|
||||
public class ModelManagerMixin implements DynamicModelProvider.ModelManagerExtension {
|
||||
@Shadow private Map<ModelResourceLocation, BakedModel> bakedBlockStateModels;
|
||||
@Shadow private Map<ResourceLocation, ItemModel> bakedItemStackModels;
|
||||
@Shadow private Map<ResourceLocation, ClientItem.Properties> itemProperties;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void injectDummyBakedRegistry(CallbackInfo ci) {
|
||||
if(this.bakedRegistry == null) {
|
||||
this.bakedRegistry = new HashMap<>();
|
||||
}
|
||||
}
|
||||
@Unique
|
||||
private DynamicModelProvider mfix$modelProvider;
|
||||
|
||||
@Redirect(method = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelManager;loadBlockModels(Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
|
||||
private CompletableFuture<Map<ResourceLocation, BlockModel>> deferBlockModelLoad(ResourceManager manager, Executor executor) {
|
||||
var cache = CacheUtil.<ResourceLocation, BlockModel>simpleCacheForLambda(location -> loadSingleBlockModel(manager, location), 100L);
|
||||
return CompletableFuture.completedFuture(new LambdaMap<>(location -> cache.getUnchecked(location)));
|
||||
return CompletableFuture.completedFuture(Map.of());
|
||||
}
|
||||
|
||||
@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 = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/BlockStateModelLoader;loadBlockStates(Lnet/minecraft/client/resources/model/UnbakedModel;Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
|
||||
private CompletableFuture<BlockStateModelLoader.LoadedModels> deferBlockStateLoad(UnbakedModel unbakedModel, ResourceManager resourceManager, Executor executor) {
|
||||
return CompletableFuture.completedFuture(new BlockStateModelLoader.LoadedModels(Map.of()));
|
||||
}
|
||||
|
||||
@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();
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason disable map creation
|
||||
*/
|
||||
@Overwrite
|
||||
private static Map<BlockState, BakedModel> createBlockStateToModelDispatch(Map<ModelResourceLocation, BakedModel> map, BakedModel bakedModel) {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
private BlockModel loadSingleBlockModel(ResourceManager manager, ResourceLocation location) {
|
||||
return manager.getResource(location).map(resource -> {
|
||||
try (BufferedReader reader = resource.openAsReader()) {
|
||||
return BlockModel.fromStream(reader);
|
||||
} catch(IOException e) {
|
||||
ModernFix.LOGGER.error("Couldn't load model", e);
|
||||
return null;
|
||||
}
|
||||
}).orElse(null);
|
||||
@Redirect(method = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ClientItemInfoLoader;scheduleLoad(Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
|
||||
private CompletableFuture<ClientItemInfoLoader.LoadedClientInfos> disableClientItemEarlyLoad(ResourceManager resourceManager, Executor executor) {
|
||||
return CompletableFuture.completedFuture(new ClientItemInfoLoader.LoadedClientInfos(Map.of()));
|
||||
}
|
||||
|
||||
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());
|
||||
@ModifyArg(method = "reload", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;allOf([Ljava/util/concurrent/CompletableFuture;)Ljava/util/concurrent/CompletableFuture;", ordinal = 1))
|
||||
private CompletableFuture<?>[] createModelProvider(CompletableFuture<?>[] cfs, @Local(ordinal = 0) CompletableFuture<EntityModelSet> entityModelFuture, @Local(ordinal = 0, argsOnly = true) Executor executor, @Local(ordinal = 0) Map<ResourceLocation, CompletableFuture<AtlasSet.StitchResult>> atlasPreparations) {
|
||||
CompletableFuture<Void> makeModelProviderFuture = CompletableFuture.supplyAsync(() -> {
|
||||
return Map.copyOf(Maps.transformValues(atlasPreparations, CompletableFuture::join));
|
||||
}, executor).thenAcceptBoth(entityModelFuture, (stitchResults, entityModelSet) -> {
|
||||
this.mfix$modelProvider = new DynamicModelProvider(
|
||||
Minecraft.getInstance().getResourceManager(),
|
||||
entityModelSet,
|
||||
stitchResults
|
||||
);
|
||||
DynamicModelProvider.currentReloadingModelProvider = new WeakReference<>(this.mfix$modelProvider);
|
||||
});
|
||||
return ArrayUtils.add(cfs, makeModelProviderFuture);
|
||||
}
|
||||
|
||||
@Inject(method = "apply", at = @At("RETURN"))
|
||||
private void setModelRegistries(CallbackInfo ci) {
|
||||
this.bakedBlockStateModels = this.mfix$modelProvider.getTopLevelEmulatedRegistry();
|
||||
this.bakedItemStackModels = this.mfix$modelProvider.getItemModelEmulatedRegistry();
|
||||
this.itemProperties = this.mfix$modelProvider.getItemPropertiesEmulatedRegistry();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DynamicModelProvider mfix$getModelProvider() {
|
||||
return this.mfix$modelProvider;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||
@Mixin(GameRenderer.class)
|
||||
@ClientOnlyMixin
|
||||
public class GameRendererMixin {
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;renderLevel(FJLcom/mojang/blaze3d/vertex/PoseStack;)V", shift = At.Shift.BEFORE))
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;renderLevel(Lnet/minecraft/client/DeltaTracker;)V", shift = At.Shift.BEFORE))
|
||||
private void markRenderingLevel(CallbackInfo ci) {
|
||||
RenderState.IS_RENDERING_LEVEL = true;
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;renderLevel(FJLcom/mojang/blaze3d/vertex/PoseStack;)V", shift = At.Shift.AFTER))
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;renderLevel(Lnet/minecraft/client/DeltaTracker;)V", shift = At.Shift.AFTER))
|
||||
private void markNotRenderingLevel(CallbackInfo ci) {
|
||||
RenderState.IS_RENDERING_LEVEL = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.faster_item_rendering;
|
||||
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(ItemStackRenderState.class)
|
||||
@ClientOnlyMixin
|
||||
public interface ItemStackRenderStateAccessor {
|
||||
@Accessor
|
||||
ItemDisplayContext getDisplayContext();
|
||||
}
|
||||
|
|
@ -1,36 +1,30 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.faster_item_rendering;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransform;
|
||||
import net.minecraft.client.renderer.entity.ItemRenderer;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.SimpleBakedModel;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.embeddedt.modernfix.render.FastItemRenderType;
|
||||
import org.embeddedt.modernfix.render.RenderState;
|
||||
import org.embeddedt.modernfix.render.SimpleItemModelView;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(value = ItemRenderer.class, priority = 600)
|
||||
@Mixin(value = ItemStackRenderState.LayerRenderState.class, priority = 600)
|
||||
@ClientOnlyMixin
|
||||
public abstract class ItemRendererMixin {
|
||||
private ItemDisplayContext transformType;
|
||||
private final SimpleItemModelView modelView = new SimpleItemModelView();
|
||||
public abstract class LayerRenderStateMixin {
|
||||
@Shadow @Final private ItemStackRenderState field_55345;
|
||||
|
||||
@Inject(method = "render", at = @At("HEAD"))
|
||||
private void markRenderingType(ItemStack itemStack, ItemDisplayContext transformType, boolean leftHand, PoseStack matrixStack, MultiBufferSource buffer, int combinedLight, int combinedOverlay, BakedModel model, CallbackInfo ci) {
|
||||
this.transformType = transformType;
|
||||
}
|
||||
@Shadow abstract ItemTransform transform();
|
||||
|
||||
@Unique
|
||||
private final SimpleItemModelView modelView = new SimpleItemModelView();
|
||||
|
||||
/**
|
||||
* If a model
|
||||
|
|
@ -40,20 +34,24 @@ public abstract class ItemRendererMixin {
|
|||
* we do not need to go through the process of rendering every quad. Just render the south ones (the ones facing the
|
||||
* camera).
|
||||
*/
|
||||
@ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/ItemRenderer;renderModelLists(Lnet/minecraft/client/resources/model/BakedModel;Lnet/minecraft/world/item/ItemStack;IILcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;)V"), index = 0)
|
||||
private BakedModel useSimpleWrappedItemModel(BakedModel model, ItemStack stack, int combinedLight, int combinedOverlay, PoseStack matrixStack, VertexConsumer buffer, @Local(ordinal = 0) BakedModel originalModel) {
|
||||
@ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/ItemRenderer;renderItem(Lnet/minecraft/world/item/ItemDisplayContext;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;II[ILnet/minecraft/client/resources/model/BakedModel;Lnet/minecraft/client/renderer/RenderType;Lnet/minecraft/client/renderer/item/ItemStackRenderState$FoilType;)V"), index = 6)
|
||||
private BakedModel useSimpleWrappedItemModel(BakedModel model) {
|
||||
var transformType = ((ItemStackRenderStateAccessor)this.field_55345).getDisplayContext();
|
||||
// Forge composite models split themselves into a smaller simple model, we need to detect that the parent
|
||||
// was not simple
|
||||
// TODO 1.21.4 - I don't think that is needed anymore with the changes to item rendering
|
||||
/*
|
||||
if(originalModel != null && originalModel.getClass() != SimpleBakedModel.class) {
|
||||
return model;
|
||||
}
|
||||
*/
|
||||
|
||||
if(!RenderState.IS_RENDERING_LEVEL && !stack.isEmpty() && model.getClass() == SimpleBakedModel.class && transformType == ItemDisplayContext.GUI) {
|
||||
if(!RenderState.IS_RENDERING_LEVEL && model.getClass() == SimpleBakedModel.class && transformType == ItemDisplayContext.GUI) {
|
||||
FastItemRenderType type;
|
||||
ItemTransform transform = model.getTransforms().gui;
|
||||
ItemTransform transform = this.transform();
|
||||
if(transform == ItemTransform.NO_TRANSFORM)
|
||||
type = FastItemRenderType.SIMPLE_ITEM;
|
||||
else if(stack.getItem() instanceof BlockItem && isBlockTransforms(transform))
|
||||
else if(model.isGui3d() && isBlockTransforms(transform))
|
||||
type = FastItemRenderType.SIMPLE_BLOCK;
|
||||
else
|
||||
return model;
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.faster_structure_location;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureCheck;
|
||||
import org.embeddedt.modernfix.duck.IStructureCheck;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(ServerLevel.class)
|
||||
public class ServerLevelMixin {
|
||||
@Shadow @Final private ServerChunkCache chunkSource;
|
||||
|
||||
@ModifyExpressionValue(method = "<init>", at = @At(value = "NEW", target = "(Lnet/minecraft/world/level/chunk/storage/ChunkScanAccess;Lnet/minecraft/core/RegistryAccess;Lnet/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager;Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/world/level/chunk/ChunkGenerator;Lnet/minecraft/world/level/levelgen/RandomState;Lnet/minecraft/world/level/LevelHeightAccessor;Lnet/minecraft/world/level/biome/BiomeSource;JLcom/mojang/datafixers/DataFixer;)Lnet/minecraft/world/level/levelgen/structure/StructureCheck;", ordinal = 0))
|
||||
private StructureCheck attachGeneratorState(StructureCheck check) {
|
||||
((IStructureCheck)check).mfix$setStructureState(this.chunkSource.getGeneratorState());
|
||||
return check;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.faster_structure_location;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureCheck;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureCheckResult;
|
||||
import org.embeddedt.modernfix.duck.IStructureCheck;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(StructureCheck.class)
|
||||
public class StructureCheckMixin implements IStructureCheck {
|
||||
@Shadow @Final private Registry<Structure> structureConfigs;
|
||||
|
||||
private ChunkGeneratorStructureState mfix$structureState;
|
||||
|
||||
@Override
|
||||
public void mfix$setStructureState(ChunkGeneratorStructureState state) {
|
||||
mfix$structureState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt (inspired by 24w04a and Bytzo's comment on https://bugs.mojang.com/browse/MC-249136)
|
||||
* @reason Avoid running the canCreateStructure method (which can be expensive) if the structure placement already
|
||||
* forbids placing the structure in this chunk.
|
||||
*/
|
||||
@ModifyExpressionValue(method = "checkStart", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/structure/StructureCheck;tryLoadFromStorage(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/levelgen/structure/Structure;ZJ)Lnet/minecraft/world/level/levelgen/structure/StructureCheckResult;"))
|
||||
private StructureCheckResult mfix$checkForValidPosition(StructureCheckResult storageResult, ChunkPos chunkPos, Structure structure, boolean skipKnownStructures) {
|
||||
if (storageResult != null) {
|
||||
return storageResult;
|
||||
} else if(mfix$structureState != null) {
|
||||
// Check if any of the placements allow for this structure to be in this chunk
|
||||
var structureHolder = this.structureConfigs.wrapAsHolder(structure);
|
||||
for (var placement : mfix$structureState.getPlacementsForStructure(structureHolder)) {
|
||||
if (placement.isStructureChunk(mfix$structureState, chunkPos.x, chunkPos.z)) {
|
||||
// Allowed - return null so regular check runs
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Not allowed - early exit by returning a non-null value
|
||||
return StructureCheckResult.START_NOT_PRESENT;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ public class BlockableEventLoopMixin {
|
|||
* @reason yielding the thread is pretty pointless if we're about to park anyway
|
||||
*/
|
||||
@Overwrite
|
||||
protected void waitForTasks() {
|
||||
public void waitForTasks() {
|
||||
LockSupport.parkNanos("waiting for tasks", MFIX$TICK_WAIT_TIME);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.model_optimizations;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(BooleanProperty.class)
|
||||
public class BooleanPropertyMixin {
|
||||
/**
|
||||
* There is no point comparing the immutable sets in any two instances of this class, as they will always be
|
||||
* the same.
|
||||
*/
|
||||
@Redirect(method = "equals", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableSet;equals(Ljava/lang/Object;)Z", remap = false), remap = false)
|
||||
private boolean skipEqualityCheck(ImmutableSet instance, Object object) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.mojang_registry_size;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectList;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(MappedRegistry.class)
|
||||
public class MappedRegistryMixin {
|
||||
/**
|
||||
* Avoid copying the ID list to a slightly larger one every time an entry is added to the registry.
|
||||
* The original behavior causes O(n) time complexity for registration.
|
||||
*/
|
||||
@Redirect(
|
||||
method = "registerMapping(ILnet/minecraft/resources/ResourceKey;Ljava/lang/Object;Lcom/mojang/serialization/Lifecycle;)Lnet/minecraft/core/Holder$Reference;",
|
||||
at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/objects/ObjectList;size(I)V", remap = false)
|
||||
)
|
||||
private void setSizeSmart(ObjectList<?> list, int size) {
|
||||
if(list instanceof ObjectArrayList && size > list.size()) {
|
||||
int requestedSize = size;
|
||||
/* choose next power of two, or this value if it is a power of two */
|
||||
int p2Size = Integer.highestOneBit(size);
|
||||
if(p2Size != size)
|
||||
size = p2Size << 1;
|
||||
// grow backing array to power-of-two size, this will return instantly in most cases
|
||||
((ObjectArrayList<?>)list).ensureCapacity(size);
|
||||
// write null entries to fill size, to match the behavior of list.size(int)
|
||||
while(list.size() < requestedSize)
|
||||
list.add(null);
|
||||
} else {
|
||||
list.size(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.mojang_registry_size;
|
||||
|
||||
import com.google.common.collect.ArrayTable;
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.ImmutableTable;
|
||||
import com.google.common.collect.Table;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
|
||||
import net.minecraft.world.level.block.state.StateHolder;
|
||||
import net.minecraft.world.level.block.state.properties.Property;
|
||||
import org.embeddedt.modernfix.annotation.RequiresMod;
|
||||
|
|
@ -13,6 +10,8 @@ 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;
|
||||
|
||||
/**
|
||||
* Minor mixin to avoid duplicate empty neighbor tables, used when FerriteCore is not present. Won't be enabled in 99% of
|
||||
* modded environments but is useful for testing in dev without dragging in Fabric API.
|
||||
|
|
@ -20,12 +19,15 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||
@Mixin(StateHolder.class)
|
||||
@RequiresMod("!ferritecore")
|
||||
public class StateHolderMixin {
|
||||
@Shadow private Table<Property<?>, Comparable<?>, ?> neighbours;
|
||||
private static final Reference2ObjectArrayMap<Property<?>, ?> EMPTY_NEIGHBOURS = new Reference2ObjectArrayMap<>();
|
||||
|
||||
@Shadow private Map<Property<?>, ?> neighbours;
|
||||
|
||||
/* optimize the case where block has no properties */
|
||||
@Inject(method = "populateNeighbours", at = @At("RETURN"), require = 0)
|
||||
private void replaceEmptyTable(CallbackInfo ci) {
|
||||
if((this.neighbours instanceof ArrayTable || this.neighbours instanceof HashBasedTable) && this.neighbours.isEmpty())
|
||||
this.neighbours = ImmutableTable.of();
|
||||
if (this.neighbours.isEmpty()) {
|
||||
this.neighbours = EMPTY_NEIGHBOURS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import java.util.Map;
|
|||
|
||||
@Mixin(targets = "net/minecraft/nbt/CompoundTag$1")
|
||||
public class CompoundTag1Mixin {
|
||||
@ModifyVariable(method = "load(Ljava/io/DataInput;ILnet/minecraft/nbt/NbtAccounter;)Lnet/minecraft/nbt/CompoundTag;", at = @At(value = "INVOKE_ASSIGN", target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;", remap = false))
|
||||
private Map<String, Tag> modifyMap(Map<String, Tag> map) {
|
||||
@ModifyVariable(method = "loadCompound", at = @At(value = "INVOKE_ASSIGN", target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;", remap = false))
|
||||
private static Map<String, Tag> modifyMap(Map<String, Tag> map) {
|
||||
CanonizingStringMap<Tag> newMap = new CanonizingStringMap<>();
|
||||
if(map != null)
|
||||
newMap.putAll(map);
|
||||
|
|
|
|||
|
|
@ -1,129 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateHolder;
|
||||
import net.minecraft.world.level.block.state.properties.Property;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.world.level.material.Fluids;
|
||||
import org.embeddedt.modernfix.duck.IBlockState;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
|
||||
@Mixin(BlockBehaviour.BlockStateBase.class)
|
||||
public abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState> implements IBlockState {
|
||||
protected BlockStateBaseMixin(Block object, ImmutableMap<Property<?>, Comparable<?>> immutableMap, MapCodec<BlockState> mapCodec) {
|
||||
super(object, immutableMap, mapCodec);
|
||||
}
|
||||
|
||||
private static final FluidState MFIX$VANILLA_DEFAULT_FLUID = Fluids.EMPTY.defaultFluidState();
|
||||
|
||||
@Shadow public abstract void initCache();
|
||||
|
||||
@Shadow private BlockBehaviour.BlockStateBase.Cache cache;
|
||||
@Shadow private FluidState fluidState;
|
||||
@Shadow private boolean isRandomlyTicking;
|
||||
@Shadow @Deprecated private boolean legacySolid;
|
||||
|
||||
@Shadow protected abstract BlockState asState();
|
||||
|
||||
private volatile boolean cacheInvalid = false;
|
||||
private static boolean buildingCache = false;
|
||||
@Override
|
||||
public void clearCache() {
|
||||
cacheInvalid = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCacheInvalid() {
|
||||
return cacheInvalid;
|
||||
}
|
||||
|
||||
private void mfix$generateCache() {
|
||||
if(cacheInvalid) {
|
||||
// Ensure that only one block's cache is built at a time
|
||||
synchronized (BlockBehaviour.BlockStateBase.class) {
|
||||
if(cacheInvalid) {
|
||||
// Ensure that if we end up in here recursively, we just use the original cache
|
||||
if(!buildingCache) {
|
||||
buildingCache = true;
|
||||
try {
|
||||
this.initCache();
|
||||
cacheInvalid = false;
|
||||
} finally {
|
||||
buildingCache = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "*", at = @At(
|
||||
value = "FIELD",
|
||||
opcode = Opcodes.GETFIELD,
|
||||
target = "Lnet/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase;cache:Lnet/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase$Cache;",
|
||||
ordinal = 0
|
||||
))
|
||||
private BlockBehaviour.BlockStateBase.Cache dynamicCacheGen(BlockBehaviour.BlockStateBase base) {
|
||||
mfix$generateCache();
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
@Redirect(method = "*", at = @At(
|
||||
value = "FIELD",
|
||||
opcode = Opcodes.GETFIELD,
|
||||
target = "Lnet/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase;fluidState:Lnet/minecraft/world/level/material/FluidState;",
|
||||
ordinal = 0
|
||||
), require = 0)
|
||||
private FluidState genCacheBeforeGettingFluid(BlockBehaviour.BlockStateBase base) {
|
||||
// don't generate the full cache here as mods will iterate for the fluid state a lot
|
||||
// assume blockstates will not change their contained fluidstate at runtime more than once
|
||||
// this is how Lithium's implementation used to work, so it should be fine
|
||||
if(this.cacheInvalid && this.fluidState == MFIX$VANILLA_DEFAULT_FLUID) {
|
||||
synchronized (BlockBehaviour.BlockStateBase.class) {
|
||||
if(!buildingCache) {
|
||||
buildingCache = true;
|
||||
try {
|
||||
this.fluidState = this.owner.getFluidState(this.asState());
|
||||
} finally {
|
||||
buildingCache = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return this.fluidState;
|
||||
}
|
||||
|
||||
@Redirect(method = "*", at = @At(
|
||||
value = "FIELD",
|
||||
opcode = Opcodes.GETFIELD,
|
||||
target = "Lnet/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase;isRandomlyTicking:Z",
|
||||
ordinal = 0
|
||||
))
|
||||
private boolean genCacheBeforeGettingTicking(BlockBehaviour.BlockStateBase base) {
|
||||
if(this.cacheInvalid)
|
||||
return this.owner.isRandomlyTicking(this.asState());
|
||||
return this.isRandomlyTicking;
|
||||
}
|
||||
|
||||
@Redirect(method = "*", at = @At(
|
||||
value = "FIELD",
|
||||
opcode = Opcodes.GETFIELD,
|
||||
target = "Lnet/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase;legacySolid:Z",
|
||||
ordinal = 0
|
||||
))
|
||||
private boolean genCacheBeforeCheckingSolid(BlockBehaviour.BlockStateBase base) {
|
||||
mfix$generateCache();
|
||||
return this.legacySolid;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds;
|
||||
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.embeddedt.modernfix.blockstate.BlockStateCacheHandler;
|
||||
import org.embeddedt.modernfix.duck.IBlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Mixin(value = Blocks.class, priority = 1100)
|
||||
public class BlocksMixin {
|
||||
@ModifyArg(method = "rebuildCache", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/IdMapper;forEach(Ljava/util/function/Consumer;)V"), index = 0)
|
||||
private static Consumer getEmptyConsumer(Consumer original) {
|
||||
BlockStateCacheHandler.rebuildParallel(true);
|
||||
return o -> {};
|
||||
}
|
||||
|
||||
// require = 0 due to Forge removing the BLOCK_STATE_REGISTRY init here
|
||||
@Redirect(method = "<clinit>", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;initCache()V"), require = 0)
|
||||
private static void skipCacheInit(BlockState state) {
|
||||
// Mark the cache as invalid
|
||||
((IBlockState)state).clearCache();
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import org.spongepowered.asm.mixin.Shadow;
|
|||
/* Idea from Lithium for 1.19.3 */
|
||||
@Mixin(Biome.class)
|
||||
public abstract class BiomeMixin {
|
||||
@Shadow protected abstract float getHeightAdjustedTemperature(BlockPos pos);
|
||||
@Shadow protected abstract float getHeightAdjustedTemperature(BlockPos pos, int i);
|
||||
|
||||
/**
|
||||
* @author 2No2Name
|
||||
|
|
@ -18,7 +18,7 @@ public abstract class BiomeMixin {
|
|||
* @return
|
||||
*/
|
||||
@Overwrite
|
||||
private float getTemperature(BlockPos pos) {
|
||||
return this.getHeightAdjustedTemperature(pos);
|
||||
private float getTemperature(BlockPos pos, int i) {
|
||||
return this.getHeightAdjustedTemperature(pos, i);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.remove_spawn_chunks;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.TicketType;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public class EntityMixin {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason If the spawn chunks are not loaded, end portals linking to the overworld will teleport entities into
|
||||
* the void at the spawn position, which is not ideal. To solve this, we create a PORTAL ticket if the expected
|
||||
* overworld chunk is missing.
|
||||
*/
|
||||
@ModifyExpressionValue(method = "findDimensionEntryPoint", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerLevel;getSharedSpawnPos()Lnet/minecraft/core/BlockPos;"), require = 0)
|
||||
private BlockPos mfix$triggerChunkloadAtSpawnPos(BlockPos spawnPos, ServerLevel destination) {
|
||||
// Only apply this change if the overworld is the destination
|
||||
if (destination.dimension() == ServerLevel.OVERWORLD) {
|
||||
// No ticket is required if the chunk happens to already be loaded
|
||||
if(!destination.hasChunk(spawnPos.getX() >> 4, spawnPos.getZ() >> 4)) {
|
||||
// Create a portal ticket. While we could just load the chunk once, it would immediately unload on the
|
||||
// next tick, causing churn. The ticket will keep it loaded for a few seconds which should give high
|
||||
// performance for farms pumping things through portals frequently.
|
||||
BlockPos key = spawnPos.immutable();
|
||||
destination.getChunkSource().addRegionTicket(TicketType.PORTAL, new ChunkPos(key), 3, key);
|
||||
// Wait for the chunk to be loaded, as adding the ticket is asynchronous
|
||||
destination.getChunk(key);
|
||||
}
|
||||
}
|
||||
return spawnPos;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.remove_spawn_chunks;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.TicketType;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(value = MinecraftServer.class, priority = 1100)
|
||||
public class MinecraftServerMixin {
|
||||
@Redirect(method = "prepareLevels", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V"))
|
||||
private void addSpawnChunkTicket(ServerChunkCache cache, TicketType<?> type, ChunkPos pos, int distance, Object o) {
|
||||
// load first chunk
|
||||
cache.getChunk(pos.x, pos.z, ChunkStatus.FULL, true);
|
||||
}
|
||||
|
||||
@Redirect(method = "prepareLevels", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;getTickingGenerated()I"), require = 0)
|
||||
private int getGenerated(ServerChunkCache cache) {
|
||||
return 441;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.remove_spawn_chunks;
|
||||
|
||||
import net.minecraft.server.level.DistanceManager;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(ServerChunkCache.class)
|
||||
public interface ServerChunkCacheAccessor {
|
||||
@Accessor("distanceManager")
|
||||
DistanceManager getDistanceManager();
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.remove_spawn_chunks;
|
||||
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.TicketType;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(ServerLevel.class)
|
||||
public class ServerLevelMixin {
|
||||
@Redirect(method = "setDefaultSpawnPos", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V"))
|
||||
private void removeTicket(ServerChunkCache cache, TicketType<?> type, ChunkPos pos, int distance, Object o) {
|
||||
}
|
||||
|
||||
@Redirect(method = "setDefaultSpawnPos", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V"))
|
||||
private void addTicket(ServerChunkCache cache, TicketType<?> type, ChunkPos pos, int distance, Object o) {
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.remove_spawn_chunks;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.util.SortedArraySet;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(SortedArraySet.class)
|
||||
public class SortedArraySetMixin<T> {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Make add() not crash with a null key, since some mods (Carpet) assume there will always be a spawn ticket,
|
||||
* and then assume the reference they have is non-null (it can be null with this option enabled).
|
||||
*/
|
||||
@WrapOperation(method = "add", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/SortedArraySet;findIndex(Ljava/lang/Object;)I"), require = 0)
|
||||
private int checkStatus(SortedArraySet<T> instance, T object, Operation<Integer> original) {
|
||||
if(object == null) {
|
||||
ModernFix.LOGGER.error("Attempted to insert a null key into SortedArraySet, ignoring");
|
||||
return 0;
|
||||
}
|
||||
return original.call(instance, object);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.ticking_chunk_alloc;
|
||||
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import net.minecraft.server.level.ChunkHolder;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.embeddedt.modernfix.util.EitherUtil;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Mixin(value = ChunkHolder.class, priority = 500)
|
||||
public abstract class ChunkHolderMixin {
|
||||
@Shadow public abstract CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> getTickingChunkFuture();
|
||||
|
||||
@Shadow public abstract CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> getFullChunkFuture();
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason avoid Optional allocation
|
||||
*/
|
||||
@Overwrite
|
||||
public LevelChunk getTickingChunk() {
|
||||
CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.getTickingChunkFuture();
|
||||
Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either = completableFuture.getNow(null);
|
||||
return either == null ? null : EitherUtil.leftOrNull(either);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason avoid Optional allocation
|
||||
*/
|
||||
@Overwrite
|
||||
public LevelChunk getFullChunk() {
|
||||
CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.getFullChunkFuture();
|
||||
Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either = completableFuture.getNow(null);
|
||||
return either == null ? null : EitherUtil.leftOrNull(either);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.worldgen_allocation;
|
||||
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.levelgen.DensityFunction;
|
||||
import net.minecraft.world.level.levelgen.NoiseChunk;
|
||||
import net.minecraft.world.level.levelgen.material.MaterialRuleList;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(value = MaterialRuleList.class, priority = 100)
|
||||
public class MaterialRuleListMixin {
|
||||
@Shadow @Final private List<NoiseChunk.BlockStateFiller> materialRuleList;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Avoid iterator allocation
|
||||
*/
|
||||
@Overwrite
|
||||
@Nullable
|
||||
public BlockState calculate(DensityFunction.FunctionContext arg) {
|
||||
BlockState state = null;
|
||||
int s = this.materialRuleList.size();
|
||||
for(int i = 0; state == null && i < s; i++) {
|
||||
NoiseChunk.BlockStateFiller blockStateFiller = this.materialRuleList.get(i);
|
||||
state = blockStateFiller.calculate(arg);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.safety;
|
||||
|
||||
import net.minecraft.client.color.item.ItemColors;
|
||||
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;
|
||||
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
@Mixin(value = ItemColors.class, priority = 700)
|
||||
@ClientOnlyMixin
|
||||
public class ItemColorsMixin {
|
||||
private Lock mapLock = new ReentrantLock();
|
||||
@Inject(method = "register", at = @At("HEAD"))
|
||||
private void lockMapBeforeAccess(CallbackInfo ci) {
|
||||
mapLock.lock();
|
||||
}
|
||||
@Inject(method = "register", at = @At("TAIL"))
|
||||
private void unlockMap(CallbackInfo ci) {
|
||||
mapLock.unlock();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.safety;
|
||||
|
||||
import net.minecraft.client.renderer.item.ItemProperties;
|
||||
import net.minecraft.client.renderer.item.ItemPropertyFunction;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(value = ItemProperties.class, priority = 700)
|
||||
@ClientOnlyMixin
|
||||
public class ItemPropertiesMixin {
|
||||
@Shadow @Final @Mutable private static Map<ResourceLocation, ItemPropertyFunction> GENERIC_PROPERTIES;
|
||||
@Shadow @Final @Mutable private static Map<Item, Map<ResourceLocation, ItemPropertyFunction>> PROPERTIES;
|
||||
|
||||
@Inject(method = "<clinit>", at = @At("RETURN"))
|
||||
private static void useConcurrentMaps(CallbackInfo ci) {
|
||||
GENERIC_PROPERTIES = Collections.synchronizedMap(GENERIC_PROPERTIES);
|
||||
PROPERTIES = Collections.synchronizedMap(PROPERTIES);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,14 @@
|
|||
package org.embeddedt.modernfix.core;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig;
|
||||
import org.embeddedt.modernfix.core.config.Option;
|
||||
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||
import org.embeddedt.modernfix.world.ThreadDumper;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.*;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
|
||||
import org.spongepowered.asm.mixin.transformer.meta.MixinMerged;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
|
@ -150,138 +146,6 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
|
|||
|
||||
@Override
|
||||
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
|
||||
if(mixinClassName.equals("org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds.BlockStateBaseMixin")) {
|
||||
try {
|
||||
applyBlockStateCacheScan(targetClass);
|
||||
} catch(RuntimeException e) {
|
||||
ModernFixMixinPlugin.instance.logger.error("Applying blockstate cache ASM patch failed", e);
|
||||
}
|
||||
}
|
||||
ModernFixPlatformHooks.INSTANCE.applyASMTransformers(mixinClassName, targetClass);
|
||||
}
|
||||
|
||||
private void applyBlockStateCacheScan(ClassNode targetClass) {
|
||||
Set<String> initCacheMethodNames = ImmutableSet.of("m_60611_", "func_215692_c", "method_26200", "initCache");
|
||||
Set<String> whitelistedInjections = ImmutableSet.of(
|
||||
"getFluidState", "method_26227", "m_60819_", "func_204520_s"
|
||||
);
|
||||
Map<String, MethodNode> injectorMethodNames = new HashMap<>();
|
||||
Map<String, MethodNode> allMethods = new HashMap<>();
|
||||
Map<String, String> injectorMixinSource = new HashMap<>();
|
||||
String descriptor = Type.getDescriptor(MixinMerged.class);
|
||||
for(MethodNode m : targetClass.methods) {
|
||||
if((m.access & Opcodes.ACC_STATIC) != 0)
|
||||
continue;
|
||||
allMethods.put(m.name, m);
|
||||
Set<AnnotationNode> seenNodes = new HashSet<>();
|
||||
if(m.invisibleAnnotations != null) {
|
||||
for(AnnotationNode ann : m.invisibleAnnotations) {
|
||||
if(ann.desc.equals(descriptor)) {
|
||||
seenNodes.add(ann);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(m.visibleAnnotations != null) {
|
||||
for(AnnotationNode ann : m.visibleAnnotations) {
|
||||
if(ann.desc.equals(descriptor)) {
|
||||
seenNodes.add(ann);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(seenNodes.size() > 0) {
|
||||
injectorMethodNames.put(m.name, m);
|
||||
for(AnnotationNode node : seenNodes) {
|
||||
for(int i = 0; i < node.values.size(); i += 2) {
|
||||
if(Objects.equals(node.values.get(i), "mixin")) {
|
||||
injectorMixinSource.put(m.name, (String)node.values.get(i + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Set<String> cacheCalledInjectors = new HashSet<>();
|
||||
// Search for initCache in the class
|
||||
for(MethodNode m : targetClass.methods) {
|
||||
if((m.access & Opcodes.ACC_STATIC) != 0)
|
||||
continue;
|
||||
if(initCacheMethodNames.contains(m.name)) {
|
||||
// This is it. Check for any injectors it calls
|
||||
for(AbstractInsnNode n : m.instructions) {
|
||||
if(n instanceof MethodInsnNode) {
|
||||
MethodInsnNode invoke = (MethodInsnNode)n;
|
||||
if(((MethodInsnNode)n).owner.equals(targetClass.name) && injectorMethodNames.containsKey(((MethodInsnNode)n).name)) {
|
||||
cacheCalledInjectors.add(invoke.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
Set<String> accessedFieldNames = new HashSet<>();
|
||||
|
||||
// Make a map of all injected methods called by initCache
|
||||
Map<String, MethodNode> writingMethods = new HashMap<>(injectorMethodNames);
|
||||
writingMethods.keySet().retainAll(cacheCalledInjectors);
|
||||
|
||||
// Recursively check the injected methods for any methods they may call
|
||||
int previousSize = 0;
|
||||
Set<String> checkedCalls = new HashSet<>();
|
||||
while(writingMethods.size() > previousSize) {
|
||||
previousSize = writingMethods.size();
|
||||
List<String> keysToCheck = new ArrayList<>(writingMethods.keySet());
|
||||
for(String name : keysToCheck) {
|
||||
if(!checkedCalls.add(name))
|
||||
continue;
|
||||
for(AbstractInsnNode n : writingMethods.get(name).instructions) {
|
||||
if(n instanceof MethodInsnNode) {
|
||||
MethodInsnNode invokeNode = (MethodInsnNode)n;
|
||||
if(invokeNode.owner.equals(targetClass.name)) {
|
||||
MethodNode theMethod = allMethods.get(invokeNode.name);
|
||||
if(theMethod != null)
|
||||
writingMethods.put(invokeNode.name, theMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We now know all methods that have been injected into initCache, and their callers. See what fields they write to
|
||||
writingMethods.forEach((name, method) -> {
|
||||
for(AbstractInsnNode n : method.instructions) {
|
||||
if(n instanceof FieldInsnNode) {
|
||||
FieldInsnNode fieldAcc = (FieldInsnNode)n;
|
||||
if(fieldAcc.getOpcode() == Opcodes.PUTFIELD && fieldAcc.owner.equals(targetClass.name)) {
|
||||
accessedFieldNames.add(fieldAcc.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Lastly, scan all injected methods and see if they retrieve from the field. If so, inject a generateCache
|
||||
// call at the start.
|
||||
injectorMethodNames.forEach((name, method) -> {
|
||||
// skip whitelisted injectors, and injectors called by initCache itself (to prevent recursion)
|
||||
if(whitelistedInjections.contains(name) || cacheCalledInjectors.contains(name))
|
||||
return;
|
||||
boolean needInjection = false;
|
||||
for(AbstractInsnNode n : method.instructions) {
|
||||
if(n instanceof FieldInsnNode) {
|
||||
FieldInsnNode fieldAcc = (FieldInsnNode)n;
|
||||
if(fieldAcc.getOpcode() == Opcodes.GETFIELD && accessedFieldNames.contains(fieldAcc.name)) {
|
||||
needInjection = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(needInjection) {
|
||||
ModernFixMixinPlugin.instance.logger.info("Injecting BlockStateBase cache population hook into {} from {}",
|
||||
name, injectorMixinSource.getOrDefault(name, "[unknown mixin]"));
|
||||
// inject this.mfix$generateCache() at method head
|
||||
InsnList injection = new InsnList();
|
||||
injection.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
||||
injection.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, targetClass.name, "mfix$generateCache", "()V"));
|
||||
method.instructions.insert(injection);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ public class ModernFixEarlyConfig {
|
|||
private static final String MIXIN_REQUIRES_MOD_DESC = Type.getDescriptor(RequiresMod.class);
|
||||
private static final String MIXIN_DEV_ONLY_DESC = Type.getDescriptor(IgnoreOutsideDev.class);
|
||||
|
||||
private static final Pattern PLATFORM_PREFIX = Pattern.compile("(forge|fabric|common)\\.");
|
||||
private static final Pattern PLATFORM_PREFIX = Pattern.compile("(neoforge|fabric|common)\\.");
|
||||
|
||||
public static String sanitize(String mixinClassName) {
|
||||
return PLATFORM_PREFIX.matcher(mixinClassName).replaceFirst("");
|
||||
|
|
@ -78,7 +78,7 @@ public class ModernFixEarlyConfig {
|
|||
}
|
||||
|
||||
private void scanForAndBuildMixinOptions() {
|
||||
List<String> configFiles = ImmutableList.of("modernfix-common.mixins.json", "modernfix-fabric.mixins.json", "modernfix-forge.mixins.json");
|
||||
List<String> configFiles = ImmutableList.of("modernfix-common.mixins.json", "modernfix-fabric.mixins.json", "modernfix-neoforge.mixins.json");
|
||||
List<String> mixinPaths = new ArrayList<>();
|
||||
for(String configFile : configFiles) {
|
||||
InputStream stream = ModernFixEarlyConfig.class.getClassLoader().getResourceAsStream(configFile);
|
||||
|
|
@ -163,7 +163,6 @@ public class ModernFixEarlyConfig {
|
|||
.put("mixin.perf.dynamic_resources", false)
|
||||
.put("mixin.feature.direct_stack_trace", false)
|
||||
.put("mixin.feature.stalled_chunk_load_detection", false)
|
||||
.put("mixin.perf.blast_search_trees.force", false)
|
||||
.put("mixin.bugfix.restore_old_dragon_movement", false)
|
||||
.put("mixin.perf.worldgen_allocation", false) // experimental
|
||||
.put("mixin.feature.cause_lag_by_disabling_threads", false)
|
||||
|
|
@ -182,9 +181,7 @@ public class ModernFixEarlyConfig {
|
|||
.put("mixin.feature.warn_missing_perf_mods", true)
|
||||
.put("mixin.feature.spark_profile_launch", false)
|
||||
.put("mixin.devenv", isDevEnv)
|
||||
.put("mixin.perf.remove_spawn_chunks", isDevEnv)
|
||||
.putConditionally(() -> !isFabric, "mixin.bugfix.fix_config_crashes", true)
|
||||
.putConditionally(() -> !isFabric, "mixin.bugfix.forge_at_inject_error", true)
|
||||
.putConditionally(() -> !isFabric, "mixin.feature.registry_event_progress", false)
|
||||
.putConditionally(() -> isFabric, "mixin.perf.clear_fabric_mapping_tables", false)
|
||||
.build();
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
package org.embeddedt.modernfix.dfu;
|
||||
|
||||
import com.mojang.datafixers.DSL;
|
||||
import com.mojang.datafixers.DataFixer;
|
||||
import com.mojang.datafixers.schemas.Schema;
|
||||
import com.mojang.serialization.Dynamic;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class LazyDataFixer implements DataFixer {
|
||||
private static final Logger LOGGER = LogManager.getLogger("ModernFix");
|
||||
private DataFixer backingDataFixer;
|
||||
private final Supplier<DataFixer> dfuSupplier;
|
||||
|
||||
public LazyDataFixer(Supplier<DataFixer> dfuSupplier) {
|
||||
LOGGER.info("Bypassed Mojang DFU");
|
||||
this.backingDataFixer = null;
|
||||
this.dfuSupplier = dfuSupplier;
|
||||
}
|
||||
|
||||
private DataFixer getDataFixer() {
|
||||
synchronized (this) {
|
||||
if(backingDataFixer == null) {
|
||||
LOGGER.info("Instantiating Mojang DFU");
|
||||
DFUBlaster.blastMaps();
|
||||
backingDataFixer = dfuSupplier.get();
|
||||
}
|
||||
}
|
||||
return backingDataFixer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Dynamic<T> update(DSL.TypeReference type, Dynamic<T> input, int version, int newVersion) {
|
||||
if(version >= newVersion)
|
||||
return input;
|
||||
return getDataFixer().update(type, input, version, newVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Schema getSchema(int key) {
|
||||
return getDataFixer().getSchema(key);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package org.embeddedt.modernfix.duck;
|
||||
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
|
||||
public interface IBlockStateModelLoader {
|
||||
void loadSpecificBlock(ModelResourceLocation location);
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package org.embeddedt.modernfix.duck;
|
||||
|
||||
public interface IExtendedModelBaker {
|
||||
/**
|
||||
* Causes the ModelBaker to throw when it finds a missing model instead of proceeding with the bake.
|
||||
* @return the previous value of this flag
|
||||
*/
|
||||
boolean throwOnMissingModel(boolean flag);
|
||||
}
|
||||
|
|
@ -1,17 +1,11 @@
|
|||
package org.embeddedt.modernfix.duck;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
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 net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
|
||||
public interface IExtendedModelBakery {
|
||||
ImmutableList<BlockState> getBlockStatesForMRL(StateDefinition<Block, BlockState> stateDefinition, ModelResourceLocation location);
|
||||
BakedModel bakeDefault(ResourceLocation modelLocation, ModelState state);
|
||||
UnbakedModel mfix$getUnbakedMissingModel();
|
||||
void mfix$tick();
|
||||
void mfix$finishLoading();
|
||||
UnbakedModel mfix$loadUnbakedModelDynamic(ModelResourceLocation location);
|
||||
UnbakedModel mfix$getMissingModel();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package org.embeddedt.modernfix.duck;
|
||||
|
||||
public interface IExtendedModelManager {
|
||||
void mfix$tick();
|
||||
}
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
package org.embeddedt.modernfix.dynamicresources;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.mojang.math.Transformation;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.renderer.block.model.ItemOverrides;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransforms;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.BlockModelRotation;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DynamicBakedModelProvider implements Map<ResourceLocation, BakedModel> {
|
||||
/**
|
||||
* The list of blacklisted resource locations that are never baked as top-level models.
|
||||
*
|
||||
* This is a hack to get around the fact that we don't really know exactly what models were supposed to end up
|
||||
* in the baked registry ahead of time.
|
||||
*/
|
||||
private static final ImmutableSet<ResourceLocation> BAKE_SKIPPED_TOPLEVEL = ImmutableSet.<ResourceLocation>builder()
|
||||
.add(new ResourceLocation("custommachinery", "block/custom_machine_block"))
|
||||
.build();
|
||||
public static DynamicBakedModelProvider currentInstance = null;
|
||||
private final ModelBakery bakery;
|
||||
private final Map<ModelBakery.BakedCacheKey, BakedModel> bakedCache;
|
||||
private final Map<ResourceLocation, BakedModel> permanentOverrides;
|
||||
private BakedModel missingModel;
|
||||
private static final BakedModel SENTINEL = new BakedModel() {
|
||||
@Override
|
||||
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction direction, RandomSource random) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useAmbientOcclusion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGui3d() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean usesBlockLight() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCustomRenderer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextureAtlasSprite getParticleIcon() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemTransforms getTransforms() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemOverrides getOverrides() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
public DynamicBakedModelProvider(ModelBakery bakery, Map<ModelBakery.BakedCacheKey, BakedModel> cache) {
|
||||
this.bakery = bakery;
|
||||
this.bakedCache = cache;
|
||||
this.permanentOverrides = Collections.synchronizedMap(new Object2ObjectOpenHashMap<>());
|
||||
if(currentInstance == null)
|
||||
currentInstance = this;
|
||||
}
|
||||
|
||||
private static ModelBakery.BakedCacheKey vanillaKey(Object o) {
|
||||
return new ModelBakery.BakedCacheKey((ResourceLocation)o, BlockModelRotation.X0_Y0.getRotation(), false);
|
||||
}
|
||||
@Override
|
||||
public int size() {
|
||||
return bakedCache.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return bakedCache.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object o) {
|
||||
return permanentOverrides.getOrDefault(o, SENTINEL) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object o) {
|
||||
return permanentOverrides.containsValue(o) || bakedCache.containsValue(o);
|
||||
}
|
||||
|
||||
private static boolean isVanillaTopLevelModel(ResourceLocation location) {
|
||||
if(location instanceof ModelResourceLocation) {
|
||||
try {
|
||||
ModelResourceLocation mrl = (ModelResourceLocation)location;
|
||||
ResourceLocation registryKey = new ResourceLocation(mrl.getNamespace(), mrl.getPath());
|
||||
// check for standard inventory model
|
||||
if(mrl.getVariant().equals("inventory") && BuiltInRegistries.ITEM.containsKey(registryKey))
|
||||
return true;
|
||||
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(registryKey);
|
||||
if(blockOpt.isPresent()) {
|
||||
return ModelBakeryHelpers.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), mrl).size() > 0;
|
||||
}
|
||||
} catch(RuntimeException ignored) {
|
||||
// can occur if the MRL is not valid for that blockstate, ignore
|
||||
}
|
||||
}
|
||||
if(location.getNamespace().equals("minecraft") && location.getPath().equals("builtin/missing"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private BakedModel getMissingModel() {
|
||||
BakedModel m = missingModel;
|
||||
if(m == null) {
|
||||
m = missingModel = ((IExtendedModelBakery)bakery).bakeDefault(ModelBakery.MISSING_MODEL_LOCATION, BlockModelRotation.X0_Y0);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel get(Object o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
BakedModel model = permanentOverrides.getOrDefault(o, SENTINEL);
|
||||
if(model != SENTINEL)
|
||||
return model;
|
||||
else {
|
||||
try {
|
||||
if(BAKE_SKIPPED_TOPLEVEL.contains((ResourceLocation)o))
|
||||
model = getMissingModel();
|
||||
else
|
||||
model = ((IExtendedModelBakery)bakery).bakeDefault((ResourceLocation)o, BlockModelRotation.X0_Y0);
|
||||
} catch(RuntimeException e) {
|
||||
ModernFix.LOGGER.error("Exception baking {}: {}", o, e);
|
||||
model = getMissingModel();
|
||||
}
|
||||
if(model == getMissingModel()) {
|
||||
// to correctly emulate the original map, we return null for missing models, unless they are top-level
|
||||
model = isVanillaTopLevelModel((ResourceLocation)o) ? model : null;
|
||||
permanentOverrides.put((ResourceLocation) o, model);
|
||||
}
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel put(ResourceLocation resourceLocation, BakedModel bakedModel) {
|
||||
BakedModel m = permanentOverrides.put(resourceLocation, bakedModel);
|
||||
if(m != null)
|
||||
return m;
|
||||
else
|
||||
return bakedCache.get(vanillaKey(resourceLocation));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel remove(Object o) {
|
||||
BakedModel m = permanentOverrides.remove(o);
|
||||
if(m != null)
|
||||
return m;
|
||||
return bakedCache.remove(vanillaKey(o));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(@NotNull Map<? extends ResourceLocation, ? extends BakedModel> map) {
|
||||
permanentOverrides.putAll(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<ResourceLocation> keySet() {
|
||||
return bakedCache.keySet().stream().map(ModelBakery.BakedCacheKey::id).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<BakedModel> values() {
|
||||
return bakedCache.values();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<Entry<ResourceLocation, BakedModel>> entrySet() {
|
||||
return bakedCache.entrySet().stream().map(entry -> new AbstractMap.SimpleImmutableEntry<>(entry.getKey().id(), entry.getValue())).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BakedModel replace(ResourceLocation key, BakedModel value) {
|
||||
BakedModel existingOverride = permanentOverrides.get(key);
|
||||
// as long as no valid override was put in (null can mean unable to load model, so we treat as invalid), replace
|
||||
// the model
|
||||
if(existingOverride == null)
|
||||
return this.put(key, value);
|
||||
else
|
||||
return existingOverride;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(BiFunction<? super ResourceLocation, ? super BakedModel, ? extends BakedModel> function) {
|
||||
Set<ResourceLocation> overridenLocations = permanentOverrides.keySet();
|
||||
permanentOverrides.replaceAll(function);
|
||||
boolean uvLock = BlockModelRotation.X0_Y0.isUvLocked();
|
||||
Transformation rotation = BlockModelRotation.X0_Y0.getRotation();
|
||||
bakedCache.replaceAll((loc, oldModel) -> {
|
||||
if(loc.transformation() != rotation || loc.isUvLocked() != uvLock || overridenLocations.contains(loc.id()))
|
||||
return oldModel;
|
||||
else
|
||||
return function.apply(loc.id(), oldModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
package org.embeddedt.modernfix.dynamicresources;
|
||||
|
||||
import it.unimi.dsi.fastutil.Function;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
|
||||
/**
|
||||
* The Mojang Triple-based baked cache system is too slow to be hitting on every model retrieval, so
|
||||
* we need a fast, concurrency-safe wrapper on top.
|
||||
*/
|
||||
public class DynamicModelCache<K> {
|
||||
private final Reference2ReferenceLinkedOpenHashMap<K, BakedModel> cache = new Reference2ReferenceLinkedOpenHashMap<>();
|
||||
private final StampedLock lock = new StampedLock();
|
||||
private final Function<K, BakedModel> modelRetriever;
|
||||
private final boolean allowNulls;
|
||||
|
||||
public DynamicModelCache(Function<K, BakedModel> modelRetriever, boolean allowNulls) {
|
||||
this.modelRetriever = modelRetriever;
|
||||
this.allowNulls = allowNulls;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
long stamp = lock.writeLock();
|
||||
try {
|
||||
cache.clear();
|
||||
} finally {
|
||||
lock.unlock(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean needToPopulate(K state) {
|
||||
long stamp = lock.readLock();
|
||||
try {
|
||||
return !cache.containsKey(state);
|
||||
} finally {
|
||||
lock.unlock(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
private BakedModel getModelFromCache(K state) {
|
||||
long stamp = lock.readLock();
|
||||
try {
|
||||
return cache.get(state);
|
||||
} finally {
|
||||
lock.unlock(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
private BakedModel cacheModel(K state) {
|
||||
BakedModel model = modelRetriever.apply(state);
|
||||
|
||||
// Lock and modify our local, faster cache
|
||||
long stamp = lock.writeLock();
|
||||
|
||||
try {
|
||||
cache.putAndMoveToFirst(state, model);
|
||||
// TODO: choose less arbitrary number
|
||||
if(cache.size() >= 1000) {
|
||||
cache.removeLast();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock(stamp);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
public BakedModel get(K key) {
|
||||
BakedModel model = getModelFromCache(key);
|
||||
|
||||
if(model == null && (!allowNulls || needToPopulate(key))) {
|
||||
model = cacheModel(key);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +1,618 @@
|
|||
package org.embeddedt.modernfix.dynamicresources;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import net.minecraft.client.model.geom.EntityModelSet;
|
||||
import net.minecraft.client.renderer.block.BlockModelShaper;
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.renderer.block.model.BlockModelDefinition;
|
||||
import net.minecraft.client.renderer.block.model.ItemModelGenerator;
|
||||
import net.minecraft.client.renderer.block.model.UnbakedBlockStateModel;
|
||||
import net.minecraft.client.renderer.item.ClientItem;
|
||||
import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.renderer.item.MissingItemModel;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlas;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.AtlasSet;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.BlockModelRotation;
|
||||
import net.minecraft.client.resources.model.BlockStateModelLoader;
|
||||
import net.minecraft.client.resources.model.Material;
|
||||
import net.minecraft.client.resources.model.MissingBlockModel;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
import net.minecraft.client.resources.model.ModelDebugName;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.client.resources.model.ModelState;
|
||||
import net.minecraft.client.resources.model.SpriteGetter;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
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.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Handles loading models dynamically, rather than at startup time.
|
||||
*/
|
||||
public class DynamicModelProvider {
|
||||
private final Map<ResourceLocation, UnbakedModel> internalModels;
|
||||
private final Cache<ResourceLocation, Optional<UnbakedModel>> loadedModels =
|
||||
CacheBuilder.newBuilder()
|
||||
.expireAfterAccess(3, TimeUnit.MINUTES)
|
||||
.maximumSize(1000)
|
||||
.concurrencyLevel(8)
|
||||
.softValues()
|
||||
.build();
|
||||
private final LoadingCache<ResourceLocation, Optional<BlockStateModelLoader.LoadedModels>> loadedStateDefinitions =
|
||||
this.makeLoadingCache(this::loadBlockStateDefinition);
|
||||
|
||||
public DynamicModelProvider(Map<ResourceLocation, UnbakedModel> initialModels) {
|
||||
this.internalModels = initialModels;
|
||||
}
|
||||
private final LoadingCache<ResourceLocation, Optional<UnbakedModel>> loadedBlockModels =
|
||||
this.makeLoadingCache(this::loadBlockModel);
|
||||
|
||||
public UnbakedModel getModel(ResourceLocation location) {
|
||||
private final LoadingCache<ModelResourceLocation, Optional<BakedModel>> loadedBakedModels =
|
||||
this.makeLoadingCache(this::loadBakedModel);
|
||||
|
||||
private final LoadingCache<ResourceLocation, Optional<ClientItem>> loadedClientItemProperties =
|
||||
this.makeLoadingCache(this::loadClientItemProperties);
|
||||
|
||||
private final LoadingCache<ResourceLocation, Optional<ItemModel>> loadedItemModels =
|
||||
this.makeLoadingCache(this::loadItemModel);
|
||||
|
||||
private final LoadingCache<ResourceLocation, Optional<BakedModel>> loadedStandaloneModels =
|
||||
this.makeLoadingCache(this::loadStandaloneModel);
|
||||
|
||||
private final BakedModel missingModel;
|
||||
private final ItemModel missingItemModel;
|
||||
private final UnbakedModel unbakedMissingModel;
|
||||
private final Function<ResourceLocation, StateDefinition<Block, BlockState>> stateMapper;
|
||||
private final ResourceManager resourceManager;
|
||||
private final ModelBakery.TextureGetter textureGetter;
|
||||
private final DynamicResolver resolver;
|
||||
private final EntityModelSet entityModelSet;
|
||||
private final ItemModelGenerator itemModelGenerator;
|
||||
|
||||
private final Map<ModelResourceLocation, BakedModel> mrlModelOverrides = new ConcurrentHashMap<>();
|
||||
private final Map<ResourceLocation, ItemModel> itemStackModelOverrides = new ConcurrentHashMap<>();
|
||||
private final Map<ResourceLocation, BakedModel> standaloneModelOverrides = new ConcurrentHashMap<>();
|
||||
private final Map<ModelResourceLocation, UnbakedBlockStateModel> unbakedBlockStateModelOverrides = new ConcurrentHashMap<>();
|
||||
|
||||
private final List<DynamicModelProvider.DynamicModelPlugin> pluginList = new ArrayList<>();
|
||||
|
||||
private static final boolean DEBUG_DYNAMIC_MODEL_LOADING = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
|
||||
|
||||
public DynamicModelProvider(ResourceManager resourceManager, EntityModelSet entityModelSet,
|
||||
Map<ResourceLocation, AtlasSet.StitchResult> atlasMap) {
|
||||
this.unbakedMissingModel = MissingBlockModel.missingModel();
|
||||
this.entityModelSet = entityModelSet;
|
||||
var missing = atlasMap.get(TextureAtlas.LOCATION_BLOCKS).missing();
|
||||
this.textureGetter = new ModelBakery.TextureGetter() {
|
||||
@Override
|
||||
public TextureAtlasSprite get(ModelDebugName modelDebugName, Material material) {
|
||||
var atlas = atlasMap.get(material.atlasLocation());
|
||||
var sprite = atlas.getSprite(material.texture());
|
||||
if (sprite != null) {
|
||||
return sprite;
|
||||
} else {
|
||||
ModernFix.LOGGER.warn("Unable to find sprite '{}' referenced by model '{}'", material.texture(), modelDebugName.get());
|
||||
return missing;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextureAtlasSprite reportMissingReference(ModelDebugName modelDebugName, String string) {
|
||||
return missing;
|
||||
}
|
||||
};
|
||||
this.stateMapper = BlockStateModelLoader.definitionLocationToBlockMapper();
|
||||
this.resourceManager = resourceManager;
|
||||
this.resolver = new DynamicResolver();
|
||||
this.itemModelGenerator = new ItemModelGenerator();
|
||||
this.missingModel = this.bakeMissingModel();
|
||||
this.missingItemModel = new MissingItemModel(this.missingModel);
|
||||
try {
|
||||
return loadedModels.get(location, () -> Optional.ofNullable(loadModel(location))).orElse(null);
|
||||
} catch(ExecutionException e) {
|
||||
throw new RuntimeException(e.getCause());
|
||||
Class.forName("net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin");
|
||||
pluginList.add(new FabricDynamicModelHandler(this));
|
||||
} catch(Exception ignored) {
|
||||
// Fabric API likely not present
|
||||
}
|
||||
}
|
||||
|
||||
private UnbakedModel loadModel(ResourceLocation location) {
|
||||
return null; /* TODO :) */
|
||||
public BakedModel getMissingBakedModel() {
|
||||
return this.missingModel;
|
||||
}
|
||||
|
||||
public ItemModel getMissingItemModel() {
|
||||
return this.missingItemModel;
|
||||
}
|
||||
|
||||
private static final Supplier<Set<ModelResourceLocation>> TOP_LEVEL_LOCATIONS_SUPPLIER = Suppliers.memoizeWithExpiration(() -> {
|
||||
Set<ModelResourceLocation> set = new HashSet<>();
|
||||
// Skip going through ModelLocationCache because most of the accesses will be misses
|
||||
BuiltInRegistries.BLOCK.entrySet().forEach(entry -> {
|
||||
var location = entry.getKey().location();
|
||||
for(BlockState state : entry.getValue().getStateDefinition().getPossibleStates()) {
|
||||
set.add(BlockModelShaper.stateToModelLocation(location, state));
|
||||
}
|
||||
});
|
||||
return Collections.unmodifiableSet(set);
|
||||
}, 2, TimeUnit.MINUTES);
|
||||
|
||||
public Map<ModelResourceLocation, BakedModel> getTopLevelEmulatedRegistry() {
|
||||
return new EmulatedRegistry<>(ModelResourceLocation.class, this.loadedBakedModels, TOP_LEVEL_LOCATIONS_SUPPLIER, this.mrlModelOverrides);
|
||||
}
|
||||
|
||||
public Map<ResourceLocation, BakedModel> getStandaloneEmulatedRegistry() {
|
||||
return new EmulatedRegistry<>(ResourceLocation.class, this.loadedStandaloneModels, Set::of, this.standaloneModelOverrides);
|
||||
}
|
||||
|
||||
public Map<ResourceLocation, ItemModel> getItemModelEmulatedRegistry() {
|
||||
return new EmulatedRegistry<>(ResourceLocation.class, this.loadedItemModels, BuiltInRegistries.ITEM::keySet, this.itemStackModelOverrides);
|
||||
}
|
||||
|
||||
public Map<ResourceLocation, ClientItem.Properties> getItemPropertiesEmulatedRegistry() {
|
||||
return Maps.transformValues(new EmulatedRegistry<>(ResourceLocation.class, this.loadedClientItemProperties, BuiltInRegistries.ITEM::keySet, Map.of()), ClientItem::properties);
|
||||
}
|
||||
|
||||
private <K, V> LoadingCache<K, Optional<V>> makeLoadingCache(Function<K, Optional<V>> loadingFunction) {
|
||||
return CacheBuilder.newBuilder()
|
||||
.expireAfterAccess(3, TimeUnit.MINUTES)
|
||||
.maximumSize(1000)
|
||||
.concurrencyLevel(8)
|
||||
.softValues()
|
||||
.build(new CacheLoader<>() {
|
||||
@Override
|
||||
public Optional<V> load(K key) {
|
||||
return loadingFunction.apply(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class EmulatedRegistry<K, V> implements Map<K, V> {
|
||||
private final LoadingCache<K, Optional<V>> realCache;
|
||||
private final Supplier<Set<K>> keys;
|
||||
private final Map<K, V> overrides;
|
||||
private final Class<K> keyClass;
|
||||
|
||||
public EmulatedRegistry(Class<K> keyClass, LoadingCache<K, Optional<V>> realCache, Supplier<Set<K>> keys, Map<K, V> overrides) {
|
||||
this.keyClass = keyClass;
|
||||
this.realCache = realCache;
|
||||
this.keys = keys;
|
||||
this.overrides = overrides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
if (this.keyClass.isAssignableFrom(key.getClass())) {
|
||||
return this.realCache.getUnchecked((K)key).orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getOrDefault(Object key, V defaultValue) {
|
||||
if (this.keyClass.isAssignableFrom(key.getClass())) {
|
||||
return this.realCache.getUnchecked((K)key).orElse(defaultValue);
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
V oldValue = this.realCache.getUnchecked(key).orElse(null);
|
||||
this.overrides.put(key, value);
|
||||
this.realCache.invalidate(key);
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
this.overrides.remove(key);
|
||||
this.realCache.invalidate(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(@NotNull Map<? extends K, ? extends V> m) {
|
||||
m.forEach(this::put);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.overrides.clear();
|
||||
this.realCache.invalidateAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<K> keySet() {
|
||||
return keys.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<V> values() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return keys.get().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return keys.get().contains(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<Entry<K, V>> entrySet() {
|
||||
return new AbstractSet<>() {
|
||||
@Override
|
||||
public Iterator<Entry<K, V>> iterator() {
|
||||
return Iterators.transform(keys.get().iterator(), key -> new Entry<>() {
|
||||
@Override
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getValue() {
|
||||
return get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V setValue(V value) {
|
||||
return put(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return keys.get().size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
|
||||
for(K location : keys.get()) {
|
||||
/*
|
||||
* Fetching every model is insanely slow. So we call the function with a null object first, since it
|
||||
* probably isn't expecting that. If we get an exception thrown, or it returns nonnull, then we know
|
||||
* it actually cares about the given model.
|
||||
*/
|
||||
boolean needsReplacement;
|
||||
try {
|
||||
needsReplacement = function.apply(location, null) != null;
|
||||
} catch(Throwable e) {
|
||||
needsReplacement = true;
|
||||
}
|
||||
if(needsReplacement) {
|
||||
V existing = get(location);
|
||||
V replacement = function.apply(location, existing);
|
||||
if(replacement != existing) {
|
||||
put(location, replacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<BlockStateModelLoader.LoadedModels> loadBlockStateDefinition(ResourceLocation location) {
|
||||
StateDefinition<Block, BlockState> stateDefinition = this.stateMapper.apply(location);
|
||||
if(stateDefinition == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (DEBUG_DYNAMIC_MODEL_LOADING) {
|
||||
ModernFix.LOGGER.info("Loading blockstate definition '{}'", location);
|
||||
}
|
||||
List<Resource> resources = resourceManager.getResourceStack(ResourceLocation.fromNamespaceAndPath(location.getNamespace(), "blockstates/" + location.getPath() + ".json"));
|
||||
List<BlockStateModelLoader.LoadedBlockModelDefinition> loadedDefinitions = new ArrayList<>(resources.size());
|
||||
for(Resource resource : resources) {
|
||||
try(Reader reader = resource.openAsReader()) {
|
||||
JsonObject jsonObject = GsonHelper.parse(reader);
|
||||
BlockModelDefinition blockModelDefinition = BlockModelDefinition.fromJsonElement(jsonObject);
|
||||
loadedDefinitions.add(new BlockStateModelLoader.LoadedBlockModelDefinition(resource.sourcePackId(), blockModelDefinition));
|
||||
} catch(Exception e) {
|
||||
ModernFix.LOGGER.error("Failed to load blockstate definition {} from pack '{}'", location, resource.sourcePackId(), e);
|
||||
}
|
||||
}
|
||||
var loadedModels = new HashMap<>(BlockStateModelLoader.loadBlockStateDefinitionStack(location, stateDefinition, loadedDefinitions, this.unbakedMissingModel).models());
|
||||
if (!pluginList.isEmpty()) {
|
||||
loadedModels.replaceAll((mrl, oldModel) -> {
|
||||
UnbakedBlockStateModel ubm = oldModel.model();
|
||||
for (var plugin : pluginList) {
|
||||
ubm = plugin.modifyBlockModelOnLoad(ubm, mrl, oldModel.state());
|
||||
}
|
||||
if (ubm == oldModel.model()) {
|
||||
return oldModel;
|
||||
} else {
|
||||
return new BlockStateModelLoader.LoadedModel(oldModel.state(), ubm);
|
||||
}
|
||||
});
|
||||
}
|
||||
return Optional.of(new BlockStateModelLoader.LoadedModels(loadedModels));
|
||||
}
|
||||
|
||||
private BakedModel bakeMissingModel() {
|
||||
this.resolver.clearResolver();
|
||||
this.unbakedMissingModel.resolveDependencies(this.resolver);
|
||||
var modelBaker = new DynamicBaker(() -> "missing");
|
||||
return UnbakedModel.bakeWithTopModelValues(this.unbakedMissingModel, modelBaker, BlockModelRotation.X0_Y0);
|
||||
}
|
||||
|
||||
private BakedModel bakeModel(UnbakedModel model, ResourceLocation location) {
|
||||
if (DEBUG_DYNAMIC_MODEL_LOADING) {
|
||||
ModernFix.LOGGER.info("Baking model '{}'", location);
|
||||
}
|
||||
synchronized (this) {
|
||||
this.resolver.clearResolver();
|
||||
model.resolveDependencies(this.resolver);
|
||||
var modelBaker = new DynamicBaker(location::toString);
|
||||
for (var plugin : pluginList) {
|
||||
model = plugin.modifyModelBeforeBake(model, location, BlockModelRotation.X0_Y0, modelBaker);
|
||||
}
|
||||
var bakedModel = UnbakedModel.bakeWithTopModelValues(model, modelBaker, BlockModelRotation.X0_Y0);
|
||||
for (var plugin : pluginList) {
|
||||
bakedModel = plugin.modifyModelAfterBake(bakedModel, model, location, BlockModelRotation.X0_Y0, modelBaker);
|
||||
}
|
||||
return bakedModel;
|
||||
}
|
||||
}
|
||||
|
||||
private BakedModel bakeModel(UnbakedBlockStateModel model, ModelResourceLocation mrl) {
|
||||
if (DEBUG_DYNAMIC_MODEL_LOADING) {
|
||||
ModernFix.LOGGER.info("Baking model '{}'", mrl);
|
||||
}
|
||||
synchronized (this) {
|
||||
this.resolver.clearResolver();
|
||||
model.resolveDependencies(this.resolver);
|
||||
var modelBaker = new DynamicBaker(mrl::toString);
|
||||
for (var plugin : pluginList) {
|
||||
model = plugin.modifyBlockModelBeforeBake(model, mrl, modelBaker);
|
||||
}
|
||||
var bakedModel = model.bake(modelBaker);
|
||||
for (var plugin : pluginList) {
|
||||
bakedModel = plugin.modifyBlockModelAfterBake(bakedModel, model, mrl, modelBaker);
|
||||
}
|
||||
return bakedModel;
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<BakedModel> loadBakedModel(ModelResourceLocation location) {
|
||||
var override = this.mrlModelOverrides.get(location);
|
||||
if (override != null) {
|
||||
return Optional.of(override);
|
||||
}
|
||||
if (location.variant().equals("standalone") || location.variant().equals("fabric_resource")) {
|
||||
return this.loadStandaloneModel(location.id());
|
||||
} else {
|
||||
Optional<UnbakedBlockStateModel> unbakedModelOpt = Optional.ofNullable(this.unbakedBlockStateModelOverrides.get(location));
|
||||
if (unbakedModelOpt.isEmpty()) {
|
||||
var optLoadedModels = this.loadedStateDefinitions.getUnchecked(location.id());
|
||||
unbakedModelOpt = optLoadedModels.map(loadedModels -> {
|
||||
var loadedModel = loadedModels.models().get(location);
|
||||
if(loadedModel != null) {
|
||||
return loadedModel.model();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
return unbakedModelOpt.map(unbakedModel -> {
|
||||
return this.bakeModel(unbakedModel, location);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<BakedModel> loadStandaloneModel(ResourceLocation location) {
|
||||
var override = this.standaloneModelOverrides.get(location);
|
||||
if (override != null) {
|
||||
return Optional.of(override);
|
||||
}
|
||||
return this.loadedBlockModels.getUnchecked(location).map(unbakedModel -> {
|
||||
return this.bakeModel(unbakedModel, location);
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<UnbakedModel> loadBlockModelDefault(ResourceLocation location) {
|
||||
if (DEBUG_DYNAMIC_MODEL_LOADING) {
|
||||
ModernFix.LOGGER.info("Loading block model '{}'", location);
|
||||
}
|
||||
if (location.equals(ItemModelGenerator.GENERATED_ITEM_MODEL_ID)) {
|
||||
return Optional.of(this.itemModelGenerator);
|
||||
} else if (location.equals(MissingBlockModel.LOCATION)) {
|
||||
return Optional.of(this.unbakedMissingModel);
|
||||
}
|
||||
var resource = this.resourceManager.getResource(ResourceLocation.fromNamespaceAndPath(location.getNamespace(), "models/" + location.getPath() + ".json"));
|
||||
if(resource.isPresent()) {
|
||||
try(Reader reader = resource.get().openAsReader()) {
|
||||
BlockModel blockModel = BlockModel.fromStream(reader);
|
||||
return Optional.of(blockModel);
|
||||
} catch(Exception e) {
|
||||
ModernFix.LOGGER.error("Failed to load block model {} from '{}'", location, resource.get().sourcePackId(), e);
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
ModernFix.LOGGER.warn("Model '{}' does not exist in any resource packs", location);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<UnbakedModel> loadBlockModel(ResourceLocation location) {
|
||||
Optional<UnbakedModel> value = loadBlockModelDefault(location);
|
||||
for (var plugin : this.pluginList) {
|
||||
value = plugin.modifyModelOnLoad(value, location);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
private Optional<ClientItem> loadClientItemProperties(ResourceLocation location) {
|
||||
if (DEBUG_DYNAMIC_MODEL_LOADING) {
|
||||
ModernFix.LOGGER.info("Loading client item '{}'", location);
|
||||
}
|
||||
var resource = this.resourceManager.getResource(ResourceLocation.fromNamespaceAndPath(location.getNamespace(), "items/" + location.getPath() + ".json"));
|
||||
if(resource.isPresent()) {
|
||||
try(Reader reader = resource.get().openAsReader()) {
|
||||
ClientItem clientItem = ClientItem.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).getOrThrow();
|
||||
return Optional.of(clientItem);
|
||||
} catch(Exception e) {
|
||||
ModernFix.LOGGER.error("Failed to load client item {} from '{}'", location, resource.get().sourcePackId(), e);
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
ModernFix.LOGGER.warn("Client item '{}' does not exist in any resource packs", location);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<ItemModel> loadItemModel(ResourceLocation location) {
|
||||
if (DEBUG_DYNAMIC_MODEL_LOADING) {
|
||||
ModernFix.LOGGER.info("Loading item model '{}'", location);
|
||||
}
|
||||
var override = this.itemStackModelOverrides.get(location);
|
||||
if (override != null) {
|
||||
return Optional.of(override);
|
||||
}
|
||||
return this.loadedClientItemProperties.getUnchecked(location).map(clientItem -> {
|
||||
var bakingContext = new ItemModel.BakingContext(new DynamicBaker(location::toString), this.entityModelSet, this.missingItemModel);
|
||||
return clientItem.model().bake(bakingContext);
|
||||
});
|
||||
}
|
||||
|
||||
public BakedModel getModel(ModelResourceLocation location) {
|
||||
return this.loadedBakedModels.getUnchecked(location).orElse(this.missingModel);
|
||||
}
|
||||
|
||||
public ClientItem.Properties getClientItemProperties(ResourceLocation location) {
|
||||
return this.loadedClientItemProperties.getUnchecked(location).map(ClientItem::properties).orElse(ClientItem.Properties.DEFAULT);
|
||||
}
|
||||
|
||||
public ItemModel getItemModel(ResourceLocation location) {
|
||||
return this.loadedItemModels.getUnchecked(location).orElse(this.missingItemModel);
|
||||
}
|
||||
|
||||
public BakedModel getStandaloneModel(ResourceLocation location) {
|
||||
return this.loadedStandaloneModels.getUnchecked(location).orElse(this.missingModel);
|
||||
}
|
||||
|
||||
public void addUnbakedBlockStateOverride(ModelResourceLocation location, UnbakedBlockStateModel model) {
|
||||
this.unbakedBlockStateModelOverrides.put(location, model);
|
||||
}
|
||||
|
||||
private class DynamicBaker implements ModelBaker {
|
||||
private final ModelDebugName modelDebugName;
|
||||
|
||||
private DynamicBaker(ModelDebugName modelDebugName) {
|
||||
this.modelDebugName = modelDebugName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel bake(ResourceLocation location, ModelState transform) {
|
||||
return DynamicModelProvider.this.loadedBlockModels.getUnchecked(location).map(unbakedModel -> {
|
||||
DynamicModelProvider.this.resolver.clearResolver();
|
||||
unbakedModel.resolveDependencies(DynamicModelProvider.this.resolver);
|
||||
return UnbakedModel.bakeWithTopModelValues(unbakedModel, this, transform);
|
||||
}).orElse(DynamicModelProvider.this.missingModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpriteGetter sprites() {
|
||||
return DynamicModelProvider.this.textureGetter.bind(this.modelDebugName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelDebugName rootName() {
|
||||
return this.modelDebugName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the Mojang impl but with some changes to make it slightly more efficient.
|
||||
*/
|
||||
private class DynamicResolver implements UnbakedModel.Resolver {
|
||||
private final Set<ResourceLocation> stack = new ObjectOpenHashSet<>(4);
|
||||
private final Set<ResourceLocation> resolvedModels = new ObjectOpenHashSet<>();
|
||||
|
||||
@Override
|
||||
public UnbakedModel resolve(ResourceLocation resourceLocation) {
|
||||
if (this.stack.contains(resourceLocation)) {
|
||||
ModernFix.LOGGER.warn("Detected model loading loop: {}->{}", this.stacktraceToString(), resourceLocation);
|
||||
return DynamicModelProvider.this.unbakedMissingModel;
|
||||
} else {
|
||||
UnbakedModel unbakedModel = DynamicModelProvider.this.loadedBlockModels.getUnchecked(resourceLocation).orElse(DynamicModelProvider.this.unbakedMissingModel);
|
||||
if (this.resolvedModels.add(resourceLocation)) {
|
||||
this.stack.add(resourceLocation);
|
||||
unbakedModel.resolveDependencies(this);
|
||||
this.stack.remove(resourceLocation);
|
||||
}
|
||||
|
||||
return unbakedModel;
|
||||
}
|
||||
}
|
||||
|
||||
private String stacktraceToString() {
|
||||
return this.stack.stream().map(ResourceLocation::toString).collect(Collectors.joining("->"));
|
||||
}
|
||||
|
||||
public void clearResolver() {
|
||||
this.stack.clear();
|
||||
this.resolvedModels.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static WeakReference<DynamicModelProvider> currentReloadingModelProvider = new WeakReference<>(null);
|
||||
|
||||
public interface ModelManagerExtension {
|
||||
DynamicModelProvider mfix$getModelProvider();
|
||||
}
|
||||
|
||||
public interface DynamicModelPlugin {
|
||||
Optional<UnbakedModel> modifyModelOnLoad(Optional<UnbakedModel> model, ResourceLocation id);
|
||||
UnbakedBlockStateModel modifyBlockModelOnLoad(UnbakedBlockStateModel model, ModelResourceLocation id, BlockState state);
|
||||
|
||||
UnbakedModel modifyModelBeforeBake(UnbakedModel model, ResourceLocation id, ModelState state, ModelBaker baker);
|
||||
BakedModel modifyModelAfterBake(BakedModel bakedModel, UnbakedModel model, ResourceLocation id, ModelState state, ModelBaker baker);
|
||||
|
||||
UnbakedBlockStateModel modifyBlockModelBeforeBake(UnbakedBlockStateModel model, ModelResourceLocation id, ModelBaker baker);
|
||||
BakedModel modifyBlockModelAfterBake(BakedModel bakedModel, UnbakedBlockStateModel model, ModelResourceLocation id, ModelBaker baker);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,280 @@
|
|||
package org.embeddedt.modernfix.dynamicresources;
|
||||
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.BlockStateResolver;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier;
|
||||
import net.fabricmc.fabric.api.event.Event;
|
||||
import net.fabricmc.fabric.api.event.EventFactory;
|
||||
import net.minecraft.client.renderer.block.BlockModelShaper;
|
||||
import net.minecraft.client.renderer.block.model.UnbakedBlockStateModel;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
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 net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class FabricDynamicModelHandler implements DynamicModelProvider.DynamicModelPlugin {
|
||||
private final List<ModelLoadingPlugin> pluginList;
|
||||
|
||||
// Borrowed from Fabric API, this dispatching logic is extremely trivial
|
||||
|
||||
private static final ResourceLocation[] MODEL_MODIFIER_PHASES = new ResourceLocation[] { ModelModifier.OVERRIDE_PHASE, ModelModifier.DEFAULT_PHASE, ModelModifier.WRAP_PHASE, ModelModifier.WRAP_LAST_PHASE };
|
||||
|
||||
private final Event<ModelModifier.OnLoad> onLoadModifiers = EventFactory.createWithPhases(ModelModifier.OnLoad.class, modifiers -> (model, context) -> {
|
||||
for (ModelModifier.OnLoad modifier : modifiers) {
|
||||
try {
|
||||
model = modifier.modifyModelOnLoad(model, context);
|
||||
} catch (Exception exception) {
|
||||
ModernFix.LOGGER.error("Failed to modify unbaked model on load", exception);
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}, MODEL_MODIFIER_PHASES);
|
||||
|
||||
private final Event<ModelModifier.OnLoadBlock> onLoadBlockModifiers = EventFactory.createWithPhases(ModelModifier.OnLoadBlock.class, modifiers -> (model, context) -> {
|
||||
for (ModelModifier.OnLoadBlock modifier : modifiers) {
|
||||
try {
|
||||
model = modifier.modifyModelOnLoad(model, context);
|
||||
} catch (Exception exception) {
|
||||
ModernFix.LOGGER.error("Failed to modify unbaked block model on load", exception);
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}, MODEL_MODIFIER_PHASES);
|
||||
|
||||
private final Event<ModelModifier.BeforeBakeBlock> beforeBakeBlockModifiers = EventFactory.createWithPhases(ModelModifier.BeforeBakeBlock.class, modifiers -> (model, context) -> {
|
||||
for (ModelModifier.BeforeBakeBlock modifier : modifiers) {
|
||||
try {
|
||||
model = modifier.modifyModelBeforeBake(model, context);
|
||||
} catch (Exception exception) {
|
||||
ModernFix.LOGGER.error("Failed to modify unbaked block model before bake", exception);
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}, MODEL_MODIFIER_PHASES);
|
||||
private final Event<ModelModifier.AfterBakeBlock> afterBakeBlockModifiers = EventFactory.createWithPhases(ModelModifier.AfterBakeBlock.class, modifiers -> (model, context) -> {
|
||||
for (ModelModifier.AfterBakeBlock modifier : modifiers) {
|
||||
try {
|
||||
model = modifier.modifyModelAfterBake(model, context);
|
||||
} catch (Exception exception) {
|
||||
ModernFix.LOGGER.error("Failed to modify baked block model after bake", exception);
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}, MODEL_MODIFIER_PHASES);
|
||||
private final Event<ModelModifier.BeforeBake> beforeBakeModifiers = EventFactory.createWithPhases(ModelModifier.BeforeBake.class, modifiers -> (model, context) -> {
|
||||
for (ModelModifier.BeforeBake modifier : modifiers) {
|
||||
try {
|
||||
model = modifier.modifyModelBeforeBake(model, context);
|
||||
} catch (Exception exception) {
|
||||
ModernFix.LOGGER.error("Failed to modify unbaked model before bake", exception);
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}, MODEL_MODIFIER_PHASES);
|
||||
private final Event<ModelModifier.AfterBake> afterBakeModifiers = EventFactory.createWithPhases(ModelModifier.AfterBake.class, modifiers -> (model, context) -> {
|
||||
for (ModelModifier.AfterBake modifier : modifiers) {
|
||||
try {
|
||||
model = modifier.modifyModelAfterBake(model, context);
|
||||
} catch (Exception exception) {
|
||||
ModernFix.LOGGER.error("Failed to modify baked model after bake", exception);
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}, MODEL_MODIFIER_PHASES);
|
||||
|
||||
public FabricDynamicModelHandler(DynamicModelProvider provider) {
|
||||
this.pluginList = ModelLoadingPlugin.getAll();
|
||||
var context = new PluginContext(provider);
|
||||
for (var plugin : this.pluginList) {
|
||||
plugin.initialize(context);
|
||||
}
|
||||
context.fireResolvers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<UnbakedModel> modifyModelOnLoad(Optional<UnbakedModel> model, ResourceLocation id) {
|
||||
return Optional.ofNullable(this.onLoadModifiers.invoker().modifyModelOnLoad(model.orElse(null), () -> id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnbakedBlockStateModel modifyBlockModelOnLoad(UnbakedBlockStateModel model, ModelResourceLocation id, BlockState state) {
|
||||
return this.onLoadBlockModifiers.invoker().modifyModelOnLoad(model, new ModelModifier.OnLoadBlock.Context() {
|
||||
@Override
|
||||
public ModelResourceLocation id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState state() {
|
||||
return state;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnbakedModel modifyModelBeforeBake(UnbakedModel model, ResourceLocation id, ModelState state, ModelBaker baker) {
|
||||
return beforeBakeModifiers.invoker().modifyModelBeforeBake(model, new ModelModifier.BeforeBake.Context() {
|
||||
@Override
|
||||
public ResourceLocation id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelState settings() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelBaker baker() {
|
||||
return baker;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel modifyModelAfterBake(BakedModel bakedModel, UnbakedModel model, ResourceLocation id, ModelState state, ModelBaker baker) {
|
||||
return afterBakeModifiers.invoker().modifyModelAfterBake(bakedModel, new ModelModifier.AfterBake.Context() {
|
||||
@Override
|
||||
public ResourceLocation id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnbakedModel sourceModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelState settings() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelBaker baker() {
|
||||
return baker;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnbakedBlockStateModel modifyBlockModelBeforeBake(UnbakedBlockStateModel model, ModelResourceLocation id, ModelBaker baker) {
|
||||
return beforeBakeBlockModifiers.invoker().modifyModelBeforeBake(model, new ModelModifier.BeforeBakeBlock.Context() {
|
||||
@Override
|
||||
public ModelResourceLocation id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelBaker baker() {
|
||||
return baker;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel modifyBlockModelAfterBake(BakedModel bakedModel, UnbakedBlockStateModel model, ModelResourceLocation id, ModelBaker baker) {
|
||||
return afterBakeBlockModifiers.invoker().modifyModelAfterBake(bakedModel, new ModelModifier.AfterBakeBlock.Context() {
|
||||
@Override
|
||||
public ModelResourceLocation id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnbakedBlockStateModel sourceModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelBaker baker() {
|
||||
return baker;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class PluginContext implements ModelLoadingPlugin.Context {
|
||||
private final DynamicModelProvider provider;
|
||||
private final Map<Block, BlockStateResolver> resolvers = new HashMap<>();
|
||||
|
||||
private PluginContext(DynamicModelProvider provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addModels(ResourceLocation... ids) {
|
||||
/* no-op on dynamic model loader */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addModels(Collection<? extends ResourceLocation> ids) {
|
||||
/* no-op on dynamic model loader */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBlockStateResolver(Block block, BlockStateResolver resolver) {
|
||||
resolvers.put(block, resolver);
|
||||
}
|
||||
|
||||
public void fireResolvers() {
|
||||
resolvers.forEach((block, resolver) -> {
|
||||
resolver.resolveBlockStates(new BlockStateResolver.Context() {
|
||||
@Override
|
||||
public Block block() {
|
||||
return block;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(BlockState state, UnbakedBlockStateModel model) {
|
||||
provider.addUnbakedBlockStateOverride(BlockModelShaper.stateToModelLocation(state), model);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event<ModelModifier.OnLoad> modifyModelOnLoad() {
|
||||
return onLoadModifiers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event<ModelModifier.OnLoadBlock> modifyBlockModelOnLoad() {
|
||||
return onLoadBlockModifiers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event<ModelModifier.BeforeBake> modifyModelBeforeBake() {
|
||||
return beforeBakeModifiers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event<ModelModifier.AfterBake> modifyModelAfterBake() {
|
||||
return afterBakeModifiers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event<ModelModifier.BeforeBakeBlock> modifyBlockModelBeforeBake() {
|
||||
return beforeBakeBlockModifiers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event<ModelModifier.AfterBakeBlock> modifyBlockModelAfterBake() {
|
||||
return afterBakeBlockModifiers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,58 +2,15 @@ package org.embeddedt.modernfix.dynamicresources;
|
|||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
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 net.minecraft.world.level.block.state.properties.Property;
|
||||
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class ModelBakeryHelpers {
|
||||
/**
|
||||
* The maximum number of baked models kept in memory at once.
|
||||
*/
|
||||
public static final int MAX_BAKED_MODEL_COUNT = 10000;
|
||||
/**
|
||||
* The maximum number of unbaked models kept in memory at once.
|
||||
*/
|
||||
public static final int MAX_UNBAKED_MODEL_COUNT = 10000;
|
||||
/**
|
||||
* The time in seconds after which a model becomes eligible for eviction if not used.
|
||||
*/
|
||||
public static final int MAX_MODEL_LIFETIME_SECS = 300;
|
||||
|
||||
/**
|
||||
* These folders will have all textures stitched onto the atlas when dynamic resources is enabled.
|
||||
*/
|
||||
public static String[] getExtraTextureFolders() {
|
||||
return new String[] {
|
||||
"attachment",
|
||||
"bettergrass",
|
||||
"block",
|
||||
"blocks",
|
||||
"cape",
|
||||
"entity/bed",
|
||||
"entity/chest",
|
||||
"item",
|
||||
"items",
|
||||
"model",
|
||||
"models",
|
||||
"part",
|
||||
"pipe",
|
||||
"ropebridge",
|
||||
"runes",
|
||||
"solid_block",
|
||||
"spell_effect",
|
||||
"spell_projectile"
|
||||
};
|
||||
}
|
||||
|
||||
private static <T extends Comparable<T>, V extends T> BlockState setPropertyGeneric(BlockState state, Property<T> prop, Object o) {
|
||||
return state.setValue(prop, (V)o);
|
||||
}
|
||||
|
|
@ -108,13 +65,4 @@ public class ModelBakeryHelpers {
|
|||
}
|
||||
return ImmutableList.copyOf(finalList);
|
||||
}
|
||||
|
||||
public static ModernFixClientIntegration bakedModelWrapper(BiFunction<ResourceLocation, Pair<UnbakedModel, BakedModel>, BakedModel> consumer) {
|
||||
return new ModernFixClientIntegration() {
|
||||
@Override
|
||||
public BakedModel onBakedModelLoad(ResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery) {
|
||||
return consumer.apply(location, Pair.of(baseModel, originalModel));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
package org.embeddedt.modernfix.dynamicresources;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockFaceUV;
|
||||
|
||||
public class UVController {
|
||||
public static final ThreadLocal<Boolean> useDummyUv = ThreadLocal.withInitial(() -> Boolean.FALSE);
|
||||
public static final BlockFaceUV dummyUv = new BlockFaceUV(new float[4], 0);
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package org.embeddedt.modernfix.entity;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.packet.EntityIDSyncPacket;
|
||||
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class EntityDataIDSyncHandler {
|
||||
private static Map<Class<? extends Entity>, List<Pair<String, Integer>>> fieldsToSyncMap;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void onDatapackSyncEvent(ServerPlayer targetPlayer) {
|
||||
if(targetPlayer != null) {
|
||||
/* Compute the current set of serializer IDs in use and send them */
|
||||
if(fieldsToSyncMap == null) {
|
||||
fieldsToSyncMap = new HashMap<>();
|
||||
Map<Class<? extends Entity>, Integer> entityPoolMap = SynchedEntityData.ENTITY_ID_POOL;
|
||||
List<Field> fieldsToSync = new ArrayList<>();
|
||||
for(Class<? extends Entity> eClass : entityPoolMap.keySet()) {
|
||||
fieldsToSync.clear();
|
||||
try {
|
||||
Field[] classFields = eClass.getDeclaredFields();
|
||||
for(Field field : classFields) {
|
||||
if(!Modifier.isStatic(field.getModifiers()))
|
||||
continue;
|
||||
field.setAccessible(true);
|
||||
Object o = field.get(null);
|
||||
if(o != null && EntityDataAccessor.class.isAssignableFrom(o.getClass())) {
|
||||
fieldsToSync.add(field);
|
||||
}
|
||||
}
|
||||
for(Field field : fieldsToSync) {
|
||||
int id = ((EntityDataAccessor<?>)field.get(null)).id;
|
||||
fieldsToSyncMap.computeIfAbsent(eClass, k -> new ArrayList<>()).add(Pair.of(field.getName(), id));
|
||||
}
|
||||
} catch(Throwable e) {
|
||||
ModernFix.LOGGER.error("Skipping entity ID sync for {}: {}", eClass.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
EntityIDSyncPacket packet = new EntityIDSyncPacket(fieldsToSyncMap);
|
||||
ModernFix.LOGGER.debug("Sending ID correction packet to client with " + fieldsToSyncMap.size() + " classes");
|
||||
ModernFixPlatformHooks.INSTANCE.sendPacket(targetPlayer, packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,9 +17,9 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class EntityRendererMap implements Map<EntityType<?>, EntityRenderer<?>> {
|
||||
public class EntityRendererMap implements Map<EntityType<?>, EntityRenderer<?, ?>> {
|
||||
private final Map<EntityType<?>, EntityRendererProvider<?>> rendererProviders;
|
||||
private final LoadingCache<EntityType<?>, EntityRenderer<?>> rendererMap;
|
||||
private final LoadingCache<EntityType<?>, EntityRenderer<?, ?>> rendererMap;
|
||||
private final EntityRendererProvider.Context context;
|
||||
|
||||
public EntityRendererMap(Map<EntityType<?>, EntityRendererProvider<?>> rendererProviders, EntityRendererProvider.Context context) {
|
||||
|
|
@ -28,12 +28,12 @@ public class EntityRendererMap implements Map<EntityType<?>, EntityRenderer<?>>
|
|||
this.rendererMap = CacheBuilder.newBuilder().build(new RenderConstructor());
|
||||
}
|
||||
|
||||
class RenderConstructor extends CacheLoader<EntityType<?>, EntityRenderer<?>> {
|
||||
class RenderConstructor extends CacheLoader<EntityType<?>, EntityRenderer<?, ?>> {
|
||||
@Override
|
||||
public EntityRenderer<?> load(EntityType<?> key) throws Exception {
|
||||
public EntityRenderer<?, ?> load(EntityType<?> key) throws Exception {
|
||||
EntityRendererProvider<?> provider = rendererProviders.get(key);
|
||||
synchronized(EntityRenderers.class) {
|
||||
EntityRenderer<?> renderer;
|
||||
EntityRenderer<?, ?> renderer;
|
||||
try {
|
||||
if(provider == null)
|
||||
throw new RuntimeException("Provider not registered");
|
||||
|
|
@ -69,9 +69,9 @@ public class EntityRendererMap implements Map<EntityType<?>, EntityRenderer<?>>
|
|||
}
|
||||
|
||||
@Override
|
||||
public EntityRenderer<?> get(Object o) {
|
||||
public EntityRenderer<?, ?> get(Object o) {
|
||||
try {
|
||||
EntityRenderer<?> renderer = rendererMap.get((EntityType<?>)o);
|
||||
EntityRenderer<?, ?> renderer = rendererMap.get((EntityType<?>)o);
|
||||
if(renderer == null)
|
||||
throw new AssertionError("Returned entity renderer should never be null");
|
||||
return renderer;
|
||||
|
|
@ -84,21 +84,21 @@ public class EntityRendererMap implements Map<EntityType<?>, EntityRenderer<?>>
|
|||
|
||||
@Nullable
|
||||
@Override
|
||||
public EntityRenderer<?> put(EntityType<?> entityType, EntityRenderer<?> entityRenderer) {
|
||||
EntityRenderer<?> old = rendererMap.getIfPresent(entityType);
|
||||
public EntityRenderer<?, ?> put(EntityType<?> entityType, EntityRenderer<?, ?> entityRenderer) {
|
||||
EntityRenderer<?, ?> old = rendererMap.getIfPresent(entityType);
|
||||
rendererMap.put(entityType, entityRenderer);
|
||||
return old;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityRenderer<?> remove(Object o) {
|
||||
EntityRenderer<?> r = rendererMap.getIfPresent(o);
|
||||
public EntityRenderer<?, ?> remove(Object o) {
|
||||
EntityRenderer<?, ?> r = rendererMap.getIfPresent(o);
|
||||
rendererMap.invalidate(o);
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(@NotNull Map<? extends EntityType<?>, ? extends EntityRenderer<?>> map) {
|
||||
public void putAll(@NotNull Map<? extends EntityType<?>, ? extends EntityRenderer<?, ?>> map) {
|
||||
rendererMap.putAll(map);
|
||||
}
|
||||
|
||||
|
|
@ -115,13 +115,13 @@ public class EntityRendererMap implements Map<EntityType<?>, EntityRenderer<?>>
|
|||
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<EntityRenderer<?>> values() {
|
||||
public Collection<EntityRenderer<?, ?>> values() {
|
||||
return rendererMap.asMap().values();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<Map.Entry<EntityType<?>, EntityRenderer<?>>> entrySet() {
|
||||
public Set<Map.Entry<EntityType<?>, EntityRenderer<?, ?>>> entrySet() {
|
||||
return rendererMap.asMap().entrySet();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,26 +5,25 @@ import net.minecraft.client.renderer.MultiBufferSource;
|
|||
import net.minecraft.client.renderer.culling.Frustum;
|
||||
import net.minecraft.client.renderer.entity.EntityRenderer;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlas;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.client.renderer.entity.state.EntityRenderState;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
|
||||
public class ErroredEntityRenderer<T extends Entity> extends EntityRenderer<T> {
|
||||
public class ErroredEntityRenderer<T extends Entity> extends EntityRenderer<T, EntityRenderState> {
|
||||
public ErroredEntityRenderer(EntityRendererProvider.Context arg) {
|
||||
super(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTextureLocation(T entity) {
|
||||
return TextureAtlas.LOCATION_BLOCKS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRender(T livingEntity, Frustum camera, double camX, double camY, double camZ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(T entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight) {
|
||||
public EntityRenderState createRenderState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(EntityRenderState entityRenderState, PoseStack poseStack, MultiBufferSource multiBufferSource, int i) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,78 +0,0 @@
|
|||
package org.embeddedt.modernfix.packet;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
public class EntityIDSyncPacket {
|
||||
private Map<Class<? extends Entity>, List<Pair<String, Integer>>> map;
|
||||
|
||||
public EntityIDSyncPacket(Map<Class<? extends Entity>, List<Pair<String, Integer>>> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public Map<Class<? extends Entity>, List<Pair<String, Integer>>> getFieldInfo() {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
public EntityIDSyncPacket() {
|
||||
this.map = new HashMap<>();
|
||||
}
|
||||
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
buf.writeVarInt(map.keySet().size());
|
||||
for(Map.Entry<Class<? extends Entity>, List<Pair<String, Integer>>> entry : map.entrySet()) {
|
||||
buf.writeUtf(entry.getKey().getName());
|
||||
buf.writeVarInt(entry.getValue().size());
|
||||
for(Pair<String, Integer> field : entry.getValue()) {
|
||||
buf.writeUtf(field.getFirst());
|
||||
buf.writeVarInt(field.getSecond());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static EntityIDSyncPacket deserialize(FriendlyByteBuf buf) {
|
||||
EntityIDSyncPacket self = new EntityIDSyncPacket();
|
||||
int numEntityClasses = buf.readVarInt();
|
||||
for(int i = 0; i < numEntityClasses; i++) {
|
||||
String clzName = buf.readUtf();
|
||||
try {
|
||||
Class<?> clz;
|
||||
try {
|
||||
clz = Class.forName(clzName);
|
||||
} catch(ClassNotFoundException e) {
|
||||
ModernFix.LOGGER.warn("Entity class not found: {}", clzName);
|
||||
break;
|
||||
}
|
||||
if(!Entity.class.isAssignableFrom(clz)) {
|
||||
ModernFix.LOGGER.error("Not an entity: " + clzName);
|
||||
break;
|
||||
}
|
||||
int numFields = buf.readVarInt();
|
||||
for(int j = 0; j < numFields; j++) {
|
||||
String fieldName = buf.readUtf();
|
||||
int id = buf.readVarInt();
|
||||
Field f = clz.getDeclaredField(fieldName);
|
||||
if(!Modifier.isStatic(f.getModifiers()))
|
||||
continue;
|
||||
f.setAccessible(true);
|
||||
if(!EntityDataAccessor.class.isAssignableFrom(f.get(null).getClass())) {
|
||||
ModernFix.LOGGER.error("Not a data accessor field: " + clz + "." + fieldName);
|
||||
continue;
|
||||
}
|
||||
self.map.computeIfAbsent((Class<? extends Entity>)clz, k -> new ArrayList<>()).add(Pair.of(fieldName, id));
|
||||
}
|
||||
} catch(ReflectiveOperationException e) {
|
||||
ModernFix.LOGGER.error("Error deserializing packet", e);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,16 +2,13 @@ package org.embeddedt.modernfix.platform;
|
|||
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import net.minecraft.client.searchtree.SearchRegistry;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface ModernFixPlatformHooks {
|
||||
|
|
@ -39,7 +36,7 @@ public interface ModernFixPlatformHooks {
|
|||
|
||||
Path getGameDirectory();
|
||||
|
||||
void sendPacket(ServerPlayer player, Object packet);
|
||||
void sendPacket(ServerPlayer player, CustomPacketPayload packet);
|
||||
|
||||
Multimap<String, String> getCustomModOptions();
|
||||
|
||||
|
|
@ -47,7 +44,5 @@ public interface ModernFixPlatformHooks {
|
|||
|
||||
void onLaunchComplete();
|
||||
|
||||
void registerCreativeSearchTrees(SearchRegistry registry, SearchRegistry.TreeBuilderSupplier<ItemStack> nameSupplier, SearchRegistry.TreeBuilderSupplier<ItemStack> tagSupplier, BiConsumer<SearchRegistry.Key<ItemStack>, List<ItemStack>> populator);
|
||||
|
||||
String getPlatformName();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import java.lang.reflect.Constructor;
|
|||
|
||||
class PlatformHookLoader {
|
||||
static ModernFixPlatformHooks findInstance() {
|
||||
String[] locations = new String[] { "forge", "fabric" };
|
||||
String[] locations = new String[] { "neoforge", "fabric" };
|
||||
for(String location : locations) {
|
||||
try {
|
||||
Class<?> clz = Class.forName("org.embeddedt.modernfix.platform." + location + ".ModernFixPlatformHooksImpl");
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
package org.embeddedt.modernfix.registry;
|
||||
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
||||
import net.minecraft.core.RegistrationInfo;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
|
||||
public class LifecycleMap<T> extends Reference2ReferenceOpenHashMap<T, Lifecycle> {
|
||||
public class LifecycleMap<T> extends Reference2ReferenceOpenHashMap<ResourceKey<T>, RegistrationInfo> {
|
||||
public LifecycleMap() {
|
||||
this.defaultReturnValue(Lifecycle.stable());
|
||||
this.defaultReturnValue(RegistrationInfo.BUILT_IN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lifecycle put(T t, Lifecycle lifecycle) {
|
||||
public RegistrationInfo put(ResourceKey<T> t, RegistrationInfo lifecycle) {
|
||||
if(lifecycle != defRetValue)
|
||||
return super.put(t, lifecycle);
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package org.embeddedt.modernfix.render;
|
|||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.renderer.block.model.ItemOverrides;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransforms;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
|
|
@ -72,11 +71,6 @@ public class SimpleItemModelView implements BakedModel {
|
|||
return wrappedItem.usesBlockLight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCustomRenderer() {
|
||||
return wrappedItem.isCustomRenderer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextureAtlasSprite getParticleIcon() {
|
||||
return wrappedItem.getParticleIcon();
|
||||
|
|
@ -86,9 +80,5 @@ public class SimpleItemModelView implements BakedModel {
|
|||
public ItemTransforms getTransforms() {
|
||||
return wrappedItem.getTransforms();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemOverrides getOverrides() {
|
||||
return wrappedItem.getOverrides();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
package org.embeddedt.modernfix.render;
|
||||
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Helper that frees ByteBuffers allocated by BufferBuilders, and nulls out the address pointer
|
||||
* to prevent double frees.
|
||||
*
|
||||
* @author Moulberry
|
||||
*/
|
||||
public class UnsafeBufferHelper {
|
||||
private static final MemoryUtil.MemoryAllocator ALLOCATOR = MemoryUtil.getAllocator(false);
|
||||
|
||||
private static sun.misc.Unsafe UNSAFE = null;
|
||||
private static long ADDRESS = -1;
|
||||
|
||||
static {
|
||||
try {
|
||||
final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
|
||||
theUnsafe.setAccessible(true);
|
||||
UNSAFE = (Unsafe)theUnsafe.get(null);
|
||||
|
||||
final Field addressField = MemoryUtil.class.getDeclaredField("ADDRESS");
|
||||
addressField.setAccessible(true);
|
||||
ADDRESS = addressField.getLong(null);
|
||||
} catch(Throwable t) {
|
||||
ModernFix.LOGGER.error("Could load unsafe/buffer address", t);
|
||||
}
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
|
||||
}
|
||||
|
||||
public static void free(ByteBuffer buf) {
|
||||
if(UNSAFE != null && ADDRESS >= 0) {
|
||||
// set the address to 0 to prevent double free
|
||||
long address = UNSAFE.getAndSetLong(buf, ADDRESS, 0);
|
||||
if(address != 0) {
|
||||
ALLOCATOR.free(address);
|
||||
}
|
||||
} else {
|
||||
ALLOCATOR.free(MemoryUtil.memAddress0(buf));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -158,7 +158,7 @@ public class PackResourcesCacheEngine {
|
|||
if(!fullTestPath.startsWith(testPath)) {
|
||||
continue;
|
||||
}
|
||||
ResourceLocation foundResource = new ResourceLocation(resourceNamespace, fullPath);
|
||||
ResourceLocation foundResource = ResourceLocation.fromNamespaceAndPath(resourceNamespace, fullPath);
|
||||
if(!filter.test(foundResource))
|
||||
continue;
|
||||
resources.add(foundResource);
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
package org.embeddedt.modernfix.resources;
|
||||
|
||||
import net.minecraft.ReportType;
|
||||
import net.minecraft.ReportedException;
|
||||
import net.minecraft.TracingExecutor;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.ForkJoinWorkerThread;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class ReloadExecutor {
|
||||
public static ExecutorService createCustomResourceReloadExecutor() {
|
||||
public static TracingExecutor createCustomResourceReloadExecutor() {
|
||||
ClassLoader loader = ReloadExecutor.class.getClassLoader();
|
||||
AtomicInteger workerCount = new AtomicInteger(0);
|
||||
return new ForkJoinPool(ForkJoinPool.getCommonPoolParallelism(), (forkJoinPool) -> {
|
||||
return new TracingExecutor(new ForkJoinPool(ForkJoinPool.getCommonPoolParallelism(), (forkJoinPool) -> {
|
||||
ForkJoinWorkerThread forkJoinWorkerThread = new ForkJoinWorkerThread(forkJoinPool) {
|
||||
protected void onTermination(Throwable throwOnTermination) {
|
||||
if (throwOnTermination != null) {
|
||||
|
|
@ -30,7 +31,7 @@ public class ReloadExecutor {
|
|||
forkJoinWorkerThread.setContextClassLoader(loader);
|
||||
forkJoinWorkerThread.setName("Worker-ResourceReload-" + workerCount.getAndIncrement());
|
||||
return forkJoinWorkerThread;
|
||||
}, ReloadExecutor::handleException, true);
|
||||
}, ReloadExecutor::handleException, true));
|
||||
}
|
||||
|
||||
private static void handleException(Thread thread, Throwable throwable) {
|
||||
|
|
@ -39,7 +40,7 @@ public class ReloadExecutor {
|
|||
}
|
||||
|
||||
if (throwable instanceof ReportedException) {
|
||||
Bootstrap.realStdoutPrintln(((ReportedException)throwable).getReport().getFriendlyReport());
|
||||
Bootstrap.realStdoutPrintln(((ReportedException)throwable).getReport().getFriendlyReport(ReportType.CRASH));
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,11 +42,10 @@ public class ModernFixConfigScreen extends Screen {
|
|||
|
||||
@Override
|
||||
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) {
|
||||
this.renderBackground(guiGraphics);
|
||||
super.render(guiGraphics, mouseX, mouseY, partialTicks);
|
||||
this.optionList.render(guiGraphics, mouseX, mouseY, partialTicks);
|
||||
guiGraphics.drawCenteredString(this.font, this.title, this.width / 2, 8, 16777215);
|
||||
this.doneButton.setMessage(madeChanges ? Component.translatable("modernfix.config.done_restart") : CommonComponents.GUI_DONE);
|
||||
super.render(guiGraphics, mouseX, mouseY, partialTicks);
|
||||
}
|
||||
|
||||
public void setLastScrollAmount(double d) {
|
||||
|
|
|
|||
|
|
@ -41,9 +41,8 @@ public class ModernFixOptionInfoScreen extends Screen {
|
|||
|
||||
@Override
|
||||
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) {
|
||||
this.renderBackground(guiGraphics);
|
||||
super.render(guiGraphics, mouseX, mouseY, partialTicks);
|
||||
guiGraphics.drawCenteredString(this.font, this.title, this.width / 2, 8, 16777215);
|
||||
this.drawMultilineString(guiGraphics, this.minecraft.font, description, 10, 50);
|
||||
super.render(guiGraphics, mouseX, mouseY, partialTicks);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public class OptionList extends ContainerObjectSelectionList<OptionList.Entry> {
|
|||
}
|
||||
|
||||
public OptionList(ModernFixConfigScreen arg, Minecraft arg2) {
|
||||
super(arg2,arg.width + 45, arg.height, 43, arg.height - 32, 20);
|
||||
super(arg2,arg.width + 45, arg.height - 52, 20, 20);
|
||||
|
||||
this.mainScreen = arg;
|
||||
|
||||
|
|
@ -91,8 +91,9 @@ public class OptionList extends ContainerObjectSelectionList<OptionList.Entry> {
|
|||
}
|
||||
}
|
||||
|
||||
protected int getScrollbarPosition() {
|
||||
return super.getScrollbarPosition() + 15 + 20;
|
||||
@Override
|
||||
protected int scrollBarY() {
|
||||
return super.scrollBarY() + 15 + 20;
|
||||
}
|
||||
|
||||
public int getRowWidth() {
|
||||
|
|
@ -166,7 +167,7 @@ public class OptionList extends ContainerObjectSelectionList<OptionList.Entry> {
|
|||
}).tooltip(toggleTooltip).pos(0, 0).size(55, 20).build();
|
||||
updateStatus();
|
||||
this.helpButton = new Button.Builder(Component.literal("?"), (arg) -> {
|
||||
mainScreen.setLastScrollAmount(getScrollAmount());
|
||||
mainScreen.setLastScrollAmount(scrollAmount());
|
||||
Minecraft.getInstance().setScreen(new ModernFixOptionInfoScreen(mainScreen, optionName));
|
||||
}).pos(75, 0).size(20, 20).build();
|
||||
if(!I18n.exists("modernfix.option." + optionName)) {
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
package org.embeddedt.modernfix.searchtree;
|
||||
|
||||
import net.minecraft.client.searchtree.RefreshableSearchTree;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Dummy search tree that stores nothing and returns nothing on searches.
|
||||
*/
|
||||
public class DummySearchTree<T> implements RefreshableSearchTree<T> {
|
||||
public DummySearchTree() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> search(String pSearchText) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
static final SearchTreeProviderRegistry.Provider PROVIDER = new SearchTreeProviderRegistry.Provider() {
|
||||
|
||||
@Override
|
||||
public RefreshableSearchTree<ItemStack> getSearchTree(boolean tag) {
|
||||
return new DummySearchTree<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Dummy";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
package org.embeddedt.modernfix.searchtree;
|
||||
|
||||
import mezz.jei.api.ingredients.ITypedIngredient;
|
||||
import mezz.jei.gui.ingredients.IngredientFilter;
|
||||
import mezz.jei.gui.ingredients.IngredientFilterApi;
|
||||
import mezz.jei.library.runtime.JeiRuntime;
|
||||
import net.minecraft.client.searchtree.RefreshableSearchTree;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Uses JEI to handle search tree lookups.
|
||||
*/
|
||||
public class JEIBackedSearchTree extends DummySearchTree<ItemStack> {
|
||||
private final boolean filteringByTag;
|
||||
private String lastSearchText = "";
|
||||
private final List<ItemStack> listCache = new ArrayList<>();
|
||||
|
||||
private static final Field filterField;
|
||||
private static final MethodHandle getIngredientListUncached;
|
||||
|
||||
static {
|
||||
MethodHandle m;
|
||||
Field f;
|
||||
try {
|
||||
Method jeiMethod = IngredientFilter.class.getDeclaredMethod("getIngredientListUncached", String.class);
|
||||
jeiMethod.setAccessible(true);
|
||||
m = MethodHandles.lookup().unreflect(jeiMethod);
|
||||
f = IngredientFilterApi.class.getDeclaredField("ingredientFilter");
|
||||
f.setAccessible(true);
|
||||
} catch(ReflectiveOperationException | RuntimeException | NoClassDefFoundError e) {
|
||||
m = null;
|
||||
f = null;
|
||||
}
|
||||
getIngredientListUncached = m;
|
||||
filterField = f;
|
||||
}
|
||||
|
||||
public JEIBackedSearchTree(boolean filteringByTag) {
|
||||
this.filteringByTag = filteringByTag;
|
||||
}
|
||||
@Override
|
||||
public List<ItemStack> search(String pSearchText) {
|
||||
Optional<JeiRuntime> runtime = JEIRuntimeCapturer.runtime();
|
||||
if(runtime.isPresent()) {
|
||||
IngredientFilterApi iFilterApi = (IngredientFilterApi)runtime.get().getIngredientFilter();
|
||||
IngredientFilter filter;
|
||||
try {
|
||||
filter = (IngredientFilter)filterField.get(iFilterApi);
|
||||
} catch(ReflectiveOperationException e) {
|
||||
ModernFix.LOGGER.error(e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return this.searchJEI(filter, pSearchText);
|
||||
} else {
|
||||
/* Use the default, dummy implementation */
|
||||
return super.search(pSearchText);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ItemStack> searchJEI(IngredientFilter filter, String pSearchText) {
|
||||
if(!pSearchText.equals(lastSearchText)) {
|
||||
listCache.clear();
|
||||
Stream<ITypedIngredient<?>> ingredients;
|
||||
String finalSearchTerm = filteringByTag ? ("$" + pSearchText) : pSearchText;
|
||||
try {
|
||||
ingredients = (Stream<ITypedIngredient<?>>)getIngredientListUncached.invokeExact(filter, finalSearchTerm);
|
||||
} catch(Throwable e) {
|
||||
ModernFix.LOGGER.error("Error searching", e);
|
||||
ingredients = Stream.empty();
|
||||
}
|
||||
ingredients.toList().forEach(ingredient -> {
|
||||
if(ingredient.getIngredient() instanceof ItemStack) {
|
||||
listCache.add((ItemStack)ingredient.getIngredient());
|
||||
}
|
||||
});
|
||||
lastSearchText = pSearchText;
|
||||
}
|
||||
return listCache;
|
||||
}
|
||||
|
||||
public static final SearchTreeProviderRegistry.Provider PROVIDER = new SearchTreeProviderRegistry.Provider() {
|
||||
@Override
|
||||
public RefreshableSearchTree<ItemStack> getSearchTree(boolean tag) {
|
||||
return new JEIBackedSearchTree(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
return ModernFixPlatformHooks.INSTANCE.modPresent("jei") && !ModernFixPlatformHooks.INSTANCE.modPresent("emi") && getIngredientListUncached != null && filterField != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "JEI";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package org.embeddedt.modernfix.searchtree;
|
||||
|
||||
import mezz.jei.api.IModPlugin;
|
||||
import mezz.jei.api.JeiPlugin;
|
||||
import mezz.jei.api.runtime.IJeiRuntime;
|
||||
import mezz.jei.library.runtime.JeiRuntime;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@JeiPlugin
|
||||
public class JEIRuntimeCapturer implements IModPlugin {
|
||||
private static JeiRuntime runtimeHandle = null;
|
||||
|
||||
public static Optional<JeiRuntime> runtime() {
|
||||
return Optional.ofNullable(runtimeHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getPluginUid() {
|
||||
return new ResourceLocation(ModernFix.MODID, "capturer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRuntimeAvailable(IJeiRuntime jeiRuntime) {
|
||||
runtimeHandle = (JeiRuntime)jeiRuntime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRuntimeUnavailable() {
|
||||
runtimeHandle = null;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user