mirror of
https://github.com/rivo/tview.git
synced 2024-11-15 06:12:46 +00:00
Add logic to flush channels, and to safeguard adding of events, after main loop in Application.Run exited
This commit is contained in:
parent
9994674d60
commit
fc1e4b9f8f
163
application.go
163
application.go
@ -1,6 +1,7 @@
|
|||||||
package tview
|
package tview
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -66,6 +67,9 @@ type queuedUpdate struct {
|
|||||||
type Application struct {
|
type Application struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
|
||||||
|
runContext context.Context
|
||||||
|
runCancelFunc context.CancelFunc
|
||||||
|
|
||||||
// The application's screen. Apart from Run(), this variable should never be
|
// The application's screen. Apart from Run(), this variable should never be
|
||||||
// set directly. Always use the screenReplacement channel after calling
|
// set directly. Always use the screenReplacement channel after calling
|
||||||
// Fini(), to set a new screen (or nil to stop the application).
|
// Fini(), to set a new screen (or nil to stop the application).
|
||||||
@ -120,9 +124,39 @@ type Application struct {
|
|||||||
lastMouseButtons tcell.ButtonMask // The last mouse button state.
|
lastMouseButtons tcell.ButtonMask // The last mouse button state.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Application) Close() error {
|
||||||
|
a.runCancelFunc()
|
||||||
|
close(a.events)
|
||||||
|
close(a.screenReplacement)
|
||||||
|
close(a.updates)
|
||||||
|
|
||||||
|
// flush events channel
|
||||||
|
go func() {
|
||||||
|
for range a.events {
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// flush screenReplacement channel
|
||||||
|
go func() {
|
||||||
|
for range a.screenReplacement {
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// flush updates channel
|
||||||
|
go func() {
|
||||||
|
for up := range a.updates {
|
||||||
|
// important to set done for calling channel to be able to return
|
||||||
|
up.done <- struct{}{}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewApplication creates and returns a new application.
|
// NewApplication creates and returns a new application.
|
||||||
func NewApplication() *Application {
|
func NewApplication() *Application {
|
||||||
|
cancelContext, cancelFunc := context.WithCancel(context.Background())
|
||||||
return &Application{
|
return &Application{
|
||||||
|
runContext: cancelContext,
|
||||||
|
runCancelFunc: cancelFunc,
|
||||||
events: make(chan tcell.Event, queueSize),
|
events: make(chan tcell.Event, queueSize),
|
||||||
updates: make(chan queuedUpdate, queueSize),
|
updates: make(chan queuedUpdate, queueSize),
|
||||||
screenReplacement: make(chan tcell.Screen, 1),
|
screenReplacement: make(chan tcell.Screen, 1),
|
||||||
@ -188,7 +222,10 @@ func (a *Application) SetScreen(screen tcell.Screen) *Application {
|
|||||||
oldScreen := a.screen
|
oldScreen := a.screen
|
||||||
a.Unlock()
|
a.Unlock()
|
||||||
oldScreen.Fini()
|
oldScreen.Fini()
|
||||||
a.screenReplacement <- screen
|
// check to see if the Application.Run is still valid
|
||||||
|
if a.runContext.Err() == nil {
|
||||||
|
a.screenReplacement <- screen
|
||||||
|
}
|
||||||
|
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
@ -252,8 +289,15 @@ func (a *Application) Run() error {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
// call the runCancelFunc when exiting this function.
|
||||||
|
//This will stop the channels accepting any more events
|
||||||
|
a.runCancelFunc()
|
||||||
|
}()
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
for {
|
|
||||||
|
// check to see if the Application.Run is still valid
|
||||||
|
for a.runContext.Err() == nil {
|
||||||
a.RLock()
|
a.RLock()
|
||||||
screen := a.screen
|
screen := a.screen
|
||||||
a.RUnlock()
|
a.RUnlock()
|
||||||
@ -271,37 +315,47 @@ func (a *Application) Run() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// A screen was finalized (event is nil). Wait for a new scren.
|
// A screen was finalized (event is nil). Wait for a new screen.
|
||||||
screen = <-a.screenReplacement
|
var ok bool
|
||||||
if screen == nil {
|
select {
|
||||||
// No new screen. We're done.
|
// exit when runContext complete
|
||||||
a.QueueEvent(nil)
|
case <-a.runContext.Done():
|
||||||
return
|
return
|
||||||
}
|
case screen, ok = <-a.screenReplacement:
|
||||||
|
if !ok || screen == nil {
|
||||||
|
// No new screen. We're done.
|
||||||
|
a.QueueEvent(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// We have a new screen. Keep going.
|
// We have a new screen. Keep going.
|
||||||
a.Lock()
|
a.Lock()
|
||||||
a.screen = screen
|
a.screen = screen
|
||||||
enableMouse := a.enableMouse
|
enableMouse := a.enableMouse
|
||||||
a.Unlock()
|
a.Unlock()
|
||||||
|
|
||||||
// Initialize and draw this screen.
|
// Initialize and draw this screen.
|
||||||
if err := screen.Init(); err != nil {
|
if err := screen.Init(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
}
|
||||||
|
if enableMouse {
|
||||||
|
screen.EnableMouse()
|
||||||
|
}
|
||||||
|
a.draw()
|
||||||
}
|
}
|
||||||
if enableMouse {
|
|
||||||
screen.EnableMouse()
|
|
||||||
}
|
|
||||||
a.draw()
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Start event loop.
|
// Start event loop.
|
||||||
EventLoop:
|
EventLoop:
|
||||||
for {
|
// check to see if the Application.Run is still valid
|
||||||
|
for a.runContext.Err() == nil {
|
||||||
select {
|
select {
|
||||||
case event := <-a.events:
|
// break loop when runContext complete
|
||||||
if event == nil {
|
case <-a.runContext.Done():
|
||||||
|
break EventLoop
|
||||||
|
case event, ok := <-a.events:
|
||||||
|
if !ok || event == nil {
|
||||||
break EventLoop
|
break EventLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,9 +402,14 @@ EventLoop:
|
|||||||
if redrawTimer != nil {
|
if redrawTimer != nil {
|
||||||
redrawTimer.Stop()
|
redrawTimer.Stop()
|
||||||
}
|
}
|
||||||
redrawTimer = time.AfterFunc(redrawPause, func() {
|
redrawTimer = time.AfterFunc(redrawPause,
|
||||||
a.events <- event
|
func() {
|
||||||
})
|
// check to see if the Application.Run is still valid
|
||||||
|
if a.runContext.Err() == nil {
|
||||||
|
a.events <- event
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
a.RLock()
|
a.RLock()
|
||||||
screen := a.screen
|
screen := a.screen
|
||||||
@ -376,13 +435,19 @@ EventLoop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we have updates, now is the time to execute them.
|
// If we have updates, now is the time to execute them.
|
||||||
case update := <-a.updates:
|
case update, ok := <-a.updates:
|
||||||
|
if !ok {
|
||||||
|
break EventLoop
|
||||||
|
}
|
||||||
update.f()
|
update.f()
|
||||||
if update.done != nil {
|
if update.done != nil {
|
||||||
update.done <- struct{}{}
|
update.done <- struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// call the runCancelFunc when exiting eventLoop.
|
||||||
|
//This will stop the channels accepting any more events
|
||||||
|
a.runCancelFunc()
|
||||||
|
|
||||||
// Wait for the event loop to finish.
|
// Wait for the event loop to finish.
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@ -500,7 +565,11 @@ func (a *Application) Stop() {
|
|||||||
}
|
}
|
||||||
a.screen = nil
|
a.screen = nil
|
||||||
screen.Fini()
|
screen.Fini()
|
||||||
a.screenReplacement <- nil
|
|
||||||
|
// check to see if the Application.Run is still valid
|
||||||
|
if a.runContext.Err() == nil {
|
||||||
|
a.screenReplacement <- nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suspend temporarily suspends the application by exiting terminal UI mode and
|
// Suspend temporarily suspends the application by exiting terminal UI mode and
|
||||||
@ -618,15 +687,20 @@ func (a *Application) draw() *Application {
|
|||||||
// corrupted so you may want to offer your users a keyboard shortcut to refresh
|
// corrupted so you may want to offer your users a keyboard shortcut to refresh
|
||||||
// the screen.
|
// the screen.
|
||||||
func (a *Application) Sync() *Application {
|
func (a *Application) Sync() *Application {
|
||||||
a.updates <- queuedUpdate{f: func() {
|
// check to see if the Application.Run is still valid
|
||||||
a.RLock()
|
if a.runContext.Err() == nil {
|
||||||
screen := a.screen
|
a.updates <- queuedUpdate{
|
||||||
a.RUnlock()
|
f: func() {
|
||||||
if screen == nil {
|
a.RLock()
|
||||||
return
|
screen := a.screen
|
||||||
|
a.RUnlock()
|
||||||
|
if screen == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
screen.Sync()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
screen.Sync()
|
}
|
||||||
}}
|
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -741,9 +815,15 @@ func (a *Application) GetFocus() Primitive {
|
|||||||
//
|
//
|
||||||
// This function returns after f has executed.
|
// This function returns after f has executed.
|
||||||
func (a *Application) QueueUpdate(f func()) *Application {
|
func (a *Application) QueueUpdate(f func()) *Application {
|
||||||
ch := make(chan struct{})
|
// check to see if the Application.Run is still valid
|
||||||
a.updates <- queuedUpdate{f: f, done: ch}
|
if a.runContext.Err() == nil {
|
||||||
<-ch
|
ch := make(chan struct{})
|
||||||
|
a.updates <- queuedUpdate{
|
||||||
|
f: f,
|
||||||
|
done: ch,
|
||||||
|
}
|
||||||
|
<-ch
|
||||||
|
}
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -761,6 +841,9 @@ func (a *Application) QueueUpdateDraw(f func()) *Application {
|
|||||||
//
|
//
|
||||||
// It is not recommended for event to be nil.
|
// It is not recommended for event to be nil.
|
||||||
func (a *Application) QueueEvent(event tcell.Event) *Application {
|
func (a *Application) QueueEvent(event tcell.Event) *Application {
|
||||||
a.events <- event
|
// check to see if the Application.Run is still valid
|
||||||
|
if a.runContext.Err() == nil {
|
||||||
|
a.events <- event
|
||||||
|
}
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user