chrly/internal/mojang/batch_uuids_provider.go
2024-02-01 07:58:26 +01:00

115 lines
2.4 KiB
Go

package mojang
import (
"strings"
"sync"
"time"
"github.com/elyby/chrly/internal/utils"
)
type BatchUuidsProvider struct {
UsernamesToUuidsEndpoint func(usernames []string) ([]*ProfileInfo, error)
batch int
delay time.Duration
fireOnFull bool
queue *utils.Queue[*job]
fireChan chan any
stopChan chan any
onFirstCall sync.Once
}
func NewBatchUuidsProvider(
endpoint func(usernames []string) ([]*ProfileInfo, error),
batchSize int,
awaitDelay time.Duration,
fireOnFull bool,
) *BatchUuidsProvider {
return &BatchUuidsProvider{
UsernamesToUuidsEndpoint: endpoint,
stopChan: make(chan any),
batch: batchSize,
delay: awaitDelay,
fireOnFull: fireOnFull,
queue: utils.NewQueue[*job](),
fireChan: make(chan any),
}
}
type job struct {
Username string
ResultChan chan<- *jobResult
}
type jobResult struct {
Profile *ProfileInfo
Error error
}
func (ctx *BatchUuidsProvider) GetUuid(username string) (*ProfileInfo, error) {
resultChan := make(chan *jobResult)
n := ctx.queue.Enqueue(&job{username, resultChan})
if ctx.fireOnFull && n%ctx.batch == 0 {
ctx.fireChan <- struct{}{}
}
ctx.onFirstCall.Do(ctx.startQueue)
result := <-resultChan
return result.Profile, result.Error
}
func (ctx *BatchUuidsProvider) StopQueue() {
close(ctx.stopChan)
}
func (ctx *BatchUuidsProvider) startQueue() {
go func() {
for {
t := time.NewTimer(ctx.delay)
select {
case <-ctx.stopChan:
return
case <-t.C:
go ctx.fireRequest()
case <-ctx.fireChan:
t.Stop()
go ctx.fireRequest()
}
}
}()
}
func (ctx *BatchUuidsProvider) fireRequest() {
jobs, _ := ctx.queue.Dequeue(ctx.batch)
if len(jobs) == 0 {
return
}
usernames := make([]string, len(jobs))
for i, job := range jobs {
usernames[i] = job.Username
}
profiles, err := ctx.UsernamesToUuidsEndpoint(usernames)
for _, job := range jobs {
response := &jobResult{}
if err == nil {
// The profiles in the response aren't ordered, so we must search each username over full array
for _, profile := range profiles {
if strings.EqualFold(job.Username, profile.Name) {
response.Profile = profile
break
}
}
} else {
response.Error = err
}
job.ResultChan <- response
close(job.ResultChan)
}
}