Scheduling Events in Boodler’s “distant” future

One of my original requests to Andrew was to extend Boodler’s scheduling time limitation (of just over one hour), which seemed too restrictive for those of us who “think big” and wanted something to happen further in the future than that limitation would allow — a perfectly reasonable request, especially when you listen for extended periods of time, and/or want to introduce a high level of randomness in your environments (including events’ overall duration).

I believe part of my misunderstanding of the problem stemmed from the ‘owstorm’ agent — after all, why would you  want to restrict a thunderstorm to sixty minutes’ duration if you could make them longer ad lib?

After having mentioned this in an email (including about how I had altered agent.py to work around the limitation without ill effect, it seemed), Andrew explained the reason for the limitation — nothing to do with memory, as I had  originally thought, incidentally — which was because of “computations of sound position overflow”, in Andrew’s words.  There were more, but I won’t include them here.

He also roundly chastised me for my idiotic solution:

I cut [the scheduling limit] down to 1 hour to be safe, and to encourage people to solve their problems in agent code rather than in the scheduler.

(I see how well that worked.)

… Oops.

In response to the problem, I have penned a solution; an agent that I have tentatively dubbed ‘FutureAgent’, which looks like this:

from boodle.agent import Agent

class FutureAgent(Agent):
    """FutureAgent: subclass of Agent for scheduling events in the
    future.

    Methods to call:

    sched_future_agent() -- schedule an event for the 'distant' future.
    This is typically any value over 3600 seconds, although anything
    under that will work as well.

    Otherwise, treat it like any other Agent.
    """
    def __init__(self):
        Agent.__init__(self)
        self.timeleft = None

        # Don't set this value above 3600 or else you will defeat the
        # purpose.
        self.btimelimit = 3600

    def sched_future_agent(self, ag, delay=0, chan=None):
        """sched_future_agent(agent [, delay=0, chan=self.channel])

        Schedules an agent to run at some point in the distant future
        (e.g. greater than 3600 seconds). Unlike Boodler's agent, this
        scheduler  will keep calling a 'delaying' internal class before
        actually scheduling itself.

        """
        if self.timeleft is None:
            self.timeleft = delay

        if self.timeleft > self.btimelimit:
            # Time is greater than time limit, so sleep then try again.
            self._sleep(self.btimelimit)
        else:
            # Schedule the agent normally, now.
            self.sched_agent(ag, self.timeleft, chan)

    def _sleep(self, delay=3600):
        """sleep([delay=3600])

        Sleeps for the specified delay time. This would usually be 3600
        seconds, so as to circumvent Boodler's built-in scheduling
        limitation of a maximum of 3605 seconds.

        """
        if self.timeleft is None:
            errormsg = 'Time left for "%s" not yet set, can't sleep'
            raise NotImplementedError, errormsg % (self.getname(), )

        # Subtract the delay from the time left then reschedule myself.
        self.timeleft = self.timeleft - delay
        self.resched(delay)

Its use is straightforward — You import the module containing this class then use it (instead of Agent) to create your new agent. Then, when you need to schedule an event in the “distant” future — that is, further than 3600 seconds from  ‘now’ — you call sched_future_agent() instead of sched_agent(), and it will handle the delay without error (or so my  limited testing suggested, anyway).

I suspect that something like this might find its way into a future release of Boodler as part of the trunk, but I thought I’d share this now in case anyone else has run into a similar problem (and has to date found a similarly dull response to my own).

Thanks of course go to Andrew, who all but put the details in writing; I merely followed his suggestion and wrote the code to support them (any implementation errors or design flaws are therefore my own responsibility).

Leave a Reply