commit a4f9b7a

uint  ·  2026-08-04 19:50:05 +0000 UTC
parent 1b98d94
move test_scene chunking functionality to net/mc_chunk and client/world
10 files changed,  +583, -420
+25, -0
 1@@ -0,0 +1,25 @@
 2+#ifndef WORLD_H
 3+#define WORLD_H
 4+
 5+#include "net/mc_chunk.h"
 6+#include "world/chunk.h"
 7+
 8+#define CLIENT_WORLD_MAX_CHUNKS 128
 9+
10+typedef struct {
11+	Chunk chunks[CLIENT_WORLD_MAX_CHUNKS];
12+} ClientWorld;
13+
14+/* initialise world with 0 */
15+void world_init(ClientWorld* world);
16+
17+/* destroy world (frees all chunks) */
18+void world_destroy(ClientWorld* world);
19+
20+/* remove chunk from  world (clears data, marks unsued) */
21+void world_remove_chunk(ClientWorld* world, i32 x, i32 z);
22+
23+/* update chunk with data from server */
24+i8 world_update_chunk(ClientWorld* world, const MCChunkUpdate* update, const u8* data);
25+
26+#endif /* WORLD_H */
+38, -0
 1@@ -0,0 +1,38 @@
 2+#ifndef MC_CHUNK_H
 3+#define MC_CHUNK_H
 4+
 5+#include <stddef.h>
 6+
 7+#include "types.h"
 8+#include "net/mc_proto.h"
 9+
