Skip to content

fix: stopwatch.Start()#707

Merged
caarlos0 merged 1 commit intocharmbracelet:masterfrom
bevicted:master
Jan 22, 2025
Merged

fix: stopwatch.Start()#707
caarlos0 merged 1 commit intocharmbracelet:masterfrom
bevicted:master

Conversation

@bevicted
Copy link
Copy Markdown
Contributor

@bevicted bevicted commented Jan 22, 2025

stopwatch currently has a non deterministic way of starting. This is easily fixed by using tea.Sequence instead of tea.Batch.
timer is not affected as it doesn't use batch on Start()

Bug

Relevant code parts from stopwatch's file:

// Start starts the stopwatch.
func (m Model) Start() tea.Cmd {
	return tea.Batch(func() tea.Msg {
		return StartStopMsg{ID: m.id, running: true}
	}, tick(m.id, m.tag, m.Interval))
}

...

func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
	...
	case TickMsg:
		if !m.running || msg.ID != m.id {
			break
		}
	...
}

Batch's description:

Batch performs a bunch of commands concurrently with no ordering guarantees about the results.

So what basically happens is sometimes TickMsg arrives before StartStopMsg, as running is still false this is thrown away.
After that the StartStopMsg finally comes in and switches running to true, however as the TickMsg is already thrown away, nothing happens (except messing up someone's code who uses Toggle())

Workaround

func (sl *StatusLine) StartIOBuffer(msg string) tea.Cmd {
	sl.isIOInProgress = true
	sl.msg = msg
	// workaround until it's fixed in the stopwatch package
	bmsg := sl.stopwatch.Start()().(tea.BatchMsg)
	return tea.Batch(sl.spinner.Tick, tea.Sequence(sl.stopwatch.Reset(), bmsg[0], bmsg[1]))
}

As a temp workaround, because I (annoyingly) can't set StartStopMsg.running to true, BatchMsg's type is []tea.Cmd, so I unpacked it into a Sequence.

Contrib notes

Steps to reproduce are... well basically just use the package, calling Start and Stop/reinit model. Way I figured out what happens is set a UI texts to Running() and whether TickMsg was received or not.

Signed-off-by: Bence Vidosits <bence.vidosits1@ibm.com>
@bevicted bevicted requested a review from caarlos0 as a code owner January 22, 2025 06:28
@bevicted
Copy link
Copy Markdown
Contributor Author

bevicted commented Jan 22, 2025

I've tried to reproduce this using the example in bubbletea but couldn't, which is interesting.

My setup was very simple, like this:

func (sl *StatusLine) Update(msg tea.Msg) (*StatusLine, tea.Cmd) {
...
	case stopwatch.StartStopMsg, stopwatch.ResetMsg, stopwatch.TickMsg:
		sl.stopwatch, cmd = sl.stopwatch.Update(msg)
...
}

func (sl *StatusLine) StartIOBuffer() tea.Cmd {
	return sl.stopwatch.Start()
}

func (sl *StatusLine) StopIOBuffer() tea.Cmd {
	return sl.stopwatch.Stop()
}

I guess my source is now officially "trust me bro", I do hope though that based on the official tea.Batch docs and code I provided this makes sense

@caarlos0
Copy link
Copy Markdown
Contributor

ah, I can see that happening indeed.

good catch!

@caarlos0 caarlos0 changed the title fix stopwatch.Start() fix: stopwatch.Start() Jan 22, 2025
@caarlos0 caarlos0 merged commit 1bdd4c6 into charmbracelet:master Jan 22, 2025
@caarlos0
Copy link
Copy Markdown
Contributor

Thanks @bevicted

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants