Skip to content

Commit

Permalink
we ball
Browse files Browse the repository at this point in the history
  • Loading branch information
Taiyou06 committed Jul 7, 2024
1 parent feaec74 commit 9fb9753
Show file tree
Hide file tree
Showing 32 changed files with 1,734 additions and 133 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group=net.gensokyoreimagined.nitori
version=1.2-SNAPSHOT
version=1.3-SNAPSHOT
description=Converting patches into mixins, for the Ignite Framework

org.gradle.parallel=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package net.gensokyoreimagined.nitori.common.ai.pathing;

public class BlockStatePathingCache {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package net.gensokyoreimagined.nitori.common.ai.pathing;

import net.gensokyoreimagined.nitori.common.block.BlockCountingSection;
import net.gensokyoreimagined.nitori.common.block.BlockStateFlags;
import net.gensokyoreimagined.nitori.common.util.Pos;
import net.gensokyoreimagined.nitori.common.world.ChunkView;
import net.gensokyoreimagined.nitori.common.world.WorldHelper;
import me.jellysquid.mods.lithium.mixin.ai.pathing.PathContextAccessor;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;
import net.minecraft.world.level.pathfinder.BinaryHeap;
import net.minecraft.entity.ai.pathing.PathNodeType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockView;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkSection;

public abstract class PathNodeCache {
private static boolean isChunkSectionDangerousNeighbor(ChunkSection section) {
return section.getBlockStateContainer()
.hasAny(state -> getNeighborPathNodeType(state) != PathNodeType.OPEN);
}

public static PathNodeType getPathNodeType(BlockState state) {
return ((BlockStatePathingCache) state).lithium$getPathNodeType();
}

public static PathNodeType getNeighborPathNodeType(BlockBehaviour.AbstractBlockState state) {
return ((BlockStatePathingCache) state).lithium$getNeighborPathNodeType();
}

/**
* Returns whether a chunk section is free of dangers. This makes use of a caching layer to greatly
* accelerate neighbor danger checks when path-finding.
*
* @param section The chunk section to test for dangers
* @return True if this neighboring section is free of any dangers, otherwise false if it could
* potentially contain dangers
*/
public static boolean isSectionSafeAsNeighbor(ChunkSection section) {
// Empty sections can never contribute a danger
if (section.isEmpty()) {
return true;
}

if (BlockStateFlags.ENABLED) {
return !((BlockCountingSection) section).lithium$mayContainAny(BlockStateFlags.PATH_NOT_OPEN);
}
return !isChunkSectionDangerousNeighbor(section);
}


public static PathNodeType getNodeTypeFromNeighbors(BinaryHeap context, int x, int y, int z, PathNodeType fallback) {
BlockView world = context.getWorld();

ChunkSection section = null;

// Check that all the block's neighbors are within the same chunk column. If so, we can isolate all our block
// reads to just one chunk and avoid hits against the server chunk manager.
if (world instanceof ChunkView chunkView && WorldHelper.areNeighborsWithinSameChunkSection(x, y, z)) {
// If the y-coordinate is within bounds, we can cache the chunk section. Otherwise, the if statement to check
// if the cached chunk section was initialized will early-exit.
if (!world.isOutOfHeightLimit(y)) {
Chunk chunk = chunkView.lithium$getLoadedChunk(Pos.ChunkCoord.fromBlockCoord(x), Pos.ChunkCoord.fromBlockCoord(z));

// If the chunk is absent, the cached section above will remain null, as there is no chunk section anyway.
// An empty chunk or section will never pose any danger sources, which will be caught later.
if (chunk != null) {
section = chunk.getSectionArray()[Pos.SectionYIndex.fromBlockCoord(world, y)];
}
}

// If we can guarantee that blocks won't be modified while the cache is active, try to see if the chunk
// section is empty or contains any dangerous blocks within the palette. If not, we can assume any checks
// against this chunk section will always fail, allowing us to fast-exit.
if (section == null || PathNodeCache.isSectionSafeAsNeighbor(section)) {
return fallback; //TODO side effects of vanilla's path node caching
}
}

int xStart = x - 1;
int yStart = y - 1;
int zStart = z - 1;

int xEnd = x + 1;
int yEnd = y + 1;
int zEnd = z + 1;

// Vanilla iteration order is XYZ
for (int adjX = xStart; adjX <= xEnd; adjX++) {
for (int adjY = yStart; adjY <= yEnd; adjY++) {
for (int adjZ = zStart; adjZ <= zEnd; adjZ++) {
// Skip the vertical column of the origin block
if (adjX == x && adjZ == z) {
continue;
}

BlockState state;

// If we're not accessing blocks outside a given section, we can greatly accelerate block state
// retrieval by calling upon the cached chunk directly.
if (section != null) {
state = section.getBlockState(adjX & 15, adjY & 15, adjZ & 15);
} else {
BlockPos.Mutable pos = ((PathContextAccessor) context).getLastNodePos().set(adjX, adjY, adjZ);
state = world.getBlockState(pos);
}

if (state.isAir()) {
continue;
}

PathNodeType neighborType = PathNodeCache.getNeighborPathNodeType(state);

if (neighborType == null) { //Here null means that no path node type is cached (uninitialized or dynamic)
//Passing null as previous node type to the method signals to other lithium mixins that we only want the neighbor behavior of this block and not its neighbors
neighborType = WalkNodeEvaluator.getNodeTypeFromNeighbors(context, adjX + 1, adjY + 1, adjZ + 1, null);
//Here null means that the path node type is not changed by the block!
if (neighborType == null) {
neighborType = PathNodeType.OPEN;
}
}
if (neighborType != PathNodeType.OPEN) {
return neighborType;
}
}
}
}

return fallback;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package net.gensokyoreimagined.nitori.common.block;

import net.gensokyoreimagined.nitori.common.entity.block_tracking.SectionedBlockChangeTracker;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.Level;

public interface BlockListeningSection {

void lithium$addToCallback(ListeningBlockStatePredicate blockGroup, SectionedBlockChangeTracker tracker, long sectionPos, Level world);

void lithium$removeFromCallback(ListeningBlockStatePredicate blockGroup, SectionedBlockChangeTracker tracker);

void lithium$invalidateListeningSection(SectionPos sectionPos);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package net.gensokyoreimagined.nitori.common.block;

import it.unimi.dsi.fastutil.objects.Reference2BooleanArrayMap;
import net.gensokyoreimagined.nitori.common.ai.pathing.BlockStatePathingCache;
import net.gensokyoreimagined.nitori.common.ai.pathing.PathNodeCache;
import net.gensokyoreimagined.nitori.common.entity.FluidCachingEntity;
import net.gensokyoreimagined.nitori.common.reflection.ReflectionUtil;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.ai.pathing.PathNodeType;
import net.minecraft.registry.tag.FluidTags;
import net.minecraft.world.chunk.ChunkSection;

import java.util.ArrayList;

public class BlockStateFlags {
public static final boolean ENABLED = BlockCountingSection.class.isAssignableFrom(ChunkSection.class);

public static final int NUM_LISTENING_FLAGS;
public static final ListeningBlockStatePredicate[] LISTENING_FLAGS;
public static final int LISTENING_MASK_OR;

//Listening Flag
public static final ListeningBlockStatePredicate ANY;

public static final int NUM_TRACKED_FLAGS;
public static final TrackedBlockStatePredicate[] TRACKED_FLAGS;

//Counting flags
public static final TrackedBlockStatePredicate OVERSIZED_SHAPE;
public static final TrackedBlockStatePredicate PATH_NOT_OPEN;
public static final TrackedBlockStatePredicate WATER;
public static final TrackedBlockStatePredicate LAVA;

public static final TrackedBlockStatePredicate[] FLAGS;

//Non counting flags
public static final TrackedBlockStatePredicate ENTITY_TOUCHABLE;

static {
Reference2BooleanArrayMap<ListeningBlockStatePredicate> listeningFlags = new Reference2BooleanArrayMap<>();

ANY = new ListeningBlockStatePredicate(listeningFlags.size()) {
@Override
public boolean test(BlockState operand) {
return true;
}
};
//false -> we listen to changes of all blocks that pass the predicate test.
//true -> we only listen to changes of the predicate test result
listeningFlags.put(ANY, false);

NUM_LISTENING_FLAGS = listeningFlags.size();
int listenMaskOR = 0;
int iteration = 0;
for (var entry : listeningFlags.reference2BooleanEntrySet()) {
boolean listenOnlyXOR = entry.getBooleanValue();
listenMaskOR |= listenOnlyXOR ? 0 : 1 << iteration;
}
LISTENING_MASK_OR = listenMaskOR;
LISTENING_FLAGS = listeningFlags.keySet().toArray(new ListeningBlockStatePredicate[NUM_LISTENING_FLAGS]);


ArrayList<TrackedBlockStatePredicate> countingFlags = new ArrayList<>(listeningFlags.keySet());

OVERSIZED_SHAPE = new TrackedBlockStatePredicate(countingFlags.size()) {
@Override
public boolean test(BlockState operand) {
return operand.exceedsCube();
}
};
countingFlags.add(OVERSIZED_SHAPE);

if (FluidCachingEntity.class.isAssignableFrom(Entity.class)) {
WATER = new TrackedBlockStatePredicate(countingFlags.size()) {
@Override
public boolean test(BlockState operand) {
return operand.getFluidState().getFluid().isIn(FluidTags.WATER);
}
};
countingFlags.add(WATER);

LAVA = new TrackedBlockStatePredicate(countingFlags.size()) {
@Override
public boolean test(BlockState operand) {
return operand.getFluidState().getFluid().isIn(FluidTags.LAVA);
}
};
countingFlags.add(LAVA);
} else {
WATER = null;
LAVA = null;
}

if (BlockStatePathingCache.class.isAssignableFrom(AbstractBlock.AbstractBlockState.class)) {
PATH_NOT_OPEN = new TrackedBlockStatePredicate(countingFlags.size()) {
@Override
public boolean test(BlockState operand) {
return PathNodeCache.getNeighborPathNodeType(operand) != PathNodeType.OPEN;
}
};
countingFlags.add(PATH_NOT_OPEN);
} else {
PATH_NOT_OPEN = null;
}

NUM_TRACKED_FLAGS = countingFlags.size();
TRACKED_FLAGS = countingFlags.toArray(new TrackedBlockStatePredicate[NUM_TRACKED_FLAGS]);

ArrayList<TrackedBlockStatePredicate> flags = new ArrayList<>(countingFlags);

ENTITY_TOUCHABLE = new TrackedBlockStatePredicate(countingFlags.size()) {
@Override
public boolean test(BlockState operand) {
return ReflectionUtil.isBlockStateEntityTouchable(operand);
}
};
flags.add(ENTITY_TOUCHABLE);

FLAGS = flags.toArray(new TrackedBlockStatePredicate[0]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package net.gensokyoreimagined.nitori.common.block;

public abstract class ListeningBlockStatePredicate extends TrackedBlockStatePredicate {
public static int LISTENING_MASK;

protected ListeningBlockStatePredicate(int index) {
super(index);
LISTENING_MASK |= (1 << this.getIndex());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package net.gensokyoreimagined.nitori.common.block.entity;

import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.TickingBlockEntity;
import org.jetbrains.annotations.NotNull;

public record SleepUntilTimeBlockEntityTickInvoker(BlockEntity sleepingBlockEntity, long sleepUntilTickExclusive,
TickingBlockEntity delegate) implements TickingBlockEntity {

@Override
public void tick() {
//noinspection ConstantConditions
long tickTime = this.sleepingBlockEntity.getLevel().getGameTime();
if (tickTime >= this.sleepUntilTickExclusive) {
((SleepingBlockEntity) this.sleepingBlockEntity).setTicker(this.delegate);
this.delegate.tick();
}
}

@Override
public boolean isRemoved() {
return this.sleepingBlockEntity.isRemoved();
}

@Override
public @NotNull BlockPos getPos() {
return this.sleepingBlockEntity.getBlockPos();
}

@Override
public String getType() {
//noinspection ConstantConditions
return BlockEntityType.getKey(this.sleepingBlockEntity.getType()).toString();
}
}
Loading

0 comments on commit 9fb9753

Please sign in to comment.