Improved scheduler (#1864)

changes:

Rewrote scheduler to use a sorted linked-list to store scheduled tasks so that only one element needs to be checked if none of the tasks are ready to fire.
Scheduler now checks tasks every 2 ds.
Adding tasks to an empty scheduler or tasks that are triggered after any existing tasks is fast, but adding tasks that trigger between two existing tasks requires iterating through some of the task list.

It's no addtimer(), but it's something.
This commit is contained in:
Lohikar
2017-03-04 18:01:06 -06:00
committed by skull132
parent d97afa0bee
commit 97856e8735
6 changed files with 204 additions and 113 deletions

View File

@@ -30,3 +30,5 @@
last_slept = TimeOfHour; \
tick_start = world.tick_usage; \
}
#define PSCHED_CHECK_TICK (world.tick_usage > 100 || (world.tick_usage - tick_start) > tick_allowance)

View File

@@ -4,54 +4,141 @@
* Scheduler *
************/
/datum/controller/process/scheduler
var/list/scheduled_tasks
var/tick_completed = TRUE
var/list/queued_tasks
var/list/datum/scheduled_task/tasks = list()
var/datum/scheduled_task/head
/datum/controller/process/scheduler/setup()
name = "scheduler"
schedule_interval = 2 SECONDS
scheduled_tasks = list()
schedule_interval = 2
scheduler = src
/datum/controller/process/scheduler/doWork()
if (tick_completed)
queued_tasks = scheduled_tasks.Copy()
tick_completed = FALSE
while (head && !PSCHED_CHECK_TICK)
if (head.destroyed)
head = head.next
tasks -= head
head.kill()
continue
if (head.trigger_time >= world.time)
return // Nothing after this will be ready to fire.
while (queued_tasks.len)
var/datum/scheduled_task/task = queued_tasks[queued_tasks.len]
queued_tasks.len--
// This one's ready to fire, process it.
var/datum/scheduled_task/task = head
head = task.next
task.pre_process()
task.process()
task.post_process()
if (task.destroyed) // post_process probably destroyed it.
tasks -= task
task.kill()
/datum/controller/process/scheduler/proc/queue(datum/scheduled_task/task)
if (!task || !task.trigger_time)
warning("scheduler: Invalid task queued! Ignoring.")
return
// Reset this in-case we're doing a rebuild.
task.next = null
if (!head && !tasks.len)
head = task
tasks += task
return
if (!head) // Head's missing but we still have tasks, rebuild.
tasks += task
rebuild_queue()
return
var/datum/scheduled_task/curr = head
while (curr.next && curr.trigger_time < task.trigger_time)
curr = curr.next
if (!curr.next)
// We're at the end of the queue, just append.
curr.next = task
tasks += task
return
// Inserting midway into the list.
var/old_next = curr.next
curr.next = task
task.next = old_next
tasks += task
// Rebuilds the queue linked-list, removing invalid or destroyed entries.
/datum/controller/process/scheduler/proc/rebuild_queue()
log_debug("scheduler: Rebuilding queue.")
var/list/old_tasks = tasks
tasks = list()
if (!old_tasks.len)
log_debug("scheduler: rebuild was called on empty queue! Aborting.")
return
// Find the head.
for (var/thing in old_tasks)
var/datum/scheduled_task/task = thing
if (QDELETED(task))
scheduled_tasks -= task
old_tasks -= task
continue
if (world.time > task.trigger_time)
unschedule(task)
task.pre_process()
task.process()
task.post_process()
F_SCHECK
if (task.destroyed)
old_tasks -= task
task.kill()
continue
tick_completed = TRUE
if (!head || task.trigger_time < head.trigger_time)
head = task
if (!head)
log_debug("scheduler: unable to find head! Purging task queue.")
for (var/thing in old_tasks)
var/datum/scheduled_task/task = thing
if (QDELETED(task))
continue
task.kill()
head = null
return
// Don't queue the head.
tasks += head
old_tasks -= head
// Now rebuild the queue.
for (var/thing in old_tasks)
var/datum/scheduled_task/task = thing
queue(task)
log_debug("scheduler: Queue diff is [old_tasks.len - tasks.len].")
/datum/controller/process/scheduler/statProcess()
..()
stat(null, "[scheduled_tasks.len] tasks, [queued_tasks.len] queued")
stat(null, "[tasks.len] task\s")
/datum/controller/process/scheduler/proc/schedule(var/datum/scheduled_task/st)
scheduled_tasks += st
destroyed_event.register(st, src, /datum/controller/process/scheduler/proc/unschedule)
queue(st)
/datum/controller/process/scheduler/proc/unschedule(var/datum/scheduled_task/st)
if(st in scheduled_tasks)
scheduled_tasks -= st
destroyed_event.unregister(st, src)
st.destroyed = TRUE
/**********
* Helpers *
**********/
/proc/schedule(source, the_proc, time, ...)
if (time < 0)
return
time += world.time
var/list/the_args
if (length(args) > 3)
the_args = args.Copy(4)
else
the_args = list()
if (source)
return schedule_task_with_source(time, the_proc, the_args)
else
return schedule_task(time, the_proc, the_args)
/proc/schedule_task_in(var/in_time, var/procedure, var/list/arguments = list())
return schedule_task(world.time + in_time, procedure, arguments)
@@ -77,68 +164,3 @@
var/datum/scheduled_task/st = new/datum/scheduled_task/source(trigger_time, source, procedure, arguments, /proc/repeat_scheduled_task, list(repeat_interval))
scheduler.schedule(st)
return st
/*************
* Task Datum *
*************/
/datum/scheduled_task
var/trigger_time
var/procedure
var/list/arguments
var/task_after_process
var/list/task_after_process_args
/datum/scheduled_task/New(var/trigger_time, var/procedure, var/list/arguments, var/proc/task_after_process, var/list/task_after_process_args)
..()
src.trigger_time = trigger_time
src.procedure = procedure
src.arguments = arguments ? arguments : list()
src.task_after_process = task_after_process ? task_after_process : /proc/destroy_scheduled_task
src.task_after_process_args = istype(task_after_process_args) ? task_after_process_args : list()
task_after_process_args += src
/datum/scheduled_task/Destroy()
procedure = null
arguments.Cut()
task_after_process = null
task_after_process_args.Cut()
return ..()
/datum/scheduled_task/proc/pre_process()
task_triggered_event.raise_event(list(src))
/datum/scheduled_task/proc/process()
if(procedure)
call(procedure)(arglist(arguments))
/datum/scheduled_task/proc/post_process()
call(task_after_process)(arglist(task_after_process_args))
// Resets the trigger time, has no effect if the task has already triggered
/datum/scheduled_task/proc/trigger_task_in(var/trigger_in)
src.trigger_time = world.time + trigger_in
/datum/scheduled_task/source
var/datum/source
/datum/scheduled_task/source/New(var/trigger_time, var/datum/source, var/procedure, var/list/arguments, var/proc/task_after_process, var/list/task_after_process_args)
src.source = source
destroyed_event.register(src.source, src, /datum/scheduled_task/source/proc/source_destroyed)
..(trigger_time, procedure, arguments, task_after_process, task_after_process_args)
/datum/scheduled_task/source/Destroy()
source = null
return ..()
/datum/scheduled_task/source/process()
call(source, procedure)(arglist(arguments))
/datum/scheduled_task/source/proc/source_destroyed()
qdel(src)
/proc/destroy_scheduled_task(var/datum/scheduled_task/st)
qdel(st)
/proc/repeat_scheduled_task(var/trigger_delay, var/datum/scheduled_task/st)
st.trigger_time = world.time + trigger_delay
scheduler.schedule(st)

