// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv FRAME START vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv f64 last_time = 0; while(running > 0) { f64 now = performance_counter.get_ms(); f64 ms_passed = now - last_time; g_platform_shared.delta = (ms_passed * 0.001); last_time = now; while(PeekMessageA(&msg, null, 0, 0, PM_REMOVE)) { if(LOWORD(msg.message) == WM_QUIT) { // @Note(tkap): We get a WM_QUIT message because we destroy the fake window, so we want to actually // receive 2 WM_QUIT messages to quit. That is why we initially set "running" to 2 instead of 1 (true) running -= 1; } TranslateMessage(&msg); DispatchMessage(&msg); } game_window.w = (float)g_window_width; game_window.h = (float)g_window_height; game_window.center.x = g_window_width / 2.0f; game_window.center.y = g_window_height / 2.0f; la_reset(&game_network.arena); la_reset(&arenas.string_arena); assert(game_network.arena.push_count == 0); // @Note(tkap): This wont work for release, because we are targeting "build" directory. // Not that it should, since release + internal makes no sense #ifdef m_debug_internal constexpr char* original_path = "build\\game.dll"; constexpr char* temp_path = "build\\temp_game.dll"; FILETIME time = get_last_write_time(original_path); if(CompareFileTime(&dll_last_write_time, &time) != 0) { dll_last_write_time = time; dll = force_load_dll(original_path, temp_path, dll); assert(dll); update_game = (t_update_game*)GetProcAddress(dll, "update_game"); assert(update_game); need_to_load_gl_functions = true; } #endif // m_debug_internal // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv HANDLE INPUT START vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv { s_v2 unbound_mouse = get_window_mouse(window.handle); g_platform_shared.platform_mouse.x = clamp(0.0f, game_window.w, unbound_mouse.x); g_platform_shared.platform_mouse.y = clamp(0.0f, game_window.h, unbound_mouse.y); { b8 should_show_cursor = unbound_mouse.x <= 0 || unbound_mouse.x >= game_window.w || unbound_mouse.y <= 0 || unbound_mouse.y >= game_window.h; should_show_cursor |= g_platform_shared.use_default_cursor; if(showing_cursor && !should_show_cursor) { ShowCursor(false); showing_cursor = false; } else if(!showing_cursor && should_show_cursor) { ShowCursor(true); showing_cursor = true; } } #ifdef m_debug_internal b8 is_window_active = GetActiveWindow() == window.handle; #endif // m_debug_internal #ifdef m_debug_internal if(g_platform_shared.playing_replay) { if(g_platform_shared.render_count >= g_recorded_input->mouse.count) { log_info("Ran out of recorded input"); g_platform_shared.playing_replay = false; // @Note(tkap, 18/06/2024): Release ALT in case we ALT+F4'd to quit when recording apply_key_event( &g_platform_shared.update_input, key_left_alt, key_alt, false, false, 0 ); apply_key_event( &g_platform_shared.render_input, key_left_alt, key_alt, false, false, 0 ); } else { apply_recorded_input(g_platform_shared.render_count, is_window_active); } } else if(recording_data.playing_loop) { int fake_frame = recording_data.recording_start_frame + recording_data.frames_played; apply_recorded_input(fake_frame, is_window_active); } #endif // m_debug_internal } // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ HANDLE INPUT END ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INIT ENET START vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv if(game_network.init_connection) { game_network.init_connection = false; if(network.enet_initialized) { if(network.me) { enet_host_destroy(network.me); network.me = null; } enet_deinitialize(); } network.enet_initialized = true; if(enet_initialize() != 0) { error(false); } if(game_network.is_host) { ENetAddress address = zero; address.host = ENET_HOST_ANY; address.port = game_network.port; network.me = enet_host_create( &address, // create a client host enet_max_clients, // only allow 1 outgoing connection 2, // allow up 2 channels to be used, 0 and 1 0, // assume any amount of incoming bandwidth 0 // assume any amount of outgoing bandwidth ); network.peers_active[c_host_index] = true; } else { network.me = enet_host_create( null, // create a client host 1, // only allow 1 outgoing connection 2, // allow up 2 channels to be used, 0 and 1 0, // assume any amount of incoming bandwidth 0 // assume any amount of outgoing bandwidth ); } if(network.me == null) { error(false); } } // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ INIT ENET END ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv CONNECT TO SERVER START vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv if(game_network.connect_to_server) { assert(!game_network.is_host); assert(network.me); game_network.connect_to_server = false; game_network.connection_state = e_connection_state_connecting; ENetAddress address = zero; assert(game_network.port); assert(game_network.ip[0]); enet_address_set_host(&address, game_network.ip); address.port = game_network.port; game_network.time_spent_trying_to_connect = 0; // @Note(tkap, 01/02/2024): I'd like to call this, but it seems to crash sometimes and I don't know which members to check // to prevent that // if(network.server && network.server->state != ENET_PEER_STATE_DISCONNECTED && network.server->connectID != 0) { // enet_peer_reset(network.server); // } network.server = enet_host_connect(network.me, &address, 2, 0); if(network.server == null) { game_network.connection_state = e_connection_state_not_connected; game_network.failed_to_connect = true; log_error("enet_host_connect failed"); } else { log_info("Client: Found server"); } } // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ CONNECT TO SERVER END ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DISCONNECT START vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv { if(game_network.disconnect) { assert(!game_network.is_host); assert(game_network.connection_state == e_connection_state_connected); game_network.clients_active.clear(); game_network.disconnect = false; game_network.connection_state = e_connection_state_not_connected; enet_peer_disconnect_now(network.server, 0); log_info("Client: Successfully disconnected"); } } // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DISCONNECT END ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv UPDATE NETWORK START vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv if( game_network.connection_state == e_connection_state_connected || game_network.connection_state == e_connection_state_connecting ) { if(game_network.connection_state == e_connection_state_connecting) { game_network.time_spent_trying_to_connect += (float)g_platform_shared.delta; #ifdef m_debug_internal constexpr int max_connect_time = 10; #else // m_debug_internal constexpr int max_connect_time = 5; #endif // NOT m_debug_internal if(game_network.time_spent_trying_to_connect > max_connect_time) { game_network.connection_state = e_connection_state_not_connected; game_network.failed_to_connect = true; } } ENetEvent event = zero; while(enet_host_service(network.me, &event, 0) > 0) { switch(event.type) { case ENET_EVENT_TYPE_NONE: { } break; case ENET_EVENT_TYPE_CONNECT: { if(game_network.is_host) { if(game_network.reject_connections) { enet_peer_disconnect_now(event.peer, 0); } else { for(int i = 0; i < enet_max_clients; i += 1) { if(network.peers_active[i]) { continue; } network.peers_active[i] = true; network.peers[i] = event.peer; assert(game_network.incoming_packets.count < max_packets); s_packet packet = zero; packet.type = e_packet_connect; packet.sender = i; log_info("Host: %i connected!", i); game_network.incoming_packets.add(packet); break; } } } else { assert(game_network.connection_state != e_connection_state_connected); assert(!game_network.just_connected); game_network.just_connected = true; log_info("Client: Connected!"); } } break; case ENET_EVENT_TYPE_DISCONNECT: { if(game_network.is_host) { #ifdef m_debug b8 found = false; #endif // m_debug for(int client_i = 1; client_i < enet_max_clients; client_i += 1) { if(!network.peers_active[client_i]) { continue; } if(event.peer->connectID == network.peers[client_i]->connectID) { #ifdef m_debug found = true; #endif // m_debug network.peers_active[client_i] = false; network.peers[client_i] = null; assert(game_network.incoming_packets.count < max_packets); s_packet packet = zero; packet.type = e_packet_disconnect; packet.sender = client_i; game_network.incoming_packets.add(packet); break; } } assert(found); } else { assert(game_network.connection_state == e_connection_state_connected); game_network.connection_state = e_connection_state_not_connected; game_network.just_disconnected = true; log_info("Client: Disconnected!"); } } break; case ENET_EVENT_TYPE_RECEIVE: { if(game_network.is_host) { int index = -1; for(int client_i = 1; client_i < enet_max_clients; client_i += 1) { if(network.peers_active[client_i] && event.peer->connectID == network.peers[client_i]->connectID) { index = client_i; break; } } if(index != -1) { assert(game_network.incoming_packets.count < max_packets); s_packet packet = zero; packet.type = e_packet_rpc; packet.size = (int)event.packet->dataLength; packet.data = (u8*)la_get(&game_network.arena, event.packet->dataLength); packet.sender = index; memcpy(packet.data, event.packet->data, event.packet->dataLength); game_network.incoming_packets.add(packet); enet_packet_destroy(event.packet); } else { // @TODO(tkap): Logging } } else { assert(game_network.incoming_packets.count < max_packets); s_packet packet = zero; packet.size = (int)event.packet->dataLength; packet.data = (u8*)la_get(&game_network.arena, event.packet->dataLength); packet.type = e_packet_rpc; memcpy(packet.data, event.packet->data, event.packet->dataLength); game_network.incoming_packets.add(packet); enet_packet_destroy(event.packet); } } break; invalid_default_case; } } } // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UPDATE NETWORK END ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #ifdef m_debug_internal { if( (g_platform_shared.render_input.key_states[key_f8].down && g_platform_shared.render_input.key_states[key_f8].half_transition_count == 1) || g_platform_shared.render_input.key_states[key_f8].half_transition_count >= 2 ) { transparent_window = !transparent_window; if(transparent_window) { LONG_PTR style = GetWindowLongPtrA( window.handle, GWL_EXSTYLE ); style |= WS_EX_LAYERED; SetWindowLongPtrA( window.handle, GWL_EXSTYLE, style ); BOOL result = SetWindowPos( window.handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED ); assert(result); SetLayeredWindowAttributes(window.handle, RGB(0, 0, 0), 128, LWA_ALPHA); } else { LONG_PTR style = GetWindowLongPtrA( window.handle, GWL_EXSTYLE ); style &= ~WS_EX_LAYERED; SetWindowLongPtrA( window.handle, GWL_EXSTYLE, style ); BOOL result = SetWindowPos( window.handle, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED ); assert(result); SetLayeredWindowAttributes(window.handle, RGB(0, 0, 0), 255, LWA_ALPHA); } } } #endif #ifdef m_debug_internal // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv record input start vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv if(!g_platform_shared.replay_argument) { g_recorded_input->mouse.add_checked(g_platform_shared.platform_mouse); g_recorded_input->wheel_movement.add_checked(g_platform_shared.platform_wheel_movement); } // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ record input end ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #endif // m_debug_internal s_audio_stuff audio_stuff = zero; audio_stuff.audio_enabled = g_xaudio.audio_enabled; audio_stuff.play_sound = play_sound; audio_stuff.play_music = play_music; audio_stuff.set_effect_volume = set_effect_volume; audio_stuff.set_music_volume = set_music_volume; update_game( game_memory, rendering_memory, game_window, &arenas, &game_network, performance_counter, need_to_load_gl_functions, args, running <= 0, &g_platform_shared, audio_stuff m_arg(g_recorded_input) m_arg(&recording_data) ); g_platform_shared.platform_wheel_movement = 0; g_platform_shared.platform_last_mouse = g_platform_shared.platform_mouse; g_platform_shared.mouse_delta = zero; need_to_load_gl_functions = false; // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv SEND PACKETS START vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv for(int i = 0; i < game_network.outgoing_packets.count; i += 1) { s_packet* packet = &game_network.outgoing_packets[i]; assert(packet->target >= 0); assert(packet->target < enet_max_clients); ENetPacket* enet_packet = enet_packet_create(packet->data, packet->size, packet->reliable ? ENET_PACKET_FLAG_RELIABLE : 0); if(game_network.is_host) { assert(packet->target != c_host_index); assert(network.peers_active[packet->target]); assert(network.peers[packet->target]); if(packet->free_packet_callback) { assert(packet->reliable); clients_to_kick.add(packet->target); enet_packet->userData = packet->free_packet_callback; enet_packet->freeCallback = enet_packet_free_callback; } enet_peer_send(network.peers[packet->target], 0, enet_packet); } else { assert(network.server); assert(packet->target == c_host_index); // @Note(tkap, 07/12/2022): We do not allow free callbacks for clients currently assert(!packet->free_packet_callback); enet_peer_send(network.server, 0, enet_packet); } } game_network.outgoing_packets.count = 0; // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SEND PACKETS END ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ if(game_network.deinitialize_network && clients_to_kick.count <= 0) { assert(game_network.connection_state == e_connection_state_connected); game_network.deinitialize_network = false; network.enet_initialized = false; game_network.connection_state = e_connection_state_not_connected; game_network.clients_active.clear(); network.peers_active.clear(); if(network.me) { enet_host_destroy(network.me); network.me = null; } invalid_else; enet_deinitialize(); } g_platform_shared.ms_taken_by_last_frame = performance_counter.get_ms() - last_time; SwapBuffers(window.dc); } // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FRAME END ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^