Managing memory in general with C++ has been partially covered in C++ tips, but Cocos2d comes with its own memory management system that is all modders have to learn at some point. On top of this, Geode adds two classes,Documentation Index
Fetch the complete documentation index at: https://mintlify.com/geode-sdk/docs/llms.txt
Use this file to discover all available pages before exploring further.
Ref and WeakRef, which Geode devs will tell you to use but not how to use them. This tutorial aims to remedy that, explaining how memory management in Cocos2d works in detail.
Memory management
Cocos2d’s memory management features are available to all classes that inherit fromCCObject, as it is the base class that implements the required features. Cocos2d’s memory management works by ref counting - in other words, it just stores how many references exist to an object, and once the ref count reaches 0, the object’s memory is freed.
Unfortunately, due to how C++ works, the ref counting is not directly automatic - it doesn’t know where the object is actually used, only the ref count stored inside it. In practice, classes like CCNode always handle retaining and releasing their children, so you usually don’t have to deal with memory management when building nodes. However, this does have consequences when it comes to class members, as we will see later.
The easiest way to concretize this is to look at manual memory management in Cocos2d, which works by using the retain and release functions. In their simplicity, retain increments the object’s ref count by one, and release decrements it by one.
retain and release like the following: retain tells Cocos2d “Hey, I’m still using this object, please don’t free it!”, and release tells it “Okay, I don’t need this object anymore!”
In practice, if you have to do manual memory management, you likely won’t be using the retain and release functions directly and instead use the CC_SAFE_RETAIN and CC_SAFE_RELEASE macros:
nullptr first.
Classes
Remembering to manage every object you have ever created is a real problem, though. Luckily, most Cocos2d classes callretain for you.
release on the array. However, Cocos2d has an automatic garbage collector that handles situations like this. You may have seen that all create functions in nodes call a function called autorelease:
retain are made to this object, it should be freed automatically later on. This means that the following code:
retain calls to array have been made, and frees it.
However, the array is not freed immediately; instead, it is usually freed at earliest on the next frame. This means that you can still use the array to do things:
Class members
autorelease is very neat when you just want to quickly create an array to pass into a function, or something similar. However, it causes a bit of a headache when dealing with class members:
getFirst after a while. The reason for this is that m_array has been freed - no calls to retain were ever made to it, and since Cocos2d ref counting doesn’t actually have any knowledge of what pointers to an object exist, Cocos2d concludes that the array must not be in use.
The solution, then, is to call retain on the array:
m_array is actually being used, and won’t free it. However, now we have caused a memory leak: when MyNode is destroyed, the array’s ref count stays at 1, meaning it never gets freed. To fix this, we need to add a call to release in MyNode’s destructor:
CCObject members, and you may find yourself reading old code that uses it, or even using it yourself. However, it comes with a problem: this gets really complex to work with really fast. Imagine that m_array is not just a member that is created once at the start and freed at the end, but instead may be nullptr, or may be removed later, or may even be swapped with other arrays. Dealing with ref counting manually in that case is a real pain:
Ref.
Ref
Ref is a smart pointer for CCObjects - in essence, it’s just a class that retains the object it points to, and releases it when Ref goes out of scope. In other words, it’s a RAII alternative to manual retain and release calls. Using it, we can refactor our previous code to just this:
retain and release disappeared from the code - Ref handles all of them for you. This makes writing and reasoning about code much simpler - you can be assured that as long as you have initially made a Ref to a valid object, it’s always going to stay valid, and the memory will be freed appropriately when you actually no longer use it.
Ref is also relatively cheap - if you are unsure whether the lifetime of a pointer you have extends to your usage, just store the pointer in a Ref to stay safe.
In general, you should at least stick Ref to all your class members, unless you can be certain about the lifetime of the object otherwise.
WeakRef
Ref does have a problem, however: since it increments the ref count, the object being pointed to will only be freed once the Ref goes out of scope. However, sometimes this won’t happen at a desirable time: for example, some mod might have a map like the following:
CCNode pointer it has is no longer valid, so if the scene is changed and the nodes are freed while they are still in REGISTERED_NODES, the next call to doSomethingWithNodes will cause a crash trying to access already freed memory.
Unfortunately, C++ has no way to know if a raw pointer is valid or not. Your first instinct here might be to make the CCNode a Ref:
REGISTERED_NODES only contains valid pointers to CCNodes. However, now we have a memory leak: if the user switches scenes, the only reference left to the CCNode will be the one in REGISTERED_NODES, which is probably undesirable, since we’d want the node to be removed from REGISTERED_NODES too when the node is no longer visible.
A primitive solution to fixing this would be to check the node’s ref count, and if it’s 1 then we know only REGISTERED_NODES has a reference to it:
WeakRef, which is like Ref but it doesn’t change the ref count:
Ref and WeakRef is that Ref allows you to access the pointed object directly since it’s guaranteed to be valid. However, WeakRef has no such guarantees since it doesn’t impact the ref count, so you need to first lock it to see if the object is still valid:
lock returns a Ref, so you have guaranteed safe access to the object for as long as you have the Ref. As WeakRef does not increment the reference count, if something frees the object beforehand, lock will return a null Ref.
Now, WeakRef still won’t automatically remove itself from your maps, so you do still have to manually clear invalid pointers: