game.c (26327B)
1 #include "game.h" 2 3 #if !defined(PLATFORM_WASM) 4 #include "am_gl.c" 5 #endif 6 7 #include "shader.c" 8 #include "memory.c" 9 #include "collision.c" 10 #include "render.c" 11 #include "world.c" 12 #include "input.c" 13 #include "image.c" 14 15 #include "w_tutorial.h" 16 17 internal bool timer_is_expired(struct Timer t) 18 { 19 return t.elapsed_ms >= t.limit_ms; 20 } 21 22 internal void timer_init(struct Timer *t, i64 limit_ms) 23 { 24 *t = (struct Timer) { 25 .elapsed_ms = 0, 26 .limit_ms = limit_ms, 27 }; 28 } 29 30 internal void timer_update(struct Timer *t, i64 elapsed_ms) 31 { 32 t->elapsed_ms += elapsed_ms; 33 } 34 35 internal void move_mode_print(enum MoveMode s) 36 { 37 switch(s) 38 { 39 case MOVE_MODE_FALLING: 40 printf("FALLING\n"); 41 break; 42 case MOVE_MODE_GROUNDED: 43 printf("GROUNDED\n"); 44 break; 45 case MOVE_MODE_JUMPING: 46 printf("JUMPING\n"); 47 break; 48 case MOVE_MODE_CLIMBING: 49 printf("CLIMBING\n"); 50 break; 51 case MOVE_MODE_FLOATING: 52 printf("FLOATING\n"); 53 break; 54 default: 55 printf("INVALID\n"); 56 break; 57 } 58 } 59 60 internal bool entity_is_adjacent_to_solid_tiles(struct World *world, struct AbsolutePos entity_p, rect entity_rect, enum Direction dir) 61 { 62 v2 tile_p_to_search_from_min = {0}; 63 v2 tile_p_to_search_from_max = {0}; 64 switch(dir) 65 { 66 case DIR_RIGHT: 67 tile_p_to_search_from_min = (v2) {math_ceil(entity_rect.max.x), math_floor(entity_rect.min.y)}; 68 tile_p_to_search_from_max = (v2) {math_ceil(entity_rect.max.x), math_floor(entity_rect.max.y)}; 69 break; 70 case DIR_LEFT: 71 tile_p_to_search_from_min = (v2) {math_floor(entity_rect.min.x - 1.0f), math_floor(entity_rect.min.y)}; 72 tile_p_to_search_from_max = (v2) {math_floor(entity_rect.min.x - 1.0f), math_floor(entity_rect.max.y)}; 73 break; 74 case DIR_UP: 75 tile_p_to_search_from_min = (v2) {math_floor(entity_rect.min.x), math_ceil(entity_rect.max.y)}; 76 tile_p_to_search_from_max = (v2) {math_floor(entity_rect.max.x), math_ceil(entity_rect.max.y)}; 77 break; 78 case DIR_DOWN: 79 tile_p_to_search_from_min = (v2) {math_floor(entity_rect.min.x), math_floor(entity_rect.max.y - 1.0f)}; 80 tile_p_to_search_from_max = (v2) {math_floor(entity_rect.max.x), math_floor(entity_rect.min.y - 1.0f)}; 81 break; 82 default: 83 assert(false); 84 break; 85 } 86 87 bool result = false; 88 if (world_tile_is_solid(world, (struct AbsolutePos) {.room = entity_p.room, .local = tile_p_to_search_from_min}) 89 || world_tile_is_solid(world, (struct AbsolutePos) {.room = entity_p.room, .local = tile_p_to_search_from_max})) 90 { 91 result = true; 92 } 93 return result; 94 } 95 96 internal void game_init(struct GameMemory *game_memory, v2u framebuffer) 97 { 98 assert(sizeof(struct GameState) <= game_memory->buffer_size); 99 struct GameState *game_state = (struct GameState *)game_memory->buffer; 100 101 // init player 102 103 // In Knytt, the player is 9 by 14 texels and a tile is 24 by 24 texels. 104 // These dimensions are relative to a square 'meter', one tile 105 game_state->player.dimensions = (v2) {0.375f, 0.583f}; 106 game_state->player.move_mode = MOVE_MODE_FALLING; 107 game_state->player.facing = DIR_RIGHT; 108 109 // TODO: make player spawn location part of the world data 110 game_state->player.pos = (struct AbsolutePos) { 111 .room = {0, 0}, 112 // TODO: Handle player wall spawning properly (extrude them) 113 .local = {6.0f, 6.0f + (game_state->player.dimensions.y * 0.5f)}, 114 }; 115 116 size_t temp_memory_size = MEBIBYTES(2); 117 size_t world_memory_size = game_memory->buffer_size - (sizeof(struct GameState) + temp_memory_size); 118 mem_st_init( 119 &game_state->temp_allocator, 120 game_memory->buffer + sizeof(struct GameState), 121 temp_memory_size); 122 mem_st_init( 123 &game_state->world_allocator, 124 game_state->temp_allocator.base + temp_memory_size, 125 world_memory_size); 126 127 game_state->world = mem_st_alloc_struct(&game_state->world_allocator, struct World); 128 game_state->world->num_rooms = 0; 129 130 for (u32 i = 0; i < W_TUTORIAL_NUM_ROOMS; i++) 131 { 132 world_room_set(game_state->world, w_tutorial_rooms[i], &game_state->world_allocator); 133 } 134 135 size_t asset_loading_free_marker = mem_st_get_marker(&game_state->temp_allocator); 136 struct PlatformApi platform = game_memory->platform; 137 // game_shader_load 138 { 139 size_t temp_free_marker = mem_st_get_marker(&game_state->temp_allocator); 140 char *v_source = (char *)platform.platform_read_entire_file("shader/main_v.glsl", NULL, &game_state->temp_allocator); 141 char *f_source = (char *)platform.platform_read_entire_file("shader/main_f.glsl", NULL, &game_state->temp_allocator); 142 struct Shader main_shader = {0}; 143 if (!shader_compile(v_source, f_source, &main_shader)) 144 { 145 exit(1); 146 // TODO: handle error 147 } 148 game_state->renderer.shader = main_shader; 149 mem_st_free_to_marker(&game_state->temp_allocator, temp_free_marker); 150 } 151 152 struct Image img = {0}; 153 // game_load_images 154 { 155 size_t temp_free_marker = mem_st_get_marker(&game_state->temp_allocator); 156 size_t file_len = 0; 157 u8 *t = platform.platform_read_entire_file("assets/tileset0.tga", &file_len, &game_state->temp_allocator); 158 assert(t); 159 160 i32 img_w = 0; 161 i32 img_h = 0; 162 u8 *data = img_load_from_memory(t, file_len, &img_w, &img_h, &game_state->temp_allocator); 163 mem_st_free_to_marker(&game_state->temp_allocator, temp_free_marker); 164 assert(data); 165 166 img.data = data; 167 img.dim = (v2u) {img_w, img_h}; 168 } 169 170 renderer_init(&game_state->renderer, &img, 1, &game_state->world_allocator); 171 mem_st_free_to_marker(&game_state->temp_allocator, asset_loading_free_marker); 172 } 173 174 // NOTE(amin): For now updating and rendering are interleaved. We simulate the 175 // world at the same rate we render it. Our timestep is not fixed. We may want 176 // to change this in the future. 177 void game_update_and_render(struct GameMemory *game_memory, struct GameInput *game_input, v2u framebuffer) 178 { 179 assert(sizeof(struct GameState) <= game_memory->buffer_size); 180 struct GameState *game_state = (struct GameState *)game_memory->buffer; 181 f32 dt = game_input->dt; 182 183 // TODO: pass total elapsed ms in as an i64. Use that for the timer. 184 timer_update(&game_state->jump_allowance_timer, (i64)(dt * 1000.0f)); 185 186 v2i current_room_i = game_state->player.pos.room; 187 struct Room *current_room = world_room_get(game_state->world, current_room_i); 188 189 assert(current_room); 190 assert(math_v2i_eq(current_room_i, current_room->index)); 191 192 // game_update_input 193 { 194 u32 button_changes = game_input->prev_button_states ^ game_input->button_states; 195 game_input->button_ups = button_changes & (~game_input->button_states); 196 game_input->button_downs = button_changes & game_input->button_states; 197 game_input->prev_button_states = game_input->button_states; 198 } 199 200 // game_update_player 201 { 202 struct Entity *player = &game_state->player; 203 u32 button_states = game_input->button_states; 204 if (dt >= 0.5f) 205 { 206 // NOTE(amin): If our dt is 0.5 seconds, we've probably been 207 // debugging and sitting at a breakpoint, and should just update as 208 // if we've been going at a steady 60fps. 209 printf("WARNING: Clamping frame dt to 1/60\n"); 210 dt = 1.0f / 60.0f; 211 } 212 213 f32 max_run_speed = 6.0f; 214 f32 max_climb_speed = 7.0f; 215 f32 max_fall_speed = 9.0f; 216 217 f32 acceleration_rate = 50.0f; 218 f32 gravity = 30.0f; 219 f32 friction = 0.7f; 220 enum Direction previous_facing_dir = player->facing; 221 222 if (btn_is_down(button_states, BTN_LEFT)) 223 { 224 player->facing = DIR_LEFT; 225 player->acceleration.x = -acceleration_rate; 226 } 227 else if (btn_is_down(button_states, BTN_RIGHT)) 228 { 229 player->facing = DIR_RIGHT; 230 player->acceleration.x = acceleration_rate; 231 } 232 else 233 { 234 player->acceleration.x = 0.0f; 235 player->velocity.x = player->velocity.x * friction; 236 } 237 238 if (btn_was_just_pressed(game_input, BTN_DEBUG_FLOAT)) 239 { 240 if (player->move_mode == MOVE_MODE_FLOATING) 241 { 242 player->move_mode = MOVE_MODE_FALLING; 243 } 244 else 245 { 246 player->move_mode = MOVE_MODE_FLOATING; 247 } 248 } 249 250 rect player_rect = math_rect_from_center_dim(player->pos.local, player->dimensions); 251 252 // determine this frame's movement mode 253 switch(player->move_mode) 254 { 255 case MOVE_MODE_FALLING: 256 if (!timer_is_expired(game_state->jump_allowance_timer)) 257 { 258 if (btn_was_just_pressed(game_input, BTN_JUMP)) 259 { 260 player->move_mode = MOVE_MODE_JUMPING; 261 } 262 } 263 break; 264 case MOVE_MODE_GROUNDED: 265 if(entity_is_adjacent_to_solid_tiles(game_state->world, player->pos, player_rect, DIR_DOWN)) 266 { 267 if (btn_was_just_pressed(game_input, BTN_JUMP)) 268 { 269 player->move_mode = MOVE_MODE_JUMPING; 270 } 271 } 272 else 273 { 274 timer_init(&game_state->jump_allowance_timer, JUMP_ALLOWANCE_MS); 275 player->move_mode = MOVE_MODE_FALLING; 276 } 277 break; 278 case MOVE_MODE_CLIMBING: 279 if (player->facing == previous_facing_dir) 280 { 281 if (entity_is_adjacent_to_solid_tiles(game_state->world, player->pos, player_rect, player->facing)) 282 { 283 if (btn_was_just_pressed(game_input, BTN_JUMP)) 284 { 285 // nudge the player away from the wall 286 if (player->facing == DIR_RIGHT) 287 { 288 player->velocity.x = -3.0f; 289 } 290 else if (player->facing == DIR_LEFT) 291 { 292 player->velocity.x = 3.0f; 293 } 294 player->move_mode = MOVE_MODE_JUMPING; 295 } 296 } 297 else 298 { 299 if (player->velocity.y > 0.0f) 300 { 301 // nudge the player over the ledge 302 if (player->facing == DIR_RIGHT) 303 { 304 player->velocity.x = 3.0f; 305 } 306 else if (player->facing == DIR_LEFT) 307 { 308 player->velocity.x = -3.0f; 309 } 310 player->acceleration.y = 0.0f; 311 player->velocity.y = 0.0f; 312 } 313 player->move_mode = MOVE_MODE_FALLING; 314 } 315 } 316 else 317 { 318 if (btn_was_just_pressed(game_input, BTN_JUMP)) 319 { 320 // nudge the player away from the wall 321 if (player->facing == DIR_RIGHT) 322 { 323 player->velocity.x = 3.0f; 324 } 325 else if (player->facing == DIR_LEFT) 326 { 327 player->velocity.x = -3.0f; 328 } 329 player->move_mode = MOVE_MODE_JUMPING; 330 } 331 else 332 { 333 player->acceleration.y = 0.0f; 334 player->velocity.y = 0.0f; 335 timer_init(&game_state->jump_allowance_timer, JUMP_ALLOWANCE_MS); 336 player->move_mode = MOVE_MODE_FALLING; 337 } 338 } 339 break; 340 case MOVE_MODE_JUMPING: 341 if (btn_was_just_released(game_input, BTN_JUMP)) 342 { 343 player->move_mode = MOVE_MODE_FALLING; 344 } 345 break; 346 case MOVE_MODE_FLOATING: 347 break; 348 default: 349 assert(false); 350 break; 351 } 352 353 //move_mode_print(player->move_mode); 354 355 // simulate movement mode 356 switch(player->move_mode) 357 { 358 case MOVE_MODE_FALLING: 359 player->acceleration.y = -gravity; 360 break; 361 case MOVE_MODE_GROUNDED: 362 player->acceleration.y = 0.0f; 363 player->velocity.y = 0.0f; 364 break; 365 case MOVE_MODE_CLIMBING: 366 if (btn_is_down(button_states, BTN_DOWN)) 367 { 368 player->acceleration.y = -acceleration_rate; 369 } 370 else 371 { 372 if (player->velocity.y < 0.0f) 373 { 374 player->acceleration.y = 0.0f; 375 player->velocity.y = 0.0f; 376 } 377 378 if (btn_is_down(button_states, BTN_UP)) 379 { 380 player->acceleration.y = acceleration_rate; 381 } 382 else 383 { 384 player->acceleration.y = 0.0f; 385 player->velocity.y = -1.0f; 386 } 387 } 388 break; 389 case MOVE_MODE_JUMPING: 390 { 391 f32 jump_height = 1.0f; 392 // v^2 = v0^2 - 2g(y - y0) 393 // 0 = v0^2 - 2g(h - 0) 394 // 0 = v0^2 - 2gh 395 // v0^2 = 2gh 396 // v0 = sqrt(2gh) 397 player->velocity.y = sqrtf(2.0f * gravity * jump_height); 398 player->move_mode = MOVE_MODE_FALLING; 399 break; 400 } 401 case MOVE_MODE_FLOATING: 402 if (btn_is_down(button_states, BTN_UP)) 403 { 404 player->acceleration.y = acceleration_rate; 405 } 406 else if (btn_is_down(button_states, BTN_DOWN)) 407 { 408 player->acceleration.y = -acceleration_rate; 409 } 410 else 411 { 412 player->acceleration.y = 0.0f; 413 player->velocity.y = player->velocity.y * friction; 414 } 415 break; 416 default: 417 assert(false); 418 break; 419 } 420 421 // Semi implicit Euler integration: https://gafferongames.com/post/integration_basics/ 422 player->velocity = math_v2_a(player->velocity, math_v2f_m(player->acceleration, dt)); 423 // TODO: clamp the length of the velocity vector, not each of its components 424 math_clamp(&player->velocity.x, -max_run_speed, max_run_speed); 425 switch(player->move_mode) 426 { 427 case MOVE_MODE_CLIMBING: 428 math_clamp(&player->velocity.y, -max_fall_speed, max_climb_speed); 429 break; 430 case MOVE_MODE_FALLING: 431 if (player->velocity.y < -max_fall_speed) 432 { 433 player->velocity.y = -max_fall_speed; 434 } 435 break; 436 case MOVE_MODE_FLOATING: 437 math_clamp(&player->velocity.y, -max_run_speed, max_run_speed); 438 break; 439 default: 440 break; 441 } 442 443 //printf("v: "); 444 //math_print(player->velocity); 445 //printf("a: "); 446 //math_print(player->acceleration); 447 448 // game_detect_collisions 449 { 450 #if 0 451 #define RENDER_COLLISION_DEBUG_QUAD(r, c) renderer_debug_quad_draw(&game_state->renderer, (r), (c)); 452 #else 453 #define RENDER_COLLISION_DEBUG_QUAD(r, c) 454 #endif 455 v2 player_delta = math_v2f_m(player->velocity, dt); 456 v2 new_p = math_v2_a(player->pos.local, player_delta); 457 458 rect player_traversal_bb = { 459 .min = {math_min(player->pos.local.x, new_p.x), math_min(player->pos.local.y, new_p.y)}, 460 .max = {math_max(player->pos.local.x, new_p.x), math_max(player->pos.local.y, new_p.y)}, 461 }; 462 463 rect player_traversal_occupancy_bb = math_minkowski_sum_rect(player_traversal_bb, player->dimensions); 464 465 rect tile_search_range = { 466 .min = { 467 math_floor(player_traversal_occupancy_bb.min.x - 1), 468 math_floor(player_traversal_occupancy_bb.min.y - 1), 469 }, 470 .max = { 471 math_floor(player_traversal_occupancy_bb.max.x + 2), 472 math_floor(player_traversal_occupancy_bb.max.y + 2), 473 }, 474 }; 475 476 RENDER_COLLISION_DEBUG_QUAD(tile_search_range, ((v3) {0.8f, 0.8f, 0.8f})); 477 478 f32 remaining_time_factor = 1.0f; 479 for (u32 i = 0; i < 4 && remaining_time_factor > 0.0f; i++) 480 { 481 v2 wall_normal = {0}; 482 f32 smallest_distance_scale_factor = 1.0f; 483 bool collision_occurred = false; 484 bool edges_collided[MAX_RECT_EDGE] = {0}; 485 for (i32 tile_y = tile_search_range.min.y; tile_y < tile_search_range.max.y; tile_y++) 486 { 487 for (i32 tile_x = tile_search_range.min.x; tile_x < tile_search_range.max.x; tile_x++) 488 { 489 struct AbsolutePos tile_pos = world_tile_pos_recanonicalize( 490 (struct AbsolutePos) { 491 .room = current_room_i, 492 .local = (v2) {tile_x, tile_y}, 493 }); 494 495 if (world_tile_is_solid(game_state->world, tile_pos)) 496 { 497 rect tile_aabb = { 498 .min = {tile_x, tile_y}, 499 .max = {tile_x + TILE_SIZE, tile_y + TILE_SIZE}, 500 }; 501 RENDER_COLLISION_DEBUG_QUAD(tile_aabb, ((v3) {0.8f, 0.4f, 0.4f})); 502 503 rect tile_player_sum = math_minkowski_sum_rect(tile_aabb, player->dimensions); 504 505 for (enum MathRectEdge edge = 0; edge < MAX_RECT_EDGE; edge++) 506 { 507 segment player_sum_wall = math_rect_get_edge(tile_player_sum, edge); 508 v2 normal = math_rect_get_normal(edge); 509 segment tile_wall = math_rect_get_edge(tile_aabb, edge); 510 511 if (!wall_is_internal(game_state->world, current_room_i, tile_wall)) 512 { 513 struct WallCollision c = get_wall_collision(player->pos.local, new_p, player_sum_wall, normal); 514 if (c.collision_occurred) 515 { 516 collision_occurred = true; 517 edges_collided[edge] = true; 518 if (smallest_distance_scale_factor > c.distance_scale_factor) 519 { 520 smallest_distance_scale_factor = c.distance_scale_factor; 521 } 522 wall_normal = normal; 523 RENDER_COLLISION_DEBUG_QUAD( 524 ((rect) { 525 .min = tile_wall.min, 526 .max = math_v2_a(tile_wall.max, math_v2f_m(normal, -0.2f)), 527 }), 528 ((v3) {0.0f, 0.8f, 0.0f})); 529 } 530 } 531 } 532 } 533 } 534 } 535 536 if (collision_occurred && player->move_mode != MOVE_MODE_FLOATING) 537 { 538 if (edges_collided[RECT_EDGE_TOP] && player->move_mode == MOVE_MODE_FALLING) 539 { 540 player->move_mode = MOVE_MODE_GROUNDED; 541 } 542 else if (edges_collided[RECT_EDGE_LEFT] || edges_collided[RECT_EDGE_RIGHT]) 543 { 544 player->move_mode = MOVE_MODE_CLIMBING; 545 } 546 } 547 548 v2 delta_to_nearest_collision = math_v2f_m(player_delta, smallest_distance_scale_factor); 549 v2 nearest_collision_p = math_v2_a(player->pos.local, delta_to_nearest_collision); 550 551 f32 collision_epsilon = 0.0001f; 552 rect local_inhabitable_region = { 553 .min = { 554 math_min(player->pos.local.x, nearest_collision_p.x + collision_epsilon), 555 math_min(player->pos.local.y, nearest_collision_p.y + collision_epsilon), 556 }, 557 .max = { 558 math_max(player->pos.local.x, nearest_collision_p.x - collision_epsilon), 559 math_max(player->pos.local.y, nearest_collision_p.y - collision_epsilon), 560 }, 561 }; 562 563 player->pos.local = nearest_collision_p; 564 math_clamp(&player->pos.local.x, local_inhabitable_region.min.x, local_inhabitable_region.max.x); 565 math_clamp(&player->pos.local.y, local_inhabitable_region.min.y, local_inhabitable_region.max.y); 566 567 // NOTE(amin): 568 // n * dot(v, n) 569 // = n * (|v| * |n| * cos(theta)) 570 // = n * (|v| * 1 * cos(theta)) 571 // = n * (|v| * cos(theta)) 572 // = n * |v_projected_onto_n| 573 // = v_projected_onto_n 574 v2 colliding_velocity_component = math_v2f_m(wall_normal, math_v2_dot(player->velocity, wall_normal)); 575 576 player->velocity = math_v2_s(player->velocity, colliding_velocity_component); 577 578 player_delta = math_v2f_m(player->velocity, dt); 579 new_p = math_v2_a(player->pos.local, player_delta); 580 remaining_time_factor -= smallest_distance_scale_factor; 581 } 582 } 583 #undef RENDER_COLLISION_DEBUG_QUAD 584 585 player->pos = world_pos_recanonicalize(player->pos); 586 } 587 588 // render_player 589 { 590 struct Entity player = game_state->player; 591 592 m4 model = math_m4_init_id(); 593 model = math_translate(model, (v3) {player.pos.local.x, player.pos.local.y, 0.0f}); 594 model = math_scale(model, (v3) {player.dimensions.x, player.dimensions.y, 1.0f}); 595 596 v3 color = (v3) { 1.0f, 0.0f, 1.0f }; 597 598 renderer_job_enqueue( 599 &game_state->renderer, 600 (struct RenderJob) { 601 .ebo = game_state->renderer.quad_ebo, 602 .color = color, 603 .model = model, 604 .layer = RENDER_LAYER_PLAYER, 605 }); 606 } 607 608 // game_render_tiles 609 { 610 for (size_t y = 0; y < ROOM_DIM_Y; y++) 611 { 612 for (size_t x = 0; x < ROOM_DIM_X; x++) 613 { 614 v2 tile_pos = { 615 x, 616 ROOM_DIM_Y - 1.0f - y, 617 }; 618 619 m4 model = math_m4_init_id(); 620 // our square verts are anchored around the center point of the 621 // square, so we want to offset by 0.5 to instead have our 622 // anchor in the min corner 623 model = math_translate(model, (v3) {TILE_SIZE * 0.5f, TILE_SIZE * 0.5f, 0.0f}); 624 model = math_translate(model, (v3) {tile_pos.x, tile_pos.y, 0.0f}); 625 model = math_scale(model, (v3) {TILE_SIZE, TILE_SIZE, 1.0f}); 626 627 v3 color; 628 bool solid = world_tile_in_room_is_solid(current_room->tiles, tile_pos); 629 f32 tex_interp = 0.0f; 630 u8 tile_id = 0; 631 if (solid) 632 { 633 color = (v3) {0.4f, 0.4f, 0.4f}; 634 tex_interp = 1.0f; 635 tile_id = get_tile_value(current_room->tiles, tile_pos); 636 assert(tile_id > 0); 637 tile_id -= 1; 638 tile_id += current_room->tileset_offset; 639 } 640 else 641 { 642 color = (math_v3_from_rgb(0x16161D)); 643 } 644 645 renderer_job_enqueue( 646 &game_state->renderer, 647 (struct RenderJob) { 648 .ebo = game_state->renderer.quad_ebo, 649 .color = color, 650 .model = model, 651 .tex_interp = tex_interp, 652 .tile_id = tile_id, 653 .layer = RENDER_LAYER_TILES, 654 }); 655 } 656 } 657 } 658 659 renderer_jobs_sort(&game_state->renderer, &game_state->world_allocator); 660 renderer_jobs_draw(&game_state->renderer, framebuffer); 661 } 662 663 internal void game_cleanup(struct GameMemory *game_memory) 664 { 665 assert(sizeof(struct GameState) <= game_memory->buffer_size); 666 struct GameState *game_state = (struct GameState *)game_memory->buffer; 667 renderer_cleanup(&game_state->renderer); 668 } 669 670 #ifdef PLATFORM_HOTLOAD_GAME_CODE 671 void game_load_opengl_symbols(void) 672 { 673 #if defined(PLATFORM_LINUX) 674 am_gl_init(); 675 #endif 676 } 677 #endif