FR: Callback for sound.TimePosition changes

I would like to be able to stop playing a sound when the TimePosition property of a sound reaches or exceeds a given value without resorting to a wait loop. I propose adding a callback to the Sound object that would be called when the TimePosition exceeds a provided value.

Today:

sound.TimePosition = 3.84
local endPos = 6.97
sound:Play()
while sound.TimePosition < endPos do wait() end --Busy wait loop here. Ew.
sound:Stop()

With a callback:

sound.TimePosition = 3.84
local endPos = 6.97
sound:Play()
sound.TimePositionPassed(endPos, sound.Stop) --no busy loop. Yay!
1 Like

(‘Feature Requests’ > ‘Client Features’ would be the right category.)

Based on this post I don’t think they will add additional properties just for looping:

Also you can improve the busy-waiting in your code a bit like this:

sound.TimePosition = 3.84
local endPos = 6.97
sound:Play()
wait(endPos - sound.TimePosition - 0.1) -- solve most of busy waiting
while sound.TimePosition < endPos do wait() end
sound:Stop()

You can just do this using a wait loop.

while sound.TimePosition < 5 do
wait(0.1)
end
print(“finished”)

100 millisecond resolution should be enough for almost all game-related cases (we won’t be running any lua on the audio-processing thread obviously).

local function PlayClip(sound, startTime, stopTime)
	sound.TimePosition = startTime
	sound:Play()
	delay(
		(stopTime - startTime)/sound.Pitch,
		function() sound:Stop(); end
	)
end

PlayClip(thing, 3.84, 6.97)

Could work if you keep the pitch constant

The busy wait is what I’d like to avoid.

But there’s nothing “busy” about it :/ Something like that doesn’t even scratch performance if that’s what you’re implying.

1 Like

Thanks for your remarks @EchoReaper. It’s important to be specific so I will provide more info.

I’m referring to the software engineering term “Busy waiting”. It’s what I was demonstrating in the “Today” example in my original post and is generally considered an anti-pattern. My concern is that slower processors may perform poorly with a busy loop. In my scenario, if the sound plays 0.05 seconds beyond the calculated TimePosition the user’s experience will be degraded.

Here’s a discussion on busy waiting vs. callbacks on stackoverflow.

I’d test it out (best platform for this would be mobile) and see if there were any problems. Unless you are doing hundreds of these at once, there shouldn’t be.

Waits in ROBLOX aren’t busy waiting, they’re yields. This means that you check your condition, and if it’s false, you yield and something else runs. I’m not sure you’d get much of a hit on performance on the slowest of toasters doing that. You can also just bind something to RenderStepped or Heartbeat if you want to be as precise as the engine will ever allow.

1 Like

I am not claiming that a wait() in ROBLOX is a busy wait. I’m saying that the code to examine the condition, wait and then loop is a busy wait.

while sound.TimePosition < endPos do wait() end

This while loop is a busy wait.

Yes, there are workarounds. My intent with this FR is to improve the API to avoid anti-patterns. As the primary introduction to programming that ROBLOX is to many burgeoning programmers, weeding out anti-patterns seems worthwhile.

That’s not a busy wait, nor is it an anti-pattern. It’s no different than checking something each frame (if you wait for RunService.Heartbeat), which happens in games all the time. Like I said, ROBLOX’s wait() is not spinning. It’s yielding. Spinning would be this:

while sound.TimePosition < endPos do end

This is a busy wait, and you don’t want this. Yielding, however, solves the complaint that your own Wikipedia link claims:

In general, however, spinning is considered an anti-pattern and should be avoided,[1] as processor time that could be used to execute a different task is instead wasted on useless activity.

There is no need to add a callback for this, either. TimePosition is constantly changing in a predictable manner. Check it on RenderStepped or Heartbeat if you need this. That is literally the canonically correct way. Since TimePosition is changing, a callback would be called every frame anyway.

2 Likes

Good explanation. Thanks.

1 Like

It probably could have been better. I’m not so good at concisely conveying technical information…