First pass at a qdel() garbage collection system for tgstation

Works pretty well. If it can't GC something, it'll just del() it and be done.
Speed is amazing, holy shit.

New procs you should be aware of:
qdel(atom/movable) - sets up an object for garbage collection. Call this rather than del(atom/movable).
atom/movable/Destroy() - called right before the object is GC'd, so it still has a loc. Also called if the object is del()'d.
new controller - garbage.dm has all the details on this. Basically it nulls all references on GC'd objects and force del() them if necessary.
Generally speaking, objects should use Destroy() for behavior prior to deletion rather than Del(). You should also always call the parent so the object gets the right gc_destroyed var set.

ISSUES:
Tries to GC mobs atm. This actually works for new players, not so much for humans/monkies/simple_animals/anything. I'm guessing it needs to clear out their mind and HUD and maybe other things.
Gibbing is really bugged. It works, but the overlays just sit there for awhile and ugh. I'm very tempted just to del() mob/living and mob/camera and call it a day.
qdel() equipment doesn't unequip the item.
Pipes don't generally GC correctly. Debugging suggests they get referenced in many pipenets and that isn't cleared properly. However some do work fine. Need assistance here.
Bots don't GC, probably in the radio controller.
Lots of other shit doesn't GC but it's hard to find them because of the pipe spam.
I think I'm calling Destroy() twice by accident.
This commit is contained in:
MrPerson
2014-02-23 14:55:12 -08:00
parent 2979e26798
commit 9eee3e5067
432 changed files with 2672 additions and 2569 deletions

View File

@@ -139,7 +139,7 @@ atom/movable/New()
light = new(src)
//Objects with opacity will trigger nearby lights to update at next lighting process.
atom/movable/Del()
atom/movable/Destroy()
if(opacity)
if(isturf(loc))
if(loc:lighting_lumcount > 1)

107
code/controllers/garbage.dm Normal file
View File

