Implement Full Synchronization Milestone - v2.0.4

This commit is contained in:
Michael Howard 2026-04-23 18:05:47 -05:00
parent fbf45cc41c
commit 70b417b87c
7 changed files with 36 additions and 15 deletions

View File

@ -13,7 +13,12 @@ MorriCraft is a high-performance voxel engine built with C++ and Raylib. It feat
## Version History ## Version History
### v2.0.3 - Multiplayer Protocol Hardening (Latest) ### v2.0.4 - Full Synchronization Milestone (Latest)
* **Robust Data Streaming**: Implemented `RecvAll` with multi-platform error handling (Winsock/POSIX) to guarantee entire packet bodies are read.
* **Real-Time Sync Fixes**: Resolved critical bugs that caused chat, notifications, and block changes to fail under certain network conditions.
* **Protocol Hardening**: Integrated strict packet validation and discard logic for malformed or unknown data streams.
### v2.0.3 - Multiplayer Protocol Hardening
* **Protocol Synchronization**: Overhauled the packet processing loop to prevent data drift and 'ghosting' blocks. * **Protocol Synchronization**: Overhauled the packet processing loop to prevent data drift and 'ghosting' blocks.
* **Notification Debouncing**: Implemented filters to ensure join/leave messages are only triggered once per action. * **Notification Debouncing**: Implemented filters to ensure join/leave messages are only triggered once per action.
* **Cross-Platform Stability**: Replaced non-portable socket flags with a robust `select()`-based architecture for Windows/Linux parity. * **Cross-Platform Stability**: Replaced non-portable socket flags with a robust `select()`-based architecture for Windows/Linux parity.

Binary file not shown.

View File

@ -1 +1 @@
v2.0.2 v2.0.4

Binary file not shown.

View File

@ -1 +1 @@
v2.0.2 v2.0.4

View File

