Jump to content

Multi-Timer Algorithm


Love Zhaoying
 Share

You are about to reply to a thread that has been inactive for 663 days.

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

I've been working on an algorithm for a "multi-timer".  That is, a script that can use a single timer() event across multiple timer needs.
 
Let me know what you think!
 
Thanks,
 
Love
 
Global variables needed:
 
integer TimerCount - # of timers (helps with the JSON logic)
string jsonHeap - Keeps the individual timer Object data
 
These are globals for the "current" timer, but also represent an individual JSON entry:
string TimerName  Name of the current Timer - Also the key in each JSON entry
TimerLastFiredTime - When the last timer fired - element in each JSON entry
TimerLengthTime - Time period for the last timer - element in each JSON entry
TimerExpectedNextFireTime - Expected time when the next timer will fire - element in each JSON entry
TimeCallBackInfo - Expected information that will be needed to tell some other script which timer fired (besides just the TimerName), etc.
 
Note: Timer Last fired time + Timer length time = Timer expected next fire time
 
  1. When adding a new timer with TimerTime:
 
a. If no timers yet, set timer, save to list, set globals.
 
b. Else timers exist in the list:
Calculate TempExpected for new timer if it were set now: CurrentTime + TimerTime.
 
c. Then: Check all timers:
if TempExpected < TimerExpectedNextFireTime for all, use new timer instead
- Set timer via llSetTimerEvent()
- Set globals,
- Add to timers in JSON
Otherwise if this new timer would NOT be the first "expected timer", just add it to the timers:
- Add to timers in JSON

1. When the current timer fires:

 
a. Check all timers
Calculate new ExpectedTime.
Find the next timer with soonest ExpectedTime.
 
B. Set timer to the Timer with the soonest ExpectedTime via llSetTimerEvent()
Use that timer for globals.
 
2. When deleting a timer:
 
a. If timer is the current global, stop the timer so it won't fire via llSetTimerEvent(0.0).
b. Delete timer from JSON 
c. Perform same steps as in #1 above.
 
3. To delete all timers:
 
Stop timer via llSetTimerEvent(0.0)
Delete all the JSON data: jsonHeap="";
 
Link to comment
Share on other sites

Why are you using JSON rather than a regular list? I've looked at JSON a little, but never understood what advantage it had for the increased processing time.

Also, I'm not sure why you're storing redundant information:

  • I would either store the last fire time, or the next fire time, but not both. as you cna calculate one from the other by adding/subtracting the fire rate.
  • I would usually embed this sort of thing in a single script, so no information other than the name of the timer is neccessary to distinguish it from others.
  • Like 2
  • Thanks 1
Link to comment
Share on other sites

This would seem to be a specific case of a multiplexed timer system, as outlined in the LSL Tips sticky thread at the top of this forum. One of the challenges of multiplexing is that it generally means running a fast timer whose rate is the lowest common divisor of all the periods you want the script to watch. If I understand your algorithm correctly, it would still require running the timer at the fastest rate you designate in your JSON array, but it would ease the requirement that all of the rates have a common divisor.  I like that idea. I wonder only whether it will lead to accumulating small roundoff errors each time that you calculate and reset the timer.  Would it be smart, say, to establish a start time -- either with llGetUnixTime or simply with llResetTime -- and periodically making an offset correction relative to it?

  • Thanks 1
Link to comment
Share on other sites

4 minutes ago, Quistess Alpha said:

Why are you using JSON rather than a regular list? I've looked at JSON a little, but never understood what advantage it had for the increased processing time.

Setting the timer model aside, that's an interesting question in its own right.  I have spent very little time with JSON in SL because I cannot see any advantage to it myself. I have the impression that JSON capability was introduced a while back to accommodate scripters who are already comfortable using it outside of SL, not because we really needed it to do anything that LSL wasn't already doing with lists. 

  • Like 2
Link to comment
Share on other sites

2 minutes ago, Rolig Loon said:

If I understand your algorithm correctly, it would still require running the timer at the fastest rate you designate in your JSON array, but it would ease the requirement that all of the rates have a common divisor. 

Yes! However, I don't see a need for "fast" timers, so the real use-case is "multiple client scripts served by 1 timer server" with probably a 1-second offset at minimum for each timer.

So, a 1-second "max" timer with multiple clients that would only need 1-second max timers, offset by at least 1 second each. 

