To może po kolei, bo się jakiś tumult zrobił w temacie :). Ten wątek jest niejako powiązany z innym zagadnieniem (Struktura danych bez ECS w silniku gry) i dotyczy sposobu przechowywania danych wewnątrz gry.
U mnie takie paczki danych, będące aktualnie allokowane w pamięci to Levele
, stąd nazwa klasy. Generalnie zastanawiałem się, jak zbudować taką strukturę, która przechowuje obiekty różnego typu i jednocześnie nie robić tego przez staroszkolne i statyczne dziedziczenie, w stylu:
class LevelBase
{
protected:
virtual void Load() = 0;
}
class Level : public LevelBase
{
private:
void Load() override
{
}
public:
struct
{
Obj_Skeleton* Skeleton = nullptr;
Obj_Orc* Orc = nullptr;
Obj_Dragon Dragon* = nullptr;
} Enemies;
struct
{
Obj_Merchant* Merchant = nullptr;
Obj_Guard* Guard = nullptr;
} NPC;
}
//itd.
Nie uśmiechało mi się projektowanie wielu takich struktur dla każdej gry z osobna (a jednocześnie nie chciałem korzystać z ECS), więc zacząłem kombinować z czymś bardziej dynamicznym. No i wymyśliłem, klasa szablonowa, która jako argumenty przyjmuje dowolną liczbę typów, a potem tworzy dla każdego własną listę generycznych wskaźników, które można allokować na dowolny typ polimorficzny i potem odpowiednio rzutować:
level.Get<Skeleton>(0) //Dzięki szablonowemu argumentowi <Skeleton> klasa wie na jakiej liście obiektów
//ma szukać (bo każdy typ ma nadany dynamicznie, własny identyfikator) i dodatkowo na jaki typ ma rzutować zwracany obiekt
Funkcjonalne i całkiem eleganckie, ale zabrakło mi jednej, bardzo ważnej rzeczy - leniwej allokacji pamięci. Tworząc grę, chciałbym móc przygotować poziomy wcześniej, np.:
Level<Orc, Skeleton, Sword> Level01;
Level01.AllocateObjectGroup<Orc>(10) //Allokuje generyczną tablicę (Object*[0]) o wielkości 10 rekordów.
Level01.AllocateObjectGroup<Sword>(2) //Allokuje generyczną tablicę (Object*[1]) o wielkości 2 rekordów.
Level01.AllocateObject<Orc>(0) //Allokuje generyczny wskaźnik (Object*) w pierwszym rekordzie indywidualnej tablicy (Object[0][0]) na typ Orc.
Level01.AllocateObject<Orc>(1) //Allokuje generyczny wskaźnik (Object*) w drugim rekordzie indywidualnej tablicy (Object[0][1]) na typ Orc.
itd.
Potem tak samo dla levelu 2, 3, 5... 176 itd. Tyle że nie będę przecież trzymał tych wszystkich poziomów w pamięci jednocześnie, więc muszę mieć możliwość ich dowolnej allokacji i zwalniania w miarę potrzeb. No ale tablice operują na typach generycznych Object*
, więc po zwolnieniu pamięci nie jestem w stanie odtworzyć ich poprzedniego typu i stąd mój problem.
No ale już prawie go rozwiązałem. Zrobiłem wersję, która zamiast generycznych tablic używa twardych typów popakowanych w krotkę (std::tuple
). Testuję też możliwość przechowywania typów przy pomocy wrapperów na właściwe typy ObjectWrapper<Object>
. Jak skończę, wrzucę kod na jeden i drugi sposób, żeby domknąć temat.
Tak czy inaczej, dzięki wszystkim za sugestie i burzę mózgów, wiele postów posłużyło mi za dobrą wskazówkę jak się z problemem uporać.