1use dashmap::DashMap;
36use std::sync::Arc;
37use std::time::{Duration, Instant};
38
39use titanium_model::{Channel, Guild, GuildMember, Role, Snowflake, User};
40
41pub trait Cache: Send + Sync {
46 fn guild(&self, id: Snowflake) -> Option<Arc<Guild<'static>>>;
48 fn channel(&self, id: Snowflake) -> Option<Arc<Channel<'static>>>;
50 fn user(&self, id: Snowflake) -> Option<Arc<User<'static>>>;
52 fn member(&self, guild_id: Snowflake, user_id: Snowflake) -> Option<Arc<GuildMember<'static>>>;
54 fn role(&self, id: Snowflake) -> Option<Arc<Role<'static>>>;
56
57 fn insert_guild(&self, guild: Arc<Guild<'static>>);
59 fn insert_channel(&self, channel: Arc<Channel<'static>>);
61 fn insert_user(&self, user: Arc<User<'static>>);
63 fn insert_member(&self, guild_id: Snowflake, member: Arc<GuildMember<'static>>);
65 fn insert_role(&self, id: Snowflake, role: Arc<Role<'static>>);
67
68 fn remove_guild(&self, id: Snowflake) -> Option<Arc<Guild<'static>>>;
70 fn remove_channel(&self, id: Snowflake) -> Option<Arc<Channel<'static>>>;
72 fn remove_user(&self, id: Snowflake) -> Option<Arc<User<'static>>>;
74 fn remove_member(
76 &self,
77 guild_id: Snowflake,
78 user_id: Snowflake,
79 ) -> Option<Arc<GuildMember<'static>>>;
80 fn remove_role(&self, id: Snowflake) -> Option<Arc<Role<'static>>>;
82}
83
84struct CachedItem<T> {
86 value: T,
87 created_at: Instant,
88}
89
90impl<T> CachedItem<T> {
91 fn new(value: T) -> Self {
92 Self {
93 value,
94 created_at: Instant::now(),
95 }
96 }
97
98 fn is_expired(&self, ttl: Duration) -> bool {
99 self.created_at.elapsed() > ttl
100 }
101}
102
103pub struct InMemoryCache {
114 guilds: DashMap<Snowflake, CachedItem<Arc<Guild<'static>>>>,
115 channels: DashMap<Snowflake, CachedItem<Arc<Channel<'static>>>>,
116 users: DashMap<Snowflake, CachedItem<Arc<User<'static>>>>,
117 members: DashMap<(Snowflake, Snowflake), CachedItem<Arc<GuildMember<'static>>>>,
118 roles: DashMap<Snowflake, CachedItem<Arc<Role<'static>>>>,
119 ttl: Duration,
120}
121
122impl InMemoryCache {
123 pub fn new() -> Self {
125 Self::with_ttl(Duration::from_secs(3600))
126 }
127
128 pub fn with_ttl(ttl: Duration) -> Self {
130 Self {
131 guilds: DashMap::new(),
132 channels: DashMap::new(),
133 users: DashMap::new(),
134 members: DashMap::new(),
135 roles: DashMap::new(),
136 ttl,
137 }
138 }
139
140 pub fn sweep(&self) -> usize {
144 let ttl = self.ttl;
145 let before = self.guilds.len()
146 + self.channels.len()
147 + self.users.len()
148 + self.members.len()
149 + self.roles.len();
150
151 self.guilds.retain(|_, v| !v.is_expired(ttl));
152 self.channels.retain(|_, v| !v.is_expired(ttl));
153 self.users.retain(|_, v| !v.is_expired(ttl));
154 self.members.retain(|_, v| !v.is_expired(ttl));
155 self.roles.retain(|_, v| !v.is_expired(ttl));
156
157 let after = self.guilds.len()
158 + self.channels.len()
159 + self.users.len()
160 + self.members.len()
161 + self.roles.len();
162 before.saturating_sub(after)
163 }
164}
165
166impl Cache for InMemoryCache {
167 fn guild(&self, id: Snowflake) -> Option<Arc<Guild<'static>>> {
168 self.guilds
169 .get(&id)
170 .filter(|i| !i.is_expired(self.ttl))
171 .map(|r| r.value.clone())
172 }
173
174 fn channel(&self, id: Snowflake) -> Option<Arc<Channel<'static>>> {
175 self.channels
176 .get(&id)
177 .filter(|i| !i.is_expired(self.ttl))
178 .map(|r| r.value.clone())
179 }
180
181 fn user(&self, id: Snowflake) -> Option<Arc<User<'static>>> {
182 self.users
183 .get(&id)
184 .filter(|i| !i.is_expired(self.ttl))
185 .map(|r| r.value.clone())
186 }
187
188 fn member(&self, guild_id: Snowflake, user_id: Snowflake) -> Option<Arc<GuildMember<'static>>> {
189 self.members
190 .get(&(guild_id, user_id))
191 .filter(|i| !i.is_expired(self.ttl))
192 .map(|r| r.value.clone())
193 }
194
195 fn role(&self, id: Snowflake) -> Option<Arc<Role<'static>>> {
196 self.roles
197 .get(&id)
198 .filter(|i| !i.is_expired(self.ttl))
199 .map(|r| r.value.clone())
200 }
201
202 fn insert_guild(&self, guild: Arc<Guild<'static>>) {
203 for role in &guild.roles {
204 self.insert_role(role.id, Arc::new(role.clone()));
205 }
206 self.guilds.insert(guild.id, CachedItem::new(guild));
207 }
208
209 fn insert_channel(&self, channel: Arc<Channel<'static>>) {
210 self.channels.insert(channel.id, CachedItem::new(channel));
211 }
212
213 fn insert_user(&self, user: Arc<User<'static>>) {
214 self.users.insert(user.id, CachedItem::new(user));
215 }
216
217 fn insert_member(&self, guild_id: Snowflake, member: Arc<GuildMember<'static>>) {
218 if let Some(ref user) = member.user {
219 self.insert_user(Arc::new(user.clone()));
220 }
221 self.members.insert(
222 (
223 guild_id,
224 member.user.as_ref().map(|u| u.id).unwrap_or_default(),
225 ),
226 CachedItem::new(member),
227 );
228 }
229
230 fn insert_role(&self, id: Snowflake, role: Arc<Role<'static>>) {
231 self.roles.insert(id, CachedItem::new(role));
232 }
233
234 fn remove_guild(&self, id: Snowflake) -> Option<Arc<Guild<'static>>> {
235 self.guilds.remove(&id).map(|(_, v)| v.value)
236 }
237
238 fn remove_channel(&self, id: Snowflake) -> Option<Arc<Channel<'static>>> {
239 self.channels.remove(&id).map(|(_, v)| v.value)
240 }
241
242 fn remove_user(&self, id: Snowflake) -> Option<Arc<User<'static>>> {
243 self.users.remove(&id).map(|(_, v)| v.value)
244 }
245
246 fn remove_member(
247 &self,
248 guild_id: Snowflake,
249 user_id: Snowflake,
250 ) -> Option<Arc<GuildMember<'static>>> {
251 self.members
252 .remove(&(guild_id, user_id))
253 .map(|(_, v)| v.value)
254 }
255
256 fn remove_role(&self, id: Snowflake) -> Option<Arc<Role<'static>>> {
257 self.roles.remove(&id).map(|(_, v)| v.value)
258 }
259}
260
261impl Default for InMemoryCache {
262 fn default() -> Self {
263 Self::new()
264 }
265}