Merge pull request #8707 from PsiOmegaDelta/Garbage

Ports /vg/&/tg/'s garbage collector.
This commit is contained in:
Chinsky
2015-04-09 08:50:12 +03:00
5 changed files with 276 additions and 15 deletions
+2
View File
@@ -16,6 +16,7 @@
#include "code\setup.dm"
#include "code\stylesheet.dm"
#include "code\world.dm"
#include "code\__HELPERS\atom_pool.dm"
#include "code\__HELPERS\files.dm"
#include "code\__HELPERS\game.dm"
#include "code\__HELPERS\global_lists.dm"
@@ -102,6 +103,7 @@
#include "code\controllers\Processes\disease.dm"
#include "code\controllers\Processes\emergencyShuttle.dm"
#include "code\controllers\Processes\event.dm"
#include "code\controllers\Processes\garbage.dm"
#include "code\controllers\Processes\inactivity.dm"
#include "code\controllers\Processes\lighting.dm"
#include "code\controllers\Processes\machinery.dm"
+96
View File
@@ -0,0 +1,96 @@
/*
/tg/station13 /atom/movable Pool:
---------------------------------
By RemieRichards
Creation/Deletion is laggy, so let's reduce reuse and recycle!
Locked to /atom/movable and it's subtypes due to Loc being a const var on /atom
but being read&write on /movable due to how they... move.
*/
var/global/list/GlobalPool = list()
//You'll be using this proc 90% of the time.
//It grabs a type from the pool if it can
//And if it can't, it creates one
//The pool is flexible and will expand to fit
//The new created atom when it eventually
//Goes into the pool
//Second argument can be a new location
//Or a list of arguments
//Either way it gets passed to new
/proc/PoolOrNew(var/get_type,var/second_arg)
if(!get_type)
return
var/atom/movable/AM
AM = GetFromPool(get_type,second_arg)
if(!AM)
if(ispath(get_type))
if(islist(second_arg))
AM = new get_type (arglist(second_arg))
else
AM = new get_type (second_arg)
if(AM)
return AM
/proc/GetFromPool(var/get_type,var/second_arg)
if(!get_type)
return 0
if(isnull(GlobalPool[get_type]))
return 0
if(length(GlobalPool[get_type]) == 0)
return 0
var/atom/movable/AM = pick_n_take(GlobalPool[get_type])
if(AM)
AM.ResetVars()
if(islist(second_arg))
AM.loc = second_arg[1]
AM.New(arglist(second_arg))
else
AM.loc = second_arg
AM.New(second_arg)
return AM
return 0
/proc/PlaceInPool(var/atom/movable/AM)
if(!istype(AM))
return
if(AM in GlobalPool[AM.type])
return
if(!GlobalPool[AM.type])
GlobalPool[AM.type] = list()
GlobalPool[AM.type] |= AM
AM.Destroy()
AM.ResetVars()
/atom/movable/proc/ResetVars(var/list/exlude = list())
var/list/excluded = list("animate_movement", "loc", "locs", "parent_type", "vars", "verbs", "type") + exlude
for(var/V in vars)
if(V in excluded)
continue
vars[V] = initial(vars[V])
vars["loc"] = null
+160
View File
@@ -0,0 +1,160 @@
var/datum/controller/process/garbage_collector/garbage_collector
// #define GC_DEBUG 1
/datum/controller/process/garbage_collector
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 SS 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 SS tick.
var/dels = 0 // number of del()'s we've done this tick
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()
// we do this so we aren't constantly locating them and preventing them from being gc'd
var/list/logging = list() // list of all types that have failed to GC associated with the number of times that's happened.
// the types are stored as strings
/datum/controller/process/garbage_collector/setup()
name = "garbage"
schedule_interval = 60 // every 6 seconds
if(!garbage_collector)
garbage_collector = src
/datum/controller/process/garbage_collector/doWork()
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
while(destroyed.len && --checkRemain >= 0)
if(dels >= maxDels)
#ifdef GC_DEBUG
testing("GC: Reached max force dels per tick [dels] vs [GC_FORCE_DEL_PER_TICK]")
#endif
break // Server's already pretty pounded, everything else can wait 2 seconds
var/refID = destroyed[1]
var/GCd_at_time = destroyed[refID]
if(GCd_at_time > time_to_kill)
#ifdef GC_DEBUG
testing("GC: [refID] not old enough, breaking at [world.time] for [GCd_at_time - time_to_kill] deciseconds until [GCd_at_time + GC_COLLECTION_TIMEOUT]")
#endif
break // Everything else is newer, skip them
var/atom/A = locate(refID)
#ifdef GC_DEBUG
testing("GC: [refID] old enough to test: GCd_at_time: [GCd_at_time] time_to_kill: [time_to_kill] current: [world.time]")
#endif
if(A && A.gc_destroyed == GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake
// Something's still referring to the qdel'd object. Kill it.
testing("GC: -- \ref[A] | [A.type] was unable to be GC'd and was deleted --")
logging["[A.type]"]++
del(A)
++dels
#ifdef GC_DEBUG
else
testing("GC: [refID] properly GC'd at [world.time] with timeout [GCd_at_time]")
#endif
destroyed.Cut(1, 2)
scheck()
/datum/controller/process/garbage_collector/proc/AddTrash(datum/A)
if(!istype(A) || !isnull(A.gc_destroyed))
return
#ifdef GC_DEBUG
testing("GC: AddTrash([A.type])")
#endif
A.gc_destroyed = world.time
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
// Should be treated as a replacement for the 'del' keyword.
// Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
/proc/qdel(var/datum/A)
if(!A)
return
if(!istype(A))
//warning("qdel() passed object of type [A.type]. qdel() can only handle /datum types.")
del(A)
garbage_collector.dels++
else if(isnull(A.gc_destroyed))
// Let our friend know they're about to get fucked up.
. = !A.Destroy()
if(. && A)
A.finalize_qdel()
/datum/proc/finalize_qdel()
del(src)
/turf/finalize_qdel()
garbage_collector.AddTrash(src)
// Default implementation of clean-up code.
// This should be overridden to remove all references pointing to the object being destroyed.
// Return true if the the GC controller should allow the object to continue existing. (Useful if pooling objects.)
/datum/proc/Destroy()
return
/datum/var/gc_destroyed //Time when this object was destroyed.
#ifdef TESTING
/client/var/running_find_references
/atom/verb/find_references()
set category = "Debug"
set name = "Find References"
set background = 1
set src in world
if(!usr || !usr.client)
return
if(usr.client.running_find_references)
testing("CANCELLED search for references to a [usr.client.running_find_references].")
usr.client.running_find_references = null
return
if(alert("Running this will create a lot of lag until it finishes. You can cancel it by running it again. Would you like to begin the search?", "Find References", "Yes", "No") == "No")
return
// Remove this object from the list of things to be auto-deleted.
if(garbage)
garbage.destroyed -= "\ref[src]"
usr.client.running_find_references = type
testing("Beginning search for references to a [type].")
var/list/things = list()
for(var/client/thing)
things += thing
for(var/datum/thing)
things += thing
for(var/atom/thing)
things += thing
testing("Collected list of things in search for references to a [type]. ([things.len] Thing\s)")
for(var/datum/thing in things)
if(!usr.client.running_find_references) return
for(var/varname in thing.vars)
var/variable = thing.vars[varname]
if(variable == src)
testing("Found [src.type] \ref[src] in [thing.type]'s [varname] var.")
else if(islist(variable))
if(src in variable)
testing("Found [src.type] \ref[src] in [thing.type]'s [varname] list var.")
testing("Completed search for references to a [type].")
usr.client.running_find_references = null
/client/verb/purge_all_destroyed_objects()
set category = "Debug"
if(garbage)
while(garbage.destroyed.len)
var/datum/o = locate(garbage.destroyed[1])
if(istype(o) && o.gc_destroyed)
del(o)
garbage.dels++
garbage.destroyed.Cut(1, 2)
#endif
#ifdef GC_DEBUG
#undef GC_DEBUG
#endif
+1 -1
View File
@@ -3,7 +3,7 @@
/datum/controller/process/nanoui/setup()
name = "nanoui"
schedule_interval = 20 // every 2 seconds
schedule_interval = 10 // every 1 second
updateQueueInstance = new
/datum/controller/process/nanoui/doWork()
+17 -14
View File
@@ -731,25 +731,31 @@ note dizziness decrements automatically in the mob's Life() proc.
if(statpanel("Status") && processScheduler && processScheduler.getIsRunning())
var/datum/controller/process/process
process = processScheduler.getProcess("ticker")
stat(null, "[getStatName(process)]\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("air")
stat(null, "[getStatName(process)]\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("lighting")
stat(null, "[getStatName(process)]\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("alarm")
var/list/alarms = alarm_manager.active_alarms()
stat(null, "[getStatName(process)]([alarms.len])\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("mob")
stat(null, "[getStatName(process)]([mob_list.len])\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("disease")
stat(null, "[getStatName(process)]([active_diseases.len])\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("garbage")
stat(null, "[getStatName(process)]([garbage_collector.destroyed.len])\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("machinery")
stat(null, "[getStatName(process)]([machines.len])\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("mob")
stat(null, "[getStatName(process)]([mob_list.len])\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("nanoui")
stat(null, "[getStatName(process)]([nanomanager.processing_uis.len])\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("lighting")
stat(null, "[getStatName(process)]\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("obj")
stat(null, "[getStatName(process)]([processing_objects.len])\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
@@ -759,15 +765,12 @@ note dizziness decrements automatically in the mob's Life() proc.
process = processScheduler.getProcess("powernet")
stat(null, "[getStatName(process)]([powernets.len])\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("nanoui")
stat(null, "[getStatName(process)]([nanomanager.processing_uis.len])\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("disease")
stat(null, "[getStatName(process)]([active_diseases.len])\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("sun")
stat(null, "[getStatName(process)]\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
process = processScheduler.getProcess("ticker")
stat(null, "[getStatName(process)]\t - #[process.getTicks()]\t - [process.getLastRunTime()]")
else
stat(null, "processScheduler is not running.")