My design idea was for sub-seconds but, I don't have a use-case currently. Maybe 0.5 second max.

Link to comment
Share on other sites

9 minutes ago, Quistess Alpha said:

Why are you using JSON rather than a regular list? I've looked at JSON a little, but never understood what advantage it had for the increased processing time.

Also, I'm not sure why you're storing redundant information:

  • I would either store the last fire time, or the next fire time, but not both. as you cna calculate one from the other by adding/subtracting the fire rate.
  • I would usually embed this sort of thing in a single script, so no information other than the name of the timer is neccessary to distinguish it from others.

I've been using JSON more lately, and benefitting from the features. Single JSON "list", multiple elements per object vs. either multiple lists, or lists with multiple strides. A LOT less coding is required this way. Plus, if I want to add a "feature" it's just another named element in the JSON object. 
 

In another project, I'm using 1 JSON heap with multiple object types keyed multiple ways to do a reverse lookup of client vs. server keys for some specific task - and it's only 6 lines of llJson() code. 

Link to comment
Share on other sites

6 minutes ago, Rolig Loon said:

Setting the timer model aside, that's an interesting question in its own right.  I have spent very little time with JSON in SL because I cannot see any advantage to it myself. I have the impression that JSON capability was introduced a while back to accommodate scripters who are already comfortable using it outside of SL, not because we really needed it to do anything that LSL wasn't already doing with lists. 

See my answer to Quintess but also: I had avoided JSON due to perceived memory use by JSON, and warnings (old) in the LSL Wiki about script timing difference vs. lists. But, having done more JSON programming the last few years in my RL job, I started fresh with JSON in a recent big LSL project, and am VERY happy with that decision. 

Link to comment
Share on other sites

"Fast" is a relative (and negotiable) definition.  To my way of thinking, you should always use the slowest timer rate you can get away with, so as to reduce script time load on the region servers. So anything that is faster than it needs to be is "fast".    My point was that if you have a basic multiplexed timer system that has to watch activities at 1.0, 1.3, and 1.7 second intervals, you need to design the script to trigger no slower than once every 0.1 seconds (since anything slower than that will not prompt actions at each of those intervals).  I'd say that a 0.1 second timer is "fast" and almost certainly one to avoid if you can.

With your model, the script could start at the base rate of 1.0 seconds. When it triggers, it then looks at the JSON array to calculate when the next expected trigger should occur.  In this case, that would be 0.3 seconds later, so you llSetTimerEvent(0.3).  When that trigger occurs, repeat the exercise and llSetTimerEvent(0.4) so that the timer fires 1.7 seconds after the script started.   And so on and so on.  The script would still be running ":fast", but at a variable fastness and never faster than once every 0.3 seconds, which is an improvement over 0.1.

Edited by Rolig Loon
typos. as always.
  • Thanks 1
Link to comment
Share on other sites

FYI - the only things LSL JSON seems to lack that I revert to lists for, posted in the Pet Peeves thread recently, I was able to code around using "just JSON" but ultimately leverage lists for:

- Getting a JSON array length

- Sorting a JSON list or array

- Convert JSON to CSV is supported but if it's not an array and you just want 1 element, going through a strided list first is better

Edited by Love Zhaoying
Link to comment
Share on other sites

1 minute ago, Rolig Loon said:

"Fast" is a relative (and negotiable) definition.  To my way of thinking, you should always use the slowest timer rate you can get away with, so as to reduce script time load on the region servers. So anything that is faster that it needs to be is "fast".    My point was that if you have a basic multiplexed timer system that has to watch activities at 1.0, 1.3, and 1.7 second intervals, you need to design the script to trigger no slower than once every 0.1 seconds (since anything slower than that will not prompt actions at each of those intervals).  I'd say that a 0.1 second timer is "fast" and almost certainly one to avoid if you can.

With your model, the script could start at the base rate of 1.0 seconds. When it triggers, it then looks at the JSON array to calculate when the next expected trigger should occur.  In this case, that would be 0.3 seconds later, so you llSetTimerEvent(0.3).  When that trigger occurs, repeat the exercise and llSetTimerEvent(0.4) so that the timer fires 1.7 seconds after the script started.   And so on and so on.  The script would still be running ":fast", but at a variable fastness and never faster than once every 0.3 seconds, which is an improvement over 0.1.

I'm expecting and ok with some accuracy lost due to my approach. Think of a simple listener timeout for user input, normally those are multi-second.
 

