W tej części cyklu zoptymalizujemy proces wysyłania danych, zapoznamy się z pojęciem kompresji i formatami wymiany danych. Zapraszam.
Na koniec ostatniej części wspomniałem o możliwym sposobie przyśpieszenia transmisji dźwiękiem, czyli wykorzystaniem innego systemu liczbowego którym odwzorowujemy znaki ASCII. Ponieważ plusy przesyłania decymalnie lub hexadecymalnie (szesnastkowo) zamiast binarnie przedstawiłem już w poprzednim poście, przejdę od razu do paru zmian jakie zaszły w kodzie (nie jest ich dużo, wystarczyło 5 minut na odpowiednie zmiany).
Zmiany w kodzie
W klasie Variables zadeklarowałem nowe częstotliwości, tym razem mamy ich więcej, więc ułatwiłem sobie korzystając z tablicy:
Zmieniona klasa Variables.
Dodatkowo też zadeklarowałem zmienną TOLERANCE z której korzystam w odpowiednio zmodyfikowanym modelu DetectedFrequency – wcześniej była tam hardkodowana wartość 50, tym razem warto ją gdzieś zapisać dla czytelności i łatwiejszych zmian. Zmieniony konstruktor modelu przedstawia się następująco:
Iteruję po elementach tablicy i sprawdzam czy wykryta częstotliwość zawiera się w przedziale: (częstotliwość z elementu tablicy minus tolerancja, częstotliwość z elementu tablicy plus tolerancja). Jeśłi tak, to przypisuję tę wartość do obiektu, jeśli nie, przechodzę do następnego elementu tablicy. Jeśli nadal po przeiterowaniu nie ma żadnego dopasowanego znaku (porównanie matchedSign == null), to sprawdzam dalej, czy wykryta częstotliwość nie jest pauzą albo początkiem/końcem transmisji. W przeciwnym razie, jeśli jest to szum, przypisuję pusty ciąg znaków.
Do tego takie drobiazgi, jak:
broadcaster.sendMessage(Integer.toString(takeInput()));
Zamiast wcześniejszego:
broadcaster.sendMessage(Integer.toBinaryString(takeInput()));
I analogicznie przy odczytywaniu znaku z liczby:
int code = Integer.parseInt(formattedMessage, 10);
Zamiast wcześniejszego:
int code = Integer.parseInt(formattedMessage, 2);
argument == 10 to wskazanie parserowi że ma do czynienia z liczbą zapisaną decymalnie, argument == 2 – binarnie.
Do tego zmieniłem czas nadawania znaku początku transmisji z 10*INTERVAL na 3*INTERVAL, co również ma duży wpływ na szybkość bez widocznego zwiększenia awaryjności.
Czas na testy
Przesłanie znaku ‘i'(1101001, 7 bitów) zajęło 1578 milisekund.
Dla porównania, kiedy ostatnim razem przesyłaliśmy 7 bitów, zajęło nam to ponad 4 sekundy. Dzielenie 4 przez 1.578 daje 2.53 i tyle razy szybciej wysyłamy znaki teraz.
Tym razem daje to już 1.92 kB na godzinę, więc przesłanie 17 kB zdjęcia kaczki z ostatniego posta zajęłoby już tylko niecałe 9 godzin.
Trochę eksperymentując, ustawiłem czas nadawania pojedyńczej częstotliwości na tylko 50 milisekund. Co oczywiste, skraca to niezawodność, ale w ten sposób przesłałem bez problemu 5 znaków pod rząd osiągając w każdym ~1050 milisekund.
Granicą takiego schodzenia coraz niżej jest niestety nadal błąd dostępu do głośników który sporadycznie się wtedy pojawia i blokuje cały program. Z jednej strony zrozumiałe, w końcu wywołuję dostęp do nich co bardzo krótki czas, a z drugiej raczej muszę robić coś źle, ktoś musiał już przewidzieć takie sytuacje.
Prześlijmy wyraz
Po prostej zmianie polegającej na przyjęciu całego stringa i przesyłaniu go znak po znaku do naszego Broadcastera, wyniki przesyłania ciągu “napis ćwiczebny” są następujące:
Litery ‘i’ i ‘z’ nie przesłały się pomyślnie, a sama transmisja zajęła aż 26 sekund. O ile więc nie znajdę sposobu rozwiązania problemu czasu dostępu do głośników, podczas przesyłania wyników gry między komputerami będzie można zaparzyć sobie herbatę.
Co jeszcze może pomóc?
Kompresja, chociaż nie w każdym przypadku. Zadziała wtedy, kiedy mamy do czynienia z tekstem w którym pewne ciągi pojawiają się wielokrotnie w tym samym ułożeniu. Dobry przykład jest tutaj.
Mało prawdopodobne jest, żeby wśród przesyłanych przez 2 gry danych pojawiał się aż tak podatny materiał, dlatego ten sposób zostawię na koniec pracy z naszym modemem.
Do głowy przychodzi mi już tylko zakodowanie pewnych często powtarzających się znaków/kombinacji jako dodatkowe częstotliwości, co rzeczywiście mogłoby znacząco skrócić czas nadawania.
W jaki sposób będziemy wymieniać dane między grami?
Przesyłanie zdań albo liter to jedno, a przesłanie powiązanych ze sobą zmiennych i ich wartości, a później dopasowanie ich to drugie. Moim pomysłem jest skorzystanie z gotowego formatu wymiany danych, który zrobi to za mnie – JSON. Korzystałem z niego już przy okazji przesyłania testów między serwerem a klientem w aplikacji Speechlist (odsyłam do poświęconemu jej posta).
Dla przykładu skorzystam z webowego generatora jsonów:
Po przesłaniu ciągu znaków:
{“pozycjaGraczaX”: 10,”pozycjaGraczaY”: 20,”punktyZdrowia”: 99,”ekwipunek”: {“jabłko”: 10,”pieniądze”: 40,”miecz”: 1}}
Program mógłby oddzielić odpowiednie zmienne i załadować je do ich odpowiedników w swojej pamięci. Dodatkowo, kiedy będę chciał odwzorować jakikolwiek swój obiekt w postaci jsona, są już do tego odpowiednie biblioteki (np. gson), które przyjmując obiekt w argumencie oddadzą go w postaci stringa.
Co dalej
W następnej części postaram się usunąć ograniczenie jakie narzuca czas dostępu do głośników i przesłać jakiegoś jsona między komputerami. Do tego przydałby się jakiś system kontroli błędów.
Potem została do napisania już tylko gra wykorzystująca modem.
Odnośniki:
Jak zawsze, kod dostępny jest na GitHubie:
https://github.com/dbeef/soundcoding