Any agent can trigger events on a channel. Outside circumstances can also trigger events, if Boodler is listening to them. (However, the timing of outside events is never exact.)
Any agent can listen for events. When you listen for events, you can discriminate in two ways: the channel you listen on, and the event names you are listening for.
When you listen on a channel, you hear events on subchannels as well. (If you listen on the root channel, you hear all events.)
The name of an event is a hierarchical qualified name: com.eblong.whatever.event. These names are not tied to sound package names. (Maybe they should be?) When you listen for a name, you hear events with that name and any sub-names for it. (So listening for com.eblong would catch com.eblong.whatever.event. If you do not specify a name, or give the empty string, you will hear all names.)
In addition to its name, an event can contain any number of Python objects as data.
The agent class has a listen() method. You can put a call to listen() in the agent's run() method.
If the agent is to run more than once -- say, if it reschedules itself -- you will want to ensure that it only calls listen() once. (If you make multiple listen() calls, you are setting up multiple handlers. Each event will trigger every handler you set up.)
The easiest way to do this is to use the firsttime field:
def run(self):
if self.firsttime:
self.listen()
#...
self.resched(1.0)
An agent's listen() method should only be called by the agent itself. What if you want to create another agent, and set it listening?
The obvious path is to put the listen() call in its run() method, as described above. The first time the new agent runs, it will begin listening.
However, if you find this scheme (or the firsttime field) to be inelegant, you can create the agent and call its post_agent() method. This sets it up (without scheduling it), and then calls listen() for you.
An agent can take either of two basic plans towards event listening:
In either case, when an appropriate event occurs, the agent's handler method will run. This can do any of the usual things that agents do: schedule notes, create channels, schedule other agents. The difference between the two plans arises if the agent tries to schedule (or reschedule) itself.
In plan A, the agent is "waiting forever". Because it is not initially on the schedule, it can respond to events by scheduling itself. However, this requires care, because several events could occur in close succession. If the agent tries to schedule itself when it's already scheduled, you'll get an error.
(A better plan is to separate the work into two agents. The first listens for events. When it hears one, it creates a new instance of the second agent and schedules that.)
In plan B, you have a single agent on the schedule; but it listens for events while it waits. The event handler cannot reschedule the agent, since it is already on the schedule. (The agent's normal run method can reschedule it, of course, in the usual way.)
(This plan is best when the agent does not wish to take any overt action for events -- just adjust some internal variables. For example, a continuous rainstorm might get louder if thunder has sounded in the last thirty seconds. The thunder listener would set a volume flag, which the rain agent would take into account at its next iteration.)
Normally, a channel expires when no agents are scheduled in it. This means that the "wait forever unscheduled" model of agent will expire with it.
When you set up a listener, you can declare that it will hold the channel it is attached to. A channel will not expire when an agent has a hold on it. The hold ends when the agent stops listening, or if the channel is explicitly stopped.
(In fact, a listener can be associated with two channels: the channel it is listening to, and the channel it will run in when it hears an event. If these are different, you can declare that it will hold either or both.)