Skip to content

Commit 921d69e

Browse files
committed
feat: Implement GCObject for native c++ struct
1 parent eede9af commit 921d69e

File tree

8 files changed

+192
-162
lines changed

8 files changed

+192
-162
lines changed

Test/GCTest.hpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ class TestObject : public sh::core::SObject
3030
PROPERTY(objectArray)
3131
std::array<TestObject*, 3> objectArray{ nullptr, nullptr, nullptr };
3232

33+
struct TestStruct : sh::core::GCObject
34+
{
35+
TestObject* tptr = nullptr;
36+
37+
void PushReferenceObjects(sh::core::GarbageCollection& gc) override
38+
{
39+
gc.PushReferenceObject(tptr);
40+
}
41+
} testStruct;
42+
3343
TestObject(int id = 0) : id(id) {}
3444
~TestObject() override {
3545
// 소멸 시 id를 0으로 만들어 소멸되었음을 외부에서 확인할 수 있도록 함
@@ -278,4 +288,55 @@ TEST_F(GCTest, ShouldDestroyChildrenBeforeParent)
278288
// 모든 객체가 소멸되었는지 확인
279289
ASSERT_EQ(gc->GetObjectCount(), 0);
280290
ASSERT_FALSE(isWorldAlive);
291+
}
292+
293+
TEST_F(GCTest, GCObjectTest)
294+
{
295+
TestObject* root = sh::core::SObject::Create<TestObject>(100);
296+
TestObject* obj1 = sh::core::SObject::Create<TestObject>(50);
297+
TestObject* obj2 = sh::core::SObject::Create<TestObject>(25);
298+
obj1->child = obj2;
299+
300+
gc->SetRootSet(root);
301+
root->testStruct.tptr = obj1;
302+
gc->Collect();
303+
// testStruct는 GCObject이고 tptr을 추적하게 설정해놨으므로 obj는 살아있어야 한다
304+
EXPECT_FALSE(obj1->IsPendingKill());
305+
// obj2는 obj의 자식이므로 마찬가지로 생존 해야 한다
306+
EXPECT_FALSE(obj2->IsPendingKill());
307+
gc->RemoveRootSet(root);
308+
gc->Collect();
309+
310+
EXPECT_TRUE(root->IsPendingKill());
311+
// root가 완전히 소멸하기전 까지는 살아있음
312+
EXPECT_FALSE(obj1->IsPendingKill());
313+
EXPECT_FALSE(obj2->IsPendingKill());
314+
gc->DestroyPendingKillObjs();
315+
gc->Collect();
316+
// testStruct가 root가 소멸하면서 사라졌으므로 obj를 추적할 수 없으므로 삭제 돼야함
317+
EXPECT_TRUE(obj1->IsPendingKill());
318+
EXPECT_TRUE(obj2->IsPendingKill());
319+
gc->DestroyPendingKillObjs();
320+
321+
EXPECT_EQ(gc->GetObjectCount(), 0);
322+
}
323+
324+
TEST_F(GCTest, GCObjectDanglingPointerTest)
325+
{
326+
TestObject* root = sh::core::SObject::Create<TestObject>(100);
327+
TestObject* obj = sh::core::SObject::Create<TestObject>(50);
328+
gc->SetRootSet(root);
329+
root->testStruct.tptr = obj;
330+
331+
obj->Destroy();
332+
gc->Collect();
333+
334+
// tptr은 GCObject에서 추적하던 대상이고 가르키던 obj가 삭제 됐으므로 nullptr로 바뀌어야 함
335+
EXPECT_TRUE(root->testStruct.tptr == nullptr);
336+
337+
// 정리
338+
gc->RemoveRootSet(root);
339+
gc->Collect();
340+
gc->DestroyPendingKillObjs();
341+
EXPECT_EQ(gc->GetObjectCount(), 0);
281342
}

