refactor(launch system): Make shared.Run cancellable
Not sure if it works yet
This commit is contained in:
parent
781f6a10ec
commit
11747b4158
2 changed files with 101 additions and 15 deletions
67
launcher/viewMain.go
Normal file
67
launcher/viewMain.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package launcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"gioui.org/app"
|
||||||
|
"gioui.org/io/event"
|
||||||
|
"git.mstar.dev/mstar/timer/shared"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LauncherState struct {
|
||||||
|
possibleTargets map[string]func() shared.WindowState
|
||||||
|
possibleTargetsLock sync.RWMutex
|
||||||
|
activeWindows map[string]bool
|
||||||
|
activeWindowsLock sync.RWMutex
|
||||||
|
initialSetupDone bool
|
||||||
|
initialSetupLock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LauncherState) Run(globalState *shared.GlobalState, event event.Event) shared.NewState {
|
||||||
|
if !s.initialSetupDone {
|
||||||
|
s.initialSetup(globalState)
|
||||||
|
}
|
||||||
|
switch event := event.(type) {
|
||||||
|
case app.FrameEvent:
|
||||||
|
return s.handleFrameEvent(globalState, &event)
|
||||||
|
case app.DestroyEvent:
|
||||||
|
return s.handleQuitEvent()
|
||||||
|
case app.WaylandViewEvent, app.X11ViewEvent:
|
||||||
|
return shared.EmptyEvent()
|
||||||
|
default:
|
||||||
|
log.Debug().Any("event", event).Type("type", event).Msg("Unknown event")
|
||||||
|
return shared.NewState{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LauncherState) Exit(globalState *shared.GlobalState, wg *sync.WaitGroup) {
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LauncherState) AddTarget(name string, builder func() shared.WindowState) {
|
||||||
|
s.possibleTargets[name] = builder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LauncherState) initialSetup(globalState *shared.GlobalState) {
|
||||||
|
s.activeWindowsLock.Lock()
|
||||||
|
defer s.activeWindowsLock.Unlock()
|
||||||
|
s.possibleTargetsLock.Lock()
|
||||||
|
defer s.possibleTargetsLock.Unlock()
|
||||||
|
s.initialSetupLock.Lock()
|
||||||
|
defer s.initialSetupLock.Unlock()
|
||||||
|
s.initialSetupDone = true
|
||||||
|
s.possibleTargets = map[string]func() shared.WindowState{}
|
||||||
|
s.activeWindows = map[string]bool{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LauncherState) handleQuitEvent() shared.NewState {
|
||||||
|
return shared.NewState{ExitCode: shared.ErrExitOk}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LauncherState) handleFrameEvent(
|
||||||
|
globalState *shared.GlobalState,
|
||||||
|
frameEvent *app.FrameEvent,
|
||||||
|
) shared.NewState {
|
||||||
|
return shared.NewState{}
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
package shared
|
package shared
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"gioui.org/app"
|
"gioui.org/app"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/widget/material"
|
"gioui.org/widget/material"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
@ -14,7 +16,7 @@ type GlobalState struct {
|
||||||
state WindowState
|
state WindowState
|
||||||
}
|
}
|
||||||
|
|
||||||
func Run(window *app.Window, initialState WindowState) error {
|
func Run(ctx context.Context, window *app.Window, initialState WindowState) error {
|
||||||
// Set up global data first
|
// Set up global data first
|
||||||
state := GlobalState{}
|
state := GlobalState{}
|
||||||
theme := material.NewTheme()
|
theme := material.NewTheme()
|
||||||
|
@ -26,21 +28,38 @@ func Run(window *app.Window, initialState WindowState) error {
|
||||||
// Then start showing the window and polling for events
|
// Then start showing the window and polling for events
|
||||||
// Though technically, I don't think window.Event will cause the window to display yet
|
// Though technically, I don't think window.Event will cause the window to display yet
|
||||||
// since what should it display? It doesn't have anything to display. So first display happens at event.Frame
|
// since what should it display? It doesn't have anything to display. So first display happens at event.Frame
|
||||||
for {
|
eventChan := make(chan event.Event)
|
||||||
e := window.Event()
|
go func() {
|
||||||
// Let the current view handle the event, then update accordingly
|
for {
|
||||||
newState := state.state.Run(&state, e)
|
eventChan <- window.Event()
|
||||||
if newState.NextState != nil {
|
if ctx.Err() != nil {
|
||||||
state.state = newState.NextState
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if newState.ExitCode != nil {
|
}()
|
||||||
log.Info().Msg("Received shutdown request")
|
for {
|
||||||
wg := sync.WaitGroup{}
|
select {
|
||||||
wg.Add(1)
|
case <-ctx.Done():
|
||||||
go state.state.Exit(&state, &wg)
|
log.Info().Msg("Context cancelled, preparing shutdown")
|
||||||
wg.Wait()
|
// Force one update for polling goroutine to exit
|
||||||
log.Info().Msg("Shutdown of window complete")
|
state.Window.Invalidate()
|
||||||
return nil
|
// Then keep awaiting more events. Since quit is just another event
|
||||||
|
// and the event channel is unbuffered, at least one more event has to be read for the poller to exit
|
||||||
|
case e := <-eventChan:
|
||||||
|
// Let the current view handle the event, then update accordingly
|
||||||
|
newState := state.state.Run(&state, e)
|
||||||
|
if newState.NextState != nil {
|
||||||
|
state.state = newState.NextState
|
||||||
|
}
|
||||||
|
if newState.ExitCode != nil {
|
||||||
|
log.Info().Msg("Received shutdown request from window")
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(1)
|
||||||
|
go state.state.Exit(&state, &wg)
|
||||||
|
wg.Wait()
|
||||||
|
log.Info().Msg("Shutdown of window complete")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue