AGENT SKILL

29-singleton

暂无简介

License 许可
MIT
所属仓库
devwinsoft/devian
最近更新
2026-04-12
Stars
★ 0
Forks
0

03 SKILL.md 正文

29-singleton

Status: ACTIVE AppliesTo: v10 Type: Component Specification

0. 목표

  • 개발자가 실수 없이 싱글톤을 사용하도록 script-created AutoSingleton을 제공한다.
  • 필요 시 CompoSingleton으로 씬/프리팹 배치 책임(component)을 명시한다.
  • 필요 시 Singleton.Create 또는 **Singleton.CreateFromResources**로 Boot source 싱글톤을 명시 생성한다.
  • 모든 싱글톤은 **단일 저장소(SingletonRegistry)**를 통해 통합 관리한다.

1. 제공 타입 (2종)

AutoSingleton<T> (기본)

T.Instance 접근 시:

  1. Registry 조회
  2. 없으면 새 GameObject 생성 + AddComponent
  3. Awake()(base 내부 고정)에서 Registry 등록 + DontDestroyOnLoad 적용 + onInitAwake() 훅 호출
  4. OnDestroy()(base 내부 고정)에서 onDestroy() 훅 호출 후 Registry 해제. 하위 클래스는 onDestroy()를 override한다.

원칙: AutoSingleton은 script code가 Instance 또는 프레임워크 생성 경로를 통해 만드는 패턴만 지원한다. 씬/프리팹에 미리 부착해서 사용하는 것은 금지다.

Shutdown 억제: 에디터 종료/플레이 종료/앱 종료 중(IsShuttingDown == true)에는 자동 생성이 억제되며 Instance는 null을 반환한다. Shutdown 방어가 필요하면:

  • AutoSingleton<T>.IsShuttingDown으로 사전 체크
  • Singleton.TryGet<T>(out var t) 또는 T.TryGet(out var t)로 안전 조회 (자동 생성 없음)

CompoSingleton<T> (선택)

  • 씬/프리팹에 컴포넌트로 붙여서 사용한다.
  • 런타임 AddComponent로 생성하는 것은 패턴 위반이다.
  • Awake()(base 내부 고정)에서 Registry에 등록하고, 하위 클래스 초기화는 onInitAwake()에서 수행한다.
  • OnDestroy()(base 내부 고정)에서 onDestroy() 훅 호출 후 Registry 해제. 하위 클래스 정리는 onDestroy()에서 수행한다.
  • 우선순위 최고: CompoSingleton이 등록되면 같은 타입의 Auto/Boot 인스턴스를 대체한다(Adopt).

Auto vs Compo vs Create vs CreateFromResources 선택 기준

Auto Compo Create CreateFromResources
생성 방식 script code가 Instance로 new GameObject + AddComponent 씬/프리팹에 미리 직접 배치 new GameObject + AddComponent (명시 호출) Resources.Load<GameObject> + Instantiate
우선순위 Auto(0) 최저 Compo(2) 최고 Boot(1) 중간 Boot(1) 중간
SerializeField 불가 (빈 GO 생성) 가능 (씬/프리팹에 설정) 불가 (빈 GO 생성) 가능 (프리팹에 미리 설정)
자동 생성 O (Instance가 script-create) X (씬/프리팹 사전 배치 필요) X (명시적 호출 필요) X (명시적 호출 필요)
Base 상속 불가 불가 가능 가능
용도 설정 불필요한 script-created 싱글톤 씬/프리팹 배치가 필요한 싱글톤 런타임에서 명시 부트 생성 프리팹 설정값이 필요한 부트 싱글톤

2. 우선순위 규칙 (Hard Rule)

같은 타입 T에 대해 Compo > Boot > Auto가 항상 승리한다.

  • CompoSingleton이 늦게 로드되어도, 기존 AutoSingleton/Boot 인스턴스를 **대체(Adopt)**해야 한다.
  • 동일 우선순위끼리 중복은 **즉시 실패(예외)**로 처리한다.