But yes, I see your point and don't disagree, it's just not a concern with my current use-case. If I needed a "faster" timer, I could still use the timer "server" with just a single timer; assuming it's in the same object as the client script for relatively fast link_message() communication. 
 

Part of the overall design reason: the initial client script will only have 1 or 2 events: link_message() and possibly dataserver() if I leave the notecard read in that script. Any other events get farmed out to separate script(s). Im finishing that up currently for listen().

Link to comment
Share on other sites

56 minutes ago, Quistess Alpha said:

I would either store the last fire time, or the next fire time, but not both. as you cna calculate one from the other by adding/subtracting the fire rate.

Good catch, I was actually only going to store the "last fire time" since the "next fire time" would have to be recalculated anyway each time I check the list in JSON. I failed to update my notes, so that crept into the post.

Link to comment
Share on other sites

58 minutes ago, Quistess Alpha said:

I would usually embed this sort of thing in a single script, so no information other than the name of the timer is neccessary to distinguish it from others.

The JSON will store the client script's needed callback information. Message prefix (not shown), timer name..GUID / handle if I need one. Whatever it is, this script will send it back to the client script, so that the client script knows exactly where to pick up after the timer. 

Link to comment
Share on other sites

30 minutes ago, Love Zhaoying said:

Good catch, I was actually only going to store the "last fire time" since the "next fire time" would have to be recalculated anyway each time I check the list in JSON. I failed to update my notes, so that crept into the post.

Actually, depending on your algorithm, you might not even need a fire time, if you don't mind the first firing to be arbitrary, fire when count%timerLength ==0;

  • Like 1
Link to comment
Share on other sites

1 hour ago, Quistess Alpha said:

Actually, depending on your algorithm, you might not even need a fire time, if you don't mind the first firing to be arbitrary, fire when count%timerLength ==0;

I only plan to fire when needed. 
Although, if I knew and committed that all timers would be at multiples of a second, then yes. But I'd still have to track more than just the timer length for multiple active timers of different duration, to track for instance "when 7-second timer C" fired last. Otherwise, I don't know when it should fire next - unless it is the "global" timer. The "global" timer just represents the current "next to fire" (shortest time duration until next fire).

Tracking "tick" counts works too.

Link to comment
Share on other sites

8 minutes ago, Love Zhaoying said:

Tracking "tick" counts works too.

That's how most multiplexed timers work but, as I said earlier, that requires you to have each of your separate timer instances firing at some multiple of the base (fastest) timer rate. 

What Tessa is saying is that if all you care about is that things are happening every 3, 6, 10, 25 seconds but really don't care whether they started at 5:13:00 p.m. on 9 July 2022, you don't really need to record a firing time.  You just llSetTimerEvent(1.0), ++count each time the event triggers, and pay attention to when ++count%3 == 0, or ++count%6 == 0, and so on.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

13 minutes ago, Rolig Loon said:

That's how most multiplexed timers work but, as I said earlier, that requires you to have each of your separate timer instances firing at some multiple of the base (fastest) timer rate. 

What Tessa is saying is that if all you care about is that things are happening every 3, 6, 10, 25 seconds but really don't care whether they started at 5:13:00 p.m. on 9 July 2022, you don't really need to record a firing time.  You just llSetTimerEvent(1.0), ++count each time the event triggers, and pay attention to when ++count%3 == 0, or ++count%6 == 0, and so on.

I see 2 two main drawbacks (and of course plenty of benefits): I'd have to always run time timer at 1 second, even if I only needed a single 30-second timer (unless the algorithm adjusted for that type of situation). And, I'd have to check all the timers on each fire, vs. just when the "next soonest" timer fires. 

So, the advantage that I'm building into my timer is, it only fires when needed, and I only need to check "the other timers" when it does fire. I hope that makes sense!

  • Like 1
Link to comment
Share on other sites

24 minutes ago, Rolig Loon said:

that requires you to have each of your separate timer instances firing at some multiple of the base (fastest) timer rate. 

Having a base rate doesn't preclude "skipping over" times when no timer will fire, and incrementing by the time to get to the next 'real' event:

list gTimeIntervals = [10,11,12];
integer gTick; // increment by 1 each second.

integer BIG = 100000; // a very large number.