@@ -0,0 +1,107 @@
#define GC_COLLECTIONS_PER_TICK 100 // maybe make this a config option at some point
#define GC_COLLECTION_TIMEOUT 300 // deciseconds to wait to let running procs finish before we just say fuck it and force del() the object
var/datum/controller/garbage_collector/garbage = new()
var/list/uncollectable_vars=list(
// "bounds", // bounds and its ilk are all caught by the issaved() check later on
"contents",
"gc_destroyed",
"gender", // Causes runtimes if the logging is on
"parent",
"step_size",
)
// These are the vars left from /vg/'s implementation that aren't const or global
// I dunno how many are necessary but since most of these are numbers anyways, I don't care.
/datum/controller/garbage_collector
var/list/queue = list() // list of things that have yet to have all their vars nulled out
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
/datum/controller/garbage_collector/proc/AddTrash(var/atom/movable/A)
if(!istype(A))
return
// testing("GC: AddTrash([A.type])")
queue |= A
/datum/controller/garbage_collector/proc/Pop()
var/atom/movable/A = queue[1]
if(!A)
queue.Cut(1, 2)
// testing("GC: Pop() given null")
return
if(!istype(A,/atom/movable))
// testing("GC: -- Pop() given [A.type] --")
queue.Cut(1, 2)
del(A)
return
for(var/vname in A.vars)
if(!issaved(A.vars[vname]))
// testing("GC: Skipping [vname] in [A.type]: it's const|global|tmp")
continue
if(vname in uncollectable_vars)
// testing("GC: Skipping [vname] in [A.type]: it's uncollectable")
continue
// testing("GC: Unsetting [vname] in [A.type]")
A.vars[vname] = null
// testing("GC: Pop([A.type]) - destroyed\[\ref[A]\] = [A.gc_destroyed] current time: [world.timeofday]")
destroyed["\ref[A]"] = A.gc_destroyed
queue.Cut(1, 2)
/datum/controller/garbage_collector/proc/process()
var/i = 1
while(queue.len && i <= GC_COLLECTIONS_PER_TICK)
Pop()
i++
i = 1
var/time_to_kill = world.timeofday - GC_COLLECTION_TIMEOUT // Anything qdel() but not GC'd BEFORE this time needs to be manually del()
if(time_to_kill < 1) // Within the first GC_COLLECTION_TIMEOUT deciseconds of midnight
time_to_kill += MIDNIGHT_ROLLOVER
while(i <= destroyed.len && i <= GC_COLLECTIONS_PER_TICK)
var/refID = destroyed[i]
var/GCd_at_time = destroyed[refID]
if(GCd_at_time > time_to_kill)
// testing("GC: [refID] not old enough, breaking at [world.timeofday] for [time_to_kill - GCd_at_time] deciseconds")
i++
break // Everything else is newer, skip them
var/atom/A = locate(refID)
// testing("GC: [refID] old enough to test: GCd_at_time: [GCd_at_time] time_to_kill: [time_to_kill] current: [world.timeofday]")
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 garbage collected and was force del() --")
del(A)
// else
// testing("GC: [refID] properly GC'd at [world.timeofday] with timeout [GCd_at_time]")
destroyed.Cut(i, ++i) // also increases i in general
/**
* NEVER USE THIS FOR ANYTHING OTHER THAN /atom/movable
* OTHER TYPES CANNOT BE QDEL'D BECAUSE THEIR LOC IS LOCKED OR THEY DON'T HAVE ONE.
* While I'm leaving the above comment in since /atoms cannot be garbage collected, datums and lists can be garbage collected just fine.
* Read the DM guide on it. Hit f1 and search for "garbage collection"
*/
/proc/qdel(var/atom/movable/A)
if(!A)
return
if(!istype(A))
warning("qdel() passed object of type [A.type]. qdel() can only handle /atom/movable types.")
del(A)
return
if(!garbage)
del(A)
return
// Let our friend know they're about to get fucked up.
A.Destroy()
garbage.AddTrash(A)
/* // If you can get this to run, report the results please.
/client/verb/delete_everything()
set name = "qdel() everything"
set category = "Debug"
set background = 1
if(input("Are you sure you want to do that?") as null|anything in list("Yes","No") != "Yes")
return
src << "qdel(everything)"
for(var/atom/movable/everything in world)
qdel(everything)
*/

View File

@@ -27,6 +27,7 @@ datum/controller/game_controller
var/nano_cost = 0
var/events_cost = 0
var/ticker_cost = 0
var/gc_cost = 0
var/total_cost = 0
var/last_thing_processed
@@ -63,7 +64,6 @@ datum/controller/game_controller/New()
if(!emergency_shuttle) emergency_shuttle = new /datum/shuttle_controller/emergency_shuttle()
if(!supply_shuttle) supply_shuttle = new /datum/controller/supply_shuttle()
datum/controller/game_controller/proc/setup()
world.tick_lag = config.Ticklag
@@ -201,6 +201,12 @@ datum/controller/game_controller/proc/process()
ticker.process()
ticker_cost = (world.timeofday - timer) / 10
// GC
timer = world.timeofday
last_thing_processed = garbage.type
garbage.process()
gc_cost = (world.timeofday - timer) / 10
//TIMING
total_cost = air_cost + sun_cost + mobs_cost + diseases_cost + machines_cost + objects_cost + networks_cost + powernets_cost + nano_cost + events_cost + ticker_cost
@@ -228,7 +234,7 @@ datum/controller/game_controller/proc/process_mobs()
var/i = 1
while(i<=mob_list.len)
var/mob/M = mob_list[i]
if(M)
if(M && !M.gc_destroyed)
last_thing_processed = M.type
M.Life()
i++
@@ -250,7 +256,7 @@ datum/controller/game_controller/proc/process_machines()
var/i = 1
while(i<=machines.len)
var/obj/machinery/Machine = machines[i]
if(Machine)
if(Machine && !Machine.gc_destroyed)
last_thing_processed = Machine.type
if(Machine.process() != PROCESS_KILL)
if(Machine)
@@ -264,7 +270,7 @@ datum/controller/game_controller/proc/process_objects()
var/i = 1
while(i<=processing_objects.len)
var/obj/Object = processing_objects[i]
if(Object)
if(Object && !Object.gc_destroyed)
last_thing_processed = Object.type
Object.process()
i++