3. Adopt 정책 (Hard Rule)

Registry에 Auto/Boot가 등록된 상태에서 Compo가 등록되면:

  1. CompoSingleton을 "정본"으로 등록
  2. 기존 AutoSingleton/Boot 인스턴스는 **제거(파괴)**하여 중복을 해소
  3. 이 상황은 실수 가능성이 높으므로 Error 로그를 남긴다 (단, 앱이 계속 진행 가능해야 함)

4. 접근 API (권장)

API 동작
Singleton.Get<T>() 없으면 예외 (Fail-fast)
Singleton.TryGet<T>(out T) 없으면 false
Singleton.IsShuttingDown Shutdown 구간 여부. BaseApplication.IsApplicationQuitting || !Application.isPlaying에 위임. Create* / CreateFromResources* 생성 억제 기준
Singleton.Create<T>() 빈 GameObject 생성 + AddComponent + Registry 등록(Boot). key=T
Singleton.Create<TBase,TSelf>() 빈 GameObject 생성 + AddComponent + Registry 등록(Boot). key=TBase
Singleton.CreateFromResources<T>(path) Resources에서 프리팹 로드 + Registry 등록(Boot). key=T
Singleton.CreateFromResources<TBase,TSelf>(path) Resources에서 프리팹 로드 + Registry 등록(Boot). key=TBase
T.Instance AutoSingleton/CompoSingleton이 제공하는 편의. Auto 계열은 script-create, Compo 계열은 기등록 인스턴스 조회. Shutdown 중 null 반환은 Auto 계열만 해당
AutoSingleton<T>.IsShuttingDown Shutdown 구간 여부 (Singleton.IsShuttingDown에 위임)

4.1 Boot source 의미

  • SingletonSource.Boot는 Registry 우선순위 이름이다.
  • 이것이 프레임워크가 Bootstrap prefab 생성이나 BootProc() 호출을 자동 처리한다는 뜻은 아니다.
  • Bootstrap 생성/BootProc() 호출은 app/contents layer가 명시적으로 수행한다.

5. 금지 (Hard Rule)

  • 기존 싱글톤 시스템(SceneSingleton/MonoSingleton 등)을 새로 사용/추가하지 않는다.
  • Registry의 Adopt/Destroy는 구현 정책으로 존재한다. 호출자는 이 동작에 의존해 중복 생성 흐름을 설계하지 않는다.
  • Registry를 우회하는 static instance 보관을 금지한다 (모든 인스턴스는 Registry가 SSOT).

6. 파일 위치 (SSOT)

com.devian.foundation/Runtime/Unity/Singletons/
├── SingletonSource.cs      # enum
├── SingletonRegistry.cs    # SSOT 저장소
├── Singleton.cs            # 정적 파사드
├── AutoSingleton.cs        # AutoSingleton<T>
└── CompoSingleton.cs       # CompoSingleton<T>

7. SingletonSource enum

public enum SingletonSource
{
    Auto = 0,   // 자동 생성 (최저 우선순위)
    Boot = 1,   // Resources 로드 등록 (중간 우선순위)
    Compo = 2,  // 씬/프리팹 컴포넌트 (최고 우선순위)
}

8. SingletonRegistry 규약

저장소 구조

private static readonly Dictionary<Type, Entry> _entries;

private readonly struct Entry
{
    public readonly object Instance;
    public readonly SingletonSource Source;
    public readonly string DebugSource;
}

등록 규칙 (Hard Rule)

기존 신규 결과
Auto Boot Adopt (기존 파괴 + Warning 로그)
Auto Compo Adopt (기존 파괴 + Error 로그)
Boot Compo Adopt (기존 파괴 + Error 로그)
Compo Compo 즉시 예외 (중복 금지)
Boot Boot 즉시 예외 (중복 금지)
Auto Auto 즉시 예외 (중복 금지)
Compo Boot/Auto 무시 (Compo 우선)
Boot Auto 무시 (Boot 우선)

