Autor Wątek: wzorzec fasadowy i powiazania obiektow  (Przeczytany 6914 razy)

upshader

  • Gość
# Maj 13, 2007, 22:53:53
Witam,
Tak wlasnie projektuje swoj silnik i napotkalem na pewien problem. Mianowicie silnik podzielilem na nastepujace moduly:
-graficzny (wszystko co zwiazane z wizualizacja, wyswietlaniem grafiki)
-logiczny (przesuwanie obiektow, ale takze np. odnajdowanie drogi, proste kolizje)
-zarzadzania oknem
-input
-audio
-main (m. in. router dla komunikatow miedzy modulami).

Kazdy z modulow bedzie podlegal hermetyzacji i dostep do niego bedzie tylko i wylacznie poprzez klase fasadowa (wzorzec projektowy fasada).

Ktos kto bedzie korzystal z silnika doda najpierw jakis obiekt do modulu graficznego:
FasadaGrafiki->DodajObiekt(ID,kolor,rozmiar);

Potem doda jakis obiekt do modulu logicznego:
FasadaLogiki->DodajObiekt(ID,x,y,z);
No i mam w swoim module logicznym ten obiekt logiczny - nazwijmy go logicalObject. Dostaje on jakies komunikaty... przesuwam go sobie ladnie i w pewnym momencie chce go sobie wyswietlic. Wiec robie cos w stylu:
FasadaGrafiki->DodajObiektDoKolejkiRenderowania(ID,logicalObject);

Dzieki temu modul graficzny ma wiedziec, ze ten obiekt ktory wlasnie przyszedl do kolejki renderowania to jest ten sam obiekt ktory wczesniej zostal do niego dodany z zewnatrz poprzez "FasadaGrafiki->DodajObiekt(ID,kolor,rozmiar);" (ID sie zgadza). A poniewaz logicalObiect ma x,y,z to modul graficzny wie w ktorym miejscu go narysowac.

No i doszedlem do tego miejsca i stwierdzilem, ze to chyba nie jest zgodne z cala filozofia fasad. Bo przeciez modul graficzny widzi obiekt logicalObject - tzn. musi wiedziec jak go uzywac, ze ma np. wywolac logicalObject->GetX() zeby pobrac z niego wspolrzedna x itd. ale modul graficzny mial "widziec" jedynie swoja fasade!
A przy takim podejsciu to on widzi i uzywa konkretne mniejsze obiekty z innych modulow. Co z tego, ze zostaly one przekazane do niego przez fasade? To sprzeczne z zalozeniami. Jak to rozwiazac?

Z drugiej strony modul graficzny musi wiedziec gdzie narysowac dany obiekt. Do tego musi dostac jakis obiekt informujacy o polozeniu. Ten obiekt informujacy o polozeniu z kolei, z zalozenia nalezy do modulu logicznego. Widze tutaj taki paradoks ;/ pomozcie
« Ostatnia zmiana: Maj 13, 2007, 22:55:33 wysłana przez upshader »

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline Krzysiek K.

  • Redaktor
    • DevKK.net

# Maj 13, 2007, 23:59:19
Cytuj
Kazdy z modulow bedzie podlegal hermetyzacji i dostep do niego bedzie tylko i wylacznie poprzez klase fasadowa (wzorzec projektowy fasada).
Proponuję zrobienie kilku klas w zależności od potrzeb (przykładowo IRenderer, ISceneNode, itp). To jest w sumie oczywiste, ale że napisałeś jedna klasa na moduł, to wolę o tym przypomnieć. :)

Cytuj
Ktos kto bedzie korzystal z silnika doda najpierw jakis obiekt do modulu graficznego:
FasadaGrafiki->DodajObiekt(ID,kolor,rozmiar);
Właśnie w takiej sytuacji widziałbym coś w stylu Renderer->CreateObject(...), który zwraca utworzony interfejs (czy tam fasadę) oczekiwanego obiektu (albo zwraca NULL lub rzuca wyjątkiem, jak wolisz). Dalsze operacje na tym obiekcie mogą być wykonywane już wyłacznie przez interfejs obiektu (np. object->SetPosition(...) ). :)

Cytuj
FasadaGrafiki->DodajObiektDoKolejkiRenderowania(ID,logicalObject);
Kolejka renderowania powinna byc IMO niewidoczna dla reszty świata. Z punktu widzenia użytkownika silnika graficznego, tworzy się obiekt, przesuwa się go i wywołuje od czasu do czasu renderowanie sceny. Jak to jest w środku zrobione to już wyłącznie kwestia silnika graficznego (np. w przypadku raytracera nie miałbys kolejki renderowania a jedynie jakieś octtree na obiekty). :)

