PART 3 ยท ๊ฐ•์˜ 4/4

Weak Reference ํŒจํ„ด

TWeakObjectPtr ์‚ฌ์šฉ๋ฒ•, IsValid ํŒจํ„ด, ์ˆœํ™˜ ์ฐธ์กฐ ๋ฐฉ์ง€ ์ „๋žต์„ ์‹ค์ „ ์˜ˆ์ œ๋กœ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค

SECTION 01

TWeakObjectPtr ์‹ฌํ™”

์•ฝํ•œ ์ฐธ์กฐ์˜ ๋‚ด๋ถ€ ๋™์ž‘๊ณผ ์•ˆ์ „ํ•œ ์‚ฌ์šฉ ํŒจํ„ด

๋‚ด๋ถ€ ๋™์ž‘ ์›๋ฆฌ

TWeakObjectPtr๋Š” GUObjectArray์˜ ์ธ๋ฑ์Šค(ObjectIndex)์™€ ์‹œ๋ฆฌ์–ผ ๋ฒˆํ˜ธ(ObjectSerialNumber)๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์ ‘๊ทผ ์‹œ ์ด ๋‘ ๊ฐ’์„ ๊ฒ€์ฆํ•˜์—ฌ ๋Œ€์ƒ ๊ฐ์ฒด์˜ ์œ ํšจ์„ฑ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

C++ // ์•ˆ์ „ํ•œ ์‚ฌ์šฉ ํŒจํ„ด TWeakObjectPtr<AActor> WeakTarget; // ํŒจํ„ด 1: IsValid + Get if (WeakTarget.IsValid()) { AActor* Actor = WeakTarget.Get(); // ์ฃผ์˜: ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ๋Š” IsValid์™€ Get ์‚ฌ์ด์— // ๊ฐ์ฒด๊ฐ€ ํŒŒ๊ดด๋  ์ˆ˜ ์žˆ์Œ } // ํŒจํ„ด 2: Get + null ์ฒดํฌ (๋” ์•ˆ์ „) if (AActor* Actor = WeakTarget.Get()) { Actor->DoSomething(); } // ํŒจํ„ด 3: IsStale ์ฒดํฌ (๋ช…์‹œ์  ํŒŒ๊ดด ๊ฐ์ง€) if (WeakTarget.IsStale()) { // ํ•œ๋•Œ ์œ ํšจํ–ˆ์ง€๋งŒ ์ด์ œ ํŒŒ๊ดด๋จ // (ํ•œ ๋ฒˆ๋„ ํ• ๋‹น ์•ˆ ๋œ null๊ณผ ๊ตฌ๋ถ„ ๊ฐ€๋Šฅ) } // ๋ฆฌ์…‹ WeakTarget.Reset(); // null๋กœ ์ดˆ๊ธฐํ™”
SECTION 02

์ˆœํ™˜ ์ฐธ์กฐ ๋ฐฉ์ง€

TWeakObjectPtr๋ฅผ ํ™œ์šฉํ•œ ์ˆœํ™˜ ์ฐธ์กฐ ํ•ด๊ฒฐ ์ „๋žต

์ˆœํ™˜ ์ฐธ์กฐ ๋ฌธ์ œ

๋‘ UObject๊ฐ€ UPROPERTY๋กœ ์„œ๋กœ๋ฅผ ์ฐธ์กฐํ•˜๋ฉด, ๋‘˜ ๋‹ค Root Set์—์„œ ๋„๋‹ฌ ๋ถˆ๊ฐ€๋Šฅํ•ด๋„ ์„œ๋กœ์˜ ์ฐธ์กฐ๋กœ ์ธํ•ด GC๊ฐ€ ์ˆ˜์ง‘ํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ UE GC๋Š” ๋„๋‹ฌ ๊ฐ€๋Šฅ์„ฑ ๊ธฐ๋ฐ˜์ด๋ฏ€๋กœ ์ˆœํ™˜ ์ฐธ์กฐ ์ž์ฒด๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ์ผ์œผํ‚ค์ง€๋Š” ์•Š์ง€๋งŒ, ์˜๋„์น˜ ์•Š์€ ๊ฐ์ฒด ์กด์†์˜ ์›์ธ์ด ๋ฉ๋‹ˆ๋‹ค.

