Skip to content

Commit

Permalink
Merge branch 'dev/1.21.3' into dev/1.21.4
Browse files Browse the repository at this point in the history
# Conflicts:
#	.github/workflows/build.yml
  • Loading branch information
ishland committed Nov 8, 2024
2 parents 466b093 + 54bd535 commit bd55340
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 15 deletions.
33 changes: 26 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,33 @@ C^2M-Engine, or C2ME for short, is a Fabric mod designed to improve the performa
## What does C2ME stand for?
Concurrent chunk management engine, it's about making the game better threaded and more scalable in regard to world gen and chunk io performance.

## So what is C2ME not?
**C2ME is currently in alpha stage and pretty experimental.**
Although it is usable in most cases and tested during build time, it doesn't mean that it is fully stable for a production server.
So backup your worlds and practice good game modding skills.
## Vanilla parity
C2ME does not sacrifice vanilla functionality or behavior, or alter the vanilla world generation in the name of raw speed by default.
However, due to the [non-determinism of vanilla world generation](https://bugs.mojang.com/browse/MC-55596), worlds will vary
significantly run-to-run even with the same seed. This is not a bug on our side.

While we carefully check that we do not modify any vanilla behavior, bugs are unavoidable after all.
So, if you do encounter an issue where C2ME deviates from the intended vanilla behavior, don't hesitate to open an issue.

## Mod and Datapack compatibility
World generation datapacks that can run on vanilla Minecraft are fully supported.
Custom world generators implemented in mods usually runs well, but *may* cause compatibility issues due to certain
design assumption used by mod authors being broken for further speedups of world generation.
As a world generation mod author, if you find your mod broken, don't hesitate to look for help in our discord server (linked below).
We are willing to help mod authors to embrace scalable world generation.

### Undefined behavior sanitization
C2ME includes `CheckedThreadLocalRandom` for world random (included in [UWRAD](https://modrinth.com/mod/uwrad)) plus a few others.
These detections exist to prevent mods from screwing up Minecraft internals and causing undebuggable problems.
The detection should almost **never** produce false positives, and should be taken seriously and reported
to corresponding mod authors instead.

## Usage notice
**Backup your worlds and practice good game modding skills.**

## Downloads
Modrinth: https://modrinth.com/mod/c2me-fabric
CurseForge: https://www.curseforge.com/minecraft/mc-mods/c2me-fabric
Modrinth: https://modrinth.com/mod/c2me-fabric
CurseForge: https://www.curseforge.com/minecraft/mc-mods/c2me

## Support status for Minecraft versions
Only the latest Minecraft release and the latest Minecraft snapshot are fully supported.
Expand All @@ -33,7 +52,7 @@ Our issue tracker: [link](https://github.com/RelativityMC/C2ME-fabric/issues)
Our discord server: [link](https://discord.gg/Kdy8NM5HW4)

## Building and setting up
JDK 22+, Clang 18+ and MinGW are required to build C2ME
JDK 22+, Clang 18+ are required to build C2ME
Run the following commands in the root directory:

```shell
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,30 @@ Whether to allow POIs (Point of Interest) to be unloaded
""")
.getBoolean(true, false);

public static final boolean suppressGhostMushrooms = new ConfigSystem.ConfigAccessor()
.key("chunkSystem.suppressGhostMushrooms")
.comment("""
This option workarounds MC-276863, a bug that makes mushrooms appear in non-postprocessed chunks
This bug is amplified with notickvd as it exposes non-postprocessed chunks to players
This should not affect other worldgen behavior and game mechanics in general
""")
.getBoolean(true, false);

public static final boolean syncPlayerTickets = new ConfigSystem.ConfigAccessor()
.key("chunkSystem.syncPlayerTickets")
.comment("""
Whether to synchronize the management of player tickets
In vanilla Minecraft, player tickets are not always removed immediately when players leave an area.
The delay in removal increases with the chunk system’s throughput, but due to vanilla’s typically
slow chunk loading, tickets are almost always removed immediately. However, some contraptions rely
on this immediate removal behavior and tend to be broken with the increased chunk throughput.
Enabling this option synchronizes player ticket handling, making it more predictable and
thus improving compatibility with these contraptions.
""")
.getBoolean(true, false);

public static void init() {
// intentionally empty
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public CompletionStage<Void> upgradeToThis(ChunkLoadingContext context) {
public CompletionStage<Void> downgradeFromThis(ChunkLoadingContext context) {
throw new UnsupportedOperationException();
}

@Override
public int toVanillaLevel() {
return ChunkLevels.INACCESSIBLE + 1;
}
};
statuses.add(NEW);
DISK = Config.asyncSerialization ? new ReadFromDiskAsync(statuses.size()) : new ReadFromDisk(statuses.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,22 @@
import com.ishland.c2me.rewrites.chunksystem.common.fapi.LifecycleEventInvoker;
import com.ishland.flowsched.scheduler.ItemHolder;
import com.ishland.flowsched.scheduler.KeyStatusPair;
import it.unimi.dsi.fastutil.shorts.ShortList;
import it.unimi.dsi.fastutil.shorts.ShortListIterator;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.SpawnReason;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.server.world.ChunkLevels;
import net.minecraft.server.world.ServerChunkLoadingManager;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.ChunkRegion;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkGenerationStep;
import net.minecraft.world.chunk.ChunkGenerationSteps;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.chunk.ProtoChunk;
import net.minecraft.world.chunk.WorldChunk;
Expand Down Expand Up @@ -56,6 +64,31 @@ public CompletionStage<Void> upgradeToThis(ChunkLoadingContext context) {
final Chunk chunk = context.holder().getItem().get().chunk();
Preconditions.checkState(chunk instanceof ProtoChunk, "Chunk must be a proto chunk");
ProtoChunk protoChunk = (ProtoChunk) chunk;

if (Config.suppressGhostMushrooms) {
ServerWorld serverWorld = ((IThreadedAnvilChunkStorage) context.tacs()).getWorld();
ChunkRegion chunkRegion = new ChunkRegion(serverWorld, context.chunks(), ChunkGenerationSteps.GENERATION.get(ChunkStatus.FULL), chunk);

ChunkPos chunkPos = context.holder().getKey();

ShortList[] postProcessingLists = protoChunk.getPostProcessingLists();
for (int i = 0; i < postProcessingLists.length; i++) {
if (postProcessingLists[i] != null) {
for (ShortListIterator iterator = postProcessingLists[i].iterator(); iterator.hasNext(); ) {
short short_ = iterator.nextShort();
BlockPos blockPos = ProtoChunk.joinBlockPos(short_, protoChunk.sectionIndexToCoord(i), chunkPos);
BlockState blockState = protoChunk.getBlockState(blockPos);

if (blockState.getBlock() == Blocks.BROWN_MUSHROOM || blockState.getBlock() == Blocks.RED_MUSHROOM) {
if (!blockState.canPlaceAt(chunkRegion, blockPos)) {
protoChunk.setBlockState(blockPos, Blocks.AIR.getDefaultState(), false); // TODO depends on the fact that the chunk system always locks the current chunk
}
}
}
}
}
}

return CompletableFuture.runAsync(() -> {
ServerWorld serverWorld = ((IThreadedAnvilChunkStorage) context.tacs()).getWorld();
final WorldChunk worldChunk = toFullChunk(protoChunk, serverWorld);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package com.ishland.c2me.rewrites.chunksystem.mixin;

import com.bawnorton.mixinsquared.TargetHandler;
import com.ishland.c2me.rewrites.chunksystem.common.Config;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.server.world.ChunkHolder;
import net.minecraft.server.world.ChunkTicketManager;
import net.minecraft.server.world.ThrottledChunkTaskScheduler;
import net.minecraft.util.thread.TaskExecutor;
import org.spongepowered.asm.mixin.Dynamic;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

import java.util.concurrent.Executor;

@Mixin(value = ChunkTicketManager.class, priority = 1051)
public class MixinChunkTicketManager {

Expand All @@ -21,4 +28,13 @@ private int fakeLevel(ChunkHolder instance) {
return Integer.MAX_VALUE;
}

@WrapOperation(method = "<init>", at = @At(value = "NEW", target = "(Lnet/minecraft/util/thread/TaskExecutor;Ljava/util/concurrent/Executor;I)Lnet/minecraft/server/world/ThrottledChunkTaskScheduler;"))
private ThrottledChunkTaskScheduler syncPlayerTickets(TaskExecutor<Runnable> executor, Executor dispatchExecutor, int maxConcurrentChunks, Operation<ThrottledChunkTaskScheduler> original, Executor workerExecutor, Executor mainThreadExecutor) {
if (Config.syncPlayerTickets) {
return original.call(executor, mainThreadExecutor, maxConcurrentChunks); // improve player ticket consistency
} else {
return original.call(executor, dispatchExecutor, maxConcurrentChunks);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,64 @@ public abstract class MixinServerChunkManager {
@Shadow
protected abstract @Nullable ChunkHolder getChunkHolder(long pos);

@Shadow @Final private long[] chunkPosCache;

@Shadow @Final private ChunkStatus[] chunkStatusCache;

@Shadow @Final private Chunk[] chunkCache;

@Inject(method = "getChunk(IILnet/minecraft/world/chunk/ChunkStatus;Z)Lnet/minecraft/world/chunk/Chunk;", at = @At("HEAD"), cancellable = true)
private void shortcutGetChunk(int x, int z, ChunkStatus leastStatus, boolean create, CallbackInfoReturnable<Chunk> cir) {
if (Thread.currentThread() != this.serverThread) {
final ChunkHolder holder = this.getChunkHolder(ChunkPos.toLong(x, z));
if (holder != null) {
final CompletableFuture<OptionalChunk<Chunk>> future = holder.load(leastStatus, this.chunkLoadingManager); // thread-safe in new system
Chunk chunk = future.getNow(ChunkHolder.UNLOADED).orElse(null);
if (chunk instanceof WrapperProtoChunk readOnlyChunk) chunk = readOnlyChunk.getWrappedChunk();
Chunk chunk = this.c2me$fastpathExistingChunks(x, z, leastStatus);
if (chunk != null) {
cir.setReturnValue(chunk);
return;
} else if (!create) {
cir.setReturnValue(null);
return;
}
} else { // on server thread
if (!create) { // no create required
Chunk chunk = this.c2me$getFromCahe(x, z, leastStatus, false);
if (chunk != null) {
cir.setReturnValue(chunk); // also cancels
return;
cir.setReturnValue(chunk);
} else {
cir.setReturnValue(this.c2me$fastpathExistingChunks(x, z, leastStatus));
}
return;
}
}
}

@Unique
private Chunk c2me$getFromCahe(int x, int z, ChunkStatus leastStatus, boolean create) {
long l = ChunkPos.toLong(x, z);

if (this.chunkPosCache == null || this.chunkStatusCache == null || this.chunkCache == null) {
return null; // no cache
}

for (int i = 0; i < 4; i++) {
if (l == this.chunkPosCache[i] && leastStatus == this.chunkStatusCache[i]) {
Chunk chunk = this.chunkCache[i];
if (chunk != null || !create) {
return chunk;
}
}
}

return null;
}

@Unique
private Chunk c2me$fastpathExistingChunks(int x, int z, ChunkStatus leastStatus) {
final ChunkHolder holder = this.getChunkHolder(ChunkPos.toLong(x, z));
if (holder != null) {
final CompletableFuture<OptionalChunk<Chunk>> future = holder.load(leastStatus, this.chunkLoadingManager); // thread-safe in new system
return future.getNow(ChunkHolder.UNLOADED).orElse(null);
}
return null;
}

@Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerChunkManager;updateChunks()Z", shift = At.Shift.AFTER))
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ yarn_mappings=24w45a+build.1
loader_version=0.16.9
fabric_version=0.107.0+1.21.3
# Mod Properties
mod_version=0.3.0+alpha.0
mod_version=0.3.1+alpha.0
maven_group=com.ishland.c2me
archives_base_name=c2me-fabric
# Java Dependencies
Expand Down

0 comments on commit bd55340

Please sign in to comment.