2020-04-06 14:10:54 +00:00
|
|
|
#pragma once
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "tarray.h"
|
|
|
|
class DObject;
|
|
|
|
class FSerializer;
|
|
|
|
|
|
|
|
enum EObjectFlags
|
|
|
|
{
|
|
|
|
// GC flags
|
|
|
|
OF_White0 = 1 << 0, // Object is white (type 0)
|
|
|
|
OF_White1 = 1 << 1, // Object is white (type 1)
|
|
|
|
OF_Black = 1 << 2, // Object is black
|
|
|
|
OF_Fixed = 1 << 3, // Object is fixed (should not be collected)
|
|
|
|
OF_Rooted = 1 << 4, // Object is soft-rooted
|
|
|
|
OF_EuthanizeMe = 1 << 5, // Object wants to die
|
|
|
|
OF_Cleanup = 1 << 6, // Object is now being deleted by the collector
|
|
|
|
OF_YesReallyDelete = 1 << 7, // Object is being deleted outside the collector, and this is okay, so don't print a warning
|
|
|
|
|
|
|
|
OF_WhiteBits = OF_White0 | OF_White1,
|
|
|
|
OF_MarkBits = OF_WhiteBits | OF_Black,
|
|
|
|
|
|
|
|
// Other flags
|
|
|
|
OF_JustSpawned = 1 << 8, // Thinker was spawned this tic
|
|
|
|
OF_SerialSuccess = 1 << 9, // For debugging Serialize() calls
|
|
|
|
OF_Sentinel = 1 << 10, // Object is serving as the sentinel in a ring list
|
|
|
|
OF_Transient = 1 << 11, // Object should not be archived (references to it will be nulled on disk)
|
|
|
|
OF_Spawned = 1 << 12, // Thinker was spawned at all (some thinkers get deleted before spawning)
|
|
|
|
OF_Released = 1 << 13, // Object was released from the GC system and should not be processed by GC function
|
|
|
|
};
|
|
|
|
|
|
|
|
template<class T> class TObjPtr;
|
|
|
|
|
|
|
|
namespace GC
|
|
|
|
{
|
|
|
|
enum EGCState
|
|
|
|
{
|
|
|
|
GCS_Pause,
|
|
|
|
GCS_Propagate,
|
|
|
|
GCS_Sweep,
|
2022-11-12 09:45:39 +00:00
|
|
|
GCS_Destroy,
|
|
|
|
GCS_Done,
|
|
|
|
|
|
|
|
GCS_COUNT
|
2020-04-06 14:10:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Number of bytes currently allocated through M_Malloc/M_Realloc.
|
|
|
|
extern size_t AllocBytes;
|
|
|
|
|
2022-11-12 09:45:39 +00:00
|
|
|
// Number of bytes allocated since last collection step.
|
|
|
|
extern size_t RunningAllocBytes;
|
|
|
|
|
|
|
|
// Number of bytes freed since last collection step.
|
|
|
|
extern size_t RunningDeallocBytes;
|
|
|
|
|
2020-04-06 14:10:54 +00:00
|
|
|
// Amount of memory to allocate before triggering a collection.
|
|
|
|
extern size_t Threshold;
|
|
|
|
|
|
|
|
// List of gray objects.
|
|
|
|
extern DObject *Gray;
|
|
|
|
|
|
|
|
// List of every object.
|
|
|
|
extern DObject *Root;
|
|
|
|
|
|
|
|
// Current white value for potentially-live objects.
|
|
|
|
extern uint32_t CurrentWhite;
|
|
|
|
|
|
|
|
// Current collector state.
|
|
|
|
extern EGCState State;
|
|
|
|
|
|
|
|
// Position of GC sweep in the list of objects.
|
|
|
|
extern DObject **SweepPos;
|
|
|
|
|
|
|
|
// Size of GC pause.
|
|
|
|
extern int Pause;
|
|
|
|
|
|
|
|
// Size of GC steps.
|
|
|
|
extern int StepMul;
|
|
|
|
|
|
|
|
// Is this the final collection just before exit?
|
|
|
|
extern bool FinalGC;
|
|
|
|
|
|
|
|
// Current white value for known-dead objects.
|
|
|
|
static inline uint32_t OtherWhite()
|
|
|
|
{
|
|
|
|
return CurrentWhite ^ OF_WhiteBits;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Does one collection step.
|
|
|
|
void Step();
|
|
|
|
|
|
|
|
// Does a complete collection.
|
|
|
|
void FullGC();
|
|
|
|
|
|
|
|
// Handles the grunt work for a write barrier.
|
|
|
|
void Barrier(DObject *pointing, DObject *pointed);
|
|
|
|
|
|
|
|
// Handles a write barrier.
|
|
|
|
static inline void WriteBarrier(DObject *pointing, DObject *pointed);
|
|
|
|
|
|
|
|
// Handles a write barrier for a pointer that isn't inside an object.
|
|
|
|
static inline void WriteBarrier(DObject *pointed);
|
|
|
|
|
|
|
|
// Handles a read barrier.
|
|
|
|
template<class T> inline T *ReadBarrier(T *&obj)
|
|
|
|
{
|
|
|
|
if (obj == NULL || !(obj->ObjectFlags & OF_EuthanizeMe))
|
|
|
|
{
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
return obj = NULL;
|
|
|
|
}
|
|
|
|
|
2022-08-10 21:45:29 +00:00
|
|
|
// Handles a read barrier for a const pointer. This does not alter the source data, but only returns NULL if the object is destroyed.
|
|
|
|
template<class T> inline T* ReadBarrier(const T*& obj)
|
|
|
|
{
|
|
|
|
if (obj == NULL || !(obj->ObjectFlags & OF_EuthanizeMe))
|
|
|
|
{
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-04-06 14:10:54 +00:00
|
|
|
// Check if it's time to collect, and do a collection step if it is.
|
2022-11-12 09:45:39 +00:00
|
|
|
void CheckGC();
|
2020-04-06 14:10:54 +00:00
|
|
|
|
|
|
|
// Forces a collection to start now.
|
|
|
|
static inline void StartCollection()
|
|
|
|
{
|
|
|
|
Threshold = AllocBytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Marks a white object gray. If the object wants to die, the pointer
|
|
|
|
// is NULLed instead.
|
|
|
|
void Mark(DObject **obj);
|
|
|
|
|
|
|
|
// Marks an array of objects.
|
|
|
|
void MarkArray(DObject **objs, size_t count);
|
|
|
|
|
|
|
|
// For cleanup
|
|
|
|
void DelSoftRootHead();
|
|
|
|
|
|
|
|
// Soft-roots an object.
|
|
|
|
void AddSoftRoot(DObject *obj);
|
|
|
|
|
|
|
|
// Unroots an object.
|
|
|
|
void DelSoftRoot(DObject *obj);
|
|
|
|
|
|
|
|
template<class T> void Mark(T *&obj)
|
|
|
|
{
|
|
|
|
union
|
|
|
|
{
|
|
|
|
T *t;
|
|
|
|
DObject *o;
|
|
|
|
};
|
|
|
|
o = obj;
|
|
|
|
Mark(&o);
|
|
|
|
obj = t;
|
|
|
|
}
|
|
|
|
template<class T> void Mark(TObjPtr<T> &obj);
|
|
|
|
|
|
|
|
template<class T> void MarkArray(T **obj, size_t count)
|
|
|
|
{
|
|
|
|
MarkArray((DObject **)(obj), count);
|
|
|
|
}
|
2021-12-05 13:00:22 +00:00
|
|
|
template<class T> void MarkArray(TObjPtr<T>* obj, size_t count)
|
|
|
|
{
|
|
|
|
MarkArray((DObject**)(obj), count);
|
|
|
|
}
|
2020-04-06 14:10:54 +00:00
|
|
|
template<class T> void MarkArray(TArray<T> &arr)
|
|
|
|
{
|
|
|
|
MarkArray(&arr[0], arr.Size());
|
|
|
|
}
|
2020-04-06 22:35:53 +00:00
|
|
|
|
|
|
|
using GCMarkerFunc = void(*)();
|
|
|
|
void AddMarkerFunc(GCMarkerFunc func);
|
|
|
|
|
2022-11-12 09:45:39 +00:00
|
|
|
// Report an allocation to the GC
|
|
|
|
static inline void ReportAlloc(size_t alloc)
|
|
|
|
{
|
|
|
|
AllocBytes += alloc;
|
|
|
|
RunningAllocBytes += alloc;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Report a deallocation to the GC
|
|
|
|
static inline void ReportDealloc(size_t dealloc)
|
|
|
|
{
|
|
|
|
AllocBytes -= dealloc;
|
|
|
|
RunningDeallocBytes += dealloc;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Report a reallocation to the GC
|
|
|
|
static inline void ReportRealloc(size_t oldsize, size_t newsize)
|
|
|
|
{
|
|
|
|
if (oldsize < newsize)
|
|
|
|
{
|
|
|
|
ReportAlloc(newsize - oldsize);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ReportDealloc(oldsize - newsize);
|
|
|
|
}
|
|
|
|
}
|
2020-04-06 14:10:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// A template class to help with handling read barriers. It does not
|
|
|
|
// handle write barriers, because those can be handled more efficiently
|
|
|
|
// with knowledge of the object that holds the pointer.
|
|
|
|
template<class T>
|
|
|
|
class TObjPtr
|
|
|
|
{
|
|
|
|
union
|
|
|
|
{
|
|
|
|
T pp;
|
|
|
|
DObject *o;
|
|
|
|
};
|
|
|
|
public:
|
2022-01-11 16:20:48 +00:00
|
|
|
|
2022-06-06 13:28:41 +00:00
|
|
|
constexpr TObjPtr<T>& operator=(T q) noexcept
|
2020-04-06 14:10:54 +00:00
|
|
|
{
|
|
|
|
pp = q;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2022-06-06 13:28:41 +00:00
|
|
|
constexpr TObjPtr<T>& operator=(std::nullptr_t nul) noexcept
|
2020-04-06 14:10:54 +00:00
|
|
|
{
|
|
|
|
o = nullptr;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// To allow NULL, too.
|
2022-06-06 13:28:41 +00:00
|
|
|
TObjPtr<T>& operator=(const int val) noexcept
|
2020-04-06 14:10:54 +00:00
|
|
|
{
|
|
|
|
assert(val == 0);
|
|
|
|
o = nullptr;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// To allow NULL, too. In Clang NULL is a long.
|
2022-06-06 13:28:41 +00:00
|
|
|
TObjPtr<T>& operator=(const long val) noexcept
|
2020-04-06 14:10:54 +00:00
|
|
|
{
|
|
|
|
assert(val == 0);
|
|
|
|
o = nullptr;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2022-06-06 13:28:41 +00:00
|
|
|
constexpr T Get() noexcept
|
2020-04-06 14:10:54 +00:00
|
|
|
{
|
|
|
|
return GC::ReadBarrier(pp);
|
|
|
|
}
|
|
|
|
|
2022-08-10 21:45:29 +00:00
|
|
|
constexpr T Get() const noexcept
|
|
|
|
{
|
|
|
|
auto ppp = pp;
|
|
|
|
return GC::ReadBarrier(ppp);
|
|
|
|
}
|
|
|
|
|
|
|
|
constexpr T ForceGet() const noexcept //for situations where the read barrier needs to be skipped.
|
2020-04-06 21:32:29 +00:00
|
|
|
{
|
|
|
|
return pp;
|
|
|
|
}
|
|
|
|
|
2022-06-06 13:28:41 +00:00
|
|
|
constexpr operator T() noexcept
|
2020-04-06 14:10:54 +00:00
|
|
|
{
|
|
|
|
return GC::ReadBarrier(pp);
|
|
|
|
}
|
2022-06-06 13:28:41 +00:00
|
|
|
constexpr T &operator*() noexcept
|
2020-04-06 14:10:54 +00:00
|
|
|
{
|
|
|
|
T q = GC::ReadBarrier(pp);
|
|
|
|
assert(q != NULL);
|
|
|
|
return *q;
|
|
|
|
}
|
2022-06-06 13:28:41 +00:00
|
|
|
constexpr T operator->() noexcept
|
2020-04-06 14:10:54 +00:00
|
|
|
{
|
|
|
|
return GC::ReadBarrier(pp);
|
|
|
|
}
|
2022-06-06 13:28:41 +00:00
|
|
|
constexpr bool operator!=(T u) noexcept
|
2020-04-06 14:10:54 +00:00
|
|
|
{
|
|
|
|
return GC::ReadBarrier(o) != u;
|
|
|
|
}
|
2022-06-06 13:28:41 +00:00
|
|
|
constexpr bool operator==(T u) noexcept
|
2020-04-06 14:10:54 +00:00
|
|
|
{
|
|
|
|
return GC::ReadBarrier(o) == u;
|
|
|
|
}
|
|
|
|
|
2023-11-07 17:35:11 +00:00
|
|
|
constexpr bool operator==(TObjPtr<T> u) noexcept
|
|
|
|
{
|
|
|
|
return ForceGet() == u.ForceGet();
|
|
|
|
}
|
|
|
|
|
2020-04-06 14:10:54 +00:00
|
|
|
template<class U> friend inline void GC::Mark(TObjPtr<U> &obj);
|
|
|
|
template<class U> friend FSerializer &Serialize(FSerializer &arc, const char *key, TObjPtr<U> &value, TObjPtr<U> *);
|
|
|
|
template<class U> friend FSerializer &Serialize(FSerializer &arc, const char *key, TObjPtr<U> &value, U *);
|
|
|
|
|
|
|
|
friend class DObject;
|
|
|
|
};
|
|
|
|
|
2022-06-06 13:28:41 +00:00
|
|
|
// This is only needed because some parts of GCC do not treat a class with any constructor as trivial.
|
|
|
|
// TObjPtr needs to be fully trivial, though - some parts in the engine depend on it.
|
|
|
|
template<class T>
|
|
|
|
constexpr TObjPtr<T> MakeObjPtr(T t) noexcept
|
|
|
|
{
|
|
|
|
// since this exists to replace the constructor we cannot initialize in the declaration as this would require the constructor we want to avoid.
|
|
|
|
TObjPtr<T> tt;
|
|
|
|
tt = t;
|
|
|
|
return tt;
|
|
|
|
}
|
|
|
|
|
2020-04-06 14:10:54 +00:00
|
|
|
// Use barrier_cast instead of static_cast when you need to cast
|
|
|
|
// the contents of a TObjPtr to a related type.
|
|
|
|
template<class T,class U> inline T barrier_cast(TObjPtr<U> &o)
|
|
|
|
{
|
|
|
|
return static_cast<T>(static_cast<U>(o));
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace GC
|
|
|
|
{
|
|
|
|
template<class T> inline void Mark(TObjPtr<T> &obj)
|
|
|
|
{
|
|
|
|
GC::Mark(&obj.o);
|
|
|
|
}
|
|
|
|
}
|