From a6b54dc818b4c09a4935b9523c8fcc4cf76d3c20 Mon Sep 17 00:00:00 2001 From: Anotra Date: Thu, 24 Mar 2022 13:25:40 -0400 Subject: [PATCH 01/15] feat(third_party): add priority_queue --- Makefile | 3 +- core/third-party/priority_queue.c | 310 ++++++++++++++++++++++++++++++ core/third-party/priority_queue.h | 59 ++++++ licenses/LICENSE.priority_queue | 23 +++ 4 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 core/third-party/priority_queue.c create mode 100644 core/third-party/priority_queue.h create mode 100644 licenses/LICENSE.priority_queue diff --git a/Makefile b/Makefile index 106cd414e..47844e048 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,8 @@ CORE_OBJS = $(OBJDIR)/$(CORE_DIR)/work.o \ $(OBJDIR)/$(CORE_DIR)/io_poller.o THIRDP_OBJS = $(OBJDIR)/$(THIRDP_DIR)/sha1.o \ $(OBJDIR)/$(THIRDP_DIR)/curl-websocket.o \ - $(OBJDIR)/$(THIRDP_DIR)/threadpool.o + $(OBJDIR)/$(THIRDP_DIR)/threadpool.o \ + $(OBJDIR)/$(THIRDP_DIR)/priority_queue.o DISCORD_OBJS = $(OBJDIR)/$(SRC_DIR)/concord-once.o \ $(OBJDIR)/$(SRC_DIR)/discord-adapter.o \ $(OBJDIR)/$(SRC_DIR)/discord-ratelimit.o \ diff --git a/core/third-party/priority_queue.c b/core/third-party/priority_queue.c new file mode 100644 index 000000000..1772c3858 --- /dev/null +++ b/core/third-party/priority_queue.c @@ -0,0 +1,310 @@ + +// MIT License +// Copyright (c) 2022 Anotra +// https://github.com/Anotra/priority_queue + +#include +#include + +#include "priority_queue.h" + +#define queue_CMP(result, queue, a, b) \ + do { \ + result = queue->cmp(a, b); \ + if (queue->max_queue) { \ + switch (result) { \ + case -1: result = 1; break; \ + case 0: result = 0; break; \ + case 1: result = -1; break; \ + default: result = result > 0 ? -1 : 1; \ + } \ + } \ + } while (0) + +struct priority_queue { + struct { + priority_queue_id *arr; + size_t cap; + size_t len; + } queue; + struct { + struct { + priority_queue_id position; + } *info; + struct { + char *arr; + size_t size; + } keys; + struct { + char *arr; + size_t size; + } vals; + size_t cap; + size_t len; + size_t max; + } elements; + int(*cmp)(const void *a, const void *b); + int max_queue; + priority_queue_id scan_start; +}; + +priority_queue * +priority_queue_create( + size_t key_size, size_t val_size, + int(*cmp)(const void *a, const void *b), + priority_queue_flags flags) +{ + priority_queue *queue = calloc(1, sizeof *queue); + if (queue) { + if (flags & priority_queue_max) + queue->max_queue = 1; + queue->elements.keys.size = key_size; + queue->elements.vals.size = val_size; + queue->elements.max = UINT32_MAX - 2; + queue->cmp = cmp; + queue->queue.len = 1; + queue->queue.arr = calloc((queue->queue.cap = 0x400), sizeof *queue->queue.arr); + if (queue->queue.arr) + return queue; + free(queue); + } + return NULL; +} + + +void +priority_queue_destroy(priority_queue *queue) { + free(queue->queue.arr); + free(queue->elements.info); + free(queue->elements.keys.arr); + free(queue->elements.vals.arr); + free(queue); +} + +size_t +priority_queue_length(priority_queue *queue) { + return queue->queue.len - 1; +} + +void +priority_queue_set_max_capacity(priority_queue *queue, size_t capacity) { + queue->elements.max = capacity; +} + +static void +priority_queue_swap( + priority_queue *queue, + priority_queue_id a, priority_queue_id b) +{ + priority_queue_id ai = queue->queue.arr[a]; + priority_queue_id bi = queue->queue.arr[b]; + queue->queue.arr[a] = bi; + queue->queue.arr[b] = ai; + queue->elements.info[ai].position = b; + queue->elements.info[bi].position = a; +} + +static void +priority_queue_bubble_down(priority_queue *queue, priority_queue_id pos) { + const size_t key_size = queue->elements.keys.size; + while (pos < queue->queue.len) { + priority_queue_id lchild = pos << 1; + priority_queue_id rchild = (pos << 1) + 1; + + if (lchild >= queue->queue.len) + return; + + priority_queue_id successor = lchild; + + int cmp = -1; + if (rchild < queue->queue.len) { + queue_CMP(cmp, queue, + queue->elements.keys.arr + key_size * queue->queue.arr[lchild], + queue->elements.keys.arr + key_size * queue->queue.arr[rchild]); + if (cmp >= 0) + successor = rchild; + } + + queue_CMP(cmp, queue, + queue->elements.keys.arr + key_size * queue->queue.arr[pos], + queue->elements.keys.arr + key_size * queue->queue.arr[successor]); + if (cmp <= 0) + return; + priority_queue_swap(queue, pos, successor); + pos = successor; + } +} + +static void +priority_queue_bubble_up(priority_queue *queue, priority_queue_id pos) { + while (pos > 1) { + priority_queue_id par = pos >> 1; + priority_queue_id par_index = queue->queue.arr[par]; + priority_queue_id pos_index = queue->queue.arr[pos]; + + int cmp; + queue_CMP(cmp, queue, + queue->elements.keys.arr + queue->elements.keys.size * par_index, + queue->elements.keys.arr + queue->elements.keys.size * pos_index); + if (cmp < 0) + return; + priority_queue_swap(queue, par, pos); + pos = par; + } +} + +priority_queue_id +priority_queue_push(priority_queue *queue, void *key, void *val) { + if (!key) + return 0; + if (priority_queue_length(queue) >= queue->elements.max) + return 0; + + if (queue->elements.len == queue->elements.cap) { + size_t cap = queue->elements.cap ? queue->elements.cap << 1 : 0x40; + if (cap > queue->elements.max) + cap = queue->elements.max; + if (cap > queue->elements.max) + return 0; + void *tmp; + + tmp = realloc(queue->elements.info, cap * sizeof *queue->elements.info); + if (!tmp) return 0; + if (queue->elements.info) { + memset(tmp + queue->elements.cap * sizeof *queue->elements.info, + 0, (cap - queue->elements.cap) * sizeof *queue->elements.info); + } else { + memset(tmp, 0, cap * sizeof *queue->elements.info); + } + queue->elements.info = tmp; + + tmp = realloc(queue->elements.keys.arr, queue->elements.keys.size * cap); + if (!tmp) return 0; + queue->elements.keys.arr = tmp; + + if (queue->elements.vals.size) { + tmp = realloc(queue->elements.vals.arr, queue->elements.vals.size * cap); + if (!tmp) return 0; + queue->elements.vals.arr = tmp; + } + + queue->elements.cap = cap; + } + + if (queue->queue.len == queue->queue.cap) { + size_t cap = queue->queue.cap << 1; + void *tmp = realloc(queue->queue.arr, cap * sizeof *queue->queue.arr); + if (!tmp) return 0; + queue->queue.arr = tmp; + queue->queue.cap = cap; + } + + priority_queue_id id = queue->scan_start; + for (; queue->elements.info[id].position; id++); + queue->elements.len++; + queue->scan_start = id + 1; + + memcpy(queue->elements.keys.arr + queue->elements.keys.size * id, + key, queue->elements.keys.size); + + if (queue->elements.vals.size) { + if (val) { + memcpy(queue->elements.vals.arr + queue->elements.vals.size * id, + val, queue->elements.vals.size); + } else { + memset(queue->elements.vals.arr + queue->elements.vals.size * id, + 0, queue->elements.vals.size); + } + } + priority_queue_id pos = queue->queue.len++; + queue->queue.arr[pos] = id; + queue->elements.info[id].position = pos; + priority_queue_bubble_up(queue, pos); + + return id + 1; +} + +priority_queue_id +priority_queue_peek(priority_queue *queue, void *key, void *val) { + if (queue->queue.len == 1) + return 0; + priority_queue_id pos = queue->queue.arr[1]; + if (key) + memcpy(key, queue->elements.keys.arr + queue->elements.keys.size * pos, + queue->elements.keys.size); + if (val && queue->elements.vals.size) + memcpy(val, queue->elements.vals.arr + queue->elements.vals.size * pos, + queue->elements.vals.size); + return pos + 1; +} + +priority_queue_id +priority_queue_pop(priority_queue *queue, void *key, void *val) { + priority_queue_id id = priority_queue_peek(queue, key, val); + if (id) priority_queue_del(queue, id); + return id; +} + + +priority_queue_id +priority_queue_get( + priority_queue *queue, + priority_queue_id id, + void *key, void *val) { + id--; + if (id >= queue->elements.len || !queue->elements.info[id].position) + return 0; + priority_queue_id pos = queue->queue.arr[queue->elements.info[id].position]; + if (key) + memcpy(key, queue->elements.keys.arr + queue->elements.keys.size * pos, + queue->elements.keys.size); + if (val && queue->elements.vals.size) + memcpy(val, queue->elements.vals.arr + queue->elements.vals.size * pos, + queue->elements.vals.size); + return id + 1; +} + +int +priority_queue_del(priority_queue *queue, priority_queue_id id) { + if (queue->queue.len == 1) + return 0; + id--; + if (id >= queue->elements.len || !queue->elements.info[id].position) + return 0; + if (queue->scan_start > id) + queue->scan_start = id; + priority_queue_id pos = queue->elements.info[id].position; + priority_queue_swap(queue, pos, --queue->queue.len); + queue->elements.info[queue->queue.arr[queue->queue.len]].position = 0; + priority_queue_bubble_up(queue, pos); + priority_queue_bubble_down(queue, pos); + return 1; +} + +int +priority_queue_update( + priority_queue *queue, priority_queue_id id, + void *key, void *val) +{ + if (queue->queue.len == 1) + return 0; + id--; + if (id >= queue->elements.len || !queue->elements.info[id].position) + return 0; + memcpy(queue->elements.keys.arr + queue->elements.keys.size * id, + key, queue->elements.keys.size); + + if (queue->elements.vals.size) { + if (val) { + memcpy(queue->elements.vals.arr + queue->elements.vals.size * id, + val, queue->elements.vals.size); + } else { + memset(queue->elements.vals.arr + queue->elements.vals.size * id, + 0, queue->elements.vals.size); + } + } + priority_queue_id pos = queue->elements.info[id].position; + priority_queue_bubble_up(queue, pos); + priority_queue_bubble_down(queue, pos); + return 1; +} \ No newline at end of file diff --git a/core/third-party/priority_queue.h b/core/third-party/priority_queue.h new file mode 100644 index 000000000..b9029bbc9 --- /dev/null +++ b/core/third-party/priority_queue.h @@ -0,0 +1,59 @@ + +// MIT License +// Copyright (c) 2022 Anotra +// https://github.com/Anotra/priority_queue + +#pragma once + +#ifndef PRIORITY_QUEUE_H +#define PRIORITY_QUEUE_H + +#include + +typedef struct priority_queue priority_queue; +typedef unsigned priority_queue_id; + +typedef enum { + priority_queue_min = 0, + priority_queue_max = 1, +} priority_queue_flags; + +priority_queue *priority_queue_create( + size_t key_size, size_t val_size, + int(*cmp)(const void *a, const void *b), + priority_queue_flags flags); + +void priority_queue_destroy(priority_queue *queue); + +size_t priority_queue_length(priority_queue *queue); + +void priority_queue_set_max_capacity( + priority_queue *queue, + size_t capacity); + +priority_queue_id priority_queue_push( + priority_queue *queue, + void *key, void *val); + +priority_queue_id priority_queue_peek( + priority_queue *queue, + void *key, void *val); + +priority_queue_id priority_queue_pop( + priority_queue *queue, + void *key, void *val); + +priority_queue_id priority_queue_get( + priority_queue *queue, + priority_queue_id id, + void *key, void *val); + +int priority_queue_del( + priority_queue *queue, + priority_queue_id id); + +int priority_queue_update(priority_queue *queue, + priority_queue_id id, + void *key, void *val); + +#endif //! PRIORITY_QUEUE_H \ No newline at end of file diff --git a/licenses/LICENSE.priority_queue b/licenses/LICENSE.priority_queue new file mode 100644 index 000000000..bb6aeb842 --- /dev/null +++ b/licenses/LICENSE.priority_queue @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2022 Anotra + +https://github.com/Anotra/priority_queue + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file From 5dcc700107e4ebc10a6dfa506c5b0b2652219621 Mon Sep 17 00:00:00 2001 From: Anotra Date: Thu, 24 Mar 2022 17:04:08 -0400 Subject: [PATCH 02/15] feat(discord-client): add discord_timestamp_us(microseconds) --- cog-utils/cog-utils.c | 11 +++++++++++ cog-utils/cog-utils.h | 7 +++++++ include/discord.h | 8 ++++++++ src/discord-client.c | 6 ++++++ 4 files changed, 32 insertions(+) diff --git a/cog-utils/cog-utils.c b/cog-utils/cog-utils.c index 3b7feeb0e..2c0ce902a 100644 --- a/cog-utils/cog-utils.c +++ b/cog-utils/cog-utils.c @@ -226,6 +226,17 @@ cog_timestamp_ms(void) return 0; } +/* returns current timestamp in microseconds */ +uint64_t +cog_timestamp_us(void) +{ + struct PsnipClockTimespec t; + if (0 == psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &t)) { + return (uint64_t)t.seconds * 1000000 + (uint64_t)t.nanoseconds / 1000; + } + return 0; +} + /* this can be used for checking if a user-given string does not * exceeds a arbitrary threshold length */ size_t diff --git a/cog-utils/cog-utils.h b/cog-utils/cog-utils.h index 4a80bc139..0eed6f06a 100644 --- a/cog-utils/cog-utils.h +++ b/cog-utils/cog-utils.h @@ -155,6 +155,13 @@ int cog_sleep_ms(const long tms); */ uint64_t cog_timestamp_ms(void); +/** + * @brief Get the current timestamp in microseconds + * + * @return the timestamp on success, 0 on failure + */ +uint64_t cog_timestamp_us(void); + /** * @brief Check if arbitrary string length is exceeded * diff --git a/include/discord.h b/include/discord.h index df7a1ce25..4ab70b8b0 100644 --- a/include/discord.h +++ b/include/discord.h @@ -257,6 +257,14 @@ int discord_get_ping(struct discord *client); */ uint64_t discord_timestamp(struct discord *client); +/** + * @brief Get the current timestamp (in microseconds) + * + * @param client the client created with discord_init() + * @return the timestamp in microseconds + */ +uint64_t discord_timestamp_us(struct discord *client); + /** * @brief Retrieve client's logging module for configuration purposes * @see logconf_setup(), logconf_set_quiet(), logconf_set_level() diff --git a/src/discord-client.c b/src/discord-client.c index 17fe21c19..c0fd38ce3 100644 --- a/src/discord-client.c +++ b/src/discord-client.c @@ -686,6 +686,12 @@ discord_timestamp(struct discord *client) (void)client; return cog_timestamp_ms(); } +uint64_t +discord_timestamp_us(struct discord *client) +{ + (void)client; + return cog_timestamp_us(); +} struct logconf * discord_get_logconf(struct discord *client) From 536e8fd476c67f8e565f0ce4439759d61fff6aa1 Mon Sep 17 00:00:00 2001 From: Anotra Date: Thu, 24 Mar 2022 17:30:27 -0400 Subject: [PATCH 03/15] feat(timers): add timers support --- Makefile | 1 + include/discord-internal.h | 19 ++++++ include/discord.h | 29 +++++++++ src/discord-client.c | 18 +++++- src/discord-timer.c | 122 +++++++++++++++++++++++++++++++++++++ 5 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 src/discord-timer.c diff --git a/Makefile b/Makefile index 47844e048..9708a5185 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,7 @@ DISCORD_OBJS = $(OBJDIR)/$(SRC_DIR)/concord-once.o \ $(OBJDIR)/$(SRC_DIR)/discord-ratelimit.o \ $(OBJDIR)/$(SRC_DIR)/discord-client.o \ $(OBJDIR)/$(SRC_DIR)/discord-gateway.o \ + $(OBJDIR)/$(SRC_DIR)/discord-timer.o \ $(OBJDIR)/$(SRC_DIR)/discord-misc.o \ $(OBJDIR)/$(SRC_DIR)/application_command.o \ $(OBJDIR)/$(SRC_DIR)/interaction.o \ diff --git a/include/discord-internal.h b/include/discord-internal.h index bf92d49db..182ce4128 100644 --- a/include/discord-internal.h +++ b/include/discord-internal.h @@ -28,6 +28,7 @@ #include "uthash.h" #include "queue.h" #include "heap-inl.h" +#include "priority_queue.h" /** @brief Return 1 if string isn't considered empty */ #define NOT_EMPTY_STR(str) ((str) && *(str)) @@ -684,6 +685,19 @@ void discord_gateway_send_presence_update(struct discord_gateway *gw); /** @} DiscordInternalGateway */ +struct discord_timers { + priority_queue *q; +}; + + +void discord_timers_init(struct discord *client); +void discord_timers_cleanup(struct discord *client); +void discord_timers_run(struct discord *client, struct discord_timers *timer); +unsigned _discord_timer_ctl( + struct discord *client, + struct discord_timers *timers, + struct discord_timer *timer); + /** * @brief The Discord client handler * @@ -707,6 +721,11 @@ struct discord { /** the client's user structure */ struct discord_user self; + struct { + struct discord_timers internal; + struct discord_timers user; + } timers; + /** wakeup timer handle */ struct { /** callback to be triggered on timer's timeout */ diff --git a/include/discord.h b/include/discord.h index 4ab70b8b0..ebdc4de71 100644 --- a/include/discord.h +++ b/include/discord.h @@ -282,6 +282,35 @@ struct logconf *discord_get_logconf(struct discord *client); */ struct io_poller *discord_get_io_poller(struct discord *client); +struct discord_timer; +typedef void (*discord_ev_timer) + (struct discord *client, struct discord_timer *ev); + +enum discord_timer_flags { + DISCORD_TIMER_MILLISECONDS = 0, + DISCORD_TIMER_MICROSECONDS = 1 << 0, + DISCORD_TIMER_DELETE = 1 << 1, + DISCORD_TIMER_DELETE_AUTO = 1 << 2, + DISCORD_TIMER_CANCELED = 1 << 3, +}; + +struct discord_timer { + unsigned id; + discord_ev_timer cb; + void *data; + int64_t start_after; + int64_t interval; + int64_t repeat; + int flags; +}; + + +unsigned discord_timer_ctl(struct discord *client, struct discord_timer *timer); + + +unsigned discord_timer(struct discord *client, discord_ev_timer cb, + void *data, int64_t start_after); + /** @} Discord */ #endif /* DISCORD_H */ diff --git a/src/discord-client.c b/src/discord-client.c index c0fd38ce3..52efd1926 100644 --- a/src/discord-client.c +++ b/src/discord-client.c @@ -12,7 +12,7 @@ static void _discord_init(struct discord *new_client) { ccord_global_init(); - + discord_timers_init(new_client); new_client->io_poller = io_poller_create(); discord_adapter_init(&new_client->adapter, &new_client->conf, &new_client->token); @@ -97,6 +97,7 @@ void discord_cleanup(struct discord *client) { if (client->is_original) { + discord_timers_cleanup(client); logconf_cleanup(&client->conf); discord_adapter_cleanup(&client->adapter); discord_gateway_cleanup(&client->gw); @@ -339,7 +340,18 @@ discord_run(struct discord *client) poll_time = (int)(client->wakeup_timer.next - now); } } - + int64_t key; + if (priority_queue_peek(client->timers.user.q, &key, NULL)) { + key /= 1000; + if (key >= 0) { + if (key >= now) { + poll_time = 0; + } else if (key - now > poll_time) { + poll_time = (int)(key - now); + } + } + } + poll_result = io_poller_poll(client->io_poller, poll_time); if (-1 == poll_result) { /* TODO: handle poll error here */ @@ -355,6 +367,8 @@ discord_run(struct discord *client) break; now = (int64_t)cog_timestamp_ms(); + discord_timers_run(client, &client->timers.internal); + discord_timers_run(client, &client->timers.user); /* check for pending wakeup timers */ if (client->wakeup_timer.next != -1 diff --git a/src/discord-timer.c b/src/discord-timer.c new file mode 100644 index 000000000..0b0b9c7be --- /dev/null +++ b/src/discord-timer.c @@ -0,0 +1,122 @@ + +#include "discord.h" +#include "discord-internal.h" + +static int +cmp_timers(const void *a, const void *b) +{ + const int64_t l = *(int64_t *)a; + const int64_t r = *(int64_t *)b; + if (l == r || (l < 0 && r < 0)) + return 0; + if (l < 0) return 1; + if (r < 0) return -1; + return l > r ? 1 : -1; +} + +void +discord_timers_init(struct discord *client) +{ + client->timers.internal.q = + priority_queue_create(sizeof(int64_t), sizeof(struct discord_timer), + cmp_timers, 0); + client->timers.user.q = + priority_queue_create(sizeof(int64_t), sizeof(struct discord_timer), + cmp_timers, 0); +} + +static void +discord_timers_cancel_all(struct discord *client, priority_queue *q) +{ + struct discord_timer timer; + while ((timer.id = priority_queue_pop(q, NULL, &timer))) { + timer.flags |= DISCORD_TIMER_CANCELED; + if (timer.cb) timer.cb(client, &timer); + } +} + +void +discord_timers_cleanup(struct discord *client) +{ + discord_timers_cancel_all(client, client->timers.user.q); + priority_queue_destroy(client->timers.user.q); + + discord_timers_cancel_all(client, client->timers.internal.q); + priority_queue_destroy(client->timers.internal.q); +} + +unsigned +_discord_timer_ctl( + struct discord *client, + struct discord_timers *timers, + struct discord_timer *timer) +{ + int64_t now = -1; + if (timer->start_after >= 0) + now = (int64_t)discord_timestamp_us(client) + + ((timer->flags & DISCORD_TIMER_MICROSECONDS) + ? timer->start_after : timer->start_after * 1000); + if (!timer->id) { + return priority_queue_push(timers->q, &now, timer); + } else { + if (priority_queue_update(timers->q, timer->id, &now, &timer)) + return timer->id; + return 0; + } +} +#define TIMER_TRY_DELETE \ + if (timer.flags & DISCORD_TIMER_DELETE && timer.repeat == 0) { \ + priority_queue_pop(timers->q, NULL, NULL); \ + continue; \ + } +void +discord_timers_run(struct discord *client, struct discord_timers *timers) +{ + int64_t now = (int64_t)discord_timestamp_us(client); + struct discord_timer timer; + for (int64_t trigger; + (timer.id = priority_queue_peek(timers->q, &trigger, &timer));) + { + if (trigger > now || trigger == -1) return; + + TIMER_TRY_DELETE + + if (timer.flags & DISCORD_TIMER_DELETE_AUTO) { + timer.flags |= DISCORD_TIMER_DELETE; + priority_queue_update(timers->q, timer.id, &now, &timer); + } + + if (timer.cb) timer.cb(client, &timer); + TIMER_TRY_DELETE + + int64_t next = -1; + if (timer.repeat != 0) { + if (timer.repeat > 0) + timer.repeat--; + if (timer.interval > 0) + next = now + ((timer.flags & DISCORD_TIMER_MICROSECONDS) + ? timer.interval : timer.interval * 1000); + } + if (priority_queue_peek(timers->q, NULL, NULL) != timer.id) + continue; + + priority_queue_update(timers->q, timer.id, &next, &timer); + } +} +unsigned +discord_timer_ctl(struct discord *client, struct discord_timer *timer) +{ + return _discord_timer_ctl(client, &client->timers.user, timer); +} + +unsigned discord_timer(struct discord *client, discord_ev_timer cb, + void *data, int64_t start_after) +{ + struct discord_timer timer = { + .cb = cb, + .data = data, + .start_after = start_after, + .flags = DISCORD_TIMER_DELETE_AUTO, + }; + return discord_timer_ctl(client, &timer); +} From 09b262facccedba8eed768035c096a7cba333169 Mon Sep 17 00:00:00 2001 From: Anotra Date: Fri, 25 Mar 2022 12:36:41 -0400 Subject: [PATCH 04/15] Update include/discord.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lucas Müller --- include/discord.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/discord.h b/include/discord.h index ebdc4de71..c54cbb05c 100644 --- a/include/discord.h +++ b/include/discord.h @@ -282,6 +282,10 @@ struct logconf *discord_get_logconf(struct discord *client); */ struct io_poller *discord_get_io_poller(struct discord *client); +/** @defgroup DiscordTimer Timer + * @brief Schedule callbacks to be called in the future + * @{ */ + struct discord_timer; typedef void (*discord_ev_timer) (struct discord *client, struct discord_timer *ev); @@ -310,7 +314,8 @@ unsigned discord_timer_ctl(struct discord *client, struct discord_timer *timer); unsigned discord_timer(struct discord *client, discord_ev_timer cb, void *data, int64_t start_after); - + +/** @} DiscordTimer */ /** @} Discord */ #endif /* DISCORD_H */ From 5c35d67c1c749e8218ed454502fec740cf9aa0ec Mon Sep 17 00:00:00 2001 From: Anotra Date: Fri, 25 Mar 2022 13:57:07 -0400 Subject: [PATCH 05/15] docs(timers): added documentation for using timers --- include/discord-internal.h | 30 +++++++++++++++++++++++++-- include/discord.h | 42 +++++++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/include/discord-internal.h b/include/discord-internal.h index 182ce4128..f78faef06 100644 --- a/include/discord-internal.h +++ b/include/discord-internal.h @@ -689,10 +689,36 @@ struct discord_timers { priority_queue *q; }; - +/** + * @brief prepare timers for usage + * + * @param client the client created with discord_init() + */ void discord_timers_init(struct discord *client); + +/** + * @brief cleanup timers and call cancel any running ones + * + * @param client the client created with discord_init() + */ void discord_timers_cleanup(struct discord *client); -void discord_timers_run(struct discord *client, struct discord_timers *timer); + +/** + * @brief run all timers that are due + * + * @param client the client created with discord_init() + * @param timers the timers to run + */ +void discord_timers_run(struct discord *client, struct discord_timers *timers); + +/** + * @brief modifies or creates a timer + * + * @param client the client created with discord_init() + * @param timers the timer group to perform this operation on + * @param timer the timer that should be modified + * @return unsigned the id of the timer + */ unsigned _discord_timer_ctl( struct discord *client, struct discord_timers *timers, diff --git a/include/discord.h b/include/discord.h index c54cbb05c..e64ce20d9 100644 --- a/include/discord.h +++ b/include/discord.h @@ -285,33 +285,69 @@ struct io_poller *discord_get_io_poller(struct discord *client); /** @defgroup DiscordTimer Timer * @brief Schedule callbacks to be called in the future * @{ */ - + +/** + * @brief struct used for modifying, and getting info about a timer + */ struct discord_timer; + +/** + * @brief callback to be used with struct discord_timer + */ typedef void (*discord_ev_timer) (struct discord *client, struct discord_timer *ev); +/** + * @brief flags used to change behaviour of timer + */ enum discord_timer_flags { + /** use milliseconds for interval and start_time */ DISCORD_TIMER_MILLISECONDS = 0, + /** use microseconds for interval and start_time */ DISCORD_TIMER_MICROSECONDS = 1 << 0, + /** whether or not timer is marked for deletion */ DISCORD_TIMER_DELETE = 1 << 1, + /** automatically delete a timer once its repeat counter runs out */ DISCORD_TIMER_DELETE_AUTO = 1 << 2, + /** timer has been canceled. user should cleanup only */ DISCORD_TIMER_CANCELED = 1 << 3, }; struct discord_timer { + /** the identifier used for the timer. 0 creates a new timer */ unsigned id; + /** the callback that should be called when timer triggers */ discord_ev_timer cb; + /** user data */ void *data; + /** delay before timer should start */ int64_t start_after; + /** interval that the timer should repeat at. must be > 1 */ int64_t interval; + /** how many times a timer should repeat (-1 == infinity) */ int64_t repeat; int flags; }; - +/** + * @brief modifies or creates a timer + * + * @param client the client created with discord_init() + * @param timer the timer that should be modified + * @return unsigned the id of the timer + */ unsigned discord_timer_ctl(struct discord *client, struct discord_timer *timer); - +/** + * @brief creates a one shot timer that automatically + * deletes itself upon completion + * + * @param client the client created with discord_init() + * @param cb the callback that should be called when timer triggers + * @param data user data + * @param start_after delay before timer should start in milliseconds + * @return unsigned + */ unsigned discord_timer(struct discord *client, discord_ev_timer cb, void *data, int64_t start_after); From 3a09258ce0b8d7ce449a1dfa4f6cd95110996ed2 Mon Sep 17 00:00:00 2001 From: Anotra Date: Fri, 25 Mar 2022 14:30:55 -0400 Subject: [PATCH 06/15] docs(exampes timer.c): added example for using discord_timer --- examples/Makefile | 1 + examples/timers.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 examples/timers.c diff --git a/examples/Makefile b/examples/Makefile index 0fda4e31c..1d27ad638 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -29,6 +29,7 @@ BOTS := audit-log \ slash-commands2 \ spam \ webhook \ + timers \ $(XSRC) CFLAGS += -I$(INCLUDE_DIR) -I$(COGUTILS_DIR) -I$(CORE_DIR) \ diff --git a/examples/timers.c b/examples/timers.c new file mode 100644 index 000000000..5bb076152 --- /dev/null +++ b/examples/timers.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include + +#include "discord.h" + +static int interrupted = 0; + +static void +on_sigint(int sig) { + interrupted = 1; +} + +static void +one_shot_timer_cb(struct discord *client, struct discord_timer *timer) { + printf("one_shot_timer_cb %u triggered with flags %i\n", + timer->id, timer->flags); + + //DO NOT IGNORE CANCELATION + if (timer->flags & DISCORD_TIMER_CANCELED) { + puts("Timer has been canceled"); + return; + } + if (interrupted) { + puts("Shutdown Canceled"); + return; + } + puts(timer->data); + discord_shutdown(client); +} + +static void +repeating_timer_cb(struct discord *client, struct discord_timer *timer) { + printf("repeating_timer_cb %u triggered with flags %i\n", + timer->id, timer->flags); + printf("%"PRIi64", %"PRIi64"\n", timer->interval, timer->repeat); + if (timer->repeat == 1) + puts("Shutting down soon, press ctrl + c to cancel"); +} + +int +main(int argc, char *argv[]) +{ + const char *config_file = argc > 1 ? argv[1] : "../config.json"; + + signal(SIGINT, on_sigint); + ccord_global_init(); + + struct discord *client = discord_config_init(config_file); + + //create one shot auto deleting timer + unsigned one_shot_timer_id = + discord_timer(client, one_shot_timer_cb, "Shutting Down", 5000); + + discord_timer_ctl(client, &(struct discord_timer) { + .id = 0, /* 0 to create a new timer */ + .cb = repeating_timer_cb, + .data = &one_shot_timer_id, + .start_after = 0, /* start right away */ + .interval = 100, + .repeat = 10, /* -1 for infinity, 0 for never */ + .flags = DISCORD_TIMER_DELETE_AUTO, + }); + + discord_run(client); + + discord_cleanup(client); + ccord_global_cleanup(); +} \ No newline at end of file From e0ddfbdc7b48ecb17f5a876f2e2a817e6ee7119a Mon Sep 17 00:00:00 2001 From: Anotra Date: Fri, 25 Mar 2022 16:50:22 -0400 Subject: [PATCH 07/15] refactor(timer): change param start_after to delay --- examples/timers.c | 2 +- include/discord.h | 6 +++--- src/discord-timer.c | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/timers.c b/examples/timers.c index 5bb076152..a7873e1fb 100644 --- a/examples/timers.c +++ b/examples/timers.c @@ -58,7 +58,7 @@ main(int argc, char *argv[]) .id = 0, /* 0 to create a new timer */ .cb = repeating_timer_cb, .data = &one_shot_timer_id, - .start_after = 0, /* start right away */ + .delay = 0, /* start right away */ .interval = 100, .repeat = 10, /* -1 for infinity, 0 for never */ .flags = DISCORD_TIMER_DELETE_AUTO, diff --git a/include/discord.h b/include/discord.h index e64ce20d9..3bdd36bf7 100644 --- a/include/discord.h +++ b/include/discord.h @@ -321,7 +321,7 @@ struct discord_timer { /** user data */ void *data; /** delay before timer should start */ - int64_t start_after; + int64_t delay; /** interval that the timer should repeat at. must be > 1 */ int64_t interval; /** how many times a timer should repeat (-1 == infinity) */ @@ -345,11 +345,11 @@ unsigned discord_timer_ctl(struct discord *client, struct discord_timer *timer); * @param client the client created with discord_init() * @param cb the callback that should be called when timer triggers * @param data user data - * @param start_after delay before timer should start in milliseconds + * @param delay delay before timer should start in milliseconds * @return unsigned */ unsigned discord_timer(struct discord *client, discord_ev_timer cb, - void *data, int64_t start_after); + void *data, int64_t delay); /** @} DiscordTimer */ /** @} Discord */ diff --git a/src/discord-timer.c b/src/discord-timer.c index 0b0b9c7be..d052ef4f3 100644 --- a/src/discord-timer.c +++ b/src/discord-timer.c @@ -52,10 +52,10 @@ _discord_timer_ctl( struct discord_timer *timer) { int64_t now = -1; - if (timer->start_after >= 0) + if (timer->delay >= 0) now = (int64_t)discord_timestamp_us(client) + ((timer->flags & DISCORD_TIMER_MICROSECONDS) - ? timer->start_after : timer->start_after * 1000); + ? timer->delay : timer->delay * 1000); if (!timer->id) { return priority_queue_push(timers->q, &now, timer); } else { @@ -110,12 +110,12 @@ discord_timer_ctl(struct discord *client, struct discord_timer *timer) } unsigned discord_timer(struct discord *client, discord_ev_timer cb, - void *data, int64_t start_after) + void *data, int64_t delay) { struct discord_timer timer = { .cb = cb, .data = data, - .start_after = start_after, + .delay = delay, .flags = DISCORD_TIMER_DELETE_AUTO, }; return discord_timer_ctl(client, &timer); From 0dd9a439be2807419e751c87cf785b1f8393383c Mon Sep 17 00:00:00 2001 From: Anotra Date: Fri, 25 Mar 2022 16:58:11 -0400 Subject: [PATCH 08/15] fix(discord-timers.c): repeat should give the proper value --- examples/timers.c | 2 +- src/discord-timer.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/timers.c b/examples/timers.c index a7873e1fb..bff5a03b5 100644 --- a/examples/timers.c +++ b/examples/timers.c @@ -36,7 +36,7 @@ repeating_timer_cb(struct discord *client, struct discord_timer *timer) { printf("repeating_timer_cb %u triggered with flags %i\n", timer->id, timer->flags); printf("%"PRIi64", %"PRIi64"\n", timer->interval, timer->repeat); - if (timer->repeat == 1) + if (timer->repeat == 0) puts("Shutting down soon, press ctrl + c to cancel"); } diff --git a/src/discord-timer.c b/src/discord-timer.c index d052ef4f3..f00a042e6 100644 --- a/src/discord-timer.c +++ b/src/discord-timer.c @@ -86,13 +86,13 @@ discord_timers_run(struct discord *client, struct discord_timers *timers) priority_queue_update(timers->q, timer.id, &now, &timer); } + if (timer.repeat > 0) + timer.repeat--; if (timer.cb) timer.cb(client, &timer); TIMER_TRY_DELETE int64_t next = -1; if (timer.repeat != 0) { - if (timer.repeat > 0) - timer.repeat--; if (timer.interval > 0) next = now + ((timer.flags & DISCORD_TIMER_MICROSECONDS) ? timer.interval : timer.interval * 1000); From 208bd9a8609e6aa3739543bc5e4a4053184a54c3 Mon Sep 17 00:00:00 2001 From: Anotra Date: Fri, 25 Mar 2022 17:14:58 -0400 Subject: [PATCH 09/15] feat(discord-timer.c): added ability to delete a timer by id --- src/discord-timer.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/discord-timer.c b/src/discord-timer.c index f00a042e6..9c7989436 100644 --- a/src/discord-timer.c +++ b/src/discord-timer.c @@ -52,6 +52,14 @@ _discord_timer_ctl( struct discord_timer *timer) { int64_t now = -1; + if (timer->flags & DISCORD_TIMER_DELETE) { + unsigned id; + if (timer->id) { + id = priority_queue_get(timers->q, timer->id, NULL, timer); + if (id) return priority_queue_del(timers->q, id) ? id : 0; + } + return 0; + } if (timer->delay >= 0) now = (int64_t)discord_timestamp_us(client) + ((timer->flags & DISCORD_TIMER_MICROSECONDS) @@ -60,15 +68,17 @@ _discord_timer_ctl( return priority_queue_push(timers->q, &now, timer); } else { if (priority_queue_update(timers->q, timer->id, &now, &timer)) - return timer->id; + return timer->id; return 0; } } + #define TIMER_TRY_DELETE \ if (timer.flags & DISCORD_TIMER_DELETE && timer.repeat == 0) { \ - priority_queue_pop(timers->q, NULL, NULL); \ - continue; \ + priority_queue_pop(timers->q, NULL, NULL); \ + continue; \ } + void discord_timers_run(struct discord *client, struct discord_timers *timers) { From eedba35264ed2b4aa444c17c922dd7b7c56e8b77 Mon Sep 17 00:00:00 2001 From: Anotra Date: Fri, 25 Mar 2022 17:35:33 -0400 Subject: [PATCH 10/15] fix(discord-timer.c): fix timer auto delete for repeatable timers --- src/discord-timer.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/discord-timer.c b/src/discord-timer.c index 9c7989436..0bb1bf729 100644 --- a/src/discord-timer.c +++ b/src/discord-timer.c @@ -73,10 +73,10 @@ _discord_timer_ctl( } } -#define TIMER_TRY_DELETE \ - if (timer.flags & DISCORD_TIMER_DELETE && timer.repeat == 0) { \ - priority_queue_pop(timers->q, NULL, NULL); \ - continue; \ +#define TIMER_TRY_DELETE \ + if (timer.flags & DISCORD_TIMER_DELETE) { \ + priority_queue_pop(timers->q, NULL, NULL); \ + continue; \ } void @@ -91,14 +91,11 @@ discord_timers_run(struct discord *client, struct discord_timers *timers) TIMER_TRY_DELETE - if (timer.flags & DISCORD_TIMER_DELETE_AUTO) { - timer.flags |= DISCORD_TIMER_DELETE; - priority_queue_update(timers->q, timer.id, &now, &timer); - } - if (timer.repeat > 0) timer.repeat--; if (timer.cb) timer.cb(client, &timer); + if (timer.repeat == 0 && (timer.flags & DISCORD_TIMER_DELETE_AUTO)) + timer.flags |= DISCORD_TIMER_DELETE; TIMER_TRY_DELETE int64_t next = -1; From a10a3102f78c8daa9221790a669f76d25839619c Mon Sep 17 00:00:00 2001 From: Anotra Date: Fri, 25 Mar 2022 18:00:14 -0400 Subject: [PATCH 11/15] refactor(struct discord_timer): move flags beside id to save memory --- include/discord.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/discord.h b/include/discord.h index 3bdd36bf7..dbf0f31dc 100644 --- a/include/discord.h +++ b/include/discord.h @@ -316,6 +316,8 @@ enum discord_timer_flags { struct discord_timer { /** the identifier used for the timer. 0 creates a new timer */ unsigned id; + /** the flags used to manipulate the timer */ + enum discord_timer_flags flags; /** the callback that should be called when timer triggers */ discord_ev_timer cb; /** user data */ @@ -326,7 +328,6 @@ struct discord_timer { int64_t interval; /** how many times a timer should repeat (-1 == infinity) */ int64_t repeat; - int flags; }; /** From bc5d02deeee1c954d69f33594ee334740e8aa4be Mon Sep 17 00:00:00 2001 From: Anotra Date: Fri, 25 Mar 2022 18:30:07 -0400 Subject: [PATCH 12/15] fix(discord-timer.c): block user from adding timers when cleaning up --- src/discord-timer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/discord-timer.c b/src/discord-timer.c index 0bb1bf729..cbf55131e 100644 --- a/src/discord-timer.c +++ b/src/discord-timer.c @@ -38,9 +38,11 @@ discord_timers_cancel_all(struct discord *client, priority_queue *q) void discord_timers_cleanup(struct discord *client) { + priority_queue_set_max_capacity(client->timers.user.q, 0); discord_timers_cancel_all(client, client->timers.user.q); priority_queue_destroy(client->timers.user.q); + priority_queue_set_max_capacity(client->timers.internal.q, 0); discord_timers_cancel_all(client, client->timers.internal.q); priority_queue_destroy(client->timers.internal.q); } From 4219ea2e589f61ef87d94a8181db72f9ef694fbd Mon Sep 17 00:00:00 2001 From: Anotra Date: Fri, 25 Mar 2022 21:21:15 -0400 Subject: [PATCH 13/15] Update include/discord-internal.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lucas Müller --- include/discord-internal.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/discord-internal.h b/include/discord-internal.h index f78faef06..27034b048 100644 --- a/include/discord-internal.h +++ b/include/discord-internal.h @@ -685,6 +685,10 @@ void discord_gateway_send_presence_update(struct discord_gateway *gw); /** @} DiscordInternalGateway */ +/** @defgroup DiscordInternalTimer Timer API + * @brief Callback scheduling API + * @{ */ + struct discord_timers { priority_queue *q; }; @@ -724,6 +728,7 @@ unsigned _discord_timer_ctl( struct discord_timers *timers, struct discord_timer *timer); +/** @} DiscordInternalTimer */ /** * @brief The Discord client handler * From 1ddcf9b56b3e49b7e5a7f7fb408a68def7414f98 Mon Sep 17 00:00:00 2001 From: Anotra Date: Fri, 25 Mar 2022 21:21:22 -0400 Subject: [PATCH 14/15] Update include/discord.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lucas Müller --- include/discord.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/discord.h b/include/discord.h index dbf0f31dc..06a556b52 100644 --- a/include/discord.h +++ b/include/discord.h @@ -351,6 +351,9 @@ unsigned discord_timer_ctl(struct discord *client, struct discord_timer *timer); */ unsigned discord_timer(struct discord *client, discord_ev_timer cb, void *data, int64_t delay); + +/** @example timers.c + * Demonstrates the Timer API for callback scheduling */ /** @} DiscordTimer */ /** @} Discord */ From 9552dc9cd36b0d261a626d7abc23867fa1e201d9 Mon Sep 17 00:00:00 2001 From: Anotra Date: Fri, 25 Mar 2022 21:21:28 -0400 Subject: [PATCH 15/15] Update include/discord.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lucas Müller --- include/discord.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/discord.h b/include/discord.h index 06a556b52..1ac4c05b8 100644 --- a/include/discord.h +++ b/include/discord.h @@ -286,10 +286,9 @@ struct io_poller *discord_get_io_poller(struct discord *client); * @brief Schedule callbacks to be called in the future * @{ */ -/** - * @brief struct used for modifying, and getting info about a timer - */ +/* forward declaration */ struct discord_timer; +/**/ /** * @brief callback to be used with struct discord_timer @@ -313,6 +312,9 @@ enum discord_timer_flags { DISCORD_TIMER_CANCELED = 1 << 3, }; +/** + * @brief struct used for modifying, and getting info about a timer + */ struct discord_timer { /** the identifier used for the timer. 0 creates a new timer */ unsigned id;