include/Core/GCObject.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#pragma once
2+
#include "Export.h"
3+
4+
namespace sh::core
5+
{
6+
class GarbageCollection;
7+
class GCObject
8+
{
9+
public:
10+
SH_CORE_API GCObject();
11+
SH_CORE_API virtual ~GCObject();
12+
SH_CORE_API virtual void PushReferenceObjects(GarbageCollection& gc) {};
13+
};
14+
}//namespace

include/Core/GarbageCollection.h

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ namespace sh::core
2929
template<typename T, typename IsSObject = std::enable_if_t<std::is_base_of_v<SObject, T>>>
3030
class SObjWeakPtr;
3131

32+
class GCObject;
33+
3234
/// @brief 마크 앤 스윕기반 가비지 컬렉터
3335
class GarbageCollection : public Singleton<GarbageCollection>
3436
{
@@ -45,17 +47,12 @@ namespace sh::core
4547
/// @brief 루트셋으로 지정하는 함수. 루트셋 객체는 참조하고 있는 객체가 없어도 메모리에서 유지된다.
4648
/// @param obj 루트셋으로 지정할 SObject 포인터
4749
SH_CORE_API void SetRootSet(SObject* obj);
48-
SH_CORE_API auto GetRootSet() const -> const std::vector<SObject*>&;
49-
SH_CORE_API auto GetRootSetCount() const -> uint64_t;
50-
/// @brief 해당 프레임마다 가비지 컬렉터를 수행한다.
51-
/// @param tick 목표 프레임
52-
SH_CORE_API void SetUpdateTick(uint32_t tick);
53-
SH_CORE_API auto GetUpdateTick() const -> uint32_t;
54-
SH_CORE_API auto GetCurrentTick() const -> uint32_t;
55-
5650
/// @brief 루트셋에서 해당 객체를 제외하는 함수.
5751
/// @param obj SObject 포인터
5852
SH_CORE_API void RemoveRootSet(const SObject* obj);
53+
/// @brief 해당 프레임마다 가비지 컬렉터를 수행한다.
54+
/// @param tick 목표 프레임
55+
SH_CORE_API void SetUpdateTick(uint32_t tick);
5956

6057
/// @brief GC를 갱신하며 지정된 시간이 흐르면 Collect()와 DestroyPendingKillObjs()가 호출 된다.
6158
SH_CORE_API void Update();
@@ -79,12 +76,19 @@ namespace sh::core
7976
/// @brief 외부에서는 TrackedContainer의 fn함수 내에서만 사용해야 한다.
8077
SH_CORE_API void MarkBFS(std::queue<SObject*>& bfs);
8178

79+
SH_CORE_API void AddGCObject(GCObject& obj);
80+
SH_CORE_API void RemoveGCObject(GCObject& obj);
81+
8282
SH_CORE_API void AddContainerTracking(const TrackedContainer& container);
8383
SH_CORE_API void RemoveContainerTracking(void* containerPtr);
8484

85+
SH_CORE_API auto GetRootSet() const -> const std::vector<SObject*>& { return rootSets; }
86+
SH_CORE_API auto GetRootSetCount() const -> uint64_t { return rootSets.size(); }
87+
SH_CORE_API auto GetTrackedContainerCount() const -> std::size_t { return trackingContainers.size(); }
88+
SH_CORE_API auto GetUpdateTick() const -> uint32_t { return updatePeriodTick; }
89+
SH_CORE_API auto GetCurrentTick() const -> uint32_t { return tick; }
8590
/// @brief 이전에 GC를 수행하는데 걸린 시간(ms)을 반환 하는 함수
8691
SH_CORE_API auto GetElapsedTime() -> uint32_t { return elapseTime; }
87-
SH_CORE_API auto GetTrackedContainerCount() const -> std::size_t { return trackingContainers.size(); }
8892

8993
template<typename T>
9094
void AddPointerTracking(SObjWeakPtr<T>& ptr)
@@ -107,10 +111,28 @@ namespace sh::core
107111
if (it != trackingContainers.end())
108112
trackingContainers.erase(it);
109113
}
114+
115+
template<typename SObjectT, typename Check = std::enable_if_t<std::is_base_of_v<SObject, SObjectT>>>
116+
void PushReferenceObject(SObjectT*& sobjPtr)
117+
{
118+
if (sobjPtr == nullptr)
119+
return;
120+
121+
if (sobjPtr->IsPendingKill())
122+
{
123+
sobjPtr = nullptr;
124+
return;
125+
}
126+
127+
if constexpr (std::is_const_v<SObjectT>)
128+
refObjs.push_back(const_cast<std::remove_const_t<SObjectT>&>(*sobjPtr));
129+
else
130+
refObjs.push_back(*sobjPtr);
131+
}
110132
private:
111133
SH_CORE_API GarbageCollection();
112-
/// @brief ref가 pendingkill상태면 nullptr로 바꾸고 아니라면 bfs큐에 넣는 함수
113-
void TraceRef(SObject*& ref, std::queue<SObject*>& bfs);
134+
135+
void CollectReferenceObjs();
114136
void Mark(std::size_t start, std::size_t end);
115137
void MarkProperties(SObject* obj, std::queue<SObject*>& bfs);
116138
void MarkWithMultiThread();
@@ -125,6 +147,9 @@ namespace sh::core
125147
std::unordered_map<SObject*, std::size_t> rootSetIdx;
126148
std::vector<SObject*> rootSets;
127149
std::vector<SObject*> pendingKillObjs;
150+
std::unordered_map<GCObject*, std::size_t> gcObjIdx;
151+
std::vector<std::reference_wrapper<GCObject>> gcObjs;
152+
std::vector<std::reference_wrapper<SObject>> refObjs;
128153

