Sat
23
May 2009
Sam manager zasobów napisałem wg podobnych założeń, jakie miałem w TFQ7. Jest jeden globalny manager zasobów g_ResMngr przechowujący kolekcję wszystkich zasobów. Zasób jest obiektem klasy pochodnej od Resource implementującej odpowiednie metody wirtualne. Zasób może mieć nazwę i można wyszukiwać zasoby wg nazwy, ale może też mieć nazwę pustą. Zasoby można swobodnie tworzyć i usuwać. Obiekt klasy Resource istnieje przez cały czas życia zasobu, a wewnątrz pamięta stan - niezaładowany, w tracie ładowania, załadowany itd.
class Resource
{
public:
enum STATE {
STATE_UNLOADED, STATE_LOADING, STATE_LOADED, STATE_LOADED_ERROR,
};
Resource(const string &Name);
virtual ~Resource();
STATE GetState() { return m_State; }
bool IsLoaded() { return m_State == STATE_LOADED; }
void Load(); // Żąda załadowania już teraz
void BeginLoading(); // Rozpoczyna ładowanie w tle
void Unload(); // Odładowuje
//...
protected:
virtual void OnLoad() = 0;
virtual void GetLoadType(bool *OutUseBkg, BkgJob::TYPE *OutBkgJobType) = 0;
virtual void OnLoadBkg() { } // Wykonywana na osobnym wątku
virtual void OnLoadAfterBkg() { }
virtual void OnUnload() = 0;
private:
string m_Name;
STATE m_State;
//...
};
class ResourceManager
{
public:
//...
Resource * Find(const tstring &Name);
Resource * MustFind(const tstring &Name);
template <typename T> T * FindEx(const tstring &Name) { /*...*/ }
template <typename T> T * MustFindEx(const tstring &Name) { /*...*/ }
};
extern ResourceManager * g_ResMngr;
Dodatkowe wyjaśnienia do tego kodu: Metody szablonowe FindEx i MustFindEx służą do wygodniejszego pobierania zasobu określonego typu klasy pochodnej (sprawdzając ten typ). Pisałem o tym TUTAJ. Klasa Resource sama w konstruktorze rejestruje się w g_ResMngr dodając się do jego kolekcji zasobów, a w destruktorze wyrejestrowuje się z niego. Inaczej niż w TFQ7, tym razem jakikolwiek błąd we wczytywaniu zasobów nie zamyka całego programu. Zasoby mają dodatkowy stan STATE_LOADED_ERROR, który pozwala zasygnalizować błąd wczytywania i pobrać powstały przy tym wyjątek.
Koncepcja taka jak tutaj opisana daje dużą elastyczność w zarządzaniu zasobami. Na przykład jakiś kod może mieć swój zasób na własność:
D3dEffect *effRes = new D3dEffect( string(), // Nazwa zasobu "MyPrivateShader.fx", "Cache/MyPrivateShader.fxo"); // Używaj effRes... delete effRes;
Albo też można umówić się, że nazwa zasobu (podana wyżej jako łańcuch pusty) będzie równocześnie nazwą pliku i różne części kodu mogą odwoływać się do takiego zasobu sprawdzając najpierw, czy już taki istnieje:
D3dTextureFromFile *texRes =
g_ResMngr->FindEx<D3dTextureFromFile>(texFileName);
if (texRes == NULL)
texRes = new D3dTextureFromFile(
texFileName, // Nazwa zasobu
texFileName); / Nazwa pliku tekstury
Różnie można też zarządzać wczytywaniem zasobów. Zasób można wczytywać zaraz po utworzeniu, żeby był wczytany przez cały czas życia:
D3dEffect *effRes = new D3dEffect(string(), "MyPrivateShader.fx", "Cache/MyPrivateShader.fxo"); effRes->Load(); // Używaj effRes... delete effRes;
Albo wczytywać go dopiero przy pierwszym użyciu:
if (!effRes->IsLoaded()) effRes->BeginLoading(); // Rozpoczyna wczytywanie w tle else // Używaj effRes...
Klasy pochodne odpowiadające typom zasobów, które dotychczas napisałem, to:
class D3dTexture : public Resource // Klasa abstrakcyjna
{
public:
D3dTexture(const tstring &Name);
IDirect3DBaseTexture9 * GetBaseTexture();
IDirect3DTexture9 * GetTexture();
IDirect3DCubeTexture9 * GetCubeTexture();
IDirect3DVolumeTexture9 * GetVolumeTexture();
//...
};
class D3dTextureFromFile : public D3dTexture
{
public:
D3dTextureFromFile(const tstring &Name,
const tstring &FileName,
D3DRESOURCETYPE Type = (D3DRESOURCETYPE)0,
D3DCOLOR ColorKey = 0,
D3DPOOL Pool = D3DPOOL_MANAGED,
DWORD Usage = 0,
D3DFORMAT Format = D3DFMT_UNKNOWN,
uint MipLevels = D3DX_DEFAULT,
DWORD Filter = D3DX_DEFAULT,
DWORD MipFilter = D3DX_DEFAULT,
uint Width = D3DX_DEFAULT,
uint Height = D3DX_DEFAULT,
uint Depth = D3DX_DEFAULT);
//...
};
class D3dTextureProcedural : public D3dTexture // Klasa abstrakcyjna
{
public:
D3dTextureProcedural(const tstring &Name);
protected:
virtual void _GetImageInfo(D3DXIMAGE_INFO *Out) = 0;
virtual void _Generate(void *Out, uint DataSize) = 0;
//...
};
class D3dTextureSingleColor : public D3dTextureProcedural
{
public:
D3dTextureSingleColor(const tstring &Name, common::COLOR Color);
//...
};
class D3dFont : public Resource
{
public:
D3dFont(const tstring &Name, const D3DXFONT_DESC &Desc);
ID3DXFont * GetFont();
//...
};
class D3dEffect : public Resource
{
public:
D3dEffect(const tstring &Name, const tstring &FileName,
const tstring &CacheFileName = "");
ID3DXEffect * GetEffect() { return m_Effect.get(); }
//...
};
class D3dMesh : public Resource
{
public:
D3dMesh(const tstring &Name);
IDirect3DVertexBuffer9 * GetVertexBuffer();
IDirect3DIndexBuffer9 * GetIndexBuffer();
IDirect3DVertexDeclaration9 * GetVertexDeclaration();
size_t GetObjectCount();
const OBJECT & GetObject(size_t Index);
DWORD GetFVF();
size_t GetBytesPerVertex();
size_t GetBytesPerIndex();
size_t GetVertexCount();
size_t GetIndexCount();
size_t GetVertexBufferSize();
size_t GetIndexBufferSize();
const common::BOX & GetBoundingBox();
float GetBoundingSphereRadius();
//...
};
class D3dMeshFromFile : public D3dMesh
{
public:
D3dMeshFromFile(const tstring &Name, const tstring &FileName);
//...
};
Jak wygląda wczytywanie zasobu, pokażę na przykładzie tekstury wczytywanej z pliku. Ponieważ D3D nie jest wielowątkowy (a w każdym razie chroni swoje funkcje jednym wielkim wewnętrznym muteksem, tak że wywoływać jego funkcji z wielu wątków nie ma sensu), to wczytanie takiego zasobu polega na wczytaniu w tle pliku do pamięci i potem w OnLoadAfterBkg utworzeniu z tych danych tekstury Direct3D.
class D3dTextureFromFile : public D3dTexture
{
//...
private:
////// Do przekazywania od wątku ładującego do wątku głównego
std::vector<char> m_FileData;
common::scoped_ptr<D3DXIMAGE_INFO> m_ImageInfo;
void ClearLoadingInternals();
};
void D3dTextureFromFile::OnLoad()
{
}
void D3dTextureFromFile::GetLoadType(
bool *OutUseBkg, BkgJob::TYPE *OutBkgJobType)
{
*OutUseBkg = true;
*OutBkgJobType = BkgJob::TYPE_IO;
}
void D3dTextureFromFile::OnLoadBkg()
{
common::FileStream fs(m_FileName, common::FM_READ);
m_FileData.resize(fs.GetSize());
fs.MustRead(&m_FileData[0], m_FileData.size());
if (m_Type == (D3DRESOURCETYPE)0)
{
D3DXIMAGE_INFO *Info = new D3DXIMAGE_INFO;
D3DXGetImageInfoFromFileInMemory(&m_FileData[0], m_FileData.size(), Info);
m_ImageInfo.reset(Info);
}
}
void D3dTextureFromFile::OnLoadAfterBkg()
{
if (m_ImageInfo != NULL)
m_Type = m_ImageInfo->ResourceType;
switch (m_Type)
{
case D3DRTYPE_TEXTURE:
{
IDirect3DTexture9 *Texture;
D3DXCreateTextureFromFileInMemoryEx(
frame::Dev,
&m_FileData[0],
m_FileData.size(),
m_Width, m_Height, m_MipLevels, m_Usage, m_Format, m_Pool, m_Filter,
m_MipFilter, m_ColorKey,
NULL, NULL, &Texture);
m_BaseTexture.reset(Texture);
m_Texture = Texture;
}
break;
case D3DRTYPE_VOLUMETEXTURE:
// Analogicznie...
case D3DRTYPE_CUBETEXTURE:
// Analogicznie...
}
ClearLoadingInternals();
}
void D3dTextureFromFile::ClearLoadingInternals()
{
m_FileData.swap(std::vector<char>()); // Swap trick [Meyers]
m_ImageInfo.reset();
}
Comments | #algorithms #c++ #engine Share