commit 877d445

uint  ·  2026-08-04 21:10:37 +0000 UTC
parent d86ed5d
refactor main() to be less test like
1 files changed,  +225, -194
+225, -194
  1@@ -9,135 +9,187 @@
  2 #include "client/world.h"
  3 #include "net/mc_chunk.h"
  4 #include "net/mc_proto.h"
  5-#include "net/packet.h"
  6 #include "platform/platform.h"
  7 #include "renderer/renderer.h"
  8 #include "world/block.h"
  9 #include "world/chunk.h"
 10 
 11-#define TEST_WORLD_WIDTH 50
 12-#define TEST_WORLD_DEPTH 50
 13-
 14-static MGContext ctx = {
 15-	NULL, { NULL }, "Magnolia", 0, 0, 1024, 768, { MGK_UNKNOWN },
 16-};
 17-
 18-static MGCamera    cam;
 19-static ClientWorld world;
 20-static i8          server_mode;
 21-
 22-static void test_scene_create(Player* player, i8 server)
 23+#define LOCAL_WORLD_WIDTH 50
 24+#define LOCAL_WORLD_DEPTH 50
 25+#define PACKETS_PER_FRAME 64
 26+
 27+typedef struct {
 28+	MGContext   ctx;
 29+	MGCamera    cam;
 30+	Player      player;
 31+	ClientWorld world;
 32+	MCClient*   client;
 33+} Magnolia;
 34+
 35+static Magnolia game;
 36+
 37+static void create_local_world(void);
 38+static void game_dies(void);
 39+static void game_init(int argc, char** argv);
 40+static void game_run(void);
 41+static void handle_chunk_packet(const MCPacket* packet);
 42+static void handle_input(void);
 43+static i8   handle_server_packets(void);
 44+static void render_world(void);
 45+
 46+static void create_local_world(void)
 47 {
 48 	Chunk* chunk;
 49 	i32    chunk_index;
 50 	i32    x;
 51 	i32    z;
 52 
 53-	if (!block_textures_create("assets/resourcepacks/default"))
 54-		mg_die(MG_EC_TEXTURE_LOAD, "Failed to load block textures");
 55+	for (chunk_index = 0; chunk_index < CLIENT_WORLD_MAX_CHUNKS; chunk_index++) {
 56+		x = chunk_index % LOCAL_WORLD_WIDTH;
 57+		z = chunk_index / LOCAL_WORLD_WIDTH;
 58+		if (z >= LOCAL_WORLD_DEPTH)
 59+			break;
 60 
 61-	camera_init(&cam, MG_V3(8.0f, 38.0f, 24.0f));
 62-	cam.yaw = MG_AngleDeg(-180.0f);
 63-	cam.pitch = MG_AngleDeg(-25.0f);
 64-
 65-	server_mode = server;
 66-	world_init(&world);
 67-	player->x = 0.0;
 68-	player->y = 0.0;
 69-	player->z = 0.0;
 70-	player->yaw = 0.0f;
 71-	player->pitch = 0.0f;
 72-	player->positioned = 0;
 73-
 74-	if (server_mode) {
 75-		cam.far = 512.0f;
 76-	}
 77-	else { /* test world */
 78-		for (chunk_index = 0; chunk_index < CLIENT_WORLD_MAX_CHUNKS; chunk_index++) {
 79-			x = chunk_index % TEST_WORLD_WIDTH;
 80-			z = chunk_index / TEST_WORLD_WIDTH;
 81-			if (z >= TEST_WORLD_DEPTH)
 82-				break;
 83-
 84-			chunk = &world.chunks[chunk_index];
 85-			chunk->used = 1;
 86-			chunk->x = x;
 87-			chunk->z = z;
 88-			chunk_section_create(&chunk->sections[0]);
 89-			chunk->section_mask = 1;
 90-
 91-			if (!chunk_section_mesh_create(&chunk->sections[0]))
 92-				mg_die(MG_EC_MESH_CREATE, "Failed to create chunk mesh");
 93-		}
 94+		chunk = &game.world.chunks[chunk_index];
 95+		chunk->used = 1;
 96+		chunk->x = x;
 97+		chunk->z = z;
 98+		chunk_section_create(&chunk->sections[0]);
 99+		chunk->section_mask = 1;
100+
101+		if (!chunk_section_mesh_create(&chunk->sections[0]))
102+			mg_die(MG_EC_MESH_CREATE, "Failed to create chunk mesh");
103 	}
104 }
105 
106-static void test_scene_draw(MGContext* ctx)
107+static void game_dies(void)
108 {
109-	MGMat4 proj;
110-	MGMat4 view;
111-	MGMat4 mvp;
112-	MGMat4 model;
113-	i32    width;
114-	i32    height;
115-	float  aspect;
116-	i32    texture;
117+	if (game.client)
118+		mc_client_destroy(game.client);
119 
120-	Chunk*        column;
121-	ChunkSection* section_chunk;
122-	i32           section;
123-	i32           i;
124+	world_destroy(&game.world);
125+	block_textures_destroy();
126+	renderer_destroy();
127+	platform_shutdown(&game.ctx);
128+}
129 
130-	platform_get_drawable_size(ctx, &width, &height);
131+static void game_init(int argc, char** argv)
132+{
133+	MCAccount account;
134+	char      error[1024];
135 
136-	if (width <= 0 || height <= 0)
137-		return;
138+	if (argc != 1 && argc != 3)
139+		mg_die(MG_EC_INVALID_ARGS, "usage: %s [server[:port] username]\n", argv[0]);
140 
141-	aspect = (float)width / (float)height;
142+	if (argc == 3) {
143+		account.username = argv[2];
144+		account.profile_uuid = getenv("MANGOLIA_PROFILE_UUID");
145+		account.access_token = getenv("MANGOLIA_ACCESS_TOKEN");
146 
147-	proj = camera_get_proj(&cam, aspect);
148-	view = camera_get_view(&cam);
149+		if (!mc_client_login(&game.client, argv[1], &account, error, sizeof(error)))
150+			mg_die(MG_EC_MCCLIENT_LOGIN, "%s\n", error);
151+	}
152 
153-	for (i = 0; i < CLIENT_WORLD_MAX_CHUNKS; i++) {
154-		column = &world.chunks[i];
155+	config_default();
156 
157-		if (!column->used)
158-			continue;
159+	game.ctx.title = "Magnolia";
160+	game.ctx.width = mg_config.display_width;
161+	game.ctx.height = mg_config.display_height;
162 
163-		for (section = 0; section < CHUNK_SECTION_COUNT; section++) {
164-			if (!(column->section_mask & (1U << section)))
165-				continue;
166+	platform_init(&game.ctx);
167+	platform_window_create(&game.ctx);
168+	renderer_create(&game.ctx);
169+	platform_set_mouse_relative(1);
170 
171-			section_chunk = &column->sections[section];
172-			model = MG_Translate(
173-			    MG_V3(column->x * CHUNK_WIDTH, section * CHUNK_SECTION_HEIGHT, column->z * CHUNK_DEPTH));
174-			mvp = MG_MulM4(proj, MG_MulM4(view, model));
175-			for (texture = 0; texture < BLOCK_TEXTURE_COUNT; texture++) {
176-				if (section_chunk->meshes[texture] != MGMESH_INVALID)
177-					renderer_mesh_draw(section_chunk->meshes[texture], block_texture_get(texture),
178-					                   &mvp);
179-			}
180-		}
181+	if (!block_textures_create("assets/resourcepacks/default"))
182+		mg_die(MG_EC_TEXTURE_LOAD, "Failed to load block textures");
183+
184+	camera_init(&game.cam, MG_V3(8.0f, 38.0f, 24.0f));
185+	game.cam.yaw = MG_AngleDeg(-180.0f);
186+	game.cam.pitch = MG_AngleDeg(-25.0f);
187+
188+	world_init(&game.world);
189+
190+	game.player.x = 0.0;
191+	game.player.y = 0.0;
192+	game.player.z = 0.0;
193+	game.player.yaw = 0.0f;
194+	game.player.pitch = 0.0f;
195+	game.player.positioned = 0;
196+
197+	if (!game.client)
198+		create_local_world();
199+}
200+
201+static void game_run(void)
202+{
203+	while (platform_poll(&game.ctx)) {
204+		if (!handle_server_packets())
205+			break;
206+
207+		handle_input();
208+
209+		if (game.client)
210+			player_server_send_position(&game.player, &game.cam, game.client);
211+
212+		renderer_begin();
213+		render_world();
214+		renderer_end();
215+
216+		platform_present(&game.ctx);
217+		platform_wait(1);
218 	}
219 }
220 
221-static void test_scene_destroy(void)
222+static void handle_chunk_packet(const MCPacket* packet)
223 {
224-	world_destroy(&world);
225-	block_textures_destroy();
226+	MCChunkPacket chunk;
227+	size_t        offset;
228+	size_t        size;
229+	i32           update;
230+
231+	if (!mc_chunk_decode(&chunk, packet))
232+		return;
233+
234+	if (chunk.unload) {
235+		world_remove_chunk(&game.world, chunk.unload_x, chunk.unload_z);
236+		mc_chunk_destroy(&chunk);
237+		return;
238+	}
239+
240+	offset = 0;
241+
242+	for (update = 0; update < chunk.update_count; update++) {
243+		size = mc_chunk_update_size(chunk.updates[update].mask, chunk.skylight, chunk.bulk);
244+		if (offset > chunk.data_size || size > chunk.data_size - offset) {
245+			mg_log(LOG_ERR, "Chunk data is incomplete");
246+			break;
247+		}
248+
249+		if (!world_update_chunk(&game.world, &chunk.updates[update], chunk.data + offset))
250+			break;
251+
252+		offset += size;
253+	}
254+
255+	mc_chunk_destroy(&chunk);
256 }
257 
258-static void input(void)
259+static void handle_input(void)
260 {
261 	static u32 last;
262 
263-	MGVec3 move = MG_V3(0.0f, 0.0f, 0.0f);
264-	u64    now = platform_get_ticks();
265-	float  sensitivity = mg_config.input_sensitivity;
266+	MGVec3 move;
267+	u64    now;
268+	float  sensitivity;
269 	float  dt;
270 	i32    mx;
271 	i32    my;
272 
273+	move = MG_V3(0.0f, 0.0f, 0.0f);
274+	now = platform_get_ticks();
275+	sensitivity = mg_config.input_sensitivity;
276+
277 	if (!last) {
278 		last = now;
279 		return;
280@@ -146,148 +198,127 @@ static void input(void)
281 	dt = (now - last) / 1000.0f;
282 	last = now;
283 
284-	if (platform_key_down(&ctx, MGK_W))
285+	/* TODO normalise */
286+	if (platform_key_down(&game.ctx, MGK_W))
287 		move.z += 1.0f;
288-	if (platform_key_down(&ctx, MGK_A))
289+	if (platform_key_down(&game.ctx, MGK_A))
290 		move.x -= 1.0f;
291-	if (platform_key_down(&ctx, MGK_S))
292+	if (platform_key_down(&game.ctx, MGK_S))
293 		move.z -= 1.0f;
294-	if (platform_key_down(&ctx, MGK_D))
295+	if (platform_key_down(&game.ctx, MGK_D))
296 		move.x += 1.0f;
297-	if (platform_key_down(&ctx, MGK_SPACE))
298+	if (platform_key_down(&game.ctx, MGK_SPACE))
299 		move.y += 1.0f;
300-	if (platform_key_down(&ctx, MGK_LCTRL))
301+	if (platform_key_down(&game.ctx, MGK_LCTRL))
302 		move.y -= 1.0f;
303 
304-	/* debug keys */
305-	if (platform_key_down(&ctx, MGK_L)) {
306+	if (platform_key_down(&game.ctx, MGK_L)) {
307 		mg_config.debug_wireframe = !mg_config.debug_wireframe;
308 		renderer_debug_wireframe(mg_config.debug_wireframe);
309 	}
310 
311 	move = MG_MulV3F(move, mg_config.temp_movement_speed * dt);
312-	camera_move(&cam, move);
313+	camera_move(&game.cam, move);
314 	platform_get_mouse_state(&mx, &my);
315-	camera_rotate(&cam, mx * sensitivity, -my * sensitivity);
316+	camera_rotate(&game.cam, mx * sensitivity, -my * sensitivity);
317 }
318 
319-static void chunk_packet(ClientWorld* world, const MCPacket* packet)
320+static i8 handle_server_packets(void)
321 {
322-	MCChunkPacket chunk;
323-	size_t        offset;
324-	size_t        size;
325-	i32           update;
326+	MCPacket packet;
327+	char     error[1024];
328+	int      poll_result;
329+	int      packets;
330 
331-	if (!mc_chunk_decode(&chunk, packet))
332-		return;
333+	if (!game.client)
334+		return 1;
335 
336-	if (chunk.unload) {
337-		world_remove_chunk(world, chunk.unload_x, chunk.unload_z);
338-		mc_chunk_destroy(&chunk);
339-		return;
340-	}
341+	for (packets = 0; packets < PACKETS_PER_FRAME; packets++) {
342+		poll_result = mc_client_poll(game.client, &packet, error, sizeof(error));
343+		if (poll_result <= 0)
344+			break;
345 
346-	offset = 0;
347+		printf("packet 0x%02x size %lu\n", packet.id, packet.size);
348+		switch (packet.id) {
349+		case 0x00:
350+			if (!mc_client_write(game.client, 0x00, packet.data, packet.size))
351+				mg_log(LOG_ERR, "Failed to echo keepalive");
352+			break;
353 
354-	for (update = 0; update < chunk.update_count; update++) {
355-		size = mc_chunk_update_size(chunk.updates[update].mask, chunk.skylight, chunk.bulk);
356-		if (offset > chunk.data_size || size > chunk.data_size - offset) {
357-			mg_log(LOG_ERR, "Chunk data is incomplete");
358+		case 0x08:
359+			player_server_handle_position(&game.player, &game.cam, &packet);
360 			break;
361-		}
362 
363-		if (!world_update_chunk(world, &chunk.updates[update], chunk.data + offset))
364+		case 0x21:
365+		case 0x26:
366+			/* both single and bulk changes
367+			   handled in handle_chunk_packet */
368+			handle_chunk_packet(&packet);
369 			break;
370+		}
371 
372-		offset += size;
373+		mc_packet_free(&packet);
374 	}
375 
376-	mc_chunk_destroy(&chunk);
377+	if (poll_result < 0) {
378+		mg_log(LOG_ERR, "%s", error);
379+		return 0;
380+	}
381+
382+	return 1;
383 }
384 
385-int main(int argc, char** argv)
386+static void render_world(void)
387 {
388-	MCClient* client = NULL;
389-	MCAccount account;
390-	MCPacket  packet;
391-	char      error[1024];
392-	int       poll_result;
393-	int       packets;
394-
395-	Player player;
396+	MGMat4 proj;
397+	MGMat4 view;
398+	MGMat4 mvp;
399+	MGMat4 model;
400+	i32    width;
401+	i32    height;
402+	float  aspect;
403+	i32    texture;
404 
405-	if (argc != 1 && argc != 3)
406-		mg_die(MG_EC_INVALID_ARGS, "usage: %s [server[:port] username]\n", argv[0]);
407-	if (argc == 3) {
408-		account.username = argv[2];
409-		account.profile_uuid = getenv("MANGOLIA_PROFILE_UUID");
410-		account.access_token = getenv("MANGOLIA_ACCESS_TOKEN");
411+	Chunk*        chunk;
412+	ChunkSection* section;
413+	i32           section_index;
414+	i32           chunk_index;
415 
416-		if (!mc_client_login(&client, argv[1], &account, error, sizeof(error)))
417-			mg_die(MG_EC_MCCLIENT_LOGIN, "%s\n", error);
418-	}
419+	platform_get_drawable_size(&game.ctx, &width, &height);
420+	if (width <= 0 || height <= 0)
421+		return;
422 
423-	config_default();
424+	aspect = (float)width / (float)height;
425+	proj = camera_get_proj(&game.cam, aspect);
426+	view = camera_get_view(&game.cam);
427 
428-	ctx.width = mg_config.display_width;
429-	ctx.height = mg_config.display_height;
430+	for (chunk_index = 0; chunk_index < CLIENT_WORLD_MAX_CHUNKS; chunk_index++) {
431+		chunk = &game.world.chunks[chunk_index];
432+		if (!chunk->used)
433+			continue;
434 
435-	platform_init(&ctx);
436-	platform_window_create(&ctx);
437-	renderer_create(&ctx);
438-	test_scene_create(&player, client != NULL);
439-	platform_set_mouse_relative(1);
440+		for (section_index = 0; section_index < CHUNK_SECTION_COUNT; section_index++) {
441+			if (!(chunk->section_mask & (1U << section_index)))
442+				continue;
443 
444-	while (platform_poll(&ctx)) {
445-		if (client) {
446-			for (packets = 0; packets < 64; packets++) {
447-				poll_result = mc_client_poll(client, &packet, error, sizeof(error));
448-				if (poll_result <= 0)
449-					break;
450-
451-				printf("packet 0x%02x size %lu\n", packet.id, packet.size);
452-				switch (packet.id) {
453-				case 0x00:
454-					if (!mc_client_write(client, 0x00, packet.data, packet.size))
455-						mg_log(LOG_ERR, "Failed to echo keepalive");
456-					break;
457-				case 0x08:
458-					player_server_handle_position(&player, &cam, &packet);
459-					break;
460-				case 0x21:
461-				case 0x26:
462-					chunk_packet(&world, &packet);
463-					break;
464-				}
465-
466-				mc_packet_free(&packet);
467-			}
468+			section = &chunk->sections[section_index];
469+			model = MG_Translate(MG_V3(chunk->x * CHUNK_WIDTH, section_index * CHUNK_SECTION_HEIGHT,
470+			                           chunk->z * CHUNK_DEPTH));
471+			mvp = MG_MulM4(proj, MG_MulM4(view, model));
472 
473-			if (poll_result < 0) {
474-				mg_log(LOG_ERR, "%s", error);
475-				break;
476+			for (texture = 0; texture < BLOCK_TEXTURE_COUNT; texture++) {
477+				if (section->meshes[texture] != MGMESH_INVALID)
478+					renderer_mesh_draw(section->meshes[texture], block_texture_get(texture), &mvp);
479 			}
480 		}
481-
482-		input();
483-
484-		if (client)
485-			player_server_send_position(&player, &cam, client);
486-
487-		renderer_begin();
488-
489-		/* ... */
490-		test_scene_draw(&ctx);
491-
492-		renderer_end();
493-		platform_present(&ctx);
494-		platform_wait(1);
495 	}
496+}
497+
498+int main(int argc, char** argv)
499+{
500+	game_init(argc, argv);
501+	game_run();
502+	game_dies();
503 
504-	if (client)
505-		mc_client_destroy(client);
506-	test_scene_destroy();
507-	renderer_destroy();
508-	platform_shutdown(&ctx);
509 	return MG_EC_EXIT_SUCCESS;
510 }