10+typedef struct {
11+	i32 x;
12+	i32 z;
13+	u16 mask;
14+	i8  replace;
15+} MCChunkUpdate;
16+
17+typedef struct {
18+	MCChunkUpdate* updates;
19+	MCChunkUpdate  single_update;
20+	i32            update_count;
21+	const u8*      data;
22+	size_t         data_size;
23+	u8             skylight;
24+	i8             bulk;
25+	i8             unload;
26+	i32            unload_x;
27+	i32            unload_z;
28+} MCChunkPacket;
29+
30+/* general decode chunk data from packet */
31+i8 mc_chunk_decode(MCChunkPacket* chunk, const MCPacket* packet);
32+
33+/* destroy chunk data */
34+void mc_chunk_destroy(MCChunkPacket* chunk);
35+
36+/* calculate size of chunk data */
37+size_t mc_chunk_update_size(u16 mask, u8 skylight, i8 bulk);
38+
39+#endif /* MC_CHUNK_H */
+31, -316
  1@@ -5,6 +5,7 @@
  2 
  3 #include "mgmath.h"
  4 #include "util.h"
  5+#include "client/world.h"
  6 #include "net/mc_proto.h"
  7 #include "net/packet.h"
  8 #include "client/camera.h"
  9@@ -15,293 +16,15 @@
 10 
 11 /* big TODO. most of this stuff belongs elsewhere !!! */
 12 
 13-typedef struct {
 14-	i32 x;
 15-	i32 z;
 16-
 17-	Chunk sections[16]; /* a 16x256x16 chunk */
 18-	u16   section_mask;
 19-
 20-	i8 used;
 21-} ServerChunk;
 22-
 23-typedef struct {
 24-	i32 x;
 25-	i32 z;
 26-	u16 mask;
 27-} BulkChunkMeta;
 28+#define TEST_WORLD_WIDTH 50
 29+#define TEST_WORLD_DEPTH 50
 30 
 31 static MGCamera cam;
 32-static Chunk    chunks[WORLD_WIDTH][WORLD_DEPTH];
 33+static Chunk    chunks[TEST_WORLD_WIDTH][TEST_WORLD_DEPTH];
 34 
 35-#define NCHUNKS 128
 36-static ServerChunk server_chunks[NCHUNKS];
 37+static ClientWorld server_world;
 38 static i8          server_mode;
 39 
 40-ServerChunk* server_chunk_find(i32 x, i32 z)
 41-{
 42-	i32 i;
 43-
 44-	for (i = 0; i < NCHUNKS; i++) {
 45-		if (!server_chunks[i].used)
 46-			continue;
 47-
 48-		if (server_chunks[i].x == x && server_chunks[i].z == z)
 49-			return &server_chunks[i];
 50-	}
 51-
 52-	return NULL;
 53-}
 54-
 55-ServerChunk* server_chunk_get(i32 x, i32 z)
 56-{
 57-	ServerChunk* col;
 58-	i32          i;
 59-
 60-	col = server_chunk_find(x, z);
 61-	if (col)
 62-		return col;
 63-
 64-	for (i = 0; i < NCHUNKS; i++) {
 65-		if (server_chunks[i].used)
 66-			continue;
 67-
 68-		col = &server_chunks[i];
 69-
 70-		memset(col, 0, sizeof(*col));
 71-
 72-		col->used = 1;
 73-		col->x = x;
 74-		col->z = z;
 75-
 76-		return col;
 77-	}
 78-
 79-	mg_log(LOG_ERR, "Server chunk storage is full");
 80-	return NULL;
 81-}
 82-
 83-void server_chunk_clear(ServerChunk* column)
 84-{
 85-	i32 section;
 86-
 87-	if (!column)
 88-		return;
 89-
 90-	for (section = 0; section < 16; section++) {
 91-		chunk_mesh_destroy(&column->sections[section]);
 92-		memset(&column->sections[section], 0, sizeof(column->sections[section]));
 93-	}
 94-
 95-	column->section_mask = 0;
 96-}
 97-
 98-void server_chunk_remove(ServerChunk* column)
 99-{
100-	if (!column)
101-		return;
102-
103-	server_chunk_clear(column);
104-	memset(column, 0, sizeof(*column));
105-}
106-
107-i32 section_count(u16 mask)
108-{
109-	i32 section;
110-	i32 count;
111-
112-	count = 0;
113-
114-	for (section = 0; section < 16; section++) {
115-		if (mask & (1U << section))
116-			count++;
117-	}
118-
119-	return count;
120-}
121-
122-i8 load_server_section(Chunk* section, const u8* data, size_t size)
123-{
124-	i32 index;
125-	i32 x;
126-	i32 y;
127-	i32 z;
128-	u16 state;
129-
130-	if (size < 8192)
131-		return 0;
132-
133-	chunk_mesh_destroy(section);
134-	memset(section, 0, sizeof(*section));
135-
136-	for (index = 0; index < 4096; index++) {
137-		state = (u16)data[index * 2] | ((u16)data[index * 2 + 1] << 8);
138-
139-		x = index & 15;
140-		z = (index >> 4) & 15;
141-		y = (index >> 8) & 15;
142-
143-		chunk_set(section, x, y, z, state);
144-	}
145-
146-	if (!chunk_mesh_create(section)) {
147-		mg_log(LOG_ERR, "Failed to create server section mesh");
148-		return 0;
149-	}
150-
151-	return 1;
152-}
153-
154-void test_scene_chunk(const MCPacket* packet)
155-{
156-	PacketReader reader = packet_reader_init(packet->data, packet->size);
157-	ServerChunk* column;
158-	const u8*    data;
159-	size_t       required_size;
160-
161-	i32 chunk_x;
162-	i32 chunk_z;
163-	i32 data_size;
164-	i32 included;
165-	i32 section;
166-	i32 data_section;
167-	u8  ground_up;
168-	u16 mask;
169-
170-	if (!packet_read_i32(&reader, &chunk_x) || !packet_read_i32(&reader, &chunk_z) ||
171-	    !packet_read_u8(&reader, &ground_up) || !packet_read_u16(&reader, &mask) ||
172-	    !packet_read_varint(&reader, &data_size)) {
173-		mg_log(LOG_ERR, "Malformed Chunk Data packet");
174-		return;
175-	}
176-
177-	if (data_size < 0)
178-		return;
179-
180-	if ((size_t)data_size > reader.size - reader.cursor)
181-		return;
182-
183-	if (ground_up && mask == 0) {
184-		column = server_chunk_find(chunk_x, chunk_z);
185-		if (column)
186-			server_chunk_remove(column);
187-		return;
188-	}
189-
190-	included = section_count(mask);
191-	required_size = included * 8192;
192-
193-	if (required_size > (size_t)data_size) {
194-		mg_log(LOG_ERR, "Chunk packet block data is imcomplete");
195-		return;
196-	}
197-
198-	column = server_chunk_get(chunk_x, chunk_z);
199-
200-	if (!column)
201-		return;
202-
203-	if (ground_up)
204-		server_chunk_clear(column);
205-
206-	data = reader.data + reader.cursor;
207-	data_section = 0;
208-
209-	for (section = 0; section < 16; section++) {
210-		if (!(mask & (1U << section)))
211-			continue;
212-
213-		if (!load_server_section(&column->sections[section], data + data_section * 8192, 8192))
214-			return;
215-
216-		column->section_mask |= (u16)(1U << section);
217-		data_section++;
218-	}
219-}
220-
221-void test_scene_bulk_chunks(const MCPacket* packet)
222-{
223-	PacketReader   reader = packet_reader_init(packet->data, packet->size);
224-	BulkChunkMeta* metadata;
225-	ServerChunk*   column;
226-	const u8*      data;
227-	size_t         offset;
228-	size_t         block_size;
229-	size_t         column_size;
230-	i32            count;
231-	i32            index;
232-	i32            sections;
233-	i32            section;
234-	i32            data_section;
235-	u8             skylight;
236-
237-	reader.data = packet->data;
238-	reader.size = packet->size;
239-	reader.cursor = 0;
240-
241-	if (!packet_read_u8(&reader, &skylight) || !packet_read_varint(&reader, &count)) {
242-		mg_log(LOG_ERR, "Malformed Map Chunk Bulk packet");
243-		return;
244-	}
245-
246-	if (count <= 0 || count > 4096)
247-		return;
248-
249-	metadata = malloc(count * sizeof(*metadata));
250-
251-	if (!metadata)
252-		return;
253-
254-	for (index = 0; index < count; index++) {
255-		if (!packet_read_i32(&reader, &metadata[index].x) || !packet_read_i32(&reader, &metadata[index].z) ||
256-		    !packet_read_u16(&reader, &metadata[index].mask)) {
257-			free(metadata);
258-			return;
259-		}
260-	}
261-
262-	data = reader.data + reader.cursor;
263-	offset = 0;
264-
265-	for (index = 0; index < count; index++) {
266-		sections = section_count(metadata[index].mask);
267-		block_size = sections * 8192;
268-		column_size = block_size + sections * 2048 + (skylight ? sections * 2048 : 0) + 256;
269-
270-		if (offset > reader.size - reader.cursor)
271-			break;
272-
273-		if (column_size > reader.size - reader.cursor - offset) {
274-			mg_log(LOG_ERR, "Bulk chunk data is incomplete");
275-			break;
276-		}
277-
278-		column = server_chunk_get(metadata[index].x, metadata[index].z);
279-		if (!column) {
280-			offset += column_size;
281-			continue;
282-		}
283-
284-		server_chunk_clear(column);
285-		data_section = 0;
286-
287-		for (section = 0; section < 16; section++) {
288-			if (!(metadata[index].mask & (1U << section)))
289-				continue;
290-
291-			if (!load_server_section(&column->sections[section], data + offset + data_section * 8192, 8192))
292-				break;
293-
294-			column->section_mask |= (u16)(1U << section);
295-			data_section++;
296-		}
297-
298-		offset += column_size;
299-	}
300-
301-	free(metadata);
302-}
303-
304 void test_scene_create(Player* player, i8 server)
305 {
306 	i32 x;
307@@ -315,7 +38,7 @@ void test_scene_create(Player* player, i8 server)
308 	cam.pitch = MG_AngleDeg(-25.0f);
309 
310 	server_mode = server;
311-	memset(server_chunks, 0, sizeof(server_chunks));
312+	world_init(&server_world);
313 	player->x = 0.0;
314 	player->y = 0.0;
315 	player->z = 0.0;
316@@ -327,11 +50,13 @@ void test_scene_create(Player* player, i8 server)
317 		cam.far = 512.0f;
318 	}
319 	else { /* test world */
320-		for (z = 0; z < WORLD_DEPTH; z++) {
321-			for (x = 0; x < WORLD_WIDTH; x++) {
322-				chunk_create(&chunks[x][z]);
323+		for (z = 0; z < TEST_WORLD_DEPTH; z++) {
324+			for (x = 0; x < TEST_WORLD_WIDTH; x++) {
325+				chunk_clear(&chunks[x][z]);
326+				chunk_section_create(&chunks[x][z].sections[0]);
327+				chunks[x][z].section_mask = 1;
328 
329-				if (!chunk_mesh_create(&chunks[x][z]))
330+				if (!chunk_section_mesh_create(&chunks[x][z].sections[0]))
331 					mg_die(MG_EC_MESH_CREATE, "Failed to create chunk mesh");
332 			}
333 		}
334@@ -351,10 +76,10 @@ void test_scene_draw(MGContext* ctx)
335 	float  aspect;
336 	i32    texture;
337 
338-	ServerChunk* column;
339-	Chunk*       section_chunk;
340-	i32          section;
341-	i32          i;
342+	Chunk*        column;
343+	ChunkSection* section_chunk;
344+	i32           section;
345+	i32           i;
346 
347 	platform_get_drawable_size(ctx, &width, &height);
348 
349@@ -367,19 +92,19 @@ void test_scene_draw(MGContext* ctx)
350 	view = camera_get_view(&cam);
351 
352 	if (server_mode) {
353-		for (i = 0; i < NCHUNKS; i++) {
354-			column = &server_chunks[i];
355+		for (i = 0; i < CLIENT_WORLD_MAX_CHUNKS; i++) {
356+			column = &server_world.chunks[i];
357 
358 			if (!column->used)
359 				continue;
360 
361-			for (section = 0; section < 16; section++) {
362+			for (section = 0; section < CHUNK_SECTION_COUNT; section++) {
363 				if (!(column->section_mask & (1U << section)))
364 					continue;
365 
366 				section_chunk = &column->sections[section];
367-				model = MG_Translate(
368-				    MG_V3(column->x * CHUNK_WIDTH, section * CHUNK_HEIGHT, column->z * CHUNK_DEPTH));
369+				model = MG_Translate(MG_V3(column->x * CHUNK_WIDTH, section * CHUNK_SECTION_HEIGHT,
370+				                           column->z * CHUNK_DEPTH));
371 				mvp = MG_MulM4(proj, MG_MulM4(view, model));
372 				for (texture = 0; texture < BLOCK_TEXTURE_COUNT; texture++) {
373 					if (section_chunk->meshes[texture] != MGMESH_INVALID)
374@@ -390,16 +115,16 @@ void test_scene_draw(MGContext* ctx)
375 		}
376 	}
377 	else { /* test world */
378-		for (z = 0; z < WORLD_DEPTH; z++) {
379-			for (x = 0; x < WORLD_WIDTH; x++) {
380+		for (z = 0; z < TEST_WORLD_DEPTH; z++) {
381+			for (x = 0; x < TEST_WORLD_WIDTH; x++) {
382 				model = MG_Translate(MG_V3(x * CHUNK_WIDTH, 0.0f, z * CHUNK_DEPTH));
383 				mvp = MG_MulM4(proj, MG_MulM4(view, model));
384 
385 				for (texture = 0; texture < BLOCK_TEXTURE_COUNT; texture++) {
386-					if (chunks[x][z].meshes[texture] == MGMESH_INVALID)
387+					if (chunks[x][z].sections[0].meshes[texture] == MGMESH_INVALID)
388 						continue;
389-					renderer_mesh_draw(chunks[x][z].meshes[texture], block_texture_get(texture),
390-					                   &mvp);
391+					renderer_mesh_draw(chunks[x][z].sections[0].meshes[texture],
392+					                   block_texture_get(texture), &mvp);
393 				}
394 			}
395 		}
396@@ -411,22 +136,12 @@ void test_scene_destroy(void)
397 	i32 x;
398 	i32 z;
399 
400-	i32 section;
401-	i32 i;
402-
403-	if (server_mode) {
404-		for (i = 0; i < NCHUNKS; i++) {
405-			if (!server_chunks[i].used)
406-				continue;
407-
408-			for (section = 0; section < 16; section++)
409-				chunk_mesh_destroy(&server_chunks[i].sections[section]);
410-		}
411-	}
412+	if (server_mode)
413+		world_destroy(&server_world);
414 	else {
415-		for (z = 0; z < WORLD_DEPTH; z++) {
416-			for (x = 0; x < WORLD_WIDTH; x++)
417-				chunk_mesh_destroy(&chunks[x][z]);
418+		for (z = 0; z < TEST_WORLD_DEPTH; z++) {
419+			for (x = 0; x < TEST_WORLD_WIDTH; x++)
420+				chunk_remove(&chunks[x][z]);
421 		}
422 	}
423 
+35, -22
 1@@ -6,38 +6,51 @@
 2 #include "renderer/renderer.h"
 3 
 4 #define CHUNK_WIDTH 16
 5-#define CHUNK_HEIGHT 16
 6 #define CHUNK_DEPTH 16
 7+#define CHUNK_HEIGHT 256
 8 
 9-#define WORLD_WIDTH 10
10-#define WORLD_DEPTH 10
11+#define CHUNK_SECTION_WIDTH 16
12+#define CHUNK_SECTION_HEIGHT 16
13+#define CHUNK_SECTION_DEPTH 16
14+#define CHUNK_SECTION_COUNT 16
15 
16 typedef struct {
17-	BlockType blocks[CHUNK_WIDTH][CHUNK_HEIGHT][CHUNK_DEPTH];
18+	BlockType blocks[CHUNK_SECTION_WIDTH][CHUNK_SECTION_HEIGHT][CHUNK_SECTION_DEPTH];
19 	MGMesh    meshes[BLOCK_TEXTURE_COUNT];
20+} ChunkSection;
21+
22+typedef struct {
23+	i32 x;
24+	i32 z;
25+
26+	ChunkSection sections[CHUNK_SECTION_COUNT];
27+	u16          section_mask;
28+	i8           used;
29 } Chunk;
30 
31-/* create a new chunk
32-   just stone and dirt with a hint of brick for now */
33-void chunk_create(Chunk* chunk);
34+/* clear chunk contents and mark unused */
35+void chunk_clear(Chunk* chunk);
36+
37+/* clear chunk contents and keep unloaded */
38+void chunk_remove(Chunk* chunk);
39+
40+/* create a new chunk section
41+   TODO: make it general not only for test world */
42+void chunk_section_create(ChunkSection* section);
43+
44+/* decode packed block states into a chunk section */
45+i8 chunk_section_decode(ChunkSection* section, const u8* data, size_t size);
46 
47-/* get the block at specified position
48-   TODO? should we use x, y, z or a vec3?  c89 doesnt
49-         have compound literals so it may be a pain in
50-         the future? who knows */
51-BlockType chunk_get(const Chunk* chunk, i32 x, i32 y, i32 z);
52+/* get the block at specified coordinate in chunk section */
53+BlockType chunk_section_get(const ChunkSection* section, i32 x, i32 y, i32 z);
54 
55-/* set the block at specified position
56-   TODO? should we use x, y, z or a vec3?  c89 doesnt
57-         have compound literals so it may be a pain in
58-         the future? who knows */
59-void chunk_set(Chunk* chunk, i32 x, i32 y, i32 z, BlockType block);
60+/* set the block at specified coordinate in chunk section */
61+void chunk_section_set(ChunkSection* section, i32 x, i32 y, i32 z, BlockType block);
62 
63-/* create a mesh for chunk by generating vertices
64-   and indices for each (visible) face */
65-i8 chunk_mesh_create(Chunk* chunk);
66+/* create a mesh for a chunk section */
67+i8 chunk_section_mesh_create(ChunkSection* section);
68 
69-/* destroy a chunks mesh */
70-void chunk_mesh_destroy(Chunk* chunk);
71+/* destroy a chunk sections mesh */
72+void chunk_section_mesh_destroy(ChunkSection* section);
73 
74 #endif /* CHUNK_H */
+2, -0
 1@@ -29,7 +29,9 @@ sources = [
 2 	'source/world/chunk.c',
 3 	'source/client/camera.c',
 4 	'source/client/player.c',
 5+	'source/client/world.c',
 6 	'source/net/mc_auth.c',
 7+	'source/net/mc_chunk.c',
 8 	'source/net/mc_buffer.c',
 9 	'source/net/mc_client.c',
10 	'source/net/mc_crypto.c',
+121, -0
  1@@ -0,0 +1,121 @@
  2+#include <string.h>
  3+
  4+#include "types.h"
  5+#include "util.h"
  6+#include "client/world.h"
  7+
  8+#define MC_CHUNK_SECTION_BLOCK_BYTES 8192
  9+
 10+static Chunk* world_chunk_find(ClientWorld* world, i32 x, i32 z);
 11+static Chunk* world_chunk_get(ClientWorld* world, i32 x, i32 z);
 12+
 13+static Chunk* world_chunk_find(ClientWorld* world, i32 x, i32 z)
 14+{
 15+	i32 i;
 16+
 17+	for (i = 0; i < CLIENT_WORLD_MAX_CHUNKS; i++) {
 18+		if (!world->chunks[i].used)
 19+			continue;
 20+
 21+		if (world->chunks[i].x == x && world->chunks[i].z == z)
 22+			return &world->chunks[i];
 23+	}
 24+
 25+	return NULL;
 26+}
 27+
 28+static Chunk* world_chunk_get(ClientWorld* world, i32 x, i32 z)
 29+{
 30+	Chunk* chunk;
 31+	i32    i;
 32+
 33+	chunk = world_chunk_find(world, x, z);
 34+	if (chunk)
 35+		return chunk;
 36+
 37+	for (i = 0; i < CLIENT_WORLD_MAX_CHUNKS; i++) {
 38+		if (world->chunks[i].used)
 39+			continue;
 40+
 41+		/* unused chunk: init and return */
 42+		chunk = &world->chunks[i];
 43+		memset(chunk, 0, sizeof(*chunk));
 44+		chunk->used = 1;
 45+		chunk->x = x;
 46+		chunk->z = z;
 47+		return chunk;
 48+	}
 49+
 50+	mg_log(LOG_ERR, "World chunk storage is full");
 51+	return NULL;
 52+}
 53+
 54+void world_init(ClientWorld* world)
 55+{
 56+	if (!world)
 57+		return;
 58+
 59+	memset(world, 0, sizeof(*world));
 60+}
 61+
 62+void world_destroy(ClientWorld* world)
 63+{
 64+	i32 i;
 65+
 66+	if (!world)
 67+		return;
 68+
 69+	for (i = 0; i < CLIENT_WORLD_MAX_CHUNKS; i++)
 70+		chunk_remove(&world->chunks[i]);
 71+}
 72+
 73+void world_remove_chunk(ClientWorld* world, i32 x, i32 z)
 74+{
 75+	Chunk* chunk;
 76+
 77+	if (!world)
 78+		return;
 79+
 80+	chunk = world_chunk_find(world, x, z);
 81+	if (chunk)
 82+		chunk_remove(chunk);
 83+}
 84+
 85+i8 world_update_chunk(ClientWorld* world, const MCChunkUpdate* update, const u8* data)
 86+{
 87+	Chunk* chunk;
 88+	i32    section;
 89+	i32    data_section;
 90+
 91+	if (!world || !update || !data)
 92+		return 0;
 93+
 94+	chunk = world_chunk_get(world, update->x, update->z);
 95+	if (!chunk)
 96+		return 1;
 97+
 98+	if (update->replace)
 99+		chunk_clear(chunk);
100+
101+	/* decode each section in chunk */
102+	data_section = 0;
103+	for (section = 0; section < CHUNK_SECTION_COUNT; section++) {
104+		/* skip sections not in update mask */
105+		if (!(update->mask & (1U << section)))
106+			continue;
107+
108+		/* decode section data into mesh */
109+		if (!chunk_section_decode(&chunk->sections[section],
110+		                          data + (size_t)data_section * MC_CHUNK_SECTION_BLOCK_BYTES,
111+		                          MC_CHUNK_SECTION_BLOCK_BYTES)) {
112+			mg_log(LOG_ERR, "Failed to create chunk mesh");
113+			return 0;
114+		}
115+
116+		/* mark section present in chunk */
117+		chunk->section_mask |= (u16)(1U << section);
118+		data_section++;
119+	}
120+
121+	return 1;
122+}
+36, -3
 1@@ -4,6 +4,7 @@
 2 #include "config.h"
 3 #include "util.h"
 4 #include "client/player.h"
 5+#include "net/mc_chunk.h"
 6 #include "net/mc_proto.h"
 7 #include "platform/platform.h"
 8 #include "renderer/renderer.h"
 9@@ -57,6 +58,40 @@ static void input(void)
10 	camera_rotate(&cam, mx * sensitivity, -my * sensitivity);
11 }
12 
13+static void chunk_packet(ClientWorld* world, const MCPacket* packet)
14+{
15+	MCChunkPacket chunk;
16+	size_t        offset;
17+	size_t        size;
18+	i32           update;
19+
20+	if (!mc_chunk_decode(&chunk, packet))
21+		return;
22+
23+	if (chunk.unload) {
24+		world_remove_chunk(world, chunk.unload_x, chunk.unload_z);
25+		mc_chunk_destroy(&chunk);
26+		return;
27+	}
28+
29+	offset = 0;
30+
31+	for (update = 0; update < chunk.update_count; update++) {
32+		size = mc_chunk_update_size(chunk.updates[update].mask, chunk.skylight, chunk.bulk);
33+		if (offset > chunk.data_size || size > chunk.data_size - offset) {
34+			mg_log(LOG_ERR, "Chunk data is incomplete");
35+			break;
36+		}
37+
38+		if (!world_update_chunk(world, &chunk.updates[update], chunk.data + offset))
39+			break;
40+
41+		offset += size;
42+	}
43+
44+	mc_chunk_destroy(&chunk);
45+}
46+
47 int main(int argc, char** argv)
48 {
49 	MCClient* client = NULL;
50@@ -107,10 +142,8 @@ int main(int argc, char** argv)
51 					player_server_handle_position(&player, &cam, &packet);
52 					break;
53 				case 0x21:
54-					test_scene_chunk(&packet);
55-					break;
56 				case 0x26:
57-					test_scene_bulk_chunks(&packet);
58+					chunk_packet(&server_world, &packet);
59 					break;
60 				}
61 
+151, -0
  1@@ -0,0 +1,151 @@
  2+#include <stdlib.h>
  3+#include <string.h>
  4+
  5+#include "util.h"
  6+#include "net/mc_chunk.h"
  7+#include "net/packet.h"
  8+#include "world/chunk.h"
  9+
 10+#define MC_CHUNK_SECTION_BLOCK_BYTES 8192
 11+#define MC_CHUNK_SECTION_LIGHT_BYTES 2048
 12+#define MC_CHUNK_BIOME_BYTES 256
 13+
 14+static i8  mc_chunk_decode_single(MCChunkPacket* chunk, PacketReader* reader);
 15+static i8  mc_chunk_decode_bulk(MCChunkPacket* chunk, PacketReader* reader);
 16+static i32 mc_chunk_section_count(u16 mask);
 17+
 18+/* decode single chunk data from packet */
 19+static i8 mc_chunk_decode_single(MCChunkPacket* chunk, PacketReader* reader)
 20+{
 21+	MCChunkUpdate* update;
 22+	i32            declared_size;
 23+	u8             ground_up;
 24+
 25+	update = &chunk->single_update;
 26+
 27+	/* read chunk coordinates, mask, size */
 28+	if (!packet_read_i32(reader, &update->x) || !packet_read_i32(reader, &update->z) ||
 29+	    !packet_read_u8(reader, &ground_up) || !packet_read_u16(reader, &update->mask) ||
 30+	    !packet_read_varint(reader, &declared_size)) {
 31+		mg_log(LOG_ERR, "Malformed Chunk Data packet");
 32+		return 0;
 33+	}
 34+
 35+	if (declared_size < 0 || (size_t)declared_size > reader->size - reader->cursor)
 36+		return 0;
 37+
 38+	/* if ground_up set and mask is 0, set chunk for unload */
 39+	if (ground_up && update->mask == 0) {
 40+		chunk->unload = 1;
 41+		chunk->unload_x = update->x;
 42+		chunk->unload_z = update->z;
 43+		return 1;
 44+	}
 45+
 46+	update->replace = ground_up;
 47+	chunk->updates = update;
 48+	chunk->update_count = 1;
 49+	chunk->data = reader->data + reader->cursor;
 50+	chunk->data_size = declared_size;
 51+	return 1;
 52+}
 53+
 54+/* decode bulk chunk data from packet */
 55+static i8 mc_chunk_decode_bulk(MCChunkPacket* chunk, PacketReader* reader)
 56+{
 57+	i32 update;
 58+
 59+	if (!packet_read_u8(reader, &chunk->skylight) || !packet_read_varint(reader, &chunk->update_count)) {
 60+		mg_log(LOG_ERR, "Malformed Map Chunk Bulk packet");
 61+		return 0;
 62+	}
 63+
 64+	if (chunk->update_count <= 0 || chunk->update_count > 4096)
 65+		return 0;
 66+
 67+	chunk->updates = malloc((size_t)chunk->update_count * sizeof(*chunk->updates));
 68+	if (!chunk->updates)
 69+		return 0;
 70+
 71+	/* read chunk updates */
 72+	for (update = 0; update < chunk->update_count; update++) {
 73+		if (!packet_read_i32(reader, &chunk->updates[update].x) ||
 74+		    !packet_read_i32(reader, &chunk->updates[update].z) ||
 75+		    !packet_read_u16(reader, &chunk->updates[update].mask)) {
 76+			mg_log(LOG_ERR, "Malformed Map Chunk Bulk packet");
 77+			mc_chunk_destroy(chunk);
 78+			return 0;
 79+		}
 80+
 81+		chunk->updates[update].replace = 1;
 82+	}
 83+
 84+	chunk->bulk = 1;
 85+	chunk->data = reader->data + reader->cursor;
 86+	chunk->data_size = reader->size - reader->cursor;
 87+	return 1;
 88+}
 89+
 90+/* count number of chunk sections mask */
 91+static i32 mc_chunk_section_count(u16 mask)
 92+{
 93+	i32 section;
 94+	i32 count;
 95+
 96+	count = 0;
 97+
 98+	for (section = 0; section < CHUNK_SECTION_COUNT; section++) {
 99+		if (mask & (1U << section)) /* section present in mask */
100+			count++;
101+	}
102+
103+	return count;
104+}
105+
106+i8 mc_chunk_decode(MCChunkPacket* chunk, const MCPacket* packet)
107+{
108+	PacketReader reader;
109+
110+	if (!chunk || !packet)
111+		return 0;
112+
113+	memset(chunk, 0, sizeof(*chunk));
114+	reader = packet_reader_init(packet->data, packet->size);
115+
116+	switch (packet->id) {
117+	case 0x21:
118+		return mc_chunk_decode_single(chunk, &reader);
119+	case 0x26:
120+		return mc_chunk_decode_bulk(chunk, &reader);
121+	}
122+
123+	return 0;
124+}
125+
126+void mc_chunk_destroy(MCChunkPacket* chunk)
127+{
128+	if (!chunk)
129+		return;
130+
131+	if (chunk->updates != &chunk->single_update)
132+		free(chunk->updates);
133+
134+	memset(chunk, 0, sizeof(*chunk));
135+}
136+
137+size_t mc_chunk_update_size(u16 mask, u8 skylight, i8 bulk)
138+{
139+	i32    sections;
140+	size_t size;
141+
142+	sections = mc_chunk_section_count(mask);
143+	size = sections * MC_CHUNK_SECTION_BLOCK_BYTES;
144+	if (bulk) {
145+		size += sections * MC_CHUNK_SECTION_LIGHT_BYTES;
146+		size += MC_CHUNK_BIOME_BYTES;
147+		if (skylight)
148+			size += sections * MC_CHUNK_SECTION_LIGHT_BYTES;
149+	}
150+
151+	return size;
152+}
+30, -30
 1@@ -8,7 +8,7 @@
 2 #include "renderer/renderer.h"
 3 #include "renderer_backend.h"
 4 
 5-#define MAX_MESHES   8192
 6+#define MAX_MESHES 8192
 7 #define MAX_TEXTURES 256
 8 
 9 typedef struct {
10@@ -47,35 +47,35 @@ static void create_sampler(void);
11 static void sokol_log(const char* tag, u32 level, u32 item, const char* msg, u32 ln, const char* file, void* user);
12 
13 static RendererState ren;
14-static const char* vsdr = "#version 330\n"
15-                          "layout(location=0) in vec3 position;\n"
16-                          "layout(location=1) in vec2 texcoord0;\n"
17-                          "layout(location=2) in vec4 color0;\n"
18-                          "\n"
19-                          "uniform mat4 mvp;\n"
20-                          "\n"
21-                          "out vec2 uv;\n"
22-                          "out vec4 colour;\n"
23-                          "\n"
24-                          "void main(void)\n"
25-                          "{\n"
26-                          "    gl_Position = mvp * vec4(position, 1.0);\n"
27-                          "    uv = texcoord0;\n"
28-                          "    colour = color0;\n"
29-                          "}\n";
30-static const char* fsdr = "#version 330\n"
31-                          "in vec2 uv;\n"
32-                          "in vec4 colour;\n"
33-                          "\n"
34-                          "uniform sampler2D tex;\n"
35-                          "\n"
36-                          "out vec4 frag_colour;\n"
37-                          "\n"
38-                          "void main(void)\n"
39-                          "{\n"
40-                          "    frag_colour = texture(tex, uv) * colour;\n"
41-                          "    if (frag_colour.a < 0.5) discard;\n"
42-                          "}\n";
43+static const char*   vsdr = "#version 330\n"
44+                            "layout(location=0) in vec3 position;\n"
45+                            "layout(location=1) in vec2 texcoord0;\n"
46+                            "layout(location=2) in vec4 color0;\n"
47+                            "\n"
48+                            "uniform mat4 mvp;\n"
49+                            "\n"
50+                            "out vec2 uv;\n"
51+                            "out vec4 colour;\n"
52+                            "\n"
53+                            "void main(void)\n"
54+                            "{\n"
55+                            "    gl_Position = mvp * vec4(position, 1.0);\n"
56+                            "    uv = texcoord0;\n"
57+                            "    colour = color0;\n"
58+                            "}\n";
59+static const char*   fsdr = "#version 330\n"
60+                            "in vec2 uv;\n"
61+                            "in vec4 colour;\n"
62+                            "\n"
63+                            "uniform sampler2D tex;\n"
64+                            "\n"
65+                            "out vec4 frag_colour;\n"
66+                            "\n"
67+                            "void main(void)\n"
68+                            "{\n"
69+                            "    frag_colour = texture(tex, uv) * colour;\n"
70+                            "    if (frag_colour.a < 0.5) discard;\n"
71+                            "}\n";
72 
73 static void create_pipelines(void)
74 {
+114, -49
  1@@ -14,6 +14,7 @@ static const i32 face_offsets[6][3] = {
  2 	{ 0, 0, -1 }, { 0, 0, 1 }, { -1, 0, 0 }, { 1, 0, 0 }, { 0, -1, 0 }, { 0, 1, 0 }
  3 };
  4 
  5+/* add a face to the mesh */
  6 static void add_face(MGVertex* vertices, u16* indices, u32* vertex_count, u32* index_count, const BlockFace* face,
  7                      i32 x, i32 y, i32 z)
  8 {
  9@@ -41,67 +42,131 @@ static void add_face(MGVertex* vertices, u16* indices, u32* vertex_count, u32* i
 10 	indices[(*index_count)++] = (u16)(base + 3);
 11 }
 12 
 13-void chunk_create(Chunk* chunk)
 14+void chunk_clear(Chunk* chunk)
 15+{
 16+	i32 section;
 17+
 18+	if (!chunk)
 19+		return;
 20+
 21+	for (section = 0; section < CHUNK_SECTION_COUNT; section++) {
 22+		chunk_section_mesh_destroy(&chunk->sections[section]);
 23+		memset(&chunk->sections[section], 0, sizeof(chunk->sections[section]));
 24+	}
 25+
 26+	chunk->section_mask = 0;
 27+}
 28+
 29+void chunk_remove(Chunk* chunk)
 30+{
 31+	if (!chunk)
 32+		return;
 33+
 34+	chunk_clear(chunk);
 35+	memset(chunk, 0, sizeof(*chunk));
 36+}
 37+
 38+void chunk_section_create(ChunkSection* section)
 39 {
 40 	i32 x;
 41 	i32 y;
 42 	i32 z;
 43 
 44-	if (!chunk)
 45+	if (!section)
 46 		return;
 47 
 48-	memset(chunk, 0, sizeof(*chunk));
 49-	for (z = 0; z < CHUNK_DEPTH; z++) {
 50-		for (x = 0; x < CHUNK_WIDTH; x++) {
 51-			for (y = 0; y < CHUNK_HEIGHT - 5; y++)
 52-				chunk_set(chunk, x, y, z, BLOCK_STONE);
 53+	memset(section, 0, sizeof(*section));
 54+	for (z = 0; z < CHUNK_SECTION_DEPTH; z++) {
 55+		for (x = 0; x < CHUNK_SECTION_WIDTH; x++) {
 56+			for (y = 0; y < CHUNK_SECTION_HEIGHT - 5; y++)
 57+				chunk_section_set(section, x, y, z, BLOCK_STONE);
 58 
 59-			chunk_set(chunk, x, CHUNK_HEIGHT - 5, z, BLOCK_DIRT);
 60+			chunk_section_set(section, x, CHUNK_SECTION_HEIGHT - 5, z, BLOCK_DIRT);
 61 		}
 62 	}
 63 
 64-	chunk_set(chunk, 6, 12, 8, BLOCK_BRICK);
 65-	chunk_set(chunk, 8, 12, 8, BLOCK_BRICK);
 66-	chunk_set(chunk, 7, 13, 8, BLOCK_BRICK);
 67-	chunk_set(chunk, 7, 14, 8, BLOCK_BRICK);
 68-	chunk_set(chunk, 7, 15, 8, BLOCK_STONE);
 69+	/* dih */
 70+	chunk_section_set(section, 6, 12, 8, BLOCK_BRICK);
 71+	chunk_section_set(section, 8, 12, 8, BLOCK_BRICK);
 72+	chunk_section_set(section, 7, 13, 8, BLOCK_BRICK);
 73+	chunk_section_set(section, 7, 14, 8, BLOCK_BRICK);
 74+	chunk_section_set(section, 7, 15, 8, BLOCK_STONE);
 75 }
 76 
 77-BlockType chunk_get(const Chunk* chunk, i32 x, i32 y, i32 z)
 78+i8 chunk_section_decode(ChunkSection* section, const u8* data, size_t size)
 79 {
 80-	if (!chunk)
 81+	i32 index;
 82+	i32 x;
 83+	i32 y;
 84+	i32 z;
 85+	u16 state;
 86+
 87+	if (!section || !data)
 88+		return 0;
 89+
 90+	if (size < 8192)
 91+		return 0;
 92+
 93+	chunk_section_mesh_destroy(section);
 94+	memset(section, 0, sizeof(*section));
 95+
 96+	for (index = 0; index < 4096; index++) {
 97+		/* combine first two bytes to get block state (l operation
 98+		   is little endian) */
 99+		state = (u16)data[index * 2] | ((u16)data[index * 2 + 1] << 8);
100+
101+		/* convert index->3D */
102+		x = index & 15;
103+		z = (index >> 4) & 15;
104+		y = (index >> 8) & 15;
105+
106+		chunk_section_set(section, x, y, z, state);
107+	}
108+
109+	/* create mesh for section */
110+	if (!chunk_section_mesh_create(section)) {
111+		mg_log(LOG_ERR, "Failed to create server section mesh");
112+		return 0;
113+	}
114+
115+	return 1;
116+}
117+
118+BlockType chunk_section_get(const ChunkSection* section, i32 x, i32 y, i32 z)
119+{
120+	if (!section)
121 		return BLOCK_AIR;
122 
123-	if (x < 0 || x >= CHUNK_WIDTH)
124+	if (x < 0 || x >= CHUNK_SECTION_WIDTH)
125 		return BLOCK_AIR;
126 
127-	if (y < 0 || y >= CHUNK_HEIGHT)
128+	if (y < 0 || y >= CHUNK_SECTION_HEIGHT)
129 		return BLOCK_AIR;
130 
131-	if (z < 0 || z >= CHUNK_DEPTH)
132+	if (z < 0 || z >= CHUNK_SECTION_DEPTH)
133 		return BLOCK_AIR;
134 
135-	return chunk->blocks[x][y][z];
136+	return section->blocks[x][y][z];
137 }
138 
139-void chunk_set(Chunk* chunk, i32 x, i32 y, i32 z, BlockType block)
140+void chunk_section_set(ChunkSection* section, i32 x, i32 y, i32 z, BlockType block)
141 {
142-	if (!chunk)
143+	if (!section)
144 		return;
145 
146-	if (x < 0 || x >= CHUNK_WIDTH)
147+	if (x < 0 || x >= CHUNK_SECTION_WIDTH)
148 		return;
149 
150-	if (y < 0 || y >= CHUNK_HEIGHT)
151+	if (y < 0 || y >= CHUNK_SECTION_HEIGHT)
152 		return;
153 
154-	if (z < 0 || z >= CHUNK_DEPTH)
155+	if (z < 0 || z >= CHUNK_SECTION_DEPTH)
156 		return;
157 
158-	chunk->blocks[x][y][z] = block;
159+	section->blocks[x][y][z] = block;
160 }
161 
162-i8 chunk_mesh_create(Chunk* chunk)
163+i8 chunk_section_mesh_create(ChunkSection* section)
164 {
165 	MGVertex*        vertices[BLOCK_TEXTURE_COUNT];
166 	u16*             indices[BLOCK_TEXTURE_COUNT];
167@@ -119,7 +184,7 @@ i8 chunk_mesh_create(Chunk* chunk)
168 	i32              y;
169 	i32              z;
170 
171-	if (!chunk)
172+	if (!section)
173 		return 0;
174 
175 	memset(vertices, 0, sizeof(vertices));
176@@ -129,13 +194,13 @@ i8 chunk_mesh_create(Chunk* chunk)
177 	memset(index_counts, 0, sizeof(index_counts));
178 
179 	for (texture = 0; texture < BLOCK_TEXTURE_COUNT; texture++)
180-		chunk->meshes[texture] = MGMESH_INVALID;
181+		section->meshes[texture] = MGMESH_INVALID;
182 
183 	/* count visible faces belonging to each block type  */
184-	for (z = 0; z < CHUNK_DEPTH; z++) {
185-		for (y = 0; y < CHUNK_HEIGHT; y++) {
186-			for (x = 0; x < CHUNK_WIDTH; x++) {
187-				block = chunk_get(chunk, x, y, z);
188+	for (z = 0; z < CHUNK_SECTION_DEPTH; z++) {
189+		for (y = 0; y < CHUNK_SECTION_HEIGHT; y++) {
190+			for (x = 0; x < CHUNK_SECTION_WIDTH; x++) {
191+				block = chunk_section_get(section, x, y, z);
192 
193 				if (block == BLOCK_AIR)
194 					continue;
195@@ -146,9 +211,9 @@ i8 chunk_mesh_create(Chunk* chunk)
196 						continue;
197 					if (faces[face].cullface >= 0) {
198 						texture = faces[face].cullface;
199-						neighbour = chunk_get(chunk, x + face_offsets[texture][0],
200-						                      y + face_offsets[texture][1],
201-						                      z + face_offsets[texture][2]);
202+						neighbour = chunk_section_get(section, x + face_offsets[texture][0],
203+						                              y + face_offsets[texture][1],
204+						                              z + face_offsets[texture][2]);
205 						if (neighbour != BLOCK_AIR && block_model_occludes(neighbour))
206 							continue;
207 					}
208@@ -177,10 +242,10 @@ i8 chunk_mesh_create(Chunk* chunk)
209 	}
210 
211 	/* omit culled model faces hidden by a full cube */
212-	for (z = 0; z < CHUNK_DEPTH; z++) {
213-		for (y = 0; y < CHUNK_HEIGHT; y++) {
214-			for (x = 0; x < CHUNK_WIDTH; x++) {
215-				block = chunk_get(chunk, x, y, z);
216+	for (z = 0; z < CHUNK_SECTION_DEPTH; z++) {
217+		for (y = 0; y < CHUNK_SECTION_HEIGHT; y++) {
218+			for (x = 0; x < CHUNK_SECTION_WIDTH; x++) {
219+				block = chunk_section_get(section, x, y, z);
220 
221 				if (block == BLOCK_AIR)
222 					continue;
223@@ -192,9 +257,9 @@ i8 chunk_mesh_create(Chunk* chunk)
224 						continue;
225 					if (faces[face].cullface >= 0) {
226 						texture = faces[face].cullface;
227-						neighbour = chunk_get(chunk, x + face_offsets[texture][0],
228-						                      y + face_offsets[texture][1],
229-						                      z + face_offsets[texture][2]);
230+						neighbour = chunk_section_get(section, x + face_offsets[texture][0],
231+						                              y + face_offsets[texture][1],
232+						                              z + face_offsets[texture][2]);
233 						if (neighbour != BLOCK_AIR && block_model_occludes(neighbour))
234 							continue;
235 					}
236@@ -217,9 +282,9 @@ i8 chunk_mesh_create(Chunk* chunk)
237 		desc.idc = indices[texture];
238 		desc.idcc = index_counts[texture];
239 
240-		chunk->meshes[texture] = renderer_mesh_create(&desc);
241+		section->meshes[texture] = renderer_mesh_create(&desc);
242 
243-		if (chunk->meshes[texture] == MGMESH_INVALID) {
244+		if (section->meshes[texture] == MGMESH_INVALID) {
245 			mg_log(LOG_ERR, "Failed to upload chunk mesh");
246 			goto fail;
247 		}
248@@ -238,19 +303,19 @@ fail:
249 		free(vertices[texture]);
250 	}
251 
252-	chunk_mesh_destroy(chunk);
253+	chunk_section_mesh_destroy(section);
254 	return 0;
255 }
256 
257-void chunk_mesh_destroy(Chunk* chunk)
258+void chunk_section_mesh_destroy(ChunkSection* section)
259 {
260 	i32 texture;
261 
262-	if (!chunk)
263+	if (!section)
264 		return;
265 
266 	for (texture = 0; texture < BLOCK_TEXTURE_COUNT; texture++) {
267-		renderer_mesh_destroy(chunk->meshes[texture]);
268-		chunk->meshes[texture] = MGMESH_INVALID;
269+		renderer_mesh_destroy(section->meshes[texture]);
270+		section->meshes[texture] = MGMESH_INVALID;
271 	}
272 }