This is a legacy Trac instance left read-only for reference purposes. More info. dev main | home

Propozycja interfejsu między WaterWorld a kodem zewnętrznym

Opisujemy tutaj szkic pliku nagłówkowego, który znajdzie się w naszym kodzie źródłowym, stanowiącego nasze API. Użytkownik pisze własne implementacje znajdujących się tutaj klas, przez co podpina do symulacji pożądane zachowania animatów i niektóre zasady panujące w świecie (na przykład pojawianie się nowego pożywienia).

Z poprzednich dyskusji wynika, że gdzie to tylko możliwe zamiast dziedziczenia powinniśmy wykorzystywać agregację.

Uwaga: jest to tylko opis interfejsu. Nie wchodzimy w szczegóły implementacyjne, które nie są częscią komunikacji naszego kodu z zewnętrznym. Tudzież dla skrócenia zapisu - wszystkie pola i metody są implicite public.

class ai
{
   void sense (vector<sensorStatus>);
   void actuate (vector<actuatorStatus>&);
};

struct sensorStatus
{
   const sensorInfo &info;
   float stimulation;
};

struct actuatorStatus
{
   const actuatorInfo &info;
   float power;
};

To tylko taka wstępna propozycja, na ten moment wyraźnie niekompletna. Bazgrać po niej śmiało.

(mceier) moja propozycja, uwzględniająca tickety, i dyskusję z SensorySygnalyAktuatory :

brakuje w niej klas: Sygnału chemicznego oraz Funkcji sygnał<->sensor ...

jeszcze jedno: Twoja klasa ai, to w mojej propozycji IAnimatController, struktura sensorStatus to ISensor, struktura actuatorStatus, to IActuator ...

(lRem) Drobne uwagi:

  • sensory, jak i aktuatory, powinny przechowywać informacje o
    • położeniu (mceier) to się doda i kierunek także, jako macierz :)
    • maksymalnej mocy aktuatora
      (mceier) to może być ukryte w funkcji setForce aktuatora, nie wydaje mi się by kontroler musiał o tym wiedzieć (lRem) No ja sobie nie wyobrażam skutecznego sterowania czymś, czego parametrów nie znam... (mceier) Sterowanie odbywa się na podstawie wartości sensorów - i to są parametry :p
    • rodzajach substancji na które czuły jest sensor informacje takie proponowałbym oddelegować do osobnej struktury (u mnie wyżej const actuatorInfo &info;)
      (mceier) to załatwia funkcja sensor<->sygnał ... SensorySygnalyAktuatory :p (lRem) Ale ona też będzie musiała mieć skądś te informacje. Dodanie ich tutaj wydaje mi się po prostu naturalne. (mceier) ale my nie musimy wiedzieć skąd ona ma te informacje
  • nie jestem przekonany do tej osobnej klasy na kontroler animata
    (mceier) Cytat z SensorySygnalyAktuatory : Aktuatory nie są bezpośrednio modyfikowane przez sensory. Przepływ informacji w animacie jest taki: sensor->kontroler animata->aktuator.
    kontroler animata jest po to by można było testować różne algorytmy reagowania na wskazania sensorów ... (lRem) No tak, ale czemu nie jest on po prostu częścią animata?
    (mceier) Masz na myśli agregację ? Nie wiem czy to jest prawidłowe rozwiązanie
    Cytat z SensorySygnalyAktuatory: Dla każdego animata, wywołujemy metodę aktualizacji animata, klasy kontrolera animata
    ja to zinterpretowałem tak, że podczas jednego kroku symulacji tylko jeden kontroler steruje wszystkimi animatami
    tzn. animaty nie posiadają kontrolerów
    (mjoach) konceptualnie to każdy animat ma własny kontroler - własną sieć neuronową, podobną do sieci rodzica, ale z jakąś tam mutacją. A animaty będące jedzeniem dostają jakiś trywialny kontroler, który mówi że mają np. poruszać się ze stałą prędkością w określoną stronę. Tak więc można to zaimplementować na dwa sposoby - albo jako jedną funkcję systemu, która bierze na wejście animata i zidentyfikowawszy go sięga po odpowiedni kontroler albo jako własność każdej instancji obiektu typu animat. Ale z racji, że kontroler==mózg, jest bardziej naturalnym żeby każda instancja animata miała własną instancję mózgu. (mceier) dobrze, kontroler przeniesie się do animata :)

