Skip to content

Commit

Permalink
Refactor event polling code.
Browse files Browse the repository at this point in the history
This centralizes much of the logic (hopefully reducing duplication)
for polling events and the queue.  This will make it easier to make
further design changes to express a better, simpler, API to consumers.

While here addressed missing logic to handle Fini correctly on Windows.
  • Loading branch information
gdamore committed Dec 8, 2023
1 parent e3a99dd commit 8041b8e
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 222 deletions.
96 changes: 33 additions & 63 deletions console_win.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ type cScreen struct {
out syscall.Handle
cancelflag syscall.Handle
scandone chan struct{}
evch chan Event
quit chan struct{}
curx int
cury int
Expand All @@ -53,11 +52,11 @@ type cScreen struct {
oomode uint32
cells CellBuffer

finiOnce sync.Once

mouseEnabled bool
wg sync.WaitGroup
eventQ chan Event
stopQ chan struct{}
finiOnce sync.Once

sync.Mutex
}
Expand Down Expand Up @@ -146,7 +145,7 @@ const (
vtSgr0 = "\x1b[0m"
vtBold = "\x1b[1m"
vtUnderline = "\x1b[4m"
vtBlink = "\x1b[5m" // Not sure this is processed
vtBlink = "\x1b[5m" // Not sure if this is processed
vtReverse = "\x1b[7m"
vtSetFg = "\x1b[38;5;%dm"
vtSetBg = "\x1b[48;5;%dm"
Expand Down Expand Up @@ -179,7 +178,7 @@ func NewConsoleScreen() (Screen, error) {
}

func (s *cScreen) Init() error {
s.evch = make(chan Event, 10)
s.eventQ = make(chan Event, 10)
s.quit = make(chan struct{})
s.scandone = make(chan struct{})

Expand Down Expand Up @@ -286,7 +285,10 @@ func (s *cScreen) EnableFocus() {}
func (s *cScreen) DisableFocus() {}

func (s *cScreen) Fini() {
s.disengage()
s.finiOnce.Do(func() {
close(s.quit)
s.disengage()
})
}

func (s *cScreen) disengage() {
Expand Down Expand Up @@ -356,52 +358,6 @@ func (s *cScreen) engage() error {
return nil
}

func (s *cScreen) PostEventWait(ev Event) {
s.evch <- ev
}

func (s *cScreen) PostEvent(ev Event) error {
select {
case s.evch <- ev:
return nil
default:
return ErrEventQFull
}
}

func (s *cScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) {
defer close(ch)
for {
select {
case <-quit:
return
case <-s.stopQ:
return
case ev := <-s.evch:
select {
case <-quit:
return
case <-s.stopQ:
return
case ch <- ev:
}
}
}
}

func (s *cScreen) PollEvent() Event {
select {
case <-s.stopQ:
return nil
case ev := <-s.evch:
return ev
}
}

func (s *cScreen) HasPendingEvent() bool {
return len(s.evch) > 0
}

type cursorInfo struct {
size uint32
visible uint32
Expand Down Expand Up @@ -701,6 +657,13 @@ func mrec2btns(mbtns, flags uint32) ButtonMask {
return btns
}

func (s *cScreen) postEvent(ev Event) {
select {
case s.eventQ <- ev:
case <-s.quit:
}
}

func (s *cScreen) getConsoleInput() error {
// cancelFlag comes first as WaitForMultipleObjects returns the lowest index
// in the event that both events are signalled.
Expand Down Expand Up @@ -743,19 +706,17 @@ func (s *cScreen) getConsoleInput() error {
krec.mod = getu32(rec.data[12:])

if krec.isdown == 0 || krec.repeat < 1 {
// its a key release event, ignore it
// it's a key release event, ignore it
return nil
}
if krec.ch != 0 {
// synthesized key code
for krec.repeat > 0 {
// convert shift+tab to backtab
if mod2mask(krec.mod) == ModShift && krec.ch == vkTab {
s.PostEventWait(NewEventKey(KeyBacktab, 0,
ModNone))
s.postEvent(NewEventKey(KeyBacktab, 0, ModNone))
} else {
s.PostEventWait(NewEventKey(KeyRune, rune(krec.ch),
mod2mask(krec.mod)))
s.postEvent(NewEventKey(KeyRune, rune(krec.ch), mod2mask(krec.mod)))
}
krec.repeat--
}
Expand All @@ -767,8 +728,7 @@ func (s *cScreen) getConsoleInput() error {
return nil
}
for krec.repeat > 0 {
s.PostEventWait(NewEventKey(key, rune(krec.ch),
mod2mask(krec.mod)))
s.postEvent(NewEventKey(key, rune(krec.ch), mod2mask(krec.mod)))
krec.repeat--
}

Expand All @@ -781,14 +741,13 @@ func (s *cScreen) getConsoleInput() error {
mrec.flags = getu32(rec.data[12:])
btns := mrec2btns(mrec.btns, mrec.flags)
// we ignore double click, events are delivered normally
s.PostEventWait(NewEventMouse(int(mrec.x), int(mrec.y), btns,
mod2mask(mrec.mod)))
s.postEvent(NewEventMouse(int(mrec.x), int(mrec.y), btns, mod2mask(mrec.mod)))

case resizeEvent:
var rrec resizeRecord
rrec.x = geti16(rec.data[0:])
rrec.y = geti16(rec.data[2:])
s.PostEventWait(NewEventResize(int(rrec.x), int(rrec.y)))
s.postEvent(NewEventResize(int(rrec.x), int(rrec.y)))

default:
}
Expand Down Expand Up @@ -1128,7 +1087,10 @@ func (s *cScreen) resize() {
uintptr(s.out),
uintptr(1),
uintptr(unsafe.Pointer(&r)))
_ = s.PostEvent(NewEventResize(w, h))
select {
case s.eventQ <- NewEventResize(w, h):
default:
}
}

func (s *cScreen) clearScreen(style Style, vtEnable bool) {
Expand Down Expand Up @@ -1298,3 +1260,11 @@ func (s *cScreen) Tty() (Tty, bool) {
func (s *cScreen) GetCells() *CellBuffer {
return &s.cells
}

func (s *cScreen) EventQ() chan Event {
return s.eventQ
}

func (s *cScreen) StopQ() <-chan struct{} {
return s.stopQ
}
65 changes: 59 additions & 6 deletions screen.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022 The TCell Authors
// Copyright 2023 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
Expand Down Expand Up @@ -314,11 +314,6 @@ type screenImpl interface {
HideCursor()
SetCursorStyle(CursorStyle)
Size() (width, height int)
ChannelEvents(ch chan<- Event, quit <-chan struct{})
PollEvent() Event
HasPendingEvent() bool
PostEvent(ev Event) error
PostEventWait(ev Event)
EnableMouse(...MouseFlags)
DisableMouse()
EnablePaste()
Expand Down Expand Up @@ -351,6 +346,15 @@ type screenImpl interface {
// GetCells returns a pointer to the underlying CellBuffer that the implementation uses.
// Various methods will write to these for performance, but will use the lock to do so.
GetCells() *CellBuffer

// StopQ is closed when the screen is shut down via Fini. It remains open if the screen
// is merely suspended.
StopQ() <-chan struct{}

// EventQ delivers events. Events are posted to this by the screen in response to
// key presses, resizes, etc. Application code receives events from this via the
// Screen.PollEvent, Screen.ChannelEvents APIs.
EventQ() chan Event
}

type baseScreen struct {
Expand Down Expand Up @@ -417,3 +421,52 @@ func (b *baseScreen) LockRegion(x, y, width, height int, lock bool) {
}
b.Unlock()
}

func (b *baseScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) {
defer close(ch)
for {
select {
case <-quit:
return
case <-b.StopQ():
return
case ev := <-b.EventQ():
select {
case <-quit:
return
case <-b.StopQ():
return
case ch <- ev:
}
}
}
}

func (b *baseScreen) PollEvent() Event {
select {
case <-b.StopQ():
return nil
case ev := <-b.EventQ():
return ev
}
}

func (b *baseScreen) HasPendingEvent() bool {
return len(b.EventQ()) > 0
}

func (b *baseScreen) PostEventWait(ev Event) {
select {
case b.EventQ() <- ev:
case <-b.StopQ():
}
}

func (b *baseScreen) PostEvent(ev Event) error {
select {
case b.EventQ() <- ev:
return nil
default:
return ErrEventQFull
}
}
63 changes: 16 additions & 47 deletions simulation.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,68 +309,29 @@ func (s *simscreen) resize() {
if w != ow || h != oh {
s.back.Resize(w, h)
ev := NewEventResize(w, h)
s.PostEvent(ev)
s.postEvent(ev)
}
}

func (s *simscreen) Colors() int {
return 256
}

func (s *simscreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) {
defer close(ch)
for {
select {
case <-quit:
return
case <-s.quit:
return
case ev := <-s.evch:
select {
case <-quit:
return
case <-s.quit:
return
case ch <- ev:
}
}
}
}

func (s *simscreen) PollEvent() Event {
select {
case <-s.quit:
return nil
case ev := <-s.evch:
return ev
}
}

func (s *simscreen) HasPendingEvent() bool {
return len(s.evch) > 0
}

func (s *simscreen) PostEventWait(ev Event) {
s.evch <- ev
}

func (s *simscreen) PostEvent(ev Event) error {
func (s *simscreen) postEvent(ev Event) {
select {
case s.evch <- ev:
return nil
default:
return ErrEventQFull
case <-s.quit:
}
}

func (s *simscreen) InjectMouse(x, y int, buttons ButtonMask, mod ModMask) {
ev := NewEventMouse(x, y, buttons, mod)
s.PostEvent(ev)
s.postEvent(ev)
}

func (s *simscreen) InjectKey(key Key, r rune, mod ModMask) {
ev := NewEventKey(key, r, mod)
s.PostEvent(ev)
s.postEvent(ev)
}

func (s *simscreen) InjectKeyBytes(b []byte) bool {
Expand All @@ -381,7 +342,7 @@ outer:
if b[0] >= ' ' && b[0] <= 0x7F {
// printable ASCII easy to deal with -- no encodings
ev := NewEventKey(KeyRune, rune(b[0]), ModNone)
s.PostEvent(ev)
s.postEvent(ev)
b = b[1:]
continue
}
Expand All @@ -393,7 +354,7 @@ outer:
mod = ModCtrl
}
ev := NewEventKey(Key(b[0]), 0, mod)
s.PostEvent(ev)
s.postEvent(ev)
b = b[1:]
continue
}
Expand All @@ -407,7 +368,7 @@ outer:
r, _ := utf8.DecodeRune(utfb[:nout])
if r != utf8.RuneError {
ev := NewEventKey(KeyRune, r, ModNone)
s.PostEvent(ev)
s.postEvent(ev)
}
b = b[nin:]
continue outer
Expand Down Expand Up @@ -526,3 +487,11 @@ func (s *simscreen) Tty() (Tty, bool) {
func (s *simscreen) GetCells() *CellBuffer {
return &s.back
}

func (s *simscreen) EventQ() chan Event {
return s.evch
}

func (s *simscreen) StopQ() <-chan struct{} {
return s.quit
}
Loading

0 comments on commit 8041b8e

Please sign in to comment.