View File

@@ -0,0 +1,79 @@
/*************
* Task Datum *
*************/
/datum/scheduled_task
var/trigger_time
var/procedure
var/list/arguments
var/task_after_process
var/list/task_after_process_args
var/datum/scheduled_task/next
var/destroyed = FALSE
/datum/scheduled_task/New(var/trigger_time, var/procedure, var/list/arguments, var/proc/task_after_process, var/list/task_after_process_args)
..()
src.trigger_time = trigger_time
src.procedure = procedure
src.arguments = arguments ? arguments : list()
src.task_after_process = task_after_process ? task_after_process : /proc/destroy_scheduled_task
src.task_after_process_args = istype(task_after_process_args) ? task_after_process_args : list()
task_after_process_args += src
/datum/scheduled_task/Destroy()
if (!destroyed)
kill(no_del = TRUE)
procedure = null
arguments.Cut()
task_after_process = null
task_after_process_args.Cut()
return ..()
/datum/scheduled_task/proc/pre_process()
return
/datum/scheduled_task/proc/process()
if(procedure)
call(procedure)(arglist(arguments))
/datum/scheduled_task/proc/post_process()
call(task_after_process)(arglist(task_after_process_args))
// Resets the trigger time, has no effect if the task has already triggered
/datum/scheduled_task/proc/trigger_task_in(var/trigger_in)
src.trigger_time = world.time + trigger_in
/datum/scheduled_task/proc/kill(no_del = FALSE)
if (!destroyed)
warning("scheduler: Non-destroyed task was killed!")
destroyed = TRUE
if (src in scheduler.tasks)
warning("scheduler: Task was not cleaned up correctly, rebuilding scheduler queue!")
scheduler.rebuild_queue()
if (!no_del)
qdel(src)
/datum/scheduled_task/source
var/datum/source
/datum/scheduled_task/source/New(var/trigger_time, var/datum/source, var/procedure, var/list/arguments, var/proc/task_after_process, var/list/task_after_process_args)
src.source = source
..(trigger_time, procedure, arguments, task_after_process, task_after_process_args)
/datum/scheduled_task/source/Destroy()
source = null
return ..()
/datum/scheduled_task/source/process()
call(source, procedure)(arglist(arguments))
/datum/scheduled_task/source/proc/source_destroyed()
destroyed = TRUE
/proc/destroy_scheduled_task(var/datum/scheduled_task/st)
st.destroyed = TRUE
/proc/repeat_scheduled_task(var/trigger_delay, var/datum/scheduled_task/st)
st.trigger_time = world.time + trigger_delay
scheduler.schedule(st)

