Manager zasobów #2 - Wczytywanie w tle

Uwaga! Informacje na tej stronie mają ponad 5 lat. Nadal je udostępniam, ale prawdopodobnie nie odzwierciedlają one mojej aktualnej wiedzy ani przekonań.

# Manager zasobów #2 - Wczytywanie w tle

12:23
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

Comments

STAT NO AD
[Stat] [STAT NO AD] [Download] [Dropbox] [pub] [Mirror]
Copyright © 2004-2017