valueEntry : map.get(entryValues.getKey()).entrySet()) {
650 | valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue());
651 | allSkipped = false;
652 | }
653 | if (!allSkipped) {
654 | reallyAllSkipped = false;
655 | valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build());
656 | }
657 | }
658 | if (reallyAllSkipped) {
659 | // Null = skip the chart
660 | return null;
661 | }
662 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
663 | }
664 | }
665 |
666 | /**
667 | * An extremely simple JSON builder.
668 | *
669 | * While this class is neither feature-rich nor the most performant one, it's sufficient enough
670 | * for its use-case.
671 | */
672 | public static class JsonObjectBuilder {
673 |
674 | private StringBuilder builder = new StringBuilder();
675 |
676 | private boolean hasAtLeastOneField = false;
677 |
678 | public JsonObjectBuilder() {
679 | builder.append("{");
680 | }
681 |
682 | /**
683 | * Appends a null field to the JSON.
684 | *
685 | * @param key The key of the field.
686 | * @return A reference to this object.
687 | */
688 | public JsonObjectBuilder appendNull(String key) {
689 | appendFieldUnescaped(key, "null");
690 | return this;
691 | }
692 |
693 | /**
694 | * Appends a string field to the JSON.
695 | *
696 | * @param key The key of the field.
697 | * @param value The value of the field.
698 | * @return A reference to this object.
699 | */
700 | public JsonObjectBuilder appendField(String key, String value) {
701 | if (value == null) {
702 | throw new IllegalArgumentException("JSON value must not be null");
703 | }
704 | appendFieldUnescaped(key, "\"" + escape(value) + "\"");
705 | return this;
706 | }
707 |
708 | /**
709 | * Appends an integer field to the JSON.
710 | *
711 | * @param key The key of the field.
712 | * @param value The value of the field.
713 | * @return A reference to this object.
714 | */
715 | public JsonObjectBuilder appendField(String key, int value) {
716 | appendFieldUnescaped(key, String.valueOf(value));
717 | return this;
718 | }
719 |
720 | /**
721 | * Appends an object to the JSON.
722 | *
723 | * @param key The key of the field.
724 | * @param object The object.
725 | * @return A reference to this object.
726 | */
727 | public JsonObjectBuilder appendField(String key, JsonObject object) {
728 | if (object == null) {
729 | throw new IllegalArgumentException("JSON object must not be null");
730 | }
731 | appendFieldUnescaped(key, object.toString());
732 | return this;
733 | }
734 |
735 | /**
736 | * Appends a string array to the JSON.
737 | *
738 | * @param key The key of the field.
739 | * @param values The string array.
740 | * @return A reference to this object.
741 | */
742 | public JsonObjectBuilder appendField(String key, String[] values) {
743 | if (values == null) {
744 | throw new IllegalArgumentException("JSON values must not be null");
745 | }
746 | String escapedValues =
747 | Arrays.stream(values)
748 | .map(value -> "\"" + escape(value) + "\"")
749 | .collect(Collectors.joining(","));
750 | appendFieldUnescaped(key, "[" + escapedValues + "]");
751 | return this;
752 | }
753 |
754 | /**
755 | * Appends an integer array to the JSON.
756 | *
757 | * @param key The key of the field.
758 | * @param values The integer array.
759 | * @return A reference to this object.
760 | */
761 | public JsonObjectBuilder appendField(String key, int[] values) {
762 | if (values == null) {
763 | throw new IllegalArgumentException("JSON values must not be null");
764 | }
765 | String escapedValues =
766 | Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(","));
767 | appendFieldUnescaped(key, "[" + escapedValues + "]");
768 | return this;
769 | }
770 |
771 | /**
772 | * Appends an object array to the JSON.
773 | *
774 | * @param key The key of the field.
775 | * @param values The integer array.
776 | * @return A reference to this object.
777 | */
778 | public JsonObjectBuilder appendField(String key, JsonObject[] values) {
779 | if (values == null) {
780 | throw new IllegalArgumentException("JSON values must not be null");
781 | }
782 | String escapedValues =
783 | Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(","));
784 | appendFieldUnescaped(key, "[" + escapedValues + "]");
785 | return this;
786 | }
787 |
788 | /**
789 | * Appends a field to the object.
790 | *
791 | * @param key The key of the field.
792 | * @param escapedValue The escaped value of the field.
793 | */
794 | private void appendFieldUnescaped(String key, String escapedValue) {
795 | if (builder == null) {
796 | throw new IllegalStateException("JSON has already been built");
797 | }
798 | if (key == null) {
799 | throw new IllegalArgumentException("JSON key must not be null");
800 | }
801 | if (hasAtLeastOneField) {
802 | builder.append(",");
803 | }
804 | builder.append("\"").append(escape(key)).append("\":").append(escapedValue);
805 | hasAtLeastOneField = true;
806 | }
807 |
808 | /**
809 | * Builds the JSON string and invalidates this builder.
810 | *
811 | * @return The built JSON string.
812 | */
813 | public JsonObject build() {
814 | if (builder == null) {
815 | throw new IllegalStateException("JSON has already been built");
816 | }
817 | JsonObject object = new JsonObject(builder.append("}").toString());
818 | builder = null;
819 | return object;
820 | }
821 |
822 | /**
823 | * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt.
824 | *
825 | *
This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'.
826 | * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n").
827 | *
828 | * @param value The value to escape.
829 | * @return The escaped value.
830 | */
831 | private static String escape(String value) {
832 | final StringBuilder builder = new StringBuilder();
833 | for (int i = 0; i < value.length(); i++) {
834 | char c = value.charAt(i);
835 | if (c == '"') {
836 | builder.append("\\\"");
837 | } else if (c == '\\') {
838 | builder.append("\\\\");
839 | } else if (c <= '\u000F') {
840 | builder.append("\\u000").append(Integer.toHexString(c));
841 | } else if (c <= '\u001F') {
842 | builder.append("\\u00").append(Integer.toHexString(c));
843 | } else {
844 | builder.append(c);
845 | }
846 | }
847 | return builder.toString();
848 | }
849 |
850 | /**
851 | * A super simple representation of a JSON object.
852 | *
853 | *
This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not
854 | * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String,
855 | * JsonObject)}.
856 | */
857 | public static class JsonObject {
858 |
859 | private final String value;
860 |
861 | private JsonObject(String value) {
862 | this.value = value;
863 | }
864 |
865 | @Override
866 | public String toString() {
867 | return value;
868 | }
869 | }
870 | }
871 | }
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/metrics/MetricsConfig.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.metrics;
2 |
3 | import java.util.UUID;
4 |
5 | public class MetricsConfig {
6 |
7 | final String _notice1 = "bStats (https://bStats.org) collects some basic information for plugin authors, like how";
8 | final String _notice2 = "many people use their plugin and their total player count. It's recommended to keep bStats";
9 | final String _notice3 = "enabled, but if you're not comfortable with this, you can turn this setting off. There is no";
10 | final String _notice4 = "performance penalty associated with having metrics enabled, and data sent to bStats is fully";
11 | final String _notice5 = "anonymous.";
12 | final String _notice6 = "C2ME-forge bStats page: https://bstats.org/plugin/bukkit/C2ME-forge/10823";
13 | String serverUuid;
14 | boolean enabled;
15 | boolean logFailedRequests;
16 | boolean logSentData;
17 | boolean logResponseStatusText;
18 |
19 | public MetricsConfig() {
20 | serverUuid = UUID.randomUUID().toString();
21 | enabled = true;
22 | logFailedRequests = false;
23 | logSentData = false;
24 | logResponseStatusText = false;
25 | }
26 |
27 | public MetricsConfig(String serverUuid, boolean enabled, boolean logFailedRequests, boolean logSentData, boolean logResponseStatusText) {
28 | this.serverUuid = serverUuid;
29 | this.enabled = enabled;
30 | this.logFailedRequests = logFailedRequests;
31 | this.logSentData = logSentData;
32 | this.logResponseStatusText = logResponseStatusText;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/C2MEMixinPlugin.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin;
2 |
3 | import org.apache.logging.log4j.LogManager;
4 | import org.apache.logging.log4j.Logger;
5 | import org.objectweb.asm.tree.ClassNode;
6 | import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
7 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
8 | import org.yatopiamc.c2me.common.config.C2MEConfig;
9 |
10 | import java.util.List;
11 | import java.util.Set;
12 |
13 | public class C2MEMixinPlugin implements IMixinConfigPlugin {
14 | private static final Logger LOGGER = LogManager.getLogger("C2ME Mixin");
15 |
16 | @Override
17 | public void onLoad(String mixinPackage) {
18 | C2MEConfig.threadedWorldGenConfig.getClass().getName(); // Load configuration
19 | LOGGER.info("Successfully loaded configuration for C2ME");
20 | }
21 |
22 | @Override
23 | public String getRefMapperConfig() {
24 | return null;
25 | }
26 |
27 | @Override
28 | public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
29 | if (mixinClassName.startsWith("org.yatopiamc.c2me.mixin.threading.worldgen."))
30 | return C2MEConfig.threadedWorldGenConfig.enabled;
31 | if (mixinClassName.startsWith("org.yatopiamc.c2me.mixin.threading.chunkio."))
32 | return C2MEConfig.asyncIoConfig.enabled;
33 | return true;
34 | }
35 |
36 | @Override
37 | public void acceptTargets(Set myTargets, Set otherTargets) {
38 |
39 | }
40 |
41 | @Override
42 | public List getMixins() {
43 | return null;
44 | }
45 |
46 | @Override
47 | public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
48 |
49 | }
50 |
51 | @Override
52 | public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
53 |
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/chunkscheduling/fix_unload/MixinThreadedAnvilChunkStorage.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.chunkscheduling.fix_unload;
2 |
3 | import it.unimi.dsi.fastutil.longs.LongSet;
4 | import net.minecraft.util.concurrent.ThreadTaskExecutor;
5 | import net.minecraft.village.PointOfInterestManager;
6 | import net.minecraft.world.server.ChunkHolder;
7 | import net.minecraft.world.server.ChunkManager;
8 | import org.spongepowered.asm.mixin.Dynamic;
9 | import org.spongepowered.asm.mixin.Final;
10 | import org.spongepowered.asm.mixin.Mixin;
11 | import org.spongepowered.asm.mixin.Mutable;
12 | import org.spongepowered.asm.mixin.Overwrite;
13 | import org.spongepowered.asm.mixin.Shadow;
14 | import org.spongepowered.asm.mixin.injection.At;
15 | import org.spongepowered.asm.mixin.injection.Inject;
16 | import org.spongepowered.asm.mixin.injection.Redirect;
17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
18 | import org.yatopiamc.c2me.common.structs.LongHashSet;
19 | import org.yatopiamc.c2me.common.util.ShouldKeepTickingUtils;
20 |
21 | import java.util.function.BooleanSupplier;
22 |
23 | @Mixin(ChunkManager.class)
24 | public abstract class MixinThreadedAnvilChunkStorage {
25 |
26 | @Shadow @Final private ThreadTaskExecutor mainThreadExecutor;
27 |
28 | @Shadow protected abstract void processUnloads(BooleanSupplier shouldKeepTicking);
29 |
30 | @Mutable
31 | @Shadow @Final private LongSet toDrop;
32 |
33 | /**
34 | * @author ishland
35 | * @reason Queue unload immediately
36 | */
37 | @SuppressWarnings("OverwriteTarget")
38 | @Dynamic
39 | @Overwrite(aliases = "func_222962_a_")
40 | private void lambda$unpackTicks$38(ChunkHolder holder, Runnable runnable) { // TODO synthetic method in thenApplyAsync call of makeChunkAccessible
41 | this.mainThreadExecutor.execute(runnable);
42 | }
43 |
44 | @Redirect(method = "tick(Ljava/util/function/BooleanSupplier;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/village/PointOfInterestManager;tick(Ljava/util/function/BooleanSupplier;)V"))
45 | private void redirectTickPointOfInterestStorageTick(PointOfInterestManager pointOfInterestStorage, BooleanSupplier shouldKeepTicking) {
46 | pointOfInterestStorage.tick(ShouldKeepTickingUtils.minimumTicks(shouldKeepTicking, 32));
47 | }
48 |
49 | @Redirect(method = "tick(Ljava/util/function/BooleanSupplier;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/server/ChunkManager;processUnloads(Ljava/util/function/BooleanSupplier;)V"))
50 | private void redirectTickUnloadChunks(ChunkManager threadedAnvilChunkStorage, BooleanSupplier shouldKeepTicking) {
51 | this.processUnloads(ShouldKeepTickingUtils.minimumTicks(shouldKeepTicking, 32));
52 | }
53 |
54 | @Inject(method = "", at = @At("RETURN"))
55 | private void onInit(CallbackInfo info) {
56 | this.toDrop = new LongHashSet();
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/chunkscheduling/mid_tick_chunk_tasks/MixinMinecraftServer.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.chunkscheduling.mid_tick_chunk_tasks;
2 |
3 | import net.minecraft.server.MinecraftServer;
4 | import net.minecraft.world.server.ServerWorld;
5 | import org.spongepowered.asm.mixin.Final;
6 | import org.spongepowered.asm.mixin.Mixin;
7 | import org.spongepowered.asm.mixin.Shadow;
8 | import org.yatopiamc.c2me.common.chunkscheduling.ServerMidTickTask;
9 | import org.yatopiamc.c2me.mixin.util.accessor.IThreadTaskExecutor;
10 |
11 | import java.util.concurrent.atomic.AtomicLong;
12 |
13 | @Mixin(MinecraftServer.class)
14 | public abstract class MixinMinecraftServer implements ServerMidTickTask {
15 |
16 | @Shadow public abstract Iterable getAllLevels();
17 |
18 | @Shadow @Final private Thread serverThread;
19 | private static final long minMidTickTaskInterval = 25_000L; // 25us
20 | private final AtomicLong lastRun = new AtomicLong(System.nanoTime());
21 |
22 | public void executeTasksMidTick() {
23 | if (this.serverThread != Thread.currentThread()) return;
24 | if (System.nanoTime() - lastRun.get() < minMidTickTaskInterval) return;
25 | for (ServerWorld world : this.getAllLevels()) {
26 | ((IThreadTaskExecutor) world.getChunkSource().mainThreadProcessor).IPollTask();
27 | }
28 | lastRun.set(System.nanoTime());
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/chunkscheduling/mid_tick_chunk_tasks/MixinServerChunkManager.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.chunkscheduling.mid_tick_chunk_tasks;
2 |
3 | import net.minecraft.world.server.ServerChunkProvider;
4 | import net.minecraft.world.server.ServerWorld;
5 | import org.spongepowered.asm.mixin.Dynamic;
6 | import org.spongepowered.asm.mixin.Final;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.Shadow;
9 | import org.spongepowered.asm.mixin.injection.At;
10 | import org.spongepowered.asm.mixin.injection.Inject;
11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
12 | import org.yatopiamc.c2me.common.chunkscheduling.ServerMidTickTask;
13 |
14 | @Mixin(ServerChunkProvider.class)
15 | public class MixinServerChunkManager {
16 |
17 | @Shadow @Final
18 | public ServerWorld level;
19 |
20 | @Dynamic
21 | @Inject(method = {"lambda$tickChunks$5", "func_241099_a_"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/server/ServerWorld;tickChunk(Lnet/minecraft/world/chunk/Chunk;I)V"))
22 | private void onPostTickChunk(CallbackInfo ci) { // TODO synthetic method - in tickChunks()
23 | ((ServerMidTickTask) this.level.getServer()).executeTasksMidTick();
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/chunkscheduling/mid_tick_chunk_tasks/MixinServerTickScheduler.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.chunkscheduling.mid_tick_chunk_tasks;
2 |
3 | import net.minecraft.world.server.ServerTickList;
4 | import net.minecraft.world.server.ServerWorld;
5 | import org.spongepowered.asm.mixin.Final;
6 | import org.spongepowered.asm.mixin.Mixin;
7 | import org.spongepowered.asm.mixin.Shadow;
8 | import org.spongepowered.asm.mixin.injection.At;
9 | import org.spongepowered.asm.mixin.injection.Inject;
10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
11 | import org.yatopiamc.c2me.common.chunkscheduling.ServerMidTickTask;
12 |
13 | @Mixin(ServerTickList.class)
14 | public class MixinServerTickScheduler {
15 |
16 | @Shadow @Final public ServerWorld level;
17 |
18 | @Inject(method = "tick", at = @At(value = "INVOKE", target = "Ljava/util/function/Consumer;accept(Ljava/lang/Object;)V", shift = At.Shift.AFTER))
19 | private void onPostActionTick(CallbackInfo ci) {
20 | ((ServerMidTickTask) this.level.getServer()).executeTasksMidTick();
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/chunkscheduling/mid_tick_chunk_tasks/MixinWorld.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.chunkscheduling.mid_tick_chunk_tasks;
2 |
3 | import net.minecraft.server.MinecraftServer;
4 | import net.minecraft.world.World;
5 | import org.spongepowered.asm.mixin.Final;
6 | import org.spongepowered.asm.mixin.Mixin;
7 | import org.spongepowered.asm.mixin.Shadow;
8 | import org.spongepowered.asm.mixin.injection.At;
9 | import org.spongepowered.asm.mixin.injection.Inject;
10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
11 | import org.yatopiamc.c2me.common.chunkscheduling.ServerMidTickTask;
12 |
13 | import javax.annotation.Nullable;
14 |
15 | @Mixin(World.class)
16 | public abstract class MixinWorld {
17 |
18 | @Shadow @Nullable
19 | public abstract MinecraftServer getServer();
20 |
21 | @Shadow @Final public boolean isClientSide;
22 |
23 | @Inject(method = "guardEntityTick", at = @At("TAIL"))
24 | private void onPostTickEntity(CallbackInfo ci) {
25 | final MinecraftServer server = this.getServer();
26 | if (!this.isClientSide && server != null) {
27 | ((ServerMidTickTask) server).executeTasksMidTick();
28 | }
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/chunkscheduling/package-info.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.chunkscheduling;
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/optimizations/package-info.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.optimizations;
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/optimizations/thread_local_biome_sampler/MixinOverworldBiomeProvider.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.optimizations.thread_local_biome_sampler;
2 |
3 | import net.minecraft.util.registry.Registry;
4 | import net.minecraft.world.biome.Biome;
5 | import net.minecraft.world.biome.provider.OverworldBiomeProvider;
6 | import net.minecraft.world.gen.layer.Layer;
7 | import net.minecraft.world.gen.layer.LayerUtil;
8 | import org.spongepowered.asm.mixin.Final;
9 | import org.spongepowered.asm.mixin.Mixin;
10 | import org.spongepowered.asm.mixin.Overwrite;
11 | import org.spongepowered.asm.mixin.Shadow;
12 | import org.spongepowered.asm.mixin.injection.At;
13 | import org.spongepowered.asm.mixin.injection.Inject;
14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
15 |
16 | @Mixin(OverworldBiomeProvider.class)
17 | public class MixinOverworldBiomeProvider {
18 |
19 | @Shadow @Final private Registry biomes;
20 | private ThreadLocal layerThreadLocal = new ThreadLocal<>();
21 |
22 | @Inject(method = "", at = @At("RETURN"))
23 | private void onInit(long p_i241958_1_, boolean p_i241958_3_, boolean p_i241958_4_, Registry p_i241958_5_, CallbackInfo ci) {
24 | this.layerThreadLocal = ThreadLocal.withInitial(() -> LayerUtil.getDefaultLayer(p_i241958_1_, p_i241958_3_, p_i241958_4_ ? 6 : 4, 4)); // [VanillaCopy]
25 | }
26 |
27 | /**
28 | * @author ishland
29 | * @reason use thread_local sampler
30 | */
31 | @Overwrite
32 | public Biome getNoiseBiome(int p_225526_1_, int p_225526_2_, int p_225526_3_) {
33 | return this.layerThreadLocal.get().get(this.biomes, p_225526_1_, p_225526_3_);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/package-info.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin;
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/chunkio/MixinChunkSerializer.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.chunkio;
2 |
3 | import net.minecraft.block.Block;
4 | import net.minecraft.fluid.Fluid;
5 | import net.minecraft.nbt.CompoundNBT;
6 | import net.minecraft.nbt.ListNBT;
7 | import net.minecraft.tileentity.TileEntity;
8 | import net.minecraft.util.math.BlockPos;
9 | import net.minecraft.util.math.ChunkPos;
10 | import net.minecraft.village.PointOfInterestManager;
11 | import net.minecraft.world.ITickList;
12 | import net.minecraft.world.LightType;
13 | import net.minecraft.world.chunk.Chunk;
14 | import net.minecraft.world.chunk.ChunkSection;
15 | import net.minecraft.world.chunk.IChunk;
16 | import net.minecraft.world.chunk.storage.ChunkSerializer;
17 | import net.minecraft.world.lighting.IWorldLightListener;
18 | import net.minecraft.world.lighting.WorldLightManager;
19 | import net.minecraft.world.server.ServerTickList;
20 | import org.spongepowered.asm.mixin.Mixin;
21 | import org.spongepowered.asm.mixin.injection.At;
22 | import org.spongepowered.asm.mixin.injection.Redirect;
23 | import org.yatopiamc.c2me.common.threading.chunkio.AsyncSerializationManager;
24 | import org.yatopiamc.c2me.common.threading.chunkio.ChunkIoMainThreadTaskUtils;
25 |
26 | import java.util.Set;
27 | import java.util.concurrent.CompletableFuture;
28 |
29 | @Mixin(ChunkSerializer.class)
30 | public class MixinChunkSerializer {
31 |
32 | @Redirect(method = "read", at = @At(value = "INVOKE", target = "Lnet/minecraft/village/PointOfInterestManager;checkConsistencyWithBlocks(Lnet/minecraft/util/math/ChunkPos;Lnet/minecraft/world/chunk/ChunkSection;)V"))
33 | private static void onPoiStorageInitForPalette(PointOfInterestManager pointOfInterestStorage, ChunkPos chunkPos, ChunkSection chunkSection) {
34 | ChunkIoMainThreadTaskUtils.executeMain(() -> pointOfInterestStorage.checkConsistencyWithBlocks(chunkPos, chunkSection));
35 | }
36 |
37 | @Redirect(method = "write", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/chunk/IChunk;getBlockEntitiesPos()Ljava/util/Set;"))
38 | private static Set onChunkGetBlockEntityPositions(IChunk chunk) {
39 | final AsyncSerializationManager.Scope scope = AsyncSerializationManager.getScope(chunk.getPos());
40 | return scope != null ? scope.blockEntities.keySet() : chunk.getBlockEntitiesPos();
41 | }
42 |
43 | @Redirect(method = "write", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/chunk/IChunk;getBlockEntityNbtForSaving(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/nbt/CompoundNBT;"))
44 | private static CompoundNBT onChunkGetPackedBlockEntityTag(IChunk chunk, BlockPos pos) {
45 | final AsyncSerializationManager.Scope scope = AsyncSerializationManager.getScope(chunk.getPos());
46 | if (scope == null) return chunk.getBlockEntityNbtForSaving(pos);
47 | final TileEntity blockEntity = scope.blockEntities.get(pos);
48 | if (blockEntity == null || blockEntity.isRemoved()) return null;
49 | final CompoundNBT compoundTag = new CompoundNBT();
50 | if (chunk instanceof Chunk) compoundTag.putBoolean("keepPacked", false);
51 | blockEntity.save(compoundTag);
52 | return compoundTag;
53 | }
54 |
55 | @Redirect(method = "write", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/chunk/IChunk;getBlockTicks()Lnet/minecraft/world/ITickList;"))
56 | private static ITickList onChunkGetBlockTickScheduler(IChunk chunk) {
57 | final AsyncSerializationManager.Scope scope = AsyncSerializationManager.getScope(chunk.getPos());
58 | return scope != null ? scope.blockTickScheduler : chunk.getBlockTicks();
59 | }
60 |
61 | @Redirect(method = "write", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/chunk/IChunk;getLiquidTicks()Lnet/minecraft/world/ITickList;"))
62 | private static ITickList onChunkGetFluidTickScheduler(IChunk chunk) {
63 | final AsyncSerializationManager.Scope scope = AsyncSerializationManager.getScope(chunk.getPos());
64 | return scope != null ? scope.fluidTickScheduler : chunk.getLiquidTicks();
65 | }
66 |
67 | @Redirect(method = "write", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/server/ServerTickList;save(Lnet/minecraft/util/math/ChunkPos;)Lnet/minecraft/nbt/ListNBT;"))
68 | private static ListNBT onServerTickSchedulerToTag(@SuppressWarnings("rawtypes") ServerTickList serverTickScheduler, ChunkPos chunkPos) {
69 | final AsyncSerializationManager.Scope scope = AsyncSerializationManager.getScope(chunkPos);
70 | return scope != null ? CompletableFuture.supplyAsync(() -> serverTickScheduler.save(chunkPos), serverTickScheduler.level.chunkSource.mainThreadProcessor).join() : serverTickScheduler.save(chunkPos);
71 | }
72 |
73 | @Redirect(method = "write", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/lighting/WorldLightManager;getLayerListener(Lnet/minecraft/world/LightType;)Lnet/minecraft/world/lighting/IWorldLightListener;"))
74 | private static IWorldLightListener onLightingProviderGet(WorldLightManager lightingProvider, LightType lightType) {
75 | final AsyncSerializationManager.Scope scope = AsyncSerializationManager.getScope(null);
76 | return scope != null ? scope.lighting.get(lightType) : lightingProvider.getLayerListener(lightType);
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/chunkio/MixinChunkTickScheduler.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.chunkio;
2 |
3 | import net.minecraft.nbt.ListNBT;
4 | import net.minecraft.util.math.ChunkPos;
5 | import net.minecraft.world.chunk.ChunkPrimerTickList;
6 | import org.spongepowered.asm.mixin.Final;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.Shadow;
9 | import org.yatopiamc.c2me.common.util.DeepCloneable;
10 |
11 | import java.util.function.Predicate;
12 |
13 | @Mixin(ChunkPrimerTickList.class)
14 | public abstract class MixinChunkTickScheduler implements DeepCloneable {
15 |
16 | @Shadow
17 | public abstract ListNBT save();
18 |
19 | @Shadow
20 | @Final
21 | private ChunkPos chunkPos;
22 | @Shadow @Final protected Predicate ignore;
23 |
24 | public ChunkPrimerTickList deepClone() {
25 | return new ChunkPrimerTickList<>(ignore, chunkPos, save());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/chunkio/MixinScheduledTick.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.chunkio;
2 |
3 | import net.minecraft.world.NextTickListEntry;
4 | import org.spongepowered.asm.mixin.Final;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.Mutable;
7 | import org.spongepowered.asm.mixin.Shadow;
8 | import org.spongepowered.asm.mixin.injection.At;
9 | import org.spongepowered.asm.mixin.injection.Inject;
10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
11 |
12 | import java.util.concurrent.atomic.AtomicLong;
13 |
14 | @Mixin(NextTickListEntry.class)
15 | public class MixinScheduledTick {
16 |
17 | @Mutable
18 | @Shadow @Final private long c;
19 | private static final AtomicLong COUNTER = new AtomicLong(0);
20 |
21 | @Inject(method = "", at = @At("TAIL"))
22 | private void onInit(CallbackInfo info) {
23 | this.c = COUNTER.getAndIncrement();
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/chunkio/MixinSerializingRegionBasedStorage.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.chunkio;
2 |
3 | import com.mojang.serialization.DynamicOps;
4 | import net.minecraft.nbt.CompoundNBT;
5 | import net.minecraft.nbt.NBTDynamicOps;
6 | import net.minecraft.util.math.ChunkPos;
7 | import net.minecraft.world.chunk.storage.RegionSectionCache;
8 | import org.spongepowered.asm.mixin.Mixin;
9 | import org.spongepowered.asm.mixin.Shadow;
10 | import org.yatopiamc.c2me.common.threading.chunkio.ISerializingRegionBasedStorage;
11 |
12 | import javax.annotation.Nullable;
13 |
14 | @Mixin(RegionSectionCache.class)
15 | public abstract class MixinSerializingRegionBasedStorage implements ISerializingRegionBasedStorage {
16 |
17 | @Shadow protected abstract void readColumn(ChunkPos p_235992_1_, DynamicOps p_235992_2_, @Nullable T p_235992_3_);
18 |
19 | @Override
20 | public void update(ChunkPos pos, CompoundNBT tag) {
21 | this.readColumn(pos, NBTDynamicOps.INSTANCE, tag);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/chunkio/MixinServerChunkManagerMainThreadExecutor.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.chunkio;
2 |
3 | import net.minecraft.util.concurrent.ThreadTaskExecutor;
4 | import net.minecraft.world.server.ServerChunkProvider;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.injection.At;
7 | import org.spongepowered.asm.mixin.injection.Inject;
8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
9 |
10 | @Mixin(ServerChunkProvider.ChunkExecutor.class)
11 | public abstract class MixinServerChunkManagerMainThreadExecutor extends ThreadTaskExecutor {
12 |
13 | protected MixinServerChunkManagerMainThreadExecutor(String name) {
14 | super(name);
15 | }
16 |
17 | @Inject(method = "pollTask", at = @At("RETURN"))
18 | private void onPostRunTask(CallbackInfoReturnable cir) {
19 | super.pollTask();
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/chunkio/MixinSimpleTickScheduler.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.chunkio;
2 |
3 | import it.unimi.dsi.fastutil.objects.ObjectArrayList;
4 | import net.minecraft.util.ResourceLocation;
5 | import net.minecraft.world.ITickList;
6 | import net.minecraft.world.SerializableTickList;
7 | import org.spongepowered.asm.mixin.Final;
8 | import org.spongepowered.asm.mixin.Mixin;
9 | import org.spongepowered.asm.mixin.Shadow;
10 | import org.yatopiamc.c2me.common.util.DeepCloneable;
11 |
12 | import java.util.function.Function;
13 |
14 | @Mixin(SerializableTickList.class)
15 | public abstract class MixinSimpleTickScheduler implements DeepCloneable {
16 |
17 | @Shadow @Final private Function toId;
18 |
19 | @Shadow public abstract void copyOut(ITickList scheduler);
20 |
21 | @Override
22 | public Object deepClone() {
23 | final SerializableTickList scheduler = new SerializableTickList<>(toId, new ObjectArrayList<>());
24 | copyOut(scheduler);
25 | return scheduler;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/chunkio/MixinStorageIoWorker.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.chunkio;
2 |
3 | import com.mojang.datafixers.util.Either;
4 | import net.minecraft.nbt.CompoundNBT;
5 | import net.minecraft.util.math.ChunkPos;
6 | import net.minecraft.world.chunk.storage.IOWorker;
7 | import net.minecraft.world.chunk.storage.RegionFileCache;
8 | import org.apache.logging.log4j.Logger;
9 | import org.spongepowered.asm.mixin.Final;
10 | import org.spongepowered.asm.mixin.Mixin;
11 | import org.spongepowered.asm.mixin.Shadow;
12 | import org.spongepowered.asm.mixin.injection.At;
13 | import org.spongepowered.asm.mixin.injection.Inject;
14 | import org.spongepowered.asm.mixin.injection.Redirect;
15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
16 | import org.yatopiamc.c2me.common.threading.chunkio.C2MECachedRegionStorage;
17 | import org.yatopiamc.c2me.common.threading.chunkio.ChunkIoThreadingExecutorUtils;
18 | import org.yatopiamc.c2me.common.threading.chunkio.IAsyncChunkStorage;
19 |
20 | import java.util.Map;
21 | import java.util.concurrent.CompletableFuture;
22 | import java.util.concurrent.Executor;
23 | import java.util.concurrent.ExecutorService;
24 | import java.util.concurrent.Executors;
25 | import java.util.concurrent.atomic.AtomicBoolean;
26 | import java.util.concurrent.atomic.AtomicReference;
27 | import java.util.function.Supplier;
28 |
29 | @Mixin(IOWorker.class)
30 | public abstract class MixinStorageIoWorker implements IAsyncChunkStorage {
31 |
32 | @Shadow @Final private AtomicBoolean shutdownRequested;
33 |
34 | @Shadow @Final private static Logger LOGGER;
35 |
36 | @Shadow protected abstract CompletableFuture submitTask(Supplier> p_235975_1_);
37 |
38 | @Shadow @Final private RegionFileCache storage;
39 |
40 | @Shadow @Final private Map pendingWrites;
41 |
42 | @Inject(method = "", at = @At("RETURN"))
43 | private void onPostInit(CallbackInfo info) {
44 | //noinspection ConstantConditions
45 | if (((Object) this) instanceof C2MECachedRegionStorage) {
46 | this.shutdownRequested.set(true);
47 | }
48 | }
49 |
50 | private AtomicReference executorService = new AtomicReference<>();
51 |
52 | @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Util;ioPool()Ljava/util/concurrent/Executor;"))
53 | private Executor onGetStorageIoWorker() {
54 | executorService.set(Executors.newSingleThreadExecutor(ChunkIoThreadingExecutorUtils.ioWorkerFactory));
55 | return executorService.get();
56 | }
57 |
58 | @Inject(method = "close", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/concurrent/DelegatedTaskExecutor;close()V", shift = At.Shift.AFTER))
59 | private void onClose(CallbackInfo ci) {
60 | final ExecutorService executorService = this.executorService.get();
61 | if (executorService != null) executorService.shutdown();
62 | }
63 |
64 | @Override
65 | public CompletableFuture getNbtAtAsync(ChunkPos pos) {
66 | // TODO [VanillaCopy]
67 | return this.submitTask(() -> {
68 | IOWorker.Entry result = (IOWorker.Entry)this.pendingWrites.get(pos);
69 | if (result != null) {
70 | return Either.left(result.data);
71 | } else {
72 | try {
73 | CompoundNBT compoundTag = this.storage.read(pos);
74 | return Either.left(compoundTag);
75 | } catch (Exception var4) {
76 | LOGGER.warn((String)"Failed to read chunk {}", (Object)pos, (Object)var4);
77 | return Either.right(var4);
78 | }
79 | }
80 | });
81 |
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/chunkio/MixinThreadedAnvilChunkStorage.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.chunkio;
2 |
3 | import com.ibm.asyncutil.locks.AsyncNamedLock;
4 | import com.mojang.datafixers.DataFixer;
5 | import com.mojang.datafixers.util.Either;
6 | import net.minecraft.nbt.CompoundNBT;
7 | import net.minecraft.util.concurrent.ThreadTaskExecutor;
8 | import net.minecraft.util.math.ChunkPos;
9 | import net.minecraft.util.palette.UpgradeData;
10 | import net.minecraft.village.PointOfInterestManager;
11 | import net.minecraft.world.chunk.ChunkPrimer;
12 | import net.minecraft.world.chunk.ChunkStatus;
13 | import net.minecraft.world.chunk.IChunk;
14 | import net.minecraft.world.chunk.storage.ChunkLoader;
15 | import net.minecraft.world.chunk.storage.ChunkSerializer;
16 | import net.minecraft.world.gen.feature.structure.StructureStart;
17 | import net.minecraft.world.gen.feature.template.TemplateManager;
18 | import net.minecraft.world.server.ChunkHolder;
19 | import net.minecraft.world.server.ChunkManager;
20 | import net.minecraft.world.server.ServerWorld;
21 | import net.minecraft.world.storage.DimensionSavedDataManager;
22 | import org.apache.logging.log4j.Logger;
23 | import org.spongepowered.asm.mixin.Dynamic;
24 | import org.spongepowered.asm.mixin.Final;
25 | import org.spongepowered.asm.mixin.Mixin;
26 | import org.spongepowered.asm.mixin.Overwrite;
27 | import org.spongepowered.asm.mixin.Shadow;
28 | import org.spongepowered.asm.mixin.injection.At;
29 | import org.spongepowered.asm.mixin.injection.Inject;
30 | import org.spongepowered.asm.mixin.injection.Redirect;
31 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
32 | import org.yatopiamc.c2me.common.threading.chunkio.AsyncSerializationManager;
33 | import org.yatopiamc.c2me.common.threading.chunkio.C2MECachedRegionStorage;
34 | import org.yatopiamc.c2me.common.threading.chunkio.ChunkIoMainThreadTaskUtils;
35 | import org.yatopiamc.c2me.common.threading.chunkio.ChunkIoThreadingExecutorUtils;
36 | import org.yatopiamc.c2me.common.threading.chunkio.IAsyncChunkStorage;
37 | import org.yatopiamc.c2me.common.threading.chunkio.ISerializingRegionBasedStorage;
38 | import org.yatopiamc.c2me.common.util.SneakyThrow;
39 |
40 | import java.io.File;
41 | import java.util.HashSet;
42 | import java.util.Set;
43 | import java.util.concurrent.CompletableFuture;
44 | import java.util.concurrent.ConcurrentLinkedQueue;
45 | import java.util.function.Supplier;
46 |
47 | @Mixin(ChunkManager.class)
48 | public abstract class MixinThreadedAnvilChunkStorage extends ChunkLoader implements ChunkHolder.IPlayerProvider {
49 |
50 | public MixinThreadedAnvilChunkStorage(File file, DataFixer dataFixer, boolean bl) {
51 | super(file, dataFixer, bl);
52 | }
53 |
54 | @Shadow
55 | @Final
56 | private ServerWorld level;
57 |
58 | @Shadow
59 | @Final
60 | private TemplateManager structureManager;
61 |
62 | @Shadow
63 | @Final
64 | private PointOfInterestManager poiManager;
65 |
66 | @Shadow
67 | protected abstract byte markPosition(ChunkPos chunkPos, ChunkStatus.Type chunkType);
68 |
69 | @Shadow
70 | @Final
71 | private static Logger LOGGER;
72 |
73 | @Shadow
74 | protected abstract void markPositionReplaceable(ChunkPos chunkPos);
75 |
76 | @Shadow
77 | @Final
78 | private Supplier overworldDataStorage;
79 |
80 | @Shadow
81 | @Final
82 | private ThreadTaskExecutor mainThreadExecutor;
83 |
84 | @Shadow
85 | protected abstract boolean isExistingChunkFull(ChunkPos chunkPos);
86 |
87 | private AsyncNamedLock chunkLock = AsyncNamedLock.createFair();
88 |
89 | @Inject(method = "", at = @At("RETURN"))
90 | private void onInit(CallbackInfo info) {
91 | chunkLock = AsyncNamedLock.createFair();
92 | }
93 |
94 | private Set scheduledChunks = new HashSet<>();
95 |
96 | /**
97 | * @author ishland
98 | * @reason async io and deserialization
99 | */
100 | @Overwrite
101 | private CompletableFuture> scheduleChunkLoad(ChunkPos pos) {
102 | if (scheduledChunks == null) scheduledChunks = new HashSet<>();
103 | synchronized (scheduledChunks) {
104 | if (scheduledChunks.contains(pos)) throw new IllegalArgumentException("Already scheduled");
105 | scheduledChunks.add(pos);
106 | }
107 |
108 | final CompletableFuture poiData = ((IAsyncChunkStorage) this.poiManager.worker).getNbtAtAsync(pos);
109 |
110 | final CompletableFuture> future = getUpdatedChunkTagAtAsync(pos).thenApplyAsync(compoundTag -> {
111 | if (compoundTag != null) {
112 | try {
113 | if (compoundTag.contains("Level", 10) && compoundTag.getCompound("Level").contains("Status", 8)) {
114 | return ChunkSerializer.read(this.level, this.structureManager, this.poiManager, pos, compoundTag);
115 | }
116 |
117 | LOGGER.warn("Chunk file at {} is missing level data, skipping", pos);
118 | } catch (Throwable t) {
119 | LOGGER.error("Couldn't load chunk {}, chunk data will be lost!", pos, t);
120 | }
121 | }
122 | return null;
123 | }, ChunkIoThreadingExecutorUtils.serializerExecutor).thenCombine(poiData, (protoChunk, tag) -> protoChunk).thenApplyAsync(protoChunk -> {
124 | ((ISerializingRegionBasedStorage) this.poiManager).update(pos, poiData.join());
125 | ChunkIoMainThreadTaskUtils.drainQueue();
126 | if (protoChunk != null) {
127 | protoChunk.setLastSaveTime(this.level.getGameTime());
128 | this.markPosition(pos, protoChunk.getStatus().getChunkType());
129 | return Either.left(protoChunk);
130 | } else {
131 | this.markPositionReplaceable(pos);
132 | return Either.left(new ChunkPrimer(pos, UpgradeData.EMPTY));
133 | }
134 | }, this.mainThreadExecutor);
135 | future.exceptionally(throwable -> null).thenRun(() -> {
136 | synchronized (scheduledChunks) {
137 | scheduledChunks.remove(pos);
138 | }
139 | });
140 | return future;
141 |
142 | // [VanillaCopy] - for reference
143 | /*
144 | return CompletableFuture.supplyAsync(() -> {
145 | try {
146 | CompoundTag compoundTag = this.getUpdatedChunkTag(pos);
147 | if (compoundTag != null) {
148 | boolean bl = compoundTag.contains("Level", 10) && compoundTag.getCompound("Level").contains("Status", 8);
149 | if (bl) {
150 | Chunk chunk = ChunkSerializer.deserialize(this.world, this.structureManager, this.pointOfInterestStorage, pos, compoundTag);
151 | chunk.setLastSaveTime(this.world.getTime());
152 | this.method_27053(pos, chunk.getStatus().getChunkType());
153 | return Either.left(chunk);
154 | }
155 |
156 | LOGGER.error((String)"Chunk file at {} is missing level data, skipping", (Object)pos);
157 | }
158 | } catch (CrashException var5) {
159 | Throwable throwable = var5.getCause();
160 | if (!(throwable instanceof IOException)) {
161 | this.method_27054(pos);
162 | throw var5;
163 | }
164 |
165 | LOGGER.error("Couldn't load chunk {}", pos, throwable);
166 | } catch (Exception var6) {
167 | LOGGER.error("Couldn't load chunk {}", pos, var6);
168 | }
169 |
170 | this.method_27054(pos);
171 | return Either.left(new ProtoChunk(pos, UpgradeData.NO_UPGRADE_DATA));
172 | }, this.mainThreadExecutor);
173 | */
174 | }
175 |
176 | private CompletableFuture getUpdatedChunkTagAtAsync(ChunkPos pos) {
177 | return chunkLock.acquireLock(pos).toCompletableFuture().thenCompose(lockToken -> ((IAsyncChunkStorage) this.worker).getNbtAtAsync(pos).thenApply(compoundTag -> {
178 | if (compoundTag != null)
179 | return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, compoundTag);
180 | else return null;
181 | }).handle((tag, throwable) -> {
182 | lockToken.releaseLock();
183 | if (throwable != null)
184 | SneakyThrow.sneaky(throwable);
185 | return tag;
186 | }));
187 | }
188 |
189 | private ConcurrentLinkedQueue> saveFutures = new ConcurrentLinkedQueue<>();
190 |
191 | @Dynamic
192 | @Redirect(method = {"lambda$scheduleUnload$10", "func_219185_a"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/server/ChunkManager;save(Lnet/minecraft/world/chunk/IChunk;)Z")) // method: consumer in tryUnloadChunk
193 | private boolean asyncSave(ChunkManager tacs, IChunk p_219229_1_) {
194 | // TODO [VanillaCopy] - check when updating minecraft version
195 | this.poiManager.flush(p_219229_1_.getPos());
196 | if (!p_219229_1_.isUnsaved()) {
197 | return false;
198 | } else {
199 | p_219229_1_.setLastSaveTime(this.level.getGameTime());
200 | p_219229_1_.setUnsaved(false);
201 | ChunkPos chunkpos = p_219229_1_.getPos();
202 |
203 | try {
204 | ChunkStatus chunkstatus = p_219229_1_.getStatus();
205 | if (chunkstatus.getChunkType() != ChunkStatus.Type.LEVELCHUNK) {
206 | if (this.isExistingChunkFull(chunkpos)) {
207 | return false;
208 | }
209 |
210 | if (chunkstatus == ChunkStatus.EMPTY && p_219229_1_.getAllStarts().values().stream().noneMatch(StructureStart::isValid)) {
211 | return false;
212 | }
213 | }
214 |
215 | this.level.getProfiler().incrementCounter("chunkSave");
216 | // C2ME start - async serialization
217 | if (saveFutures == null) saveFutures = new ConcurrentLinkedQueue<>();
218 | AsyncSerializationManager.Scope scope = new AsyncSerializationManager.Scope(p_219229_1_, level);
219 |
220 | saveFutures.add(chunkLock.acquireLock(p_219229_1_.getPos()).toCompletableFuture().thenCompose(lockToken ->
221 | CompletableFuture.supplyAsync(() -> {
222 | scope.open();
223 | AsyncSerializationManager.push(scope);
224 | try {
225 | return ChunkSerializer.write(this.level, p_219229_1_);
226 | } finally {
227 | AsyncSerializationManager.pop(scope);
228 | }
229 | }, ChunkIoThreadingExecutorUtils.serializerExecutor)
230 | .thenAcceptAsync(compoundTag -> {
231 | net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.world.ChunkDataEvent.Save(p_219229_1_, p_219229_1_.getWorldForge() != null ? p_219229_1_.getWorldForge() : this.level, compoundTag)); // [VanillaCopy]
232 | this.write(chunkpos, compoundTag);
233 | }, this.mainThreadExecutor)
234 | .handle((unused, throwable) -> {
235 | lockToken.releaseLock();
236 | if (throwable != null)
237 | LOGGER.error("Failed to save chunk {},{}", chunkpos.x, chunkpos.z, throwable);
238 | return unused;
239 | })));
240 | this.markPosition(chunkpos, chunkstatus.getChunkType());
241 | // C2ME end
242 | return true;
243 | } catch (Exception exception) {
244 | LOGGER.error("Failed to save chunk {},{}", chunkpos.x, chunkpos.z, exception);
245 | return false;
246 | }
247 | }
248 | }
249 |
250 | @Inject(method = "tick(Ljava/util/function/BooleanSupplier;)V", at = @At("HEAD"))
251 | private void onTick(CallbackInfo info) {
252 | ChunkIoThreadingExecutorUtils.serializerExecutor.execute(() -> saveFutures.removeIf(CompletableFuture::isDone));
253 | }
254 |
255 | @Override
256 | public void flushWorker() {
257 | final CompletableFuture future = CompletableFuture.allOf(saveFutures.toArray(new CompletableFuture[0]));
258 | this.mainThreadExecutor.managedBlock(future::isDone); // wait for serialization to complete
259 | super.flushWorker();
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/chunkio/MixinVersionedChunkStorage.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.chunkio;
2 |
3 | import com.ibm.asyncutil.locks.AsyncLock;
4 | import com.mojang.datafixers.DataFixer;
5 | import net.minecraft.nbt.CompoundNBT;
6 | import net.minecraft.nbt.NBTUtil;
7 | import net.minecraft.util.RegistryKey;
8 | import net.minecraft.util.SharedConstants;
9 | import net.minecraft.util.datafix.DefaultTypeReferences;
10 | import net.minecraft.world.World;
11 | import net.minecraft.world.chunk.storage.ChunkLoader;
12 | import net.minecraft.world.chunk.storage.IOWorker;
13 | import net.minecraft.world.gen.feature.structure.LegacyStructureDataUtil;
14 | import net.minecraft.world.storage.DimensionSavedDataManager;
15 | import org.spongepowered.asm.mixin.Final;
16 | import org.spongepowered.asm.mixin.Mixin;
17 | import org.spongepowered.asm.mixin.Overwrite;
18 | import org.spongepowered.asm.mixin.Shadow;
19 | import org.spongepowered.asm.mixin.injection.At;
20 | import org.spongepowered.asm.mixin.injection.Inject;
21 | import org.spongepowered.asm.mixin.injection.Redirect;
22 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
23 | import org.yatopiamc.c2me.common.threading.chunkio.C2MECachedRegionStorage;
24 |
25 | import javax.annotation.Nullable;
26 | import java.io.File;
27 | import java.util.function.Supplier;
28 |
29 | @Mixin(ChunkLoader.class)
30 | public abstract class MixinVersionedChunkStorage {
31 |
32 | @Shadow @Final protected DataFixer fixerUpper;
33 |
34 | @Shadow @Nullable
35 | private LegacyStructureDataUtil legacyStructureHandler;
36 |
37 | private AsyncLock featureUpdaterLock = AsyncLock.createFair();
38 |
39 | @Inject(method = "", at = @At("RETURN"))
40 | private void onInit(CallbackInfo info) {
41 | this.featureUpdaterLock = AsyncLock.createFair();
42 | }
43 |
44 | /**
45 | * @author ishland
46 | * @reason async loading
47 | */
48 | @Overwrite
49 | public CompoundNBT upgradeChunkTag(RegistryKey registryKey, Supplier persistentStateManagerFactory, CompoundNBT tag) {
50 | // TODO [VanillaCopy] - check when updating minecraft version
51 | int i = ChunkLoader.getVersion(tag);
52 | if (i < 1493) {
53 | try (final AsyncLock.LockToken ignored = featureUpdaterLock.acquireLock().toCompletableFuture().join()) { // C2ME - async chunk loading
54 | tag = NBTUtil.update(this.fixerUpper, DefaultTypeReferences.CHUNK, tag, i, 1493);
55 | if (tag.getCompound("Level").getBoolean("hasLegacyStructureData")) {
56 | if (this.legacyStructureHandler == null) {
57 | this.legacyStructureHandler = LegacyStructureDataUtil.getLegacyStructureHandler(registryKey, persistentStateManagerFactory.get());
58 | }
59 |
60 | tag = this.legacyStructureHandler.updateFromLegacy(tag);
61 | }
62 | } // C2ME - async chunk loading
63 | }
64 |
65 | tag = NBTUtil.update(this.fixerUpper, DefaultTypeReferences.CHUNK, tag, Math.max(1493, i));
66 | if (i < SharedConstants.getCurrentVersion().getWorldVersion()) {
67 | tag.putInt("DataVersion", SharedConstants.getCurrentVersion().getWorldVersion());
68 | }
69 |
70 | return tag;
71 | }
72 |
73 | @Redirect(method = "write", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/gen/feature/structure/LegacyStructureDataUtil;removeIndex(J)V"))
74 | private void onSetTagAtFeatureUpdaterMarkResolved(LegacyStructureDataUtil featureUpdater, long l) {
75 | try (final AsyncLock.LockToken ignored = featureUpdaterLock.acquireLock().toCompletableFuture().join()) {
76 | featureUpdater.removeIndex(l);
77 | }
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/lighting/MixinServerLightingProvider.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.lighting;
2 |
3 | import net.minecraft.world.server.ServerWorldLightManager;
4 | import org.spongepowered.asm.mixin.Dynamic;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.Shadow;
7 | import org.spongepowered.asm.mixin.injection.At;
8 | import org.spongepowered.asm.mixin.injection.Inject;
9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
10 |
11 | @Mixin(ServerWorldLightManager.class)
12 | public abstract class MixinServerLightingProvider {
13 |
14 | @Shadow public abstract void tryScheduleUpdate();
15 |
16 | @Dynamic
17 | @Inject(method = {"lambda$tryScheduleUpdate$22", "func_223124_c"}, at = @At("RETURN"))
18 | private void onPostRunTask(CallbackInfo info) {
19 | this.tryScheduleUpdate(); // Run more tasks
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/worldgen/MixinChunkStatus.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.worldgen;
2 |
3 | import com.mojang.datafixers.util.Either;
4 | import net.minecraft.util.registry.Registry;
5 | import net.minecraft.world.chunk.ChunkStatus;
6 | import net.minecraft.world.chunk.IChunk;
7 | import net.minecraft.world.gen.ChunkGenerator;
8 | import net.minecraft.world.gen.feature.template.TemplateManager;
9 | import net.minecraft.world.server.ChunkHolder;
10 | import net.minecraft.world.server.ServerWorld;
11 | import net.minecraft.world.server.ServerWorldLightManager;
12 | import org.spongepowered.asm.mixin.Dynamic;
13 | import org.spongepowered.asm.mixin.Final;
14 | import org.spongepowered.asm.mixin.Mixin;
15 | import org.spongepowered.asm.mixin.Overwrite;
16 | import org.spongepowered.asm.mixin.Shadow;
17 | import org.spongepowered.asm.mixin.injection.At;
18 | import org.spongepowered.asm.mixin.injection.Inject;
19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
20 | import org.yatopiamc.c2me.common.config.C2MEConfig;
21 | import org.yatopiamc.c2me.common.threading.worldgen.ChunkStatusUtils;
22 | import org.yatopiamc.c2me.common.threading.worldgen.IChunkStatus;
23 | import org.yatopiamc.c2me.common.threading.worldgen.IWorldGenLockable;
24 |
25 | import java.util.List;
26 | import java.util.concurrent.CompletableFuture;
27 | import java.util.function.Function;
28 | import java.util.function.Supplier;
29 |
30 | @Mixin(ChunkStatus.class)
31 | public abstract class MixinChunkStatus implements IChunkStatus {
32 |
33 | @Shadow
34 | @Final
35 | private ChunkStatus.IGenerationWorker generationTask;
36 |
37 | private int reducedTaskRadius = -1;
38 |
39 | @Shadow @Final private int range;
40 |
41 | public void calculateReducedTaskRadius() {
42 | if (this.range == 0) {
43 | this.reducedTaskRadius = 0;
44 | } else {
45 | for (int i = 0; i <= this.range; i++) {
46 | final ChunkStatus status = ChunkStatus.getStatus(ChunkStatus.getDistance((ChunkStatus) (Object) this) + i); // TODO [VanillaCopy] from TACS getRequiredStatusForGeneration
47 | if (status == ChunkStatus.STRUCTURE_STARTS) {
48 | this.reducedTaskRadius = Math.min(this.range, i);
49 | break;
50 | }
51 | }
52 | }
53 | //noinspection ConstantConditions
54 | if ((Object) this == ChunkStatus.LIGHT) {
55 | this.reducedTaskRadius = 1;
56 | }
57 | System.out.println(String.format("%s task radius: %d -> %d", this, this.range, this.reducedTaskRadius));
58 | }
59 |
60 | @Dynamic
61 | @Inject(method = "", at = @At("RETURN"))
62 | private static void onCLInit(CallbackInfo info) {
63 | for (ChunkStatus chunkStatus : Registry.CHUNK_STATUS) {
64 | ((IChunkStatus) chunkStatus).calculateReducedTaskRadius();
65 | }
66 | }
67 |
68 | /**
69 | * @author ishland
70 | * @reason take over generation & improve chunk status transition speed
71 | */
72 | @Overwrite
73 | public CompletableFuture> generate(ServerWorld world, ChunkGenerator chunkGenerator, TemplateManager structureManager, ServerWorldLightManager lightingProvider, Function>> function, List chunks) {
74 | final IChunk targetChunk = chunks.get(chunks.size() / 2);
75 | Supplier>> generationTask = () ->
76 | this.generationTask.doWork((ChunkStatus) (Object) this, world, chunkGenerator, structureManager, lightingProvider, function, chunks, targetChunk);
77 | if (targetChunk.getStatus().isOrAfter((ChunkStatus) (Object) this)) {
78 | return generationTask.get();
79 | } else {
80 | int lockRadius = C2MEConfig.threadedWorldGenConfig.reduceLockRadius && this.reducedTaskRadius != -1 ? this.reducedTaskRadius : this.range;
81 | //noinspection ConstantConditions
82 | return ChunkStatusUtils.runChunkGenWithLock(targetChunk.getPos(), lockRadius, ((IWorldGenLockable) world).getWorldGenChunkLock(), () ->
83 | ChunkStatusUtils.getThreadingType((ChunkStatus) (Object) this).runTask(((IWorldGenLockable) world).getWorldGenSingleThreadedLock(), generationTask));
84 | }
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/worldgen/MixinServerWorld.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.worldgen;
2 |
3 | import com.ibm.asyncutil.locks.AsyncLock;
4 | import com.ibm.asyncutil.locks.AsyncNamedLock;
5 | import net.minecraft.util.math.ChunkPos;
6 | import net.minecraft.world.server.ServerWorld;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.injection.At;
9 | import org.spongepowered.asm.mixin.injection.Inject;
10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
11 | import org.yatopiamc.c2me.common.threading.worldgen.IWorldGenLockable;
12 |
13 | @Mixin(ServerWorld.class)
14 | public class MixinServerWorld implements IWorldGenLockable {
15 |
16 | private volatile AsyncLock worldGenSingleThreadedLock = null;
17 | private volatile AsyncNamedLock worldGenChunkLock = null;
18 |
19 | @Inject(method = "", at = @At("RETURN"))
20 | private void initWorldGenSingleThreadedLock(CallbackInfo ci) {
21 | worldGenSingleThreadedLock = AsyncLock.createFair();
22 | worldGenChunkLock = AsyncNamedLock.createFair();
23 | }
24 |
25 | @Override
26 | public AsyncLock getWorldGenSingleThreadedLock() {
27 | return worldGenSingleThreadedLock;
28 | }
29 |
30 | @Override
31 | public AsyncNamedLock getWorldGenChunkLock() {
32 | return worldGenChunkLock;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/worldgen/MixinStructureManager.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.worldgen;
2 |
3 | import net.minecraft.util.ResourceLocation;
4 | import net.minecraft.world.gen.feature.template.Template;
5 | import net.minecraft.world.gen.feature.template.TemplateManager;
6 | import org.spongepowered.asm.mixin.Final;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.Mutable;
9 | import org.spongepowered.asm.mixin.Shadow;
10 | import org.spongepowered.asm.mixin.injection.At;
11 | import org.spongepowered.asm.mixin.injection.Inject;
12 | import org.spongepowered.asm.mixin.injection.Redirect;
13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
14 |
15 | import java.util.Map;
16 | import java.util.concurrent.ConcurrentHashMap;
17 | import java.util.function.Function;
18 |
19 | @Mixin(TemplateManager.class)
20 | public class MixinStructureManager {
21 |
22 | @Mutable
23 | @Shadow
24 | @Final
25 | private Map structureRepository;
26 |
27 | @Inject(method = "", at = @At("TAIL"))
28 | private void onPostInit(CallbackInfo info) {
29 | this.structureRepository = new ConcurrentHashMap<>();
30 | }
31 |
32 | @Redirect(method = "get", at = @At(value = "INVOKE", target = "Ljava/util/Map;computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;"))
33 | private V onGetStructureComputeIfAbsent(Map map, K key, Function super K, ? extends V> mappingFunction) {
34 | if (map.containsKey(key)) return map.get(key);
35 | final V value = mappingFunction.apply(key);
36 | if (value == null) return null;
37 | map.put(key, value);
38 | return value;
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/worldgen/MixinStructurePalettedBlockInfoList.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.worldgen;
2 |
3 | import net.minecraft.block.Block;
4 | import net.minecraft.world.gen.feature.template.Template;
5 | import org.spongepowered.asm.mixin.Final;
6 | import org.spongepowered.asm.mixin.Mixin;
7 | import org.spongepowered.asm.mixin.Mutable;
8 | import org.spongepowered.asm.mixin.Shadow;
9 | import org.spongepowered.asm.mixin.injection.At;
10 | import org.spongepowered.asm.mixin.injection.Inject;
11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
12 |
13 | import java.util.List;
14 | import java.util.Map;
15 | import java.util.concurrent.ConcurrentHashMap;
16 |
17 | @Mixin(Template.Palette.class)
18 | public class MixinStructurePalettedBlockInfoList {
19 |
20 | @Mutable
21 | @Shadow @Final private Map> cache;
22 |
23 | @Inject(method = "", at = @At("RETURN"))
24 | private void onInit(CallbackInfo info) {
25 | this.cache = new ConcurrentHashMap<>();
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/worldgen/MixinThreadedAnvilChunkStorage.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.worldgen;
2 |
3 | import com.mojang.datafixers.util.Either;
4 | import net.minecraft.util.concurrent.ThreadTaskExecutor;
5 | import net.minecraft.world.chunk.Chunk;
6 | import net.minecraft.world.chunk.ChunkStatus;
7 | import net.minecraft.world.chunk.IChunk;
8 | import net.minecraft.world.server.ChunkHolder;
9 | import net.minecraft.world.server.ChunkManager;
10 | import net.minecraft.world.server.ServerWorld;
11 | import org.spongepowered.asm.mixin.Dynamic;
12 | import org.spongepowered.asm.mixin.Final;
13 | import org.spongepowered.asm.mixin.Mixin;
14 | import org.spongepowered.asm.mixin.Overwrite;
15 | import org.spongepowered.asm.mixin.Shadow;
16 | import org.spongepowered.asm.mixin.injection.At;
17 | import org.spongepowered.asm.mixin.injection.Inject;
18 | import org.spongepowered.asm.mixin.injection.Redirect;
19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
20 | import org.yatopiamc.c2me.common.threading.GlobalExecutors;
21 |
22 | import java.util.concurrent.CompletableFuture;
23 | import java.util.concurrent.CompletionStage;
24 | import java.util.concurrent.Executor;
25 | import java.util.function.Function;
26 |
27 | @Mixin(ChunkManager.class)
28 | public class MixinThreadedAnvilChunkStorage {
29 |
30 | @Shadow @Final private ServerWorld level;
31 | @Shadow @Final private ThreadTaskExecutor mainThreadExecutor;
32 |
33 | private final Executor mainInvokingExecutor = runnable -> {
34 | if (this.level.getServer().isSameThread()) {
35 | runnable.run();
36 | } else {
37 | this.mainThreadExecutor.execute(runnable);
38 | }
39 | };
40 |
41 | private final ThreadLocal capturedRequiredStatus = new ThreadLocal<>();
42 |
43 | @Inject(method = "scheduleChunkGeneration", at = @At("HEAD"))
44 | private void onUpgradeChunk(ChunkHolder holder, ChunkStatus requiredStatus, CallbackInfoReturnable>> cir) {
45 | capturedRequiredStatus.set(requiredStatus);
46 | }
47 |
48 | @Redirect(method = "getEntityTickingRangeFuture", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
49 | private CompletableFuture redirectMainThreadExecutor1(CompletableFuture completableFuture, Function super T, ? extends U> fn, Executor executor) {
50 | return completableFuture.thenApplyAsync(fn, this.mainInvokingExecutor);
51 | }
52 |
53 | @Redirect(method = "schedule", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenComposeAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
54 | private CompletableFuture redirectMainThreadExecutor2(CompletableFuture completableFuture, Function super T, ? extends CompletionStage> fn, Executor executor) {
55 | return completableFuture.thenComposeAsync(fn, this.mainInvokingExecutor);
56 | }
57 |
58 | /**
59 | * @author ishland
60 | * @reason move to scheduler & improve chunk status transition speed
61 | */
62 | @SuppressWarnings("OverwriteTarget")
63 | @Dynamic
64 | @Overwrite(aliases = "func_219216_e_")
65 | private void lambda$scheduleChunkGeneration$21(ChunkHolder chunkHolder, Runnable runnable) { // synthetic method for worldGenExecutor scheduling in upgradeChunk
66 | final ChunkStatus capturedStatus = capturedRequiredStatus.get();
67 | capturedRequiredStatus.remove();
68 | if (capturedStatus != null) {
69 | final IChunk currentChunk = chunkHolder.getLastAvailable();
70 | if (currentChunk != null && currentChunk.getStatus().isOrAfter(capturedStatus)) {
71 | this.mainInvokingExecutor.execute(runnable);
72 | return;
73 | }
74 | }
75 | GlobalExecutors.scheduler.execute(runnable);
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/threading/worldgen/MixinWeightedBlockStateProvider.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.threading.worldgen;
2 |
3 | import net.minecraft.block.BlockState;
4 | import net.minecraft.util.WeightedList;
5 | import net.minecraft.util.math.BlockPos;
6 | import net.minecraft.world.gen.blockstateprovider.WeightedBlockStateProvider;
7 | import org.spongepowered.asm.mixin.Final;
8 | import org.spongepowered.asm.mixin.Mixin;
9 | import org.spongepowered.asm.mixin.Overwrite;
10 | import org.spongepowered.asm.mixin.Shadow;
11 |
12 | import java.util.Random;
13 |
14 | @Mixin(WeightedBlockStateProvider.class)
15 | public class MixinWeightedBlockStateProvider {
16 |
17 | @Shadow @Final private WeightedList weightedList;
18 |
19 | /**
20 | * @author ishland
21 | * @reason thread-safe getBlockState
22 | */
23 | @Overwrite
24 | public BlockState getState(Random random, BlockPos pos) {
25 | return new WeightedList<>(weightedList.entries).getOne(random);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/util/accessor/IServerChunkProvider.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.util.accessor;
2 |
3 | import net.minecraft.world.server.ChunkHolder;
4 | import net.minecraft.world.server.ServerChunkProvider;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.gen.Invoker;
7 |
8 | @Mixin(ServerChunkProvider.class)
9 | public interface IServerChunkProvider {
10 |
11 | @Invoker("getVisibleChunkIfPresent")
12 | ChunkHolder IGetVisibleChunkIfPresent(long p_219219_1_);
13 |
14 | @Invoker("runDistanceManagerUpdates")
15 | boolean IRunDistanceManagerUpdates();
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/util/accessor/IThreadTaskExecutor.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.util.accessor;
2 |
3 | import net.minecraft.util.concurrent.ThreadTaskExecutor;
4 | import org.spongepowered.asm.mixin.Mixin;
5 | import org.spongepowered.asm.mixin.gen.Invoker;
6 |
7 | @Mixin(ThreadTaskExecutor.class)
8 | public interface IThreadTaskExecutor {
9 |
10 | @Invoker("pollTask")
11 | boolean IPollTask();
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/util/chunkgenerator/MixinCommandGenerate.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.util.chunkgenerator;
2 |
3 | import net.minecraft.command.CommandSource;
4 | import net.minecraft.util.math.BlockPos;
5 | import net.minecraft.world.server.ServerWorld;
6 | import net.minecraftforge.server.command.ChunkGenWorker;
7 | import org.spongepowered.asm.mixin.Dynamic;
8 | import org.spongepowered.asm.mixin.Mixin;
9 | import org.spongepowered.asm.mixin.injection.At;
10 | import org.spongepowered.asm.mixin.injection.Redirect;
11 | import org.yatopiamc.c2me.common.threading.worldgen.C2MEChunkGenWorker;
12 |
13 | @Mixin(targets = "net.minecraftforge.server.command.CommandGenerate")
14 | public class MixinCommandGenerate {
15 |
16 | @Redirect(method = "execute", at = @At(value = "NEW", target = "net/minecraftforge/server/command/ChunkGenWorker"), remap = false)
17 | private static ChunkGenWorker redirectChunkGenWorkerInit(CommandSource listener, BlockPos start, int total, ServerWorld dim, int interval) {
18 | return new C2MEChunkGenWorker(listener, start, total, dim, interval);
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/util/log4j2shutdownhookisnomore/MixinMain.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.util.log4j2shutdownhookisnomore;
2 |
3 | import net.minecraft.server.Main;
4 | import org.apache.logging.log4j.LogManager;
5 | import org.apache.logging.log4j.core.impl.Log4jContextFactory;
6 | import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.injection.At;
9 | import org.spongepowered.asm.mixin.injection.Inject;
10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
11 |
12 | @Mixin(Main.class)
13 | public class MixinMain {
14 |
15 | @Inject(method = "main", at = @At("HEAD"))
16 | private static void preMain(CallbackInfo info) {
17 | try {
18 | ((DefaultShutdownCallbackRegistry) ((Log4jContextFactory) LogManager.getFactory()).getShutdownCallbackRegistry()).stop();
19 | } catch (Throwable t) {
20 | System.err.println("Unable to remove log4j2 shutdown hook");
21 | t.printStackTrace();
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/util/log4j2shutdownhookisnomore/MixinMinecraftDedicatedServer.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.util.log4j2shutdownhookisnomore;
2 |
3 | import net.minecraft.server.dedicated.DedicatedServer;
4 | import org.apache.logging.log4j.LogManager;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.injection.At;
7 | import org.spongepowered.asm.mixin.injection.Inject;
8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
9 |
10 | @Mixin(DedicatedServer.class)
11 | public class MixinMinecraftDedicatedServer {
12 |
13 | @Inject(method = "onServerExit", at = @At("RETURN"))
14 | private void onPostShutdown(CallbackInfo ci) {
15 | LogManager.shutdown();
16 | new Thread(() -> System.exit(0)).start();
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/yatopiamc/c2me/mixin/util/progresslogger/MixinWorldGenerationProgressLogger.java:
--------------------------------------------------------------------------------
1 | package org.yatopiamc.c2me.mixin.util.progresslogger;
2 |
3 | import net.minecraft.util.math.ChunkPos;
4 | import net.minecraft.util.math.MathHelper;
5 | import net.minecraft.world.chunk.ChunkStatus;
6 | import net.minecraft.world.chunk.listener.LoggingChunkStatusListener;
7 | import org.apache.logging.log4j.Logger;
8 | import org.spongepowered.asm.mixin.Final;
9 | import org.spongepowered.asm.mixin.Mixin;
10 | import org.spongepowered.asm.mixin.Overwrite;
11 | import org.spongepowered.asm.mixin.Shadow;
12 | import org.spongepowered.asm.mixin.injection.At;
13 | import org.spongepowered.asm.mixin.injection.Inject;
14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
15 |
16 | @Mixin(LoggingChunkStatusListener.class)
17 | public class MixinWorldGenerationProgressLogger {
18 |
19 | @Shadow
20 | @Final
21 | private static Logger LOGGER;
22 | @Shadow
23 | @Final
24 | private int maxCount;
25 | private volatile ChunkPos spawnPos = null;
26 | private volatile int radius = 0;
27 | private volatile int chunkStatusTransitions = 0;
28 | private volatile int chunkStatuses = 0;
29 |
30 | @Inject(method = "", at = @At("RETURN"))
31 | private void onInit(int radius, CallbackInfo info) {
32 | ChunkStatus status = ChunkStatus.FULL;
33 | this.radius = radius;
34 | chunkStatuses = 0;
35 | chunkStatusTransitions = 0;
36 | while ((status = status.getParent()) != ChunkStatus.EMPTY)
37 | chunkStatuses++;
38 | chunkStatuses++;
39 | }
40 |
41 | @Inject(method = "updateSpawnPos", at = @At("RETURN"))
42 | private void onStart(ChunkPos spawnPos, CallbackInfo ci) {
43 | this.spawnPos = spawnPos;
44 | }
45 |
46 | @Inject(method = "onStatusChange", at = @At("HEAD"))
47 | private void onSetChunkStatus(ChunkPos pos, ChunkStatus status, CallbackInfo ci) {
48 | if (status != null && (this.spawnPos == null || pos.getChessboardDistance(spawnPos) <= radius)) this.chunkStatusTransitions++;
49 | }
50 |
51 | /**
52 | * @author ishland
53 | * @reason replace impl
54 | */
55 | @Overwrite
56 | public int getProgress() {
57 | // LOGGER.info("{} / {}", chunkStatusTransitions, maxCount * chunkStatuses);
58 | return MathHelper.floor((float) this.chunkStatusTransitions * 100.0F / (float) (this.maxCount * chunkStatuses));
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/accesstransformer.cfg:
--------------------------------------------------------------------------------
1 | public net.minecraft.world.chunk.ChunkStatus$IGenerationWorker
2 | public-f net.minecraft.world.server.ServerChunkProvider$ChunkExecutor
3 | public net.minecraft.world.chunk.storage.IOWorker$Entry
4 |
5 | public net.minecraft.world.chunk.storage.RegionFileCache (Ljava/io/File;Z)V
6 | public net.minecraft.world.chunk.storage.RegionFileCache func_219098_a(Lnet/minecraft/util/math/ChunkPos;)Lnet/minecraft/world/chunk/storage/RegionFile;
7 | public net.minecraft.world.chunk.ChunkStatus$IGenerationWorker doWork(Lnet/minecraft/world/chunk/ChunkStatus;Lnet/minecraft/world/server/ServerWorld;Lnet/minecraft/world/gen/ChunkGenerator;Lnet/minecraft/world/gen/feature/template/TemplateManager;Lnet/minecraft/world/server/ServerWorldLightManager;Ljava/util/function/Function;Ljava/util/List;Lnet/minecraft/world/chunk/IChunk;)Ljava/util/concurrent/CompletableFuture;
8 | public net.minecraft.world.SerializableTickList (Ljava/util/function/Function;Ljava/util/List;)V
9 | public net.minecraft.util.WeightedList (Ljava/util/List;)V
10 |
11 | public net.minecraft.world.server.ServerTickList field_205376_f
12 | public net.minecraft.world.server.ServerChunkProvider field_217243_i
13 | public net.minecraft.world.chunk.storage.RegionSectionCache field_227173_b_
14 | public net.minecraft.world.chunk.storage.ChunkLoader field_227077_a_
15 | public net.minecraft.util.WeightedList field_220658_a
16 | public net.minecraft.world.server.ServerWorld field_241102_C_
17 | public net.minecraft.world.chunk.storage.IOWorker$Entry field_227113_a_
18 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/mods.toml:
--------------------------------------------------------------------------------
1 | # This is an example mods.toml file. It contains the data relating to the loading mods.
2 | # There are several mandatory fields (#mandatory), and many more that are optional (#optional).
3 | # The overall format is standard TOML format, v0.5.0.
4 | # Note that there are a couple of TOML lists in this file.
5 | # Find more information on toml format here: https://github.com/toml-lang/toml
6 | # The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
7 | modLoader="javafml" #mandatory
8 | # A version range to match for said mod loader - for regular FML @Mod it will be the forge version
9 | loaderVersion="[35,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.
10 | # The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
11 | # Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
12 | license="MIT"
13 | # A URL to refer people to when problems occur with this mod
14 | #issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional
15 | # A list of mods - how many allowed here is determined by the individual mod loader
16 | [[mods]] #mandatory
17 | # The modid of the mod
18 | modId="c2me" #mandatory
19 | # The version number of the mod - there's a few well known ${} variables useable here or just hardcode it
20 | # ${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata
21 | # see the associated build.gradle script for how to populate this completely automatically during a build
22 | version="${file.jarVersion}" #mandatory
23 | # A display name for the mod
24 | displayName="Concurrent Chunk Management Engine" #mandatory
25 | # A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/
26 | #updateJSONURL="https://change.me.example.invalid/updates.json" #optional
27 | # A URL for the "homepage" for this mod, displayed in the mod UI
28 | #displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
29 | # A file name (in the root of the mod JAR) containing a logo for display
30 | logoFile="c2me.png" #optional
31 | # A text field displayed in the mod UI
32 | #credits="Thanks for this example mod goes to Java" #optional
33 | # A text field displayed in the mod UI
34 | authors="ishland" #optional
35 | # The description text for the mod (multi line!) (#mandatory)
36 | description='''
37 | A Forge mod designed to improve the chunk performance of Minecraft.
38 | '''
39 | # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
40 | [[dependencies.examplemod]] #optional
41 | # the modid of the dependency
42 | modId="forge" #mandatory
43 | # Does this dependency have to exist - if not, ordering below must be specified
44 | mandatory=true #mandatory
45 | # The version range of the dependency
46 | versionRange="[35,)" #mandatory
47 | # An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory
48 | ordering="NONE"
49 | # Side this dependency is applied on - BOTH, CLIENT or SERVER
50 | side="BOTH"
51 | # Here's another dependency
52 | [[dependencies.c2me]]
53 | modId="minecraft"
54 | mandatory=true
55 | # This version range declares a minimum of the current minecraft version up to but not including the next major version
56 | versionRange="[1.16.4,1.17)"
57 | ordering="NONE"
58 | side="BOTH"
59 |
--------------------------------------------------------------------------------
/src/main/resources/c2me.mixins.json:
--------------------------------------------------------------------------------
1 | {
2 | "required": true,
3 | "minVersion": "0.8",
4 | "package": "org.yatopiamc.c2me.mixin",
5 | "compatibilityLevel": "JAVA_11",
6 | "mixins": [
7 | "chunkscheduling.fix_unload.MixinThreadedAnvilChunkStorage",
8 | "optimizations.thread_local_biome_sampler.MixinOverworldBiomeProvider",
9 | "threading.chunkio.MixinChunkSerializer",
10 | "threading.chunkio.MixinChunkTickScheduler",
11 | "threading.chunkio.MixinScheduledTick",
12 | "threading.chunkio.MixinSerializingRegionBasedStorage",
13 | "threading.chunkio.MixinServerChunkManagerMainThreadExecutor",
14 | "threading.chunkio.MixinSimpleTickScheduler",
15 | "threading.chunkio.MixinStorageIoWorker",
16 | "threading.chunkio.MixinThreadedAnvilChunkStorage",
17 | "threading.chunkio.MixinVersionedChunkStorage",
18 | "threading.lighting.MixinServerLightingProvider",
19 | "threading.worldgen.MixinChunkStatus",
20 | "threading.worldgen.MixinServerWorld",
21 | "threading.worldgen.MixinStructureManager",
22 | "threading.worldgen.MixinStructurePalettedBlockInfoList",
23 | "threading.worldgen.MixinThreadedAnvilChunkStorage",
24 | "threading.worldgen.MixinWeightedBlockStateProvider",
25 | "util.accessor.IServerChunkProvider",
26 | "util.accessor.IThreadTaskExecutor",
27 | "util.chunkgenerator.MixinCommandGenerate",
28 | "util.progresslogger.MixinWorldGenerationProgressLogger"
29 | ],
30 | "client": [
31 | ],
32 | "plugin": "org.yatopiamc.c2me.mixin.C2MEMixinPlugin",
33 | "injectors": {
34 | "defaultRequire": 1
35 | },
36 | "refmap": "c2me.refmap.json",
37 | "setSourceFile": true
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/resources/c2me.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RelativityMC/C2ME-forge/3e9c6b49497f6c6bd492c57f9bfde5e16b8b803f/src/main/resources/c2me.png
--------------------------------------------------------------------------------