package wrap import ( "context" "mayhem-party/src/device/input/button" "mayhem-party/src/device/input/raw" "os" "sync" "time" ) var ( FlagBufferedStickyDuration = os.Getenv("WRAP_BUFFERED_STICKY_DURATION") ) type Buffered struct { ctx context.Context can context.CancelFunc lock sync.Mutex keys map[byte]int64 input Wrap listenInterval time.Duration expirationInterval time.Duration } func NewBuffered(ctx context.Context, input Wrap) *Buffered { ctx, can := context.WithCancel(ctx) expirationInterval := time.Millisecond * 125 if d, err := time.ParseDuration(FlagBufferedStickyDuration); err == nil { expirationInterval = d } result := &Buffered{ input: input, ctx: ctx, can: can, lock: sync.Mutex{}, keys: map[byte]int64{}, listenInterval: time.Millisecond * 10, expirationInterval: expirationInterval, } go result.listen() return result } func (b *Buffered) listen() { for b.ctx.Err() == nil { buttons := b.input.Read() b.lock.Lock() for i := range buttons { if buttons[i].Down { b.keys[buttons[i].Char] = time.Now().UnixNano() } else { b.keys[buttons[i].Char] = 0 } } b.lock.Unlock() select { case <-b.ctx.Done(): case <-time.After(b.listenInterval): } } } func (b *Buffered) CloseWrap() raw.Raw { b.can() return b.input.CloseWrap() } func (b *Buffered) Close() { b.input.Close() b.can() } func (b *Buffered) Read() []button.Button { for b.ctx.Err() == nil { result := b.read() if len(result) > 0 { return result } select { case <-b.ctx.Done(): case <-time.After(b.listenInterval): } } return []button.Button{} } func (b *Buffered) read() []button.Button { b.lock.Lock() defer b.lock.Unlock() result := make([]button.Button, 0, len(b.keys)) for k, v := range b.keys { isFresh := v > 0 isStale := v < 0 && time.Since(time.Unix(0, -1*v)) > b.expirationInterval if isFresh || isStale { result = append(result, button.Button{Char: k, Down: isFresh}) } if isFresh { b.keys[k] = -1 * v } } for i := range result { if !result[i].Down { delete(b.keys, result[i].Char) } } return result }