diff --git a/baystation12.dme b/baystation12.dme index 8aeb018963..025113613b 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -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" diff --git a/code/__HELPERS/atom_pool.dm b/code/__HELPERS/atom_pool.dm new file mode 100644 index 0000000000..83a8a1d67b --- /dev/null +++ b/code/__HELPERS/atom_pool.dm @@ -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 diff --git a/code/controllers/Processes/garbage.dm b/code/controllers/Processes/garbage.dm new file mode 100644 index 0000000000..11ba8979f5 --- /dev/null +++ b/code/controllers/Processes/garbage.dm @@ -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 diff --git a/code/controllers/Processes/nanoui.dm b/code/controllers/Processes/nanoui.dm index c8396bcab8..a35280131f 100644 --- a/code/controllers/Processes/nanoui.dm +++ b/code/controllers/Processes/nanoui.dm @@ -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() diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 48cf3b7255..5af7e711ca 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -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.")