Debug rendering for oxygen (VERY LAGGY BUT WORKS)

This commit is contained in:
XevianLight
2026-01-30 21:46:13 -07:00
parent cc93d2fb42
commit 012985441f
10 changed files with 611 additions and 48 deletions

View File

@@ -1,19 +1,34 @@
package net.xevianlight.aphelion.block.custom;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity;
import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity;
import net.xevianlight.aphelion.core.init.ModBlockEntities;
import org.jetbrains.annotations.Nullable;
public class OxygenTestBlock extends Block implements EntityBlock {
public class OxygenTestBlock extends BaseEntityBlock {
public OxygenTestBlock(Properties properties) {
super(properties);
}
public static final MapCodec<OxygenTestBlock> CODEC = simpleCodec(OxygenTestBlock::new);
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@Override
public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
return new OxygenTestBlockEntity(blockPos, blockState);
@@ -22,4 +37,29 @@ public class OxygenTestBlock extends Block implements EntityBlock {
public static Properties getProperties() {
return Properties.of();
}
@Override
public @Nullable <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
if (level.isClientSide) {
return null;
}
return createTickerHelper(blockEntityType, ModBlockEntities.OXYGEN_TEST_BLOCK_ENTITY.get(), (level1, blockPos, blockState, oxygenTestBlockEntity) -> oxygenTestBlockEntity.tick(level1, blockPos, blockState));
}
@Override
public RenderShape getRenderShape(BlockState pState) {
return RenderShape.MODEL;
}
@Override
protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
if (state.getBlock() != newState.getBlock()) {
BlockEntity blockEntity= level.getBlockEntity(pos);
if (blockEntity instanceof OxygenTestBlockEntity oxygenTestBlockEntity) {
oxygenTestBlockEntity.removeEnclosed();
}
}
super.onRemove(state, level, pos, newState, movedByPiston);
}
}

View File

@@ -1,18 +1,17 @@
package net.xevianlight.aphelion.block.entity.custom;
import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.SectionPos;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import static net.xevianlight.aphelion.Aphelion.LOGGER;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunk;
import net.xevianlight.aphelion.core.init.ModBlockEntities;
import net.xevianlight.aphelion.core.saveddata.EnvironmentSavedData;
import net.xevianlight.aphelion.util.FloodFill3D;
import org.openjdk.nashorn.internal.runtime.regexp.joni.exception.ValueException;
import java.util.*;
@@ -43,11 +42,25 @@ public class OxygenTestBlockEntity extends BlockEntity {
return level != null && fastBlockState(pos).isAir();
}
public static final int MAX_RANGE = 100;
public static final int MAX_RANGE = 16;
public boolean isInRange(BlockPos pos1, BlockPos pos2) {
return Math.abs(pos1.getX() - pos2.getX()) + Math.abs(pos1.getY() - pos2.getY()) + Math.abs(pos1.getZ() - pos2.getZ()) <= MAX_RANGE;
}
public void removeEnclosed() {
if (enclosedCache != null) {
var singleplayerServer = Minecraft.getInstance().getSingleplayerServer();
if (singleplayerServer != null) {
var serverLevel = singleplayerServer.getLevel(level.dimension());
if (serverLevel != null) {
var savedData = EnvironmentSavedData.get(serverLevel);
savedData.resetOxygen(serverLevel, enclosedCache);
enclosedCache = Set.of();
}
}
}
}
/// 256*256*256 grid of booleans
private class BigBoolGrid {
int bitsSize;
@@ -94,7 +107,7 @@ public class OxygenTestBlockEntity extends BlockEntity {
// wrapping order is X->Z->Y, so we go along the X axis,
// wrap to the Z axis to make a square, and once the square is full
// we step up once along the Y axis.
int bitPos = inX & 7; // bottom 4 bits of X is bit pos
int bitPos = inX & 15; // bottom 4 bits of X is bit pos
int bit = (1 << bitPos);
// First (bitsSize-4) bits are for X, next (bitsSize) bits are for Z, next (bitsSize) bits are for Y
@@ -106,22 +119,29 @@ public class OxygenTestBlockEntity extends BlockEntity {
}
}
private List<BlockPos> enclosedCache;
public List<BlockPos> getEnclosedBlocks() {
if (level == null) return List.of();
if (enclosedCache != null) return enclosedCache;
private Set<BlockPos> enclosedCache;
public Set<BlockPos> getEnclosedCache() {
return enclosedCache;
}
public Set<BlockPos> getEnclosedBlocks() {
if (level == null) return Set.of();
// if (enclosedCache != null) return enclosedCache;
long start = System.nanoTime();
List<BlockPos> enclosedBlocks = new ArrayList<>();
// make this bitch BIIIIIG
BigBoolGrid seen = new BigBoolGrid(8, this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ());
Set<BlockPos> enclosedBlocks = FloodFill3D.run(level, getBlockPos(), 6000, FloodFill3D.TEST_FULL_SEAL, true);
// It's... a reasonable assumption that we won't have to include more blocks at once than the area of a sphere?
// maybe a bit more, IDK how exactly it scales to blocks.
Stack<BlockPos> stack = new Stack<>();
Stack<Integer> radiusStack = new Stack<>();
stack.add(this.getBlockPos());
// // make this bitch BIIIIIG
// BigBoolGrid seen = new BigBoolGrid(8, this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ());
//
// // It's... a reasonable assumption that we won't have to include more blocks at once than the area of a sphere?
// // maybe a bit more, IDK how exactly it scales to blocks.
// Stack<BlockPos> stack = new Stack<>();
// Stack<Integer> radiusStack = new Stack<>();
//
// stack.add(this.getBlockPos());
// Do flood fill out from this block
// Push on the top of the stack (newest), pop from the bottom of the stack (oldest).
@@ -130,21 +150,48 @@ public class OxygenTestBlockEntity extends BlockEntity {
// and you'd see that every pos of layer 1 is together, then layer 2, then layer 3...
BlockPos ourPos = getBlockPos();
while (!stack.isEmpty()) {
BlockPos spreadFromPos = stack.pop();
for (Direction d : Direction.values()) {
BlockPos relativePos = spreadFromPos.relative(d);
// BlockPos ourPos = getBlockPos();
// while (!stack.isEmpty()) {
// BlockPos spreadFromPos = stack.pop();
// for (Direction d : Direction.values()) {
// BlockPos relativePos = spreadFromPos.relative(d);
//
// if (isInRange(relativePos, ourPos) && canSpreadTo(relativePos)) {
// // seen.add runs seen.contains under the hood,
// // + this is the most expensive operation.
// // should save a lot of time!
// if (seen.add(relativePos.getX(), relativePos.getY(), relativePos.getZ())) {
// enclosedBlocks.add(relativePos);
// stack.add(relativePos);
// }
// }
// }
// }
if (isInRange(relativePos, ourPos) && canSpreadTo(relativePos)) {
// seen.add runs seen.contains under the hood,
// + this is the most expensive operation.
// should save a lot of time!
if (seen.add(relativePos.getX(), relativePos.getY(), relativePos.getZ())) {
enclosedBlocks.add(relativePos);
stack.add(relativePos);
var singleplayerServer = Minecraft.getInstance().getSingleplayerServer();
if (singleplayerServer != null) {
var serverLevel = singleplayerServer.getLevel(level.dimension());
if (serverLevel != null) {
// Build a set of longs for the newly computed blocks (order-independent)
boolean changed = isChanged(enclosedBlocks);
// boolean changed = false;
if (changed) {
var savedData = EnvironmentSavedData.get(serverLevel);
// Revert old affected area back to defaults
if (enclosedCache != null) {
savedData.resetOxygen(serverLevel, enclosedCache);
}
// Apply oxygen to new affected area
savedData.setOxygen(serverLevel, enclosedBlocks, true);
LOGGER.info("Saved data for {} blocks to leveldata", enclosedBlocks.size());
}
// Update the cache no matter what (so next compare is correct)
enclosedCache = enclosedBlocks;
}
}
long durationNanos = System.nanoTime() - start;
@@ -152,12 +199,49 @@ public class OxygenTestBlockEntity extends BlockEntity {
LOGGER.info("Flood fill completed in {}µs, {} blocks at {}µs/block", durationMicros, enclosedBlocks.size(), durationMicros / enclosedBlocks.size());
enclosedCache = enclosedBlocks;
return enclosedBlocks;
return enclosedCache;
}
private boolean isChanged(Set<BlockPos> enclosedBlocks) {
LongOpenHashSet newSet = new LongOpenHashSet(enclosedBlocks.size());
for (BlockPos p : enclosedBlocks) {
newSet.add(p.asLong());
}
// Build a set of longs for the cached blocks (if any)
LongOpenHashSet oldSet = null;
if (enclosedCache != null) {
oldSet = new LongOpenHashSet(enclosedCache.size());
for (BlockPos p : enclosedCache) {
oldSet.add(p.asLong());
}
}
// Only save if the set of affected blocks has changed
boolean changed = (oldSet == null) || !oldSet.equals(newSet);
return changed;
}
private void helper() {
var myVar = new BlockPos(1, 1, 1).hashCode();
}
int ticks = 0;
int refreshAfter = 20;
public void tick(Level level, BlockPos pos, BlockState blockState) {
if (level.isClientSide) return;
ticks++;
if (ticks >= refreshAfter) {
getEnclosedBlocks();
ticks = 0;
// UNCOMMENT FOR DEBUG ONLY!!! EXTREMELY TPS INTENSIVE!!!
// EnvironmentSavedData.refreshFromIntegratedServerIfNeeded(Minecraft.getInstance(), 64, 10000);
}
}
}

View File

@@ -12,6 +12,7 @@ import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i;
import net.minecraft.world.phys.AABB;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity;
import org.joml.Matrix4f;
import org.joml.Vector3f;
@@ -23,7 +24,10 @@ public class OxygenTestRenderer implements BlockEntityRenderer<OxygenTestBlockEn
public OxygenTestRenderer (BlockEntityRendererProvider.Context context) {}
private List<BlockPos> toBlockPositions(OxygenTestBlockEntity be) {
return be.getEnclosedBlocks();
// cache = be.getEnclosedBlocks();
// if (cache != null)
// return cache;
return List.of();
}
@Override
@@ -31,6 +35,8 @@ public class OxygenTestRenderer implements BlockEntityRenderer<OxygenTestBlockEn
return AABB.ofSize(blockEntity.getBlockPos().getCenter(), OxygenTestBlockEntity.MAX_RANGE*2, OxygenTestBlockEntity.MAX_RANGE*2, OxygenTestBlockEntity.MAX_RANGE*2);
}
List<BlockPos> cache;
private Set<BlockPos> relativePositionsCache;
@Override
// If in debug mode, renders a model made from the blocks
@@ -42,7 +48,7 @@ public class OxygenTestRenderer implements BlockEntityRenderer<OxygenTestBlockEn
// Renderers are relative to our block pos, so transform all points to be relative to block pos as well
List<BlockPos> positionsToRender = toBlockPositions(be);
BlockPos originPos = be.getBlockPos();
if (true) return;
// if (true) return;
Set<BlockPos> relativePositions;
if (relativePositionsCache != null) relativePositions = relativePositionsCache;