C++ // ๋ฌธ์ œ: ์–‘๋ฐฉํ–ฅ ๊ฐ•ํ•œ ์ฐธ์กฐ UCLASS() class UNodeA : public UObject { GENERATED_BODY() UPROPERTY() UNodeB* Partner; // ๊ฐ•ํ•œ ์ฐธ์กฐ โ†’ B๋ฅผ ๋ณดํ˜ธ }; UCLASS() class UNodeB : public UObject { GENERATED_BODY() UPROPERTY() UNodeA* Partner; // ๊ฐ•ํ•œ ์ฐธ์กฐ โ†’ A๋ฅผ ๋ณดํ˜ธ }; // Aโ†’Bโ†’A ์ˆœํ™˜! ์™ธ๋ถ€ ์ฐธ์กฐ ์—†์–ด๋„ ๋‘˜ ๋‹ค GC ์•ˆ ๋จ // ํ•ด๊ฒฐ: ํ•œ ์ชฝ์„ ์•ฝํ•œ ์ฐธ์กฐ๋กœ UCLASS() class UNodeB_Fixed : public UObject { GENERATED_BODY() UPROPERTY() TWeakObjectPtr<UNodeA> Partner; // ์•ฝํ•œ ์ฐธ์กฐ โ†’ A๋ฅผ ๋ณดํ˜ธํ•˜์ง€ ์•Š์Œ }; // ์™ธ๋ถ€ ์ฐธ์กฐ ์—†์œผ๋ฉด โ†’ A๊ฐ€ GC๋จ โ†’ B.Partner.IsValid() == false โ†’ B๋„ GC ๊ฐ€๋Šฅ
UE GC๋Š” ์ˆœํ™˜ ์ฐธ์กฐ๋ฅผ ํƒ์ง€ํ•˜๋Š”๊ฐ€?

UE์˜ Mark-Sweep GC๋Š” ๋„๋‹ฌ ๊ฐ€๋Šฅ์„ฑ(Reachability) ๊ธฐ๋ฐ˜์ž…๋‹ˆ๋‹ค. ์ˆœํ™˜ ์ฐธ์กฐ๊ฐ€ ์žˆ๋”๋ผ๋„ Root Set์—์„œ ๋„๋‹ฌ ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ ์ˆ˜์ง‘๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ˆœํ™˜ ์ฐธ์กฐ ์ค‘ ํ•˜๋‚˜๋ผ๋„ Root Set์—์„œ ๋„๋‹ฌ ๊ฐ€๋Šฅํ•˜๋ฉด ์ „์ฒด ์ˆœํ™˜ ๊ทธ๋ž˜ํ”„๊ฐ€ ๋ณดํ˜ธ๋ฉ๋‹ˆ๋‹ค. ๋ฌธ์ œ๋Š” "์˜๋„์น˜ ์•Š์€ ๋ณดํ˜ธ"๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

SECTION 03

Owner ํŒจํ„ด๊ณผ ์•ฝํ•œ ์—ญ์ฐธ์กฐ

์†Œ์œ ์ž-ํ”ผ์†Œ์œ ์ž ๊ด€๊ณ„์—์„œ์˜ ์ฐธ์กฐ ๋ฐฉํ–ฅ ์„ค๊ณ„

Owner ํŒจํ„ด

๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ํŒจํ„ด์€ ์†Œ์œ ์ž๋Š” ๊ฐ•ํ•œ ์ฐธ์กฐ๋กœ ํ”ผ์†Œ์œ ์ž๋ฅผ ์ฐธ์กฐํ•˜๊ณ , ํ”ผ์†Œ์œ ์ž๋Š” ์•ฝํ•œ ์ฐธ์กฐ๋กœ ์†Œ์œ ์ž๋ฅผ ์—ญ์ฐธ์กฐํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

