Performance + Caching optimizations
This commit is contained in:
133
main.go
133
main.go
@@ -271,17 +271,70 @@ func envTimeoutDefault(def int) int {
|
|||||||
return def
|
return def
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Helpers: Channel/Kategorie finden =====
|
// Optional: Cache-Invalidierung bei Channel-Events (empfohlen)
|
||||||
|
func hookCategoryCacheInvalidation(s *discordgo.Session) {
|
||||||
|
s.AddHandler(func(_ *discordgo.Session, e *discordgo.ChannelDelete) {
|
||||||
|
if e.Channel != nil && e.Channel.Type == discordgo.ChannelTypeGuildCategory {
|
||||||
|
// alle Keys mit dieser ID rauswerfen
|
||||||
|
catCache.Range(func(k, v interface{}) bool {
|
||||||
|
if v.(cacheEntry).id == e.ID {
|
||||||
|
catCache.Delete(k)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
s.AddHandler(func(_ *discordgo.Session, e *discordgo.ChannelUpdate) {
|
||||||
|
if e.Channel != nil && e.Channel.Type == discordgo.ChannelTypeGuildCategory {
|
||||||
|
// Bei Rename nicht perfekt zu erkennen -> TTL sorgt für Refresh
|
||||||
|
// Optional: hart löschen:
|
||||||
|
catCache.Range(func(k, v interface{}) bool {
|
||||||
|
if v.(cacheEntry).id == e.Channel.ID {
|
||||||
|
catCache.Delete(k)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Helpers: Channel/Kategorie finden (mit Cache) =====
|
||||||
func findOrCreateCategoryID(s *discordgo.Session, guildID, name string) (string, error) {
|
func findOrCreateCategoryID(s *discordgo.Session, guildID, name string) (string, error) {
|
||||||
chans, err := s.GuildChannels(guildID)
|
key := guildID + "|" + name
|
||||||
if err != nil {
|
|
||||||
return "", err
|
// 0) Cache
|
||||||
}
|
if v, ok := catCache.Load(key); ok {
|
||||||
for _, ch := range chans {
|
ce := v.(cacheEntry)
|
||||||
if ch.Type == discordgo.ChannelTypeGuildCategory && ch.Name == name {
|
if time.Since(ce.t) < cacheTTL {
|
||||||
return ch.ID, nil
|
return ce.id, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1) State (schnell) – vorausgesetzt: s.State.TrackChannels = true (vor s.Open() setzen!)
|
||||||
|
if g, err := s.State.Guild(guildID); err == nil {
|
||||||
|
for _, ch := range g.Channels {
|
||||||
|
if ch.Type == discordgo.ChannelTypeGuildCategory && ch.Name == name {
|
||||||
|
catCache.Store(key, cacheEntry{ch.ID, time.Now()})
|
||||||
|
return ch.ID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) REST (Fallback)
|
||||||
|
chans, err := s.GuildChannels(guildID)
|
||||||
|
if err == nil {
|
||||||
|
for _, ch := range chans {
|
||||||
|
if ch.Type == discordgo.ChannelTypeGuildCategory && ch.Name == name {
|
||||||
|
catCache.Store(key, cacheEntry{ch.ID, time.Now()})
|
||||||
|
return ch.ID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// wenn selbst Channels holen fehlschlägt, gib den Fehler zurück
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Nicht gefunden -> anlegen
|
||||||
cat, err := s.GuildChannelCreateComplex(guildID, discordgo.GuildChannelCreateData{
|
cat, err := s.GuildChannelCreateComplex(guildID, discordgo.GuildChannelCreateData{
|
||||||
Name: name,
|
Name: name,
|
||||||
Type: discordgo.ChannelTypeGuildCategory,
|
Type: discordgo.ChannelTypeGuildCategory,
|
||||||
@@ -289,30 +342,76 @@ func findOrCreateCategoryID(s *discordgo.Session, guildID, name string) (string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
catCache.Store(key, cacheEntry{cat.ID, time.Now()})
|
||||||
return cat.ID, nil
|
return cat.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
vcCache sync.Map // key: guildID+"|"+name → cacheEntry
|
||||||
|
catCache sync.Map // key: guildID+"|"+name -> cacheEntry
|
||||||
|
cacheTTL = 12 * time.Hour
|
||||||
|
)
|
||||||
|
|
||||||
|
type cacheEntry struct {
|
||||||
|
id string
|
||||||
|
t time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schneller: erst State, dann REST, plus kleiner TTL-Cache
|
||||||
func findVoiceChannelIDByName(s *discordgo.Session, guildID, name string) string {
|
func findVoiceChannelIDByName(s *discordgo.Session, guildID, name string) string {
|
||||||
|
key := guildID + "|" + name
|
||||||
|
if v, ok := vcCache.Load(key); ok {
|
||||||
|
ce := v.(cacheEntry)
|
||||||
|
if time.Since(ce.t) < cacheTTL {
|
||||||
|
return ce.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) State (sehr schnell)
|
||||||
|
if g, err := s.State.Guild(guildID); err == nil {
|
||||||
|
for _, ch := range g.Channels {
|
||||||
|
if ch.Type == discordgo.ChannelTypeGuildVoice && ch.Name == name {
|
||||||
|
vcCache.Store(key, cacheEntry{ch.ID, time.Now()})
|
||||||
|
return ch.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) REST (Fallback, teuer)
|
||||||
chans, err := s.GuildChannels(guildID)
|
chans, err := s.GuildChannels(guildID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
for _, ch := range chans {
|
for _, ch := range chans {
|
||||||
if ch.Type == discordgo.ChannelTypeGuildVoice && ch.Name == name {
|
if ch.Type == discordgo.ChannelTypeGuildVoice && ch.Name == name {
|
||||||
|
vcCache.Store(key, cacheEntry{ch.ID, time.Now()})
|
||||||
return ch.ID
|
return ch.ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Noch schneller: gezielt VoiceState aus dem State ziehen
|
||||||
func findUserVoiceChannelID(s *discordgo.Session, guildID, userID string) string {
|
func findUserVoiceChannelID(s *discordgo.Session, guildID, userID string) string {
|
||||||
g, err := s.State.Guild(guildID)
|
// 1) Direkt per VoiceState (O(1), wenn im State vorhanden)
|
||||||
if err != nil {
|
if vs, err := s.State.VoiceState(guildID, userID); err == nil && vs != nil && vs.ChannelID != "" {
|
||||||
g, err = s.Guild(guildID)
|
return vs.ChannelID
|
||||||
if err != nil {
|
}
|
||||||
return ""
|
|
||||||
|
// 2) State.Guild als Fallback (O(n), aber ohne REST)
|
||||||
|
if g, err := s.State.Guild(guildID); err == nil {
|
||||||
|
for _, vs := range g.VoiceStates {
|
||||||
|
if vs.UserID == userID && vs.ChannelID != "" {
|
||||||
|
return vs.ChannelID
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3) REST (letzter Ausweg)
|
||||||
|
g, err := s.Guild(guildID)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
for _, vs := range g.VoiceStates {
|
for _, vs := range g.VoiceStates {
|
||||||
if vs.UserID == userID && vs.ChannelID != "" {
|
if vs.UserID == userID && vs.ChannelID != "" {
|
||||||
return vs.ChannelID
|
return vs.ChannelID
|
||||||
@@ -1179,6 +1278,14 @@ func main() {
|
|||||||
s.AddHandler(onVoiceStateUpdate)
|
s.AddHandler(onVoiceStateUpdate)
|
||||||
s.AddHandler(onGuildCreate)
|
s.AddHandler(onGuildCreate)
|
||||||
s.AddHandler(onInteractionCreate(""))
|
s.AddHandler(onInteractionCreate(""))
|
||||||
|
s.AddHandler(func(_ *discordgo.Session, e *discordgo.ChannelDelete) {
|
||||||
|
vcCache.Range(func(k, v interface{}) bool {
|
||||||
|
if v.(cacheEntry).id == e.ID {
|
||||||
|
vcCache.Delete(k)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
if err := s.Open(); err != nil {
|
if err := s.Open(); err != nil {
|
||||||
log.Fatalf("Gateway-Start fehlgeschlagen: %v", err)
|
log.Fatalf("Gateway-Start fehlgeschlagen: %v", err)
|
||||||
|
Reference in New Issue
Block a user