default()
{   
    state_entry()
    {   integer min = llListStatistics(LIST_STAT_MIN,gTimeIntervals);
        gTick = min;
        llSetTimerEvent(min);
    }
  	timer()
    { // assume the timer got started at some point.
        integer time_next=BIG; // initialize to a very large value.
        integer index = llGetListLength(gTimeIntervals);
        while(~--index)
        {   integer interval = llList2Integer(gTimeIntervals,index);
            integer next = gTick%interval;
            if(!next)
            {  // do something because a timer went off.
            }else
            {  time_next = ((time_next<next)*(time_next-next)) + next; // set to the smaller of next, time_next.
            }
        }
        // if all of the timers went off at once, we need to correct for that.
        if(time_next==BIG) time_next = llListStatistics(LIST_STAT_MIN,gTimeIntervals);
        gTick+= time_next;
        llSetTimerEvent(time_next);
    }
}

(untested)

Edited by Quistess Alpha
Improved non-branching math.
  • Like 1
  • Thanks 2
Link to comment
Share on other sites

1 minute ago, Love Zhaoying said:

I hope that makes sense!

It does, and that's what I understood you were trying to do.  So, generalizing Tessa's comment, if you don't care exactly when your system started counting, then you don't need to record a starting time at all.  All that you really care about is the amount of time that has elapsed since the last firing, so that the script knows how to llSetTimerEvent to anticipate the next one.  You don't need an offset in clock time.

  • Thanks 1
Link to comment
Share on other sites

4 minutes ago, Quistess Alpha said:

Having a base rate doesn't preclude "skipping over" times when no timer will fire, and incrementing by the time to get to the next 'real' event:

I agree.  I assumed that's the sort of thing Love was proposing, and it's a great improvement over a normal multiplexed timer, which always keeps ticking away at some fast base rate.  What I was pointing out is that whichever model he chooses, he doesn't need to record a starting time unless he really cares whether his timer is synced with a real clock somewhere, which is what I understood your point to be.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Briefly returning to the comment I made much earlier, the only thing I worry about a little bit is that each time you go through a calculation and then reset llSetTimerEvent with a new value, you will have some small error due to the time it takes to actually do those things.  Over time, those tiny errors may accumulate to the point where their long-term effect is non-trivial.  It all depends on how big the errors are and how long the script is running (and on how picky you want to be about precision, of course).  For that reason, it might be important to actually record a starting time and then periodically check the accumulated trigger times of all those little timer events against llGetUnixTime - initial_recorded_time to resync the whole system.

With that specific case in mind, I can therefore see some reason to record an initial time.  Otherwise, meh.

  • Thanks 1
Link to comment
Share on other sites

1 hour ago, Rolig Loon said:

With that specific case in mind, I can therefore see some reason to record an initial time.  Otherwise, meh.

Yeah, at that point it really depends on the use-case and the absolute magnitude of the times in question. I think "The time I expect the timer to go off next" would be mor useful than the initial time, but here's not too much of a difference.

Also, This is totally just biased superstition, but when the time-interval is much longer than an hour (say, daily or once per week) I set the timer event to an hour and check a global clock. Hourly timers seem pretty safe as far as region script load is concerned.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

i am a fan of using integer ticks in multiplex timers.  Our script runs in server dilation time which is variable against normal time. So any time measurement method we use is also going to be variable in the same way

with integer ticks then we can know that there is a constant relationship between the timers no matter the dilation time

and I prefer to go with lowest common denominator (LCD) method as mentioned by Rolig

for example: TimerA = 2, TimerB = 3, TimerC = 4; the LCD is 1. TimerA = 2, TimerB = 4, TimerC = 6; the LCD is 2

should I want timers to work in normal time (real time) then I typically go with customised-for-purpose methods that check against actual normal time when the timer event fires  

 

Link to comment
Share on other sites

On 7/9/2022 at 3:36 PM, Love Zhaoying said:

FYI - the only things LSL JSON seems to lack that I revert to lists for, posted in the Pet Peeves thread recently, I was able to code around using "just JSON" but ultimately leverage lists for:

- Getting a JSON array length

Wow! I did get this one wrong, in the context of "process everything in the JSON array".

Because, all you have to do is read each item in the JSON array until the return value is JSON_INVALID.

Oopsie!

Guess I've been avoiding situations where JSON_VALID may be returned, but I'm not certain if "that's OK".  Not no more!

 

  • Sad 1
Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 663 days.

Please take a moment to consider if this thread is worth bumping.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
 Share

×
×
  • Create New...