C++ // Owner ํŒจํ„ด ์˜ˆ์‹œ UCLASS() class UInventory : public UObject { GENERATED_BODY() // ์†Œ์œ ์ž: ๊ฐ•ํ•œ ์ฐธ์กฐ๋กœ ์•„์ดํ…œ ๋ณด์œ  UPROPERTY() TArray<UItem*> Items; // ์†Œ์œ ์ž: ๊ฐ•ํ•œ ์ฐธ์กฐ๋กœ ์บ๋ฆญํ„ฐ ์ฐธ์กฐ UPROPERTY() TObjectPtr<ACharacter> OwnerCharacter; }; UCLASS() class UItem : public UObject { GENERATED_BODY() // ํ”ผ์†Œ์œ ์ž: ์•ฝํ•œ ์ฐธ์กฐ๋กœ ์ธ๋ฒคํ† ๋ฆฌ ์—ญ์ฐธ์กฐ UPROPERTY() TWeakObjectPtr<UInventory> OwningInventory; void Use() { if (UInventory* Inv = OwningInventory.Get()) { Inv->ConsumeItem(this); } } };

์ฐธ์กฐ ๋ฐฉํ–ฅ ์„ค๊ณ„ ์›์น™

๊ด€๊ณ„์†Œ์œ ์ž โ†’ ํ”ผ์†Œ์œ ์žํ”ผ์†Œ์œ ์ž โ†’ ์†Œ์œ ์ž
๋ถ€๋ชจ-์ž์‹UPROPERTY (๊ฐ•ํ•œ)TWeakObjectPtr (์•ฝํ•œ)
์ปดํฌ๋„ŒํŠธ-์•กํ„ฐ์ž๋™ (Component)GetOwner() ๋‚ด์žฅ
์œ„์ ฏ-๋ฐ์ดํ„ฐUPROPERTY (๊ฐ•ํ•œ)TWeakObjectPtr (์•ฝํ•œ)
AI-ํƒ€๊ฒŸTWeakObjectPtr (์•ฝํ•œ)-
SECTION 04

์‹ค์ „ ์•ฝํ•œ ์ฐธ์กฐ ์‚ฌ์šฉ ์‚ฌ๋ก€

๊ฒŒ์ž„ ๊ฐœ๋ฐœ์—์„œ ์ž์ฃผ ๋งŒ๋‚˜๋Š” ์•ฝํ•œ ์ฐธ์กฐ ํŒจํ„ด

๋ธ๋ฆฌ๊ฒŒ์ดํŠธ์™€ ์•ฝํ•œ ์ฐธ์กฐ

C++ // ํƒ€์ด๋จธ ์ฝœ๋ฐฑ์˜ ์•ฝํ•œ ์ฐธ์กฐ ํŒจํ„ด TWeakObjectPtr<AActor> WeakTarget = TargetActor; GetWorld()->GetTimerManager().SetTimer( TimerHandle, [WeakTarget]() { if (AActor* Target = WeakTarget.Get()) { Target->ApplyDamage(10.f); } // WeakTarget์ด ๋ฌดํšจ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ํ•จ (์•ˆ์ „) }, 2.0f, false ); // ๋ฉ€ํ‹ฐ์บ์ŠคํŠธ ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ์—์„œ์˜ ํŒจํ„ด OnEnemyDefeated.AddWeakLambda(this, [this](AActor* DefeatedEnemy) { // this๊ฐ€ ํŒŒ๊ดด๋˜๋ฉด ์ž๋™์œผ๋กœ ๋ฐ”์ธ๋”ฉ ํ•ด์ œ Score += 100; } ); // UI์—์„œ์˜ ์•ฝํ•œ ์ฐธ์กฐ UPROPERTY() TWeakObjectPtr<APlayerController> WeakPC; // ์œ„์ ฏ์ด PC๋ณด๋‹ค ์˜ค๋ž˜ ์‚ด ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์•ฝํ•œ ์ฐธ์กฐ ์‚ฌ์šฉ
SUMMARY

ํ•ต์‹ฌ ์š”์•ฝ

