Skip to content

Commit

Permalink
new: block listening system + entity fluid caching
Browse files Browse the repository at this point in the history
  • Loading branch information
2No2Name committed Jul 12, 2023
1 parent 480cf95 commit b32007a
Show file tree
Hide file tree
Showing 22 changed files with 638 additions and 30 deletions.
22 changes: 20 additions & 2 deletions lithium-mixin-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ The expensive check to see if a TypeFilterableList can be filtered by a specific
Copy entity hashmap instead of duplicating the list using iteration

### `mixin.collections.fluid_submersion`
(default: `true`)
(default: `false`)
Use ReferenceArraySet instead of HashSet to store the fluids the entity is currently submerged in.

### `mixin.collections.gamerules`
Expand All @@ -247,7 +247,7 @@ Various entity optimizations
Various entity collision optimizations

### `mixin.entity.collisions.fluid`
(default: `true`)
(default: `false`)
Skips being pushed by fluids when the nearby chunk sections do not contain this fluid
Requirements:
- `mixin.util.block_tracking=true`
Expand Down Expand Up @@ -323,6 +323,20 @@ Skips repeated checks whether the equipment of an entity changed. Instead equipm
(default: `true`)
Skip searching for fire sources in the burn time countdown logic when they are not on fire and the result does not make a difference.

### `mixin.experimental`
(default: `true`)
Various experimental optimizations

### `mixin.experimental.entity`
(default: `true`)
Experimental entity optimizations

### `mixin.experimental.entity.fluid_caching`
(default: `true`)
Use block listening system to allow skipping entity fluid current checks when the entity is not touching the respective fluid
Requirements:
- `mixin.util.block_tracking.block_listening=true`

### `mixin.gen`
(default: `true`)
Various world generation optimizations
Expand Down Expand Up @@ -395,6 +409,10 @@ Allows access to existing BlockEntities without creating new ones
(default: `true`)
Chunk sections count certain blocks inside them and provide a method to quickly check whether a chunk contains any of these blocks

### `mixin.util.block_tracking.block_listening`
(default: `true`)
Chunk sections can notify registered listeners about certain blocks being placed or broken

### `mixin.util.chunk_access`
(default: `true`)
Access chunks of worlds, chunk caches and chunk regions directly.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package me.jellysquid.mods.lithium.common.block;

import me.jellysquid.mods.lithium.common.entity.block_tracking.SectionedBlockChangeTracker;

