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 over 2 years 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];
};