Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage;
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection;
import ca.spottedleaf.moonrise.patches.random_ticking.RandomTickChunkSection;
import ca.spottedleaf.moonrise.patches.random_ticking.RandomTickLevelChunk;
import com.llamalad7.mixinextras.sugar.Local;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import net.minecraft.util.BitStorage;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.Palette;
import net.minecraft.world.level.chunk.PalettedContainer;
Expand All @@ -29,7 +32,7 @@
import java.util.function.Predicate;

@Mixin(LevelChunkSection.class)
abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
abstract class LevelChunkSectionMixin implements BlockCountingChunkSection, RandomTickChunkSection {

@Shadow
@Final
Expand Down Expand Up @@ -70,6 +73,32 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
@Unique
private final ShortList tickingBlocks = new ShortList();

@Unique
private RandomTickLevelChunk moonrise$randomTickChunk;

@Unique
private int moonrise$randomTickIndex = -1;

@Override
public final void moonrise$bindRandomTickChunk(final LevelChunk chunk, final int index) {
this.moonrise$randomTickChunk = (RandomTickLevelChunk)chunk;
this.moonrise$randomTickIndex = index;
}

@Override
public final void moonrise$unbindRandomTickChunk() {
this.moonrise$randomTickChunk = null;
this.moonrise$randomTickIndex = -1;
}

@Unique
private void moonrise$notifyRandomTickIndex(final boolean wasTicking) {
final boolean nowTicking = this.tickingBlockCount > 0;
if (wasTicking != nowTicking && this.moonrise$randomTickChunk != null) {
this.moonrise$randomTickChunk.moonrise$noteRandomTickSection(this.moonrise$randomTickIndex, nowTicking);
}
}

@Override
public final boolean moonrise$hasSpecialCollidingBlocks() {
return this.specialCollidingBlocks != 0;
Expand Down Expand Up @@ -125,6 +154,17 @@ private void updateBlockCallback(final int x, final int y, final int z, final Bl
tickingBlocks.add(position);
}
}

// tickingBlockCount is already updated by vanilla; recover the previous 0/non-zero state.
final boolean wasTicking;
if (oldTicking == newTicking) {
wasTicking = this.tickingBlockCount > 0;
} else if (newTicking) {
wasTicking = this.tickingBlockCount > 1;
} else {
wasTicking = true;
}
this.moonrise$notifyRandomTickIndex(wasTicking);
}

/**
Expand All @@ -134,6 +174,7 @@ private void updateBlockCallback(final int x, final int y, final int z, final Bl
@Overwrite
public void recalcBlockCounts() {
// reset, then recalculate
final boolean wasTicking = this.tickingBlockCount > 0;
this.nonEmptyBlockCount = (short)0;
this.fluidCount = (short)0;
this.tickingBlockCount = (short)0;
Expand Down Expand Up @@ -196,6 +237,7 @@ public void recalcBlockCounts() {
}
}
}
this.moonrise$notifyRandomTickIndex(wasTicking);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package ca.spottedleaf.moonrise.mixin.random_ticking;

import ca.spottedleaf.moonrise.patches.random_ticking.RandomTickLevelChunk;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(ChunkAccess.class)
abstract class ChunkAccessMixin {

@Shadow
@Final
protected LevelChunkSection[] sections;

/**
* @reason FAWE/WorldEdit replace sections via {@code getSections()[i] = newSection}.
* Mark the chunk so the next random tick rebinds. Random-tick's own
* getSections is suppressed by begin/end on the chunk.
* @author HabsW
*/
@Inject(
method = "getSections",
at = @At("HEAD")
)
private void moonrise$noteSectionArrayBorrowed(final CallbackInfoReturnable<LevelChunkSection[]> cir) {
if ((Object)this instanceof RandomTickLevelChunk chunk) {
chunk.moonrise$noteSectionArrayBorrowed();
}
}

