use std::{
collections::{BTreeMap, HashMap, HashSet},
sync::mpsc::{self, Receiver, Sender},
};
use crate::live::{Interval, Limits, Refused, Subscription};
use crate::utils::Address;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Interest {
Book {
coin: String,
},
Trades {
coin: String,
},
Candles {
coin: String,
interval: Interval,
},
Mids {
dex: String,
},
Context {
coin: String,
},
PerpContexts,
SpotContexts,
State {
user: Address,
},
Orders {
user: Address,
dex: String,
},
Fills {
user: Address,
},
Fundings {
user: Address,
},
Ledger {
user: Address,
},
}
impl Interest {
#[must_use]
pub fn subscriptions(&self) -> Vec<Subscription> {
match self {
Interest::Book { coin } => {
vec![Subscription::L2Book { coin: coin.clone(), n_sig_figs: None, mantissa: None, fast: false }]
}
Interest::Trades { coin } => vec![Subscription::Trades { coin: coin.clone() }],
Interest::Candles { coin, interval } => {
vec![Subscription::Candle { coin: coin.clone(), interval: *interval }]
}
Interest::Mids { dex } => vec![Subscription::AllMids { dex: dex.clone() }],
Interest::Context { coin } => vec![Subscription::ActiveAssetCtx { coin: coin.clone() }],
Interest::PerpContexts => vec![Subscription::AllDexsAssetCtxs],
Interest::SpotContexts => vec![Subscription::SpotAssetCtxs],
Interest::State { user } => vec![
Subscription::AllDexsClearinghouseState { user: *user },
Subscription::SpotState { user: *user, ignore_portfolio_margin: false },
Subscription::WebData3 { user: *user },
],
Interest::Orders { user, dex } => vec![Subscription::OpenOrders { user: *user, dex: dex.clone() }],
Interest::Fills { user } => vec![Subscription::UserFills { user: *user, aggregate_by_time: false }],
Interest::Fundings { user } => vec![Subscription::UserFundings { user: *user }],
Interest::Ledger { user } => vec![Subscription::UserNonFundingLedgerUpdates { user: *user }],
}
}
#[must_use]
pub const fn user(&self) -> Option<Address> {
match self {
Interest::State { user }
| Interest::Orders { user, .. }
| Interest::Fills { user }
| Interest::Fundings { user }
| Interest::Ledger { user } => Some(*user),
_ => None,
}
}
}
#[must_use = "dropping a handle ends its interest"]
#[derive(Debug)]
pub struct Handle {
id: u64,
interest: Interest,
released: Sender<u64>,
}
impl Handle {
#[must_use]
pub const fn interest(&self) -> &Interest {
&self.interest
}
}
impl Drop for Handle {
fn drop(&mut self) {
let _ = self.released.send(self.id);
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Changes {
pub unsubscribe: Vec<Subscription>,
pub subscribe: Vec<Subscription>,
}
impl Changes {
#[must_use]
pub fn is_empty(&self) -> bool {
self.unsubscribe.is_empty() && self.subscribe.is_empty()
}
}
#[derive(Debug)]
pub(crate) struct Interests {
limits: Limits,
next: u64,
held: BTreeMap<u64, Interest>,
counts: HashMap<Subscription, usize>,
order: Vec<Subscription>,
active: Vec<Subscription>,
users: Vec<Address>,
released: Receiver<u64>,
sender: Sender<u64>,
}
impl Interests {
pub(crate) fn new(limits: Limits) -> Interests {
let (sender, released) = mpsc::channel();
Interests {
limits,
next: 1,
held: BTreeMap::new(),
counts: HashMap::new(),
order: Vec::new(),
active: Vec::new(),
users: Vec::new(),
released,
sender,
}
}
pub(crate) fn set_limits(&mut self, limits: Limits) {
self.limits = limits;
}
pub(crate) fn register(&mut self, interest: Interest) -> Result<Handle, Refused> {
let needed = interest.subscriptions();
let mut new: Vec<&Subscription> = Vec::new();
for subscription in &needed {
subscription.check()?;
if !self.counts.contains_key(subscription) && !new.contains(&subscription) {
new.push(subscription);
}
}
if self.order.len().saturating_add(new.len()) > self.limits.subscriptions {
return Err(Refused::Subscriptions { limit: self.limits.subscriptions });
}
if let Some(user) = interest.user()
&& !self.users.contains(&user)
{
if self.users.len() >= self.limits.users {
return Err(Refused::Users { limit: self.limits.users });
}
self.users.push(user);
}
for subscription in needed {
let count = self.counts.entry(subscription.clone()).or_insert(0);
if *count == 0 {
self.order.push(subscription);
}
*count = count.saturating_add(1);
}
let id = self.next;
self.next = self.next.saturating_add(1);
self.held.insert(id, interest.clone());
Ok(Handle { id, interest, released: self.sender.clone() })
}
pub(crate) fn drain(&mut self) -> bool {
let mut any = false;
while let Ok(id) = self.released.try_recv() {
let Some(interest) = self.held.remove(&id) else { continue };
any = true;
for subscription in interest.subscriptions() {
let Some(count) = self.counts.get_mut(&subscription) else { continue };
*count = count.saturating_sub(1);
if *count == 0 {
self.counts.remove(&subscription);
self.order.retain(|wanted| *wanted != subscription);
}
}
}
let order = &self.order;
self.users.retain(|user| order.iter().any(|wanted| wanted.user() == Some(*user)));
any
}
pub(crate) fn wants(&self, subscription: &Subscription) -> bool {
self.counts.contains_key(subscription)
}
pub(crate) fn wanted(&self) -> &[Subscription] {
&self.order
}
pub(crate) fn changes(&mut self) -> Changes {
let held: HashSet<&Subscription> = self.active.iter().collect();
let unsubscribe = self.active.iter().filter(|held| !self.counts.contains_key(*held)).cloned().collect();
let subscribe = self.order.iter().filter(|wanted| !held.contains(wanted)).cloned().collect();
self.active.clone_from(&self.order);
Changes { unsubscribe, subscribe }
}
pub(crate) fn refused(&mut self, subscription: &Subscription) {
self.active.retain(|held| held != subscription);
}
pub(crate) fn users(&self) -> &[Address] {
&self.users
}
}