diff --git a/src/commands/general.py b/src/commands/general.py index 510eb44c78..e13f376a58 100644 --- a/src/commands/general.py +++ b/src/commands/general.py @@ -775,3 +775,21 @@ def cmd_help(command): source_object.emit_to(string) GLOBAL_CMD_TABLE.add_command("help", cmd_help) + +## def cmd_testevent(command): +## from src import events +## from src import scheduler +## source_object = command.source_object + +## if not command.command_argument: +## #event = events.IntervalEvent() +## event = events.IntervalEvent() +## event.repeats = 3 +## event.interval = 5 +## pid = scheduler.add_event(event) +## source_object.emit_to("event with pid %s added." % pid) +## else: +## pid = command.command_argument +## scheduler.del_event(pid) +## source_object.emit_to("event with pid %s removed (if it existed)." % pid) +## GLOBAL_CMD_TABLE.add_command("testevent", cmd_testevent) diff --git a/src/commands/info.py b/src/commands/info.py index e3c0779923..d2016f1d10 100644 --- a/src/commands/info.py +++ b/src/commands/info.py @@ -135,13 +135,18 @@ def cmd_ps(command): """ source_object = command.source_object - source_object.emit_to("-- Interval Events --") - for event in scheduler.schedule: - source_object.emit_to(" [%d/%d] %s" % ( - event.get_nextfire(), - event.interval, - event.description)) - source_object.emit_to("Totals: %d interval events" % (len(scheduler.schedule),)) + source_object.emit_to("Processes Scheduled:\n-- PID [time/interval] [repeats] description --") + for event in scheduler.SCHEDULE: + repeats = "[inf] " + if event.repeats != None: + repeats = "[%i] " % event.repeats + source_object.emit_to(" %i [%d/%d] %s%s" % ( + event.pid, + event.get_nextfire(), + event.interval, + repeats, + event.description)) + source_object.emit_to("Totals: %d interval events" % (len(scheduler.SCHEDULE),)) GLOBAL_CMD_TABLE.add_command("@ps", cmd_ps, priv_tuple=("genperms.process_control",), help_category="Admin") diff --git a/src/commands/privileged.py b/src/commands/privileged.py index b247fd4e3d..f97474495f 100644 --- a/src/commands/privileged.py +++ b/src/commands/privileged.py @@ -15,6 +15,9 @@ from src.helpsys import helpsystem from src.config.models import CommandAlias from src.config import edit_aliases from src import cache +from src import scheduler + + def cmd_reload(command): """ @reload - reload game subsystems @@ -761,3 +764,37 @@ def cmd_setcmdalias(command): GLOBAL_CMD_TABLE.add_command("@setcmdalias", cmd_setcmdalias, priv_tuple=("genperms.process_control",), help_category="Admin") + + +def cmd_delevent(command): + """ + @delevent - remove events manually + + Usage: + @delevent + + Removes an event with the given pid (process ID) from the event scheduler. + To see all active events and their pids, use the @ps command. + """ + source_object = command.source_object + + if not command.command_argument: + source_object.emit_to("Usage: @delevent ") + return + try: + pid = int(command.command_argument) + except ValueError: + source_object.emit_to("You must supply a valid pid number.") + return + event = scheduler.get_event(pid) + if event: + desc = event.description + scheduler.del_event(pid) + source_object.emit_to("Event %i - '%s' removed." % (pid, desc)) + else: + source_object.emit_to("No event found with a pid of %i. Use @ps to list process IDs." % pid) + +GLOBAL_CMD_TABLE.add_command("@delevent", cmd_delevent, + priv_tuple=("genperms.process_control",), + help_category="Admin") + diff --git a/src/events.py b/src/events.py index eae65237a7..a8b283e230 100644 --- a/src/events.py +++ b/src/events.py @@ -18,18 +18,28 @@ class IntervalEvent(object): """ Represents an event that is triggered periodically. Sub-class this and fill in the stub function. + + self.repeats decides if this event will fire indefinitely or only a + certain number of times. """ - def __init__(self): + def __init__(self, description="IntervalEvent"): """ Executed when the class is instantiated. """ + # This is a globally unique ID of this event. If None, a new one will + # be allocated when the event is added to the scheduler. + self.pid = None # This is set to prevent a Nonetype exception on @ps before the # event is fired for the first time. self.time_last_executed = time.time() - # This is what shows up on @ps in-game. - self.name = None + # This used to describe the event in @ps listings. + self.description = description # An interval (in seconds) for execution. self.interval = None + # How many times to repeat this event. + # None : indefinitely, + # positive integer : number of times + self.repeats = None # A reference to the task.LoopingCall object. self.looped_task = None @@ -39,6 +49,18 @@ class IntervalEvent(object): """ return self.name + def __eq__(self, event2): + """ + Handles comparison operations. + """ + return self.pid == event2.pid + + def __hash__(self): + """ + Used for dictionary key comparisons. + """ + return self.pid + def start_event_loop(self): """ Called to start up the event loop when the event is added to the @@ -78,9 +100,16 @@ class IntervalEvent(object): def fire_event(self): """ Set the last ran stamp and fire off the event. + Stop repeating if number of repeats have been achieved. """ - self.set_lastfired() + self.set_lastfired() self.event_function() + if self.repeats != None: + self.repeats -= 1 + if self.repeats <= 0 and self.pid != None: + scheduler.del_event(self.pid) + + class IEvt_Check_Sessions(IntervalEvent): """ diff --git a/src/scheduler.py b/src/scheduler.py index a6632b52ff..6fd4b31d66 100644 --- a/src/scheduler.py +++ b/src/scheduler.py @@ -9,9 +9,23 @@ ADDING AN EVENT: * Profit. """ -# List of IntervalEvent sub-classed objects. -schedule = [] - +# dict of IntervalEvent sub-classed objects, keyed by their +# process id:s. +SCHEDULE = [] + +def next_free_pid(): + """ + Find the next free pid + """ + pids = [event.pid for event in SCHEDULE] + if not pids: + return 0 + maxpid = max(pids) + freepids = [pid for pid in xrange(maxpid+1) if pid not in pids] + if freepids: + return min(freepids) + return maxpid + 1 + def add_event(event): """ Adds an event instance to the scheduled event list. Call this any time you @@ -19,21 +33,40 @@ def add_event(event): Args: * event: (IntervalEvent) The event to add to the scheduler. - """ + Returns: + * pid : (int) The process ID assigned to this event, for future reference. - #don't add multiple instances of the same event, instead replace - if event in schedule: - schedule[schedule.index(event)] = event - return + """ + # Make sure not to add multiple instances of the same event. + matches = [i for i, stored_event in enumerate(SCHEDULE) if event == stored_event] + if matches: + # Before replacing an event, stop its old incarnation. + del_event(matches[0]) + SCHEDULE[matches[0]] = event else: - schedule.append(event) + # Add a new event with a fresh pid. + event.pid = next_free_pid() + SCHEDULE.append(event) event.start_event_loop() + return event.pid -def del_event(event): +def get_event(pid): """ - Remove an event from scheduler. + Return an event with the given pid, if it exists, + otherwise return None. """ - if event in schedule: - i = schedule.index(event) - schedule[i].stop_event_loop() - del schedule[i] + pid = int(pid) + imatches = [i for i, stored_event in enumerate(SCHEDULE) if stored_event.pid == pid] + if imatches: + return SCHEDULE[imatches[0]] + +def del_event(pid): + """ + Remove an event from scheduler. There should never be more than one + event with a certain pid, this cleans up in case there are any multiples. + """ + pid = int(pid) + imatches = [i for i, stored_event in enumerate(SCHEDULE) if stored_event.pid == pid] + for imatch in imatches: + SCHEDULE[imatch].stop_event_loop() + del SCHEDULE[imatch]