public interface BlockListeningSection {

void addToCallback(ListeningBlockStatePredicate blockGroup, SectionedBlockChangeTracker tracker);
void removeFromCallback(ListeningBlockStatePredicate blockGroup, SectionedBlockChangeTracker tracker);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,81 @@
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import net.minecraft.entity.ai.pathing.PathNodeType;
import net.minecraft.fluid.Fluids;
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 NUM_FLAGS;
public static final TrackedBlockStatePredicate[] FLAGS;

public static final ListeningBlockStatePredicate FLUIDS;

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[] ALL_FLAGS;

static {
ArrayList<TrackedBlockStatePredicate> allFlags = new ArrayList<>();

ArrayList<ListeningBlockStatePredicate> listeningFlags = new ArrayList<>();
//noinspection ConstantConditions
OVERSIZED_SHAPE = new TrackedBlockStatePredicate(allFlags.size()) {
FLUIDS = new ListeningBlockStatePredicate(listeningFlags.size()) {
@Override
public boolean test(BlockState operand) {
return operand.getFluidState().getFluid() != Fluids.EMPTY;
}
};
listeningFlags.add(FLUIDS);
NUM_LISTENING_FLAGS = listeningFlags.size();
LISTENING_FLAGS = listeningFlags.toArray(new ListeningBlockStatePredicate[NUM_LISTENING_FLAGS]);


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

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

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

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

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

NUM_FLAGS = allFlags.size();
ALL_FLAGS = allFlags.toArray(new TrackedBlockStatePredicate[NUM_FLAGS]);
NUM_FLAGS = countingFlags.size();
FLAGS = countingFlags.toArray(new TrackedBlockStatePredicate[NUM_FLAGS]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package me.jellysquid.mods.lithium.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,54 @@
package me.jellysquid.mods.lithium.common.entity.block_tracking;

import me.jellysquid.mods.lithium.common.block.BlockListeningSection;
import me.jellysquid.mods.lithium.common.block.BlockStateFlags;
import me.jellysquid.mods.lithium.common.block.ListeningBlockStatePredicate;

import java.util.ArrayList;

public final class ChunkSectionChangeCallback {
private final ArrayList<SectionedBlockChangeTracker>[] trackers;
private short listeningMask;

public ChunkSectionChangeCallback() {
//noinspection unchecked
this.trackers = new ArrayList[BlockStateFlags.NUM_LISTENING_FLAGS];
this.listeningMask = 0;
}

public short onBlockChange(int flagIndex, BlockListeningSection section) {
ArrayList<SectionedBlockChangeTracker> sectionedBlockChangeTrackers = this.trackers[flagIndex];
this.trackers[flagIndex] = null;
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < sectionedBlockChangeTrackers.size(); i++) {
sectionedBlockChangeTrackers.get(i).setChanged(section);
}
this.listeningMask &= ~(1 << flagIndex);

return this.listeningMask;
}

public short addTracker(SectionedBlockChangeTracker tracker, ListeningBlockStatePredicate blockGroup) {
int blockGroupIndex = blockGroup.getIndex();
ArrayList<SectionedBlockChangeTracker> sectionedBlockChangeTrackers = this.trackers[blockGroupIndex];
if (sectionedBlockChangeTrackers == null) {
this.trackers[blockGroupIndex] = (sectionedBlockChangeTrackers = new ArrayList<>());
}
sectionedBlockChangeTrackers.add(tracker);

this.listeningMask |= (1 << blockGroupIndex);
return this.listeningMask;
}

public short removeTracker(SectionedBlockChangeTracker tracker, ListeningBlockStatePredicate blockGroup) {
int blockGroupIndex = blockGroup.getIndex();
ArrayList<SectionedBlockChangeTracker> sectionedBlockChangeTrackers = this.trackers[blockGroupIndex];
if (sectionedBlockChangeTrackers != null) {
sectionedBlockChangeTrackers.remove(tracker);
if (sectionedBlockChangeTrackers.isEmpty()) {
this.listeningMask &= ~(1 << blockGroup.getIndex());
}
}
return this.listeningMask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package me.jellysquid.mods.lithium.common.entity.block_tracking;

import it.unimi.dsi.fastutil.objects.Reference2LongArrayMap;
import net.minecraft.fluid.Fluid;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.util.math.Box;
import net.minecraft.world.World;

public final class FluidListeningInfo {
private static final int MIN_STATIONARY_COUNT = 16;
private Box cachedPos;
private SectionedFluidChangeTracker tracker;
private Reference2LongArrayMap<TagKey<Fluid>> lastNotTouchedFluidTimes;
private int stationary; //Stationary field can be left out, but is intended to avoid spamming the block tracking system with entities that are currently moving.

public FluidListeningInfo() {
this.tracker = null;
this.lastNotTouchedFluidTimes = null;
this.cachedPos = null;
this.stationary = 0;
}

public void updateTracker(Box boundingBox, World world) {
if (!boundingBox.equals(this.cachedPos)) {
if (this.tracker != null) {
if (!this.tracker.matchesMovedBox(boundingBox)) {
this.tracker.unregister();
this.tracker = null;
}
}
this.cachedPos = boundingBox;
this.stationary = 0;
if (this.lastNotTouchedFluidTimes != null) {
this.lastNotTouchedFluidTimes.clear();
}
return;
}
if (this.stationary >= MIN_STATIONARY_COUNT) {
if (this.tracker == null && this.lastNotTouchedFluidTimes != null && !this.lastNotTouchedFluidTimes.isEmpty()) {
this.tracker = SectionedFluidChangeTracker.registerAt(world, boundingBox);
}
} else {
this.stationary++;
}
}

public boolean cachedIsNotTouchingFluid(TagKey<Fluid> tag) {
if (this.stationary >= MIN_STATIONARY_COUNT && this.tracker != null && this.lastNotTouchedFluidTimes != null) {
long cachedTime = this.lastNotTouchedFluidTimes.getOrDefault(tag, Long.MIN_VALUE);
return this.tracker.isUnchangedSince(cachedTime);
}
return false;
}

public void cacheNotTouchingFluid(TagKey<Fluid> tag, long time) {
if (this.lastNotTouchedFluidTimes == null) {
this.lastNotTouchedFluidTimes = new Reference2LongArrayMap<>();
}
this.lastNotTouchedFluidTimes.put(tag, time);
}

public void remove() {
if (this.tracker != null) {
this.tracker.unregister();
}
}
}
Loading

0 comments on commit b32007a

Please sign in to comment.