Cytuj
Dzieki temu modul graficzny ma wiedziec, ze ten obiekt ktory wlasnie przyszedl do kolejki renderowania to jest ten sam obiekt ktory wczesniej zostal do niego dodany z zewnatrz poprzez "FasadaGrafiki->DodajObiekt(ID,kolor,rozmiar);" (ID sie zgadza). A poniewaz logicalObiect ma x,y,z to modul graficzny wie w ktorym miejscu go narysowac.
Zamiast ID'ków radzę używać klas interfejsów i wskaźników. :)

Cytuj
No i doszedlem do tego miejsca i stwierdzilem, ze to chyba nie jest zgodne z cala filozofia fasad. Bo przeciez modul graficzny widzi obiekt logicalObject - tzn. musi wiedziec jak go uzywac, ze ma np. wywolac logicalObject->GetX() zeby pobrac z niego wspolrzedna x itd. ale modul graficzny mial "widziec" jedynie swoja fasade!
A przy takim podejsciu to on widzi i uzywa konkretne mniejsze obiekty z innych modulow. Co z tego, ze zostaly one przekazane do niego przez fasade? To sprzeczne z zalozeniami. Jak to rozwiazac?
Można określić hierarchię zależności modułów, czyli który z którego ma prawo korzystać. Przykładowo, wszystkie moduły mają prawo korzystać z filesystemu (do ładowania zasobów) i konsoli (do komunikatów i konfiguracji). Rozwiązaniem Twojego problemu jest założenie, że moduł logiczny korzysta z modułu renderowania - utworzenie obiektu logicznego powoduje automatyczne utworzenie obiektu w module graficznym, o ile tylko utworzony obiekt jest widoczny. Podobnie przesunięcie obiektu, czy wyliczenie następnej klatki symulacji modułu logicznego powodowało by automatyczne uaktualnienie pozycji obiektów w module renderowania. Przynajmniej ja bym to tak widział. :)

Mówiąc krócej - coż z czegoś musi korzystać, bo inaczej w wyniku otrzymasz kilka całkowicie niezależnych modułów. Chodzi tu o to, żeby moduły komunikowały się z sensem. :)

upshader

  • Gość
# Maj 14, 2007, 00:54:43
Renderer->CreateObject(...),
Ale z jakiej racji w rendererze mialbym tworzyc moje obiekty gry? To system logiczny przechowuje obiekty tworzy i zarzadza nimi... renderer sluzy tylko do rysowania.

Offline Krzysiek K.

  • Redaktor
    • DevKK.net

# Maj 14, 2007, 01:26:32
Ale z jakiej racji w rendererze mialbym tworzyc moje obiekty gry? To system logiczny przechowuje obiekty tworzy i zarzadza nimi... renderer sluzy tylko do rysowania.
Nie tworzysz obiektów gry, tylko ich graficzne reprezentacje. Obiekty gry tworzysz w module logiki. Jednym z atrybutów tego obiektu jest jego graficzna reprezentacja, czyli wskaźnik na ISceneNode reprezentujący ten obiekt w rendererze. Przy przemieszczaniu obiektu gry logika automatycznie powinna aktualizować jego reprezentację graficzną. Mówiąc prościej, obiekt logiczny pozwala na przypięcie do niego obiektu graficznego. :)

upshader

  • Gość
# Maj 14, 2007, 02:30:20
OK, ale wtedy przy renderowaniu ta "graficzna reprezentacja" musi widziec obiekt logiczny - bo w nim jest zawarte x,y,z - a musi to wiedziec zeby wiedziec gdzie sie narysowac. Ew. mozna te dane zdublowac i miec x,y,z (oraz inne podobne pola) w obu czesciach obiektu czyli w czesci glownej oraz w "graficznej reprezentacji". To chyba nie powinno tak byc?

upshader

  • Gość
# Maj 14, 2007, 03:10:55
Jeśli podsystemy muszą widzieć pewne wspólne struktury to po prostu je wydziel i używaj. Podkreślam struktury (w sensie struktury danych -- analiza strukturalna się kłania) a nie obiekty lub moduły.

Offline Krzysiek K.

  • Redaktor
    • DevKK.net

# Maj 14, 2007, 03:11:23
OK, ale wtedy przy renderowaniu ta "graficzna reprezentacja" musi widziec obiekt logiczny - bo w nim jest zawarte x,y,z - a musi to wiedziec zeby wiedziec gdzie sie narysowac. Ew. mozna te dane zdublowac i miec x,y,z (oraz inne podobne pola) w obu czesciach obiektu czyli w czesci glownej oraz w "graficznej reprezentacji". To chyba nie powinno tak byc?
Dlaczego nie powinno? W jakiś sposób musisz połączyć renderer z logiką i w ogólności masz tylko dwa sposoby do wyboru:
- dokarmianie renderera - renderer trzyma własne dane o obiekcie (wygląd i położenie), a logika je ciągle aktualizuje, żeby zgadzały się z danymi w logice,
- samokarmiący się renderer - renderer z własnej inicjatywy uzyskuje aktualne współrzędne i tym podobne dane; mozna zakodować to "na stałe" (ale wtedy właśnie renderer staje się przypięty do logiki), albo zastosować fasadę w drugą stronę, na przykład przez jakiś wirtualny interfejs IPositionProvider, który rejestruje się w rendererze i który renderer może w każdej chwili odpytać o interesujące go dane - IMHO gra nie warta świeczki

