I was writing yet another throttled onupdate, and i figured, I'd try to write something that was easier to reuse.
Maybe we could improve upon this so we have a reusable version of throttled onupdate for people to use.
Anyhow, here's the code (its dry code until I edit this post after testing)
local function throttle(func, interval)
local THROTTLE_TIME = interval
local throt = THROTTLE_TIME
return function(self, elapsed, ...)
throt = throt - elapsed
if throt < 0 then
throt = THROTTLE_TIME
func(self, elapsed, ...)
end
end
end
here's how I used it:
local function sample(self, elapsed)
-- My throttled OnUpdate handler code here
end
local frame = CreateFrame("Frame")
frame:SetScript("OnUpdate", throttle(sample, 0.1) )
i think the idea of assigning a function to a script handler as a return from a called function that creates a new function using the function you passed is pretty clever and shows some of the power of lua, but man... i think you're going to cause some people to go into an endless loop and lose their minds! could you imagine trying to explain this to a new lua user? i think some of the more confusing aspects to programming come from function handles vs function calls and this one like a puzzle.
that aside, again, i think it's pretty clever and i might even use it myself, but please, hide this from the children!
one thing i might suggest to help people follow things is to maybe define throttle(func,interval) as something like THROTTLE_WRAPPER(trottledFunction, interval) so people will recognize immediately that it's special in some way.
thinking about it, this would be useful for event handlers as well.
the function "throttle" creates an anonymous function (at execution time?) that wraps up the function passed to "thottle" (your "OnUpdate" handler code) with basic throttling code. the newly created function is then returned and that function (with throttling code) is passed to the SetScript command.
...and then the old ones return and devour humanity... or something.
Enh, what the hell, I'm waiting on SWMBO to finish stonecore so we can get some lunch. I've got time.
Get a whiteboard or a scrap of paper. Drawing boxes and lines with arrows is a big help to visualization. Evaluate expressions the same way the engine does:
First thing to boil down is sample and 0.1. Okay, done, they're already as boiled-down as they can get. The first is a reference to a function, the second is just a number.
Next is throttle, which could be called something like make_throttle or throttle_factory instead. ("Wrapper" describes a slightly different behavior, but that's a different topic.) I'm going to rewrite syl's code a little bit for purposes of exploration:
local function throttle (reference_to_periodic_function, interval)
-- Starting point. The name in all caps hints that it's a constant value.
local THROTTLE_TIME = interval
-- Initial countdown.
local throt = THROTTLE_TIME
-- What we're handing back.
local result
result = function(self, elapsed, ...) -- same as "function result (self, elapsed, ...)
throt = throt - elapsed
if throt < 0 then
throt = THROTTLE_TIME
reference_to_periodic_function(self, elapsed, ...)
end
end
-- Hand back a function which periodically calls the function whose
-- reference was passed in.
return result
end
The body of result is exactly like every other throttled OnUpdate function we've seen copied around on these boards. That's because every other throttled function IS exactly the same, except for the part down in the conditional branch that "does the real work". So here, the "real work" is factored out into its own function, and a reference to that function is passed in to the factory. The factory creates a little function of its own which *calls* the real work, and gives the user that function.
The little result function has its own "THROTTLED_TIME" and "throt" variables. They continue to exist after the factory has returned, because result still refers to them, and result continues to exist.
So this now has boiled down to
frame:SetScript("OnUpdate", <reference to "result" function> )
except that now each time Syl wants to make this kind of periodic throttle function, there's none of the copy-and-paste code for counting down and resetting the timer cluttering up the "real work".
So each frame refresh, the OnUpdate script is called, passing in a frame reference and the elapsed time. The call from the WoW client would be as if:
result (frame, some_elapsed_time, args...)
Inside, "throt" gets decremented by some_elapsed_time, and if the counter has run down, it gets reset and reference_to_periodic_function is called with the same arguments. That function reference is sample, originally passed into the factory function.
i wouldn't put "OnUpdate" in there just because this could useful for other things you'd like to throttle (for example, BAG_UPDATE or other potentially fast-firing events).
i wouldn't put "OnUpdate" in there just because this could useful for other things you'd like to throttle (for example, BAG_UPDATE or other potentially fast-firing events).
As is, it can only work out of the box with OnUpdate, so I think "ThrottledOnUpdateFactory" is appropriate.
With fast-firing events, you should use something more complex, probably using some kind of bucket.
For mass bag updates you can make a "bucket" fairly easily with this function anyway... You need a few flags to indicate which bags are dirty and need updated. In your onupdate, you wait for some interval before you update the dirty bags, then you hide yourself (to stop the onupdates). In your onevent you flag what bag is dirty and show the frame to start the onupdates (if it's already shown, it just keeps running). In the frame's onshow, you set the timer to 0 to ensure it is always reset when starting.
Net result: event fires for bag1, you flag it dirty and 1 second later it is scanned. Event fires for bag 2 and then for bag 3 15 times in a row, 1 second after the bag 2 event bags two and 3 are scanned.
Factory is a bad word. Returning functions is natural in functional programming language. Lua is brilliant exactly because functions are first order types.
Factories are artifacts of object-oriented languages trying to cope with the gnarliness of rigidity of the OO data/method organizing principle in cases where you actually do not want quite so strong a rigidity. Luckily lua has a loose data structure that can be flexibly be associated with functions, so no need to factory a solution. Just return a function and make sure it has access to the data it needs.
Factory is a bad word. Returning functions is natural in functional programming language. Lua is brilliant exactly because functions are first order types.
Factories are artifacts of object-oriented languages trying to cope with the gnarliness of rigidity of the OO data/method organizing principle in cases where you actually do not want quite so strong a rigidity. Luckily lua has a loose data structure that can be flexibly be associated with functions, so no need to factory a solution. Just return a function and make sure it has access to the data it needs.
It was just a suggestion. Didn't mean to touch a nerve.
IMHO, anything that communicates the purpose well and clearly, especially to users trying to learn, is a win. Language design wars take a distant second.
Factory is a bad word. Returning functions is natural in functional programming language. Lua is brilliant exactly because functions are first order types.
Factories are artifacts of object-oriented languages trying to cope with the gnarliness of rigidity of the OO data/method organizing principle in cases where you actually do not want quite so strong a rigidity. Luckily lua has a loose data structure that can be flexibly be associated with functions, so no need to factory a solution. Just return a function and make sure it has access to the data it needs.
Actually OOP isn't a bad thing as your wording suggests. It's just a different programming paradigm. Both have different rules, advantages and drawbacks.
I don't disagree with you. I mostly object to the use of the word Factory for returning functions. Factories emerged exactly to mitigate a drawback of the OOP paradigm (mostly in a Java context), and passing functions as values is exactly a strength of functional programming.
It was just a suggestion. Didn't mean to touch a nerve.
IMHO, anything that communicates the purpose well and clearly, especially to users trying to learn, is a win. Language design wars take a distant second.
No problem, I'm mostly articulating this to educate. I think it's neat to understand the strengths and weaknesses of different programming paradigms. That is not to bash one or the other but to be clear what how they function.
Maybe we could improve upon this so we have a reusable version of throttled onupdate for people to use.
Anyhow, here's the code (its dry code until I edit this post after testing)
here's how I used it:
i think the idea of assigning a function to a script handler as a return from a called function that creates a new function using the function you passed is pretty clever and shows some of the power of lua, but man... i think you're going to cause some people to go into an endless loop and lose their minds! could you imagine trying to explain this to a new lua user? i think some of the more confusing aspects to programming come from function handles vs function calls and this one like a puzzle.
that aside, again, i think it's pretty clever and i might even use it myself, but please, hide this from the children!
one thing i might suggest to help people follow things is to maybe define throttle(func,interval) as something like THROTTLE_WRAPPER(trottledFunction, interval) so people will recognize immediately that it's special in some way.
thinking about it, this would be useful for event handlers as well.
---
I prefer to keep my sanity .. seriously I don't understand it at all =(
...and then the old ones return and devour humanity... or something.
Get a whiteboard or a scrap of paper. Drawing boxes and lines with arrows is a big help to visualization. Evaluate expressions the same way the engine does:
First thing to boil down is sample and 0.1. Okay, done, they're already as boiled-down as they can get. The first is a reference to a function, the second is just a number.
Next is throttle, which could be called something like make_throttle or throttle_factory instead. ("Wrapper" describes a slightly different behavior, but that's a different topic.) I'm going to rewrite syl's code a little bit for purposes of exploration:
The body of result is exactly like every other throttled OnUpdate function we've seen copied around on these boards. That's because every other throttled function IS exactly the same, except for the part down in the conditional branch that "does the real work". So here, the "real work" is factored out into its own function, and a reference to that function is passed in to the factory. The factory creates a little function of its own which *calls* the real work, and gives the user that function.
The little result function has its own "THROTTLED_TIME" and "throt" variables. They continue to exist after the factory has returned, because result still refers to them, and result continues to exist.
So this now has boiled down to
except that now each time Syl wants to make this kind of periodic throttle function, there's none of the copy-and-paste code for counting down and resetting the timer cluttering up the "real work".
So each frame refresh, the OnUpdate script is called, passing in a frame reference and the elapsed time. The call from the WoW client would be as if:
Inside, "throt" gets decremented by some_elapsed_time, and if the counter has run down, it gets reset and reference_to_periodic_function is called with the same arguments. That function reference is sample, originally passed into the factory function.
How about ThrottledOnUpdateFactory
maybe TrottledScriptFactory or something...?
As is, it can only work out of the box with OnUpdate, so I think "ThrottledOnUpdateFactory" is appropriate.
With fast-firing events, you should use something more complex, probably using some kind of bucket.
I think this is an example of the Decorator design pattern.
err... yeah, duh.
Net result: event fires for bag1, you flag it dirty and 1 second later it is scanned. Event fires for bag 2 and then for bag 3 15 times in a row, 1 second after the bag 2 event bags two and 3 are scanned.
Factories are artifacts of object-oriented languages trying to cope with the gnarliness of rigidity of the OO data/method organizing principle in cases where you actually do not want quite so strong a rigidity. Luckily lua has a loose data structure that can be flexibly be associated with functions, so no need to factory a solution. Just return a function and make sure it has access to the data it needs.
It was just a suggestion. Didn't mean to touch a nerve.
IMHO, anything that communicates the purpose well and clearly, especially to users trying to learn, is a win. Language design wars take a distant second.
Actually OOP isn't a bad thing as your wording suggests. It's just a different programming paradigm. Both have different rules, advantages and drawbacks.
No problem, I'm mostly articulating this to educate. I think it's neat to understand the strengths and weaknesses of different programming paradigms. That is not to bash one or the other but to be clear what how they function.