(mjoach) dla animata potrzebujecie oprócz pozycji kierunku w który jest akurat zwrócony. Zgaduję że engine używa 3 ortonormalnych wektorów wyznaczających bieżący układ współrzędnych obiektu. Pewnie też potrzebny jest aktualny wektor kierunku ruchu, ale możliwe że to wszystko engine gdzieś chowa. Za to na pewno potrzebujecie dla aktuatorów i sensorów ich położenia na powierzchni animata.

(mceier) kierunek i położenie ( zawsze względem rodzica lub świata ) można jako macierz reprezentować ...


(lRem) Niestety moje dia jest niekompatybilne z dia Mariusza, więc po prostu opiszę zasugerowane przeze mnie zmiany, licząc na to, że Mariusz je wprowadzi ;)

  1. Usunąć tego potworka o wdzięcznej nazwie SensorSignalFunction?
  2. Zamiast tego dodać wirtualną metodę pobudzenia do klasy sensora.
  3. Zmienić ChemicalSignal? na IChemicalSignal - po tym będzie się dziedziczyć, aby uzyskać różne chemiczne sygnały (na które różne sensory będą regować w różnym stopniu). Inne wyjście z tej sytuacji to dodać do sygnału i do sensora jakąś tablicę określającą z jakich związków składa się sygnał (na jakie związki reaguje sensor).

(mceier) done

(magic) jak dla mnie jest masakryczne dużo interfejsów... zazwyczaj jestem zwolennikiem "agile programming"... czyli bez interfejsów... ale można spróbować i tak ;] A wracając do sensorów... bo ja miałem się nimi zając chyba... czy nie lepiej wrócić do poprzedniego planu jaki był proponowany przez zleceniodawców -> sygnał to np INT... ale ustawiany bit po bicie - można powiedzieć 32-bitowa maska, sensor ma własną maskę i na podstawie bliskości (odległości hamminga) maski sygnału do maski sensora jest ustalony poziom pobudzenia... dodatkowo sensor może posiadać multiplikator siły reakcji... przydałoby się to w przypadku gdyby np animat "nauczył się" ze dany sygnał chemiczny prowadzi do dobrego jedzenia... wtedy zwiększa czułość sensorów na dany sensor. Siła reakcji mogła by zostać jako float w zakresie 0-1, oznaczający 0% reakcji do 100%. W IChemicalSignam nie wiem czy jest potrzebny maxRange... max ReactionRange? ma już sensor... nie wiem czy nie jest nadmiarowością ustawiać w 15-tu rożnych miejscach maksymalne zasięgi i po wielu obliczeniach ustalać ze jednak jesteśmy poza zasięgiem :] Moim zdaniem sygnał dobrze ze ma zasięg.. gdyż rozumiem to jako zasięg powstały na skutek dyfuzji sygnału w wodnym świecie... dalej po prostu sygnał jeszcze się nie "rozpłynął"... a zasięg maksymalny zależeć chyba już tylko powinien od skuteczności sensora... W prawdziwym świecie źródła nie mają nigdy maksymalnych zasięgów... tylko odbiorniki :] ... jak jakiś nadajnik nie jest słyszany z za dużej odległości, wystarczy kupić lepszy sensor :]

(lRem) Rozważ może jakieś drobniejsze dzielenie wypowiedzi, bo trudno się odnosić do poszczególnych części.Część z tego co opisujesz, to już sprawa po stronie kontrolera animata, więc nie nasza broszka. Sprawę wyczulenia sensorów na dane typy sygnałów również delegujemy dalej (co było nieoczywiste). Zaś zasięgi to sprawa optymalizacji, którą ja bym sugerował nie zajmować się teraz. Jednak Mariusz napalił się na porządne zaprojektowanie, jego sprawa. Dyfuzji sygnału w ogóle nie rozpatrujemy na tym etapie.

