tulpje_cache/
lib.rs

1mod config;
2mod event;
3mod repository;
4
5pub mod models;
6
7use std::{
8    collections::VecDeque,
9    hash::{DefaultHasher, Hash, Hasher as _},
10    ops::Deref as _,
11};
12
13use redis::aio::ConnectionManager;
14use serde::{Deserialize, Serialize};
15use twilight_model::{
16    gateway::event::Event,
17    id::{
18        Id,
19        marker::{
20            ChannelMarker, EmojiMarker, GuildMarker, IntegrationMarker, MessageMarker, RoleMarker,
21            ScheduledEventMarker, StageMarker, StickerMarker, UserMarker,
22        },
23    },
24};
25
26use models::{
27    channel::CachedChannel,
28    emoji::CachedEmoji,
29    guild::CachedGuild,
30    guild_scheduled_event::CachedGuildScheduledEvent,
31    integration::CachedGuildIntegration,
32    member::CachedMember,
33    message::CachedMessage,
34    presence::CachedPresence,
35    role::CachedRole,
36    stage_instance::CachedStageInstance,
37    sticker::CachedSticker,
38    user::{CachedCurrentUser, CachedUser},
39    voice_state::CachedVoiceState,
40};
41use repository::{MappedSetRepository, Repository, SetRepository, SingleRepository};
42
43pub use config::Config;
44pub use twilight_cache_inmemory::Config as TwilightConfig;
45pub use twilight_cache_inmemory::ResourceType;
46
47pub(crate) type Error = Box<dyn std::error::Error + Send + Sync>;
48
49pub struct Cache {
50    pub config: Config,
51
52    pub guilds: Repository<Id<GuildMarker>, CachedGuild>,
53    pub guild_channels: MappedSetRepository<Id<GuildMarker>, Id<ChannelMarker>>,
54    pub guild_scheduled_events: MappedSetRepository<Id<GuildMarker>, Id<ScheduledEventMarker>>,
55    pub guild_integrations: MappedSetRepository<Id<GuildMarker>, Id<IntegrationMarker>>,
56    pub guild_members: MappedSetRepository<Id<GuildMarker>, Id<UserMarker>>,
57    pub guild_presences: MappedSetRepository<Id<GuildMarker>, Id<UserMarker>>,
58    pub guild_emojis: MappedSetRepository<Id<GuildMarker>, Id<EmojiMarker>>,
59    pub guild_roles: MappedSetRepository<Id<GuildMarker>, Id<RoleMarker>>,
60    pub guild_stage_instances: MappedSetRepository<Id<GuildMarker>, Id<StageMarker>>,
61    pub guild_stickers: MappedSetRepository<Id<GuildMarker>, Id<StickerMarker>>,
62    pub unavailable_guilds: SetRepository<Id<GuildMarker>>,
63
64    pub channels: Repository<Id<ChannelMarker>, CachedChannel>,
65    pub channel_messages: Repository<Id<ChannelMarker>, VecDeque<Id<MessageMarker>>>,
66
67    pub scheduled_events: Repository<Id<ScheduledEventMarker>, CachedGuildScheduledEvent>,
68    pub integrations:
69        Repository<(Id<GuildMarker>, Id<IntegrationMarker>), GuildResource<CachedGuildIntegration>>,
70    pub members: Repository<(Id<GuildMarker>, Id<UserMarker>), CachedMember>,
71    pub messages: Repository<Id<MessageMarker>, CachedMessage>,
72    pub presences: Repository<(Id<GuildMarker>, Id<UserMarker>), CachedPresence>,
73    pub emojis: Repository<Id<EmojiMarker>, GuildResource<CachedEmoji>>,
74    pub roles: Repository<Id<RoleMarker>, GuildResource<CachedRole>>,
75    pub stage_instances: Repository<Id<StageMarker>, GuildResource<CachedStageInstance>>,
76    pub stickers: Repository<Id<StickerMarker>, GuildResource<CachedSticker>>,
77
78    pub current_user: SingleRepository<CachedCurrentUser>,
79    pub users: Repository<Id<UserMarker>, CachedUser>,
80    pub user_guilds: MappedSetRepository<Id<UserMarker>, Id<GuildMarker>>,
81
82    pub voice_state_channels:
83        MappedSetRepository<Id<ChannelMarker>, (Id<GuildMarker>, Id<UserMarker>)>,
84    pub voice_state_guilds: MappedSetRepository<Id<GuildMarker>, Id<UserMarker>>,
85    pub voice_states: Repository<(Id<GuildMarker>, Id<UserMarker>), CachedVoiceState>,
86}
87
88pub(crate) fn hash<T: Hash>(val: T) -> u64 {
89    let mut hasher = DefaultHasher::new();
90    val.hash(&mut hasher);
91    hasher.finish()
92}
93
94impl Cache {
95    pub fn new(redis: ConnectionManager, config: Config) -> Self {
96        Self {
97            guilds: Repository::new("guilds", config.wants(ResourceType::GUILD), redis.clone()),
98            guild_channels: MappedSetRepository::new(
99                "guild_channels",
100                config.wants(ResourceType::CHANNEL),
101                redis.clone(),
102            ),
103            guild_scheduled_events: MappedSetRepository::new(
104                "guild_scheduled_events",
105                config
106                    .resource_types
107                    .contains(ResourceType::GUILD_SCHEDULED_EVENT),
108                redis.clone(),
109            ),
110            guild_integrations: MappedSetRepository::new(
111                "guild_integrations",
112                config.wants(ResourceType::INTEGRATION),
113                redis.clone(),
114            ),
115            guild_members: MappedSetRepository::new(
116                "guild_members",
117                config.wants(ResourceType::MEMBER),
118                redis.clone(),
119            ),
120            guild_presences: MappedSetRepository::new(
121                "guild_presences",
122                config.wants(ResourceType::PRESENCE),
123                redis.clone(),
124            ),
125            guild_emojis: MappedSetRepository::new(
126                "guild_emojis",
127                config.wants(ResourceType::EMOJI),
128                redis.clone(),
129            ),
130            guild_roles: MappedSetRepository::new(
131                "guild_roles",
132                config.wants(ResourceType::ROLE),
133                redis.clone(),
134            ),
135            guild_stage_instances: MappedSetRepository::new(
136                "guild_stage_instances",
137                config.wants(ResourceType::STAGE_INSTANCE),
138                redis.clone(),
139            ),
140            guild_stickers: MappedSetRepository::new(
141                "guild_stickers",
142                config.wants(ResourceType::STICKER),
143                redis.clone(),
144            ),
145            unavailable_guilds: SetRepository::new(
146                "unavailable_guilds",
147                config.wants(ResourceType::GUILD),
148                redis.clone(),
149            ),
150
151            channels: Repository::new(
152                "channels",
153                config.wants(ResourceType::CHANNEL),
154                redis.clone(),
155            ),
156            channel_messages: Repository::new(
157                "channel_messages",
158                config.wants(ResourceType::MESSAGE),
159                redis.clone(),
160            ),
161
162            scheduled_events: Repository::new(
163                "scheduled_events",
164                config
165                    .resource_types
166                    .contains(ResourceType::GUILD_SCHEDULED_EVENT),
167                redis.clone(),
168            ),
169            integrations: Repository::new(
170                "integrations",
171                config.wants(ResourceType::INTEGRATION),
172                redis.clone(),
173            ),
174            members: Repository::new("members", config.wants(ResourceType::MEMBER), redis.clone()),
175            messages: Repository::new(
176                "messages",
177                config.wants(ResourceType::MESSAGE),
178                redis.clone(),
179            ),
180            presences: Repository::new(
181                "presences",
182                config.wants(ResourceType::PRESENCE),
183                redis.clone(),
184            ),
185            emojis: Repository::new("emojis", config.wants(ResourceType::EMOJI), redis.clone()),
186
187            roles: Repository::new("roles", config.wants(ResourceType::ROLE), redis.clone()),
188            stage_instances: Repository::new(
189                "stage_instances",
190                config.wants(ResourceType::STAGE_INSTANCE),
191                redis.clone(),
192            ),
193            stickers: Repository::new(
194                "stickers",
195                config.wants(ResourceType::STICKER),
196                redis.clone(),
197            ),
198
199            current_user: SingleRepository::new(
200                "current_user",
201                config.wants(ResourceType::USER_CURRENT),
202                redis.clone(),
203            ),
204            users: Repository::new("emojis", config.wants(ResourceType::USER), redis.clone()),
205            user_guilds: MappedSetRepository::new(
206                "user_guilds",
207                config.wants(ResourceType::USER),
208                redis.clone(),
209            ),
210
211            voice_state_channels: MappedSetRepository::new(
212                "voice_state_channels",
213                config.wants(ResourceType::VOICE_STATE),
214                redis.clone(),
215            ),
216            voice_state_guilds: MappedSetRepository::new(
217                "voice_state_guilds",
218                config.wants(ResourceType::VOICE_STATE),
219                redis.clone(),
220            ),
221            voice_states: Repository::new(
222                "voice_states",
223                config.wants(ResourceType::VOICE_STATE),
224                redis,
225            ),
226
227            config,
228        }
229    }
230
231    pub async fn update(&self, event: &impl UpdateCache) -> Result<(), Error> {
232        event.update(self).await
233    }
234}
235
236impl UpdateCache for Event {
237    async fn update(&self, cache: &Cache) -> Result<(), Error> {
238        #[expect(clippy::use_self, reason = "it's clearer to refer to Event")]
239        match self {
240            Event::ChannelCreate(v) => cache.update(v.deref()).await,
241            Event::ChannelDelete(v) => cache.update(v.deref()).await,
242            Event::ChannelPinsUpdate(v) => cache.update(v).await,
243            Event::ChannelUpdate(v) => cache.update(v.deref()).await,
244            Event::GuildCreate(v) => cache.update(v.deref()).await,
245            Event::GuildDelete(v) => cache.update(v).await,
246            Event::GuildEmojisUpdate(v) => cache.update(v).await,
247            Event::GuildStickersUpdate(v) => cache.update(v).await,
248            Event::GuildUpdate(v) => cache.update(v.deref()).await,
249            Event::GuildScheduledEventCreate(v) => cache.update(v.deref()).await,
250            Event::GuildScheduledEventDelete(v) => cache.update(v.deref()).await,
251            Event::GuildScheduledEventUpdate(v) => cache.update(v.deref()).await,
252            Event::GuildScheduledEventUserAdd(v) => cache.update(v).await,
253            Event::GuildScheduledEventUserRemove(v) => cache.update(v).await,
254            Event::IntegrationCreate(v) => cache.update(v.deref()).await,
255            Event::IntegrationDelete(v) => cache.update(v).await,
256            Event::IntegrationUpdate(v) => cache.update(v.deref()).await,
257            Event::InteractionCreate(v) => cache.update(v.deref()).await,
258            Event::MemberAdd(v) => cache.update(v.deref()).await,
259            Event::MemberRemove(v) => cache.update(v).await,
260            Event::MemberUpdate(v) => cache.update(v.deref()).await,
261            Event::MemberChunk(v) => cache.update(v).await,
262            Event::MessageCreate(v) => cache.update(v.deref()).await,
263            Event::MessageDelete(v) => cache.update(v).await,
264            Event::MessageDeleteBulk(v) => cache.update(v).await,
265            Event::MessageUpdate(v) => cache.update(v.deref()).await,
266            Event::PresenceUpdate(v) => cache.update(v.deref()).await,
267            Event::ReactionAdd(v) => cache.update(v.deref()).await,
268            Event::ReactionRemove(v) => cache.update(v.deref()).await,
269            Event::ReactionRemoveAll(v) => cache.update(v).await,
270            Event::ReactionRemoveEmoji(v) => cache.update(v).await,
271            Event::Ready(v) => cache.update(v).await,
272            Event::RoleCreate(v) => cache.update(v).await,
273            Event::RoleDelete(v) => cache.update(v).await,
274            Event::RoleUpdate(v) => cache.update(v).await,
275            Event::StageInstanceCreate(v) => cache.update(v).await,
276            Event::StageInstanceDelete(v) => cache.update(v).await,
277            Event::StageInstanceUpdate(v) => cache.update(v).await,
278            Event::ThreadCreate(v) => cache.update(v.deref()).await,
279            Event::ThreadUpdate(v) => cache.update(v.deref()).await,
280            Event::ThreadDelete(v) => cache.update(v).await,
281            Event::ThreadListSync(v) => cache.update(v).await,
282            Event::UnavailableGuild(v) => cache.update(v).await,
283            Event::UserUpdate(v) => cache.update(v).await,
284            Event::VoiceStateUpdate(v) => cache.update(v.deref()).await,
285            // Ignored events.
286            Event::AutoModerationActionExecution(_)
287            | Event::AutoModerationRuleCreate(_)
288            | Event::AutoModerationRuleDelete(_)
289            | Event::AutoModerationRuleUpdate(_)
290            | Event::BanAdd(_)
291            | Event::BanRemove(_)
292            | Event::CommandPermissionsUpdate(_)
293            | Event::GatewayClose(_)
294            | Event::GatewayHeartbeat
295            | Event::GatewayHeartbeatAck
296            | Event::GatewayHello(_)
297            | Event::GatewayInvalidateSession(_)
298            | Event::GatewayReconnect
299            | Event::GuildAuditLogEntryCreate(_)
300            | Event::GuildIntegrationsUpdate(_)
301            | Event::InviteCreate(_)
302            | Event::InviteDelete(_)
303            | Event::Resumed
304            | Event::ThreadMembersUpdate(_)
305            | Event::ThreadMemberUpdate(_)
306            | Event::TypingStart(_)
307            | Event::VoiceServerUpdate(_)
308            | Event::WebhooksUpdate(_) => Ok(()),
309            _ => Ok(()), // TODO: Remove this once we've implemented all events
310        }
311    }
312}
313
314pub trait UpdateCache {
315    #[expect(async_fn_in_trait, reason = "honestly, what else do we do")]
316    async fn update(&self, cache: &Cache) -> Result<(), Error>;
317}
318
319#[derive(Serialize, Deserialize)]
320pub struct GuildResource<T> {
321    guild_id: Id<GuildMarker>,
322    value: T,
323}