From 18a528d6f0548069ec0728195038e709c20f602b Mon Sep 17 00:00:00 2001 From: Anotra Date: Thu, 24 Mar 2022 17:30:27 -0400 Subject: [PATCH] feat(timers): add timers support --- Makefile | 1 + include/discord-internal.h | 19 +++++++ include/discord.h | 29 ++++++++++ src/discord-client.c | 5 +- src/discord-timer.c | 113 +++++++++++++++++++++++++++++++++++++ 5 files changed, 166 insertions(+), 1 deletion(-) 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..d7d8ac8ce 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); @@ -355,6 +356,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..93b48349b --- /dev/null +++ b/src/discord-timer.c @@ -0,0 +1,113 @@ + +#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->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; + } +} + +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; + + if (timer.flags & DISCORD_TIMER_DELETE && timer.repeat <= 0) { + priority_queue_pop(timers->q, NULL, NULL); + continue; + } + 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); + + if (timer.repeat != 0) { + int64_t next = -1; + if (timer.repeat > 0) + timer.repeat--; + if (timer.interval > 0) + next = now + timer.interval * 1000; + 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); +}