Obecnie projektuję mój system Entity dla C ++ i mam wielu menedżerów. W moim projekcie mam te klasy, aby związać moją bibliotekę. Słyszałem wiele złych rzeczy, jeśli chodzi o zajęcia „menedżerskie”, być może nie nazywam odpowiednio moich zajęć. Nie mam jednak pojęcia, jak inaczej je nazwać.
Większość menedżerów w mojej bibliotece składa się z tych klas (choć nieco się różni):
- Kontener - pojemnik na obiekty w menedżerze
- Atrybuty - atrybuty obiektów w menedżerze
W nowym projekcie mojej biblioteki mam te konkretne klasy, aby związać moją bibliotekę.
ComponentManager - zarządza komponentami w systemie encji
- ComponentContainer
- ComponentAttributes
- Scena * - odniesienie do Sceny (patrz poniżej)
SystemManager - zarządza systemami w systemie Entity
- SystemContainer
- Scena * - odniesienie do Sceny (patrz poniżej)
EntityManager - zarządza jednostkami w Systemie Entity
- EntityPool - pula encji
- EntityAttributes - atrybuty bytu (będzie to dostępne tylko dla klas ComponentContainer i System)
- Scena * - odniesienie do Sceny (patrz poniżej)
Scena - łączy wszystkich menedżerów razem
- ComponentManager
- Menadżer systemu
- EntityManager
Myślałem o umieszczeniu wszystkich kontenerów / basenów w samej klasie Scene.
to znaczy
Zamiast tego:
Scene scene; // create a Scene
// NOTE:
// I technically could wrap this line in a createEntity() call in the Scene class
Entity entity = scene.getEntityManager().getPool().create();
Byłoby to:
Scene scene; // create a Scene
Entity entity = scene.getEntityPool().create();
Ale nie jestem pewien. Gdybym miał zrobić to drugie, oznaczałoby to, że miałbym wiele obiektów i metod zadeklarowanych w mojej klasie Scene.
UWAGI:
- System encji to po prostu projekt wykorzystywany w grach. Składa się z 3 głównych części: komponentów, bytów i systemów. Składniki to po prostu dane, które mogą być „dodawane” do bytów, aby były one odróżniające. Obiekt jest reprezentowany przez liczbę całkowitą. Systemy zawierają logikę jednostki z określonymi komponentami.
- Powodem, dla którego zmieniam projekt mojej biblioteki, jest to, że myślę, że można ją bardzo zmienić, w tej chwili nie lubię tego stylu.
źródło
Odpowiedzi:
DeadMG zajmuje się specyfiką twojego kodu, ale wydaje mi się, że brakuje mu wyjaśnienia. Nie zgadzam się również z niektórymi jego zaleceniami, które nie mają zastosowania w określonych kontekstach, takich jak na przykład tworzenie gier wideo o wysokiej wydajności. Ale na całym świecie ma rację, że większość twojego kodu nie jest teraz przydatna.
Jak mówi Dunk, klasy menedżerów są tak nazywane, ponieważ „zarządzają” rzeczami. „zarządzanie” jest jak „dane” lub „zrób”, to abstrakcyjne słowo, które może zawierać prawie wszystko.
Około 7 lat temu byłem głównie w tym samym miejscu, co ty, i zacząłem myśleć, że w moim sposobie myślenia było coś nie tak, ponieważ tyle wysiłku włożono w kodowanie, aby nic nie zrobić.
To, co zmieniłem, aby to naprawić, to zmiana słownictwa używanego w kodzie. Całkowicie unikam słów ogólnych (chyba że jest to ogólny kod, ale jest to rzadkie, gdy nie tworzysz bibliotek ogólnych). I unikać, aby wymienić każdy rodzaj „Manager” lub „obiekt”.
Wpływ jest bezpośredni w kodzie: zmusza cię do znalezienia odpowiedniego słowa odpowiadającego rzeczywistej odpowiedzialności twojego typu. Jeśli uważasz, że ten typ ma kilka rzeczy (prowadź indeks książek, utrzymuj je przy życiu, twórz / niszcz książki), potrzebujesz różnych typów, z których każdy będzie miał jedną odpowiedzialność, a następnie połączysz je w kodzie, który ich używa. Czasami potrzebuję fabryki, a czasem nie. Czasami potrzebuję rejestru, więc konfiguruję go (używając standardowych kontenerów i inteligentnych wskaźników). Czasami potrzebuję systemu, który składa się z kilku różnych podsystemów, więc odseparowuję wszystko tak bardzo, jak tylko mogę, aby każda część zrobiła coś pożytecznego.
Nigdy nie nazywaj typu „menedżerem” i upewnij się, że wszystkie typy mają jedną unikalną rolę, to moja rekomendacja. Czasami może być trudno znaleźć nazwiska, ale jest to jedna z najtrudniejszych rzeczy do zrobienia w programowaniu
źródło
dynamic_cast
osób zabija Twoją wydajność, użyj systemu typu statycznego - po to jest. To naprawdę wyspecjalizowany kontekst, a nie ogólny, aby stworzyć własne RTTI, tak jak zrobił to LLVM. Nawet wtedy nie robią tego.template <typename T> class Class { ... };
służy do przypisywania identyfikatorów różnym klasom (mianowicie niestandardowym komponentom i niestandardowym systemom). Przypisanie identyfikatora służy do przechowywania komponentów / systemów w wektorze, ponieważ mapa może nie zapewniać wydajności, której potrzebuję do gry.Cóż, przeczytałem część kodu, do którego linkujesz, i twój post, a moje szczere podsumowanie jest takie, że większość z nich jest w zasadzie całkowicie bezwartościowa. Przepraszam. To znaczy, masz cały ten kod, ale niczego nie osiągnąłeś . W ogóle. Będę musiał tu zagłębić się, więc proszę o wyrozumiałość.
Zacznijmy od ObjectFactory . Po pierwsze, wskaźniki funkcji. Są bezstanowe, jedynym przydatnym bezstanowym sposobem na stworzenie obiektu jest
new
i nie potrzebujesz do tego fabryki. Stanowe tworzenie obiektu jest przydatne, a wskaźniki funkcji nie są przydatne do tego zadania. A po drugie, każdy obiekt musi wiedzieć, w jaki sposób, aby zniszczyć sobie , nie mając do stwierdzenia, że dokładna tworząc instancję, aby je zniszczyć. Ponadto należy zwrócić inteligentny wskaźnik, a nie surowy wskaźnik, dla wyjątku i bezpieczeństwa zasobów użytkownika. Twoja cała klasa ClassRegistryData jest sprawiedliwastd::function<std::unique_ptr<Base, std::function<void(Base*)>>()>
, ale z wyżej wspomnianymi dziurami. To wcale nie musi istnieć.Następnie mamy AllocatorFunctor - są już dostępne standardowe interfejsy alokatora - Nie wspominając, że są one tylko opakowaniami
delete obj;
ireturn new T;
- które znowu są już dostarczone i nie będą oddziaływać z żadnymi stanowymi lub niestandardowymi alokatorami pamięci, które istnieją.A ClassRegistry jest po prostu,
std::map<std::string, std::function<std::unique_ptr<Base, std::function<void(Base*)>>()>>
ale napisałeś dla niej klasę zamiast korzystać z istniejącej funkcjonalności. To ten sam, ale całkowicie niepotrzebnie globalny, zmienny stan z garścią gównianych makr.Mówię tutaj o tym, że stworzyłeś pewne klasy, w których możesz zastąpić całość funkcjonalnością zbudowaną z klas Standardowych, a następnie pogorszysz je, czyniąc je globalnym stanem zmiennym i angażując kilka makr, które są najgorsze, co możesz zrobić.
Następnie mamy możliwość zidentyfikowania . Jest to bardzo podobna historia -
std::type_info
już teraz wykonuje zadanie identyfikacji każdego typu jako wartości czasu działania i nie wymaga nadmiernej płyty kotłowej ze strony użytkownika.Problem rozwiązany, ale bez żadnej z dwóch poprzednich linii makr i tak dalej. Oznacza to również, że twoja IdentifiableArray jest niczym, ale
std::vector
musisz użyć liczenia referencji, nawet jeśli unikatowa własność lub brak własności byłyby lepsze.Aha, i czy wspomniałem, że Typy zawierają mnóstwo absolutnie bezużytecznych typedefów? Dosłownie nie zyskujesz żadnej przewagi nad bezpośrednim użyciem typów surowych i tracisz dużą część czytelności.
Następny jest ComponentContainer . Nie możesz wybrać własności, nie możesz mieć oddzielnych kontenerów dla pochodnych klas różnych komponentów, nie możesz używać własnej semantyki. Usuń bez wyrzutów sumienia.
Teraz ComponentFilter może być trochę wart, jeśli bardzo go zrestrukturyzowałeś. Coś jak
Nie dotyczy to klas wywodzących się z tych, z którymi próbujesz sobie poradzić, ale twoje też nie.
Reszta to właściwie ta sama historia. Nie ma tu nic, czego nie można byłoby zrobić znacznie lepiej bez korzystania z biblioteki.
Jak uniknąłbyś menedżerów w tym kodzie? Usuń zasadniczo wszystko.
źródło
typeid
lubdynamic_cast
. Dziękuję za opinie, doceniam to.Problem z klasami Menedżera polega na tym, że menedżer może zrobić wszystko. Nie możesz odczytać nazwy klasy i wiedzieć, co robi klasa. EntityManager .... Wiem, że robi coś z Entami, ale kto wie co. SystemManager ... tak, zarządza systemem. To bardzo pomocne.
Domyślam się, że twoje zajęcia menedżerskie prawdopodobnie robią wiele rzeczy. Założę się, że jeśli podzielisz te rzeczy na ściśle powiązane elementy funkcjonalne, prawdopodobnie będziesz w stanie wymyślić nazwy klas związane z elementami funkcjonalnymi, aby każdy mógł powiedzieć, co robią, po prostu patrząc na nazwę klasy. To znacznie ułatwi utrzymanie i zrozumienie projektu.
źródło
Nie sądzę,
Manager
żeby w tym kontekście było tak źle, wzruszając ramionami. Jeśli jest jedno miejsce, w którym moim zdaniemManager
można wybaczyć, byłoby tak w przypadku systemu podmiot-komponent, ponieważ tak naprawdę „ zarządza ” żywotnością komponentów, bytów i systemów. Przynajmniej jest to tutaj bardziej znacząca nazwa, powiedzmy,Factory
co nie ma w tym kontekście tyle sensu, ponieważ ECS naprawdę robi coś więcej niż tylko tworzy rzeczy.Przypuszczam, że można użyć
Database
, jakComponentDatabase
. Lub możesz po prostu użyćComponents
,Systems
iEntities
(tego właśnie używam osobiście, głównie dlatego, że lubię zwięzłe identyfikatory, chociaż co prawda przekazuje jeszcze mniej informacji, być może, niż „Menedżer”).Jedna część, którą uważam za nieco niezdarną, to:
To jest wiele rzeczy do zrobienia,
get
zanim będziesz mógł utworzyć encję. Wygląda na to, że projekt nieco wycieka szczegóły implementacji, jeśli musisz wiedzieć o pulach encji i „menedżerach”, aby je utworzyć. Myślę, że rzeczy takie jak „pule” i „menedżerowie” (jeśli trzymasz się tej nazwy) powinny być szczegółami implementacyjnymi ECS, a nie czymś narażonym na klienta.Ale wiem, widziałem kilka bardzo niewyszukane i ogólnych zastosowań
Manager
,Handler
,Adapter
oraz nazwy tego rodzaju, ale myślę, że jest to stosunkowo wybaczalne przypadek użycia, gdy coś, co nazywa „Manager” jest rzeczywiście odpowiedzialny za „zarządzanie” ( "kontroli? „,” obchodzenie się? ”) żywotności przedmiotów, odpowiadanie za tworzenie i niszczenie oraz zapewnianie dostępu do nich indywidualnie. To jest dla mnie najbardziej proste i intuicyjne użycie „Managera”.To powiedziawszy, jedną ogólną strategią unikania „Managera” w twoim imieniu jest po prostu wyjęcie części „Managera” i dodanie
-s
na końcu. :-D Załóżmy na przykład, że maszThreadManager
i jest on odpowiedzialny za tworzenie wątków i dostarczanie informacji o nich oraz uzyskiwanie do nich dostępu i tak dalej, ale nie łączy puli wątków, więc nie możemy tego nazwaćThreadPool
. W takim razie po prostu to nazywajThreads
. I tak właśnie robię, kiedy nie mam wyobraźni.Przypominają mi się czasy, gdy analogiczny odpowiednik „Eksploratora Windows” nazywał się „Menedżerem plików”, tak jak:
I tak naprawdę uważam, że „Menedżer plików” w tym przypadku jest bardziej opisowy niż „Eksplorator Windows”, nawet jeśli istnieje lepsza nazwa, która nie obejmuje „Menedżera”. Więc przynajmniej nie bądź zbyt fantazyjny ze swoimi nazwiskami i zacznij nazywać takie rzeczy jak „ComponentExplorer”. „ComponentManager” pozwala mi przynajmniej zrozumieć, co robi ta osoba, gdy ktoś przywykł do pracy z silnikami ECS.
źródło
Manager
jest odpowiednia. Klasa może mieć problemy z projektowaniem , ale nazwa jest na miejscu