diff options
Diffstat (limited to 'user')
| -rw-r--r-- | user/level.go | 4 | ||||
| -rw-r--r-- | user/state.go | 63 | ||||
| -rw-r--r-- | user/xp.go | 96 |
3 files changed, 72 insertions, 91 deletions
diff --git a/user/level.go b/user/level.go index 07df16f..92c10ab 100644 --- a/user/level.go +++ b/user/level.go @@ -93,13 +93,13 @@ func syncCopaings(ctx context.Context, s *discordgo.Session, ccs []CopaingCached s.Logger().Debug("sleeping...") time.Sleep(15 * time.Second) // prevents spamming the API } - oldXp := cc.XPs + oldXp := cc.XP err := cc.Sync(ctx) if err != nil { s.Logger().Error("syncing copaing", "error", err, "copaing", cc.ID, "guild", cc.GuildID) continue } - xp := cc.XPs + xp := cc.XP if exp.Level(oldXp) != exp.Level(xp) { cc.onNewLevel(s, exp.Level(xp)) } diff --git a/user/state.go b/user/state.go index b977fb6..effdc80 100644 --- a/user/state.go +++ b/user/state.go @@ -6,18 +6,24 @@ import ( "sync" "time" + "github.com/anhgelus/gokord" "github.com/nyttikord/gokord/state" ) var ErrSyncingUnsavedData = errors.New("trying to sync unsaved data") +type XPCached struct { + XP uint + Time time.Duration +} + type CopaingCached struct { ID uint DiscordID string GuildID string - XPs uint + XP uint + XPs []XPCached XPToAdd uint - lastSync time.Time // time.Time of the lastSync } // copaing turns a CopaingCached into a Copaing. @@ -39,9 +45,8 @@ func (cc *CopaingCached) Sync(ctx context.Context) error { return ErrSyncingUnsavedData } synced := FromCopaing(cc.copaing()) - synced.XPs += cc.XPToAdd + synced.XP += cc.XPToAdd synced.XPToAdd = cc.XPToAdd - synced.lastSync = time.Now() err := synced.Save(ctx) if err != nil { return err @@ -89,7 +94,12 @@ func (cc *CopaingCached) mustSave() bool { } func saveStateInDB(ctx context.Context) error { - for _, v := range GetState(ctx).storage { + state := GetState(ctx) + + state.saveInDB.Lock() + defer state.saveInDB.Unlock() + + for _, v := range state.storage { if v.mustSave() { err := v.SaveInDB(ctx) if err != nil { @@ -105,7 +115,8 @@ func FromCopaing(c *Copaing) *CopaingCached { ID: c.ID, DiscordID: c.DiscordID, GuildID: c.GuildID, - XPs: calcXP(c), + XP: calcXP(c), + XPs: generateXPs(c), XPToAdd: 0, } } @@ -121,14 +132,24 @@ func KeyCopaingCachedRaw(guildID, copaingID string) state.Key { } type State struct { - mu sync.RWMutex - storage state.MapStorage[CopaingCached] + mu sync.RWMutex + saveInDB sync.Mutex + storage state.MapStorage[CopaingCached] } func NewState() *State { - return &State{ + state := &State{ storage: state.MapStorage[CopaingCached]{}, } + var cs []*Copaing + err := gokord.DB.Find(&cs).Error + if err != nil { + panic(err) + } + for _, v := range cs { + FromCopaing(v).Save(SetState(context.Background(), state)) + } + return state } const ContextKeyState = "state" @@ -173,3 +194,27 @@ func calcXP(c *Copaing) uint { } return sum } + +func generateXPs(c *Copaing) []XPCached { + data := map[time.Duration]XPCached{} + sixH := 6 * time.Hour + for _, xp := range c.CopaingXPs { + // we add sixH at the end because we want it to be rounded to ceil + since := time.Since(xp.CreatedAt)/sixH + sixH + if v, ok := data[since]; ok { + v.XP += xp.XP + } else { + data[since] = XPCached{ + Time: since, + XP: xp.XP, + } + } + } + ccs := make([]XPCached, len(data)) + i := 0 + for _, v := range data { + ccs[i] = v + i++ + } + return ccs +} @@ -2,13 +2,10 @@ package user import ( "context" - "log/slog" "slices" - "sync" + "time" - "git.anhgelus.world/anhgelus/les-copaings-bot/config" "git.anhgelus.world/anhgelus/les-copaings-bot/exp" - "github.com/anhgelus/gokord" "github.com/nyttikord/gokord/bot" "github.com/nyttikord/gokord/user" ) @@ -27,10 +24,10 @@ func (c *cXP) GetXP() uint { } func (cc *CopaingCached) AddXP(ctx context.Context, s bot.Session, m *user.Member, xp uint, fn func(uint, uint)) { - old := cc.XPs + old := cc.XP pastLevel := exp.Level(old) s.Logger().Debug("adding xp", "user", m.DisplayName(), "old", old, "to add", xp) - cc.XPs += xp + cc.XP += xp cc.XPToAdd += xp if err := cc.Save(ctx); err != nil { s.Logger().Error("saving user in state", "error", err, "user", m.DisplayName(), "xp", xp, "guild", cc.GuildID) @@ -43,88 +40,27 @@ func (cc *CopaingCached) AddXP(ctx context.Context, s bot.Session, m *user.Membe } } -func (c *Copaing) GetXP(logger *slog.Logger) (uint, error) { - cfg := config.GetGuildConfig(c.GuildID) - return c.GetXPForDays(logger, cfg.DaysXPRemains) -} - -func (c *Copaing) GetXPForDays(logger *slog.Logger, n uint) (uint, error) { +func (cc *CopaingCached) GetXPForDays(n uint) uint { xp := uint(0) - rows, err := gokord.DB. - Model(&CopaingXP{}). - Where( - "created_at >= ? and guild_id = ? and copaing_id = ?", - exp.TimeStampNDaysBefore(n), - c.GuildID, - c.ID, - ). - Rows() - if err != nil { - return 0, err - } - defer rows.Close() - for rows.Next() { - var cxp CopaingXP - err = gokord.DB.ScanRows(rows, &cxp) - if err != nil { - logger.Error("scanning rows", "error", err, "copaing", c.ID, "guild", c.GuildID) - continue + for _, v := range cc.XPs { + if v.Time <= time.Duration(n*24)*time.Hour { + xp += v.XP } - xp += cxp.XP } - return xp, nil + return xp + cc.XPToAdd } // GetBestXP returns n Copaing with the best XP within d days (d <= cfg.DaysXPRemain; d < 0 <=> d = cfg.DaysXPRemain) -// -// This function is slow -func GetBestXP(ctx context.Context, logger *slog.Logger, guildId string, n uint, d int) ([]CopaingCached, error) { - if d < 0 { - cfg := config.GetGuildConfig(guildId) - d = int(cfg.DaysXPRemains) - return getBestXPFull(ctx, guildId, n), nil - } - rows, err := gokord.DB.Model(&Copaing{}).Where("guild_id = ?", guildId).Rows() - if err != nil { - return nil, err - } - defer rows.Close() - var l []*cXP - var wg sync.WaitGroup - for rows.Next() { - var c Copaing - err = gokord.DB.ScanRows(rows, &c) - if err != nil { - logger.Error("scanning rows", "error", err, "copaing", c.ID, "guild", c.GuildID) - continue +func GetBestXP(ctx context.Context, guildId string, n uint, d int) ([]CopaingCached, error) { + ccs := GetState(ctx).Copaings(guildId) + if d > 0 { + for _, v := range ccs { + v.XP = v.GetXPForDays(n) } - wg.Go(func() { - xp, err := c.GetXPForDays(logger, uint(d)) - if err != nil { - logger.Error("fetching xp", "error", err, "copaing", c.ID, "guild", c.GuildID) - return - } - l = append(l, &cXP{Cxp: xp, copaing: &c}) - }) } - wg.Wait() - slices.SortFunc(l, func(a, b *cXP) int { - // desc order - return int(b.Cxp) - int(a.Cxp) - }) - m := min(len(l), int(n)) - cs := make([]CopaingCached, m) - for i, c := range l[:m] { - cs[i] = CopaingCached{DiscordID: c.copaing.DiscordID, XPs: c.Cxp} - } - return cs, nil -} - -func getBestXPFull(ctx context.Context, guildId string, n uint) []CopaingCached { - ccs := GetState(ctx).Copaings(guildId) slices.SortFunc(ccs, func(a, b CopaingCached) int { - return int(b.XPs) - int(a.XPs) + // desc order + return int(b.XP) - int(a.XP) }) - m := min(len(ccs), int(n)) - return ccs[:m] + return ccs[:min(len(ccs), int(n))], nil } |
