어제의 실패 이후 검색을 열심히 해보았지만 뾰족한 수가 떠오르지 않는다.
FileManager의 역할에 대해 다시한번 규정해보자.
Asset 폴더 내의 모든 인식 가능한 파일을 찾아서 내가 만든 클래스로 만들어서 쓸수 있게 해준다. 만들어진 클래스는 FileManager가 주어진 요청에 따라 그에 맞는 데이터를 내어준다.
원인 분석을 위해 간단한 프로젝트를 만들어서 거기서 실험을 해보았더니, void*에 대한 unique_ptr이 문제를 일으키고 있다는 것을 알았다.
class A {
public:
int a = 1;
};
//template<typename T>
//T* Get(void* data, T*& out)
//{
// out = static_cast<T*>(data);
// return out;
//}
class B
{
public:
B()
{
pps.insert({ "A", std::move(unique_void(new A())) });
auto ptr = pps["A"].get(); //불가능
//cout << static_cast<A*>(ptr);
}
/*template<typename T>
T* Get( string key, T*& out)
{
pps[key];
out = static_cast<T*>(pps[key].get());
return out;
}*/
unordered_map<std::string, unique_void_ptr> pps;
};
함수 파라미터로 void*를 넘길 경우에는 static_cast가 잘 동작했지만 unique_ptr<void*>의 경우 제대로 동작하지 않았다.
unique_ptr<void>는 원칙적으로 불가능하지만, 소멸자를 등록해줄 경우 사용할 수 있다. 다음은 unique_ptr<void>를 만드는 예시이다.
using unique_void_ptr = std::unique_ptr<void, void(*)(void const*)>;
template<typename T>
auto unique_void(T* ptr) -> unique_void_ptr
{
return unique_void_ptr(ptr, [](void const* data) {
T const* p = static_cast<T const*>(data);
delete p;
});
}
여기서 만든 unique_void_ptr의 get()함수로 실제 포인터를 가져오는 과정에서 에러가 생긴다.
원인을 아직 파악하지 못하였다. 그냥 unique_void_ptr을 만들고 거기서 get()을 하면 정상적으로 포인터가 넘어오는데, unordered_map에 저장한 unique_void_ptr에서 값을 가져오려고 하면 에러가 난다.
내 수준에선 이를 개선하여 사용할 방도가 없다. 그러니 unique_ptr을 쓰지말고 소유의 개념은 없더라도 shared_ptr을 사용하고, 객체에서 해당 포인터를 참조할 때 shared_ptr를 가지고 오는게 아니라 그냥 get()함수로 가져간다면 레퍼런스 카운트가 올라가지 않으니 괜찮을 것이다.
어떻게 보면 나는 스마트 포인터를 왜 쓰냐고 뺨따구를 맞을 지도 모른다. 이따위로 쓸거면 그냥 수동으로 할당해제해라.. 당장이라도 이럴 것 같다. 미안합니다... 제 실력이 허접해서...
어쨌든 내가 스마트 포인터를 쓰는 이유에 대해 생각해보면 포인터에 대한 할당 해제를 자동으로 처리해주길 기대하기 때문이다. shared_ptr사용을 꺼린 이유는 아직 포인터가 이미 사라진 객체를 가리키는 댕글링 포인터가 생기는 경우를 어떻게 처리해야할 지 모르기 때문이다.
int main(){
shared_ptr<A> t(new A);
delete t.get();
cout << t->a;
}
여기서 처럼 get()으로 갖고온걸 부숴버리면 바로 밑에서 참조할 때 에러가 난다. unique_ptr는 그걸 자기가 막아주기 때문에 괜찮은데, shared_ptr는 이게 안된다.
음, unique_ptr의 문제라기 보단 unordered_map의 문제로 파악된다. map도 안되는데 이상하게 vector로 하면 잘만 된다.
int main() {
unordered_map<std::string, unsigned int> testMap;
vector<unique_void_ptr> test;
test.push_back(unique_void(new A));
testMap.insert(
{"test", 0}
);
auto aptr = static_cast<A*>(test[testMap["test"]].get());
cout << aptr->a;
//auto& b = test["test"];
//cout << static_cast<A*>(test["test"].get())->a;
}
주석처리한 부분이 바로 map이나 unordered_map에서 unique_void_ptr를 뽑아냈을 때 안되는 상황이었다. unordered_map 의 내부 구현에 문제가 있는 것 같다. 아니면 operator[]함수만의 문제거나.
어쨌든 vector에 저장하고 그에 대한 index를 제공하도록 하는 방식으로 해결을 할 수 있을 것 같다.
class A {
public:
int a = 1;
};
class B
{
public:
B()
{
v.push_back(unique_void(new A));
pps.insert({ "test", 0 });
}
template<typename T>
T* Get( string key, T*& out)
{
out = static_cast<T*>(v[pps[key]].get());
return out;
}
unordered_map<std::string, unsigned int> pps;
vector<unique_void_ptr> v;
};
int main() {
B b;
A* aa = b.Get("test", aa);
cout << aa->a;
}
굿굿, 이렇게 해결했다. 이제 이걸 FileManager에 적용시키자.
완성된 FileManager의 모습은 다음과 같다.
/* @file FileManager.h
* @brief Asset 폴더 내에 있는 Model(fbx, obj...etc), texture(png, jpg,...etc) 등등을 미리 초기화하여 들고 있다가 필요하다고 요청이 오면
데이터를 전달해주는 헬퍼 클래스
*/
#pragma once
#include <io.h>
#include "unique_void_ptr.h"
class FileManager
{
public:
enum class Type
{
Model,
Texture,
Shader,
VShader,
PShader,
};
static std::map<std::string, Type> mapStringValues;
public:
FileManager(Graphics& gfx);
template<typename T>
T* Get(std::string fileName, T*& out){
return out = static_cast<T*>(pDatas[dataDictionary[fileName]].get());
}
private:
//@path 해당 path내의 파일을 모두 찾아서 files에 이름, 경로로 저장
void Search(std::string path);
//파일이면 True, Directory이면 False 리턴
bool IsFile(_finddata_t fd);
//@Desc files에 있는 파일들을 Process하도록 명령
void ProcessFiles();
//@Desc 파일 이름을 분석하여 클래스로 만들어 pDatas에 저장
void Process(std::string fileName);
//파일 이름을 enum class Type으로 변환
Type GetType(std::string filename);
//@file file경로, 이름 모두 가능. 확장자를 리턴. 예) test.txt => return txt
std::string GetExtension(std::string file);
void SaveData(std::string key, void* pData);
private:
using FilePath = std::string; //FilePath와 FileName을 구분짓기 위함
std::unordered_map<std::string, FilePath> files; //@hashmap[filename] = "filePath"
//@brief pDatas에 접근하기 위한 index를 저장하는 hashMap
std::unordered_map <std::string, UINT> dataDictionary;
//@brief 실제 데이터를 저장하는 vector
std::vector<gh::unique_void_ptr> pDatas;
Graphics& gfx;
};
FileManager를 사용하는 애들은 Material 같은 경우 구조체가 현재 전부다 string으로 구성되어 있다. 어떤 이름의 셰이더를 사용하는지, 어떤 이름의 텍스쳐를 사용하는지에 대한 것만 갖고 있다가, Renderer에서 실제 데이터와 연결 시켜준다. Mesh Renderer는 Renderer에게 Model의 Mesh정보를 떼서 transform 구조체와 material 정보를 전달해준다. 근데 이것도 Renderer가 스스로 처리하도록 만드는 게 좋지 않을까 싶다.
Mesh Renderer의 모습
class MeshRenderer : public Component
{
friend class Renderer;
public:
MeshRenderer(GameObject* go = nullptr);
protected:
void Update() override;
private:
std::string _model;
Model* pModel;
std::string _material;
DEFINEPROPERTY:
PROPERTY(std::string, model);
GET(model) { return _model; } SET(model)
{
_model = value;
fileManager.Get(_model, pModel); //FileManager로부터 포인터를 받아와 저장
}
PROPERTY(std::string, material);
GET(material) { return _material; } SET(material) { _material = value; }
};
현재 Model * 를 갖고 있긴 하지만 이것도 Renderer에서 실제 레퍼런스를 fileManager에게 받아와서 처리하도록 할 생각이다.
'진행과정 기록 > GameEngine' 카테고리의 다른 글
| 규칙 (1) | 2020.02.09 |
|---|---|
| 20200207 Picking (0) | 2020.02.07 |
| 20200205 File Manager (0) | 2020.02.05 |
| 20200204 Model 임포트(Re) 하... (1) | 2020.02.04 |
| 20200204 'dll이 없어 실행할 수 없습니다.' 오류 해결 (0) | 2020.02.04 |