perf: Optimize main loop to eliminate idle CPU usage

This commit is contained in:
João Lucas
2025-09-15 14:30:44 -03:00
committed by Alexander Nutz
parent 7e7a98bd41
commit d02608c69d

View File

@@ -26,6 +26,7 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <sys/select.h>
#endif #endif
#include <unistd.h> #include <unistd.h>
#include <time.h> #include <time.h>
@@ -586,141 +587,168 @@ int main () {
// Track time of last server tick (in microseconds) // Track time of last server tick (in microseconds)
int64_t last_tick_time = get_program_time(); int64_t last_tick_time = get_program_time();
/** // Main server loop: use select() to sleep until I/O or next tick
* Cycles through all connected clients, handling one packet at a time
* from each player. With every iteration, attempts to accept a new
* client connection.
*/
while (true) { while (true) {
// Check if it's time to yield to the idle task fd_set read_fds;
task_yield(); FD_ZERO(&read_fds);
FD_SET(server_fd, &read_fds);
if (deferred_block_updates_count == MAX_DEFERRED_BLOCK_UPDATES) { if (deferred_block_updates_count == MAX_DEFERRED_BLOCK_UPDATES) {
printf("WARNING: Deferred block update queue maxed out\n"); printf("WARNING: Deferred block update queue maxed out\n");
} }
// Attempt to accept a new connection // Attempt to accept a new connection
int max_fd = server_fd;
for (int i = 0; i < MAX_PLAYERS; i ++) { for (int i = 0; i < MAX_PLAYERS; i ++) {
if (clients[i] != -1) continue;
clients[i] = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
// If the accept was successful, make the client non-blocking too
if (clients[i] != -1) { if (clients[i] != -1) {
printf("New client, fd: %d\n", clients[i]); FD_SET(clients[i], &read_fds);
#ifdef _WIN32 if (clients[i] > max_fd) max_fd = clients[i];
u_long mode = 1;
ioctlsocket(clients[i], FIONBIO, &mode);
#else
int flags = fcntl(clients[i], F_GETFL, 0);
fcntl(clients[i], F_SETFL, flags | O_NONBLOCK);
#endif
client_count ++;
} }
break;
} }
// Look for valid connected clients // Compute timeout until next server tick
client_index ++; struct timeval timeout;
if (client_index == MAX_PLAYERS) client_index = 0; int64_t now = get_program_time();
if (clients[client_index] == -1) continue; int64_t elapsed = now - last_tick_time;
int64_t to_next = TIME_BETWEEN_TICKS - elapsed;
if (to_next < 0) to_next = 0;
timeout.tv_sec = (time_t)(to_next / 1000000);
timeout.tv_usec = (suseconds_t)(to_next % 1000000);
// Handle periodic events (server ticks) int activity;
int64_t time_since_last_tick = get_program_time() - last_tick_time; #ifdef _WIN32
if (time_since_last_tick > TIME_BETWEEN_TICKS) { activity = select(0, &read_fds, NULL, NULL, &timeout);
handleServerTick(time_since_last_tick); if (activity == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err == WSAEINTR) continue;
if (err == WSAETIMEDOUT) activity = 0;
}
#else
activity = select(max_fd + 1, &read_fds, NULL, NULL, &timeout);
if (activity < 0) {
if (errno == EINTR) continue; // interrupted, retry
}
#endif
now = get_program_time();
elapsed = now - last_tick_time;
if (elapsed >= TIME_BETWEEN_TICKS) {
handleServerTick(elapsed);
last_tick_time = get_program_time(); last_tick_time = get_program_time();
} }
// Handle this individual client if (activity <= 0) {
int client_fd = clients[client_index]; // timeout or error already handled; continue loop
// Check if at least 2 bytes are available for reading
#ifdef _WIN32
recv_count = recv(client_fd, recv_buffer, 2, MSG_PEEK);
if (recv_count == 0) {
disconnectClient(&clients[client_index], 1);
continue; continue;
} }
if (recv_count == SOCKET_ERROR) {
int err = WSAGetLastError(); if (FD_ISSET(server_fd, &read_fds)) {
if (err == WSAEWOULDBLOCK) { while (true) {
continue; // no data yet, keep client alive int slot = -1;
} else { for (int i = 0; i < MAX_PLAYERS; i ++) {
disconnectClient(&clients[client_index], 1); if (clients[i] == -1) { slot = i; break; }
}
if (slot == -1) {
int tmp = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (tmp == -1) break;
#ifdef _WIN32
closesocket(tmp);
#else
close(tmp);
#endif
break;
}
clients[slot] = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (clients[slot] == -1) break; // no more pending
printf("New client, fd: %d\n", clients[slot]);
#ifdef _WIN32
u_long mode = 1;
ioctlsocket(clients[slot], FIONBIO, &mode);
#else
int flags = fcntl(clients[slot], F_GETFL, 0);
fcntl(clients[slot], F_SETFL, flags | O_NONBLOCK);
#endif
client_count ++;
}
}
for (int i = 0; i < MAX_PLAYERS; i ++) {
int client_fd = clients[i];
if (client_fd == -1) continue;
if (!FD_ISSET(client_fd, &read_fds)) continue;
#ifdef _WIN32
recv_count = recv(client_fd, recv_buffer, 2, MSG_PEEK);
if (recv_count == 0) {
disconnectClient(&clients[i], 1);
continue;
}
if (recv_count == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK) {
continue; // no data yet, keep client alive
} else {
disconnectClient(&clients[i], 1);
continue;
}
}
#else
recv_count = recv(client_fd, &recv_buffer, 2, MSG_PEEK);
if (recv_count < 2) {
if (recv_count == 0 || (recv_count < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) {
disconnectClient(&clients[i], 1);
}
continue;
}
#endif
#ifdef DEV_ENABLE_BEEF_DUMPS
if (recv_buffer[0] == 0xBE && recv_buffer[1] == 0xEF && getClientState(client_fd) == STATE_NONE) {
send_all(client_fd, block_changes, sizeof(block_changes));
send_all(client_fd, player_data, sizeof(player_data));
shutdown(client_fd, SHUT_WR);
recv_all(client_fd, recv_buffer, sizeof(recv_buffer), false);
disconnectClient(&clients[i], 6);
continue;
}
if (recv_buffer[0] == 0xFE && recv_buffer[1] == 0xED && getClientState(client_fd) == STATE_NONE) {
recv_all(client_fd, recv_buffer, 2, false);
recv_all(client_fd, block_changes, sizeof(block_changes), false);
recv_all(client_fd, player_data, sizeof(player_data), false);
for (int j = 0; j < MAX_BLOCK_CHANGES; j ++) {
if (block_changes[j].block == 0xFF) continue;
if (block_changes[j].block == B_chest) j += 14;
if (j >= block_changes_count) block_changes_count = j + 1;
}
writeBlockChangesToDisk(0, block_changes_count);
writePlayerDataToDisk();
disconnectClient(&clients[i], 7);
continue;
}
#endif
int length = readVarInt(client_fd);
if (length == VARNUM_ERROR) {
disconnectClient(&clients[i], 2);
continue;
}
int packet_id = readVarInt(client_fd);
if (packet_id == VARNUM_ERROR) {
disconnectClient(&clients[i], 3);
continue;
}
int state = getClientState(client_fd);
if (state == STATE_NONE && length == 254 && packet_id == 122) {
disconnectClient(&clients[i], 5);
continue;
}
handlePacket(client_fd, length - sizeVarInt(packet_id), packet_id, state);
if (recv_count == 0 || (recv_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK)) {
disconnectClient(&clients[i], 4);
continue; continue;
} }
} }
#else
recv_count = recv(client_fd, &recv_buffer, 2, MSG_PEEK);
if (recv_count < 2) {
if (recv_count == 0 || (recv_count < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) {
disconnectClient(&clients[client_index], 1);
}
continue;
}
#endif
// Handle 0xBEEF and 0xFEED packets for dumping/uploading world data
#ifdef DEV_ENABLE_BEEF_DUMPS
// Received BEEF packet, dump world data and disconnect
if (recv_buffer[0] == 0xBE && recv_buffer[1] == 0xEF && getClientState(client_fd) == STATE_NONE) {
// Send block changes and player data back to back
// The client is expected to know (or calculate) the size of these buffers
send_all(client_fd, block_changes, sizeof(block_changes));
send_all(client_fd, player_data, sizeof(player_data));
// Flush the socket and receive everything left on the wire
shutdown(client_fd, SHUT_WR);
recv_all(client_fd, recv_buffer, sizeof(recv_buffer), false);
// Kick the client
disconnectClient(&clients[client_index], 6);
continue;
}
// Received FEED packet, load world data from socket and disconnect
if (recv_buffer[0] == 0xFE && recv_buffer[1] == 0xED && getClientState(client_fd) == STATE_NONE) {
// Consume 0xFEED bytes (previous read was just a peek)
recv_all(client_fd, recv_buffer, 2, false);
// Write full buffers straight into memory
recv_all(client_fd, block_changes, sizeof(block_changes), false);
recv_all(client_fd, player_data, sizeof(player_data), false);
// Recover block_changes_count
for (int i = 0; i < MAX_BLOCK_CHANGES; i ++) {
if (block_changes[i].block == 0xFF) continue;
if (block_changes[i].block == B_chest) i += 14;
if (i >= block_changes_count) block_changes_count = i + 1;
}
// Update data on disk
writeBlockChangesToDisk(0, block_changes_count);
writePlayerDataToDisk();
// Kick the client
disconnectClient(&clients[client_index], 7);
continue;
}
#endif
// Read packet length
int length = readVarInt(client_fd);
if (length == VARNUM_ERROR) {
disconnectClient(&clients[client_index], 2);
continue;
}
// Read packet ID
int packet_id = readVarInt(client_fd);
if (packet_id == VARNUM_ERROR) {
disconnectClient(&clients[client_index], 3);
continue;
}
// Get client connection state
int state = getClientState(client_fd);
// Disconnect on legacy server list ping
if (state == STATE_NONE && length == 254 && packet_id == 122) {
disconnectClient(&clients[client_index], 5);
continue;
}
// Handle packet data
handlePacket(client_fd, length - sizeVarInt(packet_id), packet_id, state);
if (recv_count == 0 || (recv_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK)) {
disconnectClient(&clients[client_index], 4);
continue;
}
} }
close(server_fd); close(server_fd);