diff --git a/cmd/sst/mosaic.go b/cmd/sst/mosaic.go index 47953f63d7..33246d3fe5 100644 --- a/cmd/sst/mosaic.go +++ b/cmd/sst/mosaic.go @@ -220,11 +220,6 @@ func CmdMosaic(c *cli.Cli) error { return server.Start(c.Context, p) }) - wg.Go(func() error { - defer c.Cancel() - return deployer.Start(c.Context, p, server) - }) - currentExecutable, _ := os.Executable() mode := c.String("mode") @@ -236,6 +231,14 @@ func CmdMosaic(c *cli.Cli) error { } } + // Subscribe to CompleteEvent BEFORE starting deployer to not miss any events + completeEvts := bus.Subscribe(&project.CompleteEvent{}) + + wg.Go(func() error { + defer c.Cancel() + return deployer.Start(c.Context, p, server) + }) + if mode == "multi" { multi, err := multiplexer.New() if err != nil { @@ -251,49 +254,52 @@ func CmdMosaic(c *cli.Cli) error { defer func() { multi.Exit() }() + + processCompleteEventForMultiplexer := func(evt *project.CompleteEvent) { + for _, d := range evt.Devs { + if d.Command == "" { + continue + } + dir := filepath.Join(cwd, d.Directory) + title := d.Title + if title == "" { + title = d.Name + } + multi.AddProcess(d.Name, append([]string{currentExecutable, "dev"}), "→", title, dir, true, d.Autostart, append([]string{"SST_CHILD=" + d.Name}, multiEnv...)...) + } + + for name := range evt.Tunnels { + multi.AddProcess("tunnel", []string{currentExecutable, "tunnel", "--stage", p.App().Stage}, "⇌", "Tunnel", "", true, true, append( + multiEnv, + "SST_LOG="+p.PathLog("tunnel_"+name), + )...) + } + + if len(evt.Tasks) > 0 { + multi.AddProcess("task", []string{currentExecutable, "ui", "--filter=task"}, "⧉", "Tasks", "", false, true, append(multiEnv, "SST_LOG="+p.PathLog("ui-task"))...) + } + } + go func() { multi.Start() }() wg.Go(func() error { - evts := bus.Subscribe(&project.CompleteEvent{}) defer c.Cancel() + + // Wait for multiplexer to be ready before posting events + <-multi.Ready() + for { select { case <-c.Context.Done(): return nil - case unknown := <-evts: + case unknown := <-completeEvts: switch evt := unknown.(type) { case *project.CompleteEvent: - for _, d := range evt.Devs { - if d.Command == "" { - continue - } - dir := filepath.Join(cwd, d.Directory) - title := d.Title - if title == "" { - title = d.Name - } - multi.AddProcess( - d.Name, - append([]string{currentExecutable, "dev"}), - "→", - title, - dir, - true, - d.Autostart, - append([]string{"SST_CHILD=" + d.Name}, multiEnv...)..., - ) - } - for name := range evt.Tunnels { - multi.AddProcess("tunnel", []string{currentExecutable, "tunnel", "--stage", p.App().Stage}, "⇌", "Tunnel", "", true, true, append( - multiEnv, - "SST_LOG="+p.PathLog("tunnel_"+name), - )...) - } - if len(evt.Tasks) > 0 { - multi.AddProcess("task", []string{currentExecutable, "ui", "--filter=task"}, "⧉", "Tasks", "", false, true, append(multiEnv, "SST_LOG="+p.PathLog("ui-task"))...) + if evt.Old { + continue } - break + processCompleteEventForMultiplexer(evt) } } } @@ -311,42 +317,48 @@ func CmdMosaic(c *cli.Cli) error { mono.AddProcess("deploy", []string{currentExecutable, "ui", "--filter=sst"}, "", "SST") mono.AddProcess("function", []string{currentExecutable, "ui", "--filter=function"}, "", "Function") + processCompleteEventForMonoplexer := func(evt *project.CompleteEvent) { + for _, d := range evt.Devs { + if d.Command == "" { + continue + } + dir := filepath.Join(cwd, d.Directory) + words, _ := shellquote.Split(d.Command) + title := d.Title + if title == "" { + title = d.Name + } + mono.AddProcess( + d.Name, + append([]string{currentExecutable, "dev", "--"}, words...), + dir, + title, + ) + } + for range evt.Tunnels { + mono.AddProcess("tunnel", []string{currentExecutable, "tunnel", "--stage", p.App().Stage}, "", "Tunnel") + } + } + wg.Go(func() error { defer c.Cancel() return mono.Start(c.Context) }) wg.Go(func() error { - evts := bus.Subscribe(&project.CompleteEvent{}) defer c.Cancel() + for { select { case <-c.Context.Done(): return nil - case unknown := <-evts: + case unknown := <-completeEvts: switch evt := unknown.(type) { case *project.CompleteEvent: - for _, d := range evt.Devs { - if d.Command == "" { - continue - } - dir := filepath.Join(cwd, d.Directory) - words, _ := shellquote.Split(d.Command) - title := d.Title - if title == "" { - title = d.Name - } - mono.AddProcess( - d.Name, - append([]string{currentExecutable, "dev", "--"}, words...), - dir, - title, - ) - } - for range evt.Tunnels { - mono.AddProcess("tunnel", []string{currentExecutable, "tunnel", "--stage", p.App().Stage}, "", "Tunnel") + if evt.Old { + continue } - break + processCompleteEventForMonoplexer(evt) } } } diff --git a/cmd/sst/mosaic/dev/dev.go b/cmd/sst/mosaic/dev/dev.go index 94a15d43c7..783cbab509 100644 --- a/cmd/sst/mosaic/dev/dev.go +++ b/cmd/sst/mosaic/dev/dev.go @@ -36,7 +36,11 @@ func Start(ctx context.Context, p *project.Project, server *server.Server) error case <-ctx.Done(): return nil case evt := <-evts: - complete = evt.(*project.CompleteEvent) + casted := evt.(*project.CompleteEvent) + if casted.Old { + continue + } + complete = casted } } }) diff --git a/cmd/sst/mosaic/multiplexer/multiplexer.go b/cmd/sst/mosaic/multiplexer/multiplexer.go index d59a65c5a7..823135bee5 100644 --- a/cmd/sst/mosaic/multiplexer/multiplexer.go +++ b/cmd/sst/mosaic/multiplexer/multiplexer.go @@ -33,12 +33,16 @@ type Multiplexer struct { dragging bool click *tcell.EventMouse + ready chan struct{} + procChan chan *EventProcess } func New() (*Multiplexer, error) { var err error result := &Multiplexer{} result.processes = []*pane{} + result.ready = make(chan struct{}) + result.procChan = make(chan *EventProcess, 1000) result.screen, err = tcell.NewScreen() if err != nil { return nil, err @@ -59,6 +63,11 @@ func New() (*Multiplexer, error) { return result, nil } +// Ready returns a channel that is closed when the multiplexer is ready to receive events +func (s *Multiplexer) Ready() <-chan struct{} { + return s.ready +} + func (s *Multiplexer) mainRect() (int, int) { return s.width - SIDEBAR_WIDTH + 1, s.height } @@ -80,6 +89,15 @@ func (s *Multiplexer) Start() { }() s.resize(s.screen.Size()) + close(s.ready) + + // Process events from procChan by forwarding to screen events + go func() { + for evt := range s.procChan { + // Use PostEventWait to ensure events are not dropped + s.screen.PostEventWait(evt) + } + }() for { unknown := s.screen.PollEvent() @@ -103,41 +121,7 @@ func (s *Multiplexer) Start() { return case *EventProcess: - for _, p := range s.processes { - if p.key == evt.Key { - if p.dead && evt.Autostart { - p.start() - s.sort() - s.draw() - } - return - } - } - proc := &pane{ - icon: evt.Icon, - key: evt.Key, - dir: evt.Cwd, - title: evt.Title, - args: evt.Args, - killable: evt.Killable, - env: evt.Env, - } - term := tcellterm.New() - term.SetSurface(s.main) - term.Attach(func(ev tcell.Event) { - s.screen.PostEvent(ev) - }) - proc.vt = term - if evt.Autostart { - proc.start() - } - if !evt.Autostart { - proc.vt.Start(process.Command("echo", evt.Key+" has auto-start disabled, press enter to start.")) - proc.dead = true - } - s.processes = append(s.processes, proc) - s.sort() - s.draw() + s.InitProcess(evt.Key, evt.Args, evt.Icon, evt.Title, evt.Cwd, evt.Killable, evt.Autostart, evt.Env...) break case *tcell.EventMouse: @@ -381,3 +365,43 @@ func (s *Multiplexer) copy() { encoded := base64.StdEncoding.EncodeToString([]byte(data)) fmt.Fprintf(os.Stdout, "\x1b]52;c;%s\x07", encoded) } + +func (s *Multiplexer) InitProcess(key string, args []string, icon string, title string, cwd string, killable bool, autostart bool, env ...string) { + for _, p := range s.processes { + if p.key == key { + if p.dead && autostart { + p.start() + s.sort() + s.draw() + } + return + } + } + + proc := &pane{ + icon: icon, + key: key, + dir: cwd, + title: title, + args: args, + killable: killable, + env: env, + } + term := tcellterm.New() + term.SetSurface(s.main) + term.Attach(func(ev tcell.Event) { + s.screen.PostEvent(ev) + }) + proc.vt = term + + if autostart { + proc.start() + } else { + proc.vt.Start(process.Command("echo", key+" has auto-start disabled, press enter to start.")) + proc.dead = true + } + + s.processes = append(s.processes, proc) + s.sort() + s.draw() +} diff --git a/cmd/sst/mosaic/multiplexer/process.go b/cmd/sst/mosaic/multiplexer/process.go index efad1352d7..147dd6cf50 100644 --- a/cmd/sst/mosaic/multiplexer/process.go +++ b/cmd/sst/mosaic/multiplexer/process.go @@ -39,7 +39,7 @@ type EventProcess struct { } func (s *Multiplexer) AddProcess(key string, args []string, icon string, title string, cwd string, killable bool, autostart bool, env ...string) { - s.screen.PostEvent(&EventProcess{ + s.procChan <- &EventProcess{ Key: key, Args: args, Icon: icon, @@ -48,7 +48,7 @@ func (s *Multiplexer) AddProcess(key string, args []string, icon string, title s Killable: killable, Autostart: autostart, Env: env, - }) + } } func (p *pane) start() error {