Skip to content

Instantly share code, notes, and snippets.

@lcsmuller
Last active June 9, 2022 01:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lcsmuller/d6aee306bac229a7873f5c243bf7858b to your computer and use it in GitHub Desktop.
Save lcsmuller/d6aee306bac229a7873f5c243bf7858b to your computer and use it in GitHub Desktop.
This gist describes the necessary steps to migrate from 1.x.x to 2.x.x

Migrating Guide v1.x.x to v2.x.x

This version introduces some important breaking changes and new features for replacing old methods

Renamed fields

  • discord_ret*.high_p -> discord_ret*.high_priority

Renamed macros

  • DISCORD_EVENT_GATEWAY_* -> DISCORD_EV_*

Wrap event's callbacks parameters in structs

All event callbacks have received signature changes:

v1.x.x - The user had to tediously match each and every field, regardless if they will be used

void on_ready(struct discord *client)
{
  const struct discord_user *self = discord_self(client);
  printf("%s is ready!\n", self->username);
}

void on_reaction_add(struct discord *client,
                     u64snowflake user_id,
                     u64snowflake channel_id,
                     u64snowflake message_id,
                     u64snowflake guild_id,
                     const struct discord_guild_member *member,
                     const struct discord_emoji *emoji)
{
  printf("Received emoji %s from %s", emoji->name, member->nick);
  
  struct discord_create_message params = { .content = "Received a reaction!" };
  discord_create_message(client, &params, NULL);
}
...
discord_set_on_ready(client, on_ready);
discord_set_on_message_reaction_add(client, on_reaction_add);

v2.x.x - Now the user has to deal with at-most two parameters per event callback

void on_ready(struct discord *client, const struct discord_ready *event)
{
  printf("%s is ready!\n", event->user->username);
}

void on_reaction_add(struct discord *client, const struct discord_message_reaction_add *event)
{
  printf("Received emoji %s from user %s", event->emoji->name, event->member->nick);
  
  struct discord_create_message params = { .content = "Received a reaction!" };
  discord_create_message(client, &params, NULL);
}
...
discord_set_on_ready(client, on_ready);
discord_set_on_message_reaction_add(client, on_reaction_add);

Change request's response callbacks signature

Added a new datatype struct discord_response that is provided by Concord at every request's response callback. This provides higher flexibility for adding new fields in the future, without worrying about breaking previous builds.

v1.x.x

void done(struct discord *client, void *data, const struct discord_message *msg)
{
  char *name = data;
  printf("Hello %s, this is %s!\n", name, msg->author->username);
}

void fail(struct discord *client, CCORDcode code, void *data)
{
  printf("Failed request: %s\n", discord_strerror(code, client));
}

...
static char name[] = "Bob";
struct discord_create_message params = { .content = "Hello World!" };
discord_create_message(client, CHANNEL_ID, &params, &(struct discord_ret_message){
                                                      .done = done,
                                                      .fail = fail,
                                                      .data = name
                                                    });

v2.x.x

void done(struct discord *client, struct discord_response *resp, const struct discord_message *msg)
{
  char *name = resp->data;
  printf("Hello %s, this is %s!\n", name, msg->author->username);
}

void fail(struct discord *client, struct discord_response *resp)
{
  printf("Failed request: %s\n", discord_strerror(resp->code, client));
}

...
static char name[] = "Bob";
struct discord_create_message params = { .content = "Hello World!" };
discord_create_message(client, CHANNEL_ID, &params, &(struct discord_ret_message){
                                                      .done = done,
                                                      .fail = fail,
                                                      .data = name
                                                    });

Changed Functionality

discord_set_on_commands()

No longer relies on NULL-terminated variadic arguments. Now, a more C-idiomatic approach by passing an array of commands and its size:

...
const char *cmds[] = { "foo", "bar", "baz", "tuna" };
size_t size = sizeof(cmds) / sizeof *cmds; // 4

discord_set_on_commands(client, cmds, size, callback);

Changed Behaviors

Requests are now sent in-order

Requests are now sent in the enqueue order of their respective bucket-group, rather than "at random". As such, C-style callback-hell is no longer required if the user wishes to keep their requests ordered.

The following examples showcases the difference between sending three in-order messages at v1.x.x and v2.x.x:

v1.x.x - User requires binding callbacks to each request to ensure they are sent in-order

void send_third(struct discord *client, struct discord_response *resp)
{
  struct discord_create_message params = { .content = "Third message!" };
  discord_create_message(client, event->channel_id, &params, NULL);
}

void done_second(struct discord *client,
                 struct discord_response *resp,
                 const struct discord_message *msg)
{
  send_third(client, resp);
}

void send_second(struct discord *client, struct discord_response *resp)
{
  struct discord_create_message params = { .content = "Second message!" };
  discord_create_message(client, event->channel_id, &params, &(struct discord_ret_message){
                                                               .done = done_second,
                                                               .fail = send_third
                                                             });
}

