AmendHub

jcs

/

wallops

/

amendments

/

24

chatter+irc: Rewrite nick list handling

Use a linked list to sort the array of nicks to avoid having to
shuffle around struct members on every re-sort. This also lets us
process JOINs and PARTs/QUITs on an individual nick level to just
do one corresponding list add or remove and avoid re-sorting or
reloading the entire list.
This makes it possible to join channels with many hundreds of people
in them and keep running smoothly. The initial load still takes a
while, but it works.

jcs made amendment 24 11 months ago
--- chatter.c Tue Feb 8 14:56:52 2022 +++ chatter.c Thu Feb 10 10:27:03 2022 @@ -630,69 +630,71 @@ no_overflow: } void -chatter_sync_nick_list(struct chatter *chatter, bool initial_sync) +chatter_sync_nick_list(struct chatter *chatter, bool just_summary) { - Cell cell = { 0, 0 }; - size_t n, i, j, ops, voices; - char nick[32]; - short ret; - struct irc_channel_nick tnick; - - LDoDraw(false, chatter->nick_list); - LDelRow(0, 0, chatter->nick_list); + size_t n, i, j, tj, ops, voices; + short ret, cellv; + struct irc_channel_nick *nick = NULL; - if (chatter->irc_channel) { - /* sort nicks by flags descending, then by nick */ - for (i = 0; i < chatter->irc_channel->nusers; i++) { - for (j = 0; j < chatter->irc_channel->nusers - i - 1; j++) { - ret = 0; - if (chatter->irc_channel->users[j].flags == - chatter->irc_channel->users[j + 1].flags) - ret = strnatcasecmp( - chatter->irc_channel->users[j].nick, - chatter->irc_channel->users[j + 1].nick); - else - ret = (chatter->irc_channel->users[j].flags < - chatter->irc_channel->users[j + 1].flags); - - if (ret == 1) { - tnick = chatter->irc_channel->users[j]; - chatter->irc_channel->users[j] = - chatter->irc_channel->users[j + 1]; - chatter->irc_channel->users[j + 1] = tnick; - } - } - } + if (!just_summary) { + LDoDraw(false, chatter->nick_list); + LDelRow(0, 0, chatter->nick_list); + } - cell.v = 0; + if (chatter->irc_channel) { + cellv = 0; ops = voices = 0; - for (n = 0; n < chatter->irc_channel->nusers; n++) { - if (chatter->irc_channel->users[n].flags & IRC_NICK_FLAG_OP) { - j = snprintf(nick, sizeof(nick), "@%s", - chatter->irc_channel->users[n].nick); + nick = &chatter->irc_channel->nicks[ + chatter->irc_channel->first_nick]; + while (nick) { + if (nick->flags & IRC_NICK_FLAG_OP) ops++; - } else if (chatter->irc_channel->users[n].flags & - IRC_NICK_FLAG_VOICE) { - j = snprintf(nick, sizeof(nick), "+%s", - chatter->irc_channel->users[n].nick); + else if (nick->flags & IRC_NICK_FLAG_VOICE) voices++; - } else - j = snprintf(nick, sizeof(nick), "%s", - chatter->irc_channel->users[n].nick); - LAddRow(1, cell.v, chatter->nick_list); - LSetCell(&nick, j, cell, chatter->nick_list); - cell.v++; + + if (!just_summary) + chatter_insert_to_nick_list(chatter, nick, cellv); + + cellv++; + + if (nick->next_nick == -1) + break; + nick = &chatter->irc_channel->nicks[nick->next_nick]; } - if (initial_sync) + if (just_summary) chatter_printf(chatter, "$B%s$0: Total of $B%ld$0 nick%s $B(" "%ld$0 op%s, $B%ld$0 voice%s$B)$0", - chatter->irc_channel->name, chatter->irc_channel->nusers, - chatter->irc_channel->nusers == 1 ? "" : "s", + chatter->irc_channel->name, chatter->irc_channel->nnicks, + chatter->irc_channel->nnicks == 1 ? "" : "s", ops, ops == 1 ? "" : "s", voices, voices == 1 ? "" : "s"); } - LDoDraw(true, chatter->nick_list); - InvalRect(&(*(chatter->nick_list))->rView); + if (!just_summary) { + LDoDraw(true, chatter->nick_list); + InvalRect(&(*(chatter->nick_list))->rView); + } +} + +void +chatter_insert_to_nick_list(struct chatter *chatter, + struct irc_channel_nick *nick, short pos) +{ + Cell cell = { 0, 0 }; + struct irc_channel_nick tnick; + short j = 0; + + if (nick->flags & IRC_NICK_FLAG_OP) { + tnick.nick[0] = '@'; + j++; + } else if (nick->flags & IRC_NICK_FLAG_VOICE) { + tnick.nick[0] = '+'; + j++; + } + + cell.v = pos; + j += strlcpy(tnick.nick + j, nick->nick, sizeof(tnick.nick) - j); + LAddRow(1, cell.v, chatter->nick_list); + LSetCell(&tnick.nick, j, cell, chatter->nick_list); } --- chatter.h Mon Feb 7 17:33:39 2022 +++ chatter.h Wed Feb 9 12:45:11 2022 @@ -129,6 +129,8 @@ void chatter_init(const char *server, const unsigned s const char *realname, const char *channel); void chatter_update_titlebar(struct chatter *chatter); size_t chatter_printf(struct chatter *chatter, const char *format, ...); -void chatter_sync_nick_list(struct chatter *chatter, bool initial_sync); +void chatter_insert_to_nick_list(struct chatter *chatter, + struct irc_channel_nick *nick, short pos); +void chatter_sync_nick_list(struct chatter *chatter, bool just_summary); #endif --- irc.c Tue Feb 8 21:13:21 2022 +++ irc.c Thu Feb 10 10:11:26 2022 @@ -33,8 +33,8 @@ void irc_login(struct chatter *chatter); void irc_process_server(struct chatter *chatter); void irc_set_active_channel(struct chatter *chatter, char *channame); void irc_parse_names(struct chatter *chatter, char *line); -void irc_add_user_to_channel(struct chatter *chatter, - struct irc_user *user); +void irc_add_nick_to_channel(struct chatter *chatter, char *nick, + short flags); void irc_remove_nick_from_channel(struct chatter *chatter, char *nick); void irc_change_user_nick(struct chatter *chatter, struct irc_user *user, char *nick); @@ -180,9 +180,6 @@ irc_recv(struct chatter *chatter) if (chatter->irc_ibuflen + rlen > sizeof(chatter->irc_ibuf)) rlen = sizeof(chatter->irc_ibuf) - chatter->irc_ibuflen; - if (rlen > 128) - rlen = 128; - error = _TCPRcv(&chatter->irc_rcv_pb, chatter->irc_stream, (Ptr)(chatter->irc_ibuf + chatter->irc_ibuflen), &rlen, nil, nil, false); @@ -483,7 +480,7 @@ irc_process_server(struct chatter *chatter) if (strcmp(user->nick, chatter->irc_nick) == 0) irc_set_active_channel(chatter, msg.arg[0]); else - irc_add_user_to_channel(chatter, user); + irc_add_nick_to_channel(chatter, user->nick, 0); return; } if (strcmp(msg.cmd, "KICK") == 0) { @@ -707,7 +704,7 @@ irc_process_input(struct chatter *chatter, char *str) return; } - /* ignore / */ + /* skip / */ str++; arg0 = strsep(&str, " "); @@ -779,11 +776,9 @@ not_in_channel: void irc_set_active_channel(struct chatter *chatter, char *channame) { - struct irc_channel *channel; - if (chatter->irc_channel) { - if (chatter->irc_channel->users) - free(chatter->irc_channel->users); + if (chatter->irc_channel->nicks) + free(chatter->irc_channel->nicks); free(chatter->irc_channel); chatter->irc_channel = NULL; } @@ -791,9 +786,9 @@ irc_set_active_channel(struct chatter *chatter, char * if (channame == NULL) chatter_sync_nick_list(chatter, false); else { - channel = xmalloczero(sizeof(struct irc_channel)); - strlcpy(channel->name, channame, sizeof(channel->name)); - chatter->irc_channel = channel; + chatter->irc_channel = xmalloczero(sizeof(struct irc_channel)); + strlcpy(chatter->irc_channel->name, channame, + sizeof(chatter->irc_channel->name)); } chatter_update_titlebar(chatter); @@ -805,91 +800,177 @@ irc_parse_names(struct chatter *chatter, char *line) size_t n; char *nick; bool last = false; - struct irc_channel_nick *user; + short flags; if (!chatter->irc_channel) return; - + + LDoDraw(false, chatter->nick_list); + for (;;) { nick = strsep(&line, " "); - chatter->irc_channel->nusers++; - chatter->irc_channel->users = xreallocarray( - chatter->irc_channel->users, sizeof(struct irc_channel_nick), - chatter->irc_channel->nusers); - user = &chatter->irc_channel->users[ - chatter->irc_channel->nusers - 1]; if (nick[0] == '@') { - user->flags = IRC_NICK_FLAG_OP; + flags = IRC_NICK_FLAG_OP; nick++; } else if (nick[0] == '+') { - user->flags = IRC_NICK_FLAG_VOICE; + flags = IRC_NICK_FLAG_VOICE; nick++; } else - user->flags = 0; - strlcpy(user->nick, nick, sizeof(user->nick)); + flags = 0; + + irc_add_nick_to_channel(chatter, nick, flags); if (line == NULL) break; } + + LDoDraw(true, chatter->nick_list); + InvalRect(&(*(chatter->nick_list))->rView); } void -irc_add_user_to_channel(struct chatter *chatter, struct irc_user *user) +irc_add_nick_to_channel(struct chatter *chatter, char *nick, short flags) { - chatter->irc_channel->nusers++; - chatter->irc_channel->users = - xreallocarray(chatter->irc_channel->users, - sizeof(struct irc_channel_nick), chatter->irc_channel->nusers); - strlcpy(chatter->irc_channel->users[ - chatter->irc_channel->nusers - 1].nick, user->nick, - sizeof(chatter->irc_channel->users[0].nick)); - chatter_sync_nick_list(chatter, false); -} + struct irc_channel_nick *anick, *cnick, *pnick; + short aidx, cidx, ret; + + if (chatter->irc_channel->nnicks >= chatter->irc_channel->nicks_size) { + /* allocate a chunk at a time so we don't do this every iteration */ + chatter->irc_channel->nicks_size += 5; + chatter->irc_channel->nicks = + xreallocarray(chatter->irc_channel->nicks, + sizeof(struct irc_channel_nick), + chatter->irc_channel->nicks_size); + memset(&chatter->irc_channel->nicks[chatter->irc_channel->nnicks], + 0, + sizeof(struct irc_channel_nick) * + (chatter->irc_channel->nicks_size - chatter->irc_channel->nnicks)); + aidx = chatter->irc_channel->nnicks; + } else { + /* find an open slot */ + for (aidx = 0; aidx < chatter->irc_channel->nicks_size; aidx++) { + if (chatter->irc_channel->nicks[aidx].nick[0] == '\0') + break; + } + + if (aidx >= chatter->irc_channel->nicks_size) + panic("irc_add_nick_to_channel overflow"); + } -void -irc_remove_nick_from_channel(struct chatter *chatter, char *nick) -{ - size_t n; - bool found = false; + chatter->irc_channel->nnicks++; + anick = &chatter->irc_channel->nicks[aidx]; + strlcpy(anick->nick, nick, sizeof(anick->nick)); + anick->flags = flags; - for (n = 0; n < chatter->irc_channel->nusers; n++) { - if (strcmp(chatter->irc_channel->users[n].nick, nick) == 0) { - LDelRow(1, n, chatter->nick_list); - found = true; + if (chatter->irc_channel->nnicks == 1) { + anick->next_nick = -1; + chatter->irc_channel->first_nick = 0; + cidx = 0; + } else { + /* sort it in the right place by flags descending, then by nick */ + cnick = &chatter->irc_channel->nicks[ + chatter->irc_channel->first_nick]; + pnick = NULL; + cidx = 0; + while (cnick) { + if (cnick->nick[0] == '\0') + ret = 1; + if (cnick->flags == anick->flags) + ret = strnatcasecmp(anick->nick, cnick->nick); + else if (anick->flags > cnick->flags) + ret = -1; + else + ret = 1; + + if (ret <= 0) { + /* new nick goes before this one */ + if (pnick) { + anick->next_nick = pnick->next_nick; + pnick->next_nick = aidx; + } else { + anick->next_nick = chatter->irc_channel->first_nick; + chatter->irc_channel->first_nick = aidx; + } + break; + } + + cidx++; - /* move everyone else up */ - for (; n < chatter->irc_channel->nusers - 1; n++) - chatter->irc_channel->users[n] = - chatter->irc_channel->users[n + 1]; - break; + if (cnick->next_nick == -1) { + /* end of the line */ + cnick->next_nick = aidx; + anick->next_nick = -1; + break; + } + + pnick = cnick; + cnick = &chatter->irc_channel->nicks[cnick->next_nick]; } } - if (!found) + chatter_insert_to_nick_list(chatter, anick, cidx); +} + +void +irc_remove_nick_from_channel(struct chatter *chatter, char *nick) +{ + struct irc_channel_nick *cnick, *pnick; + short cidx; + + if (chatter->irc_channel->first_nick == -1) return; - chatter->irc_channel->nusers--; - chatter->irc_channel->users = - xreallocarray(chatter->irc_channel->users, - sizeof(struct irc_channel_nick), chatter->irc_channel->nusers); + pnick = NULL; + cnick = &chatter->irc_channel->nicks[chatter->irc_channel->first_nick]; + cidx = 0; + while (cnick) { + if (strcmp(cnick->nick, nick) != 0) { + pnick = cnick; + cidx++; + if (cnick->next_nick == -1) + return; + cnick = &chatter->irc_channel->nicks[cnick->next_nick]; + continue; + } + + /* connect the nick that pointed to this one and the one we point to */ + if (pnick) + pnick->next_nick = cnick->next_nick; + else + chatter->irc_channel->first_nick = cnick->next_nick; + + chatter->irc_channel->nnicks--; + cnick->nick[0] = '\0'; + LDelRow(1, cidx, chatter->nick_list); + return; + } } void irc_change_user_nick(struct chatter *chatter, struct irc_user *user, char *nick) { - short n; - - for (n = 0; n < chatter->irc_channel->nusers; n++) { - if (strcmp(chatter->irc_channel->users[n].nick, user->nick) == 0) { - strlcpy(chatter->irc_channel->users[n].nick, nick, - sizeof(chatter->irc_channel->users[n].nick)); - break; + struct irc_channel_nick *cnick; + short n, flags; + + if (chatter->irc_channel && chatter->irc_channel->first_nick > -1) { + /* preserve flags */ + cnick = &chatter->irc_channel->nicks[ + chatter->irc_channel->first_nick]; + while (cnick) { + if (strcmp(cnick->nick, user->nick) == 0) { + flags = cnick->flags; + break; + } + if (cnick->next_nick == -1) + break; + cnick = &chatter->irc_channel->nicks[cnick->next_nick]; } + + irc_remove_nick_from_channel(chatter, user->nick); + irc_add_nick_to_channel(chatter, nick, flags); } - - chatter_sync_nick_list(chatter, false); if (strcmp(chatter->irc_nick, user->nick) == 0) { free(chatter->irc_nick); @@ -905,8 +986,8 @@ irc_parse_channel_mode_change(struct chatter *chatter, size_t len; struct irc_channel_nick *cnick; char *user; - short n, j; - bool add = false, need_resync = false; + short n, j, flags; + bool add = false; len = strlen(mode); @@ -924,24 +1005,33 @@ irc_parse_channel_mode_change(struct chatter *chatter, user = strsep(&args, " "); if (user == NULL) user = args; - for (j = 0; j < chatter->irc_channel->nusers; j++) { - cnick = &chatter->irc_channel->users[j]; - if (strcmp(cnick->nick, user) != 0) + + cnick = &chatter->irc_channel->nicks[ + chatter->irc_channel->first_nick]; + while (cnick) { + if (strcmp(cnick->nick, user) != 0) { + if (cnick->next_nick == -1) + break; + cnick = &chatter->irc_channel->nicks[cnick->next_nick]; continue; - + } + + flags = cnick->flags; if (mode[n] == 'o') { if (add) - cnick->flags |= IRC_NICK_FLAG_OP; + flags |= IRC_NICK_FLAG_OP; else - cnick->flags &= ~IRC_NICK_FLAG_OP; + flags &= ~IRC_NICK_FLAG_OP; } else if (mode[n] == 'v') { if (add) - cnick->flags |= IRC_NICK_FLAG_VOICE; + flags |= IRC_NICK_FLAG_VOICE; else - cnick->flags &= ~IRC_NICK_FLAG_VOICE; + flags &= ~IRC_NICK_FLAG_VOICE; } - need_resync = true; + irc_remove_nick_from_channel(chatter, cnick->nick); + /* cnick is probably invalid now */ + irc_add_nick_to_channel(chatter, user, flags); break; } @@ -951,7 +1041,4 @@ irc_parse_channel_mode_change(struct chatter *chatter, break; } } - - if (need_resync) - chatter_sync_nick_list(chatter, false); } --- irc.h Tue Feb 8 17:07:09 2022 +++ irc.h Wed Feb 9 13:27:24 2022 @@ -46,12 +46,15 @@ struct irc_channel_nick { char flags; #define IRC_NICK_FLAG_OP (7 << 0) #define IRC_NICK_FLAG_VOICE (6 << 0) + short next_nick; }; struct irc_channel { char name[64]; - size_t nusers; - struct irc_channel_nick *users; + struct irc_channel_nick *nicks; + size_t nnicks; + size_t nicks_size; + short first_nick; char topic[400]; };