-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
32 changed files
with
1,734 additions
and
133 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
4 changes: 4 additions & 0 deletions
4
src/main/java/net/gensokyoreimagined/nitori/common/ai/pathing/BlockStatePathingCache.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package net.gensokyoreimagined.nitori.common.ai.pathing; | ||
|
||
public class BlockStatePathingCache { | ||
} |
134 changes: 134 additions & 0 deletions
134
src/main/java/net/gensokyoreimagined/nitori/common/ai/pathing/PathNodeCache.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
||
} |
14 changes: 14 additions & 0 deletions
14
src/main/java/net/gensokyoreimagined/nitori/common/block/BlockListeningSection.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
123 changes: 123 additions & 0 deletions
123
src/main/java/net/gensokyoreimagined/nitori/common/block/BlockStateFlags.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]); | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
src/main/java/net/gensokyoreimagined/nitori/common/block/ListeningBlockStatePredicate.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
...t/gensokyoreimagined/nitori/common/block/entity/SleepUntilTimeBlockEntityTickInvoker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
Oops, something went wrong.