void done_first(struct discord *client,
                struct discord_response *resp,
                const struct discord_message *msg)
{
  send_second(client, resp);
}

void on_message_create(struct discord *client, const struct discord_message *event)
{
  struct discord_create_message params = { .content = "First message!" };
  discord_create_message(client, event->channel_id, &params, &(struct discord_ret_message){
                                                               .done = done_first,
                                                               .fail = send_second
                                                             });
}

v2.x.x - The following is guaranteed to be sent in order:

void on_message_create(struct discord *client, const struct discord_message *event)
{
  struct discord_create_message params = { .content = "First message!" };
  discord_create_message(client, event->channel_id, &params, NULL);
  params.content = "Second message!";
  discord_create_message(client, event->channel_id, &params, NULL);
  params.content = "Third message!";
  discord_create_message(client, event->channel_id, &params, NULL);
}

Its now possible to extend the lifetime, or keep ownership of Concord parameters

Prior to v2.0.0 the user would have to create duplicates if they wanted to use some event data between callbacks. Now we have introduced the struct discord_ret*.keep field that allows extending the lifetime of a parameter to outside its initial callback, and also two new functions discord_claim() and discord_unclaim() that gives user the power of taking control over Concord's automatic cleanup.

v1.x.x - Keeping event data required creating duplicates

void done(struct discord *client, void *data, const struct discord_message *msg)
{
  u64snowflake *channel_id = data;
  struct discord_create_message params = { .content = "World!" };
  discord_create_message(client, *channel_id, &params, NULL);
}

void fail(struct discord *client, CCORDcode code, void *data)
{
  u64snowflake *channel_id = data;
  printf("Couldn't send reply at channel %lld\n", *channel_id);
}

void on_message_create(struct discord *client, const struct discord_message *event)
{
  u64snowflake *channel_id = malloc(sizeof(u64snowflake)); // create duplicate of channel_id
  *channel_id = event->channel_id;

  struct discord_create_message params = { .content = "Hello " };
  discord_create_message(client, event->channel_id, &params, &(struct discord_ret_message){
                                                               .done = done,
                                                               .fail = fail,
                                                               .data = channel_id,
                                                               .cleanup = free // ensure is cleaned up after
                                                             });
}

...
discord_set_on_message_create(client, on_message_create);

v2.x.x - Event data lifetime can be extended to other callbacks

void done(struct discord *client, struct discord_response *resp, const struct discord_message *msg)
{
  const struct discord_message *event = resp->keep;
  struct discord_create_message params = { .content = "World!" };
  discord_create_message(client, event->channel_id, &params, NULL);
}

void fail(struct discord *client, struct discord_response *resp)
{
  const struct discord_message *event = resp->keep;
  printf("Couldn't send reply at channel %lld\n", event->channel_id);
}

void on_message_create(struct discord *client, const struct discord_message *event)
{
  struct discord_create_message params = { .content = "Hello " };
  discord_create_message(client, event->channel_id, &params, &(struct discord_ret_message){
                                                               .done = done,
                                                               .fail = fail,
                                                               .keep = event // extends event lifetime to `done` or `fail`
                                                             });
}

...
discord_set_on_message_create(client, on_message_create);

Alternatively, the user may take complete ownership of the event data by claiming it with discord_claim():

struct my_context {
  const struct discord_message *event;
  const struct discord_message *reply;
};

void my_context_cleanup(struct discord *client, void *data)
{
  struct my_context *cxt = data;
  if (cxt->event) discord_unclaim(client, event); // remember to unclaim it on cleanup!
  if (cxt->reply) discord_unclaim(client, reply);
  free(cxt);
}

void done(struct discord *client, struct discord_response *resp, const struct discord_message *msg)
{
  struct my_context *cxt = resp->data;
  cxt->reply = discord_claim(msg); // data has been claimed and won't be free'd until discord_unclaim()
}

void on_message_create(struct discord *client, const struct discord_message *event)
{
  struct my_context *cxt = malloc(sizeof(struct my_context));
  cxt->event = discord_claim(client, event); // data has been claimed and won't be free'd until discord_unclaim()
  cxt->reply = NULL;
  
  struct discord_create_message params = { .content = "A reply!" };
  discord_create_message(client, event->channel_id, &params, &(struct discord_ret_message){
                                                               .done = done,
                                                               .data = cxt,
                                                               .cleanup = my_context_cleanup
                                                             });
}

...
discord_set_on_message_create(client, on_message_create);
@tarbomb
Copy link

tarbomb commented Jun 9, 2022

Amazing job Muller. Really outdone yourself with this one.

@lcsmuller
Copy link
Author

Amazing job Muller. Really outdone yourself with this one.

Thank you, really appreciate it! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment