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 28c7846
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)

Check warning on line 181 in console_win.go

View check run for this annotation

Codecov / codecov/patch

console_win.go#L181

Added line #L181 was not covered by tests
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()
})

Check warning on line 291 in console_win.go

View check run for this annotation

Codecov / codecov/patch

console_win.go#L288-L291

Added lines #L288 - L291 were not covered by tests
}

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:

Check warning on line 663 in console_win.go

View check run for this annotation

Codecov / codecov/patch

console_win.go#L660-L663

Added lines #L660 - L663 were not covered by tests
}
}

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

Check warning on line 709 in console_win.go

View check run for this annotation

Codecov / codecov/patch

console_win.go#L709

Added line #L709 was not covered by tests
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))

Check warning on line 717 in console_win.go

View check run for this annotation

Codecov / codecov/patch

console_win.go#L717

Added line #L717 was not covered by tests
} else {
s.PostEventWait(NewEventKey(KeyRune, rune(krec.ch),
mod2mask(krec.mod)))
s.postEvent(NewEventKey(KeyRune, rune(krec.ch), mod2mask(krec.mod)))

Check warning on line 719 in console_win.go

View check run for this annotation

Codecov / codecov/patch

console_win.go#L719

Added line #L719 was not covered by tests
}
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)))

Check warning on line 731 in console_win.go

View check run for this annotation

Codecov / codecov/patch

console_win.go#L731

Added line #L731 was not covered by tests
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)))

Check warning on line 744 in console_win.go

View check run for this annotation

Codecov / codecov/patch

console_win.go#L744

Added line #L744 was not covered by tests

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)))

Check warning on line 750 in console_win.go

View check run for this annotation

Codecov / codecov/patch

console_win.go#L750

Added line #L750 was not covered by tests

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:

Check warning on line 1092 in console_win.go

View check run for this annotation

Codecov / codecov/patch

console_win.go#L1090-L1092

Added lines #L1090 - L1092 were not covered by tests
}
}

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

Check warning on line 1265 in console_win.go

View check run for this annotation

Codecov / codecov/patch

console_win.go#L1264-L1265

Added lines #L1264 - L1265 were not covered by tests
}

func (s *cScreen) StopQ() <-chan struct{} {
return s.stopQ

Check warning on line 1269 in console_win.go

View check run for this annotation

Codecov / codecov/patch

console_win.go#L1268-L1269

Added lines #L1268 - L1269 were not covered by tests
}
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

Check warning on line 432 in screen.go

View check run for this annotation

Codecov / codecov/patch

screen.go#L431-L432

Added lines #L431 - L432 were not covered by tests
case ev := <-b.EventQ():
select {
case <-quit:
return
case <-b.StopQ():
return

Check warning on line 438 in screen.go

View check run for this annotation

Codecov / codecov/patch

screen.go#L435-L438

Added lines #L435 - L438 were not covered by tests
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

Check warning on line 455 in screen.go

View check run for this annotation

Codecov / codecov/patch

screen.go#L454-L455

Added lines #L454 - L455 were not covered by tests
}

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

Check warning on line 461 in screen.go

View check run for this annotation

Codecov / codecov/patch

screen.go#L458-L461

Added lines #L458 - L461 were not covered by tests
}
}

func (b *baseScreen) PostEvent(ev Event) error {
select {
case b.EventQ() <- ev:
return nil
default:
return ErrEventQFull

Check warning on line 470 in screen.go

View check run for this annotation

Codecov / codecov/patch

screen.go#L465-L470

Added lines #L465 - L470 were not covered by tests
}
}
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)

Check warning on line 312 in simulation.go

View check run for this annotation

Codecov / codecov/patch

simulation.go#L312

Added line #L312 was not covered by tests
}
}

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:

Check warning on line 323 in simulation.go

View check run for this annotation

Codecov / codecov/patch

simulation.go#L323

Added line #L323 was not covered by tests
}
}

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)

Check warning on line 334 in simulation.go

View check run for this annotation

Codecov / codecov/patch

simulation.go#L334

Added line #L334 was not covered by tests
}

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)

Check warning on line 345 in simulation.go

View check run for this annotation

Codecov / codecov/patch

simulation.go#L345

Added line #L345 was not covered by tests
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)

Check warning on line 357 in simulation.go

View check run for this annotation

Codecov / codecov/patch

simulation.go#L357

Added line #L357 was not covered by tests
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)

Check warning on line 371 in simulation.go

View check run for this annotation

Codecov / codecov/patch

simulation.go#L371

Added line #L371 was not covered by tests
}
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 28c7846

Please sign in to comment.