watch: notify WATCH/MONITOR via nick->watchers reverse indexes (watch_by/monitor_by) instead of scanning every user on each online/offline/nick-change flip — O(watchers) not O(users); maintained through centralized watch_index_*/monitor_index_* helpers + quit cleanup
This commit is contained in:
parent
16ea4b5a23
commit
16fae5b23c
3 changed files with 158 additions and 31 deletions
|
|
@ -58,11 +58,7 @@ fn watch_add(s: &mut Server, uid: Uid, nick: &str) {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Some(u) = s.users.get_mut(&uid) {
|
s.watch_index_add(uid, low);
|
||||||
if !u.watch.contains(&low) {
|
|
||||||
u.watch.push(low);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
watch_status(s, uid, nick);
|
watch_status(s, uid, nick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,9 +90,7 @@ impl Command for Watch {
|
||||||
for tok in params.iter().flat_map(|p| p.split_whitespace()) {
|
for tok in params.iter().flat_map(|p| p.split_whitespace()) {
|
||||||
match tok {
|
match tok {
|
||||||
"C" | "c" => {
|
"C" | "c" => {
|
||||||
if let Some(u) = s.users.get_mut(&uid) {
|
s.watch_index_clear(uid);
|
||||||
u.watch.clear();
|
|
||||||
}
|
|
||||||
s.numeric(uid, RPL_ENDOFWATCHLIST, ":End of WATCH list");
|
s.numeric(uid, RPL_ENDOFWATCHLIST, ":End of WATCH list");
|
||||||
}
|
}
|
||||||
"S" | "s" => {
|
"S" | "s" => {
|
||||||
|
|
@ -126,9 +120,7 @@ impl Command for Watch {
|
||||||
_ if tok.starts_with('+') => watch_add(s, uid, &tok[1..]),
|
_ if tok.starts_with('+') => watch_add(s, uid, &tok[1..]),
|
||||||
_ if tok.starts_with('-') => {
|
_ if tok.starts_with('-') => {
|
||||||
let low = tok[1..].to_ascii_lowercase();
|
let low = tok[1..].to_ascii_lowercase();
|
||||||
if let Some(u) = s.users.get_mut(&uid) {
|
s.watch_index_remove(uid, &low);
|
||||||
u.watch.retain(|n| n != &low);
|
|
||||||
}
|
|
||||||
s.numeric(
|
s.numeric(
|
||||||
uid,
|
uid,
|
||||||
RPL_WATCHOFF,
|
RPL_WATCHOFF,
|
||||||
|
|
@ -199,11 +191,7 @@ impl Command for Monitor {
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(u) = s.users.get_mut(&uid) {
|
s.monitor_index_add(uid, low);
|
||||||
if !u.monitor.contains(&low) {
|
|
||||||
u.monitor.push(low);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
added.push(t);
|
added.push(t);
|
||||||
}
|
}
|
||||||
monitor_report(s, uid, &added);
|
monitor_report(s, uid, &added);
|
||||||
|
|
@ -218,14 +206,12 @@ impl Command for Monitor {
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
if let Some(u) = s.users.get_mut(&uid) {
|
for t in &targets {
|
||||||
u.monitor.retain(|n| !targets.contains(n));
|
s.monitor_index_remove(uid, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"C" => {
|
"C" => {
|
||||||
if let Some(u) = s.users.get_mut(&uid) {
|
s.monitor_index_clear(uid);
|
||||||
u.monitor.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"L" => {
|
"L" => {
|
||||||
let list = s
|
let list = s
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,11 @@ pub struct Server {
|
||||||
/// the common case (count 0) skips the O(users) scan. Maintained solely through
|
/// the common case (count 0) skips the O(users) scan. Maintained solely through
|
||||||
/// `accept_add`/`accept_remove` and the quit path.
|
/// `accept_add`/`accept_remove` and the quit path.
|
||||||
pub accepted_nicks: HashMap<String, u32>,
|
pub accepted_nicks: HashMap<String, u32>,
|
||||||
|
/// Reverse index (lowercased) nick -> uids WATCHing / MONITORing it, so an
|
||||||
|
/// online/offline flip notifies only the watchers instead of scanning every user.
|
||||||
|
/// Maintained solely through the `watch_index_*` / `monitor_index_*` helpers and quit.
|
||||||
|
pub watch_by: HashMap<String, HashSet<Uid>>,
|
||||||
|
pub monitor_by: HashMap<String, HashSet<Uid>>,
|
||||||
// labeled-response: while Some((uid, buf)), that client's own responses are
|
// labeled-response: while Some((uid, buf)), that client's own responses are
|
||||||
// diverted into `buf` instead of the socket, so `on_line` can wrap them with
|
// diverted into `buf` instead of the socket, so `on_line` can wrap them with
|
||||||
// the command's `label` (single tag, BATCH, or ACK). RefCell because the
|
// the command's `label` (single tag, BATCH, or ACK). RefCell because the
|
||||||
|
|
@ -228,6 +233,8 @@ impl Server {
|
||||||
raw_config: cfg.raw,
|
raw_config: cfg.raw,
|
||||||
config_gen: 0,
|
config_gen: 0,
|
||||||
accepted_nicks: HashMap::default(),
|
accepted_nicks: HashMap::default(),
|
||||||
|
watch_by: HashMap::default(),
|
||||||
|
monitor_by: HashMap::default(),
|
||||||
label_capture: RefCell::new(None),
|
label_capture: RefCell::new(None),
|
||||||
log: RefCell::new(LogState::default()),
|
log: RefCell::new(LogState::default()),
|
||||||
event_tx,
|
event_tx,
|
||||||
|
|
@ -726,6 +733,23 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// drop this user from the WATCH/MONITOR reverse indexes
|
||||||
|
for n in &user.watch {
|
||||||
|
if let Some(set) = self.watch_by.get_mut(n) {
|
||||||
|
set.remove(&uid);
|
||||||
|
if set.is_empty() {
|
||||||
|
self.watch_by.remove(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for n in &user.monitor {
|
||||||
|
if let Some(set) = self.monitor_by.get_mut(n) {
|
||||||
|
set.remove(&uid);
|
||||||
|
if set.is_empty() {
|
||||||
|
self.monitor_by.remove(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.uuid_local.remove(&user.uuid);
|
self.uuid_local.remove(&user.uuid);
|
||||||
if user.registered {
|
if user.registered {
|
||||||
self.push_whowas(
|
self.push_whowas(
|
||||||
|
|
|
||||||
137
src/watch.rs
137
src/watch.rs
|
|
@ -37,15 +37,17 @@ impl Server {
|
||||||
}) else {
|
}) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
for (&uid, u) in &self.users {
|
if let Some(set) = self.watch_by.get(&low) {
|
||||||
if u.watch.contains(&low) {
|
for &uid in set {
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
RPL_LOGON,
|
RPL_LOGON,
|
||||||
&format!("{dnick} {ident} {host} {ts} :is now online"),
|
&format!("{dnick} {ident} {host} {ts} :is now online"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if u.monitor.contains(&low) {
|
}
|
||||||
|
if let Some(set) = self.monitor_by.get(&low) {
|
||||||
|
for &uid in set {
|
||||||
self.numeric(uid, RPL_MONONLINE, &format!(":{dnick}!{ident}@{host}"));
|
self.numeric(uid, RPL_MONONLINE, &format!(":{dnick}!{ident}@{host}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -56,11 +58,13 @@ impl Server {
|
||||||
pub fn watch_notify_offline(&self, nick: &str) {
|
pub fn watch_notify_offline(&self, nick: &str) {
|
||||||
let low = nick.to_ascii_lowercase();
|
let low = nick.to_ascii_lowercase();
|
||||||
let ts = now();
|
let ts = now();
|
||||||
for (&uid, u) in &self.users {
|
if let Some(set) = self.watch_by.get(&low) {
|
||||||
if u.watch.contains(&low) {
|
for &uid in set {
|
||||||
self.numeric(uid, RPL_LOGOFF, &format!("{nick} * * {ts} :is now offline"));
|
self.numeric(uid, RPL_LOGOFF, &format!("{nick} * * {ts} :is now offline"));
|
||||||
}
|
}
|
||||||
if u.monitor.contains(&low) {
|
}
|
||||||
|
if let Some(set) = self.monitor_by.get(&low) {
|
||||||
|
for &uid in set {
|
||||||
self.numeric(uid, RPL_MONOFFLINE, &format!(":{nick}"));
|
self.numeric(uid, RPL_MONOFFLINE, &format!(":{nick}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,10 +73,123 @@ impl Server {
|
||||||
/// How many users currently WATCH `nick` (for `WATCH S` stats).
|
/// How many users currently WATCH `nick` (for `WATCH S` stats).
|
||||||
pub fn watchers_of(&self, nick: &str) -> usize {
|
pub fn watchers_of(&self, nick: &str) -> usize {
|
||||||
let low = nick.to_ascii_lowercase();
|
let low = nick.to_ascii_lowercase();
|
||||||
self.users
|
self.watch_by.get(&low).map(|s| s.len()).unwrap_or(0)
|
||||||
.values()
|
}
|
||||||
.filter(|u| u.watch.contains(&low))
|
|
||||||
.count()
|
// ── WATCH/MONITOR list mutators — keep the reverse index in sync ──────────
|
||||||
|
|
||||||
|
/// Add `nick_low` to `uid`'s WATCH list (if absent) and index it.
|
||||||
|
pub fn watch_index_add(&mut self, uid: Uid, nick_low: String) {
|
||||||
|
let added = self
|
||||||
|
.users
|
||||||
|
.get_mut(&uid)
|
||||||
|
.map(|u| {
|
||||||
|
if u.watch.contains(&nick_low) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
u.watch.push(nick_low.clone());
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
if added {
|
||||||
|
self.watch_by.entry(nick_low).or_default().insert(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove `nick_low` from `uid`'s WATCH list and de-index it.
|
||||||
|
pub fn watch_index_remove(&mut self, uid: Uid, nick_low: &str) {
|
||||||
|
let removed = self
|
||||||
|
.users
|
||||||
|
.get_mut(&uid)
|
||||||
|
.map(|u| {
|
||||||
|
let before = u.watch.len();
|
||||||
|
u.watch.retain(|n| n != nick_low);
|
||||||
|
before != u.watch.len()
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
if removed {
|
||||||
|
if let Some(set) = self.watch_by.get_mut(nick_low) {
|
||||||
|
set.remove(&uid);
|
||||||
|
if set.is_empty() {
|
||||||
|
self.watch_by.remove(nick_low);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear `uid`'s whole WATCH list (WATCH C) and de-index every entry.
|
||||||
|
pub fn watch_index_clear(&mut self, uid: Uid) {
|
||||||
|
let nicks = self
|
||||||
|
.users
|
||||||
|
.get_mut(&uid)
|
||||||
|
.map(|u| std::mem::take(&mut u.watch))
|
||||||
|
.unwrap_or_default();
|
||||||
|
for n in nicks {
|
||||||
|
if let Some(set) = self.watch_by.get_mut(&n) {
|
||||||
|
set.remove(&uid);
|
||||||
|
if set.is_empty() {
|
||||||
|
self.watch_by.remove(&n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add `nick_low` to `uid`'s MONITOR list (if absent) and index it.
|
||||||
|
pub fn monitor_index_add(&mut self, uid: Uid, nick_low: String) {
|
||||||
|
let added = self
|
||||||
|
.users
|
||||||
|
.get_mut(&uid)
|
||||||
|
.map(|u| {
|
||||||
|
if u.monitor.contains(&nick_low) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
u.monitor.push(nick_low.clone());
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
if added {
|
||||||
|
self.monitor_by.entry(nick_low).or_default().insert(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove `nick_low` from `uid`'s MONITOR list and de-index it.
|
||||||
|
pub fn monitor_index_remove(&mut self, uid: Uid, nick_low: &str) {
|
||||||
|
let removed = self
|
||||||
|
.users
|
||||||
|
.get_mut(&uid)
|
||||||
|
.map(|u| {
|
||||||
|
let before = u.monitor.len();
|
||||||
|
u.monitor.retain(|n| n != nick_low);
|
||||||
|
before != u.monitor.len()
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
if removed {
|
||||||
|
if let Some(set) = self.monitor_by.get_mut(nick_low) {
|
||||||
|
set.remove(&uid);
|
||||||
|
if set.is_empty() {
|
||||||
|
self.monitor_by.remove(nick_low);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear `uid`'s whole MONITOR list (MONITOR C) and de-index every entry.
|
||||||
|
pub fn monitor_index_clear(&mut self, uid: Uid) {
|
||||||
|
let nicks = self
|
||||||
|
.users
|
||||||
|
.get_mut(&uid)
|
||||||
|
.map(|u| std::mem::take(&mut u.monitor))
|
||||||
|
.unwrap_or_default();
|
||||||
|
for n in nicks {
|
||||||
|
if let Some(set) = self.monitor_by.get_mut(&n) {
|
||||||
|
set.remove(&uid);
|
||||||
|
if set.is_empty() {
|
||||||
|
self.monitor_by.remove(&n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// True if `sender_nick` is on `target`'s ACCEPT list (callerid +g).
|
/// True if `sender_nick` is on `target`'s ACCEPT list (callerid +g).
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue