1use dashmap::DashMap;
6
7use titanium_model::{Channel, Guild, GuildMember, Role, Snowflake, User};
8
9pub trait Cache: Send + Sync {
11 fn guild(&self, id: Snowflake) -> Option<Guild<'static>>;
12 fn channel(&self, id: Snowflake) -> Option<Channel<'static>>;
13 fn user(&self, id: Snowflake) -> Option<User<'static>>;
14 fn member(&self, guild_id: Snowflake, user_id: Snowflake) -> Option<GuildMember<'static>>;
15 fn role(&self, id: Snowflake) -> Option<Role<'static>>;
16
17 fn insert_guild(&self, guild: Guild<'static>);
18 fn insert_channel(&self, channel: Channel<'static>);
19 fn insert_user(&self, user: User<'static>);
20 fn insert_member(&self, guild_id: Snowflake, member: GuildMember<'static>);
21 fn insert_role(&self, id: Snowflake, role: Role<'static>);
22}
23
24use std::time::{Duration, Instant};
25
26struct CachedItem<T> {
28 value: T,
29 created_at: Instant,
30}
31
32impl<T> CachedItem<T> {
33 fn new(value: T) -> Self {
34 Self {
35 value,
36 created_at: Instant::now(),
37 }
38 }
39
40 fn is_expired(&self, ttl: Duration) -> bool {
41 self.created_at.elapsed() > ttl
42 }
43}
44
45pub struct InMemoryCache {
47 guilds: DashMap<Snowflake, CachedItem<Guild<'static>>>,
48 channels: DashMap<Snowflake, CachedItem<Channel<'static>>>,
49 users: DashMap<Snowflake, CachedItem<User<'static>>>,
50 members: DashMap<(Snowflake, Snowflake), CachedItem<GuildMember<'static>>>,
51 roles: DashMap<Snowflake, CachedItem<Role<'static>>>,
52 ttl: Duration,
53}
54
55impl InMemoryCache {
56 pub fn new() -> Self {
58 Self::with_ttl(Duration::from_secs(3600))
59 }
60
61 pub fn with_ttl(ttl: Duration) -> Self {
63 Self {
64 guilds: DashMap::new(),
65 channels: DashMap::new(),
66 users: DashMap::new(),
67 members: DashMap::new(),
68 roles: DashMap::new(),
69 ttl,
70 }
71 }
72
73 pub fn sweep(&self) -> usize {
77 let count = 0;
78 let ttl = self.ttl;
79
80 self.guilds.retain(|_, v| !v.is_expired(ttl));
81 self.channels.retain(|_, v| !v.is_expired(ttl));
82 self.users.retain(|_, v| !v.is_expired(ttl));
83 self.members.retain(|_, v| !v.is_expired(ttl));
84 self.roles.retain(|_, v| !v.is_expired(ttl));
85
86 count
91 }
92}
93
94impl Cache for InMemoryCache {
95 fn guild(&self, id: Snowflake) -> Option<Guild<'static>> {
96 self.guilds
97 .get(&id)
98 .filter(|i| !i.is_expired(self.ttl))
99 .map(|r| r.value.clone())
100 }
101
102 fn channel(&self, id: Snowflake) -> Option<Channel<'static>> {
103 self.channels
104 .get(&id)
105 .filter(|i| !i.is_expired(self.ttl))
106 .map(|r| r.value.clone())
107 }
108
109 fn user(&self, id: Snowflake) -> Option<User<'static>> {
110 self.users
111 .get(&id)
112 .filter(|i| !i.is_expired(self.ttl))
113 .map(|r| r.value.clone())
114 }
115
116 fn member(&self, guild_id: Snowflake, user_id: Snowflake) -> Option<GuildMember<'static>> {
117 self.members
118 .get(&(guild_id, user_id))
119 .filter(|i| !i.is_expired(self.ttl))
120 .map(|r| r.value.clone())
121 }
122
123 fn role(&self, id: Snowflake) -> Option<Role<'static>> {
124 self.roles
125 .get(&id)
126 .filter(|i| !i.is_expired(self.ttl))
127 .map(|r| r.value.clone())
128 }
129
130 fn insert_guild(&self, guild: Guild<'static>) {
131 for role in &guild.roles {
132 self.insert_role(role.id, role.clone());
133 }
134 self.guilds.insert(guild.id, CachedItem::new(guild));
135 }
136
137 fn insert_channel(&self, channel: Channel<'static>) {
138 self.channels.insert(channel.id, CachedItem::new(channel));
139 }
140
141 fn insert_user(&self, user: User<'static>) {
142 self.users.insert(user.id, CachedItem::new(user));
143 }
144
145 fn insert_member(&self, guild_id: Snowflake, member: GuildMember<'static>) {
146 if let Some(ref user) = member.user {
147 self.insert_user(user.clone());
148 }
149 self.members.insert(
150 (
151 guild_id,
152 member.user.as_ref().map(|u| u.id).unwrap_or_default(),
153 ),
154 CachedItem::new(member),
155 );
156 }
157
158 fn insert_role(&self, id: Snowflake, role: Role<'static>) {
159 self.roles.insert(id, CachedItem::new(role));
160 }
161}
162
163impl Default for InMemoryCache {
164 fn default() -> Self {
165 Self::new()
166 }
167}