This is great both for (a) confirming this isn't a dangerous hack, because I got one library to work this way but was not sure I'd done the right thing, and for (b) letting me know there's a version of Underscore for Lua! I use that all the time in javascript. Thanks!
Hmm, I kinda wonder if the Playdate's import function could have an optional param to automatically turn return values from imported files into into globals so we didn't have to modify a third-party library's code.
@matt I wasn't able to use the object-oriented style in underscore after importing this way. But I figured out that it works if I changed the last line of underscore.js to __ = Underscore:new()
At first, I set __ = Underscore
after importing, but got the same results if I used Underscore
in place of __
. So in my main.lua, I had:
import 'underscore'
__ = Underscore
This worked for the standard invocation. So say self.points
is a table of {x, y} points. I get the expected table of ints matching the x values if I run
local xPlucked = __.pluck(self.points, 'x')
,
but if I try
local xPlucked = __(self.points):pluck('x')
I get this error:
attempt to call a table value (global '__')
Eventually I tracked this down to needing to assign the variable directly to Underscore:new(), rather than the return. Ultimately I just commented out the last line of underscore.lua, and set the var after importing, like this:
import 'underscore'
__ = Underscore:new()
Thanks Nick. I had done an Underscore:new() but forgot the magic step 3. Sorry! That'll teach me to post in a hurry.
Underscore has proved very useful for me to reduce code manipulating tables. For example my code checking valid combinations/patterns is much reduced. Really elegant!
Well, today I learned about method chaining because of this post! Very cool!
This is really unintuitive but import
already returns the return value of the imported script, but-but import
s only work at the beginning of a line, but-but-but, since Lua doesn't really care about whitespace, you can write it like this:
local __ =
import 'underscore'
__.each({1, 2, 3}, print)
__({1, 2, 3}):each(print)
Both examples now work as expected with an unmodified underscore.lua using this method.
Oh — thanks! I'll never stop learning.
I absolutely love how these chain together. Thank you for sharing this!
I have one question that might be dumb — and is basically academic, because it's not a big deal: would it be possible to write this in a way that wouldn't require sequence.update()
to be called every frame? Can the computation of the value just be done when get()
is called?
Oh… now that I see you've added callbacks, I guess those wouldn't work properly if you didn't have .update()
called every frame.
not your fault @nick_splendorr but I had to stop using that round code as it would often spit out 0.09999999 rather than 0.1 which was tripping up a bunch of telemetry readouts in my game.
i now use the one below, it's from https://stackoverflow.com/a/57299034/28290 but slightly modified to work with the same syntax as the one you posted (no code changes!):
function math.round(number, precision)
local fmtStr = string.format('%%0.%sf',precision)
return string.format(fmtStr,number)
end
Yeah it was already like that before the callbacks because that's a design pattern that I like. An update function is called once and all sequence object will be based on that. With just .get()
you might actually have different result depending when it's called in the code because the timing is slightly different. So you might end up with two sprites using the same sequence but get slightly different results which mean they might be off by few pixels.
But that's true it would have been a simpler design to just have a getter which is also extremely valuable.
string manipulation in lua can be a costly operation so I would recommend using this round function only when you really need the precision.
Good point! I'm only using it in debug display. But I'll try to find a faster one.
@Nic what do you think about profiling each lua command on the Playdate hardware to give us an idea about how they perform. Maybe somebody has already done this for Lua?
I don't recall seeing any lua function benchmark. That could be useful but also a lot of work I guess (I will definitively not do that )
But for performance this lua documentation is always a good reference and in this case how string are handheld in lua.
I'll try the beginnings of a function benchmark.
I've read that doc and got some nice improvements!
I wanted to revisit the rounding function. So I write one that is less susceptible to float precision errors. After some testing I didn't encounter a single precision issue.
function math.round(number, precision)
local p = math.pow(10,precision)
local half = (number >= 0 and 0.5) or -0.5
return (number*p+half)//1/p
end
But I also benchmarked the different versions and to my surprise the string version is definitively not as bad as I would have thought. It is slower but it is still close enough to use without concerns IMO. I also run the benchmark code from within a bigger codebase with more strings and I still similar results.
Update:
It should be noted also that the round function used by Nick is actually much more flexible. So I rewrote a new one to keep it precise and flexible and also twice as fast as before because I like this type of pointless exercise
function math.round( number, bracket )
bracket = bracket or 1
-- path for additional precision
if bracket<1 then
bracket = 1//bracket
local half = (number >= 0 and 0.5) or -0.5
return (number*bracket+half)//1/bracket
end
local half = (number >= 0 and bracket/2) or -bracket/2
return ((number+half)//bracket)*bracket
end
Rounding Benchmark | - | 500000 | calls |
---|---|---|---|
string | > | 430 | ms |
splendorr | > | 160 | ms |
nic (new) | > | 77 | ms |
nic | > | 145 | ms |
Love you, Nic!
I will get to start the benchmarking suite this week.
This is such a common function I wonder if it would make sense to have a version written in C added to the math table. @dan ?
Sounds like it would be a good addition! Nobody's written a C version yet, right? I'd be curious to benchmark the speed difference.