Rockbox Technical Forums

Support and General Use => Plugins/Viewers => Topic started by: splondike on January 30, 2011, 12:51:13 AM

Title: Unexpected playback stoppage in new Lua plugin
Post by: splondike on January 30, 2011, 12:51:13 AM
I've written a game plugin which I would like to see included in the official Rockbox distribution. Everything is working across all colour players (and others get an unsupported message) apart from one piece of unexpected behaviour. My player is a Fuze v2.

During the initialization part of a new game I have a CPU intensive and highly recursive function called 'calculate_par' (CP). If this function is invoked while RB is playing a track, the playback is often stopped partway through evaluating CP. This problem can be replicated on the emulator, but CP seems to be able to run on a bigger dataset before it manifests. I'm looking for some ideas about how to identify the reason for this and how to fix it.

I've written a bunch of test case scripts to see if I can replicate the problem (see footnotes for the code):

I've tried putting a rb.yield() after every statement in the CP function tree, this had no apparent effect on the device, but increased the threshold on the computer beyond what I could easily test. I put in some logging commands in CP and found that the depth of recursion reached before playback stopped was anywhere between 95 and 250. The memory used by Lua (as returned by collectgarbage("count")) was similarly variable, going from 250 to 400 before the playback stopped.

I spent a reasonably amount of time optimising CP for speed (it went from ~30s to ~1s for one data set), and is well suited to a recursive approach. As such I'd rather not rewrite it using iteration, especially when I'm not sure that the problem is a recursion limit.

If I can't get this to work, I probably have the option of just calling something like rb.audio_resume() when CP finishes (if required). Is this hack-ish solution likely to be accepted?

Test scripts:
(1) Max CPU
Code: [Select]
i=0
for i=1,1000000 do
rb.yield()
i = i + 1
end
(2) Lots of recursion
Code: [Select]
function recurse(num)
rb.yield()
if num == 0 then
return 0
else
return recurse(num - 1)
end
end

recurse(1000000)
(3) Table filling
Code: [Select]
table={}
i=1
while rb.audio_status() == 1 do
table[i] = "blh"
i = i + 1
rb.yield()
end
rb.splash(300, i)
rb.splash(300, collectgarbage("count"))
Title: Re: Unexpected playback stoppage in new Lua plugin
Post by: saratoga on January 30, 2011, 01:39:46 AM
I don't know anything about lua, but it looks like your number 3 test case is dynamically growing that array 3 or 4 bytes at a time.  Since we don't have virtual memory in rockbox, theres a good chance that can fragment the malloc buffer and grind things to a halt.

Since your plugin doesn't seem to crash, its probably then stealing the audio buffer.  I grepped the lua plugin for "audio" and as expected rockboxmalloc.c does steal the audio buffer (which halts playback) when the malloc buffer is exhausted.  So I think the problem (at least for your test case) is that you're consuming far more memory then you really have available.  

What happens in your number 3 test case if instead of appending the array, you just overwrite the first element so that its always the same size?

Edit:  Just noticed this:

recurse(1000000)

You can't possibly do that in c since the stack would overflow.  But if it doesn't generate a stack overflow exception and crash your player it probably means lua dynamically resizes the stack to avoid overflows.  In which case you'll have much the same problem with dynamically growing the array in your case 3. 
Title: Re: Unexpected playback stoppage in new Lua plugin
Post by: splondike on January 31, 2011, 05:01:25 AM
I kinda suspected that it was cannibalizing the audio buffer. Another thing I meant to test yesterday was manually forcing regular garbage collection with collectgarbage("collect"). Seems I forgot to do so, because this fixed the problem.

Thanks for your assistance saratoga, expect to see the plugin in Flyspray soon :).

Edit: Changing the line in (3) to table[1] = "blah" caused the script to run without breakage for 5 minutes after which I restarted the player. Changing the memory eating operation to a string append had the same effect as with the table (but took a bit longer).