파괴 정책

  • old 인스턴스가 Component면 Object.Destroy(comp)로 컴포넌트만 제거 (GameObject 전체 파괴 금지)
  • old 인스턴스가 UnityEngine.Object이지만 Component가 아니면 Object.Destroy(old)로 제거
  • 순수 C# 객체면 참조만 교체 (로그 남김)

9. Create / CreateFromResources 규칙

Create 규칙

생성 방식:

  • 빈 GameObject 생성 → AddComponent<T>() 또는 AddComponent<TSelf>()
  • 이름은 "[TypeName]" 형식

등록 소스: SingletonSource.Boot (중간 우선순위)

적합한 경우:

  • AutoSingleton 자동 생성이 아니라 부트 시점에 명시 생성하고 싶을 때
  • 프리팹/SerializeField 설정이 필요 없을 때

Shutdown 억제:

  • Singleton.IsShuttingDown == true이면 생성하지 않고 null 반환 (경고 로그)

CreateFromResources 규칙

경로: 호출자가 string resourcePath로 직접 지정한다.

로드 방식:

  • Resources.Load<GameObject>(resourcePath) → GetComponent<T>() 또는 GetComponent<TSelf>()
  • GameObject로 로드하는 이유: IL2CPP에서 리플렉션 없이 안전하게 동작
  • 프리팹 루트에 싱글톤 컴포넌트가 반드시 붙어있어야 함

등록 소스: SingletonSource.Boot (중간 우선순위)

Shutdown 억제:

  • Singleton.IsShuttingDown == true이면 생성하지 않고 null 반환 (경고 로그)

10. 사용 예시

기본 사용 (AutoSingleton — 1-param)

public class GameManager : AutoSingleton<GameManager>
{
    public void StartGame() { ... }
}

// 어디서든 접근 - script code가 필요 시 생성
GameManager.Instance.StartGame();

씬 배치가 필요한 경우 (CompoSingleton — 1-param)

public class UIRoot : CompoSingleton<UIRoot>
{
    [SerializeField] private Canvas _mainCanvas;
}

// UIRoot는 scene/prefab에 미리 부착해야 한다.
// runtime AddComponent로 생성하지 않는다.

명시 생성 (Singleton.Create)

// 부트스트랩 등에서 1회 명시 생성 (빈 GameObject + AddComponent)
Singleton.Create<GameRuntimeManager>();

// Base key로 등록하면서 구체 타입 생성 (시스템/컨텐츠 분리)
Singleton.Create<BaseTelemetrySystem, GameTelemetrySystem>();

Base 상속 + Resources 기반 싱글톤 (Singleton.CreateFromResources)

// ── 시스템 레이어 (framework) ──
public abstract class BaseAudioSystem : MonoBehaviour
{
    public abstract void PlayBGM(string trackName);
}

// ── 컨텐츠 레이어 (game) ──
// 프리팹 배치: Assets/Resources/Singletons/GameAudioSystem.prefab
public sealed class GameAudioSystem : BaseAudioSystem
{
    [SerializeField] private AudioSource _bgmSource;

    public override void PlayBGM(string trackName) { ... }
}

// 초기화 (Bootstrap 등에서 1회 호출)
Singleton.CreateFromResources<BaseAudioSystem, GameAudioSystem>("Singletons/GameAudioSystem");

// 접근 (Registry key = BaseAudioSystem)
Singleton.Get<BaseAudioSystem>()      // OK (반환 타입: BaseAudioSystem)

규칙 요약

  • AutoSingleton: scene/prefab에 미리 붙이지 않는다. script code가 만든다.
  • CompoSingleton: scene/prefab에 미리 붙인다. runtime AddComponent로 만들지 않는다.
  • Singleton.Create*: Boot 소스로 명시 생성할 때만 사용한다.

11. Reference

  • Parent: skills/devian/10-module/03-ssot/SKILL.md
  • Index: skills/devian-unity/20-common-package/00-overview/SKILL.md

04 同仓库更多 Skills