Jak widać, najprostszym sposobem jest zdublowanie części danych i ich aktualizowanie w rendererze co klatkę. :)

upshader

  • Gość
# Maj 14, 2007, 03:20:37
Jak widać, najprostszym sposobem jest zdublowanie części danych i ich aktualizowanie w rendererze co klatkę. :)
Przeciez takich zdublowanych danych moga byc i dziesiatki megabajtow. Ograniczasz przez to spektrum komputerow na ktorych pojdzie Twoja gra.
Dla przykladu podaje bufor z wierzcholkami: renderer musi miec oczywiscie swoj zeby go rysowac, a system logiki swoj zeby sprawdzac kolizje triangle-triangle i inne rzeczy z nim robic.

Pomnoz spory bufor razy 10 rodzajow postaci i juz masz pare mega ;]

Offline Moriturius

  • Użytkownik

# Maj 14, 2007, 07:21:07
Dlaczego nie powinno? W jakiś sposób musisz połączyć renderer z logiką i w ogólności masz tylko dwa sposoby do wyboru:

można jeszcze użyć np. mediatora wtedy logika posle wiadomość mediatorowi, że zmieniła się pozycja jakiegoś obiektu a wtedy on zaktualizuje tą pozycję w rendererze.

Cytat: upshader
Pomnoz spory bufor razy 10 rodzajow postaci i juz masz pare mega ;]

w sumie, nie wszystko co jest dostępne w grze musi być załadowane ;)
a żeby zapełnić 1 MB pamięci kostkami opisanymi 6 intami [3 na polozenie, 3 na rozmiar; oczywiście zapisanymi w pamięci podwójnie] to i tak potrzebujesz ~21.845 takich kostek ;-) biorąc pod uwagę, że kompy w tej chwili poniżej 1GB RAMu nie schodzą, to mógłbyś w ten sposób zapisać  ~22.369.622 obiekty :P

Krzysiek K. nie miał na myśli dublowania buforów z meshami, tylko danych takich jak położenie itp.
« Ostatnia zmiana: Maj 14, 2007, 07:34:08 wysłana przez Moriturius »

Offline Krzysiek K.

  • Redaktor
    • DevKK.net

# Maj 14, 2007, 08:08:31
Cytuj
Przeciez takich zdublowanych danych moga byc i dziesiatki megabajtow. Ograniczasz przez to spektrum komputerow na ktorych pojdzie Twoja gra.
Dla przykladu podaje bufor z wierzcholkami: renderer musi miec oczywiscie swoj zeby go rysowac, a system logiki swoj zeby sprawdzac kolizje triangle-triangle i inne rzeczy z nim robic.
Przykład akurat nietrafiony, bo renderer chciałby miec tą geometrię na karcie, a moduł kolizji w pamięci systemowej i to najlepiej jeszcze w jakimś octtree, ale jeżeli chodzi o zasoby współdzielone pomiędzy modułami, to wystarczy stworzyć managera, który będzie odpowiedzialny za przechowywanie i udostępnianie tych zasobów. :)

Cytuj
można jeszcze użyć np. mediatora wtedy logika posle wiadomość mediatorowi, że zmieniła się pozycja jakiegoś obiektu a wtedy on zaktualizuje tą pozycję w rendererze.
To jest jedna z możliwych implementacji pierwszego sposobu. W ogólności wszystkie metody sprowadzają się do tego, że albo renderer będzie aktualizowany z zewnątrz, albo będzie sam uzyskiwał w razie potrzeby wymagane dane. :)

Offline Charibo

  • Redaktor

# Maj 14, 2007, 11:42:43
Cytuj
a żeby zapełnić 1 MB pamięci kostkami opisanymi 6 intami [3 na polozenie, 3 na rozmiar; oczywiście zapisanymi w pamięci podwójnie] to i tak potrzebujesz ~21.845 takich kostek ;-) biorąc pod uwagę, że kompy w tej chwili poniżej 1GB RAMu nie schodzą, to mógłbyś w ten sposób zapisać  ~22.369.622 obiekty
I zabijasz cache w sposob koncertowy :)

upshader

  • Gość
