Czy istnieją przypadki, w których globale / singletony są przydatne w tworzeniu gier? [Zamknięte]

11

Wiem, że posiadanie zmiennych globalnych lub klas singletonowych tworzy przypadki, które mogą być trudne do przetestowania / zarządzania, i byłem przyłapany na używaniu tych wzorców w kodzie, ale często trzeba je wysyłać.

Czy są więc przypadki, w których zmienne globalne lub singletony są rzeczywiście przydatne w tworzeniu gier?

Lalafur Waage
źródło

Odpowiedzi:

30

Te rzeczy zawsze mogą być przydatne . To, czy jest to najładniejsze czy najbezpieczniejsze rozwiązanie, to inna sprawa, ale myślę, że tworzenie gier wymaga pewnego stopnia pragmatyzmu.

JasonD
źródło
Bardzo blisko mojej mantry.
Ólafur Waage
15

Zdecydowanie niewyczerpująca lista, ale oto:

Cons

  • Zarządzanie przez całe życie . Singletony i globale mogą chcieć się uruchomić przed zainicjowaniem kluczowych systemów (np. Sterty), w zależności od ich konfiguracji. Jeśli kiedykolwiek chcesz je zburzyć (przydatne na przykład podczas śledzenia wycieków), musisz uważać na kolejność porzucenia lub zacząć wchodzić w takie rzeczy jak singletony feniksa . (Zobacz fiasko kolejności inicjalizacji statycznej .)
  • Kontrola dostępu . Czy renderowanie powinno mieć tylko stały dostęp do danych gry, podczas gdy aktualizacja nie jest stała? Egzekwowanie tego w przypadku singletonów i globałów może być trudniejsze.
  • Stan . Jak zauważył Kaj, trudniej jest funkcjonalnie rozkładać i przekształcać singletony, aby w ogóle działały w architekturze pamięci współdzielonej. Ma to również potencjalny wpływ na wydajność dla innych typów systemów NUMA (dostęp do pamięci, która nie jest lokalna). Singletony zwykle reprezentują stan scentralizowany, a zatem są antytezą transformacji, które są łatwiejsze dzięki czystości.

Albo za albo przeciw

  • Współbieżności . W współbieżnym środowisku singletony mogą być zarówno uciążliwe (należy wziąć pod uwagę kwestie związane z wyścigiem danych / ponownym przeniesieniem), jak i błogosławieństwem (łatwiej jest scentralizować i uzasadnić blokowanie i zarządzanie zasobami). Sprytne użycie takich rzeczy jak lokalne przechowywanie wątków może nieco złagodzić potencjalne problemy, ale zwykle nie jest to łatwy problem.
  • Codegen (w zależności od kompilatora, architektury itp.): W przypadku naiwnej implementacji singletonu „twórz przy pierwszym użyciu” możesz oceniać dodatkową gałąź warunkową dla każdego dostępu. (Można dodać.) Wiele globałów używanych w funkcji może powiększać pulę literałów . Podejście „struct wszystkich globałów” może zaoszczędzić miejsce w literalnej puli: tylko jeden wpis do podstawy struktury, a następnie przesunięcia zakodowane w instrukcjach ładowania. Wreszcie, jeśli za wszelką cenę unikasz globałów i singletonów, zazwyczaj musisz użyć przynajmniej trochę dodatkowej pamięci (rejestrów, stosu lub stosu) do przekazywania wskaźników, referencji, a nawet kopiowania.

Plusy

  • Prostota . Jeśli wiesz, że wymienione powyżej minusy nie wpływają na ciebie tak bardzo (np. Pracujesz w środowisku jednowątkowym dla pojedynczej platformy podręcznej z procesorem), unikniesz pewnej architektury (takiej jak wspomniane przekazywanie argumentów). Singletony i globale mogą być łatwiejsze do zrozumienia dla mniej doświadczonych programistów (chociaż może być łatwiej używać ich nieprawidłowo).
  • Dożywotnie zarządzanie (ponownie). Jeśli używasz „struktury globali” lub jakiegoś mechanizmu innego niż tworzenie na pierwsze żądanie, możesz mieć łatwą do odczytania i precyzyjną kontrolę nad kolejnością inicjowania i niszczenia. Automatyzujesz to do pewnego stopnia lub zarządzasz nim ręcznie (konieczność zarządzania może być za lub przeciw, w zależności od liczby globali / singletonów i ich wzajemnych zależności).

W naszych przenośnych tytułach często używamy „struktury globalnych singletonów”. Tytuły na komputery PC i konsole zwykle polegają na nich mniej; przestawimy się bardziej na architekturę sterowaną zdarzeniami / wiadomościami. To powiedziawszy, tytuły komputerów / konsoli nadal często używają centralnego menedżera tekstur; ponieważ zwykle obejmuje jeden wspólny zasób (pamięć tekstur), ma to dla nas sens.

