aboutsummaryrefslogtreecommitdiff
path: root/client/helloworld
diff options
context:
space:
mode:
authorTrumeet <yuuta@yuuta.moe>2022-07-26 17:36:20 -0700
committerTrumeet <yuuta@yuuta.moe>2022-07-26 17:36:20 -0700
commit7099a86ca74fa637f26af38674f80fb8efd5f6fa (patch)
tree33315c65e82170476cf3c80d976dfa4d16a304a3 /client/helloworld
parent8b07bf593e54dd876e30d0cb1c7c7226d0d1b1e2 (diff)
downloadacron-7099a86ca74fa637f26af38674f80fb8efd5f6fa.tar
acron-7099a86ca74fa637f26af38674f80fb8efd5f6fa.tar.gz
acron-7099a86ca74fa637f26af38674f80fb8efd5f6fa.tar.bz2
acron-7099a86ca74fa637f26af38674f80fb8efd5f6fa.zip
refactor(libacron/acronc/helloworld): move to separate directories
The corresponding CMakeLists.txt files are still rough.
Diffstat (limited to 'client/helloworld')
-rw-r--r--client/helloworld/.gitignore82
-rw-r--r--client/helloworld/CMakeLists.txt50
-rw-r--r--client/helloworld/main.c370
-rw-r--r--client/helloworld/net.c181
-rw-r--r--client/helloworld/net.h45
5 files changed, 728 insertions, 0 deletions
diff --git a/client/helloworld/.gitignore b/client/helloworld/.gitignore
new file mode 100644
index 0000000..6741ba2
--- /dev/null
+++ b/client/helloworld/.gitignore
@@ -0,0 +1,82 @@
+cmake-build-debug/
+cmake-build-release/
+json-c/
+
+# https://raw.githubusercontent.com/github/gitignore/main/Global/JetBrains.gitignore
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
diff --git a/client/helloworld/CMakeLists.txt b/client/helloworld/CMakeLists.txt
new file mode 100644
index 0000000..f36e020
--- /dev/null
+++ b/client/helloworld/CMakeLists.txt
@@ -0,0 +1,50 @@
+cmake_minimum_required(VERSION 3.22)
+project(libac C)
+
+set(CMAKE_C_STANDARD 11)
+
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE Debug)
+endif()
+
+add_definitions(-D_POSIX_C_SOURCE=200809L)
+IF(CMAKE_BUILD_TYPE MATCHES Debug)
+ add_definitions(-DDEBUG)
+ENDIF(CMAKE_BUILD_TYPE MATCHES Debug)
+
+if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
+ set(CMAKE_C_FLAGS_DEBUG
+ "${CMAKE_C_FLAGS_DEBUG} -g3 -O0 -fsanitize=address")
+ set(CMAKE_EXE_LINKER_FLAGS_DEBUG
+ "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fsanitize=address")
+ set(CMAKE_C_FLAGS
+ "${CMAKE_C_FLAGS} -fvisibility=hidden")
+elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC")
+ # TODO: MSVC ASAN
+ set(CMAKE_C_FLAGS_DEBUG
+ "${CMAKE_C_FLAGS_DEBUG} /DEBUG /Z7 /Od")
+ set(CMAKE_EXE_LINKER_FLAGS_DEBUG
+ "${CMAKE_EXE_LINKER_FLAGS_DEBUG}")
+endif()
+
+add_subdirectory(../libacron ${CMAKE_BINARY_DIR}/libac)
+
+# helloworld
+if(WIN32)
+ set(APPS_DEPS ws2_32)
+endif()
+set(APPS_DEPS ${APPS_DEPS} ac)
+add_executable(helloworld
+ main.c
+ net.c
+ net.h
+ )
+target_link_libraries(helloworld ${APPS_DEPS})
+
+install(TARGETS helloworld
+ EXPORT ${PROJECT_NAME}-targets
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/libac
+)
diff --git a/client/helloworld/main.c b/client/helloworld/main.c
new file mode 100644
index 0000000..c112fcb
--- /dev/null
+++ b/client/helloworld/main.c
@@ -0,0 +1,370 @@
+/*
+ * Created by yuuta on 7/20/22.
+ */
+
+#include "libac.h"
+#include "net.h"
+
+#include <stdio.h>
+
+#ifndef WIN32
+
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+
+#endif
+
+#ifdef __STDC_NO_THREADS__
+# ifdef WIN32
+#define THREAD_WIN32
+#include <windows.h>
+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 <threads.h>
+
+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 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' died at %s(%.2f, %.2f, %.2f): %s.\n",
+ o->entity.name,
+ 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;
+ 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;
+}
+
+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];
+ size_t bytes;
+ enum ac_connection_state state;
+ printf("Waiting until the connection is established.\n");
+ bool ready = false;
+ while (1) {
+ 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; }
+ if ((r = ac_receive(connection, buffer, bytes, &obj))) {
+ if ((r = unlock())) { goto end; }
+ goto end;
+ }
+ if (!ready) {
+ /* Wait until ready. */
+ if ((r = ac_get_state(connection, &state))) {
+ if ((r = unlock())) { goto end; }
+ goto end;
+ }
+ switch (state) {
+ case AC_STATE_INIT:
+ continue;
+ case AC_STATE_READY:
+ printf("Connection is established.\n");
+ ready = true;
+ say(connection);
+ if ((r = unlock())) { goto end; }
+ continue;
+ default:
+ fprintf(stderr, "Unexpected state.\n");
+ if ((r = unlock())) { goto end; }
+ goto end;
+ }
+ }
+ if ((r = unlock())) { goto end; }
+
+ if (!obj) {
+ continue;
+ }
+ 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);
+ }
+ 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;
+} \ No newline at end of file
diff --git a/client/helloworld/net.c b/client/helloworld/net.c
new file mode 100644
index 0000000..83192e0
--- /dev/null
+++ b/client/helloworld/net.c
@@ -0,0 +1,181 @@
+/*
+ * Created by yuuta on 7/22/22.
+ *
+ * A simple cross-platform (Unix and Windows) implementation of a socket client.
+ */
+
+#include "net.h"
+
+#include <stdint.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef WIN32
+#include <windows.h> /* For DWORD */
+#include <winsock2.h>
+#include <ws2tcpip.h>
+
+#define errno_sock WSAGetLastError()
+
+#else
+
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#define closesocket(socket) close(socket)
+#define errno_sock errno
+
+#endif
+
+int net_connect(const char *host,
+ const uint16_t port,
+ SOCKET *sock) {
+ int r;
+ struct addrinfo *res;
+ char service[6];
+
+ snprintf(service, 6, "%u", port);
+ if ((r = getaddrinfo(host, service, NULL, &res))) {
+ return r;
+ }
+
+ SOCKET fd;
+ if ((fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) <= 0) {
+ const int e = errno_sock;
+ freeaddrinfo(res);
+ return e;
+ }
+ if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) {
+ const int e = errno_sock;
+ closesocket(fd);
+ freeaddrinfo(res);
+ return e;
+ }
+ freeaddrinfo(res);
+ *sock = fd;
+ return 0;
+}
+
+int net_read(const SOCKET *sock,
+ void *buffer,
+ const size_t length,
+ size_t *read,
+ const unsigned int timeout) {
+
+#ifdef WIN32
+ DWORD to = timeout * 1000;
+ if (setsockopt(*sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&to, sizeof to)) {
+#else
+ struct timeval tv;
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+ if (setsockopt(*sock, SOL_SOCKET, SO_RCVTIMEO, (const char *) &tv, sizeof tv)) {
+#endif
+ return errno_sock;
+ }
+
+ int bytes;
+ if ((bytes = (int) recv(*sock, buffer, length, 0)) <= 0) {
+ if (bytes < 0) {
+ const int e = errno_sock;
+#ifdef WIN32
+ if (e == WSAETIMEDOUT) {
+#else
+ if (e == EAGAIN) {
+#endif
+ return NET_TIMEOUT;
+ }
+ return e;
+ }
+ return NET_CLOSED;
+ }
+ *read = bytes;
+ return 0;
+}
+
+int net_send(const SOCKET *sock,
+ const void *buffer,
+ size_t len) {
+ size_t pos;
+ const uint8_t *ptr = buffer;
+ int retval;
+
+ for (pos = 0U; pos < len; pos += retval) {
+ size_t n = len - pos;
+ retval = (int) send(*sock, &ptr[pos], (int) n, 0);
+ if (retval <= 0) {
+ return errno_sock;
+ }
+ }
+ return 0;
+}
+
+int net_init(void) {
+#ifdef WIN32
+ int r;
+ WORD wVersionRequested;
+ WSADATA wsaData;
+ wVersionRequested = MAKEWORD(2, 2);
+ if ((r = WSAStartup(wVersionRequested, &wsaData))) {
+ fprintf(stderr, "WSAStartup failed with error: %d\n", r);
+ return r;
+ }
+ if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
+ fprintf(stderr, "Could not find a usable version of Winsock.dll\n");
+ WSACleanup();
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+void net_close(const SOCKET *sock) {
+ closesocket(*sock);
+}
+
+#ifdef WIN32
+static __declspec( thread ) LPTSTR err_buf = NULL;
+#else
+static _Thread_local char err_buf[1024];
+#endif
+
+void net_free(void) {
+#ifdef WIN32
+ WSACleanup();
+ if (err_buf) {
+ LocalFree(err_buf);
+ err_buf = NULL;
+ }
+#endif
+}
+
+char *net_strerror(int errnum) {
+#ifdef WIN32
+ if (err_buf) {
+ LocalFree(err_buf);
+ err_buf = NULL;
+ }
+ FormatMessage(
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ errnum,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &err_buf,
+ 0,
+ NULL);
+ if (!err_buf) {
+ return "Unknown error";
+ }
+#else
+ int r;
+ if ((r = strerror_r(errnum, err_buf, sizeof(err_buf) / sizeof(char)))) {
+ snprintf(err_buf, 1024, "%d (%d)", errnum, r);
+ }
+#endif
+ return err_buf;
+} \ No newline at end of file
diff --git a/client/helloworld/net.h b/client/helloworld/net.h
new file mode 100644
index 0000000..1a49709
--- /dev/null
+++ b/client/helloworld/net.h
@@ -0,0 +1,45 @@
+/*
+ * Created by yuuta on 7/23/22.
+ */
+
+#ifndef ACRONC_NET_H
+#define ACRONC_NET_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+
+#else
+
+#define SOCKET int
+
+#endif
+
+#define NET_CLOSED -2
+#define NET_TIMEOUT -1
+
+int net_connect(const char *host,
+ uint16_t port,
+ SOCKET *sock);
+
+int net_read(const SOCKET *sock,
+ void *buffer,
+ size_t length,
+ size_t *read,
+ unsigned int timeout);
+
+int net_send(const SOCKET *sock,
+ const void *buffer,
+ size_t len);
+
+void net_close(const SOCKET *sock);
+
+int net_init(void);
+
+void net_free(void);
+
+char *net_strerror(int errnum);
+
+#endif /* ACRONC_NET_H */