(mceier) zmieniłem typ, teraz nie powinno być problemu :)

(magic) ehh te interfejsy, masa constów i wirtualnych metod zamiast normalnego dziedziczenia, jak na razie to dla mnie wydłużają czas 20-30 krotnie do skompilowania kodu i wielokrotnie zmniejszają zrozumiałość kodu... mam nadzieje ze takie trudne są tylko początki i dalej nie będzie żadnych interfejsów... bo inaczej narobimy się tyle co na system sterowania sondą kosmiczną NASA a wyjdzie kółko i krzyżyk...


Obsługa zdarzeń

W powyższym w ogóle pominęliśmy część o obsłudze zdarzeń (patrz #12). To ogólnie wesołe API prosi się o jakieś callbacki. Szczegółowe pomysły?

(mjoach) sądzę, że prościej będzie bez callbacków. Będzie to dodatkowo sprzyjać separacji części symulacyjnej od sterującej światem. Czyli system generuje listę z wykrytymi zdarzeniami w trakcie kroku symulacyjnego po czym ja, przed uaktualnieniem kontrolerów animatów, przeglądam tę listę, robię coś z niektórymi po czym reszta zdarzeń idzie do śmieci. Zdarzenia to przede wszystkim wykrywanie kolizji animatów ze sobą oraz z innymi obiektami (teraz to tylko z dnem). Można to zrobić tak, że na końcu kroku symulacyjnego sam sobie przeglądam listę animatów i znajduję te, które mają odległość mniejsza niż x, ale zrobienie zdarzeń w środowisku ma taki sens, że:

  • pozwala schować w nim optymalizacje (nie trzeba sprawdzać kolizji z obiektami, które są tak czy owak w innych strefach)
  • w przyszłości, gdy dołoży się niesferyczne morfologie, sprawdzanie kolizji będzie już mniej trywialne i powinno to robić środowisko

Jeśli chodzi o implementację, to można to zrobić np. tak, że zdefiniujemy sobie jakiś abstrakcyjny typ event, zawierający pole typ z którego to będziemy robić potomne odpowiedzialne za dany rodzaj zdarzenia, które będą zawierały dodatkowe dane np. wskaźniki do animatów, które weszły w kolizję.

Powinno też w miarę łatwo dać się dodawać nowe eventy, np. jeśli stwierdzimy, że chcemy mieć event oznaczający, że energia komuś zeszła do zera to dorzucamy nowy typ i jego generację w pętli na końcu kroku symulacyjnego.

Bardzo pożądane byłoby też, żeby generowanie eventa przez obiekty było explicite włączane. Czyli domyślnie animat nie generuje nic, chyba że do listy generowanych zdarzeń dodam mu typ kolizja albo osiągnięcie zerowej energii.

Chodzi mi też po głowie opcja nie generowania tego samego zdarzenia wielokrotnie, która mogłaby być użyteczna z punktu widzenia nie zawsze bezbłędnego i czasem leniwego użytkowania systemu. Tzn. można zrobić tak, że jeśli dwa animaty się przenikają to nie generujemy zdarzenia o przenikaniu w każdym kroku, a jedynie raz i potem dopóki nie przestaną się przenikać i nie zetkną ponownie to zdarzenie nie jest generowane. Czyli generowanie zdarzenia tylko pod warunkiem, że w poprzednim kroku nie wygenerowaliśmy albo nie próbowaliśmy wygenerować dokładnie takiego samego. Ale zostawiłbym dodanie takiego mechanizmu pamięci na później, na rzecz posuwania teraz innych rzeczy do przodu.

Czyli podsumowując, mechanizm generowania zdarzeń:

  • ma być ogólnie czymś lekkim i prostym
  • dać się bezboleśnie rozszerzać o nowe typy zdarzeń
  • jego główne przeznaczenie to możliwość dostarczenia użytkownikowi wykrywania kolizji z ukrytą w środku optymalizacją pozwalającą uniknąć sprawdzania O(n2) potencjalnego kontaktu wszystkiego ze wszystkim w każdym kroku (np. przez podział świata na strefy)