Jeśli utrzymujesz swój interfejs API względnie czysty, może nie być zbyt strasznie trudne zreformowanie (lub włączenie!) Wzorca singletonu, kiedy potrzebujesz ...

leander
źródło
Świetna lista. Osobiście uważam tylko wartość ujemną w singletonach i globalsach z powodu problemów, które pochodzą ze wspólnego (prawdopodobnie zmiennego) stanu. Nie uważam jednak problemu „codegen” za problem; albo musisz przekazać wskaźniki przez rejestry, albo musisz wykonać sekwencję operacji, aby je uzyskać, myślę, że zmienia to wizualnie kod względem rozmiaru danych, ale suma będzie mniej więcej taka sama.
dash-tom-bang
Tak, ponownie codegen, jedyną rzeczą, którą faktycznie widzieliśmy, która robi znaczącą różnicę, było przejście od pojedynczych globali do globalizacji: zmniejszyło to dosłowne pule, a zatem rozmiar sekcji .text, o kilka procent. Co jest bardzo miłe na małych systemach. =) Korzyści płynące z niewydajności pamięci podręcznej dla literałów były po prostu przyjemną (choć w większości przypadków niewielką) korzyścią dodatkową. Kolejną zaletą przejścia do globalnego stołu była możliwość bardzo łatwego przeniesienia go do sekcji, która znajdowała się w szybszej pamięci ...
leander
4

Mogą być bardzo przydatne, szczególnie podczas prototypowania lub eksperymentalnej implementacji, ale ogólnie wolimy przekazywanie referencji dla struktur podobnych do menedżerów, w ten sposób masz przynajmniej kontrolę nad tym, skąd są dostępne. Największym problemem z globals i singletonami (moim zdaniem) jest to, że nie są one bardzo przyjazne dla wielowątkowości, a kod, który ich używa, jest znacznie trudniejszy do przeniesienia do pamięci nieudostępnionej, takiej jak SPU.

Kaj
źródło
2

Powiedziałbym, że sam projekt singletonu wcale nie jest użyteczny. Zmienne globalne mogą być z pewnością przydatne, ale wolałbym, aby były ukryte za dobrze napisanym interfejsem, abyś nie był świadomy ich istnienia. Dzięki singletonom jesteś zdecydowanie świadomy ich istnienia.

Często używam zmiennych globalnych do rzeczy, które wymagają dostępu przez cały silnik. Moje narzędzie wydajności jest dobrym przykładem, do którego dzwonię w całym silniku. Połączenia są proste; ProbeRegister (), ProbeHit () i ProbeScoped (). Ich rzeczywisty dostęp jest nieco trudniejszy i wykorzystuje niektóre zmienne globalne.

Szymon
źródło
2

Głównym problemem związanym z globaliami i źle wdrożonymi singletonami są niejasne błędy konstrukcyjne i dekonstrukcyjne.

Więc jeśli pracujesz z prymitywami, które nie mają tych problemów lub są bardzo świadome problemu ze wskaźnikiem. Następnie mogą być bezpiecznie używane.

Globale mają swoje miejsce, podobnie jak goto i nie powinny być odrzucane z ręki, ale powinny być używane ostrożnie.

Dobre wyjaśnienie znajduje się w Przewodniku po stylu Google C ++

Kimau
źródło
Nie zgadzam się, że to jest główny problem; „nie używaj globałów” sięga dalej niż konstruktory istnienia. Powiedziałbym, że są z nimi dwa główne problemy. Po pierwsze, komplikują rozumowanie dotyczące programu. Jeśli mam funkcję, która uzyskuje dostęp do globalnej, jej zachowanie zależy nie tylko od jej własnych argumentów, ale także od wszystkich innych funkcji, które uzyskują dostęp do globalnej. To o wiele więcej do przemyślenia. Po drugie, nawet jeśli potrafisz trzymać to wszystko w głowie (nie możesz), nadal powodują one bardziej skomplikowane problemy z współbieżnością.
@Joe kluczowym słowem jest „zależności”. Funkcja, która uzyskuje dostęp do globałów (lub singletonów lub dowolnego innego wspólnego stanu), ma ukryte zależności od wszystkich tych rzeczy. O wiele łatwiej jest wnioskować o odrobinie kodu, jeśli wszystkie jego zależności są jawne, co można uzyskać, gdy pełna lista zależności jest udokumentowana na liście argumentów.
dash-tom-bang
1

Globalne są przydatne podczas szybkiego prototypowania systemu, który wymaga pewnego stanu między wywołaniami funkcji. Po ustaleniu, że system działa, przenieś stan do klasy i przekształć funkcje w metody tej klasy.

