refactor(launch system): Make shared.Run cancellable

Not sure if it works yet
This commit is contained in:
Melody Becker 2025-02-04 17:54:24 +01:00
parent 781f6a10ec
commit 11747b4158
Signed by: mstar
SSH key fingerprint: SHA256:9VAo09aaVNTWKzPW7Hq2LW+ox9OdwmTSHRoD4mlz1yI
2 changed files with 101 additions and 15 deletions

67
launcher/viewMain.go Normal file
View 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{}
}

View file

@ -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,15 +28,31 @@ 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
eventChan := make(chan event.Event)
go func() {
for { for {
e := window.Event() eventChan <- window.Event()
if ctx.Err() != nil {
return
}
}
}()
for {
select {
case <-ctx.Done():
log.Info().Msg("Context cancelled, preparing shutdown")
// Force one update for polling goroutine to exit
state.Window.Invalidate()
// 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 // Let the current view handle the event, then update accordingly
newState := state.state.Run(&state, e) newState := state.state.Run(&state, e)
if newState.NextState != nil { if newState.NextState != nil {
state.state = newState.NextState state.state = newState.NextState
} }
if newState.ExitCode != nil { if newState.ExitCode != nil {
log.Info().Msg("Received shutdown request") log.Info().Msg("Received shutdown request from window")
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(1) wg.Add(1)
go state.state.Exit(&state, &wg) go state.state.Exit(&state, &wg)
@ -43,4 +61,5 @@ func Run(window *app.Window, initialState WindowState) error {
return nil return nil
} }
} }
}
} }