9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-28 19:39:11 +00:00

动态渲染器

This commit is contained in:
XiaoMoMi
2025-09-14 00:38:31 +08:00
parent 7d567dca54
commit 1ca16091bb
17 changed files with 183 additions and 156 deletions

View File

@@ -1,7 +1,7 @@
package net.momirealms.craftengine.core.block.entity;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer;
import net.momirealms.craftengine.core.block.entity.render.DynamicBlockEntityRenderer;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.ChunkPos;
@@ -15,6 +15,8 @@ public abstract class BlockEntity {
protected BlockEntityType<? extends BlockEntity> type;
protected CEWorld world;
protected boolean valid;
@Nullable
protected DynamicBlockEntityRenderer blockEntityRenderer;
protected BlockEntity(BlockEntityType<? extends BlockEntity> type, BlockPos pos, ImmutableBlockState blockState) {
this.pos = pos;
@@ -78,7 +80,11 @@ public abstract class BlockEntity {
}
public BlockEntityType<? extends BlockEntity> type() {
return type;
return this.type;
}
public @Nullable DynamicBlockEntityRenderer blockEntityRenderer() {
return blockEntityRenderer;
}
public static BlockPos readPosAndVerify(CompoundTag tag, ChunkPos chunkPos) {
@@ -95,12 +101,7 @@ public abstract class BlockEntity {
}
public BlockPos pos() {
return pos;
}
@Nullable
public ConstantBlockEntityRenderer[] getBlockEntityRenderers() {
return null;
return this.pos;
}
public boolean isValidBlockState(ImmutableBlockState blockState) {

View File

@@ -1,20 +1,38 @@
package net.momirealms.craftengine.core.block.entity.render;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.entity.player.Player;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Experimental
public abstract class ConstantBlockEntityRenderer {
public class ConstantBlockEntityRenderer {
private final BlockEntityElement[] elements;
public abstract void spawn();
public ConstantBlockEntityRenderer(BlockEntityElement[] elements) {
this.elements = elements;
}
public abstract void despawn();
public void show(Player player) {
for (BlockEntityElement element : this.elements) {
element.show(player);
}
}
public abstract void spawn(Player player);
public void hide(Player player) {
for (BlockEntityElement element : this.elements) {
element.hide(player);
}
}
public abstract void despawn(Player player);
public void deactivate() {
for (BlockEntityElement element : this.elements) {
element.deactivate();
}
}
public abstract void deactivate();
public abstract void activate();
public void activate() {
for (BlockEntityElement element : this.elements) {
element.activate();
}
}
}

View File

@@ -0,0 +1,14 @@
package net.momirealms.craftengine.core.block.entity.render;
import net.momirealms.craftengine.core.entity.player.Player;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Experimental
public interface DynamicBlockEntityRenderer {
void show(Player player);
void hide(Player player);
void update(Player player);
}

View File

@@ -6,9 +6,9 @@ import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Experimental
public interface BlockEntityElement {
void spawn(Player player);
void show(Player player);
void despawn(Player player);
void hide(Player player);
default void deactivate() {}

View File

@@ -4,8 +4,6 @@ import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTabl
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.tick.TickingBlockEntity;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
@@ -21,7 +19,7 @@ import java.util.concurrent.ConcurrentHashMap;
public abstract class CEWorld {
public static final String REGION_DIRECTORY = "craftengine";
protected final World world;
public final World world;
protected final ConcurrentLong2ReferenceChainedHashTable<CEChunk> loadedChunkMap;
protected final WorldDataStorage worldDataStorage;
protected final WorldHeight worldHeightAccessor;
@@ -219,6 +217,4 @@ public abstract class CEWorld {
}
this.isTickingBlockEntities = false;
}
public abstract ConstantBlockEntityRenderer createBlockEntityRenderer(BlockEntityElement[] elements, World world, BlockPos pos);
}

View File

@@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.world;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.sound.SoundData;
@@ -12,6 +13,7 @@ import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;
public interface World {
@@ -59,4 +61,6 @@ public interface World {
void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @NotNull Context context);
long time();
List<Player> getTrackedBy(ChunkPos pos);
}

View File

@@ -5,6 +5,7 @@ import net.momirealms.craftengine.core.block.EmptyBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer;
import net.momirealms.craftengine.core.block.entity.render.DynamicBlockEntityRenderer;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.tick.*;
@@ -25,12 +26,13 @@ public class CEChunk {
public final ChunkPos chunkPos;
public final CESection[] sections;
public final WorldHeight worldHeightAccessor;
public final Map<BlockPos, BlockEntity> blockEntities;
public final Map<BlockPos, ConstantBlockEntityRenderer> blockEntityRenderers;
public final Map<BlockPos, BlockEntity> blockEntities; // 从区域线程上访问,安全
public final Map<BlockPos, ReplaceableTickingBlockEntity> tickingBlockEntitiesByPos; // 从区域线程上访问,安全
public final Map<BlockPos, ConstantBlockEntityRenderer> constantBlockEntityRenderers; // 会从区域线程上读写netty线程上读取
public final Map<BlockPos, DynamicBlockEntityRenderer> dynamicBlockEntityRenderers; // 会从区域线程上读写netty线程上读取
private final ReentrantReadWriteLock renderLock = new ReentrantReadWriteLock();
private volatile boolean dirty;
private volatile boolean loaded;
protected final Map<BlockPos, ReplaceableTickingBlockEntity> tickingBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f);
public CEChunk(CEWorld world, ChunkPos chunkPos) {
this.world = world;
@@ -38,7 +40,9 @@ public class CEChunk {
this.worldHeightAccessor = world.worldHeight();
this.sections = new CESection[this.worldHeightAccessor.getSectionsCount()];
this.blockEntities = new Object2ObjectOpenHashMap<>(10, 0.5f);
this.blockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f);
this.constantBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f);
this.dynamicBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f);
this.tickingBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f);
this.fillEmptySection();
}
@@ -46,6 +50,8 @@ public class CEChunk {
this.world = world;
this.chunkPos = chunkPos;
this.worldHeightAccessor = world.worldHeight();
this.dynamicBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f);
this.tickingBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f);
int sectionCount = this.worldHeightAccessor.getSectionsCount();
this.sections = new CESection[sectionCount];
if (sections != null) {
@@ -67,21 +73,24 @@ public class CEChunk {
this.blockEntities = new Object2ObjectOpenHashMap<>(10, 0.5f);
}
if (itemDisplayBlockRenders != null) {
this.blockEntityRenderers = new Object2ObjectOpenHashMap<>(Math.max(itemDisplayBlockRenders.size(), 10), 0.5f);
this.constantBlockEntityRenderers = new Object2ObjectOpenHashMap<>(Math.max(itemDisplayBlockRenders.size(), 10), 0.5f);
List<BlockPos> blockEntityRendererPoses = DefaultBlockEntityRendererSerializer.deserialize(this.chunkPos, itemDisplayBlockRenders);
for (BlockPos pos : blockEntityRendererPoses) {
this.addBlockEntityRenderer(pos);
this.addConstantBlockEntityRenderer(pos);
}
} else {
this.blockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f);
this.constantBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f);
}
}
public void spawnBlockEntities(Player player) {
try {
this.renderLock.readLock().lock();
for (ConstantBlockEntityRenderer renderer : this.blockEntityRenderers.values()) {
renderer.spawn(player);
for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) {
renderer.show(player);
}
for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) {
renderer.show(player);
}
} finally {
this.renderLock.readLock().unlock();
@@ -91,19 +100,22 @@ public class CEChunk {
public void despawnBlockEntities(Player player) {
try {
this.renderLock.readLock().lock();
for (ConstantBlockEntityRenderer renderer : this.blockEntityRenderers.values()) {
renderer.despawn(player);
for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) {
renderer.hide(player);
}
for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) {
renderer.hide(player);
}
} finally {
this.renderLock.readLock().unlock();
}
}
public void addBlockEntityRenderer(BlockPos pos) {
this.addBlockEntityRenderer(pos, this.getBlockState(pos));
public void addConstantBlockEntityRenderer(BlockPos pos) {
this.addConstantBlockEntityRenderer(pos, this.getBlockState(pos));
}
public void addBlockEntityRenderer(BlockPos pos, ImmutableBlockState state) {
public void addConstantBlockEntityRenderer(BlockPos pos, ImmutableBlockState state) {
BlockEntityElementConfig<? extends BlockEntityElement>[] renderers = state.constantRenderers();
if (renderers != null && renderers.length > 0) {
BlockEntityElement[] elements = new BlockEntityElement[renderers.length];
@@ -111,23 +123,41 @@ public class CEChunk {
for (int i = 0; i < elements.length; i++) {
elements[i] = renderers[i].create(wrappedWorld, pos);
}
ConstantBlockEntityRenderer renderer = this.world.createBlockEntityRenderer(elements, wrappedWorld, pos);
renderer.spawn();
ConstantBlockEntityRenderer renderer = new ConstantBlockEntityRenderer(elements);
for (Player player : getTrackedBy()) {
renderer.show(player);
}
try {
this.renderLock.writeLock().lock();
this.blockEntityRenderers.put(pos, renderer);
this.constantBlockEntityRenderers.put(pos, renderer);
} finally {
this.renderLock.writeLock().unlock();
}
}
}
public void removeBlockEntityRenderer(BlockPos pos) {
public void removeConstantBlockEntityRenderer(BlockPos pos) {
try {
this.renderLock.writeLock().lock();
ConstantBlockEntityRenderer removed = this.blockEntityRenderers.remove(pos);
ConstantBlockEntityRenderer removed = this.constantBlockEntityRenderers.remove(pos);
if (removed != null) {
removed.despawn();
for (Player player : getTrackedBy()) {
removed.hide(player);
}
}
} finally {
this.renderLock.writeLock().unlock();
}
}
private void removeDynamicBlockEntityRenderer(BlockPos pos) {
try {
this.renderLock.writeLock().lock();
DynamicBlockEntityRenderer renderer = this.dynamicBlockEntityRenderers.remove(pos);
if (renderer != null) {
for (Player player : getTrackedBy()) {
renderer.hide(player);
}
}
} finally {
this.renderLock.writeLock().unlock();
@@ -137,6 +167,7 @@ public class CEChunk {
public void addBlockEntity(BlockEntity blockEntity) {
this.setBlockEntity(blockEntity);
this.replaceOrCreateTickingBlockEntity(blockEntity);
this.createDynamicBlockEntityRenderer(blockEntity);
}
public void removeBlockEntity(BlockPos blockPos) {
@@ -145,21 +176,28 @@ public class CEChunk {
removedBlockEntity.setValid(false);
}
this.removeBlockEntityTicker(blockPos);
this.removeDynamicBlockEntityRenderer(blockPos);
}
public void activateAllBlockEntities() {
for (BlockEntity blockEntity : this.blockEntities.values()) {
blockEntity.setValid(true);
replaceOrCreateTickingBlockEntity(blockEntity);
this.replaceOrCreateTickingBlockEntity(blockEntity);
this.createDynamicBlockEntityRenderer(blockEntity);
}
for (ConstantBlockEntityRenderer renderer : this.blockEntityRenderers.values()) {
for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) {
renderer.activate();
}
}
public List<Player> getTrackedBy() {
return this.world.world.getTrackedBy(this.chunkPos);
}
public void deactivateAllBlockEntities() {
this.blockEntities.values().forEach(e -> e.setValid(false));
this.blockEntityRenderers.values().forEach(ConstantBlockEntityRenderer::deactivate);
this.constantBlockEntityRenderers.values().forEach(ConstantBlockEntityRenderer::deactivate);
this.dynamicBlockEntityRenderers.clear();
this.tickingBlockEntitiesByPos.values().forEach((ticker) -> ticker.setTicker(DummyTickingBlockEntity.INSTANCE));
this.tickingBlockEntitiesByPos.clear();
}
@@ -184,6 +222,34 @@ public class CEChunk {
}
}
public <T extends BlockEntity> void createDynamicBlockEntityRenderer(T blockEntity) {
DynamicBlockEntityRenderer renderer = blockEntity.blockEntityRenderer();
if (renderer != null) {
DynamicBlockEntityRenderer previous;
try {
this.renderLock.writeLock().lock();
previous = this.dynamicBlockEntityRenderers.put(blockEntity.pos(), renderer);
} finally {
this.renderLock.writeLock().unlock();
}
if (previous != null) {
if (previous == renderer) {
return;
}
for (Player player : getTrackedBy()) {
previous.hide(player);
renderer.show(player);
}
} else {
for (Player player : getTrackedBy()) {
renderer.show(player);
}
}
} else {
this.removeDynamicBlockEntityRenderer(blockEntity.pos());
}
}
private void removeBlockEntityTicker(BlockPos pos) {
ReplaceableTickingBlockEntity blockEntity = this.tickingBlockEntitiesByPos.remove(pos);
if (blockEntity != null) {
@@ -239,10 +305,10 @@ public class CEChunk {
return Collections.unmodifiableCollection(this.blockEntities.values());
}
public List<BlockPos> blockEntityRenderers() {
public List<BlockPos> constantBlockEntityRenderers() {
try {
this.renderLock.readLock().lock();
return new ArrayList<>(this.blockEntityRenderers.keySet());
return new ArrayList<>(this.constantBlockEntityRenderers.keySet());
} finally {
this.renderLock.readLock().unlock();
}

View File

@@ -30,7 +30,7 @@ public final class DefaultChunkSerializer {
if (!blockEntities.isEmpty()) {
chunkNbt.put("block_entities", blockEntities);
}
ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.blockEntityRenderers());
ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.constantBlockEntityRenderers());
if (!blockEntityRenders.isEmpty()) {
chunkNbt.put("block_entity_renderers", blockEntityRenders);
}