# Maj 14, 2007, 14:42:33
można jeszcze użyć np. mediatora wtedy logika posle wiadomość mediatorowi, że zmieniła się pozycja jakiegoś obiektu a wtedy on zaktualizuje tą pozycję w rendererze..
Ooo. O cos takiego mi chodzilo. Dobry pomysl. A czy taki mediator moglby nalezec do ktoregos z modulow czy musialby byc pomiedzy nimi? Staram sie rozplanowac wszystko tak, aby wszystkie klasy nalezaly do jakichs modulow i nie bylo takich luzych. Wiem, ze to pytanie jest abstrakcyjne, i odpowiedz na nie zapewne brzmi "mozesz sobie zrobic jak chcesz", ale chcialbym wiedziec jak bedzie super poprawnie pod wzgledem projektowym, logicznym.

Cytuj
żeby zapełnić 1 MB pamięci kostkami opisanymi 6 intami [3 na polozenie, 3 na rozmiar; oczywiście zapisanymi w pamięci podwójnie] to i tak potrzebujesz ~21.845 takich kostek
Dlaczego mowisz o kostkach? Ja wspominalem o postaciach ;] to roznica. Powtorz kalkulacje dla postaci o 5000 vertexow z x,y,z,w,nx,ny,nz,c,u1,v1,u2,v2,u3,v3 (dodatkowe u,v sa dla normalmapy i bumpmapy jakbys sie pytal)

Cytuj
Przykład akurat nietrafiony, bo renderer chciałby miec tą geometrię na karcie, a moduł kolizji w pamięci systemowej
Tak jak mowilem rozmawiamy o kolizjach traingle-triangle. Jesli animuje postac to i tak musze zrobic jej lock co klatke zeby zaktualizowac geometrie, i wtedy moge sobie sprawdzic kolizje.
Nie wydaje mi sie, abym animacje mogl robic w vertex shaderze bo wtedy nie mialbym aktualnych pozycji wierzcholkow do sprawdzenia kolizji tri-tri?
« Ostatnia zmiana: Maj 14, 2007, 14:51:05 wysłana przez upshader »

Offline Krzysiek K.

  • Redaktor
    • DevKK.net

# Maj 14, 2007, 15:15:43
Cytuj
Tak jak mowilem rozmawiamy o kolizjach traingle-triangle. Jesli animuje postac to i tak musze zrobic jej lock co klatke zeby zaktualizowac geometrie, i wtedy moge sobie sprawdzic kolizje.
Nie wydaje mi sie, abym animacje mogl robic w vertex shaderze bo wtedy nie mialbym aktualnych pozycji wierzcholkow do sprawdzenia kolizji tri-tri?
Zazwyczaj animację robi się w vertex shaderze na kościach, a z punktu widzenia fizyki i kolizji kości są pudełkami albo kapsułami. :)

Offline Esidar

  • Użytkownik

# Maj 14, 2007, 21:35:14
W niektórych grach (np Dungeon Siege) stosuje się Dynamic Component Model. Każdy obiekt w grze składa się z dynamicznie dołączanych komponentów które obsługują różne cechy obiektu.
Zaczynasz od "gołego" obiektu do którego dołączasz komponent "Render". Ten komponent na starcie dołącza do tego obiektu, komponent "Location" który zawiera pozycję i orientację obiektu. Potem dołączasz komponent "Physics". Ten komponent też próbuje dołączyć komponent "Location" ale skoro już taki jest, to będzie korzystać z już istniejącego. Potem możesz dołączyć komponent "AIControl" jeśli tym obiektem ma być np. przeciwnik. I ten komponent też może próbować dołączyć "Location" jeśli potrzebuje. I tak dalej. Możesz dołączać komponenty w zależności od sposobu użycia tego obiektu. W ten sposób rozwiązujesz problem wspólnych danych które muszą być wymieniane między niezależnymi częściami / komponentami.
Ten model daje ten plus, że działanie i konstrukcję obiektu możesz robić w czasie działania kodu np. w edytorze.
Ten model ma taki minus, że jest cholernie ciężki do stworzenia :) Ale skoro chcesz się bawić w pełną enkapsulacje i używać do wszystkiego design patterns, to pewnie ten model pozwoli Ci wyeliminować wiele problemów.

upshader

  • Gość
# Maj 14, 2007, 22:31:11
To nadal nie odpowiada na pytanie do ktorego modulu (podsystemu) mialby nalezec komponent location. Bo render to wiadomo do graficznego, physics do fizycznego (czy tam u mnie logicznego jako iz praktycznie nie bede mial fizyki), AIControl do podsystemu sztucznej inteligencji (u mnie znow logiczny) ale gdzie zakwalifikowac location?
Jak wspomniales chce miec pelna enkapsulacje :)