# Guide to Hard Dels ## Contents [toc] ## What causes hard deletes? :::warning This is a simplication, see Qdel VS del for a slighly more complex explanation (Some details about the garbage system are still omitted, this guide is more about avoiding and locating hard-dels rather than how garbage collection works in Byond) ::: A hard-delete occurs when something is requested to be destroyed, but it doesn't properly remove all references to itself. This prevents Byond from automatically deleting the object, meaning we have to ask Byond to remove all references to it for us which is very expensive. Hard dels are extremely expensive and account for the majority of CPU usage of the server despite being fairly irregular. These things count as references and must be considered to prevent hard dels: - A variable holds a reference to that atom - An element of a list holds a reference to that atom - The atom has its 'tag' variable set - The item is on the map (qdel handles this by moving the item to nullspace, so don't worry about it) - Inside another atom's contents (Qdel *should* also handle this since it moves the item to nullspace) - Inside an atom's vis_contents - A local variable that performs any yielding action (sleep) - A mob has a key (This might be handled by qdel, not sure) - An image object attached to an atom ## Preventing hard dels To prevent a hard delete, simply remove the reference to the object upon deletion. ### Bad code example The following code is bad, as it will result in a hard delete should new_target be deleted. ```c= /datum/test //This variable holds a reference to an atom, //if that atom is deleted then this will result in a hard-del var/atom/target /datum/test/proc/store_target(atom/new_target) target = new_target ``` Keep in mind that this applies to lists too ```c= /datum/test //This variable holds a bunch of references to atoms, //if any atom is deleted then this will result in a hard-del var/list/atom/target = list() /datum/test/proc/store_target(atom/new_target) target += new_target ``` ### Using Destroy() :::warning Using this method is not recommended if the things aren't already storing references to each other, since it adds more references and links. ::: In some cases, the atom we are storing will have knowledge about the thing storing it. In that case, we can use the Destroy() proc to remove the references when either item is destroyed. Note that by both things having knowledge of the item, we have the potential for 2 hard-deletes now and have to account for them on both. ```c= //THE ATOM CAUSING THE HARD DEL /atom/hard_del var/datum/test/storer /atom/hard_del/Destroy() if(storer) //Remove the reference to ourself storer.target = null storer = null //THE DATUM STORING THE HARD DEL SOURCE /datum/test //This variable holds a reference to an atom, //if that atom is deleted then this will result in a hard-del var/atom/hard_del/target /datum/test/Destroy() if(target) //Remove the reference to ourself target.storer = null target = null /datum/test/proc/store_target(atom/hard_del/new_target) //If we had a target before, remove ourselfs from //its reference. if(target) target.storer = null //Set the new target target = new_target //Tell our new target that we are storing it if(target) target.storer = src ``` ### Using Signals A signal can perform some proc when a certain event occurs. We can use this to register a proc that removes any references when the target is deleted. This method is recommended as the target requires no knowledge of what is holding it, making it easier to reuse in other places of the code. Signals will create references, however the datum component system will handle these for the most part. ```c= /datum/test //The thing we are storing //Note that we don't have to specifically know what it is //and it doesn't have to know anything about us, making this //a good solution. var/atom/target /datum/test/proc/store_target(atom/new_target) //If we had a target already, unregister the delete signal //(We are no longer referencing it) if(target) UnregisterSignal(target, COMSIG_PARENT_QDELETING) //Set the reference to the new target target = new_target //If we have a target now, register the deletion signal if(target) RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/target_deleted) //Called when the signal is raised, removes the reference //preventing the hard delete. // Signals existing on target will be cleaned up by the component logic /datum/test/proc/target_deleted(datum/source, force) target = null ``` ### Using Weakrefs A weakref is a reference to an object that is weak, meaning that the weakref doesn't directly hold a reference to that object but to something that can get the object whenever needed. Weak references to atoms will not cause them to hard delete. ```c= /datum/test var/datum/weakref/target /datum/test/proc/store_target(atom/new_target) target = WEAKREF(new_target) ``` We can then access target by doing ```c var/atom/some_target = target?.Resolve() ``` ## Final Notes Be careful when using sleeps! Obviously, you don't necessarilly have to have this everywhere, since you might know for sure that something won't be deleted. :::warning Ammendment: Once an object is qdeleted, it will not immediately hard delete. There is a pretty substantial delay between calling qdel and then the hard-delete occuring. ::: ```c= //This can cause hard dels due to the sleep allowing //other code to run for 5 seconds while the reference to //some atom in the local variable A exists. /datum/test/proc/hard_del(atom/A) sleep(5000) ``` ### Qdel VS del :::info TL;DR Del is a byond thing that deletes something and removes all references to it, its CPU intensive; don't use it. Qdel is defined in space station 13 and calls Destroy() and some other stuff which should remove all references to the destroyed thing. Once all references to a thing are removed, Byond garbage collects it which is when the thing is deleted. Failure to remove all references upon qdel = hard-del. ::: `Qdel` is essentially a request to delete an object. We tell the object that it should be deleted and that object is reponsible for removing all references to itself which will lead to its deletion. When `qdel` is called on a target, that target is marked as being deleted and Destroy() is called (Which *should* remove all references to that datum). (Some other stuff also happens such as sending deletion signals and queuing in the garbage system, but you shouldn't have to worry about this) The item is not instantly removed from memory and deleted from the world, it is only marked for garbage collection. Once all references to something have been removed, it will be collected by the Byond internal garbage collector at some point. :::danger Circular references will prevent Byond's GC from running. If A references B and B references A, but they are never used anywhere else, they will not be deleted. ::: `Destroy()` is where the process of removing references is usually handled as once all references are removed, it is deleted. The Space Station 13 garbage subsystem checks an item to see if it has been destroyed. If the item hasn't been destroyed, then a reference to it must still exist which means we need to call `del()`. `del()` is a Byond proc that removes a datum and all references to it. This process is very expensive, as all references need to be located. All references to that thing will be set to null. There is quite a long delay between calling qdel() and a hard-delete occuring, since we have to allow for enough time to be certain that Byond's garbage collection has triggered already. :::info **From OpenDream's Wiki:** There are two types of deletions in BYOND: hard and soft. Soft deletion occurs automatically when a datum's reference count is zero. Hard deletions occur when del() is explicitly called with a datum that has a non-zero ref count. BYOND's garbage collector has to spend a large chunk of time finding and clearing the remaining refs. :::