@ -258,6 +258,23 @@ void NetSetBlock(int x, int y, int z, int type) {
} }
} }
int RecvAll(Socket s, char* buf, int len) {
int total = 0;
while (total < len) {
int r = recv(s, buf + total, len - total, 0);
if (r <= 0) {
#ifdef _WIN32
if (r < 0 && WSAGetLastError() == WSAEWOULDBLOCK) continue;
#else
if (r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) continue;
#endif
return r;
}
total += r;
}
return total;
}
void SetBlock(int x, int y, int z, int type) { void SetBlock(int x, int y, int z, int type) {
if (y < 0 || y >= CHUNK_HEIGHT) return; if (y < 0 || y >= CHUNK_HEIGHT) return;
int cx = (int)floorf((float)x / CHUNK_SIZE); int cx = (int)floorf((float)x / CHUNK_SIZE);
@ -733,7 +750,7 @@ int main(void)
// By default, windows have minimize, maximize, and close buttons on the top bar. // By default, windows have minimize, maximize, and close buttons on the top bar.
SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT); SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT);
InitWindow(screenWidth, screenHeight, "MorriCraft v2.0.3"); InitWindow(screenWidth, screenHeight, "MorriCraft v2.0.4");
LoadConfig(); LoadConfig();
SetExitKey(KEY_NULL); // Prevent ESC from closing the window SetExitKey(KEY_NULL); // Prevent ESC from closing the window
@ -967,7 +984,6 @@ int main(void)
// Handle incoming data // Handle incoming data
auto handleIncoming = [&](Socket sock, bool isServer, int clientIdx = -1) { auto handleIncoming = [&](Socket sock, bool isServer, int clientIdx = -1) {
while (true) { while (true) {
// Use a quick non-blocking check to see if more data is available
fd_set readfds; fd_set readfds;
FD_ZERO(&readfds); FD_ZERO(&readfds);
FD_SET(sock, &readfds); FD_SET(sock, &readfds);
@ -1005,7 +1021,7 @@ int main(void)
if (head.type == PACKET_HANDSHAKE) { if (head.type == PACKET_HANDSHAKE) {
PacketHandshake hand; PacketHandshake hand;
recv(sock, (char*)&hand, sizeof(hand), 0); if (RecvAll(sock, (char*)&hand, sizeof(hand)) <= 0) break;
if (isServer) { if (isServer) {
bool duplicate = false; bool duplicate = false;
for (auto& rp : remotePlayers) { if (rp.sock == sock) { duplicate = true; break; } } for (auto& rp : remotePlayers) { if (rp.sock == sock) { duplicate = true; break; } }
@ -1054,7 +1070,7 @@ int main(void)
} }
} else if (head.type == PACKET_PLAYER_UPDATE) { } else if (head.type == PACKET_PLAYER_UPDATE) {
PacketPlayerUpdate pu; PacketPlayerUpdate pu;
recv(sock, (char*)&pu, sizeof(pu), 0); if (RecvAll(sock, (char*)&pu, sizeof(pu)) <= 0) break;
bool found = false; bool found = false;
for (auto& rp : remotePlayers) { for (auto& rp : remotePlayers) {
if (rp.id == pu.playerID) { if (rp.id == pu.playerID) {
@ -1083,7 +1099,7 @@ int main(void)
} }
} else if (head.type == PACKET_BLOCK_CHANGE) { } else if (head.type == PACKET_BLOCK_CHANGE) {
PacketBlockChange bc; PacketBlockChange bc;
recv(sock, (char*)&bc, sizeof(bc), 0); if (RecvAll(sock, (char*)&bc, sizeof(bc)) <= 0) break;
SetBlock(bc.x, bc.y, bc.z, bc.blockType); SetBlock(bc.x, bc.y, bc.z, bc.blockType);
if (isServer) { if (isServer) {
for (auto& other : clientSockets) { for (auto& other : clientSockets) {
@ -1095,11 +1111,11 @@ int main(void)
} }
} else if (head.type == PACKET_TIME_SYNC) { } else if (head.type == PACKET_TIME_SYNC) {
PacketTimeSync ts; PacketTimeSync ts;
recv(sock, (char*)&ts, sizeof(ts), 0); if (RecvAll(sock, (char*)&ts, sizeof(ts)) <= 0) break;
if (!isServer) gameTime = ts.gameTime; if (!isServer) gameTime = ts.gameTime;
} else if (head.type == PACKET_SEED_SYNC) { } else if (head.type == PACKET_SEED_SYNC) {
PacketSeedSync ss; PacketSeedSync ss;
recv(sock, (char*)&ss, sizeof(ss), 0); if (RecvAll(sock, (char*)&ss, sizeof(ss)) <= 0) break;
if (!isServer) { if (!isServer) {
globalSeedHash = ss.seed; globalSeedHash = ss.seed;
for (auto& pair : worldChunks) delete pair.second; for (auto& pair : worldChunks) delete pair.second;
@ -1107,7 +1123,7 @@ int main(void)
} }
} else if (head.type == PACKET_CHAT) { } else if (head.type == PACKET_CHAT) {
PacketChat pc; PacketChat pc;
recv(sock, (char*)&pc, sizeof(pc), 0); if (RecvAll(sock, (char*)&pc, sizeof(pc)) <= 0) break;
chatLog.push_back({ std::string(pc.name) + ": " + pc.message, 5.0f }); chatLog.push_back({ std::string(pc.name) + ": " + pc.message, 5.0f });
if (isServer) { if (isServer) {
for (auto& other : clientSockets) { for (auto& other : clientSockets) {
@ -1120,7 +1136,7 @@ int main(void)
} else { } else {
if (head.size > 0 && head.size < 2048) { if (head.size > 0 && head.size < 2048) {
std::vector<char> discard(head.size); std::vector<char> discard(head.size);
recv(sock, discard.data(), head.size, 0); RecvAll(sock, discard.data(), head.size);
} }
} }
} }
@ -1710,8 +1726,8 @@ int main(void)
DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE); DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE);
EndMode2D(); EndMode2D();
// Show Version Number (v2.0.3) in Red // Show Version Number (v2.0.4) in Red
DrawTextEx(customFont, "v2.0.3", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED); DrawTextEx(customFont, "v2.0.4", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED);
// --- PLAYER NAME POPUP (IF MISSING) --- // --- PLAYER NAME POPUP (IF MISSING) ---
if (playerName == "") { if (playerName == "") {

View File

@ -1 +1 @@
v2.0.3 v2.0.4