| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Tags
- 주우석
- 백준
- 입출력과 사칙연산
- BOJ
- 알고리즘
- 밑바닥부터 만드는 컴퓨팅 시스템 2판
- 잡생각 정리글
- unity6
- C#
- 메타버스
- booksr.co.kr
- 일기
- JavaScript
- C
- Noam Nisan
- C++
- 생능출판
- HANBIT Academy
- 데이터 통신과 컴퓨터 네트워크
- 김진홍 옮김
- The Elements of Computing Systems 2/E
- 이득우의 게임수학
- https://insightbook.co.kr/
- (주)책만
- 전공자를 위한 C언어 프로그래밍
- 게임 수학
- Shimon Schocken
- hanbit.co.kr
- 이득우
- 박기현
Archives
- Today
- Total
cyphen156
ResourceManager 리팩토링 중간 정리 본문
요약은 클로드가 햇시우
ResourceManager 버전 비교 및 통합 분석
개요
두 개의 ResourceManager.cs 파일이 존재하며, 각각 다른 리소스 관리 접근 방식을 사용하고 있습니다.
주요 차이점
1. 아키텍처 접근 방식
첫 번째 파일 (최신 버전)
- TypeMapContainer 기반 시스템
- AccessMode (Public/Internal) 구분
- Task 기반 비동기 로딩
- StreamContainer를 통한 파일 스트림 관리
- Addressables 시스템 통합
두 번째 파일 (이전 버전)
- ContentManifest 기반 시스템
- Coroutine 기반 콘텐츠 동기화
- 서버 검증 및 업데이트 시스템
- 번들 다운로드 및 캐싱
기능 비교표
기능첫 번째 파일두 번째 파일
| 비동기 처리 | Task/async-await | Coroutine |
| 파일 스트림 관리 | StreamContainer + 리스 시스템 | 직접 File I/O |
| 리소스 컨테이너 | TypeMapContainer (형식 안전) | Dictionary (기본) |
| 서버 동기화 | ❌ 없음 | ✅ ContentManifest 검증 |
| 번들 관리 | Addressables 기본 | 커스텀 번들 다운로드 |
| 메타데이터 관리 | DomainAddressResolver | ContentMeta + 서버 검증 |
| 에러 처리 | IOResult 구조체 | try-catch + 로그 |
| 동시성 제어 | lock + Task 큐잉 | Coroutine 순차 처리 |
각 버전의 강점
첫 번째 파일 강점
- 현대적인 C# 패턴
- async/await 사용
- 형식 안전한 제네릭 시스템
- RAII 패턴 (StreamContainer)
- 강력한 동시성 관리
csharp
// 중복 로딩 방지
Task<IOResult> previousTask = null;
lock (internalContainer.LockObj) {
TryGetAsset_Internal<Task<IOResult>>(staticKey, AccessMode.Internal, out previousTask);
}
- StreamContainer 리스 시스템
- 파일 중복 열기 방지
- 자동 리소스 정리
- Task 완료 시 자동 Dispose
- 상세한 IOResult
csharp
public struct IOResult {
public IOFailReason failReason;
public Exception exception;
public long httpResponseCode;
}
두 번째 파일 강점
- 콘텐츠 업데이트 시스템
- 서버 메타 검증
- SHA256 해시 비교
- 점진적 업데이트
- 번들 캐싱
csharp
// {persistent}/Bundles/{schema}/{bundleId}/{sha256}.bundle
string bundleDir = Path.Combine(bundleRoot, entry.id);
- 버전 관리
- RequiredOnBoot 플래그
- 지연 로딩 지원
- 로컬 폴백 메커니즘
통합 권장사항
1. 코어 시스템: 첫 번째 파일 기반
- TypeMapContainer 유지
- Task 기반 비동기 유지
- StreamContainer 리스 시스템 유지
2. 추가할 기능: 두 번째 파일에서
csharp
// ContentManifest 시스템 통합
internal async Task<IOResult> SyncContentManifestAsync(CancellationToken ct = default)
{
// C_SyncContentManifest를 async/await로 변환
}
// 메타 검증을 Task 기반으로
internal async Task<(IOResult result, VerifyResult verifyResult)>
VerifyContentMetaAsync(ContentMeta localMeta, string id, string schema, CancellationToken ct = default)
{
// C_VerifyContentMeta를 async/await로 변환
}
3. 통합 아키텍처
ResourceManager
├── Legacy Resources (Resources.Load)
├── TypeMapContainer System (형식 안전 캐시)
├── StreamContainer System (파일 스트림 관리)
├── Addressables Integration (첫 번째 파일)
└── Content Update System (두 번째 파일, async 변환)
├── ContentManifest
├── ContentMeta Verification
└── Bundle Download/Cache
마이그레이션 계획
Phase 1: 기본 통합
- 첫 번째 파일을 베이스로 사용
- ContentManifest 관련 클래스 추가
- DomainAddressResolver와 ContentManifest 통합
Phase 2: 비동기 변환
csharp
// Before (Coroutine)
public IEnumerator C_SyncContentManifest() { ... }
// After (Task)
public async Task<IOResult> SyncContentManifestAsync(CancellationToken ct = default)
{
LoadContentMeta(out localMeta, contentManifest.id, contentManifest.schema);
var verifyResult = await VerifyContentMetaAsync(
localMeta,
contentManifest.id,
contentManifest.schema,
ct
);
if (verifyResult.result == VerifyResult.Outdated) {
await UpdateContentManifestAsync(verifyResult, ct);
}
}
Phase 3: 스트림 통합
csharp
// 번들 다운로드에 StreamContainer 사용
public async Task<IOResult> DownloadBundleAsync(
string uri,
string bundlePath,
CancellationToken ct = default)
{
// DownloadFileAsync 재사용
return await DownloadFileAsync(uri, bundlePath, ct);
}
잠재적 문제점 및 해결책
문제 1: Coroutine vs Task 혼용
현상: Unity의 일부 API는 Coroutine만 지원 해결:
csharp
public async Task<IOResult> LoadAssetAsync<T>(uint staticKey) where T : UnityEngine.Object
{
var tcs = new TaskCompletionSource<IOResult>();
// Addressables는 Task 지원
var handle = Addressables.LoadAssetAsync<T>(resolvedPath);
await handle.Task;
return IOResult.Ok();
}
문제 2: 이중 리소스 시스템
현상: Resources.Load와 Addressables 혼재 해결:
csharp
public bool TryGetAsset<T>(uint staticKey, out T asset) where T : UnityEngine.Object
{
// 1. TypeMapContainer 확인 (Addressables)
if (TryGetAsset_Internal<T>(staticKey, AccessMode.Public, out asset)) {
return true;
}
// 2. Legacy Resources 폴백
if (domainAddressResolver.TryResolve(staticKey, out string path)) {
asset = Resources.Load<T>(path);
return asset != null;
}
return false;
}
문제 3: 메모리 관리
현상: 여러 캐시 딕셔너리 존재 해결:
csharp
internal void ClearCache(CacheType type)
{
switch (type) {
case CacheType.Sprites:
itemSprites.Clear();
monsterSprites.Clear();
worldObjectSprites.Clear();
break;
case CacheType.TypeMaps:
foreach (var container in assetContainers) {
container?.Clear();
}
break;
case CacheType.All:
// 전체 정리
ForceGarbageCollecting();
break;
}
}
성능 최적화 제안
1. 스프라이트 캐싱 통합
csharp
// 현재: 3개의 별도 딕셔너리
private readonly Dictionary<uint, Sprite> itemSprites = new();
private readonly Dictionary<uint, Sprite> monsterSprites = new();
private readonly Dictionary<uint, Sprite> worldObjectSprites = new();
// 제안: TypeMapContainer 활용
AllocateTypeMap<Sprite>(AccessMode.Public);
public Sprite GetSprite(uint objectKey) {
if (TryGetAsset<Sprite>(objectKey, out var sprite)) {
return sprite;
}
// Resources.Load 폴백
return LoadSpriteFromResources(objectKey);
}
2. 비동기 초기화
csharp
// 현재: Awake에서 동기 로딩
private void Awake() {
Initialize(); // 모든 리소스 동기 로드
}
// 제안: 백그라운드 초기화
private void Awake() {
InitializeCore(); // 필수 항목만
_ = InitializeAsync(); // 나머지는 비동기
}
private async Task InitializeAsync() {
await LoadUserDataAsync();
await SyncContentManifestAsync();
}
3. 지연 로딩
csharp
public Sprite GetControlSprite(string controlName, bool isHighlight = false)
{
// 현재: 모든 스프라이트 사전 로드
// 제안: 첫 사용 시 로드
if (!controlSpritesLoaded) {
LoadControlSprites(currentControlScheme);
}
// 기존 로직...
}
테스트 시나리오
1. 동시성 테스트
csharp
[Test]
public async Task LoadAsset_Concurrent_ShouldNotDuplicate()
{
var tasks = new List<Task<IOResult>>();
for (int i = 0; i < 10; i++) {
tasks.Add(rm.LoadAssetAsync<Sprite>(testKey));
}
var results = await Task.WhenAll(tasks);
// 모두 성공해야 함
Assert.IsTrue(results.All(r => r.succeeded));
// 실제로는 한 번만 로드되어야 함
// (내부 카운터나 로그로 검증)
}
2. 스트림 리스 테스트
csharp
[Test]
public void StreamLease_SamePath_ShouldDeny()
{
var container1 = rm.TryGetStreamContainer(testPath, "Owner1", out var reason1);
Assert.IsTrue(container1.Succeeded);
var container2 = rm.TryGetStreamContainer(testPath, "Owner2", out var reason2);
Assert.IsFalse(container2.Succeeded);
Assert.IsTrue(reason2.Contains("already leased"));
container1.Release();
var container3 = rm.TryGetStreamContainer(testPath, "Owner3", out var reason3);
Assert.IsTrue(container3.Succeeded);
}
3. 콘텐츠 업데이트 테스트
csharp
[Test]
public async Task SyncManifest_Outdated_ShouldUpdate()
{
// Mock 서버 설정
mockServer.SetupMeta("manifest", "v2", "hash_v2");
var result = await rm.SyncContentManifestAsync();
Assert.IsTrue(result.succeeded);
Assert.AreEqual("v2", rm.contentManifest.version);
}
결론
권장 접근
- 첫 번째 파일을 기반으로 유지
- 두 번째 파일의 콘텐츠 업데이트 시스템을 async/await로 변환하여 통합
- 단계적 마이그레이션으로 안정성 확보
우선순위
- ✅ TypeMapContainer 시스템 유지 (완료)
- 🔄 ContentManifest를 Task 기반으로 변환 (진행 필요)
- 🔄 스프라이트 캐싱 통합 (진행 필요)
- ⏳ 비동기 초기화 구현 (대기)
- ⏳ 포괄적인 테스트 작성 (대기)
예상 효과
- 성능: Task 기반 비동기로 메인 스레드 부담 감소
- 안정성: StreamContainer로 파일 충돌 방지
- 유지보수: 형식 안전 시스템으로 버그 감소
- 확장성: ContentManifest로 동적 콘텐츠 업데이트 지원
'프로젝트 > Skul 모작' 카테고리의 다른 글
| 스컬 모작 프로젝트 @2 도메인 시스템 개발 과정중 GPT와의 설계 논의 (0) | 2025.11.21 |
|---|---|
| 스컬 모작 프로젝트 @1 UI 개발과 리팩터링 고민_1 (1) | 2025.11.14 |
| 스컬 모작 프로젝트 #999 Extra 게임 서버 구축에 대한 고민글 (0) | 2025.10.13 |
| 스컬 모작 프로젝트 #3 메뉴 UI 만들기 (0) | 2025.10.09 |
| 스컬 모작 프로젝트 #2 프로젝트 버전 마이그레이션 (0) | 2025.10.01 |
