# Automatic Memory Management Draft (Mothblocks) :::warning This is a draft. Please give your feedbacks on both the concept and the APIs. ::: ## Problem 1. Hard deletes are very common. While C&D makes lots of cases easier to find, we will never have 100% test coverage over the codebase. 2. `Destroy()` code is completely separated, making it easier for maintainers to miss code that will hard delete. 3. It is very easy to forget what needs to be undone, like deleting variables or removing yourself from lists. ## Prior Art Let's compare to a language like C++ (sorry...sorry...). For the purposes of this proposal, we will look at two ways memory management can be done in C++. One is through `new`/`delete`. That might look like this: ```cpp class Dog { Collar* collar; }; // Constructor (Initialize()) Dog::Dog() { collar = new Collar(); } // Destructor (Destroy()) Dog::~Dog() { delete collar; } ``` This is basically identical to how we work in DM today. However, more modern C++ discourages `new`/`delete`, partially for the problem statements mentioned above. This code today might look like: ```cpp! class Dog { std::unique_ptr<Collar> collar; } // Initialize Dog::Dog() { collar = std::make_unique<Collar>(); // Don't worry too much about `make_unique`. Pretend it's "new" } ``` `std::unique_ptr` will perform "automatic memory management". This means all we have to do is create it. From there, we can use it however we like, and when `Dog` destroys, `collar` will automatically destroy itself as well. ## Automatic Memory Management Proposal This document proposes that we work to limit how much manual `Destroy()` implementations are needed by taking a similar approach. Let's check the most common reasons one might want to `Destroy()`. Some of these proposals are more concerning than others, and not all need to be implemented. If all these proposals were theoretically implemented, we could eliminate the vast majority of `Destroy()` implementations. ### `qdel(owned_variable)`/`QDEL_NULL(owned_variable)` ```dm /mob/living/dog var/obj/collar /mob/living/dog/Destroy() QDEL_NULL(collar) ``` While DM does not let us create something like `unique_ptr`, we can create an interface where you mark variables as being "owned". ```dm /mob/living/dog OWNED_VAR(obj/collar/collar) ``` Then, when the dog is qdeleted, `collar` will be as well, and cleaned up. This can be specialized for lists to delete everything inside the list. This would also work for something like: ```dm /mob/living/dog OWNED_VAR(obj/collar/collar) = new ``` <details> <summary>Implementation</summary> ```dm #define OWNED_VAR(name) \ owned_vars() { var/list/path = splittext(#name, "/"); return ..() + path[path.len]; } \ var/name ``` `qdel` would then look through all `owned_vars()` (cached by type path) and perform `QDEL_NULL`, or a similar one to specialize over lists. </details> ### `referenced_variable = null` ```dm /obj/collar var/mob/dog ``` While this can often be a weakref, weakrefs have unfortunate semantics that make them sometimes reasonable to avoid. Here, we could perform a similar pattern: ```dm /obj/collar BORROWED_VAR(mob/dog) ``` This would perform the same magic to have `dog = null` when qdel'd. ### `GLOB.list_of_mes -= src` ```dm GLOBAL_LIST_EMPTY_TYPED(dogs, /mob/living/dog) /mob/living/dog/Initialize() GLOB.dogs += src /mob/living/dog/Destroy() GLOB.dogs -= src ``` This pattern is very common and should be made consistent across the codebase. Goonstation has `by_type`, which we can use a similar version of. ```dm /mob/living/dog var/name // etc TRACK_BY_TYPE() // Later... for (var/dog in by_type(/mob/living/dog)) ``` The `by_type` proc would error if given a type that is not being tracked by `TRACK_BY_TYPE()`. All instances of the type would automatically be added to a global list, and removed on Destroy. Note that this specific form can only be added with 515 adding `__TYPE__`. ### `STOP_PROCESSING` ```dm /mob/living/dog/Initialize() START_PROCESSING(SSobj, src) /mob/living/dog/Destroy() STOP_PROCESSING(SSobj, src) ``` This could be resolved if `START_PROCESSING` registered a qdel signal to stop processing. <details> <summary>Implementation details</summary> - This would need to be registered outside of `src` so that it can still register qdel signals on its own. - `START_PROCESSING` is a pretty beefy macro, and it's not clear how hot it is. Look into if we can make it a normal proc. </details>