public class ResourceManager : SingleTon<ResourceManager>
{
private SpriteAtlas sprites;
public SpriteAtlas GetSprite
{
get
{
if (!isLoadAble<SpriteAtlas>(sprites))
{
Debug.LogError("ManagerException : ResourceManager의 sprite Instance가 비어있습니다. 호출 순서,Adressable key값 혹은 코드상의 키값을 확인해주세요");
LoadAsync<SpriteAtlas>("spritePackage", (obj) => { sprites = obj; }, true);
}
return sprites;
}
}
private bool isLoadAble<T>(T instance) { return instance != null; }
//메니저 인스턴스 생성시 실행되는 함수
public override void Init()
{
base.Init();
if(!isLoadAble<SpriteAtlas>(sprites)) LoadAsync<SpriteAtlas>("spritePackage", (obj) => { sprites = obj; }, true);
}
/// <summary>
///
/// </summary>
/// <typeparam name="T">리턴타입</typeparam>
/// <param name="key">타겟 키값</param>
/// <param name="callback">(obj)=>{targetInstance = obj}</param>
public void LoadAsync<T>(string key, Action<T> callback, bool isCaching = false)
{
if (key.Contains(".sprite"))
{
key = $"{key}[{key.Replace(".sprite", "")}]";
}
AsyncOperationHandle<T> infoAsyncOP = Addressables.LoadAssetAsync<T>(key);
infoAsyncOP.Completed += (op) =>
{
callback?.Invoke(infoAsyncOP.Result);
if(!isCaching)Addressables.Release(infoAsyncOP);
};
}
/// <summary>
///
/// </summary>
/// <typeparam name="T">리턴타입</typeparam>
/// <param name="label">타겟 키값</param>
/// <param name="callback">(obj)=>{targetInstance = obj}</param>
public void LoadAsyncAll<T>(string label, Action<(string, T)[]> callback, bool isCaching = false)
{
var labelKeys = Addressables.LoadResourceLocationsAsync(label, typeof(T));
//label의 T타입인 오브젝트들의 키값을 가져온다
labelKeys.WaitForCompletion();
//resource를 전부 load할때까지 대기
Debug.Log(labelKeys.Result);
if (labelKeys.Result.Count == 0) { Debug.LogError($"{label}라벨이 비어있습니다."); callback.Invoke(null); }//해당하는 키가 없을경우 null을 리턴
int doneCount = 0;
(string, T)[] tempT = new (string, T)[labelKeys.Result.Count];
for (int i = 0; i < tempT.Length; i++)
{
int curIndex = i;
string curKey = labelKeys.Result[i].PrimaryKey; //콜백을 동시에 실행시 클로저 이슈가 생길 수 있기에 분리(다른 루프의 스택메모리를 참조하는 현상)
LoadAsync<T>(labelKeys.Result[i].PrimaryKey, (result) =>
{
tempT[curIndex].Item1 = curKey;
tempT[curIndex].Item2 = result;
doneCount++;
if (doneCount == labelKeys.Result.Count)
{
callback?.Invoke(tempT);
if (!isCaching) Addressables.Release(labelKeys);
}
}, isCaching);
}
}
}
TIL
- 오늘 학습 키워드
- 오늘 학습 한 내용을 나만의 언어로 정리하기
- 에셋을 비동기로 캐싱할 경우 생길 수 있는 레이지 로드와 콜백 환경에서 생길 수 있는 클로저 이슈 학습
- 학습하며 겪었던 문제점 & 에러
- 문제&에러에 대한 정의
- ResourceManager Instance를 접근하여 캐싱된 데이터를 사용 할 때 캐싱이 안되어있는 자료들이 생길 수 있다.(LazyLoad)
- Stack메모리인 int를 lamda식이 캡처하는 과정에서 참조형으로 바꾸며 모든 콜백이 바뀐 참조형을 사용하며 일어나는 문제
- 내가 한 시도와 해결 방법
- Await Task를 사용하면 lazyLoad를 해결 할 수 있을 것 같지만 그냥 인스턴스 생성시에 한번 캐싱하주는 형태로 진행
유니티에서 Await을 썻다가 헬게이트가 열리기 쉽상이라..
- 별도의 스택영역을 확보해주는 형태로 해결하였다.
- 새롭게 알게 된 점
- 클로저 문제 자체는 알고있었으나 원인은 잘 몰랐는데 대략적인 스코프와 일어나는 원리를 알게되었다.
- 이 문제&에러를 다시 만나게 되었다면?
- 표면적으로 보이는 코드와 다른 산출값이 나온다면 클로저 이슈를 의심해볼 수 있게 되었다 그때도 똑같이 새로운 스택영역 혹은 멀티쓰래드 환경이 가능하다면 lock을 사용해서 해결하는 방법이 있을 것 같다.
- 다음 학습 할 것은 무엇인지