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;
|
||||
}
|
||||
if let Some(u) = s.users.get_mut(&uid) {
|
||||
if !u.watch.contains(&low) {
|
||||
u.watch.push(low);
|
||||
}
|
||||
}
|
||||
s.watch_index_add(uid, low);
|
||||
watch_status(s, uid, nick);
|
||||
}
|
||||
|
||||
|
|
@ -94,9 +90,7 @@ impl Command for Watch {
|
|||
for tok in params.iter().flat_map(|p| p.split_whitespace()) {
|
||||
match tok {
|
||||
"C" | "c" => {
|
||||
if let Some(u) = s.users.get_mut(&uid) {
|
||||
u.watch.clear();
|
||||
}
|
||||
s.watch_index_clear(uid);
|
||||
s.numeric(uid, RPL_ENDOFWATCHLIST, ":End of WATCH list");
|
||||
}
|
||||
"S" | "s" => {
|
||||
|
|
@ -126,9 +120,7 @@ impl Command for Watch {
|
|||
_ if tok.starts_with('+') => watch_add(s, uid, &tok[1..]),
|
||||
_ if tok.starts_with('-') => {
|
||||
let low = tok[1..].to_ascii_lowercase();
|
||||
if let Some(u) = s.users.get_mut(&uid) {
|
||||
u.watch.retain(|n| n != &low);
|
||||
}
|
||||
s.watch_index_remove(uid, &low);
|
||||
s.numeric(
|
||||
uid,
|
||||
RPL_WATCHOFF,
|
||||
|
|
@ -199,11 +191,7 @@ impl Command for Monitor {
|
|||
);
|
||||
continue;
|
||||
}
|
||||
if let Some(u) = s.users.get_mut(&uid) {
|
||||
if !u.monitor.contains(&low) {
|
||||
u.monitor.push(low);
|
||||
}
|
||||
}
|
||||
s.monitor_index_add(uid, low);
|
||||
added.push(t);
|
||||
}
|
||||
monitor_report(s, uid, &added);
|
||||
|
|
@ -218,14 +206,12 @@ impl Command for Monitor {
|
|||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
if let Some(u) = s.users.get_mut(&uid) {
|
||||
u.monitor.retain(|n| !targets.contains(n));
|
||||
for t in &targets {
|
||||
s.monitor_index_remove(uid, t);
|
||||
}
|
||||
}
|
||||
"C" => {
|
||||
if let Some(u) = s.users.get_mut(&uid) {
|
||||
u.monitor.clear();
|
||||
}
|
||||
s.monitor_index_clear(uid);
|
||||
}
|
||||
"L" => {
|
||||
let list = s
|
||||
|
|
|
|||
|
|
@ -171,6 +171,11 @@ pub struct Server {
|
|||
/// the common case (count 0) skips the O(users) scan. Maintained solely through
|
||||
/// `accept_add`/`accept_remove` and the quit path.
|
||||
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
|
||||
// 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
|
||||
|
|
@ -228,6 +233,8 @@ impl Server {
|
|||
raw_config: cfg.raw,
|
||||
config_gen: 0,
|
||||
accepted_nicks: HashMap::default(),
|
||||
watch_by: HashMap::default(),
|
||||
monitor_by: HashMap::default(),
|
||||
label_capture: RefCell::new(None),
|
||||
log: RefCell::new(LogState::default()),
|
||||
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);
|
||||
if user.registered {
|
||||
self.push_whowas(
|
||||
|
|
|
|||
137
src/watch.rs
137
src/watch.rs
|
|
@ -37,15 +37,17 @@ impl Server {
|
|||
}) else {
|
||||
return;
|
||||
};
|
||||
for (&uid, u) in &self.users {
|
||||
if u.watch.contains(&low) {
|
||||
if let Some(set) = self.watch_by.get(&low) {
|
||||
for &uid in set {
|
||||
self.numeric(
|
||||
uid,
|
||||
RPL_LOGON,
|
||||
&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}"));
|
||||
}
|
||||
}
|
||||
|
|
@ -56,11 +58,13 @@ impl Server {
|
|||
pub fn watch_notify_offline(&self, nick: &str) {
|
||||
let low = nick.to_ascii_lowercase();
|
||||
let ts = now();
|
||||
for (&uid, u) in &self.users {
|
||||
if u.watch.contains(&low) {
|
||||
if let Some(set) = self.watch_by.get(&low) {
|
||||
for &uid in set {
|
||||
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}"));
|
||||
}
|
||||
}
|
||||
|
|
@ -69,10 +73,123 @@ impl Server {
|
|||
/// How many users currently WATCH `nick` (for `WATCH S` stats).
|
||||
pub fn watchers_of(&self, nick: &str) -> usize {
|
||||
let low = nick.to_ascii_lowercase();
|
||||
self.users
|
||||
.values()
|
||||
.filter(|u| u.watch.contains(&low))
|
||||
.count()
|
||||
self.watch_by.get(&low).map(|s| s.len()).unwrap_or(0)
|
||||
}
|
||||
|
||||
// ── 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).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue