Może to być łatwe w przypadku gier o ściśle określonym zakresie, ale pytanie dotyczy gier typu sandbox, w których gracz może tworzyć i budować wszystko .
Możliwe techniki:
- Użyj pul pamięci z górnym limitem.
- Usuń obiekty, które nie są już potrzebne okresowo.
- Przydziel dodatkową pamięć na początku, aby można ją było później zwolnić jako mechanizm odzyskiwania. Powiedziałbym, że około 2-4 MB.
Jest to bardziej prawdopodobne na platformach mobilnych / konsolowych, w których pamięć jest zwykle ograniczona w przeciwieństwie do komputera 16 GB. Zakładam, że masz pełną kontrolę nad przydzielaniem / zwalnianiem pamięci i nie wiąże się to z odśmiecaniem pamięci. Dlatego oznaczam to jako C ++.
Pamiętaj, że nie mówię o Effective C ++ pozycja 7 „Bądź przygotowany na warunki braku pamięci” , mimo że jest to istotne, chciałbym zobaczyć odpowiedź bardziej związaną z tworzeniem gier, w której zwykle masz większą kontrolę nad tym, co jest wydarzenie.
Podsumowując pytanie, w jaki sposób przygotowujesz się na warunki braku pamięci w grach piaskownicy, gdy celujesz w platformę z ograniczoną pamięcią konsoli / urządzenia mobilnego?
źródło
Odpowiedzi:
Zasadniczo nie radzisz sobie z brakiem pamięci. Jedyną rozsądną opcją w oprogramowaniu tak dużym i złożonym, jak gra, jest jak najszybsze zawieszenie / potwierdzenie / zakończenie pracy w alokatorze pamięci (szczególnie w kompilacjach debugowania). Warunki braku pamięci są testowane i obsługiwane w niektórych podstawowych programach systemowych lub oprogramowaniu serwerowym w niektórych przypadkach, ale zwykle nie w innym miejscu.
Gdy masz górny limit pamięci, po prostu upewnij się, że nigdy nie potrzebujesz więcej niż tyle pamięci. Na przykład możesz zatrzymać maksymalną liczbę dozwolonych NPC na raz i po prostu przestać pojawiać się nowych nieistotnych NPC po osiągnięciu tego limitu. W przypadku niezbędnych NPC możesz albo zastąpić nieistotnych, albo mieć osobną pulę / limit dla niezbędnych NPC, o których projektanci wiedzą, że projektują wokół (np. Jeśli możesz mieć tylko 3 niezbędne NPCsa, projektanci nie umieszczą więcej niż 3 w obszar / fragment - dobre narzędzia pomogą projektantom zrobić to poprawnie, a testowanie jest oczywiście niezbędne).
Naprawdę dobry system przesyłania strumieniowego jest również ważny, szczególnie w grach z piaskownicą. Nie musisz przechowywać w pamięci wszystkich NPC i przedmiotów. Podczas poruszania się po częściach świata nowe fragmenty będą przesyłane strumieniowo, a stare fragmenty przesyłane strumieniowo. Będą to na ogół NPC i przedmioty, a także teren. Należy wziąć pod uwagę ograniczenia projektowe i inżynierskie dotyczące limitów przedmiotów, mając na uwadze, że co najwyżej X starych kawałków zostanie zachowanych, a proaktywnie załadowane Y nowych kawałków, więc gra musi mieć miejsce na wszystko dane fragmentów X + Y + 1 w pamięci.
Niektóre gry próbują poradzić sobie z sytuacjami braku pamięci przy podejściu dwuprzebiegowym. Pamiętając, że większość gier ma wiele zbędnych technicznie zbuforowanych danych (powiedzmy, stare fragmenty wspomniane powyżej), a przydział pamięci może zrobić coś takiego:
Jest to ostatni krok do rozwiązania nieoczekiwanych sytuacji w wydaniu, ale podczas debugowania i testowania prawdopodobnie powinieneś natychmiast po prostu ulec awarii. Nie chcesz polegać na tego rodzaju rzeczach (szczególnie dlatego, że zrzucanie pamięci podręcznych może mieć poważne konsekwencje dla wydajności).
Możesz również rozważyć zrzucenie kopii niektórych danych w wysokiej rozdzielczości, na przykład możesz zrzucić poziomy tekstur mipmap w wyższej rozdzielczości, jeśli masz mało pamięci GPU (lub dowolnej pamięci w architekturze pamięci współdzielonej). Jednak zazwyczaj wymaga to dużo pracy architektonicznej.
Pamiętaj, że niektóre bardzo nieograniczone gry z piaskownicą można dość łatwo po prostu rozbić, nawet na PC (pamiętaj, że popularne 32-bitowe aplikacje mają limit 2-3 GB przestrzeni adresowej, nawet jeśli masz komputer z 128 GB pamięci RAM; 64- bit OS i sprzęt pozwala na jednoczesne działanie większej liczby 32-bitowych aplikacji, ale nie można nic zrobić, aby 32-bitowy plik binarny miał większą przestrzeń adresową). W końcu albo masz bardzo elastyczny świat gry, który będzie wymagał nieograniczonej przestrzeni pamięci do działania w każdym przypadku, albo masz bardzo ograniczony i kontrolowany świat, który zawsze działa idealnie w ograniczonej pamięci (lub coś gdzieś pomiędzy).
źródło
Aplikacja jest zwykle testowana na platformie docelowej z najgorszymi scenariuszami i zawsze będziesz przygotowany na platformę, na którą jesteś kierowany. Idealnie byłoby, gdyby aplikacja nigdy nie ulegała awarii, ale poza optymalizacją dla konkretnych urządzeń, wybór ostrzeżenia o niskim poziomie pamięci jest niewielki.
Najlepszą praktyką jest posiadanie wstępnie przydzielonych pul, a gra od samego początku wykorzystuje całą potrzebną pamięć. Jeśli twoja gra ma maksymalnie 100 jednostek, to masz pulę na 100 jednostek i to wszystko. Jeśli 100 jednostek przekroczy wymagania pamięci dla jednego docelowego urządzenia, możesz zoptymalizować jednostkę, aby zużywała mniej pamięci lub zmienić projekt na maksymalnie 90 jednostek. Nie powinno być przypadku, w którym można budować nieograniczoną liczbę rzeczy, zawsze powinien istnieć limit. Używanie gry piaskownicy
new
dla każdej instancji byłoby bardzo złe, ponieważ nigdy nie można przewidzieć użycia pamięci, a awaria jest znacznie gorsza niż ograniczenie.Również projekt gry powinien zawsze uwzględniać urządzenia o najniższym poziomie docelowym, ponieważ jeśli oprzesz swój projekt na „nieograniczonych” rzeczach, znacznie trudniej będzie rozwiązać problemy z pamięcią lub zmienić projekt później.
źródło
Cóż, możesz przydzielić około 16 MiB (żeby być w 100% pewnym) przy starcie lub nawet w
.bss
czasie kompilacji, i użyć „bezpiecznego alokatora” z podobną sygnaturąinline __attribute__((force_inline)) void* alloc(size_t size)
(__attribute__((force_inline))
to GCC /mingw-w64
atrybut, który wymusza wstawianie krytycznych sekcji kodu nawet jeśli optymalizacje są wyłączone, nawet jeśli powinny być włączone dla gier), zamiastmalloc
tego próbuje,void* result = malloc(size)
a jeśli się nie powiedzie, upuść pamięć podręczną, zwolnij wolną pamięć (lub powiedz innemu kodowi, aby użył tej.bss
rzeczy, ale to nie wchodzi w zakres tej odpowiedzi) i opróżnij niezapisane dane (uratuj świat na dysku, jeśli używasz koncepcji kawałków w stylu Minecraft, zadzwoń jakośsaveAllModifiedChunks()
). Następnie, jeślimalloc(16777216)
(ponowne przydzielenie tych 16 MiB) nie powiedzie się (ponownie, zamień na analogowy dla.bss
), zakończ grę i pokażMessageBox(NULL, "*game name* couldn't continue because of lack of free memory, but your world was safely saved. Try closing background applications and restarting the game", "*Game name*: out of memory", MB_ICONERROR)
lub alternatywa dla konkretnej platformy. Kładąc wszystko razem:Można użyć podobnego rozwiązania z
std::set_new_handler(myHandler)
którymmyHandler
jestvoid myHandler(void)
, że jest wywoływana gdynew
nie powiedzie się:źródło