mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2025-12-20 07:12:55 +00:00
184 lines
5.2 KiB
Plaintext
184 lines
5.2 KiB
Plaintext
// Main garbage collection code
|
|
|
|
// For general information about how the GC works and how to use it, see __gc_info.dm
|
|
|
|
#define GC_COLLECTIONS_PER_TICK 150 // Was 100.
|
|
#define GC_COLLECTION_TIMEOUT (30 SECONDS)
|
|
#define GC_FORCE_DEL_PER_TICK 30
|
|
//#define GC_DEBUG
|
|
|
|
// A list of types that were queued in the GC, and had to be soft deleted; used in testing
|
|
var/list/gc_hard_del_types = list()
|
|
var/global/datum/controller/process/garbage_collector/garbageCollector
|
|
|
|
// The time a datum was destroyed by the GC, or null if it hasn't been
|
|
/datum/var/gcDestroyed
|
|
// Whether a datum was hard-deleted by the GC; 0 if not, 1 if it was queued, -1 if directly deleted
|
|
/datum/var/hard_deleted = 0
|
|
|
|
/datum/controller/process/garbage_collector
|
|
var/list/queue = new
|
|
var/del_everything = 0
|
|
|
|
// To let them know how hardworking am I :^).
|
|
var/dels_count = 0
|
|
var/hard_dels = 0
|
|
var/soft_dels = 0
|
|
|
|
/datum/controller/process/garbage_collector/proc/addTrash(var/datum/D)
|
|
if(!istype(D) || del_everything)
|
|
del(D)
|
|
hard_dels++
|
|
dels_count++
|
|
return
|
|
|
|
queue -= "\ref[D]" // If this is a re-used ref, remove the old ref from the queue
|
|
queue["\ref[D]"] = world.time
|
|
|
|
/datum/controller/process/garbage_collector/proc/processGarbage()
|
|
var/remainingCollectionPerTick = GC_COLLECTIONS_PER_TICK
|
|
var/remainingForceDelPerTick = GC_FORCE_DEL_PER_TICK
|
|
var/collectionTimeScope = world.time - GC_COLLECTION_TIMEOUT
|
|
while(queue.len && --remainingCollectionPerTick >= 0)
|
|
var/refID = queue[1]
|
|
var/destroyedAtTime = queue[refID]
|
|
|
|
if(destroyedAtTime > collectionTimeScope)
|
|
break
|
|
|
|
var/datum/D = locate(refID)
|
|
// If the object still exists, and it's the same object, hard del it
|
|
if(D && D.gcDestroyed == destroyedAtTime)
|
|
if(remainingForceDelPerTick <= 0)
|
|
break
|
|
|
|
#ifdef GC_DEBUG
|
|
gcwarning("GC process force delete [D.type]")
|
|
#endif
|
|
|
|
hardDel(D)
|
|
queue.Cut(1, 2)
|
|
|
|
remainingForceDelPerTick--
|
|
// Sleep check more aggressively when force deleting.
|
|
calls_since_last_scheck += 9
|
|
else // Otherwise, it was GC'd - remove it from the queue
|
|
queue.Cut(1, 2)
|
|
soft_dels++
|
|
dels_count++
|
|
SCHECK
|
|
|
|
#ifdef GC_DEBUG
|
|
#undef GC_DEBUG
|
|
#endif
|
|
|
|
#undef GC_FORCE_DEL_PER_TICK
|
|
#undef GC_COLLECTION_TIMEOUT
|
|
#undef GC_COLLECTIONS_PER_TICK
|
|
|
|
/datum/controller/process/garbage_collector/proc/hardDel(var/datum/D)
|
|
gc_hard_del_types |= D.type
|
|
D.hard_deleted = 1
|
|
if(!D.gcDestroyed)
|
|
spawn(-1)
|
|
D.Destroy()
|
|
D.gcDestroyed = world.time
|
|
del(D)
|
|
hard_dels++
|
|
dels_count++
|
|
|
|
// Effectively replaces del for any datum-based type
|
|
/proc/qdel(var/datum/D)
|
|
if(isnull(D))
|
|
return
|
|
|
|
if(isnull(garbageCollector))
|
|
del(D)
|
|
return
|
|
|
|
if(!istype(D)) // A non-datum was passed into qdel - just delete it outright.
|
|
// warning("qdel() passed object of type [D.type]. qdel() can only handle /datum/ types.")
|
|
del(D)
|
|
return
|
|
|
|
if(isnull(D.gcDestroyed))
|
|
// Let our friend know they're about to get fucked up.
|
|
var/hint
|
|
D.gcDestroyed = world.time
|
|
try
|
|
hint = D.Destroy()
|
|
catch(var/exception/e)
|
|
if(istype(e))
|
|
log_runtime(e, D, "Caught by qdel() destroying [D.type]")
|
|
else
|
|
gcwarning("qdel() caught runtime destroying [D.type]: [e]")
|
|
// Destroy runtimed? Panic! Hard delete!
|
|
D.hard_deleted = -1
|
|
del(D)
|
|
if(garbageCollector)
|
|
garbageCollector.dels_count++
|
|
return
|
|
if(!isnull(D.gcDestroyed) && D.gcDestroyed != world.time)
|
|
gcwarning("Sleep detected in Destroy() call of [D.type]")
|
|
D.gcDestroyed = world.time
|
|
|
|
switch(hint)
|
|
if(QDEL_HINT_QUEUE) //qdel should queue the object for deletion
|
|
garbageCollector.addTrash(D)
|
|
if(QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destroy.
|
|
return
|
|
if(QDEL_HINT_IWILLGC) //functionally the same as the above. qdel should assume the object will gc on its own, and not check it.
|
|
return
|
|
if(QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
|
|
D.hard_deleted = -1 // -1 means "this hard del skipped the queue", used for profiling
|
|
del(D)
|
|
if(garbageCollector)
|
|
garbageCollector.dels_count++
|
|
if(QDEL_HINT_PUTINPOOL) //qdel will put this object in the pool.
|
|
PlaceInPool(D,0)
|
|
if(QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion.
|
|
#ifdef TESTING
|
|
D.find_references(remove_from_queue = FALSE)
|
|
#endif
|
|
garbageCollector.addTrash(D)
|
|
else
|
|
// to_chat(world, "WARNING GC DID NOT GET A RETURN VALUE FOR [D], [D.type]!")
|
|
garbageCollector.addTrash(D)
|
|
|
|
|
|
// Returns 1 if the object has been queued for deletion.
|
|
/proc/qdeleted(var/datum/D)
|
|
if(!istype(D))
|
|
return 0
|
|
if(!isnull(D.gcDestroyed))
|
|
return 1
|
|
return 0
|
|
|
|
/*
|
|
* Like Del(), but for qdel.
|
|
* Called BEFORE qdel moves shit.
|
|
*/
|
|
/datum/proc/Destroy()
|
|
tag = null
|
|
return QDEL_HINT_QUEUE // Garbage Collect everything.
|
|
|
|
// If something gets deleted directly, make sure its Destroy proc is still called
|
|
/datum/Del()
|
|
if(isnull(gcDestroyed)) // Not GC'd
|
|
try
|
|
Destroy()
|
|
catch(var/exception/e)
|
|
if(istype(e))
|
|
log_runtime(e, src, "Caught by Del() destroying [type]")
|
|
else
|
|
gcwarning("Del() caught runtime destroying [type]: [e]")
|
|
if(del_profiling)
|
|
delete_profile(src)
|
|
else
|
|
if(del_profiling)
|
|
delete_profile(src)
|
|
return ..()
|
|
|
|
/proc/gcwarning(msg)
|
|
log_to_dd("## GC WARNING: [msg]")
|