View File

@@ -49,13 +49,13 @@ var/global/datum/controller/supply_shuttle/supply_shuttle
/obj/structure/plasticflaps/ex_act(severity)
switch(severity)
if (1)
del(src)
qdel(src)
if (2)
if (prob(50))
del(src)
qdel(src)
if (3)
if (prob(5))
del(src)
qdel(src)
/obj/structure/plasticflaps/mining //A specific type for mining that doesn't allow airflow because of them damn crates
name = "airtight plastic flaps"
@@ -67,7 +67,7 @@ var/global/datum/controller/supply_shuttle/supply_shuttle
T.blocks_air = 1
..()
Del() //lazy hack to set the turf to allow air to pass if it's a simulated floor
Destroy() //lazy hack to set the turf to allow air to pass if it's a simulated floor //wow this is terrible
var/turf/T = get_turf(loc)
if(T)
if(istype(T, /turf/simulated/floor))
@@ -231,7 +231,7 @@ var/global/datum/controller/supply_shuttle/supply_shuttle
var/crate_count = 0
centcom_message = ""
for(var/atom/movable/MA in shuttle)
if(MA.anchored) continue
@@ -286,7 +286,7 @@ var/global/datum/controller/supply_shuttle/supply_shuttle
if(istype(A, /obj/item/stack/sheet/mineral/plasma))
var/obj/item/stack/sheet/mineral/plasma/P = A
plasma_count += P.amount
if(istype(A, /obj/item/seeds))
var/obj/item/seeds/S = A
if(S.rarity == 0) // Mundane species
@@ -303,8 +303,8 @@ var/global/datum/controller/supply_shuttle/supply_shuttle
discoveredPlants[S.type] = S.potency
centcom_message += "<font color=green>+[S.rarity]</font>: New species discovered: \"[capitalize(S.species)]\". Excellent work.<BR>"
points += S.rarity // That's right, no bonus for potency. Send a crappy sample first to "show improvement" later
del(MA)
qdel(MA)
if(plasma_count)
centcom_message += "<font color=green>+[round(plasma_count/plasma_per_point)]</font>: Received [plasma_count] units of exotic material.<BR>"
points += round(plasma_count / plasma_per_point)
@@ -387,7 +387,7 @@ var/global/datum/controller/supply_shuttle/supply_shuttle
// If it has multiple items, there's a 1% of each going missing... Not for secure crates or those large wooden ones, though.
if(contains.len > 1 && prob(1) && !findtext(SP.containertype,"/secure/") && !findtext(SP.containertype,"/largecrate/"))
slip.erroneous |= MANIFEST_ERROR_ITEM // This item was not included in the shipment!
del(B2) // Lost in space... or the loading dock.
qdel(B2) // Lost in space... or the loading dock.
//manifest finalisation
slip.info += "</ul><br>"

View File

@@ -28,7 +28,7 @@
return
/client/proc/debug_controller(controller in list("Master","Failsafe","Ticker","Lighting","Air","Jobs","Sun","Radio","Supply Shuttle","Emergency Shuttle","Configuration","pAI", "Cameras", "Events"))
/client/proc/debug_controller(controller in list("Master","Failsafe","Ticker","Lighting","Garbage","Air","Jobs","Sun","Radio","Supply Shuttle","Emergency Shuttle","Configuration","pAI", "Cameras", "Events"))
set category = "Debug"
set name = "Debug Controller"
set desc = "Debug the various periodic loop controllers for the game (be careful!)"
@@ -47,6 +47,9 @@
if("Lighting")
debug_variables(lighting_controller)
feedback_add_details("admin_verb","DLighting")
if("Garbage")
debug_variables(garbage)
feedback_add_details("admin_verb","DGarbage")
if("Air")
debug_variables(air_master)
feedback_add_details("admin_verb","DAir")