129154
std::unordered_map<void*, TrackedContainer> trackingContainers; // key = 컨테이너 포인터
130155

@@ -135,7 +160,7 @@ namespace sh::core
135160
uint32_t elapseTime = 0;
136161
uint32_t tick = 0;
137162
uint32_t updatePeriodTick = 1000;
138-
bool bContainerIteratorErased = false;
163+
139164
bool bPendingKill = false;
140165
};
141166
}//namespace

include/Core/SContainer.hpp

Lines changed: 9 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22
#include "SObject.h"
3+
#include "GCObject.h"
34
#include "GarbageCollection.h"
45
#include "Reflection/TypeTraits.hpp"
56

@@ -22,65 +23,13 @@ namespace sh::core
2223
/// @brief [주의] 절대 std::vector의 다형성 용도로 사용하면 안 된다.
2324
/// @tparam SObject* 타입
2425
template<typename T, typename = std::enable_if_t<std::is_convertible_v<T, const SObject*>>>
25-
class SVector : public std::vector<T>
26+
class SVector : public std::vector<T>, public GCObject
2627
{
2728
public:
28-
SVector() :
29-
std::vector<T>()
29+
void PushReferenceObjects(GarbageCollection& gc) override
3030
{
31-
AddToGC();
32-
}
33-
template<class... Args>
34-
SVector(Args&&... args) :
35-
std::vector<T>(std::forward<Args>(args)...)
36-
{
37-
AddToGC();
38-
}
39-
SVector(const SVector& other) :
40-
std::vector<T>(other)
41-
{
42-
AddToGC();
43-
}
44-
SVector(SVector&& other) noexcept :
45-
std::vector<T>(std::move(other))
46-
{
47-
AddToGC();
48-
}
49-
~SVector()
50-
{
51-
core::GarbageCollection::GetInstance()->RemoveContainerTracking(this);
52-
}
53-
auto operator=(const SVector& other) -> SVector&
54-
{
55-
std::vector<T>::operator=(other);
56-
return *this;
57-
}
58-
auto operator=(SVector&& other) noexcept -> SVector&
59-
{
60-
std::vector<T>::operator=(std::move(other));
61-
return *this;
62-
}
63-
private:
64-
void AddToGC()
65-
{
66-
core::GarbageCollection::TrackedContainer container{};
67-
container.ptr = this;
68-
container.markFn =
69-
[this](core::GarbageCollection& gc)
70-
{
71-
std::queue<SObject*> bfs;
72-
73-
for (auto& elem : *this)
74-
{
75-
const SObject* obj = static_cast<const SObject*>(elem);
76-
if (obj == nullptr) continue;
77-
if (obj->IsPendingKill()) { elem = nullptr; continue; }
78-
79-
bfs.push(const_cast<SObject*>(obj));
80-
}
81-
gc.MarkBFS(bfs);
82-
};
83-
core::GarbageCollection::GetInstance()->AddContainerTracking(container);
31+
for (auto& elem : *this)
32+
gc.PushReferenceObject(elem);
8433
}
8534
};
8635
/// @brief 쓰레기 수집을 지원하는 std::array와 동일한 역할을 하는 컨테이너.
@@ -89,64 +38,13 @@ namespace sh::core
8938
/// @tparam T 타입
9039
/// @tparam size 배열 사이즈
9140
template<typename T, std::size_t size, typename = std::enable_if_t<std::is_convertible_v<T, const SObject*>>>
92-
class SArray : public std::array<T, size>
41+
class SArray : public std::array<T, size>, public GCObject
9342
{
9443
public:
95-
SArray()
44+
void PushReferenceObjects(GarbageCollection& gc) override
9645
{
97-
AddToGC();
98-
}
99-
template<class... Args>
100-
SArray(Args&&... args) :
101-
std::array<T, size>(std::forward<Args>(args)...)
102-
{
103-
AddToGC();
104-
}
105-
SArray(const SArray& other) :
106-
std::array<T, size>(other)
107-
{
108-
AddToGC();
109-
}
110-
SArray(SArray&& other) noexcept :
111-
std::array<T, size>(std::move(other))
112-
{
113-
AddToGC();
114-
}
115-
~SArray()
116-
{
117-
core::GarbageCollection::GetInstance()->RemoveContainerTracking(this);
118-
}
119-
auto operator=(const SArray& other) -> SArray&
120-
{
121-
std::array<T, size>::operator=(other);
122-
return *this;
123-
}
124-
auto operator=(SArray&& other) noexcept -> SArray&
125-
{
126-
std::array<T, size>::operator=(std::move(other));
127-
return *this;
128-
}
129-
private:
130-
void AddToGC()
131-
{
132-
core::GarbageCollection::TrackedContainer container{};
133-
container.ptr = this;
134-
container.markFn =
135-
[this](core::GarbageCollection& gc)
136-
{
137-
std::queue<SObject*> bfs;
138-
139-
for (auto& elem : *this)
140-
{
141-
const SObject* obj = static_cast<const SObject*>(elem);
142-
if (obj == nullptr) continue;
143-
if (obj->IsPendingKill()) { elem = nullptr; continue; }
144-
145-
bfs.push(const_cast<SObject*>(obj));
146-
}
147-
gc.MarkBFS(bfs);
148-
};
149-
core::GarbageCollection::GetInstance()->AddContainerTracking(container);
46+
for (auto& elem : *this)
47+
gc.PushReferenceObject(elem);
15048
}
15149
};
15250
/// @brief 쓰레기 수집을 지원하는 std::set과 동일한 역할을 하는 컨테이너.

src/Core/GCObject.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include "GCObject.h"
2+
#include "GarbageCollection.h"
3+
4+
namespace sh::core
5+
{
6+
GCObject::GCObject()
7+
{
8+
static GarbageCollection& gc = *GarbageCollection::GetInstance();
9+
gc.AddGCObject(*this);
10+
}
11+
GCObject::~GCObject()
12+
{
13+
static GarbageCollection& gc = *GarbageCollection::GetInstance();
14+
gc.RemoveGCObject(*this);
15+
}
16+
}//namespace

0 commit comments

Comments
 (0)