I have begun adding online functionality to my game. I use ENet to handle my networking. I intend to have the server and client run at a fixed timestep, with the server ahead in time sending world state information, and the client sending input commands. This seems to be working for the most part. However, it appears to be really jittery. The reason is that apparently every 2 or 3 frames the client tries to read from the server and gets nothing. The server somehow manages to get slightly behind for a bit despite having the same timestep as the client and starting first.
I tried adding artificial delays to the client, but eventually the server would fall slightly behind. Naturally, I assumed that perhaps my simulation code was taking too long, but having created a minimal client and server and it happened again. I've been bashing my head against this for a while and I'm out of ideas. Perhaps someone more experienced can help me out here?
Here's the code for the server loop:
void oneSecondServerThread() {
ENetAddress address;
ENetHost* server = nullptr;
ENetEvent event;
ENetPacket *packet = nullptr;
std::vector<ENetPeer*> connectedPeers;
address.host = ENET_HOST_ANY;
address.port = 8080;
server = enet_host_create(&address, 32, 2, 0, 0);
//wait for the client to connect
while (connectedPeers.empty()) {
while (enet_host_service(server, &event, 0) > 0) {
if (event.type == ENET_EVENT_TYPE_CONNECT) {
connectedPeers.push_back(event.peer);
}
}
}
bool running = true;
//for testing purposes, the timestep is one frame per second
std::chrono::high_resolution_clock::time_point currentTime = std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::time_point previousTime = std::chrono::high_resolution_clock::now();
std::chrono::nanoseconds timestep(1000000000);
std::chrono::nanoseconds accumulator(0);
uint8_t packetNumber = 0;
while (running) {
previousTime = currentTime;
currentTime = std::chrono::high_resolution_clock::now();
std::chrono::nanoseconds delta =
std::chrono::duration_cast<std::chrono::nanoseconds>(currentTime - previousTime);
accumulator += delta;
while (accumulator.count() > timestep.count()) {
accumulator -= timestep;
//check for events
while (enet_host_service(server, &event, 0) > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_CONNECT:
connectedPeers.push_back(event.peer);
break;
case ENET_EVENT_TYPE_DISCONNECT:
running = false;
break;
case ENET_EVENT_TYPE_RECEIVE:
uint8_t receivedNumber;
memcpy(&receivedNumber, event.packet->data, 1);
printf("Client packet %u received\n", packetNumber);
enet_packet_destroy(event.packet);
}
}
//create a packet consisting of a single byte
packet = enet_packet_create(&packetNumber, 1, ENET_PACKET_FLAG_UNSEQUENCED);
for (ENetPeer* peer : connectedPeers) {
enet_peer_send(peer, 0, packet);
}
printf("Server packet %u sent\n", packetNumber);
packetNumber++;
}
}
enet_host_flush(server);
enet_host_destroy(server);
}
And the client:
void oneSecondClientThread() {
ENetAddress address;
ENetHost* client = nullptr;
ENetEvent event;
ENetPeer *peer = nullptr;
ENetPacket *packet = nullptr;
client = enet_host_create(nullptr, 1, 2, 0, 0);
enet_address_set_host(&address, "localhost");
address.port = 8080;
printf("Attempting to connect ...\n");
peer = enet_host_connect(client, &address, 2, 0);
bool connected = false;
while (!connected) {
while (enet_host_service(client, &event, 0) > 0) {
if (event.type == ENET_EVENT_TYPE_CONNECT) {
printf("Connection successful!\n");
connected = true;
}
}
}
bool running = true;
//like with the server above, the timestep is 1 frame per second
std::chrono::high_resolution_clock::time_point currentTime = std::chrono::high_resolution_clock::now();
std::chrono::nanoseconds timestep(1000000000);
std::chrono::nanoseconds accumulator(0);
uint8_t packetNumber = 0;
while (running) {
std::chrono::high_resolution_clock::time_point previousTime = currentTime;
currentTime = std::chrono::high_resolution_clock::now();
std::chrono::nanoseconds delta =
std::chrono::duration_cast<std::chrono::nanoseconds>(currentTime - previousTime);
accumulator += delta;
while (accumulator.count() > timestep.count()) {
int receivedCount = 0;
accumulator -= timestep;
while (enet_host_service(client, &event, 0) > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_DISCONNECT:
running = false;
break;
case ENET_EVENT_TYPE_RECEIVE:
receivedCount++;
uint8_t receivedNumber;
memcpy(&receivedNumber, event.packet->data, 1);
printf("Server packet %u received\n", packetNumber);
enet_packet_destroy(event.packet);
}
}
//The server is sending frames once per second constantly
//if we didn't receive a packet, that means that the server fell behind
if (receivedCount == 0) {
printf("Server dropped a frame!\n");
}
//create a packet consisting of a single byte
packet = enet_packet_create(&packetNumber, 1, ENET_PACKET_FLAG_UNSEQUENCED);
enet_peer_send(peer, 0, packet);
printf("Client packet %u sent\n", packetNumber);
packetNumber++;
}
}
enet_peer_reset(peer);
enet_host_destroy(client);
}
The server method is called in its own thread, while the client works on the main thread. I'm sure I'm missing something obvious, but for the life of me I don't know what.