add cache lmao

This commit is contained in:
Yuzu 2025-04-13 00:21:34 -05:00
parent 52a0394603
commit fb8ce159d3
9 changed files with 543 additions and 8 deletions

View File

@ -39,6 +39,7 @@ pub fn main() !void {
.intents = Discord.Intents.fromRaw(53608447),
.token = std.posix.getenv("DISCORD_TOKEN").?,
.run = .{ .message_create = &message_create, .ready = &ready },
.cache = Discord.CacheTables,
});
}
```

View File

@ -28,10 +28,7 @@
.url = "https://github.com/yuzudev/zig-zlib/archive/refs/heads/main.zip",
.hash = "zlib-0.1.0-AAAAAKm6QADNBB6NBPHanW9G0EOPTmgJsRO6NFT__arp",
},
.websocket = .{
.url = "https://github.com/karlseguin/websocket.zig/archive/refs/heads/master.zip",
.hash = "websocket-0.1.0-ZPISdYBIAwB1yO6AFDHRHLaZSmpdh4Bz4dCmaQUqNNWh"
},
.websocket = .{ .url = "https://github.com/karlseguin/websocket.zig/archive/refs/heads/master.zip", .hash = "websocket-0.1.0-ZPISdYBIAwB1yO6AFDHRHLaZSmpdh4Bz4dCmaQUqNNWh" },
},
.paths = .{
"build.zig",

185
src/cache.zig Normal file
View File

@ -0,0 +1,185 @@
const std = @import("std");
// by default this caches everything
// therefore we'll allow custom cache tables
pub const CacheTables = struct {
const Snowflake = @import("./structures/snowflake.zig").Snowflake;
const Types = @import("./structures/types.zig");
const StoredUser = @import("./config.zig").StoredUser;
const StoredGuild = @import("./config.zig").StoredGuild;
const StoredChannel = @import("./config.zig").StoredChannel;
const StoredEmoji = @import("./config.zig").StoredEmoji;
const StoredMessage = @import("./config.zig").StoredMessage;
const StoredRole = @import("./config.zig").StoredRole;
const StoredSticker = @import("./config.zig").StoredSticker;
const StoredReaction = @import("./config.zig").StoredReaction;
const StoredMember = @import("./config.zig").StoredMember;
users: CacheLike(Snowflake, StoredUser),
guilds: CacheLike(Snowflake, StoredGuild),
channels: CacheLike(Snowflake, StoredChannel),
emojis: CacheLike(Snowflake, StoredEmoji),
messages: CacheLike(Snowflake, StoredMessage),
roles: CacheLike(Snowflake, StoredRole),
stickers: CacheLike(Snowflake, StoredSticker),
reactions: CacheLike(Snowflake, StoredReaction),
members: CacheLike(Snowflake, StoredMember),
threads: CacheLike(Snowflake, StoredChannel),
/// you can customize with your own cache
pub fn defaults(allocator: std.mem.Allocator) CacheTables {
var users = DefaultCache(Snowflake, StoredUser).init(allocator);
var guilds = DefaultCache(Snowflake, StoredGuild).init(allocator);
var channels = DefaultCache(Snowflake, StoredChannel).init(allocator);
var emojis = DefaultCache(Snowflake, StoredEmoji).init(allocator);
var messages = DefaultCache(Snowflake, StoredMessage).init(allocator);
var roles = DefaultCache(Snowflake, StoredRole).init(allocator);
var stickers = DefaultCache(Snowflake, StoredSticker).init(allocator);
var reactions = DefaultCache(Snowflake, StoredReaction).init(allocator);
var members = DefaultCache(Snowflake, StoredMember).init(allocator);
var threads = DefaultCache(Snowflake, StoredChannel).init(allocator);
return .{
.users = users.cache(),
.guilds = guilds.cache(),
.channels = channels.cache(),
.emojis = emojis.cache(),
.messages = messages.cache(),
.roles = roles.cache(),
.stickers = stickers.cache(),
.reactions = reactions.cache(),
.members = members.cache(),
.threads = threads.cache(),
};
}
};
pub fn CacheLike(comptime K: type, comptime V: type) type {
return struct {
ptr: *anyopaque,
putFn: *const fn(*anyopaque, K, V) anyerror!void,
getFn: *const fn(*anyopaque, K) ?V,
removeFn: *const fn(*anyopaque, K) void,
containsFn: *const fn(*anyopaque, K) bool,
countFn: *const fn(*anyopaque) usize,
pub fn put(self: CacheLike(K, V), key: K, value: V) !void {
self.putFn(self.ptr, key, value);
}
pub fn get(self: CacheLike(K, V), key: K) ?V {
return self.getFn(self.ptr, key);
}
pub fn remove(self: CacheLike(K, V), key: K) void {
self.removeFn(self.ptr, key);
}
pub fn contains(self: CacheLike(K, V), key: K) bool {
return self.containsFn(self.ptr, key);
}
pub fn count(self: CacheLike(K, V)) usize {
return self.countFn(self.ptr);
}
pub fn init(ptr: *const anyopaque) CacheLike(K, V) {
const T = @TypeOf(ptr);
const ptr_info = @typeInfo(T);
const gen = struct {
// we don't care about order
map: std.AutoHashMap(K, V),
allocator: *std.mem.Allocator,
pub fn put(pointer: *anyopaque, key: K, value: V) anyerror!void {
const self: T = @ptrCast(@alignCast(pointer));
return ptr_info.pointer.child.put(self, key, value);
}
pub fn get(pointer: *anyopaque, key: K) ?V {
const self: T = @ptrCast(@alignCast(pointer));
return ptr_info.pointer.child.get(self, key);
}
pub fn remove(pointer: *anyopaque, key: K) void {
const self: T = @ptrCast(@alignCast(pointer));
return ptr_info.pointer.child.remove(self, key);
}
pub fn contains(pointer: *anyopaque, key: K) bool {
const self: T = @ptrCast(@alignCast(pointer));
return ptr_info.pointer.child.contains(self, key);
}
pub fn count(pointer: *anyopaque) usize {
const self: T = @ptrCast(@alignCast(pointer));
return ptr_info.pointer.child.count(self);
}
};
return .{
.ptr = ptr,
.putFn = gen.put,
.getFn = gen.get,
.removeFn = gen.remove,
.containsFn = gen.contains,
.countFn = gen.count,
};
}
};
}
// make a cache that uses a hash map
// must have putFn, getFn, removeFn, etc
// must have a cache() function to return the interface
pub fn DefaultCache(comptime K: type, comptime V: type) type {
return struct {
const Self = @This();
allocator: std.mem.Allocator,
map: std.AutoHashMap(K, V),
pub fn init(allocator: std.mem.Allocator) DefaultCache(K, V) {
return .{ .allocator = allocator, .map = .init(allocator) };
}
pub fn cache(self: *Self) CacheLike(K, V) {
return .{
.ptr = self,
.putFn = put,
.getFn = get,
.removeFn = remove,
.containsFn = contains,
.countFn = count,
};
}
pub fn put(ptr: *anyopaque, key: K, value: V) !void {
const self: *Self = @ptrCast(@alignCast(ptr));
return self.map.put(key, value);
}
pub fn get(ptr: *anyopaque, key: K) ?V {
const self: *Self = @ptrCast(@alignCast(ptr));
return self.map.get(key);
}
pub fn remove(ptr: *anyopaque, key: K) void {
const self: *Self = @ptrCast(@alignCast(ptr));
_ = self.map.remove(key);
}
pub fn contains(ptr: *anyopaque, key: K) bool {
const self: *Self = @ptrCast(@alignCast(ptr));
return self.map.contains(key);
}
pub fn count(ptr: *anyopaque) usize {
const self: *Self = @ptrCast(@alignCast(ptr));
return self.map.count();
}
};
}

274
src/config.zig Normal file
View File

@ -0,0 +1,274 @@
// custom file for configurations you might want
const PremiumTypes = @import("./structures/shared.zig").PremiumTypes;
const Snowflake = @import("./structures/snowflake.zig").Snowflake;
const AvatarDecorationData = @import("./structures/user.zig").AvatarDecorationData;
/// https://discord.com/developers/docs/resources/user#user-object
/// modify this to your liking
pub const StoredUser = struct {
username: []const u8,
global_name: ?[]const u8 = null,
locale: ?[]const u8 = null,
flags: ?isize = null,
premium_type: ?PremiumTypes = null,
public_flags: ?isize = null,
accent_color: ?isize = null,
id: Snowflake,
discriminator: []const u8,
avatar: ?[]const u8 = null,
bot: ?bool = null,
system: ?bool = null,
mfa_enabled: ?bool = null,
verified: ?bool = null,
email: ?[]const u8 = null,
banner: ?[]const u8 = null,
avatar_decoration_data: ?AvatarDecorationData = null,
clan: ?[]const u8 = null,
};
const VerificationLevels = @import("./structures/shared.zig").VerificationLevels;
const DefaultMessageNotificationLevels = @import("./structures/shared.zig").DefaultMessageNotificationLevels;
const ExplicitContentFilterLevels = @import("./structures/shared.zig").ExplicitContentFilterLevels;
const GuildFeatures = @import("./structures/shared.zig").GuildFeatures;
const MfaLevels = @import("./structures/shared.zig").MfaLevels;
const SystemChannelFlags = @import("./structures/shared.zig").SystemChannelFlags;
const PremiumTiers = @import("./structures/shared.zig").PremiumTiers;
const GuildNsfwLevel = @import("./structures/shared.zig").GuildNsfwLevel;
const StageInstance = @import("./structures/channel.zig").StageInstance;
const WelcomeScreen = @import("./structures/channel.zig").WelcomeScreen;
/// https://discord.com/developers/docs/resources/guild#guild-object
/// modify this to your liking
pub const StoredGuild = struct {
name: []const u8,
widget_enabled: ?bool = null,
verification_level: VerificationLevels,
default_message_notifications: DefaultMessageNotificationLevels,
explicit_content_filter: ExplicitContentFilterLevels,
features: []GuildFeatures,
mfa_level: MfaLevels,
system_channel_flags: SystemChannelFlags,
large: ?bool = null,
unavailable: ?bool = null,
member_count: ?isize = null,
max_presences: ?isize = null,
max_members: ?isize = null,
vanity_url_code: ?[]const u8 = null,
description: ?[]const u8 = null,
premium_tier: PremiumTiers,
premium_subscription_count: ?isize = null,
/// returned from the GET /guilds/id endpoint when with_counts is true
approximate_member_count: ?isize = null,
/// returned from the GET /guilds/id endpoint when with_counts is true
approximate_presence_count: ?isize = null,
nsfw_level: GuildNsfwLevel,
id: Snowflake,
icon: ?[]const u8 = null,
icon_hash: ?[]const u8 = null,
splash: ?[]const u8 = null,
discovery_splash: ?[]const u8 = null,
owner_id: Snowflake,
permissions: ?[]const u8 = null,
afk_channel_id: ?Snowflake = null,
widget_channel_id: ?Snowflake = null,
application_id: ?Snowflake = null,
system_channel_id: ?Snowflake = null,
rules_channel_id: ?Snowflake = null,
banner: ?[]const u8 = null,
preferred_locale: []const u8,
public_updates_channel_id: ?Snowflake = null,
welcome_screen: ?WelcomeScreen = null,
stage_instances: ?[]StageInstance = null,
safety_alerts_channel_id: ?Snowflake = null,
};
const ChannelTypes = @import("./structures/shared.zig").ChannelTypes;
const Overwrite = @import("./structures/channel.zig").Overwrite;
const VideoQualityModes = @import("./structures/shared.zig").VideoQualityModes;
const ThreadMetadata = @import("./structures/thread.zig").ThreadMetadata;
const ChannelFlags = @import("./structures/shared.zig").ChannelFlags;
const ForumTag = @import("./structures/channel.zig").ForumTag;
const DefaultReactionEmoji = @import("./structures/channel.zig").DefaultReactionEmoji;
const SortOrderTypes = @import("./structures/shared.zig").SortOrderTypes;
const ForumLayout = @import("./structures/shared.zig").ForumLayout;
/// https://discord.com/developers/docs/resources/channel#channel-object
/// modify this to your liking
pub const StoredChannel = struct {
id: Snowflake,
type: ChannelTypes,
guild_id: ?Snowflake = null,
position: ?isize = null,
permission_overwrites: ?[]Overwrite = null,
name: ?[]const u8 = null,
topic: ?[]const u8 = null,
nsfw: ?bool = null,
user_limit: ?isize = null,
icon: ?[]const u8 = null,
owner_id: ?Snowflake = null,
application_id: ?Snowflake = null,
managed: ?bool = null,
parent_id: ?Snowflake = null,
last_pin_timestamp: ?[]const u8 = null,
rtc_region: ?[]const u8 = null,
video_quality_mode: ?VideoQualityModes = null,
message_count: ?isize = null,
member_count: ?isize = null,
/// Thread-specific fields not needed by other channels
/// TODO: optimise this
thread_metadata: ?ThreadMetadata = null,
/// threads
default_auto_archive_duration: ?isize = null,
permissions: ?[]const u8 = null,
flags: ?ChannelFlags = null,
total_message_sent: ?isize = null,
/// forum channels
available_tags: ?[]ForumTag = null,
/// forum channels
applied_tags: ?[][]const u8 = null,
/// forum channels
default_reaction_emoji: ?DefaultReactionEmoji = null,
/// threads and channels
default_thread_rate_limit_per_user: ?isize = null,
/// forum channels
default_sort_order: ?SortOrderTypes = null,
/// forum channels
default_forum_layout: ?ForumLayout = null,
/// When a thread is created this will be true on that channel payload for the thread.
/// TODO: optimise this
newly_created: ?bool = null,
};
/// https://discord.com/developers/docs/resources/emoji#emoji-object-emoji-structure
/// modifyus to your liking
pub const StoredEmoji = struct {
name: ?[]const u8 = null,
id: ?Snowflake = null,
roles: ?[][]const u8 = null,
user_id: ?Snowflake = null,
require_colons: ?bool = null,
managed: ?bool = null,
animated: ?bool = null,
available: ?bool = null,
};
const MessageTypes = @import("./structures/shared.zig").MessageTypes;
const Attachment = @import("./structures/attachment.zig").Attachment;
const Embed = @import("./structures/embed.zig").Embed;
const ChannelMention = @import("./structures/message.zig").ChannelMention;
const MessageActivity = @import("./structures/message.zig").MessageActivity;
const MessageFlags = @import("./structures/shared.zig").MessageFlags;
const StickerItem = @import("./structures/sticker.zig").StickerItem;
const Poll = @import("./structures/poll.zig").Poll;
const MessageCall = @import("./structures/message.zig").MessageCall;
/// https://discord.com/developers/docs/resources/channel#message-object
/// modify this to your liking
pub const StoredMessage = struct {
id: Snowflake,
channel_id: Snowflake,
guild_id: ?Snowflake = null,
author_id: Snowflake,
member_id: ?Snowflake = null,
content: ?[]const u8 = null,
timestamp: []const u8,
edited_timestamp: ?[]const u8 = null,
tts: bool,
mention_everyone: bool,
mentions: []Snowflake,
mention_roles: ?[][]const u8 = null,
mention_channels: ?[]ChannelMention = null,
attachments: []Attachment,
embeds: []Embed,
reactions: ?[]Snowflake = null,
// Used for validating a message was sent
// nonce: ?union(enum) {int: isize,string: []const u8,} = null,
pinned: bool,
webhook_id: ?Snowflake = null,
type: MessageTypes,
// interactions or webhooks
application_id: ?Snowflake = null,
// Data showing the source of a crosspost, channel follow add, pin, or reply message
// message_reference: ?Omit(MessageReference, .{"failIfNotExists"}) = null,
flags: ?MessageFlags = null,
referenced_message_id: ?Snowflake = null,
// The thread that was started from this message, includes thread member object
// thread: ?Omit(Channel, .{"member"}), //& { member: ThreadMember }; = null,
components: ?[]Snowflake = null,
sticker_items: ?[]StickerItem = null,
/// threads, may be deleted?
position: ?isize = null,
poll: ?Poll = null,
call: ?MessageCall = null,
};
const RoleFlags = @import("./structures/shared.zig").RoleFlags;
const RoleTags = @import("./structures/role.zig").RoleTags;
/// https://discord.com/developers/docs/topics/permissions#role-object-role-structure
/// modify this to your liking
pub const StoredRole = struct {
id: Snowflake,
hoist: bool,
permissions: []const u8,
managed: bool,
mentionable: bool,
tags: ?RoleTags = null,
icon: ?[]const u8 = null,
name: []const u8,
color: isize,
position: isize,
unicode_emoji: ?[]const u8 = null,
flags: RoleFlags,
};
const StickerTypes = @import("./structures/shared.zig").StickerTypes;
const StickerFormatTypes = @import("./structures/shared.zig").StickerFormatTypes;
/// https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-structure
/// I don't know why you'd cache a sticker, but I deliver what I promise
pub const StoredSticker = struct {
id: Snowflake,
pack_id: ?Snowflake = null,
name: []const u8,
description: []const u8,
tags: []const u8,
type: StickerTypes,
format_type: StickerFormatTypes,
available: ?bool = null,
guild_id: ?Snowflake = null,
user_id: ?Snowflake = null,
sort_value: ?isize = null,
};
const ReactionCountDetails = @import("./structures/message.zig").ReactionCountDetails;
/// https://discord.com/developers/docs/resources/channel#reaction-object
/// This is actually beneficial to cache
pub const StoredReaction = struct {
count: isize,
count_details: ReactionCountDetails,
me: bool,
me_burst: bool,
emoji_id: []const u8,
burst_colors: [][]const u8,
};
/// https://discord.com/developers/docs/resources/guild#guild-member-object
/// modify this to your liking
pub const StoredMember = struct {
pending: ?bool = null,
user_id: ?Snowflake = null,
nick: ?[]const u8 = null,
avatar: ?[]const u8 = null,
roles: [][]const u8,
joined_at: []const u8,
premium_since: ?[]const u8 = null,
permissions: ?[]const u8 = null,
flags: isize,
avatar_decoration_data: ?AvatarDecorationData = null,
};

View File

@ -44,6 +44,7 @@ workers: std.Thread.Pool = undefined,
/// configuration settings
options: SessionOptions,
log: Log,
cache: @import("cache.zig").CacheTables,
pub const ShardData = struct {
/// resume seq to resume connections
@ -80,6 +81,7 @@ pub fn init(allocator: mem.Allocator, settings: struct {
options: SessionOptions,
run: GatewayDispatchEvent(*Shard),
log: Log,
cache: @import("cache.zig").CacheTables,
}) mem.Allocator.Error!Self {
const concurrency = settings.options.info.session_start_limit.?.max_concurrency;
return .{
@ -104,6 +106,7 @@ pub fn init(allocator: mem.Allocator, settings: struct {
.workers_per_shard = settings.options.workers_per_shard,
},
.log = settings.log,
.cache = settings.cache,
};
}
@ -180,6 +183,7 @@ fn create(self: *Self, shard_id: usize) !Shard {
},
.run = self.handler,
.log = self.log,
.cache = self.cache,
.sharder_pool = &self.workers,
});

View File

@ -163,8 +163,6 @@ pub const ModifyGuildChannelPositions = @import("structures/types.zig").ModifyGu
pub const CreateChannelInvite = @import("structures/types.zig").CreateChannelInvite;
pub const ApplicationCommand = @import("structures/types.zig").ApplicationCommand;
pub const CreateApplicationCommand = @import("structures/types.zig").CreateApplicationCommand;
pub const LocaleMap = @import("structures/types.zig").LocaleMap;
pub const InteractionEntryPointCommandHandlerType = @import("structures/types.zig").InteractionEntryPointCommandHandlerType;
pub const ApplicationCommandOption = @import("structures/types.zig").ApplicationCommandOption;
pub const ApplicationCommandOptionChoice = @import("structures/types.zig").ApplicationCommandOptionChoice;
pub const GuildApplicationCommandPermissions = @import("structures/types.zig").GuildApplicationCommandPermissions;
@ -309,8 +307,13 @@ pub const ApplicationWebhook = @import("structures/types.zig").ApplicationWebhoo
pub const GatewayPayload = @import("structures/types.zig").GatewayPayload;
// END USING NAMESPACE
pub const CacheTables = @import("cache.zig").CacheTables;
pub const CacheLike = @import("cache.zig").CacheLike;
pub const DefaultCache = @import("cache.zig").DefaultCache;
pub const Permissions = @import("extra/permissions.zig").Permissions;
pub const Shard = @import("shard.zig");
pub const zjson = @compileError("Deprecated, use std.json instead.");
pub const zjson = @compileError("Deprecated.");
pub const Internal = @import("internal.zig");
const GatewayDispatchEvent = Internal.GatewayDispatchEvent;
@ -357,6 +360,7 @@ pub fn start(self: *Self, settings: struct {
},
run: GatewayDispatchEvent(*Shard),
log: Log,
cache: @import("cache.zig").CacheTables,
}) !void {
self.token = settings.token;
var req = FetchReq.init(self.allocator, settings.token);
@ -376,7 +380,7 @@ pub fn start(self: *Self, settings: struct {
self.sharder = try Sharder.init(self.allocator, .{
.token = settings.token,
.intents = settings.intents,
.intents = settings.intents,
.run = settings.run,
.options = SessionOptions{
.info = parsed.value,
@ -386,6 +390,7 @@ pub fn start(self: *Self, settings: struct {
.spawn_shard_delay = settings.options.spawn_shard_delay,
},
.log = settings.log,
.cache = settings.cache,
});
try self.sharder.spawnShards();

65
src/extra/permissions.zig Normal file
View File

@ -0,0 +1,65 @@
const BitwisePermissionFlags = @import("../structures/shared.zig").BitwisePermissionFlags;
pub const Permissions = struct {
bitfield: BitwisePermissionFlags = .{},
pub fn all() BitwisePermissionFlags {
var bits: @Vector(u64, u1) = @bitCast(BitwisePermissionFlags{});
bits = @splat(1);
return BitwisePermissionFlags.fromRaw(@bitCast(bits));
}
pub fn init(bitfield: anytype) Permissions {
return Permissions{ .bitfield = @bitCast(bitfield) };
}
pub fn has(self: Permissions, bit: u1) bool {
return (self.bitfield & bit) == bit;
}
pub fn missing(self: Permissions, bit: u1) bool {
return (self.bitfield & bit) == 0;
}
pub fn equals(self: Permissions, other: anytype) bool {
return self.bitfield == Permissions.init(other).bitfield;
}
pub fn add(self: Permissions, bit: u1) void {
self.bitfield |= bit;
}
pub fn remove(self: Permissions, bit: u1) void {
self.bitfield &= ~bit;
}
pub fn has2(self: Permissions, bit: u1) bool {
const administrator = BitwisePermissionFlags{ .ADMINISTRATOR = true };
return self.has(bit) or (self.bitfield & administrator) == administrator;
}
pub fn toRaw(self: Permissions) u64 {
return @bitCast(self.bitfield);
}
pub fn fromRaw(raw: u64) Permissions {
return Permissions{ .bitfield = @bitCast(raw) };
}
/// std.json stringify
pub fn jsonStringify(permissions: Permissions, writer: anytype) !void {
try writer.print("<Permissions 0x{x}>", .{permissions.toRaw()});
}
};
const testing = @import("std").testing;
test {
const all_permissions = Permissions.all();
// 2^40 - 1 = 1099511627775
const ALL: u64 = 1099511627775;
testing.expectEqual(all_permissions.toRaw(), ALL);
}

View File

@ -99,6 +99,7 @@ inflator: zlib.Decompressor,
ws_mutex: std.Thread.Mutex = .{},
rw_mutex: std.Thread.RwLock = .{},
log: Log = .no,
cache: @import("cache.zig").CacheTables,
pub fn resumable(self: *Self) bool {
return self.resume_gateway_url != null and
@ -152,6 +153,7 @@ pub fn init(allocator: mem.Allocator, shard_id: usize, total_shards: usize, sett
options: ShardOptions,
run: GatewayDispatchEvent(*Self),
log: Log,
cache: @import("cache.zig").CacheTables,
sharder_pool: ?*std.Thread.Pool = null,
}) zlib.Error!Self {
return Self{
@ -183,6 +185,7 @@ pub fn init(allocator: mem.Allocator, shard_id: usize, total_shards: usize, sett
settings.options.ratelimit_options.ratelimit_reset_interval,
Self.calculateSafeRequests(settings.options.ratelimit_options),
),
.cache = settings.cache,
.sharder_pool = settings.sharder_pool,
};
}

View File

@ -48,5 +48,6 @@ pub fn main() !void {
.run = .{ .message_create = &message_create, .ready = &ready },
.log = .yes,
.options = .{},
.cache = Discord.CacheTables.defaults(allocator),
});
}