Singletony są przydatne do powodowania problemów sobie i innym. Im bardziej globalny stan wprowadzasz, tym więcej problemów będziesz mieć z poprawnością kodu, obsługą, rozszerzalnością, współbieżnością itp. Po prostu nie rób tego.

Kylotan
źródło
1

Zwykle zalecam używanie pewnego rodzaju kontenera DI / IoC z niestandardowym zarządzaniem cyklem życia zamiast singletonów (nawet jeśli używasz menedżera czasu życia „pojedynczej instancji”). Przynajmniej wtedy łatwo jest zamienić implementację, aby ułatwić testowanie.

Mike Strobel
źródło
Dla tych z was, którzy nie wiedzą, DI to „Wstrzykiwanie zależności”, a IoC to „Odwrócenie kontroli”. Link: martinfowler.com/articles/injection.html (Czytałem o nich wcześniej, ale nigdy nie widziałem akronimu, więc trochę mnie to zajęło.) +1 za wspomnienie o tej doskonałej transformacji.
leander
0

Singletony to świetny sposób na przechowywanie stanu wspólnego we wczesnym prototypie.

Nie są srebrną kulą i mają pewne problemy, ale są bardzo przydatnym wzorem dla niektórych stanów interfejsu / logiki.

Na przykład w systemie iOS używasz singletonów do uzyskania [UIApplication sharedApplication], w cocos2d możesz użyć go do uzyskania referencji do niektórych obiektów, takich jak [CCNotifications sharedManager], a osobiście zwykle zaczynam od singletonu [Game sharedGame], w którym mogę stan magazynu, który jest współużytkowany przez wiele różnych składników.

DFectuoso
źródło
0

Wow, to mnie interesuje, ponieważ nigdy osobiście nie miałem problemu ze wzorem singletonów. Mój obecny projekt to silnik gry C ++ na konsolę Nintendo DS, a ja implementuję wiele narzędzi dostępu do sprzętu (tj. VRAM Banks, Wifi, dwa silniki graficzne) jako instancje singletonów, ponieważ mają one na celu owijanie globalnego C funkcje w podstawowej bibliotece.


źródło
Moje pytanie brzmiałoby: po co w ogóle używać singletonów? Widzę, że ludzie lubią pakować API w singletony lub klasy, które składają się tylko z funkcji statycznych, ale po co w ogóle zawracać sobie głowę klasą? Wydaje mi się, że jeszcze łatwiej jest mieć wywołania funkcji (opakowane według własnego uznania), ale potem dostęp wewnętrzny do stanu „globalnego”. Np. Log :: GetInstance () -> LogError (...) równie dobrze może być LogError (...), który wewnętrznie uzyskuje dostęp do dowolnego stanu globalnego bez konieczności znajomości kodu klienta.
dash-tom-bang
0

Tylko wtedy, gdy masz element, który ma tylko jeden kontroler, ale jest obsługiwany przez kilka modułów.

Takich jak na przykład interfejs myszy. Lub interfejs joysticka. Lub odtwarzacz muzyki. Lub odtwarzacz dźwięku. Lub ekran. Lub menedżer zapisywania plików.

zaratustra
źródło
-1

Globale są znacznie szybsze! Idealnie pasowałoby do aplikacji wymagających dużej wydajności, takich jak gry.

Singletony są lepszym globalnym, IMO, a zatem właściwym narzędziem.

Używaj go oszczędnie!

jacmoe
źródło
Znacznie szybciej niż co ? Globale będą na losowej stronie pamięci, jeśli są wskaźnikiem, a niektóre stałe, ale prawdopodobnie odległe strony pamięci, jeśli są wartością. Kompilator nie może używać na nich żadnych przydatnych optymalizacji wizjera. Pod każdym względem mogę myśleć, globały są powolne .
Szybszy niż pobierający i ustawiający oraz przekazujący referencje. Ale niewiele. Pomaga zmniejszyć rozmiary, więc pomógłby w niektórych systemach. Prawdopodobnie przesadziłem, kiedy powiedziałem „dużo”, ale jestem bardzo sceptyczny wobec każdego, kto mówi, że nie powinieneś czegoś używać. Musisz tylko kierować się zdrowym rozsądkiem. W końcu singletony są niczym więcej niż statycznym członkiem klasy, a są to globale.
jacmoe,
Ale aby rozwiązać wszelkie problemy z synchronizacją z globalnymi, potrzebujesz do nich getterów / setterów, więc to nie jest tak naprawdę istotne. Problem z (i rzadką zaletą) stanem globalnym polega na tym, że jest to stan globalny, a nie obawy o szybkość lub (efektywnie) aspekty składni interfejsu.