This commit is contained in:
Kearel
2015-11-28 11:21:07 -06:00
176 changed files with 9954 additions and 9190 deletions
@@ -1,32 +0,0 @@
// DM Environment file for ProcessScheduler.dme.
// All manual changes should be made outside the BEGIN_ and END_ blocks.
// New source code should be placed in .dm files: choose File/New --> Code File.
// BEGIN_INTERNALS
// END_INTERNALS
// BEGIN_FILE_DIR
#define FILE_DIR .
// END_FILE_DIR
// BEGIN_PREFERENCES
// END_PREFERENCES
// BEGIN_INCLUDE
#include "core\_define.dm"
#include "core\_stubs.dm"
#include "core\process.dm"
#include "core\processScheduler.dm"
#include "core\updateQueue.dm"
#include "core\updateQueueWorker.dm"
#include "test\processSchedulerView.dm"
#include "test\testDyingUpdateQueueProcess.dm"
#include "test\testHarness.dm"
#include "test\testHungProcess.dm"
#include "test\testNiceProcess.dm"
#include "test\testSlowProcess.dm"
#include "test\testUpdateQueue.dm"
#include "test\testUpdateQueueProcess.dm"
#include "test\testZombieProcess.dm"
// END_INCLUDE
@@ -1,17 +0,0 @@
// Process status defines
#define PROCESS_STATUS_IDLE 1
#define PROCESS_STATUS_QUEUED 2
#define PROCESS_STATUS_RUNNING 3
#define PROCESS_STATUS_MAYBE_HUNG 4
#define PROCESS_STATUS_PROBABLY_HUNG 5
#define PROCESS_STATUS_HUNG 6
// Process time thresholds
#define PROCESS_DEFAULT_HANG_WARNING_TIME 300 // 30 seconds
#define PROCESS_DEFAULT_HANG_ALERT_TIME 600 // 60 seconds
#define PROCESS_DEFAULT_HANG_RESTART_TIME 900 // 90 seconds
#define PROCESS_DEFAULT_SCHEDULE_INTERVAL 50 // 50 ticks
#define PROCESS_DEFAULT_SLEEP_INTERVAL 2 // 2 ticks
#define PROCESS_DEFAULT_CPU_THRESHOLD 90 // 90%
//#define UPDATE_QUEUE_DEBUG
@@ -4,15 +4,7 @@
* This file contains constructs that the process scheduler expects to exist
* in a standard ss13 fork.
*/
/*
/**
* message_admins
*
* sends a message to admins
*/
/proc/message_admins(msg)
world << msg
*/
/**
* logTheThing
*
@@ -25,14 +17,3 @@
world << "Diary: \[[diaryType]:[type]] [text]"
else
world << "Log: \[[type]] [text]"
/**
* var/disposed
*
* In goonstation, disposed is set to 1 after an object enters the delete queue
* or the object is placed in an object pool (effectively out-of-play so to speak)
*/
/datum/var/disposed
// Garbage collection (controller).
/datum/var/gcDestroyed
/datum/var/timeDestroyed
+117 -67
View File
@@ -48,7 +48,7 @@
// This controls how often the process will yield (call sleep(0)) while it is running.
// Every concurrent process should sleep periodically while running in order to allow other
// processes to execute concurrently.
var/tmp/sleep_interval = PROCESS_DEFAULT_SLEEP_INTERVAL
var/tmp/sleep_interval
// hang_warning_time - this is the time (in 1/10 seconds) after which the server will begin to show "maybe hung" in the context window
var/tmp/hang_warning_time = PROCESS_DEFAULT_HANG_WARNING_TIME
@@ -59,20 +59,20 @@
// hang_restart_time - After this much time(in 1/10 seconds), the server will automatically kill and restart the process.
var/tmp/hang_restart_time = PROCESS_DEFAULT_HANG_RESTART_TIME
// cpu_threshold - if world.cpu >= cpu_threshold, scheck() will call sleep(1) to defer further work until the next tick. This keeps a process from driving a tick into overtime (causing perceptible lag)
var/tmp/cpu_threshold = PROCESS_DEFAULT_CPU_THRESHOLD
// How many times in the current run has the process deferred work till the next tick?
var/tmp/cpu_defer_count = 0
// How many SCHECKs have been skipped (to limit btime calls)
var/tmp/calls_since_last_scheck = 0
/**
* recordkeeping vars
*/
// Records the time (server ticks) at which the process last finished sleeping
// Records the time (1/10s timeofday) at which the process last finished sleeping
var/tmp/last_slept = 0
// Records the time (s-ticks) at which the process last began running
// Records the time (1/10s timeofday) at which the process last began running
var/tmp/run_start = 0
// Records the number of times this process has been killed and restarted
@@ -85,26 +85,33 @@
var/tmp/last_object
datum/controller/process/New(var/datum/controller/processScheduler/scheduler)
// Counts the number of times an exception has occurred; gets reset after 10
var/tmp/list/exceptions = list()
// Number of deciseconds to delay before starting the process
var/start_delay = 0
/datum/controller/process/New(var/datum/controller/processScheduler/scheduler)
..()
main = scheduler
previousStatus = "idle"
idle()
name = "process"
schedule_interval = 50
sleep_interval = 2
sleep_interval = world.tick_lag / PROCESS_DEFAULT_SLEEP_INTERVAL
last_slept = 0
run_start = 0
ticks = 0
last_task = 0
last_object = null
datum/controller/process/proc/started()
/datum/controller/process/proc/started()
var/timeofhour = TimeOfHour
// Initialize last_slept so we can know when to sleep
last_slept = world.timeofday
last_slept = timeofhour
// Initialize run_start so we can detect hung processes.
run_start = world.timeofday
run_start = timeofhour
// Initialize defer count
cpu_defer_count = 0
@@ -114,65 +121,65 @@ datum/controller/process/proc/started()
onStart()
datum/controller/process/proc/finished()
/datum/controller/process/proc/finished()
ticks++
idle()
main.processFinished(src)
onFinish()
datum/controller/process/proc/doWork()
/datum/controller/process/proc/doWork()
datum/controller/process/proc/setup()
/datum/controller/process/proc/setup()
datum/controller/process/proc/process()
/datum/controller/process/proc/process()
started()
doWork()
finished()
datum/controller/process/proc/running()
/datum/controller/process/proc/running()
idle = 0
queued = 0
running = 1
hung = 0
setStatus(PROCESS_STATUS_RUNNING)
datum/controller/process/proc/idle()
/datum/controller/process/proc/idle()
queued = 0
running = 0
idle = 1
hung = 0
setStatus(PROCESS_STATUS_IDLE)
datum/controller/process/proc/queued()
/datum/controller/process/proc/queued()
idle = 0
running = 0
queued = 1
hung = 0
setStatus(PROCESS_STATUS_QUEUED)
datum/controller/process/proc/hung()
/datum/controller/process/proc/hung()
hung = 1
setStatus(PROCESS_STATUS_HUNG)
datum/controller/process/proc/handleHung()
/datum/controller/process/proc/handleHung()
var/timeofhour = TimeOfHour
var/datum/lastObj = last_object
var/lastObjType = "null"
if(istype(lastObj))
lastObjType = lastObj.type
// If world.timeofday has rolled over, then we need to adjust.
if (world.timeofday < run_start)
run_start -= 864000
var/msg = "[name] process hung at tick #[ticks]. Process was unresponsive for [(world.timeofday - run_start) / 10] seconds and was restarted. Last task: [last_task]. Last Object Type: [lastObjType]"
// If timeofhour has rolled over, then we need to adjust.
if (timeofhour < run_start)
run_start -= 36000
var/msg = "[name] process hung at tick #[ticks]. Process was unresponsive for [(timeofhour - run_start) / 10] seconds and was restarted. Last task: [last_task]. Last Object Type: [lastObjType]"
logTheThing("debug", null, null, msg)
logTheThing("diary", null, null, msg, "debug")
message_admins(msg)
main.restartProcess(src.name)
datum/controller/process/proc/kill()
/datum/controller/process/proc/kill()
if (!killed)
var/msg = "[name] process was killed at tick #[ticks]."
logTheThing("debug", null, null, msg)
@@ -182,59 +189,68 @@ datum/controller/process/proc/kill()
// Allow inheritors to clean up if needed
onKill()
killed = TRUE
// This should del
del(src)
del(src) // This should del
datum/controller/process/proc/scheck(var/tickId = 0)
// Do not call this directly - use SHECK or SCHECK_EVERY
/datum/controller/process/proc/sleepCheck(var/tickId = 0)
calls_since_last_scheck = 0
if (killed)
// The kill proc is the only place where killed is set.
// The kill proc should have deleted this datum, and all sleeping procs that are
// owned by it.
CRASH("A killed process is still running somehow...")
if (hung)
// This will only really help if the doWork proc ends up in an infinite loop.
handleHung()
CRASH("Process [name] hung and was restarted.")
// For each tick the process defers, it increments the cpu_defer_count so we don't
// defer indefinitely
if (world.cpu >= cpu_threshold + cpu_defer_count * 10)
sleep(1)
if (main.getCurrentTickElapsedTime() > main.timeAllowance)
sleep(world.tick_lag)
cpu_defer_count++
last_slept = world.timeofday
last_slept = TimeOfHour
else
// If world.timeofday has rolled over, then we need to adjust.
if (world.timeofday < last_slept)
last_slept -= 864000
var/timeofhour = TimeOfHour
// If timeofhour has rolled over, then we need to adjust.
if (timeofhour < last_slept)
last_slept -= 36000
if (world.timeofday > last_slept + sleep_interval)
// If we haven't slept in sleep_interval ticks, sleep to allow other work to proceed.
if (timeofhour > last_slept + sleep_interval)
// If we haven't slept in sleep_interval deciseconds, sleep to allow other work to proceed.
sleep(0)
last_slept = world.timeofday
last_slept = TimeOfHour
datum/controller/process/proc/update()
/datum/controller/process/proc/update()
// Clear delta
if(previousStatus != status)
setStatus(status)
var/elapsedTime = getElapsedTime()
if (elapsedTime > hang_restart_time)
if (hung)
handleHung()
return
else if (elapsedTime > hang_restart_time)
hung()
else if (elapsedTime > hang_alert_time)
setStatus(PROCESS_STATUS_PROBABLY_HUNG)
else if (elapsedTime > hang_warning_time)
setStatus(PROCESS_STATUS_MAYBE_HUNG)
datum/controller/process/proc/getElapsedTime()
if (world.timeofday < run_start)
return world.timeofday - (run_start - 864000)
return world.timeofday - run_start
datum/controller/process/proc/tickDetail()
/datum/controller/process/proc/getElapsedTime()
var/timeofhour = TimeOfHour
if (timeofhour < run_start)
return timeofhour - (run_start - 36000)
return timeofhour - run_start
/datum/controller/process/proc/tickDetail()
return
datum/controller/process/proc/getContext()
/datum/controller/process/proc/getContext()
return "<tr><td>[name]</td><td>[main.averageRunTime(src)]</td><td>[main.last_run_time[src]]</td><td>[main.highest_run_time[src]]</td><td>[ticks]</td></tr>\n"
datum/controller/process/proc/getContextData()
/datum/controller/process/proc/getContextData()
return list(
"name" = name,
"averageRunTime" = main.averageRunTime(src),
@@ -246,10 +262,10 @@ datum/controller/process/proc/getContextData()
"disabled" = disabled
)
datum/controller/process/proc/getStatus()
/datum/controller/process/proc/getStatus()
return status
datum/controller/process/proc/getStatusText(var/s = 0)
/datum/controller/process/proc/getStatusText(var/s = 0)
if(!s)
s = status
switch(s)
@@ -268,21 +284,21 @@ datum/controller/process/proc/getStatusText(var/s = 0)
else
return "UNKNOWN"
datum/controller/process/proc/getPreviousStatus()
/datum/controller/process/proc/getPreviousStatus()
return previousStatus
datum/controller/process/proc/getPreviousStatusText()
/datum/controller/process/proc/getPreviousStatusText()
return getStatusText(previousStatus)
datum/controller/process/proc/setStatus(var/newStatus)
/datum/controller/process/proc/setStatus(var/newStatus)
previousStatus = status
status = newStatus
datum/controller/process/proc/setLastTask(var/task, var/object)
/datum/controller/process/proc/setLastTask(var/task, var/object)
last_task = task
last_object = object
datum/controller/process/proc/_copyStateFrom(var/datum/controller/process/target)
/datum/controller/process/proc/_copyStateFrom(var/datum/controller/process/target)
main = target.main
name = target.name
schedule_interval = target.schedule_interval
@@ -295,28 +311,62 @@ datum/controller/process/proc/_copyStateFrom(var/datum/controller/process/target
last_object = target.last_object
copyStateFrom(target)
datum/controller/process/proc/copyStateFrom(var/datum/controller/process/target)
/datum/controller/process/proc/copyStateFrom(var/datum/controller/process/target)
datum/controller/process/proc/onKill()
/datum/controller/process/proc/onKill()
datum/controller/process/proc/onStart()
/datum/controller/process/proc/onStart()
datum/controller/process/proc/onFinish()
/datum/controller/process/proc/onFinish()
datum/controller/process/proc/disable()
/datum/controller/process/proc/disable()
disabled = 1
datum/controller/process/proc/enable()
/datum/controller/process/proc/enable()
disabled = 0
/datum/controller/process/proc/getAverageRunTime()
return main.averageRunTime(src)
/datum/controller/process/proc/getLastRunTime()
return main.getProcessLastRunTime(src)
/datum/controller/process/proc/getHighestRunTime()
return main.getProcessHighestRunTime(src)
/datum/controller/process/proc/getTicks()
return ticks
/datum/controller/process/proc/getStatName()
return name
/datum/controller/process/proc/statProcess()
var/averageRunTime = round(getAverageRunTime(), 0.1)/10
var/lastRunTime = round(getLastRunTime(), 0.1)/10
var/highestRunTime = round(getHighestRunTime(), 0.1)/10
stat("[name]", "T#[getTicks()] | AR [averageRunTime] | LR [lastRunTime] | HR [highestRunTime] | D [cpu_defer_count]")
/datum/controller/process/proc/getTickTime()
return "#[getTicks()]\t- [getLastRunTime()]"
/datum/controller/process/proc/catchException(var/exception/e, var/thrower)
var/etext = "[e]"
var/eid = "[e]" // Exception ID, for tracking repeated exceptions
var/ptext = "" // "processing..." text, for what was being processed (if known)
if(istype(e))
etext += " in [e.file], line [e.line]"
eid = "[e.file]:[e.line]"
if(eid in exceptions)
if(exceptions[eid]++ >= 10)
return
else
exceptions[eid] = 1
if(istype(thrower, /datum))
var/datum/D = thrower
ptext = " processing [D.type]"
if(istype(thrower, /atom))
var/atom/A = thrower
ptext += " ([A]) ([A.x],[A.y],[A.z])"
log_to_dd("\[[time_stamp()]\] Process [name] caught exception[ptext]: [etext]")
if(exceptions[eid] >= 10)
log_to_dd("This exception will now be ignored for ten minutes.")
spawn(6000)
exceptions[eid] = 0
/datum/controller/process/proc/catchBadType(var/datum/caught)
if(isnull(caught) || !istype(caught) || !isnull(caught.gcDestroyed))
return // Only bother with types we can identify and that don't belong
catchException("Type [caught.type] does not belong in process' queue")
@@ -1,320 +1,382 @@
// Singleton instance of game_controller_new, setup in world.New()
var/global/datum/controller/processScheduler/processScheduler
/datum/controller/processScheduler
// Processes known by the scheduler
var/tmp/datum/controller/process/list/processes = new
// Processes that are currently running
var/tmp/datum/controller/process/list/running = new
// Processes that are idle
var/tmp/datum/controller/process/list/idle = new
// Processes that are queued to run
var/tmp/datum/controller/process/list/queued = new
// Process name -> process object map
var/tmp/datum/controller/process/list/nameToProcessMap = new
// Process last start times
var/tmp/datum/controller/process/list/last_start = new
// Process last run durations
var/tmp/datum/controller/process/list/last_run_time = new
// Per process list of the last 20 durations
var/tmp/datum/controller/process/list/last_twenty_run_times = new
// Process highest run time
var/tmp/datum/controller/process/list/highest_run_time = new
// Sleep 1 tick -- This may be too aggressive.
var/tmp/scheduler_sleep_interval = 1
// Controls whether the scheduler is running or not
var/tmp/isRunning = 0
// Setup for these processes will be deferred until all the other processes are set up.
var/tmp/list/deferredSetupList = new
/**
* deferSetupFor
* @param path processPath
* If a process needs to be initialized after everything else, add it to
* the deferred setup list. On goonstation, only the ticker needs to have
* this treatment.
*/
/datum/controller/processScheduler/proc/deferSetupFor(var/processPath)
if (!(processPath in deferredSetupList))
deferredSetupList += processPath
/datum/controller/processScheduler/proc/setup()
// There can be only one
if(processScheduler && (processScheduler != src))
del(src)
return 0
var/process
// Add all the processes we can find, except for the ticker
for (process in typesof(/datum/controller/process) - /datum/controller/process)
if (!(process in deferredSetupList))
addProcess(new process(src))
for (process in deferredSetupList)
addProcess(new process(src))
/datum/controller/processScheduler/proc/start()
isRunning = 1
spawn(0)
process()
/datum/controller/processScheduler/proc/process()
while(isRunning)
checkRunningProcesses()
queueProcesses()
runQueuedProcesses()
sleep(scheduler_sleep_interval)
/datum/controller/processScheduler/proc/stop()
isRunning = 0
/datum/controller/processScheduler/proc/checkRunningProcesses()
for(var/datum/controller/process/p in running)
p.update()
if (isnull(p)) // Process was killed
continue
var/status = p.getStatus()
var/previousStatus = p.getPreviousStatus()
// Check status changes
if(status != previousStatus)
//Status changed.
switch(status)
if(PROCESS_STATUS_MAYBE_HUNG)
message_admins("Process '[p.name]' is [p.getStatusText(status)].")
if(PROCESS_STATUS_PROBABLY_HUNG)
message_admins("Process '[p.name]' is [p.getStatusText(status)].")
if(PROCESS_STATUS_HUNG)
message_admins("Process '[p.name]' is [p.getStatusText(status)].")
p.handleHung()
/datum/controller/processScheduler/proc/queueProcesses()
for(var/datum/controller/process/p in processes)
// Don't double-queue, don't queue running processes
if (p.disabled || p.running || p.queued || !p.idle)
continue
// If world.timeofday has rolled over, then we need to adjust.
if (world.timeofday < last_start[p])
last_start[p] -= 864000
// If the process should be running by now, go ahead and queue it
if (world.timeofday > last_start[p] + p.schedule_interval)
setQueuedProcessState(p)
/datum/controller/processScheduler/proc/runQueuedProcesses()
for(var/datum/controller/process/p in queued)
runProcess(p)
/datum/controller/processScheduler/proc/addProcess(var/datum/controller/process/process)
processes.Add(process)
process.idle()
idle.Add(process)
// init recordkeeping vars
last_start.Add(process)
last_start[process] = 0
last_run_time.Add(process)
last_run_time[process] = 0
last_twenty_run_times.Add(process)
last_twenty_run_times[process] = list()
highest_run_time.Add(process)
highest_run_time[process] = 0
// init starts and stops record starts
recordStart(process, 0)
recordEnd(process, 0)
// Set up process
process.setup()
// Save process in the name -> process map
nameToProcessMap[process.name] = process
/datum/controller/processScheduler/proc/replaceProcess(var/datum/controller/process/oldProcess, var/datum/controller/process/newProcess)
processes.Remove(oldProcess)
processes.Add(newProcess)
newProcess.idle()
idle.Remove(oldProcess)
running.Remove(oldProcess)
queued.Remove(oldProcess)
idle.Add(newProcess)
last_start.Remove(oldProcess)
last_start.Add(newProcess)
last_start[newProcess] = 0
last_run_time.Add(newProcess)
last_run_time[newProcess] = last_run_time[oldProcess]
last_run_time.Remove(oldProcess)
last_twenty_run_times.Add(newProcess)
last_twenty_run_times[newProcess] = last_twenty_run_times[oldProcess]
last_twenty_run_times.Remove(oldProcess)
highest_run_time.Add(newProcess)
highest_run_time[newProcess] = highest_run_time[oldProcess]
highest_run_time.Remove(oldProcess)
recordStart(newProcess, 0)
recordEnd(newProcess, 0)
nameToProcessMap[newProcess.name] = newProcess
/datum/controller/processScheduler/proc/runProcess(var/datum/controller/process/process)
spawn(0)
process.process()
/datum/controller/processScheduler/proc/processStarted(var/datum/controller/process/process)
setRunningProcessState(process)
recordStart(process)
/datum/controller/processScheduler/proc/processFinished(var/datum/controller/process/process)
setIdleProcessState(process)
recordEnd(process)
/datum/controller/processScheduler/proc/setIdleProcessState(var/datum/controller/process/process)
if (process in running)
running -= process
if (process in queued)
queued -= process
if (!(process in idle))
idle += process
process.idle()
/datum/controller/processScheduler/proc/setQueuedProcessState(var/datum/controller/process/process)
if (process in running)
running -= process
if (process in idle)
idle -= process
if (!(process in queued))
queued += process
// The other state transitions are handled internally by the process.
process.queued()
/datum/controller/processScheduler/proc/setRunningProcessState(var/datum/controller/process/process)
if (process in queued)
queued -= process
if (process in idle)
idle -= process
if (!(process in running))
running += process
process.running()
/datum/controller/processScheduler/proc/recordStart(var/datum/controller/process/process, var/time = null)
if (isnull(time))
time = world.timeofday
last_start[process] = time
/datum/controller/processScheduler/proc/recordEnd(var/datum/controller/process/process, var/time = null)
if (isnull(time))
time = world.timeofday
// If world.timeofday has rolled over, then we need to adjust.
if (time < last_start[process])
last_start[process] -= 864000
var/lastRunTime = time - last_start[process]
if(lastRunTime < 0)
lastRunTime = 0
recordRunTime(process, lastRunTime)
/**
* recordRunTime
* Records a run time for a process
*/
/datum/controller/processScheduler/proc/recordRunTime(var/datum/controller/process/process, time)
last_run_time[process] = time
if(time > highest_run_time[process])
highest_run_time[process] = time
var/list/lastTwenty = last_twenty_run_times[process]
if (lastTwenty.len == 20)
lastTwenty.Cut(1, 2)
lastTwenty.len++
lastTwenty[lastTwenty.len] = time
/**
* averageRunTime
* returns the average run time (over the last 20) of the process
*/
/datum/controller/processScheduler/proc/averageRunTime(var/datum/controller/process/process)
var/lastTwenty = last_twenty_run_times[process]
var/t = 0
var/c = 0
for(var/time in lastTwenty)
t += time
c++
if(c > 0)
return t / c
return c
/datum/controller/processScheduler/proc/getStatusData()
var/list/data = new
for (var/datum/controller/process/p in processes)
data.len++
data[data.len] = p.getContextData()
return data
/datum/controller/processScheduler/proc/getProcessCount()
return processes.len
/datum/controller/processScheduler/proc/hasProcess(var/processName as text)
if (nameToProcessMap[processName])
return 1
/datum/controller/processScheduler/proc/killProcess(var/processName as text)
restartProcess(processName)
/datum/controller/processScheduler/proc/restartProcess(var/processName as text)
if (hasProcess(processName))
var/datum/controller/process/oldInstance = nameToProcessMap[processName]
var/datum/controller/process/newInstance = new oldInstance.type(src)
newInstance._copyStateFrom(oldInstance)
replaceProcess(oldInstance, newInstance)
oldInstance.kill()
/datum/controller/processScheduler/proc/enableProcess(var/processName as text)
if (hasProcess(processName))
var/datum/controller/process/process = nameToProcessMap[processName]
process.enable()
/datum/controller/processScheduler/proc/disableProcess(var/processName as text)
if (hasProcess(processName))
var/datum/controller/process/process = nameToProcessMap[processName]
process.disable()
/datum/controller/processScheduler/proc/getProcess(var/name)
return nameToProcessMap[name]
/datum/controller/processScheduler/proc/getProcessLastRunTime(var/datum/controller/process/process)
return last_run_time[process]
/datum/controller/processScheduler/proc/getIsRunning()
return isRunning
// Singleton instance of game_controller_new, setup in world.New()
var/global/datum/controller/processScheduler/processScheduler
/datum/controller/processScheduler
// Processes known by the scheduler
var/tmp/datum/controller/process/list/processes = new
// Processes that are currently running
var/tmp/datum/controller/process/list/running = new
// Processes that are idle
var/tmp/datum/controller/process/list/idle = new
// Processes that are queued to run
var/tmp/datum/controller/process/list/queued = new
// Process name -> process object map
var/tmp/datum/controller/process/list/nameToProcessMap = new
// Process last queued times (world time)
var/tmp/datum/controller/process/list/last_queued = new
// Process last start times (real time)
var/tmp/datum/controller/process/list/last_start = new
// Process last run durations
var/tmp/datum/controller/process/list/last_run_time = new
// Per process list of the last 20 durations
var/tmp/datum/controller/process/list/last_twenty_run_times = new
// Process highest run time
var/tmp/datum/controller/process/list/highest_run_time = new
// How long to sleep between runs (set to tick_lag in New)
var/tmp/scheduler_sleep_interval
// Controls whether the scheduler is running or not
var/tmp/isRunning = 0
// Setup for these processes will be deferred until all the other processes are set up.
var/tmp/list/deferredSetupList = new
var/tmp/currentTick = 0
var/tmp/currentTickStart = 0
var/tmp/timeAllowance = 0
var/tmp/cpuAverage = 0
var/tmp/timeAllowanceMax = 0
/datum/controller/processScheduler/New()
..()
// When the process scheduler is first new'd, tick_lag may be wrong, so these
// get re-initialized when the process scheduler is started.
// (These are kept here for any processes that decide to process before round start)
scheduler_sleep_interval = world.tick_lag
timeAllowance = world.tick_lag * 0.5
timeAllowanceMax = world.tick_lag
/**
* deferSetupFor
* @param path processPath
* If a process needs to be initialized after everything else, add it to
* the deferred setup list. On goonstation, only the ticker needs to have
* this treatment.
*/
/datum/controller/processScheduler/proc/deferSetupFor(var/processPath)
if (!(processPath in deferredSetupList))
deferredSetupList += processPath
/datum/controller/processScheduler/proc/setup()
// There can be only one
if(processScheduler && (processScheduler != src))
del(src)
return 0
var/process
// Add all the processes we can find, except for the ticker
for (process in subtypesof(/datum/controller/process))
if (!(process in deferredSetupList))
addProcess(new process(src))
for (process in deferredSetupList)
addProcess(new process(src))
/datum/controller/processScheduler/proc/start()
isRunning = 1
// tick_lag will have been set by now, so re-initialize these
scheduler_sleep_interval = world.tick_lag
timeAllowance = world.tick_lag * 0.5
timeAllowanceMax = world.tick_lag
updateStartDelays()
spawn(0)
process()
/datum/controller/processScheduler/proc/process()
updateCurrentTickData()
for(var/i=world.tick_lag,i<world.tick_lag*50,i+=world.tick_lag)
spawn(i) updateCurrentTickData()
while(isRunning)
// Hopefully spawning this for 50 ticks in the future will make it the first thing in the queue.
spawn(world.tick_lag*50) updateCurrentTickData()
checkRunningProcesses()
queueProcesses()
runQueuedProcesses()
sleep(scheduler_sleep_interval)
/datum/controller/processScheduler/proc/stop()
isRunning = 0
/datum/controller/processScheduler/proc/checkRunningProcesses()
for(var/datum/controller/process/p in running)
p.update()
if (isnull(p)) // Process was killed
continue
var/status = p.getStatus()
var/previousStatus = p.getPreviousStatus()
// Check status changes
if(status != previousStatus)
//Status changed.
switch(status)
if(PROCESS_STATUS_PROBABLY_HUNG)
message_admins("Process '[p.name]' may be hung.")
if(PROCESS_STATUS_HUNG)
message_admins("Process '[p.name]' is hung and will be restarted.")
/datum/controller/processScheduler/proc/queueProcesses()
for(var/datum/controller/process/p in processes)
// Don't double-queue, don't queue running processes
if (p.disabled || p.running || p.queued || !p.idle)
continue
// If the process should be running by now, go ahead and queue it
if (world.time >= last_queued[p] + p.schedule_interval)
setQueuedProcessState(p)
/datum/controller/processScheduler/proc/runQueuedProcesses()
for(var/datum/controller/process/p in queued)
runProcess(p)
/datum/controller/processScheduler/proc/addProcess(var/datum/controller/process/process)
processes.Add(process)
process.idle()
idle.Add(process)
// init recordkeeping vars
last_start.Add(process)
last_start[process] = 0
last_run_time.Add(process)
last_run_time[process] = 0
last_twenty_run_times.Add(process)
last_twenty_run_times[process] = list()
highest_run_time.Add(process)
highest_run_time[process] = 0
// init starts and stops record starts
recordStart(process, 0)
recordEnd(process, 0)
// Set up process
process.setup()
// Save process in the name -> process map
nameToProcessMap[process.name] = process
/datum/controller/processScheduler/proc/replaceProcess(var/datum/controller/process/oldProcess, var/datum/controller/process/newProcess)
processes.Remove(oldProcess)
processes.Add(newProcess)
newProcess.idle()
idle.Remove(oldProcess)
running.Remove(oldProcess)
queued.Remove(oldProcess)
idle.Add(newProcess)
last_start.Remove(oldProcess)
last_start.Add(newProcess)
last_start[newProcess] = 0
last_run_time.Add(newProcess)
last_run_time[newProcess] = last_run_time[oldProcess]
last_run_time.Remove(oldProcess)
last_twenty_run_times.Add(newProcess)
last_twenty_run_times[newProcess] = last_twenty_run_times[oldProcess]
last_twenty_run_times.Remove(oldProcess)
highest_run_time.Add(newProcess)
highest_run_time[newProcess] = highest_run_time[oldProcess]
highest_run_time.Remove(oldProcess)
recordStart(newProcess, 0)
recordEnd(newProcess, 0)
nameToProcessMap[newProcess.name] = newProcess
/datum/controller/processScheduler/proc/updateStartDelays()
for(var/datum/controller/process/p in processes)
if(p.start_delay)
last_queued[p] = world.time - p.start_delay
/datum/controller/processScheduler/proc/runProcess(var/datum/controller/process/process)
spawn(0)
process.process()
/datum/controller/processScheduler/proc/processStarted(var/datum/controller/process/process)
setRunningProcessState(process)
recordStart(process)
/datum/controller/processScheduler/proc/processFinished(var/datum/controller/process/process)
setIdleProcessState(process)
recordEnd(process)
/datum/controller/processScheduler/proc/setIdleProcessState(var/datum/controller/process/process)
if (process in running)
running -= process
if (process in queued)
queued -= process
if (!(process in idle))
idle += process
/datum/controller/processScheduler/proc/setQueuedProcessState(var/datum/controller/process/process)
if (process in running)
running -= process
if (process in idle)
idle -= process
if (!(process in queued))
queued += process
// The other state transitions are handled internally by the process.
process.queued()
/datum/controller/processScheduler/proc/setRunningProcessState(var/datum/controller/process/process)
if (process in queued)
queued -= process
if (process in idle)
idle -= process
if (!(process in running))
running += process
/datum/controller/processScheduler/proc/recordStart(var/datum/controller/process/process, var/time = null)
if (isnull(time))
time = TimeOfHour
last_queued[process] = world.time
last_start[process] = time
else
last_queued[process] = (time == 0 ? 0 : world.time)
last_start[process] = time
/datum/controller/processScheduler/proc/recordEnd(var/datum/controller/process/process, var/time = null)
if (isnull(time))
time = TimeOfHour
// If world.timeofday has rolled over, then we need to adjust.
if (time < last_start[process])
last_start[process] -= 36000
var/lastRunTime = time - last_start[process]
if(lastRunTime < 0)
lastRunTime = 0
recordRunTime(process, lastRunTime)
/**
* recordRunTime
* Records a run time for a process
*/
/datum/controller/processScheduler/proc/recordRunTime(var/datum/controller/process/process, time)
last_run_time[process] = time
if(time > highest_run_time[process])
highest_run_time[process] = time
var/list/lastTwenty = last_twenty_run_times[process]
if (lastTwenty.len == 20)
lastTwenty.Cut(1, 2)
lastTwenty.len++
lastTwenty[lastTwenty.len] = time
/**
* averageRunTime
* returns the average run time (over the last 20) of the process
*/
/datum/controller/processScheduler/proc/averageRunTime(var/datum/controller/process/process)
var/lastTwenty = last_twenty_run_times[process]
var/t = 0
var/c = 0
for(var/time in lastTwenty)
t += time
c++
if(c > 0)
return t / c
return c
/datum/controller/processScheduler/proc/getProcessLastRunTime(var/datum/controller/process/process)
return last_run_time[process]
/datum/controller/processScheduler/proc/getProcessHighestRunTime(var/datum/controller/process/process)
return highest_run_time[process]
/datum/controller/processScheduler/proc/getStatusData()
var/list/data = new
for (var/datum/controller/process/p in processes)
data.len++
data[data.len] = p.getContextData()
return data
/datum/controller/processScheduler/proc/getProcessCount()
return processes.len
/datum/controller/processScheduler/proc/hasProcess(var/processName as text)
if (nameToProcessMap[processName])
return 1
/datum/controller/processScheduler/proc/killProcess(var/processName as text)
restartProcess(processName)
/datum/controller/processScheduler/proc/restartProcess(var/processName as text)
if (hasProcess(processName))
var/datum/controller/process/oldInstance = nameToProcessMap[processName]
var/datum/controller/process/newInstance = new oldInstance.type(src)
newInstance._copyStateFrom(oldInstance)
replaceProcess(oldInstance, newInstance)
oldInstance.kill()
/datum/controller/processScheduler/proc/enableProcess(var/processName as text)
if (hasProcess(processName))
var/datum/controller/process/process = nameToProcessMap[processName]
process.enable()
/datum/controller/processScheduler/proc/disableProcess(var/processName as text)
if (hasProcess(processName))
var/datum/controller/process/process = nameToProcessMap[processName]
process.disable()
/datum/controller/processScheduler/proc/getCurrentTickElapsedTime()
if (world.time > currentTick)
updateCurrentTickData()
return 0
else
return TimeOfHour - currentTickStart
/datum/controller/processScheduler/proc/updateCurrentTickData()
if (world.time > currentTick)
// New tick!
currentTick = world.time
currentTickStart = TimeOfHour
updateTimeAllowance()
cpuAverage = (world.cpu + cpuAverage + cpuAverage) / 3
/datum/controller/processScheduler/proc/updateTimeAllowance()
// Time allowance goes down linearly with world.cpu.
var/tmp/error = cpuAverage - 100
var/tmp/timeAllowanceDelta = sign(error) * -0.5 * world.tick_lag * max(0, 0.001 * abs(error))
//timeAllowance = world.tick_lag * min(1, 0.5 * ((200/max(1,cpuAverage)) - 1))
timeAllowance = min(timeAllowanceMax, max(0, timeAllowance + timeAllowanceDelta))
/datum/controller/processScheduler/proc/sign(var/x)
if (x == 0)
return 1
return x / abs(x)
/datum/controller/processScheduler/proc/statProcesses()
if(!isRunning)
stat("Processes", "Scheduler not running")
return
stat("Processes", "[processes.len] (R [running.len] / Q [queued.len] / I [idle.len])")
stat(null, "[round(cpuAverage, 0.1)] CPU, [round(timeAllowance, 0.1)/10] TA")
for(var/datum/controller/process/p in processes)
p.statProcess()
@@ -1,127 +0,0 @@
/**
* updateQueue.dm
*/
#ifdef UPDATE_QUEUE_DEBUG
#define uq_dbg(text) world << text
#else
#define uq_dbg(text)
#endif
/datum/updateQueue
var/tmp/list/objects
var/tmp/previousStart
var/tmp/procName
var/tmp/list/arguments
var/tmp/datum/updateQueueWorker/currentWorker
var/tmp/workerTimeout
var/tmp/adjustedWorkerTimeout
var/tmp/currentKillCount
var/tmp/totalKillCount
/datum/updateQueue/New(list/objects = list(), procName = "update", list/arguments = list(), workerTimeout = 2, inplace = 0)
..()
uq_dbg("Update queue created.")
// Init proc allows for recycling the worker.
init(objects = objects, procName = procName, arguments = arguments, workerTimeout = workerTimeout, inplace = inplace)
/**
* init
* @param list objects objects to update
* @param text procName the proc to call on each item in the object list
* @param list arguments optional arguments to pass to the update proc
* @param number workerTimeout number of ticks to wait for an update to
finish before forking a new update worker
* @param bool inplace whether the updateQueue should make a copy of objects.
the internal list will be modified, so it is usually
a good idea to leave this alone. Default behavior is to
copy.
*/
/datum/updateQueue/proc/init(list/objects = list(), procName = "update", list/arguments = list(), workerTimeout = 2, inplace = 0)
uq_dbg("Update queue initialization started.")
if (!inplace)
// Make an internal copy of the list so we're not modifying the original.
initList(objects)
else
src.objects = objects
// Init vars
src.procName = procName
src.arguments = arguments
src.workerTimeout = workerTimeout
adjustedWorkerTimeout = workerTimeout
currentKillCount = 0
totalKillCount = 0
uq_dbg("Update queue initialization finished. procName = '[procName]'")
/datum/updateQueue/proc/initList(list/toCopy)
/**
* We will copy the list in reverse order, as our doWork proc
* will access them by popping an element off the end of the list.
* This ends up being quite a lot faster than taking elements off
* the head of the list.
*/
objects = new
uq_dbg("Copying [toCopy.len] items for processing.")
for(var/i=toCopy.len,i>0,)
objects.len++
objects[objects.len] = toCopy[i--]
/datum/updateQueue/proc/Run()
uq_dbg("Starting run...")
startWorker()
while (istype(currentWorker) && !currentWorker.finished)
sleep(2)
checkWorker()
uq_dbg("UpdateQueue completed run.")
/datum/updateQueue/proc/checkWorker()
if(istype(currentWorker))
// If world.timeofday has rolled over, then we need to adjust.
if(world.timeofday < currentWorker.lastStart)
currentWorker.lastStart -= 864000
if(world.timeofday - currentWorker.lastStart > adjustedWorkerTimeout)
// This worker is a bit slow, let's spawn a new one and kill the old one.
uq_dbg("Current worker is lagging... starting a new one.")
killWorker()
startWorker()
else // No worker!
uq_dbg("update queue ended up without a worker... starting a new one...")
startWorker()
/datum/updateQueue/proc/startWorker()
// only run the worker if we have objects to work on
if(objects.len)
uq_dbg("Starting worker process.")
// No need to create a fresh worker if we already have one...
if (istype(currentWorker))
currentWorker.init(objects, procName, arguments)
else
currentWorker = new(objects, procName, arguments)
currentWorker.start()
else
uq_dbg("Queue is empty. No worker was started.")
currentWorker = null
/datum/updateQueue/proc/killWorker()
// Kill the worker
currentWorker.kill()
currentWorker = null
// After we kill a worker, yield so that if the worker's been tying up the cpu, other stuff can immediately resume
sleep(-1)
currentKillCount++
totalKillCount++
if (currentKillCount >= 3)
uq_dbg("[currentKillCount] workers have been killed with a timeout of [adjustedWorkerTimeout]. Increasing worker timeout to compensate.")
adjustedWorkerTimeout++
currentKillCount = 0
@@ -1,83 +0,0 @@
datum/updateQueueWorker
var/tmp/list/objects
var/tmp/killed
var/tmp/finished
var/tmp/procName
var/tmp/list/arguments
var/tmp/lastStart
var/tmp/cpuThreshold
datum/updateQueueWorker/New(var/list/objects, var/procName, var/list/arguments, var/cpuThreshold = 90)
..()
uq_dbg("updateQueueWorker created.")
init(objects, procName, arguments, cpuThreshold)
datum/updateQueueWorker/proc/init(var/list/objects, var/procName, var/list/arguments, var/cpuThreshold = 90)
src.objects = objects
src.procName = procName
src.arguments = arguments
src.cpuThreshold = cpuThreshold
killed = 0
finished = 0
datum/updateQueueWorker/proc/doWork()
// If there's nothing left to execute or we were killed, mark finished and return.
if (!objects || !objects.len) return finished()
lastStart = world.timeofday // Absolute number of ticks since the world started up
var/datum/object = objects[objects.len] // Pull out the object
objects.len-- // Remove the object from the list
if (istype(object) && !isturf(object) && !object.disposed && isnull(object.gcDestroyed)) // We only work with real objects
call(object, procName)(arglist(arguments))
// If there's nothing left to execute
// or we were killed while running the above code, mark finished and return.
if (!objects || !objects.len) return finished()
if (world.cpu > cpuThreshold)
// We don't want to force a tick into overtime!
// If the tick is about to go overtime, spawn the next update to go
// in the next tick.
uq_dbg("tick went into overtime with world.cpu = [world.cpu], deferred next update to next tick [1+(world.time / world.tick_lag)]")
spawn(1)
doWork()
else
spawn(0) // Execute anonymous function immediately as if we were in a while loop...
doWork()
datum/updateQueueWorker/proc/finished()
uq_dbg("updateQueueWorker finished.")
/**
* If the worker was killed while it was working on something, it
* should delete itself when it finally finishes working on it.
* Meanwhile, the updateQueue will have proceeded on with the rest of
* the queue. This will also terminate the spawned function that was
* created in the kill() proc.
*/
if(killed)
del(src)
finished = 1
datum/updateQueueWorker/proc/kill()
uq_dbg("updateQueueWorker killed.")
killed = 1
objects = null
/**
* If the worker is not done in 30 seconds after it's killed,
* we'll forcibly delete it, causing the anonymous function it was
* running to be terminated. Hasta la vista, baby.
*/
spawn(300)
del(src)
datum/updateQueueWorker/proc/start()
uq_dbg("updateQueueWorker started.")
spawn(0)
doWork()
@@ -1,94 +0,0 @@
/datum/processSchedulerView
/datum/processSchedulerView/Topic(href, href_list)
if (!href_list["action"])
return
switch (href_list["action"])
if ("kill")
var/toKill = href_list["name"]
processScheduler.killProcess(toKill)
refreshProcessTable()
if ("enable")
var/toEnable = href_list["name"]
processScheduler.enableProcess(toEnable)
refreshProcessTable()
if ("disable")
var/toDisable = href_list["name"]
processScheduler.disableProcess(toDisable)
refreshProcessTable()
if ("refresh")
refreshProcessTable()
/datum/processSchedulerView/proc/refreshProcessTable()
windowCall("handleRefresh", getProcessTable())
/datum/processSchedulerView/proc/windowCall(var/function, var/data = null)
usr << output(data, "processSchedulerContext.browser:[function]")
/datum/processSchedulerView/proc/getProcessTable()
var/text = "<table class=\"table table-striped\"><thead><tr><td>Name</td><td>Avg(s)</td><td>Last(s)</td><td>Highest(s)</td><td>Tickcount</td><td>Tickrate</td><td>State</td><td>Action</td></tr></thead><tbody>"
// and the context of each
for (var/list/data in processScheduler.getStatusData())
text += "<tr>"
text += "<td>[data["name"]]</td>"
text += "<td>[num2text(data["averageRunTime"]/10,3)]</td>"
text += "<td>[num2text(data["lastRunTime"]/10,3)]</td>"
text += "<td>[num2text(data["highestRunTime"]/10,3)]</td>"
text += "<td>[num2text(data["ticks"],4)]</td>"
text += "<td>[data["schedule"]]</td>"
text += "<td>[data["status"]]</td>"
text += "<td><button class=\"btn kill-btn\" data-process-name=\"[data["name"]]\" id=\"kill-[data["name"]]\">Kill</button>"
if (data["disabled"])
text += "<button class=\"btn enable-btn\" data-process-name=\"[data["name"]]\" id=\"enable-[data["name"]]\">Enable</button>"
else
text += "<button class=\"btn disable-btn\" data-process-name=\"[data["name"]]\" id=\"disable-[data["name"]]\">Disable</button>"
text += "</td>"
text += "</tr>"
text += "</tbody></table>"
return text
/**
* getContext
* Outputs an interface showing stats for all processes.
*/
/datum/processSchedulerView/proc/getContext()
bootstrap_browse()
usr << browse('processScheduler.js', "file=processScheduler.js;display=0")
var/text = {"<html><head>
<title>Process Scheduler Detail</title>
<script type="text/javascript">var ref = '\ref[src]';</script>
[bootstrap_includes()]
<script type="text/javascript" src="processScheduler.js"></script>
</head>
<body>
<h2>Process Scheduler</h2>
<div class="btn-group">
<button id="btn-refresh" class="btn">Refresh</button>
</div>
<h3>The process scheduler controls [processScheduler.getProcessCount()] loops.<h3>"}
text += "<div id=\"processTable\">"
text += getProcessTable()
text += "</div></body></html>"
usr << browse(text, "window=processSchedulerContext;size=800x600")
/datum/processSchedulerView/proc/bootstrap_browse()
usr << browse('bower_components/jquery/dist/jquery.min.js', "file=jquery.min.js;display=0")
usr << browse('bower_components/bootstrap2.3.2/bootstrap/js/bootstrap.min.js', "file=bootstrap.min.js;display=0")
usr << browse('bower_components/bootstrap2.3.2/bootstrap/css/bootstrap.min.css', "file=bootstrap.min.css;display=0")
usr << browse('bower_components/bootstrap2.3.2/bootstrap/img/glyphicons-halflings-white.png', "file=glyphicons-halflings-white.png;display=0")
usr << browse('bower_components/bootstrap2.3.2/bootstrap/img/glyphicons-halflings.png', "file=glyphicons-halflings.png;display=0")
usr << browse('bower_components/json2/json2.js', "file=json2.js;display=0")
/datum/processSchedulerView/proc/bootstrap_includes()
return {"
<link rel="stylesheet" href="bootstrap.min.css" />
<script type="text/javascript" src="json2.js"></script>
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="bootstrap.js"></script>
"}
@@ -1,27 +0,0 @@
/**
* testDyingUpdateQueueProcess
* This process is an example of a process using an updateQueue.
* The datums updated by this process behave badly and block the update loop
* by sleeping. If you #define UPDATE_QUEUE_DEBUG, you will see the updateQueue
* killing off its worker processes and spawning new ones to work around slow
* updates. This means that if you have a code path that sleeps for a long time
* in mob.Life once in a blue moon, the mob update loop will not hang.
*/
/datum/slowTestDatum/proc/wackyUpdateProcessName()
sleep(rand(0,20)) // Randomly REALLY slow :|
/datum/controller/process/testDyingUpdateQueueProcess
var/tmp/datum/updateQueue/updateQueueInstance
var/tmp/list/testDatums = list()
/datum/controller/process/testDyingUpdateQueueProcess/setup()
name = "Dying UpdateQueue Process"
schedule_interval = 30 // every 3 seconds
updateQueueInstance = new
for(var/i = 1, i < 30, i++)
testDatums.Add(new /datum/slowTestDatum)
/datum/controller/process/testDyingUpdateQueueProcess/doWork()
updateQueueInstance.init(testDatums, "wackyUpdateProcessName")
updateQueueInstance.Run()
@@ -1,35 +0,0 @@
/*
These are simple defaults for your project.
*/
#define DEBUG
var/global/datum/processSchedulerView/processSchedulerView
world
loop_checks = 0
New()
..()
processScheduler = new
processSchedulerView = new
mob
step_size = 8
New()
..()
verb
startProcessScheduler()
set name = "Start Process Scheduler"
processScheduler.setup()
processScheduler.start()
getProcessSchedulerContext()
set name = "Get Process Scheduler Status Panel"
processSchedulerView.getContext()
runUpdateQueueTests()
set name = "Run Update Queue Testsuite"
var/datum/updateQueueTests/t = new
t.runTests()
@@ -1,15 +0,0 @@
/**
* testHungProcess
* This process is an example of a simple update loop process that hangs.
*/
/datum/controller/process/testHungProcess/setup()
name = "Hung Process"
schedule_interval = 30 // every 3 seconds
/datum/controller/process/testHungProcess/doWork()
sleep(1000) // FUCK
// scheck is also responsible for handling hung processes. If a process
// hangs, and later resumes, but has already been killed by the scheduler,
// scheck will force the process to bail out.
scheck()
@@ -1,13 +0,0 @@
/**
* testNiceProcess
* This process is an example of a simple update loop process that is
* relatively fast.
*/
/datum/controller/process/testNiceProcess/setup()
name = "Nice Process"
schedule_interval = 10 // every second
/datum/controller/process/testNiceProcess/doWork()
sleep(rand(1,5)) // Just to pretend we're doing something
@@ -1,28 +0,0 @@
/**
* testSlowProcess
* This process is an example of a simple update loop process that is slow.
* The update loop here sleeps inside to provide an example, but if you had
* a computationally intensive loop process that is simply slow, you can use
* scheck() inside the loop to force it to yield periodically according to
* the sleep_interval var. By default, scheck will cause a loop to sleep every
* 2 ticks.
*/
/datum/controller/process/testSlowProcess/setup()
name = "Slow Process"
schedule_interval = 30 // every 3 seconds
/datum/controller/process/testSlowProcess/doWork()
// set background = 1 will cause loop constructs to sleep periodically,
// whenever the BYOND scheduler deems it productive to do so.
// This behavior is not always sufficient, nor is it always consistent.
// Rather than leaving it up to the BYOND scheduler, we can control it
// ourselves and leave nothing to the black box.
set background = 1
for(var/i=1,i<30,i++)
// Just to pretend we're doing something here
sleep(rand(3, 5))
// Forces this loop to yield(sleep) periodically.
scheck()
@@ -1,209 +0,0 @@
var/global/list/updateQueueTestCount = list()
/datum/updateQueueTests
var/start
proc
runTests()
world << "<b>Running 9 tests...</b>"
testUpdateQueuePerformance()
sleep(1)
testInplace()
sleep(1)
testInplaceUpdateQueuePerformance()
sleep(1)
testUpdateQueueReinit()
sleep(1)
testCrashingQueue()
sleep(1)
testEmptyQueue()
sleep(1)
testManySlowItemsInQueue()
sleep(1)
testVariableWorkerTimeout()
sleep(1)
testReallySlowItemInQueue()
sleep(1)
world << "<b>Finished!</b>"
beginTiming()
start = world.time
endTiming(text)
var/time = (world.time - start) / world.tick_lag
world << {"<b><font color="blue">Performance - [text] - <font color="green">[time]</font> ticks</font></b>"}
getCount()
return updateQueueTestCount[updateQueueTestCount.len]
incrementTestCount()
updateQueueTestCount.len++
updateQueueTestCount[updateQueueTestCount.len] = 0
assertCountEquals(count, text)
assertThat(getCount() == count, text)
assertCountLessThan(count, text)
assertThat(getCount() < count, text)
assertCountGreaterThan(count, text)
assertThat(getCount() > count, text)
assertThat(condition, text)
if (condition)
world << {"<font color="green"><b>PASS</b></font>: [text]"}
else
world << {"<b><font color="red">FAIL</font>: [text]</b>"}
testUpdateQueuePerformance()
incrementTestCount()
var/list/objs = new
for(var/i=1,i<=100000,i++)
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
var/datum/updateQueue/uq = new(objs)
beginTiming()
uq.Run()
endTiming("updating 100000 simple objects")
assertCountEquals(100000, "test that update queue updates all objects expected")
del(objs)
del(uq)
testUpdateQueueReinit()
incrementTestCount()
var/list/objs = new
for(var/i=1,i<=100,i++)
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
var/datum/updateQueue/uq = new(objs)
uq.Run()
objs = new
for(var/i=1,i<=100,i++)
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
uq.init(objs)
uq.Run()
assertCountEquals(200, "test that update queue reinitializes properly and updates all objects as expected.")
del(objs)
del(uq)
testInplace()
incrementTestCount()
var/list/objs = new
for(var/i=1,i<=100,i++)
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
var/datum/updateQueue/uq = new(objects = objs, inplace = 1)
uq.Run()
assertThat(objs.len == 0, "test that update queue inplace option really works inplace")
assertCountEquals(100, "test that inplace update queue updates the right number of objects")
del(objs)
del(uq)
testInplaceUpdateQueuePerformance()
incrementTestCount()
var/list/objs = new
for(var/i=1,i<=100000,i++)
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
var/datum/updateQueue/uq = new(objs)
beginTiming()
uq.Run()
endTiming("updating 100000 simple objects in place")
del(objs)
del(uq)
testCrashingQueue()
incrementTestCount()
var/list/objs = new
for(var/i=1,i<=10,i++)
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
objs.Add(new /datum/uqTestDatum/crasher(updateQueueTestCount.len))
for(var/i=1,i<=10,i++)
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
var/datum/updateQueue/uq = new(objs)
uq.Run()
assertCountEquals(20, "test that update queue handles crashed update procs OK")
del(objs)
del(uq)
testEmptyQueue()
incrementTestCount()
var/list/objs = new
var/datum/updateQueue/uq = new(objs)
uq.Run()
assertCountEquals(0, "test that update queue doesn't barf on empty lists")
del(objs)
del(uq)
testManySlowItemsInQueue()
incrementTestCount()
var/list/objs = new
for(var/i=1,i<=30,i++)
objs.Add(new /datum/uqTestDatum/slow(updateQueueTestCount.len))
var/datum/updateQueue/uq = new(objs)
uq.Run()
assertCountEquals(30, "test that update queue slows down execution if too many objects are slow to update")
del(objs)
del(uq)
testVariableWorkerTimeout()
incrementTestCount()
var/list/objs = new
for(var/i=1,i<=20,i++)
objs.Add(new /datum/uqTestDatum/slow(updateQueueTestCount.len))
var/datum/updateQueue/uq = new(objs, workerTimeout=6)
uq.Run()
assertCountEquals(20, "test that variable worker timeout works properly")
del(objs)
del(uq)
testReallySlowItemInQueue()
incrementTestCount()
var/list/objs = new
for(var/i=1,i<=10,i++)
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
objs.Add(new /datum/uqTestDatum/reallySlow(updateQueueTestCount.len))
for(var/i=1,i<=10,i++)
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
var/datum/updateQueue/uq = new(objs)
uq.Run()
assertCountEquals(20, "test that update queue skips objects that are too slow to update")
del(objs)
del(uq)
datum/uqTestDatum
var/testNum
New(testNum)
..()
src.testNum = testNum
proc/update()
updateQueueTestCount[testNum]++
proc/lag(cycles)
set background = 1
for(var/i=0,i<cycles,)
i++
datum/uqTestDatum/fast
datum/uqTestDatum/slow
update()
set background = 1
var/start = world.timeofday
while(world.timeofday - start < 5) // lag 4 deciseconds
..()
datum/uqTestDatum/reallySlow
update()
set background = 1
var/start = world.timeofday
while(world.timeofday - start < 300) // lag 30 seconds
..()
datum/uqTestDatum/crasher
update()
CRASH("I crashed! (I am supposed to crash XD)")
..() // This should do nothing lol
@@ -1,24 +0,0 @@
/**
* testUpdateQueueProcess
* This process is an example of a process using an updateQueue.
* The datums updated by this process behave nicely and do not block.
*/
/datum/fastTestDatum/proc/wackyUpdateProcessName()
sleep(prob(10)) // Pretty quick, usually instant
/datum/controller/process/testUpdateQueueProcess
var/tmp/datum/updateQueue/updateQueueInstance
var/tmp/list/testDatums = list()
/datum/controller/process/testUpdateQueueProcess/setup()
name = "UpdateQueue Process"
schedule_interval = 20 // every 2 seconds
updateQueueInstance = new
for(var/i = 1, i < 30, i++)
testDatums.Add(new /datum/fastTestDatum)
/datum/controller/process/testUpdateQueueProcess/doWork()
updateQueueInstance.init(testDatums, "wackyUpdateProcessName")
updateQueueInstance.Run()
@@ -1,13 +0,0 @@
/**
* testBadZombieProcess
* This process is an example of a simple update loop process that hangs.
*/
/datum/controller/process/testZombieProcess/setup()
name = "Zombie Process"
schedule_interval = 30 // every 3 seconds
/datum/controller/process/testZombieProcess/doWork()
for (var/i = 0, i < 1000, i++)
sleep(1)
scheck()
+1
View File
@@ -1,6 +1,7 @@
/datum/controller/process/air/setup()
name = "air"
schedule_interval = 20 // every 2 seconds
start_delay = 4
if(!air_master)
air_master = new
+34 -4
View File
@@ -1,10 +1,40 @@
// We manually initialize the alarm handlers instead of looping over all existing types
// to make it possible to write: camera.triggerAlarm() rather than alarm_manager.managers[datum/alarm_handler/camera].triggerAlarm() or a variant thereof.
/var/global/datum/alarm_handler/atmosphere/atmosphere_alarm = new()
/var/global/datum/alarm_handler/camera/camera_alarm = new()
/var/global/datum/alarm_handler/fire/fire_alarm = new()
/var/global/datum/alarm_handler/motion/motion_alarm = new()
/var/global/datum/alarm_handler/power/power_alarm = new()
// Alarm Manager, the manager for alarms.
var/datum/controller/process/alarm/alarm_manager
/datum/controller/process/alarm
var/list/datum/alarm/all_handlers
/datum/controller/process/alarm/setup()
name = "alarm"
schedule_interval = 20 // every 2 seconds
all_handlers = list(atmosphere_alarm, camera_alarm, fire_alarm, motion_alarm, power_alarm)
alarm_manager = src
/datum/controller/process/alarm/doWork()
alarm_manager.fire()
for(var/datum/alarm_handler/AH in all_handlers)
AH.process()
SCHECK
/datum/controller/process/alarm/getStatName()
var/list/alarms = alarm_manager.active_alarms()
return ..()+"([alarms.len])"
/datum/controller/process/alarm/proc/active_alarms()
var/list/all_alarms = new
for(var/datum/alarm_handler/AH in all_handlers)
var/list/alarms = AH.alarms
all_alarms += alarms
return all_alarms
/datum/controller/process/alarm/proc/number_of_active_alarms()
var/list/alarms = active_alarms()
return alarms.len
/datum/controller/process/alarm/statProcess()
..()
stat(null, "[number_of_active_alarms()] alarm\s")
+5 -6
View File
@@ -1,7 +1,6 @@
var/datum/controller/process/chemistry/chemistryProcess
/datum/controller/process/chemistry
var/tmp/datum/updateQueue/updateQueueInstance
var/list/active_holders
var/list/chemical_reactions
var/list/chemical_reagents
@@ -9,23 +8,23 @@ var/datum/controller/process/chemistry/chemistryProcess
/datum/controller/process/chemistry/setup()
name = "chemistry"
schedule_interval = 20 // every 2 seconds
updateQueueInstance = new
chemistryProcess = src
active_holders = list()
chemical_reactions = chemical_reactions_list
chemical_reagents = chemical_reagents_list
/datum/controller/process/chemistry/getStatName()
return ..()+"([active_holders.len])"
/datum/controller/process/chemistry/statProcess()
..()
stat(null, "[active_holders.len] reagent holder\s")
/datum/controller/process/chemistry/doWork()
for(var/datum/reagents/holder in active_holders)
if(!holder.process_reactions())
active_holders -= holder
scheck()
SCHECK
/datum/controller/process/chemistry/proc/mark_for_update(var/datum/reagents/holder)
if(holder in active_holders)
if(holder in active_holders)
return
//Process once, right away. If we still need to continue then add to the active_holders list and continue later
+7 -5
View File
@@ -4,11 +4,13 @@
/datum/controller/process/disease/setup()
name = "disease"
schedule_interval = 20 // every 2 seconds
updateQueueInstance = new
/datum/controller/process/disease/doWork()
updateQueueInstance.init(active_diseases, "process")
updateQueueInstance.Run()
for(var/disease in active_diseases)
var/datum/disease/D = disease
D.process()
SCHECK
/datum/controller/process/disease/getStatName()
return ..()+"([active_diseases.len])"
/datum/controller/process/disease/statProcess()
..()
stat(null, "[active_diseases.len] disease\s")
+36 -18
View File
@@ -1,13 +1,18 @@
// The time a datum was destroyed by the GC, or null if it hasn't been
/datum/var/gcDestroyed
#define GC_COLLECTIONS_PER_RUN 150
#define GC_COLLECTION_TIMEOUT (30 SECONDS)
#define GC_FORCE_DEL_PER_RUN 30
var/datum/controller/process/garbage_collector/garbage_collector
var/list/delayed_garbage = list()
/datum/controller/process/garbage_collector
var/garbage_collect = 1 // Whether or not to actually do work
var/collection_timeout = 300 //deciseconds to wait to let running procs finish before we just say fuck it and force del() the object
var/max_checks_multiplier = 5 //multiplier (per-decisecond) for calculating max number of tests per tick. These tests check if our GC'd objects are actually GC'd
var/max_forcedel_multiplier = 1 //multiplier (per-decisecond) for calculating max number of force del() calls per tick.
var/dels = 0 // number of del()'s we've done this tick
var/total_dels = 0 // number of total del()'s
var/tick_dels = 0 // number of del()'s we've done this tick
var/soft_dels = 0
var/hard_dels = 0 // number of hard dels in total
var/list/destroyed = list() // list of refID's of things that should be garbage collected
// refID's are associated with the time at which they time out and need to be manually del()
@@ -18,7 +23,8 @@ var/list/delayed_garbage = list()
/datum/controller/process/garbage_collector/setup()
name = "garbage"
schedule_interval = 2 SECONDS
schedule_interval = 10 SECONDS
start_delay = 3
if(!garbage_collector)
garbage_collector = src
@@ -36,10 +42,10 @@ world/loop_checks = 0
if(!garbage_collect)
return
dels = 0
var/time_to_kill = world.time - collection_timeout // Anything qdel() but not GC'd BEFORE this time needs to be manually del()
var/checkRemain = max_checks_multiplier * schedule_interval
var/maxDels = max_forcedel_multiplier * schedule_interval
tick_dels = 0
var/time_to_kill = world.time - GC_COLLECTION_TIMEOUT
var/checkRemain = GC_COLLECTIONS_PER_RUN
var/remaining_force_dels = GC_FORCE_DEL_PER_RUN
#ifdef GC_FINDREF
var/list/searching = list()
@@ -67,7 +73,7 @@ world/loop_checks = 0
#endif
while(destroyed.len && --checkRemain >= 0)
if(dels >= maxDels)
if(remaining_force_dels <= 0)
#ifdef GC_DEBUG
testing("GC: Reached max force dels per tick [dels] vs [maxDels]")
#endif
@@ -88,13 +94,22 @@ world/loop_checks = 0
testing("GC: -- \ref[A] | [A.type] was unable to be GC'd and was deleted --")
logging["[A.type]"]++
del(A)
++dels
++hard_dels
#ifdef GC_DEBUG
hard_dels++
remaining_force_dels--
else
#ifdef GC_DEBUG
testing("GC: [refID] properly GC'd at [world.time] with timeout [GCd_at_time]")
#endif
#endif
soft_dels++
tick_dels++
total_dels++
destroyed.Cut(1, 2)
SCHECK
#undef GC_FORCE_DEL_PER_TICK
#undef GC_COLLECTION_TIMEOUT
#undef GC_COLLECTIONS_PER_TICK
#ifdef GC_FINDREF
/datum/controller/process/garbage_collector/proc/LookForRefs(var/datum/D, var/list/targ)
@@ -132,8 +147,11 @@ world/loop_checks = 0
destroyed -= "\ref[A]" // Removing any previous references that were GC'd so that the current object will be at the end of the list.
destroyed["\ref[A]"] = world.time
/datum/controller/process/garbage_collector/getStatName()
return ..()+"([garbage_collector.destroyed.len]/[garbage_collector.dels]/[garbage_collector.hard_dels])"
/datum/controller/process/garbage_collector/statProcess()
..()
stat(null, "[garbage_collect ? "On" : "Off"], [destroyed.len] queued")
stat(null, "Dels: [total_dels], [soft_dels] soft, [hard_dels] hard, [tick_dels] last run")
// Tests if an atom has been deleted.
/proc/deleted(atom/A)
@@ -149,7 +167,7 @@ world/loop_checks = 0
crash_with("qdel() passed object of type [A.type]. qdel() can only handle /datum types.")
del(A)
if(garbage_collector)
garbage_collector.dels++
garbage_collector.total_dels++
garbage_collector.hard_dels++
else if(isnull(A.gcDestroyed))
// Let our friend know they're about to get collected
+1 -1
View File
@@ -10,4 +10,4 @@
log_access("AFK: [key_name(C)]")
C << "<SPAN CLASS='warning'>You have been inactive for more than [config.kick_inactive] minute\s and have been disconnected.</SPAN>"
del(C) // Don't qdel, cannot override finalize_qdel behaviour for clients.
scheck()
SCHECK
+1
View File
@@ -1,5 +1,6 @@
/datum/controller/process/lighting/setup()
name = "lighting"
start_delay = 1
schedule_interval = 5 // every .5 second
lighting_controller.initializeLighting()
+13 -21
View File
@@ -3,6 +3,7 @@
/datum/controller/process/machinery/setup()
name = "machinery"
schedule_interval = 20 // every 2 seconds
start_delay = 12
/datum/controller/process/machinery/doWork()
internal_sort()
@@ -19,10 +20,6 @@
/datum/controller/process/machinery/proc/internal_process_machinery()
for(var/obj/machinery/M in machines)
if(M && !M.gcDestroyed)
#ifdef PROFILE_MACHINES
var/time_start = world.timeofday
#endif
if(M.process() == PROCESS_KILL)
//M.inMachineList = 0 We don't use this debugging function
machines.Remove(M)
@@ -31,22 +28,13 @@
if(M && M.use_power)
M.auto_use_power()
#ifdef PROFILE_MACHINES
var/time_end = world.timeofday
if(!(M.type in machine_profiling))
machine_profiling[M.type] = 0
machine_profiling[M.type] += (time_end - time_start)
#endif
scheck()
SCHECK
/datum/controller/process/machinery/proc/internal_process_power()
for(var/datum/powernet/powerNetwork in powernets)
if(istype(powerNetwork) && !powerNetwork.disposed)
if(istype(powerNetwork) && isnull(powerNetwork.gcDestroyed))
powerNetwork.reset()
scheck()
SCHECK
continue
powernets.Remove(powerNetwork)
@@ -56,16 +44,20 @@
for(var/obj/item/I in processing_power_items)
if(!I.pwr_drain()) // 0 = Process Kill, remove from processing list.
processing_power_items.Remove(I)
scheck()
SCHECK
/datum/controller/process/machinery/proc/internal_process_pipenets()
for(var/datum/pipe_network/pipeNetwork in pipe_networks)
if(istype(pipeNetwork) && !pipeNetwork.disposed)
if(istype(pipeNetwork) && isnull(pipeNetwork.gcDestroyed))
pipeNetwork.process()
scheck()
SCHECK
continue
pipe_networks.Remove(pipeNetwork)
/datum/controller/process/machinery/getStatName()
return ..()+"(MCH:[machines.len] PWR:[powernets.len] PIP:[pipe_networks.len])"
/datum/controller/process/machinery/statProcess()
..()
stat(null, "[machines.len] machines")
stat(null, "[powernets.len] powernets")
stat(null, "[pipe_networks.len] pipenets")
stat(null, "[processing_power_items.len] power item\s")
+17 -11
View File
@@ -4,20 +4,26 @@
/datum/controller/process/mob/setup()
name = "mob"
schedule_interval = 20 // every 2 seconds
updateQueueInstance = new
start_delay = 16
/datum/controller/process/mob/started()
..()
if(!updateQueueInstance)
if(!mob_list)
mob_list = list()
else if(mob_list.len)
updateQueueInstance = new
if(!mob_list)
mob_list = list()
/datum/controller/process/mob/doWork()
if(updateQueueInstance)
updateQueueInstance.init(mob_list, "Life")
updateQueueInstance.Run()
for(last_object in mob_list)
var/mob/M = last_object
if(isnull(M.gcDestroyed))
try
M.Life()
catch(var/exception/e)
catchException(e, M)
SCHECK
else
catchBadType(M)
mob_list -= M
/datum/controller/process/mob/getStatName()
return ..()+"([mob_list.len])"
/datum/controller/process/mob/statProcess()
..()
stat(null, "[mob_list.len] mobs")
+15 -10
View File
@@ -1,14 +1,19 @@
/datum/controller/process/nanoui
var/tmp/datum/updateQueue/updateQueueInstance
/datum/controller/process/nanoui/setup()
name = "nanoui"
schedule_interval = 10 // every 1 second
updateQueueInstance = new
schedule_interval = 20 // every 2 seconds
/datum/controller/process/nanoui/statProcess()
..()
stat(null, "[nanomanager.processing_uis.len] UIs")
/datum/controller/process/nanoui/doWork()
updateQueueInstance.init(nanomanager.processing_uis, "process")
updateQueueInstance.Run()
/datum/controller/process/nanoui/getStatName()
return ..()+"([nanomanager.processing_uis.len])"
for(last_object in nanomanager.processing_uis)
var/datum/nanoui/NUI = last_object
if(istype(NUI) && isnull(NUI.gcDestroyed))
try
NUI.process()
catch(var/exception/e)
catchException(e, NUI)
else
catchBadType(NUI)
nanomanager.processing_uis -= NUI
+17 -15
View File
@@ -1,24 +1,26 @@
var/global/list/object_profiling = list()
/datum/controller/process/obj
var/tmp/datum/updateQueue/updateQueueInstance
/datum/controller/process/obj/setup()
name = "obj"
schedule_interval = 20 // every 2 seconds
updateQueueInstance = new
start_delay = 8
/datum/controller/process/obj/started()
..()
if(!updateQueueInstance)
if(!processing_objects)
processing_objects = list()
else if(processing_objects.len)
updateQueueInstance = new
if(!processing_objects)
processing_objects = list()
/datum/controller/process/obj/doWork()
if(updateQueueInstance)
updateQueueInstance.init(processing_objects, "process")
updateQueueInstance.Run()
for(last_object in processing_objects)
var/datum/O = last_object
if(isnull(O.gcDestroyed))
try
O:process()
catch(var/exception/e)
catchException(e, O)
SCHECK
else
catchBadType(O)
processing_objects -= O
/datum/controller/process/obj/getStatName()
return ..()+"([processing_objects.len])"
/datum/controller/process/obj/statProcess()
..()
stat(null, "[processing_objects.len] objects")
+4 -3
View File
@@ -8,7 +8,8 @@ var/global/list/turf/processing_turfs = list()
for(var/turf/T in processing_turfs)
if(T.process() == PROCESS_KILL)
processing_turfs.Remove(T)
scheck()
SCHECK
/datum/controller/process/turf/getStatName()
return ..()+"([processing_turfs.len])"
/datum/controller/process/turf/statProcess()
..()
stat(null, "[processing_turfs.len] turf\s")
+71
View File
@@ -0,0 +1,71 @@
//-------------------------------
/*
Wireless controller
Used for connecting devices to each other (i.e. machinery, doors, emitters, etc.)
Unlike the radio controller, the wireless controller does not pass communications between devices. Once the devices
have been connected they call each others procs directly, they do not use the wireless controller to communicate.
See code\modules\wireless\interfaces.dm for details of how to connect devices.
*/
//-------------------------------
var/datum/controller/process/wireless/wirelessProcess
/datum/controller/process/wireless
var/list/receiver_list
var/list/pending_connections
var/list/retry_connections
var/list/failed_connections
/datum/controller/process/wireless/setup()
name = "wireless"
schedule_interval = 50
pending_connections = new()
retry_connections = new()
failed_connections = new()
receiver_list = new()
wirelessProcess = src
/datum/controller/process/wireless/proc/add_device(var/datum/wifi/receiver/R)
if(receiver_list)
receiver_list |= R
else
receiver_list = new()
receiver_list |= R
/datum/controller/process/wireless/proc/remove_device(var/datum/wifi/receiver/R)
if(receiver_list)
receiver_list -= R
/datum/controller/process/wireless/proc/add_request(var/datum/connection_request/C)
if(pending_connections)
pending_connections += C
else
pending_connections = new()
pending_connections += C
/datum/controller/process/wireless/doWork()
//process any connection requests waiting to be retried
if(retry_connections.len > 0)
//any that fail are moved into the failed connections list
process_queue(retry_connections, failed_connections)
//process any pending connection requests
if(pending_connections.len > 0)
//any that fail are moved to the retry queue
process_queue(pending_connections, retry_connections)
/datum/controller/process/wireless/proc/process_queue(var/list/process_conections, var/list/unsuccesful_connections)
for(var/datum/connection_request/C in process_conections)
var/target_found = 0
for(var/datum/wifi/receiver/R in receiver_list)
if(R.id == C.id)
var/datum/wifi/sender/S = C.source
S.connect_device(R)
R.connect_device(S)
target_found = 1
process_conections -= C
if(!target_found)
unsuccesful_connections += C
SCHECK
+9 -1
View File
@@ -21,6 +21,7 @@ var/list/gamemode_cache = list()
var/log_pda = 0 // log pda messages
var/log_hrefs = 0 // logs all links clicked in-game. Could be used for debugging and tracking down exploits
var/log_runtime = 0 // logs world.log to a file
var/log_world_output = 0 // log world.log << messages
var/sql_enabled = 1 // for sql switching
var/allow_admin_ooccolor = 0 // Allows admins with relevant permissions to have their own ooc colour
var/allow_vote_restart = 0 // allow votes to restart
@@ -153,7 +154,8 @@ var/list/gamemode_cache = list()
var/admin_legacy_system = 0 //Defines whether the server uses the legacy admin system with admins.txt or the SQL system. Config option in config.txt
var/ban_legacy_system = 0 //Defines whether the server uses the legacy banning system with the files in /data or the SQL system. Config option in config.txt
var/use_age_restriction_for_jobs = 0 //Do jobs use account age restrictions? --requires database
var/use_age_restriction_for_jobs = 0 //Do jobs use account age restrictions? --requires database
var/use_age_restriction_for_antags = 0 //Do antags use account age restrictions? --requires database
var/simultaneous_pm_warning_timeout = 100
@@ -268,6 +270,9 @@ var/list/gamemode_cache = list()
if ("use_age_restriction_for_jobs")
config.use_age_restriction_for_jobs = 1
if ("use_age_restriction_for_antags")
config.use_age_restriction_for_antags = 1
if ("jobs_have_minimal_access")
config.jobs_have_minimal_access = 1
@@ -319,6 +324,9 @@ var/list/gamemode_cache = list()
if ("log_pda")
config.log_pda = 1
if ("log_world_output")
config.log_world_output = 1
if ("log_hrefs")
config.log_hrefs = 1
+87 -87
View File
@@ -1,87 +1,87 @@
/**
* Startup hook.
* Called in world.dm when the server starts.
*/
/hook/startup
/**
* Roundstart hook.
* Called in gameticker.dm when a round starts.
*/
/hook/roundstart
/**
* Roundend hook.
* Called in gameticker.dm when a round ends.
*/
/hook/roundend
/**
* Death hook.
* Called in death.dm when someone dies.
* Parameters: var/mob/living/carbon/human, var/gibbed
*/
/hook/death
/**
* Cloning hook.
* Called in cloning.dm when someone is brought back by the wonders of modern science.
* Parameters: var/mob/living/carbon/human
*/
/hook/clone
/**
* Debrained hook.
* Called in brain_item.dm when someone gets debrained.
* Parameters: var/obj/item/organ/brain
*/
/hook/debrain
/**
* Borged hook.
* Called in robot_parts.dm when someone gets turned into a cyborg.
* Parameters: var/mob/living/silicon/robot
*/
/hook/borgify
/**
* Podman hook.
* Called in podmen.dm when someone is brought back as a Diona.
* Parameters: var/mob/living/carbon/alien/diona
*/
/hook/harvest_podman
/**
* Payroll revoked hook.
* Called in Accounts_DB.dm when someone's payroll is stolen at the Accounts terminal.
* Parameters: var/datum/money_account
*/
/hook/revoke_payroll
/**
* Account suspension hook.
* Called in Accounts_DB.dm when someone's account is suspended or unsuspended at the Accounts terminal.
* Parameters: var/datum/money_account
*/
/hook/change_account_status
/**
* Employee reassignment hook.
* Called in card.dm when someone's card is reassigned at the HoP's desk.
* Parameters: var/obj/item/weapon/card/id
*/
/hook/reassign_employee
/**
* Employee terminated hook.
* Called in card.dm when someone's card is terminated at the HoP's desk.
* Parameters: var/obj/item/weapon/card/id
*/
/hook/terminate_employee
/**
* Crate sold hook.
* Called in supplyshuttle.dm when a crate is sold on the shuttle.
* Parameters: var/obj/structure/closet/crate/sold, var/area/shuttle
*/
/hook/sell_crate
/**
* Startup hook.
* Called in world.dm when the server starts.
*/
/hook/startup
/**
* Roundstart hook.
* Called in gameticker.dm when a round starts.
*/
/hook/roundstart
/**
* Roundend hook.
* Called in gameticker.dm when a round ends.
*/
/hook/roundend
/**
* Death hook.
* Called in death.dm when someone dies.
* Parameters: var/mob/living/carbon/human, var/gibbed
*/
/hook/death
/**
* Cloning hook.
* Called in cloning.dm when someone is brought back by the wonders of modern science.
* Parameters: var/mob/living/carbon/human
*/
/hook/clone
/**
* Debrained hook.
* Called in brain_item.dm when someone gets debrained.
* Parameters: var/obj/item/organ/brain
*/
/hook/debrain
/**
* Borged hook.
* Called in robot_parts.dm when someone gets turned into a cyborg.
* Parameters: var/mob/living/silicon/robot
*/
/hook/borgify
/**
* Podman hook.
* Called in podmen.dm when someone is brought back as a Diona.
* Parameters: var/mob/living/carbon/alien/diona
*/
/hook/harvest_podman
/**
* Payroll revoked hook.
* Called in Accounts_DB.dm when someone's payroll is stolen at the Accounts terminal.
* Parameters: var/datum/money_account
*/
/hook/revoke_payroll
/**
* Account suspension hook.
* Called in Accounts_DB.dm when someone's account is suspended or unsuspended at the Accounts terminal.
* Parameters: var/datum/money_account
*/
/hook/change_account_status
/**
* Employee reassignment hook.
* Called in card.dm when someone's card is reassigned at the HoP's desk.
* Parameters: var/obj/item/weapon/card/id
*/
/hook/reassign_employee
/**
* Employee terminated hook.
* Called in card.dm when someone's card is terminated at the HoP's desk.
* Parameters: var/obj/item/weapon/card/id
*/
/hook/terminate_employee
/**
* Crate sold hook.
* Called in supplyshuttle.dm when a crate is sold on the shuttle.
* Parameters: var/obj/structure/closet/crate/sold, var/area/shuttle
*/
/hook/sell_crate
@@ -0,0 +1,31 @@
#define OBSERVER_EVENT_DESTROY "OnDestroy"
/atom
var/list/observer_events
/atom/Destroy()
var/list/destroy_listeners = get_listener_list_from_event(OBSERVER_EVENT_DESTROY)
if(destroy_listeners)
for(var/destroy_listener in destroy_listeners)
call(destroy_listener, destroy_listeners[destroy_listener])(src)
for(var/list/listeners in observer_events)
listeners.Cut()
return ..()
/atom/proc/register(var/event, var/procOwner, var/proc_call)
var/list/listeners = get_listener_list_from_event(event)
listeners[procOwner] = proc_call
/atom/proc/unregister(var/event, var/procOwner)
var/list/listeners = get_listener_list_from_event(event)
listeners -= procOwner
/atom/proc/get_listener_list_from_event(var/observer_event)
if(!observer_events) observer_events = list()
var/list/listeners = observer_events[observer_event]
if(!listeners)
listeners = list()
observer_events[observer_event] = listeners
return listeners
@@ -0,0 +1,28 @@
/*
#define OBSERVER_EVENT_DESTROY "OnDestroy"
/datum
var/list/observer_events
/datum/Destroy()
for(var/list/listeners in observer_events)
listeners.Cut()
return ..()
/datum/proc/register(var/event, var/procOwner, var/proc_call)
var/list/listeners = get_listener_list_from_event(event)
listeners[procOwner] = proc_call
/datum/proc/unregister(var/event, var/procOwner)
var/list/listeners = get_listener_list_from_event(event)
listeners -= procOwner
/datum/proc/get_listener_list_from_event(var/observer_event)
if(!observer_events) observer_events = list()
var/list/listeners = observer_events[observer_event]
if(!listeners)
listeners = list()
observer_events[observer_event] = listeners
return listeners
*/
-30
View File
@@ -1,30 +0,0 @@
// We manually initialize the alarm handlers instead of looping over all existing types
// to make it possible to write: camera.triggerAlarm() rather than alarm_manager.managers[datum/alarm_handler/camera].triggerAlarm() or a variant thereof.
/var/global/datum/alarm_handler/atmosphere/atmosphere_alarm = new()
/var/global/datum/alarm_handler/camera/camera_alarm = new()
/var/global/datum/alarm_handler/fire/fire_alarm = new()
/var/global/datum/alarm_handler/motion/motion_alarm = new()
/var/global/datum/alarm_handler/power/power_alarm = new()
/datum/subsystem/alarm
name = "Alarm"
var/list/datum/alarm/all_handlers
/datum/subsystem/alarm/New()
all_handlers = list(atmosphere_alarm, camera_alarm, fire_alarm, motion_alarm, power_alarm)
/datum/subsystem/alarm/fire()
for(var/datum/alarm_handler/AH in all_handlers)
AH.process()
/datum/subsystem/alarm/proc/active_alarms()
var/list/all_alarms = new
for(var/datum/alarm_handler/AH in all_handlers)
var/list/alarms = AH.alarms
all_alarms += alarms
return all_alarms
/datum/subsystem/alarm/proc/number_of_active_alarms()
var/list/alarms = active_alarms()
return alarms.len
+4 -1
View File
@@ -25,7 +25,7 @@
usr.client.debug_variables(antag)
message_admins("Admin [key_name_admin(usr)] is debugging the [antag.role_text] template.")
/client/proc/debug_controller(controller in list("Master","Ticker","Ticker Process","Air","Jobs","Sun","Radio","Supply","Shuttles","Emergency Shuttle","Configuration","pAI", "Cameras", "Transfer Controller", "Gas Data","Event","Plants","Alarm","Nano","Chemistry"))
/client/proc/debug_controller(controller in list("Master","Ticker","Ticker Process","Air","Jobs","Sun","Radio","Supply","Shuttles","Emergency Shuttle","Configuration","pAI", "Cameras", "Transfer Controller", "Gas Data","Event","Plants","Alarm","Nano","Chemistry","Wireless"))
set category = "Debug"
set name = "Debug Controller"
set desc = "Debug the various periodic loop controllers for the game (be careful!)"
@@ -92,5 +92,8 @@
if("Chemistry")
debug_variables(chemistryProcess)
feedback_add_details("admin_verb", "DChem")
if("Wireless")
debug_variables(wirelessProcess)
feedback_add_details("admin_verb", "DWifi")
message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.")
return