From c8e08909647c50dfe258f56bb1213e02e6567eb3 Mon Sep 17 00:00:00 2001 From: Fabio de Souza Villaca Medeiros Date: Mon, 1 Apr 2024 02:14:02 -0300 Subject: [PATCH] added selectors core module --- core/CMakeLists.txt | 58 ++++---- core/selectors.c | 319 ++++++++++++++++++++++++++++++++++++++++++ core/selectors.h | 13 ++ docs/core-modules.md | 164 +++++++++++++++++++++- docs/core-modules.txt | 15 ++ examples/selectors.hk | 7 + 6 files changed, 549 insertions(+), 27 deletions(-) create mode 100644 core/selectors.c create mode 100644 core/selectors.h create mode 100644 examples/selectors.hk diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 341711c..a0206c4 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -63,34 +63,40 @@ add_library(ini_mod SHARED ini.c deps/ini.c) -target_link_libraries(math_mod ${STATIC_LIB_TARGET}) -target_link_libraries(os_mod ${STATIC_LIB_TARGET}) -target_link_libraries(io_mod ${STATIC_LIB_TARGET}) -target_link_libraries(numbers_mod ${STATIC_LIB_TARGET}) -target_link_libraries(strings_mod ${STATIC_LIB_TARGET}) -target_link_libraries(arrays_mod ${STATIC_LIB_TARGET}) -target_link_libraries(utf8_mod ${STATIC_LIB_TARGET}) -target_link_libraries(hashing_mod ${STATIC_LIB_TARGET}) -target_link_libraries(encoding_mod ${STATIC_LIB_TARGET}) -target_link_libraries(socket_mod ${STATIC_LIB_TARGET}) -target_link_libraries(json_mod ${STATIC_LIB_TARGET}) -target_link_libraries(lists_mod ${STATIC_LIB_TARGET}) -target_link_libraries(ini_mod ${STATIC_LIB_TARGET}) +add_library(selectors_mod SHARED + selectors.c) + +target_link_libraries(math_mod ${STATIC_LIB_TARGET}) +target_link_libraries(os_mod ${STATIC_LIB_TARGET}) +target_link_libraries(io_mod ${STATIC_LIB_TARGET}) +target_link_libraries(numbers_mod ${STATIC_LIB_TARGET}) +target_link_libraries(strings_mod ${STATIC_LIB_TARGET}) +target_link_libraries(arrays_mod ${STATIC_LIB_TARGET}) +target_link_libraries(utf8_mod ${STATIC_LIB_TARGET}) +target_link_libraries(hashing_mod ${STATIC_LIB_TARGET}) +target_link_libraries(encoding_mod ${STATIC_LIB_TARGET}) +target_link_libraries(socket_mod ${STATIC_LIB_TARGET}) +target_link_libraries(json_mod ${STATIC_LIB_TARGET}) +target_link_libraries(lists_mod ${STATIC_LIB_TARGET}) +target_link_libraries(ini_mod ${STATIC_LIB_TARGET}) +target_link_libraries(selectors_mod ${STATIC_LIB_TARGET}) if(WIN32) target_link_libraries(socket_mod ws2_32) + target_link_libraries(selectors_mod ws2_32) endif() -set_target_properties(math_mod PROPERTIES PREFIX "") -set_target_properties(os_mod PROPERTIES PREFIX "") -set_target_properties(io_mod PROPERTIES PREFIX "") -set_target_properties(numbers_mod PROPERTIES PREFIX "") -set_target_properties(strings_mod PROPERTIES PREFIX "") -set_target_properties(arrays_mod PROPERTIES PREFIX "") -set_target_properties(utf8_mod PROPERTIES PREFIX "") -set_target_properties(hashing_mod PROPERTIES PREFIX "") -set_target_properties(encoding_mod PROPERTIES PREFIX "") -set_target_properties(socket_mod PROPERTIES PREFIX "") -set_target_properties(json_mod PROPERTIES PREFIX "") -set_target_properties(lists_mod PROPERTIES PREFIX "") -set_target_properties(ini_mod PROPERTIES PREFIX "") +set_target_properties(math_mod PROPERTIES PREFIX "") +set_target_properties(os_mod PROPERTIES PREFIX "") +set_target_properties(io_mod PROPERTIES PREFIX "") +set_target_properties(numbers_mod PROPERTIES PREFIX "") +set_target_properties(strings_mod PROPERTIES PREFIX "") +set_target_properties(arrays_mod PROPERTIES PREFIX "") +set_target_properties(utf8_mod PROPERTIES PREFIX "") +set_target_properties(hashing_mod PROPERTIES PREFIX "") +set_target_properties(encoding_mod PROPERTIES PREFIX "") +set_target_properties(socket_mod PROPERTIES PREFIX "") +set_target_properties(json_mod PROPERTIES PREFIX "") +set_target_properties(lists_mod PROPERTIES PREFIX "") +set_target_properties(ini_mod PROPERTIES PREFIX "") +set_target_properties(selectors_mod PROPERTIES PREFIX "") diff --git a/core/selectors.c b/core/selectors.c new file mode 100644 index 0000000..fa2d8f3 --- /dev/null +++ b/core/selectors.c @@ -0,0 +1,319 @@ +// +// The Hook Programming Language +// selectors.c +// + +#include "selectors.h" + +#ifdef _WIN32 + #include +#else + #include +#endif + +#ifdef _WIN32 + #define Socket SOCKET + #define PollFd WSAPOLLFD +#else + #define Socket int + #define PollFd struct pollfd +#endif + +#define MAX_FDS (4096) + +typedef struct +{ + HK_USERDATA_HEADER + int fld0; + int fld1; + int fld2; + Socket sock; +} SocketUserdata; + +typedef struct +{ + HK_USERDATA_HEADER + int count; + PollFd fds[MAX_FDS]; + SocketUserdata *udatas[MAX_FDS]; +} PollSelector; + +#ifdef _WIN32 + static int initialized = 0; +#endif + +#ifdef _WIN32 + static inline void startup(void); + static inline void cleanup(void); +#endif + +static inline int socket_poll(PollFd *fds, int nfds, int timeout); +static inline PollSelector *poll_selector_new(void); +static inline bool poll_selector_register(PollSelector *selector, + SocketUserdata *udata, int events); +static inline bool poll_selector_unregister(PollSelector *selector, + SocketUserdata *udata); +static inline bool poll_selector_modify(PollSelector *selector, + SocketUserdata *udata, int events); +static inline HkArray *poll_selector_poll(PollSelector *selector, int timeout); +static void poll_selector_deinit(HkUserdata *udata); +static void new_poll_selector_call(HkState *state, HkValue *args); +static void register_call(HkState *state, HkValue *args); +static void unregister_call(HkState *state, HkValue *args); +static void modify_call(HkState *state, HkValue *args); +static void poll_call(HkState *state, HkValue *args); + +#ifdef _WIN32 + static inline void startup(void) + { + if (!initialized) + { + WSADATA wsa; + int rc = WSAStartup(MAKEWORD(2, 2), &wsa); + assert(!rc); + (void) rc; + } + ++initialized; + } + + static inline void cleanup(void) + { + --initialized; + if (initialized) return; + int rc = WSACleanup(); + assert(!rc); + (void) rc; + } +#endif + +static inline int socket_poll(PollFd *fds, int nfds, int timeout) +{ +#ifdef _WIN32 + return WSAPoll(fds, nfds, timeout); +#else + return poll(fds, nfds, timeout); +#endif +} + +static inline PollSelector *poll_selector_new(void) +{ +#ifdef _WIN32 + startup(); +#endif + PollSelector *selector = (PollSelector *) hk_allocate(sizeof(*selector)); + hk_userdata_init((HkUserdata *) selector, poll_selector_deinit); + selector->count = 0; + return selector; +} + +static inline bool poll_selector_register(PollSelector *selector, + SocketUserdata *udata, int events) +{ + if (selector->count == MAX_FDS) return false; + PollFd fd = { + .fd = (int) udata->sock, + .events = events, + .revents = 0 + }; + int index = selector->count; + selector->fds[index] = fd; + selector->udatas[index] = udata; + ++selector->count; + return true; +} + +static inline bool poll_selector_unregister(PollSelector *selector, + SocketUserdata *udata) +{ + int i = 1; + int n = selector->count; + for (; i < n; ++i) + { + PollFd *fd = &selector->fds[i]; + if (fd->fd == (int) udata->sock) break; + } + if (i == n) return false; + for (; i < n - 1; ++i) + { + selector->fds[i] = selector->fds[i + 1]; + selector->udatas[i] = selector->udatas[i + 1]; + } + --selector->count; + return true; +} + +static inline bool poll_selector_modify(PollSelector *selector, + SocketUserdata *udata, int events) +{ + int i = 1; + int n = selector->count; + for (; i < n; ++i) + { + PollFd *fd = &selector->fds[i]; + if (fd->fd == (int) udata->sock) break; + } + if (i == n) return false; + selector->fds[i].events = events; + return true; +} + +static inline HkArray *poll_selector_poll(PollSelector *selector, int timeout) +{ + HkArray *arr = hk_array_new(); + int n = selector->count; + int rc = socket_poll(selector->fds, n, timeout); + if (rc == -1) + { + hk_array_free(arr); + return NULL; + } + if (!rc) goto end; + int j = 0; + for (int i = 0; i < n && j < rc; ++i) + { + PollFd *fd = &selector->fds[i]; + int revents = fd->revents; + if (!revents) continue; + HkUserdata *udata = (HkUserdata *) selector->udatas[i]; + HkArray *event = hk_array_new_with_capacity(2); + hk_array_inplace_add_element(event, hk_userdata_value(udata)); + hk_array_inplace_add_element(event, hk_number_value(revents)); + hk_array_inplace_add_element(arr, hk_array_value(event)); + ++j; + } +end: + return arr; +} + +static void poll_selector_deinit(HkUserdata *udata) +{ + (void) udata; +#ifdef _WIN32 + cleanup(); +#endif +} + +static void new_poll_selector_call(HkState *state, HkValue *args) +{ + (void) args; + hk_state_push_userdata(state, (HkUserdata *) poll_selector_new()); +} + +static void register_call(HkState *state, HkValue *args) +{ + hk_state_check_argument_userdata(state, args, 1); + hk_return_if_not_ok(state); + hk_state_check_argument_userdata(state, args, 2); + hk_return_if_not_ok(state); + hk_state_check_argument_int(state, args, 3); + hk_return_if_not_ok(state); + PollSelector *selector = (PollSelector *) hk_as_userdata(args[1]); + SocketUserdata *udata = (SocketUserdata *) hk_as_userdata(args[2]); + int events = (int) hk_as_number(args[3]); + if (!poll_selector_register(selector, udata, events)) + { + hk_state_runtime_error(state, "too many file descriptors"); + return; + } + hk_state_push_nil(state); +} + +static void unregister_call(HkState *state, HkValue *args) +{ + hk_state_check_argument_userdata(state, args, 1); + hk_return_if_not_ok(state); + hk_state_check_argument_userdata(state, args, 2); + hk_return_if_not_ok(state); + PollSelector *selector = (PollSelector *) hk_as_userdata(args[1]); + SocketUserdata *udata = (SocketUserdata *) hk_as_userdata(args[2]); + if (!poll_selector_unregister(selector, udata)) + { + hk_state_runtime_error(state, "file descriptor not found"); + return; + } + hk_state_push_nil(state); +} + +static void modify_call(HkState *state, HkValue *args) +{ + hk_state_check_argument_userdata(state, args, 1); + hk_return_if_not_ok(state); + hk_state_check_argument_userdata(state, args, 2); + hk_return_if_not_ok(state); + hk_state_check_argument_int(state, args, 3); + hk_return_if_not_ok(state); + PollSelector *selector = (PollSelector *) hk_as_userdata(args[1]); + SocketUserdata *udata = (SocketUserdata *) hk_as_userdata(args[2]); + int events = (int) hk_as_number(args[3]); + if (!poll_selector_modify(selector, udata, events)) + { + hk_state_runtime_error(state, "file descriptor not found"); + return; + } + hk_state_push_nil(state); +} + +static void poll_call(HkState *state, HkValue *args) +{ + hk_state_check_argument_userdata(state, args, 1); + hk_return_if_not_ok(state); + hk_state_check_argument_number(state, args, 2); + hk_return_if_not_ok(state); + PollSelector *selector = (PollSelector *) hk_as_userdata(args[1]); + int timeout = (int) hk_as_number(args[2]); + HkArray *arr = poll_selector_poll(selector, timeout); + hk_state_push_array(state, arr); + if (!hk_state_is_ok(state)) + hk_array_free(arr); +} + +HK_LOAD_MODULE_HANDLER(selectors) +{ + hk_state_push_string_from_chars(state, -1, "selectors"); + hk_return_if_not_ok(state); + hk_state_push_string_from_chars(state, -1, "POLLIN"); + hk_return_if_not_ok(state); + hk_state_push_number(state, POLLIN); + hk_return_if_not_ok(state); + hk_state_push_string_from_chars(state, -1, "POLLOUT"); + hk_return_if_not_ok(state); + hk_state_push_number(state, POLLOUT); + hk_return_if_not_ok(state); + hk_state_push_string_from_chars(state, -1, "POLLERR"); + hk_return_if_not_ok(state); + hk_state_push_number(state, POLLERR); + hk_return_if_not_ok(state); + hk_state_push_string_from_chars(state, -1, "POLLHUP"); + hk_return_if_not_ok(state); + hk_state_push_number(state, POLLHUP); + hk_return_if_not_ok(state); + hk_state_push_string_from_chars(state, -1, "POLLNVAL"); + hk_return_if_not_ok(state); + hk_state_push_number(state, POLLNVAL); + hk_return_if_not_ok(state); + hk_state_push_string_from_chars(state, -1, "POLLPRI"); + hk_return_if_not_ok(state); + hk_state_push_number(state, POLLPRI); + hk_return_if_not_ok(state); + hk_state_push_string_from_chars(state, -1, "new_poll_selector"); + hk_return_if_not_ok(state); + hk_state_push_new_native(state, "new_poll_selector", 0, new_poll_selector_call); + hk_return_if_not_ok(state); + hk_state_push_string_from_chars(state, -1, "register"); + hk_return_if_not_ok(state); + hk_state_push_new_native(state, "register", 3, register_call); + hk_return_if_not_ok(state); + hk_state_push_string_from_chars(state, -1, "unregister"); + hk_return_if_not_ok(state); + hk_state_push_new_native(state, "unregister", 2, unregister_call); + hk_return_if_not_ok(state); + hk_state_push_string_from_chars(state, -1, "modify"); + hk_return_if_not_ok(state); + hk_state_push_new_native(state, "modify", 3, modify_call); + hk_return_if_not_ok(state); + hk_state_push_string_from_chars(state, -1, "poll"); + hk_return_if_not_ok(state); + hk_state_push_new_native(state, "poll", 2, poll_call); + hk_return_if_not_ok(state); + hk_state_construct(state, 11); +} diff --git a/core/selectors.h b/core/selectors.h new file mode 100644 index 0000000..6666769 --- /dev/null +++ b/core/selectors.h @@ -0,0 +1,13 @@ +// +// The Hook Programming Language +// selectors.h +// + +#ifndef SELECTORS_H +#define SELECTORS_H + +#include + +HK_LOAD_MODULE_HANDLER(selectors); + +#endif // SELECTORS_H diff --git a/docs/core-modules.md b/docs/core-modules.md index 813c8c1..43b4f4d 100644 --- a/docs/core-modules.md +++ b/docs/core-modules.md @@ -49,7 +49,7 @@ Below is a comprehensive list of all the modules available. Click on any module json lists ini - + selectors @@ -362,6 +362,7 @@ The `os` module provides a variety of functions and constants that allow you to clock time system + getenv getcwd @@ -2129,3 +2130,164 @@ Example: let config = ini.load("config.ini"); println(ini.get(config, "section", "key")); // value ``` + +### selectors + +The `selectors` module provides a wrapper around the `select`, `poll`, etc. system calls for monitoring multiple file descriptors, specially useful for network programming. + + + + + + + + + + + + + + + + + + + + + + + + +
POLLINPOLLOUTPOLLERR
POLLHUPPOLLNVALPOLLPRI
new_poll_selectorregisterunregister
modifypoll
+ +#### POLLIN + +The `POLLIN` constant is used to specify that data is available for reading. + +```rust +let POLLIN: number; +``` + +#### POLLOUT + +The `POLLOUT` constant is used to specify that data can be written without blocking. + +```rust +let POLLOUT: number; +``` + +#### POLLERR + +The `POLLERR` constant is used to specify that an error has occurred. + +```rust +let POLLERR: number; +``` + +#### POLLHUP + +The `POLLHUP` constant is used to specify that the connection has been closed. + +```rust +let POLLHUP: number; +``` + +#### POLLNVAL + +The `POLLNVAL` constant is used to specify that the file descriptor is invalid. + +```rust +let POLLNVAL: number; +``` + +#### POLLPRI + +The `POLLPRI` constant is used to specify that urgent data is available. + +```rust +let POLLPRI: number; +``` + +#### new_poll_selector + +Creates a selector of type `poll`. + +```rust +fn new_poll_selector() -> userdata; +``` + +Example: + +```rust +let selector = selectors.new_poll_selector(); +``` + +#### register + +Registers the given file descriptor with the given events. + +```rust +fn register(selector: userdata, fd: number, events: number); +``` + +- `selector` is the selector to register the file descriptor with. +- `fd` is the file descriptor to register. +- `events` is the events to monitor. It can be a combination of `POLLIN`, `POLLOUT`, `POLLERR`, `POLLHUP`, `POLLNVAL`, and `POLLPRI`. + +Example: + +```rust +let selector = selectors.new_poll_selector(); +selectors.register(selector, sock, selectors.POLLIN); +``` + +#### unregister + +Unregisters the given file descriptor. + +```rust +fn unregister(selector: userdata, fd: number); +``` + +Example: + +```rust +let selector = selectors.new_poll_selector(); +selectors.register(selector, sock, selectors.POLLIN); +selectors.unregister(selector, sock); +``` + +#### modify + +Modifies the events to monitor for the given file descriptor. + +```rust +fn modify(selector: userdata, fd: number, events: number); +``` + +Example: + +```rust +let selector = selectors.new_poll_selector(); +selectors.register(selector, sock, selectors.POLLIN); +selectors.modify(selector, sock, selectors.POLLOUT); +``` + +#### poll + +Waits for events on the registered file descriptors. + +```rust +fn poll(selector: userdata, timeout: number) -> array; +``` + +- `selector` is the selector to wait for events on. +- `timeout` is the maximum time to wait in milliseconds. If `timeout` is `0`, the function will return immediately. If `timeout` is `-1`, the function will block indefinitely. + +Example: + +```rust +let selector = selectors.new_poll_selector(); +selectors.register(selector, sock, selectors.POLLIN); +let events = selectors.poll(selector, 1000); +``` diff --git a/docs/core-modules.txt b/docs/core-modules.txt index e2aa742..b47949c 100644 --- a/docs/core-modules.txt +++ b/docs/core-modules.txt @@ -165,3 +165,18 @@ ini: load(filename: string) -> userdata get(config: userdata, section: string, key: string) -> string + +selectors: + + POLLIN: number + POLLOUT: number + POLLERR: number + POLLHUP: number + POLLNVAL: number + POLLPRI: number + + new_poll_selector() -> userdata + register(selector: userdata, sock: userdata, events: number) + unregister(selector: userdata, sock: userdata) + modify(selector: userdata, sock: userdata, events: number) + poll(selector: userdata, timeout: number) -> array diff --git a/examples/selectors.hk b/examples/selectors.hk new file mode 100644 index 0000000..1456386 --- /dev/null +++ b/examples/selectors.hk @@ -0,0 +1,7 @@ +// +// selectors.hk +// + +import selectors; + +println(selectors);