์ด ๊ฐ•์˜์—์„œ ๋ฐฐ์šด ๋‚ด์šฉ
  • TWeakObjectPtr๋Š” ObjectIndex์™€ SerialNumber๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค
  • ์–‘๋ฐฉํ–ฅ ๊ฐ•ํ•œ ์ฐธ์กฐ์˜ ์ˆœํ™˜ ๋ฌธ์ œ๋Š” ํ•œ ์ชฝ์„ TWeakObjectPtr๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค
  • Owner ํŒจํ„ด: ์†Œ์œ ์ž๋Š” ๊ฐ•ํ•œ ์ฐธ์กฐ, ํ”ผ์†Œ์œ ์ž๋Š” ์•ฝํ•œ ์—ญ์ฐธ์กฐ๊ฐ€ ํ‘œ์ค€์ž…๋‹ˆ๋‹ค
  • ํƒ€์ด๋จธ, ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ ๋“ฑ ๋น„๋™๊ธฐ ์ฝœ๋ฐฑ์—์„œ๋Š” ๋ฐ˜๋“œ์‹œ ์•ฝํ•œ ์ฐธ์กฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • UE GC๋Š” ๋„๋‹ฌ ๊ฐ€๋Šฅ์„ฑ ๊ธฐ๋ฐ˜์ด๋ฏ€๋กœ ์ˆœํ™˜ ์ž์ฒด๋Š” ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ์ง€๋งŒ, ์˜๋„์น˜ ์•Š์€ ๋ณดํ˜ธ๋ฅผ ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
PRACTICE

๋„์ „ ๊ณผ์ œ

๋ฐฐ์šด ๋‚ด์šฉ์„ ์ง์ ‘ ์‹ค์Šตํ•ด๋ณด์„ธ์š”

์‹ค์Šต 1: TWeakObjectPtr ์บ์‹œ ํŒจํ„ด ๊ตฌํ˜„

TWeakObjectPtr์„ ์‚ฌ์šฉํ•œ ๊ฐ์ฒด ์บ์‹œ ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”. ์บ์‹œ๋œ ๊ฐ์ฒด๊ฐ€ GC๋˜๋ฉด ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜์—ฌ ์žฌ์ƒ์„ฑํ•˜๋Š” ํŒจํ„ด์„ ๋งŒ๋“ค๊ณ , ๊ฐ•ํ•œ ์ฐธ์กฐ ์บ์‹œ์™€์˜ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ฐจ์ด๋ฅผ ๋น„๊ตํ•˜์„ธ์š”.

์‹ค์Šต 2: TSoftObjectPtr ์ง€์—ฐ ๋กœ๋”ฉ ๊ตฌํ˜„

TSoftObjectPtr๋กœ ์—์…‹์„ ์ฐธ์กฐํ•˜๊ณ , ํ•„์š”ํ•œ ์‹œ์ ์— LoadSynchronous() ๋˜๋Š” ๋น„๋™๊ธฐ ๋กœ๋“œ(FStreamableManager)๋กœ ์‹ค์ œ ๋กœ๋”ฉํ•˜๋Š” ํŒจํ„ด์„ ๊ตฌํ˜„ํ•˜์„ธ์š”. ๋กœ๋“œ/์–ธ๋กœ๋“œ ์‚ฌ์ดํด์—์„œ GC ๋™์ž‘์„ ํ™•์ธํ•˜์„ธ์š”.

์‹ฌํ™” ๊ณผ์ œ: ์•ฝํ•œ ์ฐธ์กฐ ๊ธฐ๋ฐ˜ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ

TWeakObjectPtr์„ ํ™œ์šฉํ•˜์—ฌ ๊ตฌ๋…์ž๊ฐ€ GC๋˜๋ฉด ์ž๋™์œผ๋กœ ๊ตฌ๋…์ด ํ•ด์ œ๋˜๋Š” ์•ˆ์ „ํ•œ ์ด๋ฒคํŠธ/๋ธ๋ฆฌ๊ฒŒ์ดํŠธ ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”. ์ผ๋ฐ˜ DECLARE_DYNAMIC_MULTICAST_DELEGATE์™€ ๋น„๊ตํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ „์„ฑ ์ฐจ์ด๋ฅผ ๋ถ„์„ํ•˜์„ธ์š”.