Files
dusk/test/network/socket/test_serveroffline.c
T
YourWishes f8607d114c Add UDP socket client/server multiplayer protocol
Implements the multiplayer transport layer from ROADMAP.md: UUID-based
client identity, a reliable-packet channel over UDP, handshake/ack/
ping/disconnect/player-joined/player-left/player-state packet types,
and split online (networked) vs offline (in-process singleplayer)
server modes wired into the engine's update/dispose loop.
2026-08-02 21:06:23 -05:00

122 lines
3.4 KiB
C

/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "dusktest.h"
#include "network/socket/client/localclient.h"
#include "network/socket/server/serveroffline.h"
#include "network/socket/payload/packetplayerstate.h"
#include "util/memory.h"
#include "time/time.h"
typedef struct {
bool_t connected;
bool_t rejected;
bool_t disconnected;
} clientevents_t;
static void onConnected(void *user) {
((clientevents_t *)user)->connected = true;
}
static void onRejected(
const packethandshakerejectreason_t reason,
void *user
) {
(void)reason;
((clientevents_t *)user)->rejected = true;
}
static void onDisconnected(errorret_t error, void *user) {
errorCatch(error);
((clientevents_t *)user)->disconnected = true;
}
static void pumpOffline(localclient_t *client, const int32_t iterations) {
for(int32_t i = 0; i < iterations; i++) {
errorCatch(serverOfflineUpdate());
errorCatch(localClientUpdate(client));
}
}
static int offline_setup(void **state) {
return 0;
}
static int offline_teardown(void **state) {
if(SERVER_OFFLINE.running) errorCatch(serverOfflineStop());
return 0;
}
static void test_offline_client_connects_without_networking(void **state) {
assert_true(errorIsOk(serverOfflineHost()));
localclient_t client;
clientevents_t events;
memoryZero(&events, sizeof(events));
assert_true(errorIsOk(localClientConnectOffline(
&client, "player", onConnected, onRejected, onDisconnected, &events
)));
// Handshake -> accept takes exactly two drain cycles, same as online:
// one for the server to see the HANDSHAKE, one for the client to see
// the HANDSHAKE_ACCEPT.
pumpOffline(&client, 4);
assert_true(events.connected);
assert_false(events.rejected);
assert_int_equal(client.state, LOCAL_CLIENT_STATE_CONNECTED);
assert_int_equal(SERVER_OFFLINE.client.type, SERVER_CLIENT_TYPE_LOCAL);
assert_true(uuidEquals(&SERVER_OFFLINE.client.local.id, &client.id));
packetplayerstate_t playerState;
memoryZero(&playerState, sizeof(playerState));
playerState.position[0] = 4.0f;
playerState.position[1] = 5.0f;
playerState.position[2] = 6.0f;
assert_true(errorIsOk(localClientSend(
&client, PACKET_TYPE_PLAYER_STATE, &playerState, false
)));
pumpOffline(&client, 2);
assert_true(SERVER_OFFLINE.client.local.position[0] == 4.0f);
assert_true(SERVER_OFFLINE.client.local.position[1] == 5.0f);
assert_true(SERVER_OFFLINE.client.local.position[2] == 6.0f);
// Simulate well past LOCAL_CLIENT_TIMEOUT_SECONDS of elapsed game time
// -- the offline server's own idle-ping keepalive must prevent the
// client from falsely timing itself out, same as the online fix.
for(int32_t i = 0; i < 6; i++) {
TIME.time += 3.0f;
pumpOffline(&client, 2);
}
assert_false(events.disconnected);
assert_int_equal(client.state, LOCAL_CLIENT_STATE_CONNECTED);
localClientDisconnect(&client);
pumpOffline(&client, 2);
assert_int_equal(SERVER_OFFLINE.client.type, SERVER_CLIENT_TYPE_NULL);
errorCatch(serverOfflineStop());
assert_int_equal(memoryGetAllocatedCount(), 0);
}
int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(
test_offline_client_connects_without_networking,
offline_setup, offline_teardown
),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}