/* * Created by yuuta on 7/20/22. */ #include #include "net.h" #include #ifndef WIN32 #include #include #include #endif #ifdef __STDC_NO_THREADS__ # ifdef WIN32 #define THREAD_WIN32 #include static HANDLE mtx_conn; int lock(void) { \ DWORD _dwlck = WaitForSingleObject(mtx_conn, 0); \ if (_dwlck == WAIT_FAILED) { _dwlck = GetLastError(); } \ if (_dwlck) { fprintf(stderr, "Cannot lock the mutex: %d.\n", _dwlck); return _dwlck; } \ return 0; \ } int unlock(void) { \ if (!ReleaseMutex(mtx_conn)) { \ int r = GetLastError(); \ fprintf(stderr, "Cannot release the mutex: %d.\n", r); \ return r; \ } \ return 0; \ } # else #error "Either C11 threading or Win32 API is required for concurrency." # endif #else #define THREAD_C11 #include static mtx_t mtx_conn; int lock(void) { \ if (mtx_lock(&mtx_conn) != thrd_success) { \ fprintf(stderr, "Cannot lock the mutex.\n"); \ return 1; \ } \ return 0; \ } int unlock(void) { \ if (mtx_unlock(&mtx_conn) != thrd_success) { \ fprintf(stderr, "Cannot release the mutex.\n"); \ return 1; \ } \ return 0; \ } #endif static bool ready = false; static const char *world_name(const enum ac_world world) { switch (world) { case overworld: return "overworld"; case nether: return "nether"; case end: return "end"; default: return "unknown world"; } } static void handle_event(const ac_event_t *event) { switch (event->type) { case AC_EVENT_LAGGING: { ac_event_lagging_t *o = (ac_event_lagging_t *) event; printf("Server lagging: running %ld milliseconds (%ld ticks) behind.\n", o->ms, o->ticks); break; } case AC_EVENT_ENTITY_DEATH: { ac_event_entity_death_t *o = (ac_event_entity_death_t *) event; printf("Entity '%s' (%s) died at %s(%.2f, %.2f, %.2f): %s.\n", o->entity.name, o->entity.type, world_name(o->entity.world), o->entity.pos.x, o->entity.pos.y, o->entity.pos.z, o->message); break; } case AC_EVENT_PLAYER_MESSAGE: { ac_event_player_message_t *o = (ac_event_player_message_t *) event; printf("Player '%s' said at %s(%.2f, %.2f, %.2f): %s.\n", o->player.name, world_name(o->player.world), o->player.pos.x, o->player.pos.y, o->player.pos.z, o->text); break; } case AC_EVENT_PLAYER_DISCONNECT: { ac_event_player_disconnect_t *o = (ac_event_player_disconnect_t *) event; if (!o->player) { printf("Unknown player disconnected: %s.\n", o->reason); } else { printf("Player '%s' disconnected at %s(%.2f, %.2f, %.2f): %s.\n", o->player->name, world_name(o->player->world), o->player->pos.x, o->player->pos.y, o->player->pos.z, o->reason); } break; } case AC_EVENT_PLAYER_JOIN: { ac_event_player_join_t *o = (ac_event_player_join_t *) event; printf("Player '%s' joined at %s(%.2f, %.2f, %.2f).\n", o->player.name, world_name(o->player.world), o->player.pos.x, o->player.pos.y, o->player.pos.z); break; } default: { printf("Received an unrecognized event of type '%u'.\n", event->type); } } } static void handle_response(const ac_response_t *response) { switch (response->type) { case AC_RESPONSE_OK: { ac_response_ok_t *o = (ac_response_ok_t *) response; printf("Request %d OK.\n", o->id); break; } case AC_RESPONSE_ERROR: { ac_response_error_t *o = (ac_response_error_t *) response; printf("Request %d failed: %s (%d).\n", o->id, o->message, o->code); break; } case AC_RESPONSE_CMD_OUT: { ac_response_cmd_out_t *o = (ac_response_cmd_out_t *) response; printf("Request %d output by %s: %s.\n", o->id, o->sender, o->out); break; } case AC_RESPONSE_CMD_RESULT: { ac_response_cmd_result_t *o = (ac_response_cmd_result_t *) response; printf("Request %d is done: %s (%d).\n", o->id, o->success ? "Success" : "Failed", o->result); break; } default: { printf("Received an unrecognized response of type '%u'.\n", response->type); } } } #ifndef WIN32 static void intr(int signum) {} #endif static int on_send(const void *sock, const void *buffer, const size_t len) { int r; printf("Writing %lu bytes.\n", len); if ((r = net_send(sock, buffer, len))) { fprintf(stderr, "Cannot write %lu bytes to server: %s (%d).\n", len, net_strerror(r), r); } return r; } static int say(void *connection) { int r; ac_config_t conf = { .name = "helloworld" }; ac_request_cmd_t req = { .type = AC_REQUEST_CMD, .id = 100, .config = &conf, .cmd = "say Hi" }; if ((r = ac_request(connection, (ac_request_t *) &req))) { return r; } return 0; } static int process(void *connection, ac_obj_t *obj) { int r; if (!ready) { /* Wait until ready. */ enum ac_connection_state state; if ((r = ac_get_state(connection, &state))) { return r; } switch (state) { case AC_STATE_INIT: return 0; case AC_STATE_READY: printf("Connection is established.\n"); ready = true; say(connection); return 0; default: fprintf(stderr, "Unexpected state.\n"); return 1; } } if (!obj) { return 0; } if (AC_IS_EVENT(obj->type)) { handle_event((ac_event_t *) obj); } else if (AC_IS_RESPONSE(obj->type)) { handle_response((ac_response_t *) obj); } ac_object_free(obj); return 0; } int main(int argc, char **argv) { int r; if ((r = net_init())) { return r; } #if defined(THREAD_WIN32) if (!(mtx_conn = CreateMutex(NULL, TRUE, NULL))) { r = GetLastError(); fprintf(stderr, "Cannot create mutex: %d.\n", r); return r; } #elif defined(THREAD_C11) if (mtx_init(&mtx_conn, mtx_plain) != thrd_success) { fprintf(stderr, "Cannot create mutex.\n"); return 1; } #endif #ifdef WIN32 fprintf(stderr, "Warning: ^C handler on Windows is not yet available.\n"); #else struct sigaction sa; sa.sa_handler = intr; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if (sigaction(SIGINT, &sa, NULL) || sigaction(SIGTERM, &sa, NULL)) { const int e = errno; fprintf(stderr, "Cannot set SIGINT or SIGTERM handlers: %s (%d).\n", strerror(e), e); return e; } #endif libac_config_t config = { #ifdef DEBUG .out = stderr, #else .out = NULL, #endif .tok = NULL }; if ((r = ac_init(&config))) return r; void *connection; SOCKET sock; ac_connection_parameters_t parameters = { .host = "localhost", .port = 25575, .id = "1", .token = "123", .version = 0, .sock = NULL, .on_send = on_send }; if ((r = net_connect(parameters.host, parameters.port, &sock))) { fprintf(stderr, "Cannot connect to server: %s (%d).\n", net_strerror(r), r); ac_free(); net_free(); return r; } parameters.sock = &sock; if ((r = ac_connect(parameters, &connection))) { net_close(&sock); ac_free(); net_free(); return r; } ac_obj_t *obj; int nr; uint8_t buffer[1000U]; printf("Waiting until the connection is established.\n"); bool again = false; while (1) { size_t read = 0; size_t pos = 0; size_t bytes; if ((nr = net_read(&sock, buffer, sizeof(buffer), &bytes, 10))) { if (nr == NET_TIMEOUT) { printf("Receive timeout.\n"); continue; } else { goto end; } } if ((r = lock())) { goto end; } while (pos < bytes) { if ((r = ac_receive(connection, buffer, pos, bytes, &obj, &read))) { bool again = r == AC_E_AGAIN; if (!again) { unlock(); goto end; } } pos += read; } r = process(connection, obj); if ((r = unlock())) { goto end; } if (r) { goto end; } while (!again) { if ((r = lock())) { goto end; } if ((r = ac_receive(connection, NULL, 0, 0, &obj, NULL))) { bool again = r == AC_E_AGAIN; if (!again) { unlock(); goto end; } } again = false; r = process(connection, obj); if ((r = unlock())) { goto end; } if (r) { goto end; } } } end: if (nr) { if (nr == NET_CLOSED) { fprintf(stderr, "Connection is closed while reading from the server.\n"); } else { fprintf(stderr, "Cannot receive from server: %s (%d).\n", net_strerror(nr), nr); } } if ((r = lock())) { goto end; } ac_disconnect(connection, false); if ((r = unlock())) { goto end; } net_close(&sock); ac_free(); net_free(); #if defined(THREAD_WIN32) CloseHandle(mtx_conn); #elif defined(THREAD_C11) mtx_destroy(&mtx_conn); #endif return r; }