Skip to content

Commit c721883

Browse files
committed
Use sub chunk request system
1 parent e5867a2 commit c721883

File tree

4 files changed

+523
-193
lines changed

4 files changed

+523
-193
lines changed

core/src/main/java/org/geysermc/geyser/level/chunk/GeyserChunk.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@
2626
package org.geysermc.geyser.level.chunk;
2727

2828
import com.github.steveice10.mc.protocol.data.game.chunk.DataPalette;
29+
import com.github.steveice10.mc.protocol.data.game.level.LightUpdateData;
30+
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
2931

3032
/**
31-
* Acts as a lightweight chunk class that doesn't store biomes, heightmaps or block entities.
33+
* Acts as a lightweight chunk class that doesn't store biomes.
3234
*/
33-
public record GeyserChunk(DataPalette[] sections) {
35+
public record GeyserChunk(DataPalette[] sections, BlockEntityInfo[][] blockEntities, LightUpdateData lightData) {
3436

35-
public static GeyserChunk from(DataPalette[] sections) {
36-
return new GeyserChunk(sections);
37+
public static GeyserChunk from(DataPalette[] sections, BlockEntityInfo[][] blockEntities, LightUpdateData lightData) {
38+
return new GeyserChunk(sections, blockEntities, lightData);
3739
}
3840
}

core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
package org.geysermc.geyser.session.cache;
2727

2828
import com.github.steveice10.mc.protocol.data.game.chunk.DataPalette;
29+
import com.github.steveice10.mc.protocol.data.game.level.LightUpdateData;
30+
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
2931
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
3032
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
3133
import lombok.Getter;
@@ -37,6 +39,7 @@
3739
import org.geysermc.geyser.util.MathUtils;
3840

3941
public class ChunkCache {
42+
@Getter
4043
private final boolean cache;
4144
private final Long2ObjectMap<GeyserChunk> chunks;
4245

@@ -57,20 +60,20 @@ public ChunkCache(GeyserSession session) {
5760
chunks = cache ? new Long2ObjectOpenHashMap<>() : null;
5861
}
5962

60-
public void addToCache(int x, int z, DataPalette[] chunks) {
63+
public void addToCache(int x, int z, DataPalette[] chunks, BlockEntityInfo[][] blockEntities, LightUpdateData lightData) {
6164
if (!cache) {
6265
return;
6366
}
6467

6568
long chunkPosition = MathUtils.chunkPositionToLong(x, z);
66-
GeyserChunk geyserChunk = GeyserChunk.from(chunks);
69+
GeyserChunk geyserChunk = GeyserChunk.from(chunks, blockEntities, lightData);
6770
this.chunks.put(chunkPosition, geyserChunk);
6871
}
6972

7073
/**
7174
* Doesn't check for cache enabled, so don't use this without checking that first!
7275
*/
73-
private GeyserChunk getChunk(int chunkX, int chunkZ) {
76+
public GeyserChunk getChunk(int chunkX, int chunkZ) {
7477
long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ);
7578
return chunks.getOrDefault(chunkPosition, null);
7679
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
/*
2+
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
* THE SOFTWARE.
21+
*
22+
* @author GeyserMC
23+
* @link https://github.com/GeyserMC/Geyser
24+
*/
25+
26+
package org.geysermc.geyser.translator.protocol.bedrock;
27+
28+
import com.github.steveice10.mc.protocol.data.game.chunk.DataPalette;
29+
import com.github.steveice10.mc.protocol.data.game.level.LightUpdateData;
30+
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
31+
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
32+
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
33+
import com.nukkitx.math.vector.Vector3i;
34+
import com.nukkitx.nbt.NBTOutputStream;
35+
import com.nukkitx.nbt.NbtMap;
36+
import com.nukkitx.nbt.NbtUtils;
37+
import com.nukkitx.protocol.bedrock.data.HeightMapDataType;
38+
import com.nukkitx.protocol.bedrock.data.SubChunkData;
39+
import com.nukkitx.protocol.bedrock.data.SubChunkRequestResult;
40+
import com.nukkitx.protocol.bedrock.packet.SubChunkPacket;
41+
import com.nukkitx.protocol.bedrock.packet.SubChunkRequestPacket;
42+
import org.geysermc.geyser.level.BedrockDimension;
43+
import org.geysermc.geyser.level.block.BlockStateValues;
44+
import org.geysermc.geyser.level.chunk.GeyserChunk;
45+
import org.geysermc.geyser.level.chunk.GeyserChunkSection;
46+
import org.geysermc.geyser.session.GeyserSession;
47+
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
48+
import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTranslator;
49+
import org.geysermc.geyser.translator.protocol.PacketTranslator;
50+
import org.geysermc.geyser.translator.protocol.Translator;
51+
import org.geysermc.geyser.translator.protocol.java.level.JavaLevelChunkWithLightTranslator;
52+
import org.geysermc.geyser.util.BlockEntityUtils;
53+
import org.geysermc.geyser.util.DimensionUtils;
54+
import io.netty.buffer.ByteBuf;
55+
import io.netty.buffer.ByteBufAllocator;
56+
import io.netty.buffer.ByteBufOutputStream;
57+
import io.netty.buffer.ByteBufUtil;
58+
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
59+
60+
import java.io.IOException;
61+
import java.util.BitSet;
62+
import java.util.List;
63+
64+
@Translator(packet = SubChunkRequestPacket.class)
65+
public class BedrockSubChunkRequestTranslator extends PacketTranslator<SubChunkRequestPacket> {
66+
@Override
67+
public void translate(GeyserSession session, SubChunkRequestPacket packet) {
68+
Vector3i centerPosition = packet.getSubChunkPosition();
69+
70+
SubChunkPacket subChunkPacket = new SubChunkPacket();
71+
subChunkPacket.setDimension(packet.getDimension());
72+
subChunkPacket.setCenterPosition(centerPosition);
73+
74+
int javaSubChunkOffset = session.getChunkCache().getChunkMinY();
75+
76+
BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension();
77+
int bedrockSubChunkMinY = bedrockDimension.minY() >> 4;
78+
int bedrockSubChunkMaxY = bedrockSubChunkMinY + (bedrockDimension.height() >> 4);
79+
80+
ByteBuf byteBuf = null, blobByteBuf = null;
81+
82+
try {
83+
for (Vector3i positionOffset : packet.getPositionOffsets()) {
84+
SubChunkData subChunkData = new SubChunkData();
85+
subChunkData.setPosition(positionOffset);
86+
subChunkPacket.getSubChunks().add(subChunkData);
87+
88+
if (!session.getChunkCache().isCache()) {
89+
subChunkData.setResult(SubChunkRequestResult.UNDEFINED);
90+
subChunkData.setData(new byte[0]);
91+
subChunkData.setHeightMapType(HeightMapDataType.NO_DATA);
92+
continue;
93+
}
94+
95+
if (packet.getDimension() != DimensionUtils.javaToBedrock(session.getDimension())) {
96+
subChunkData.setResult(SubChunkRequestResult.INVALID_DIMENSION);
97+
subChunkData.setData(new byte[0]);
98+
subChunkData.setHeightMapType(HeightMapDataType.NO_DATA);
99+
continue;
100+
}
101+
102+
Vector3i position = centerPosition.add(positionOffset);
103+
GeyserChunk chunk = session.getChunkCache().getChunk(position.getX(), position.getZ());
104+
if (chunk == null) {
105+
subChunkData.setResult(SubChunkRequestResult.CHUNK_NOT_FOUND);
106+
subChunkData.setData(new byte[0]);
107+
subChunkData.setHeightMapType(HeightMapDataType.NO_DATA);
108+
continue;
109+
}
110+
111+
int sectionY = position.getY() - javaSubChunkOffset;
112+
if (position.getY() < bedrockSubChunkMinY || position.getY() >= bedrockSubChunkMaxY) {
113+
subChunkData.setResult(SubChunkRequestResult.INDEX_OUT_OF_BOUNDS);
114+
subChunkData.setData(new byte[0]);
115+
subChunkData.setHeightMapType(HeightMapDataType.NO_DATA);
116+
continue;
117+
}
118+
119+
if (sectionY < 0) {
120+
subChunkData.setHeightMapType(HeightMapDataType.NO_DATA);
121+
} else {
122+
LightUpdateData lightData = chunk.lightData();
123+
BitSet emptyLightMask = lightData.getEmptySkyYMask();
124+
BitSet lightMask = lightData.getSkyYMask();
125+
List<byte[]> lightData_ = lightData.getSkyUpdates();
126+
if (emptyLightMask.get(sectionY + 1)) {
127+
subChunkData.setHeightMapType(HeightMapDataType.TOO_HIGH);
128+
} else if (lightMask.get(sectionY + 1)) {
129+
byte[] belowLight;
130+
if (lightMask.get(sectionY)) {
131+
int belowSection = 0;
132+
for (int i = 0; i < sectionY; i++) {
133+
if (lightMask.get(i)) {
134+
belowSection++;
135+
}
136+
}
137+
belowLight = lightData_.get(belowSection);
138+
} else {
139+
belowLight = null;
140+
}
141+
int lightIndex = 0;
142+
for (int i = 0; i < sectionY + 1; i++) {
143+
if (lightMask.get(i)) {
144+
lightIndex++;
145+
}
146+
}
147+
byte[] light = lightData_.get(lightIndex);
148+
byte[] aboveLight;
149+
if (lightMask.get(sectionY + 2)) {
150+
int aboveSection = 0;
151+
for (int i = 0; i < sectionY + 2; i++) {
152+
if (lightMask.get(i)) {
153+
aboveSection++;
154+
}
155+
}
156+
aboveLight = lightData_.get(aboveSection);
157+
} else {
158+
aboveLight = null;
159+
}
160+
161+
byte[] heightMapData = new byte[16 * 16];
162+
boolean lower = true, higher = true;
163+
xyLoop: for (int i = 0; i < heightMapData.length; i++) {
164+
if (aboveLight != null) {
165+
int key = i;
166+
int index = key >> 1;
167+
int part = key & 1;
168+
int value = part == 0 ? aboveLight[index] & 15 : aboveLight[index] >> 4 & 15;
169+
if (value != 0xF) {
170+
heightMapData[i] = 16;
171+
lower = false;
172+
continue;
173+
}
174+
}
175+
for (int y = 15; y != -1; y--) {
176+
int key = i | y << 8;
177+
int index = key >> 1;
178+
int part = key & 1;
179+
int value = part == 0 ? light[index] & 15 : light[index] >> 4 & 15;
180+
if (value != 0xF) {
181+
heightMapData[i] = (byte) y;
182+
lower = false;
183+
higher = false;
184+
continue xyLoop;
185+
}
186+
}
187+
if (belowLight != null) {
188+
int key = i | 15 << 8;
189+
int index = key >> 1;
190+
int part = key & 1;
191+
int value = part == 0 ? belowLight[index] & 15 : belowLight[index] >> 4 & 15;
192+
if (value != 0xF) {
193+
heightMapData[i] = -1;
194+
higher = false;
195+
}
196+
}
197+
}
198+
if (lower) {
199+
subChunkData.setHeightMapType(HeightMapDataType.TOO_LOW);
200+
} else if (higher) {
201+
subChunkData.setHeightMapType(HeightMapDataType.TOO_HIGH);
202+
} else {
203+
subChunkData.setHeightMapType(HeightMapDataType.HAS_DATA);
204+
subChunkData.setHeightMapData(heightMapData);
205+
}
206+
} else {
207+
subChunkData.setHeightMapType(HeightMapDataType.TOO_LOW);
208+
}
209+
}
210+
211+
DataPalette javaSection = sectionY < 0 || sectionY >= chunk.sections().length ? null : chunk.sections()[sectionY];
212+
if (javaSection == null) {
213+
subChunkData.setResult(SubChunkRequestResult.SUCCESS_ALL_AIR);
214+
subChunkData.setData(new byte[0]);
215+
continue;
216+
}
217+
218+
final BlockEntityInfo[] blockEntities = chunk.blockEntities()[sectionY];
219+
final List<NbtMap> bedrockBlockEntities = new ObjectArrayList<>();
220+
221+
GeyserChunkSection section = JavaLevelChunkWithLightTranslator.translateSubChunk(session, position, javaSection, bedrockBlockEntities);
222+
223+
final int chunkBlockX = position.getX() << 4;
224+
final int chunkBlockZ = position.getZ() << 4;
225+
for (BlockEntityInfo blockEntity : blockEntities) {
226+
BlockEntityType type = blockEntity.getType();
227+
if (type == null) {
228+
// As an example: ViaVersion will send -1 if it cannot find the block entity type
229+
// Vanilla Minecraft gracefully handles this
230+
continue;
231+
}
232+
CompoundTag tag = blockEntity.getNbt();
233+
int x = blockEntity.getX(); // Relative to chunk
234+
int y = blockEntity.getY();
235+
int z = blockEntity.getZ(); // Relative to chunk
236+
237+
// Get the Java block state ID from block entity position
238+
int blockState = javaSection.get(x, y & 0xF, z);
239+
240+
if (type == BlockEntityType.LECTERN && BlockStateValues.getLecternBookStates().get(blockState)) {
241+
// If getLecternBookStates is false, let's just treat it like a normal block entity
242+
bedrockBlockEntities.add(session.getGeyser().getWorldManager().getLecternDataAt(
243+
session, x + chunkBlockX, y, z + chunkBlockZ, true));
244+
continue;
245+
}
246+
247+
BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(type);
248+
bedrockBlockEntities.add(blockEntityTranslator.getBlockEntityTag(type, x + chunkBlockX, y, z + chunkBlockZ, tag, blockState));
249+
250+
// Check for custom skulls
251+
if (session.getPreferencesCache().showCustomSkulls() && type == BlockEntityType.SKULL && tag != null && tag.contains("SkullOwner")) {
252+
SkullBlockEntityTranslator.translateSkull(session, tag, x + chunkBlockX, y, z + chunkBlockZ, blockState);
253+
}
254+
}
255+
256+
if (byteBuf == null) {
257+
byteBuf = ByteBufAllocator.DEFAULT.buffer(section.estimateNetworkSize() + bedrockBlockEntities.size() * 64);
258+
if (!subChunkPacket.isCacheEnabled()) {
259+
blobByteBuf = byteBuf;
260+
}
261+
} else {
262+
byteBuf.clear();
263+
}
264+
265+
if (subChunkPacket.isCacheEnabled()) {
266+
if (blobByteBuf != null) {
267+
blobByteBuf.clear();
268+
} else {
269+
blobByteBuf = ByteBufAllocator.DEFAULT.buffer(section.estimateNetworkSize());
270+
}
271+
}
272+
273+
section.writeToNetwork(blobByteBuf);
274+
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf));
275+
for (NbtMap blockEntity : bedrockBlockEntities) {
276+
nbtStream.writeTag(blockEntity);
277+
}
278+
279+
subChunkData.setResult(SubChunkRequestResult.SUCCESS);
280+
subChunkData.setData(ByteBufUtil.getBytes(byteBuf));
281+
subChunkPacket.getSubChunks().add(subChunkData);
282+
}
283+
284+
session.sendUpstreamPacket(subChunkPacket);
285+
} catch (IOException ex) {
286+
session.getGeyser().getLogger().error("IO error while encoding chunk", ex);
287+
} finally {
288+
if (byteBuf != null) {
289+
byteBuf.release();
290+
}
291+
if (subChunkPacket.isCacheEnabled() && blobByteBuf != null) {
292+
blobByteBuf.release();
293+
}
294+
}
295+
}
296+
}

0 commit comments

Comments
 (0)