/**
* @reason Avoid routing {@code getSection} through {@code getSections}, which would
* mark every block lookup as a FAWE-style array borrow.
* @author HabsW
*/
@Overwrite
public LevelChunkSection getSection(final int sectionIndex) {
return this.sections[sectionIndex];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package ca.spottedleaf.moonrise.mixin.random_ticking;

import ca.spottedleaf.moonrise.patches.random_ticking.RandomTickChunkSection;
import ca.spottedleaf.moonrise.patches.random_ticking.RandomTickLevelChunk;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.EmptyLevelChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainerFactory;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.levelgen.blending.BlendingData;
import net.minecraft.world.ticks.LevelChunkTicks;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Arrays;

@Mixin(LevelChunk.class)
abstract class LevelChunkMixin extends ChunkAccess implements RandomTickLevelChunk {

public LevelChunkMixin(final ChunkPos chunkPos, final UpgradeData upgradeData, final LevelHeightAccessor levelHeightAccessor,
final PalettedContainerFactory palettedContainerFactory, final long l, final LevelChunkSection[] levelChunkSections, final BlendingData blendingData) {
super(chunkPos, upgradeData, levelHeightAccessor, palettedContainerFactory, l, levelChunkSections, blendingData);
}

@Shadow
@Final
protected LevelChunkSection[] sections;

@Unique
private long[] moonrise$randomTickSectionMask;

@Unique
private int moonrise$randomTickEligibleCount;

@Unique
private LevelChunkSection[] moonrise$boundSectionRefs;

@Unique
private int moonrise$randomTickGetSectionsSuppress;

@Unique
private boolean moonrise$sectionArrayBorrowed;

@Override
public final void moonrise$noteRandomTickSection(final int index, final boolean ticking) {
final long[] mask = this.moonrise$randomTickSectionMask;
if (mask == null || index < 0) {
return;
}
final int word = index >>> 6;
if (word >= mask.length) {
return;
}
final long bit = 1L << (index & 63);
final boolean was = (mask[word] & bit) != 0L;
if (was == ticking) {
return;
}
if (ticking) {
mask[word] |= bit;
this.moonrise$randomTickEligibleCount++;
} else {
mask[word] &= ~bit;
this.moonrise$randomTickEligibleCount--;
}
}

@Unique
private boolean moonrise$sectionRefsChanged(final LevelChunkSection[] sections) {
final LevelChunkSection[] bound = this.moonrise$boundSectionRefs;
if (bound == null || bound.length != sections.length) {
return true;
}
for (int i = 0; i < sections.length; i++) {
if (bound[i] != sections[i]) {
return true;
}
}
return false;
}

@Override
public final void moonrise$bindRandomTickSections() {
final LevelChunkSection[] sections = this.sections;
final LevelChunkSection[] oldRefs = this.moonrise$boundSectionRefs;
if (oldRefs != null) {
for (int i = 0; i < oldRefs.length; i++) {
final LevelChunkSection old = oldRefs[i];
if (old != null && (i >= sections.length || old != sections[i])) {
((RandomTickChunkSection)old).moonrise$unbindRandomTickChunk();
}
}
}

final int words = (sections.length + 63) >> 6;
if (this.moonrise$randomTickSectionMask == null || this.moonrise$randomTickSectionMask.length != words) {
this.moonrise$randomTickSectionMask = new long[Math.max(words, 1)];
} else {
Arrays.fill(this.moonrise$randomTickSectionMask, 0L);
}
this.moonrise$randomTickEligibleCount = 0;
for (int i = 0; i < sections.length; i++) {
final LevelChunkSection section = sections[i];
if (section != null) {
((RandomTickChunkSection)section).moonrise$bindRandomTickChunk((LevelChunk)(Object)this, i);
if (section.isRandomlyTickingBlocks()) {
this.moonrise$noteRandomTickSection(i, true);
}
}
}
this.moonrise$boundSectionRefs = Arrays.copyOf(sections, sections.length);
}

@Override
public final void moonrise$ensureRandomTickSections() {
final LevelChunkSection[] sections = this.sections;
if (this.moonrise$randomTickSectionMask == null || this.moonrise$sectionRefsChanged(sections)) {
this.moonrise$bindRandomTickSections();
}
}

@Override
public final void moonrise$beginRandomTickGetSections() {
this.moonrise$randomTickGetSectionsSuppress++;
}

@Override
public final void moonrise$endRandomTickGetSections() {
this.moonrise$randomTickGetSectionsSuppress--;
}

@Override
public final void moonrise$noteSectionArrayBorrowed() {
if (this.moonrise$randomTickGetSectionsSuppress == 0) {
this.moonrise$sectionArrayBorrowed = true;
}
}

@Override
public final boolean moonrise$consumeSectionArrayBorrowed() {
if (!this.moonrise$sectionArrayBorrowed) {
return false;
}
this.moonrise$sectionArrayBorrowed = false;
return true;
}

@Override
public final int moonrise$randomTickEligibleCount() {
return this.moonrise$randomTickEligibleCount;
}

@Override
public final long[] moonrise$randomTickSectionMask() {
return this.moonrise$randomTickSectionMask;
}

/**
* @reason Bind the live random-tick section bitset after construction.
* Empty chunks are skipped; they are never randomly ticked.
* @author HabsW
*/
@Inject(
method = "<init>(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/UpgradeData;Lnet/minecraft/world/ticks/LevelChunkTicks;Lnet/minecraft/world/ticks/LevelChunkTicks;J[Lnet/minecraft/world/level/chunk/LevelChunkSection;Lnet/minecraft/world/level/chunk/LevelChunk$PostLoadProcessor;Lnet/minecraft/world/level/levelgen/blending/BlendingData;)V",
at = @At("TAIL")
)
private void moonrise$bindRandomTickSectionsOnConstruct(final Level level, final ChunkPos chunkPos, final UpgradeData upgradeData,
final LevelChunkTicks levelChunkTicks, final LevelChunkTicks levelChunkTicks2,
final long l, final LevelChunkSection[] levelChunkSections,
final LevelChunk.PostLoadProcessor postLoadProcessor,
final BlendingData blendingData, final CallbackInfo ci) {
if ((Object)this instanceof EmptyLevelChunk) {
return;
}
this.moonrise$bindRandomTickSections();
}

/**
* @reason Post-load processors may replace section objects after the constructor bind.
* @author HabsW
*/
@Inject(
method = "runPostLoad",
at = @At("RETURN")
)
private void moonrise$bindRandomTickSectionsAfterPostLoad(final CallbackInfo ci) {
if ((Object)this instanceof EmptyLevelChunk) {
return;
}
this.moonrise$bindRandomTickSections();
}
}
Loading