[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CBBY0YDSAF7A3RY5SNETQX":3,"skill-same:01M6CBBY0YDSAF7A3RY5SNETQX":44},{"skillNo":4,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":21,"frontMatter":22,"refName":23,"commitSha":24,"skillDirectory":25,"forksCount":13,"repositoryHtmlUrl":26,"repositoryHomepageUrl":6,"ownerHtmlUrl":27,"ownerBlogUrl":6,"ownerDescription":28,"updatedAt":29,"install":30,"resources":43},"01M6CBBY0YDSAF7A3RY5SNETQX","29-singleton",null,"devwinsoft\u002Fdevian","devwinsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F22570160?v=4","01M6C6RR8W05NH8NPKQNCE3Q72","maoshy","01M6C6RRDJ5TJJCZXRTY801YDH",0,"MIT","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian-unity\u002F20-common-package\u002F29-singleton\u002FSKILL.md",[],[18],"29 singleton","2026-11-10 10:12:30",false,"# 29-singleton\n\nStatus: ACTIVE\nAppliesTo: v10\nType: Component Specification\n\n## 0. 목표\n\n- 개발자가 실수 없이 싱글톤을 사용하도록 **script-created AutoSingleton**을 제공한다.\n- 필요 시 **CompoSingleton**으로 씬\u002F프리팹 배치 책임(component)을 명시한다.\n- 필요 시 **`Singleton.Create`** 또는 **`Singleton.CreateFromResources`**로 Boot source 싱글톤을 명시 생성한다.\n- 모든 싱글톤은 **단일 저장소(SingletonRegistry)**를 통해 통합 관리한다.\n\n---\n\n## 1. 제공 타입 (2종)\n\n#### AutoSingleton\\\u003CT\\> (기본)\n\n`T.Instance` 접근 시:\n1. Registry 조회\n2. 없으면 새 GameObject 생성 + AddComponent\n3. `Awake()`(base 내부 고정)에서 Registry 등록 + DontDestroyOnLoad 적용 + `onInitAwake()` 훅 호출\n4. `OnDestroy()`(base 내부 고정)에서 `onDestroy()` 훅 호출 후 Registry 해제. 하위 클래스는 `onDestroy()`를 override한다.\n\n**원칙**: `AutoSingleton`은 script code가 `Instance` 또는 프레임워크 생성 경로를 통해 만드는 패턴만 지원한다.\n씬\u002F프리팹에 미리 부착해서 사용하는 것은 금지다.\n\n**Shutdown 억제**: 에디터 종료\u002F플레이 종료\u002F앱 종료 중(`IsShuttingDown == true`)에는 자동 생성이 억제되며 `Instance`는 `null`을 반환한다. Shutdown 방어가 필요하면:\n- `AutoSingleton\u003CT>.IsShuttingDown`으로 사전 체크\n- `Singleton.TryGet\u003CT>(out var t)` 또는 `T.TryGet(out var t)`로 안전 조회 (자동 생성 없음)\n\n#### CompoSingleton\\\u003CT\\> (선택)\n\n- 씬\u002F프리팹에 컴포넌트로 붙여서 사용한다.\n- 런타임 `AddComponent`로 생성하는 것은 패턴 위반이다.\n- `Awake()`(base 내부 고정)에서 Registry에 등록하고, 하위 클래스 초기화는 `onInitAwake()`에서 수행한다.\n- `OnDestroy()`(base 내부 고정)에서 `onDestroy()` 훅 호출 후 Registry 해제. 하위 클래스 정리는 `onDestroy()`에서 수행한다.\n- **우선순위 최고**: CompoSingleton이 등록되면 같은 타입의 Auto\u002FBoot 인스턴스를 대체한다(Adopt).\n\n\n### Auto vs Compo vs Create vs CreateFromResources 선택 기준\n\n| | Auto | Compo | Create | CreateFromResources |\n|---|---|---|---|---|\n| 생성 방식 | **script code가 Instance로 new GameObject + AddComponent** | **씬\u002F프리팹에 미리 직접 배치** | **new GameObject + AddComponent (명시 호출)** | **Resources.Load\\\u003CGameObject\\> + Instantiate** |\n| 우선순위 | Auto(0) 최저 | Compo(2) 최고 | **Boot(1) 중간** | **Boot(1) 중간** |\n| SerializeField | 불가 (빈 GO 생성) | 가능 (씬\u002F프리팹에 설정) | 불가 (빈 GO 생성) | **가능 (프리팹에 미리 설정)** |\n| 자동 생성 | O (`Instance`가 script-create) | X (씬\u002F프리팹 사전 배치 필요) | **X (명시적 호출 필요)** | **X (명시적 호출 필요)** |\n| Base 상속 | 불가 | 불가 | **가능** | **가능** |\n| 용도 | 설정 불필요한 script-created 싱글톤 | 씬\u002F프리팹 배치가 필요한 싱글톤 | **런타임에서 명시 부트 생성** | **프리팹 설정값이 필요한 부트 싱글톤** |\n\n---\n\n## 2. 우선순위 규칙 (Hard Rule)\n\n같은 타입 T에 대해 **Compo > Boot > Auto**가 항상 승리한다.\n\n- CompoSingleton이 늦게 로드되어도, 기존 AutoSingleton\u002FBoot 인스턴스를 **대체(Adopt)**해야 한다.\n- 동일 우선순위끼리 중복은 **즉시 실패(예외)**로 처리한다.\n\n---\n\n## 3. Adopt 정책 (Hard Rule)\n\nRegistry에 Auto\u002FBoot가 등록된 상태에서 Compo가 등록되면:\n\n1. CompoSingleton을 \"정본\"으로 등록\n2. 기존 AutoSingleton\u002FBoot 인스턴스는 **제거(파괴)**하여 중복을 해소\n3. 이 상황은 실수 가능성이 높으므로 **Error 로그**를 남긴다 (단, 앱이 계속 진행 가능해야 함)\n\n---\n\n## 4. 접근 API (권장)\n\n| API | 동작 |\n|-----|------|\n| `Singleton.Get\u003CT>()` | 없으면 예외 (Fail-fast) |\n| `Singleton.TryGet\u003CT>(out T)` | 없으면 false |\n| `Singleton.IsShuttingDown` | Shutdown 구간 여부. `BaseApplication.IsApplicationQuitting \\|\\| !Application.isPlaying`에 위임. `Create*` \u002F `CreateFromResources*` 생성 억제 기준 |\n| `Singleton.Create\u003CT>()` | 빈 GameObject 생성 + AddComponent + Registry 등록(Boot). key=T |\n| `Singleton.Create\u003CTBase,TSelf>()` | 빈 GameObject 생성 + AddComponent + Registry 등록(Boot). key=TBase |\n| `Singleton.CreateFromResources\u003CT>(path)` | Resources에서 프리팹 로드 + Registry 등록(Boot). key=T |\n| `Singleton.CreateFromResources\u003CTBase,TSelf>(path)` | Resources에서 프리팹 로드 + Registry 등록(Boot). key=TBase |\n| `T.Instance` | AutoSingleton\u002FCompoSingleton이 제공하는 편의. Auto 계열은 script-create, Compo 계열은 기등록 인스턴스 조회. **Shutdown 중 null 반환은 Auto 계열만 해당** |\n| `AutoSingleton\u003CT>.IsShuttingDown` | Shutdown 구간 여부 (`Singleton.IsShuttingDown`에 위임) |\n\n---\n\n## 4.1 Boot source 의미\n\n- `SingletonSource.Boot`는 **Registry 우선순위 이름**이다.\n- 이것이 **프레임워크가 Bootstrap prefab 생성이나 `BootProc()` 호출을 자동 처리한다**는 뜻은 아니다.\n- Bootstrap 생성\u002F`BootProc()` 호출은 app\u002Fcontents layer가 명시적으로 수행한다.\n\n---\n\n## 5. 금지 (Hard Rule)\n\n- 기존 싱글톤 시스템(SceneSingleton\u002FMonoSingleton 등)을 새로 사용\u002F추가하지 않는다.\n- Registry의 Adopt\u002FDestroy는 구현 정책으로 존재한다. 호출자는 이 동작에 의존해 중복 생성 흐름을 설계하지 않는다.\n- Registry를 우회하는 static instance 보관을 금지한다 (모든 인스턴스는 Registry가 SSOT).\n\n---\n\n## 6. 파일 위치 (SSOT)\n\n```\ncom.devian.foundation\u002FRuntime\u002FUnity\u002FSingletons\u002F\n├── SingletonSource.cs      # enum\n├── SingletonRegistry.cs    # SSOT 저장소\n├── Singleton.cs            # 정적 파사드\n├── AutoSingleton.cs        # AutoSingleton\u003CT>\n└── CompoSingleton.cs       # CompoSingleton\u003CT>\n```\n\n---\n\n## 7. SingletonSource enum\n\n```csharp\npublic enum SingletonSource\n{\n    Auto = 0,   \u002F\u002F 자동 생성 (최저 우선순위)\n    Boot = 1,   \u002F\u002F Resources 로드 등록 (중간 우선순위)\n    Compo = 2,  \u002F\u002F 씬\u002F프리팹 컴포넌트 (최고 우선순위)\n}\n```\n\n---\n\n## 8. SingletonRegistry 규약\n\n### 저장소 구조\n\n```csharp\nprivate static readonly Dictionary\u003CType, Entry> _entries;\n\nprivate readonly struct Entry\n{\n    public readonly object Instance;\n    public readonly SingletonSource Source;\n    public readonly string DebugSource;\n}\n```\n\n### 등록 규칙 (Hard Rule)\n\n| 기존 | 신규 | 결과 |\n|------|------|------|\n| Auto | Boot | Adopt (기존 파괴 + Warning 로그) |\n| Auto | Compo | Adopt (기존 파괴 + Error 로그) |\n| Boot | Compo | Adopt (기존 파괴 + Error 로그) |\n| Compo | Compo | **즉시 예외** (중복 금지) |\n| Boot | Boot | **즉시 예외** (중복 금지) |\n| Auto | Auto | **즉시 예외** (중복 금지) |\n| Compo | Boot\u002FAuto | 무시 (Compo 우선) |\n| Boot | Auto | 무시 (Boot 우선) |\n\n### 파괴 정책\n\n- old 인스턴스가 `Component`면 `Object.Destroy(comp)`로 **컴포넌트만 제거** (GameObject 전체 파괴 금지)\n- old 인스턴스가 `UnityEngine.Object`이지만 Component가 아니면 `Object.Destroy(old)`로 제거\n- 순수 C# 객체면 참조만 교체 (로그 남김)\n\n---\n\n## 9. Create \u002F CreateFromResources 규칙\n\n### Create 규칙\n\n**생성 방식**:\n- 빈 `GameObject` 생성 → `AddComponent\u003CT>()` 또는 `AddComponent\u003CTSelf>()`\n- 이름은 `\"[TypeName]\"` 형식\n\n**등록 소스**: `SingletonSource.Boot` (중간 우선순위)\n\n**적합한 경우**:\n- `AutoSingleton` 자동 생성이 아니라 **부트 시점에 명시 생성**하고 싶을 때\n- 프리팹\u002F`SerializeField` 설정이 필요 없을 때\n\n**Shutdown 억제**:\n- `Singleton.IsShuttingDown == true`이면 생성하지 않고 `null` 반환 (경고 로그)\n\n### CreateFromResources 규칙\n\n**경로**: 호출자가 `string resourcePath`로 직접 지정한다.\n\n**로드 방식**:\n- `Resources.Load\u003CGameObject>(resourcePath)` → `GetComponent\u003CT>()` 또는 `GetComponent\u003CTSelf>()`\n- GameObject로 로드하는 이유: IL2CPP에서 리플렉션 없이 안전하게 동작\n- 프리팹 루트에 싱글톤 컴포넌트가 반드시 붙어있어야 함\n\n**등록 소스**: `SingletonSource.Boot` (중간 우선순위)\n\n**Shutdown 억제**:\n- `Singleton.IsShuttingDown == true`이면 생성하지 않고 `null` 반환 (경고 로그)\n\n---\n\n## 10. 사용 예시\n\n### 기본 사용 (AutoSingleton — 1-param)\n\n```csharp\npublic class GameManager : AutoSingleton\u003CGameManager>\n{\n    public void StartGame() { ... }\n}\n\n\u002F\u002F 어디서든 접근 - script code가 필요 시 생성\nGameManager.Instance.StartGame();\n```\n\n### 씬 배치가 필요한 경우 (CompoSingleton — 1-param)\n\n```csharp\npublic class UIRoot : CompoSingleton\u003CUIRoot>\n{\n    [SerializeField] private Canvas _mainCanvas;\n}\n\n\u002F\u002F UIRoot는 scene\u002Fprefab에 미리 부착해야 한다.\n\u002F\u002F runtime AddComponent로 생성하지 않는다.\n```\n\n### 명시 생성 (Singleton.Create)\n\n```csharp\n\u002F\u002F 부트스트랩 등에서 1회 명시 생성 (빈 GameObject + AddComponent)\nSingleton.Create\u003CGameRuntimeManager>();\n\n\u002F\u002F Base key로 등록하면서 구체 타입 생성 (시스템\u002F컨텐츠 분리)\nSingleton.Create\u003CBaseTelemetrySystem, GameTelemetrySystem>();\n```\n\n### Base 상속 + Resources 기반 싱글톤 (Singleton.CreateFromResources)\n\n```csharp\n\u002F\u002F ── 시스템 레이어 (framework) ──\npublic abstract class BaseAudioSystem : MonoBehaviour\n{\n    public abstract void PlayBGM(string trackName);\n}\n\n\u002F\u002F ── 컨텐츠 레이어 (game) ──\n\u002F\u002F 프리팹 배치: Assets\u002FResources\u002FSingletons\u002FGameAudioSystem.prefab\npublic sealed class GameAudioSystem : BaseAudioSystem\n{\n    [SerializeField] private AudioSource _bgmSource;\n\n    public override void PlayBGM(string trackName) { ... }\n}\n\n\u002F\u002F 초기화 (Bootstrap 등에서 1회 호출)\nSingleton.CreateFromResources\u003CBaseAudioSystem, GameAudioSystem>(\"Singletons\u002FGameAudioSystem\");\n\n\u002F\u002F 접근 (Registry key = BaseAudioSystem)\nSingleton.Get\u003CBaseAudioSystem>()      \u002F\u002F OK (반환 타입: BaseAudioSystem)\n```\n\n### 규칙 요약\n\n- `AutoSingleton`: scene\u002Fprefab에 미리 붙이지 않는다. script code가 만든다.\n- `CompoSingleton`: scene\u002Fprefab에 미리 붙인다. runtime AddComponent로 만들지 않는다.\n- `Singleton.Create*`: Boot 소스로 명시 생성할 때만 사용한다.\n\n---\n\n## 11. Reference\n\n- Parent: `skills\u002Fdevian\u002F10-module\u002F03-ssot\u002FSKILL.md`\n- Index: `skills\u002Fdevian-unity\u002F20-common-package\u002F00-overview\u002FSKILL.md`\n",{},"main","8cb5bd877a3a7004867d2f55697cb9e4ff2f1734","skills\u002Fdevian-unity\u002F20-common-package\u002F29-singleton","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian","https:\u002F\u002Fgithub.com\u002Fdevwinsoft","Kim Hyong Joon","2026-04-12 18:44:10",{"source":7,"skillName":5,"commands":31,"prompt":41,"zipAvailable":42},[32,35,38],{"tool":33,"command":34},"npx","npx skills add devwinsoft\u002Fdevian --skill 29-singleton",{"tool":36,"command":37},"bunx","bunx skills add devwinsoft\u002Fdevian --skill 29-singleton",{"tool":39,"command":40},"pnpm","pnpm dlx skills add devwinsoft\u002Fdevian --skill 29-singleton","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian-unity\u002F20-common-package\u002F29-singleton\u002FSKILL.md",true,[],[45,51,59,67,76,83],{"skillNo":46,"name":47,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":48,"categories":49,"keywords":50,"publishedAt":19},"01M6CBX3K2JJV38ANHVGCAT8A0","devian","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002FSKILL.md",[],[47],{"skillNo":52,"name":53,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":54,"categories":55,"keywords":56,"publishedAt":19},"01M6CBX210BFMM8AMN770J9WY8","90-project-archive","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F90-project-archive\u002FSKILL.md",[],[57,58],"archive","90 project archive",{"skillNo":60,"name":61,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":62,"categories":63,"keywords":64,"publishedAt":19},"01M6CBWZSGRR9R3YJZB3177667","30-env-management","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F30-env-management\u002FSKILL.md",[],[65,66],"management","30 env management",{"skillNo":68,"name":69,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":70,"categories":71,"keywords":72,"publishedAt":19},"01M6CBWYD3TGWBQV7V35551THF","19-page-push-send","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F19-page-push-send\u002FSKILL.md",[],[73,74,75],"push","page","19 page push send",{"skillNo":77,"name":78,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":79,"categories":80,"keywords":81,"publishedAt":19},"01M6CBWWRQ05A7604GHJKTPJTF","18-page-savedata","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F18-page-savedata\u002FSKILL.md",[],[74,82],"18 page savedata",{"skillNo":84,"name":85,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":86,"categories":87,"keywords":88,"publishedAt":19},"01M6CBWVH4S2VF1V7B2GBTESCT","17-page-deobfuscate","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F17-page-deobfuscate\u002FSKILL.md",[],[74,89],"17 page deobfuscate"]