View File

@@ -30,8 +30,7 @@
var/obj/item/stack/material/steel/repairing
var/block_air_zones = 1 //If set, air zones cannot merge across the door even when it is opened.
var/open_duration = 150//How long it stays open
var/datum/scheduled_task/close_task
var/datum/scheduled_task/hatch_task
var/hashatch = 0//If 1, this door has hatches, and certain small creatures can move through them without opening the door
var/hatchstate = 0//0: closed, 1: open

View File

@@ -14,8 +14,6 @@
var/max_fire_temperature_sustained = 0 //The max temperature of the fire which it was subjected to
var/dirt = 0
var/datum/scheduled_task/unwet_task
// This is not great.
/turf/simulated/proc/wet_floor(var/wet_val = 1)
if(wet_val < wet)
@@ -26,22 +24,17 @@
wet_overlay = image('icons/effects/water.dmi',src,"wet_floor")
overlays += wet_overlay
if(unwet_task)
unwet_task.trigger_task_in(180 SECONDS)
else
unwet_task = schedule_task_in(180 SECONDS)
task_triggered_event.register(unwet_task, src, /turf/simulated/proc/task_unwet_floor)
/turf/simulated/proc/task_unwet_floor(var/triggered_task)
if(triggered_task == unwet_task)
unwet_task = null
unwet_floor()
schedule_task_with_source_in(180 SECONDS, src, .proc/unwet_floor)
/turf/simulated/proc/unwet_floor()
wet = 0
if(wet_overlay)
overlays -= wet_overlay
wet_overlay = null
--wet
if (wet < 1)
wet = 0
if(wet_overlay)
overlays -= wet_overlay
wet_overlay = null
else
schedule_task_with_source_in(180 SECONDS, src, .proc/unwet_floor)
/turf/simulated/clean_blood()
for(var/obj/effect/decal/cleanable/blood/B in contents)
@@ -54,11 +47,6 @@
holy = 1
levelupdate()
/turf/simulated/Destroy()
qdel(unwet_task)
unwet_task = null
return ..()
/turf/simulated/proc/initialize()
return