Blog i nie tylko

MyOTS v6 #5 – Wprowadzenie nad-stosu i limit houseitems.xml

MyOTS v6 #5 – Wprowadzenie nad-stosu i limit houseitems.xml

Transfer do nad-stosu w świecie minus pierwszym

W poprzednim artykule omówiłem koncept nad-stosu publikowanego w formie webowej na podstawie nad-stosu ryb, który może generować się bezpośrednio w grze.

Koncept zrealizowałem w całości i dokonałem pierwszego bardzo dużego transferu ryb 15 lutego 2022 roku.

Stos stosów w magazynie id2, świat -1

Nad-stos zawiera (19.02.2022) 65 041 200 ryb.

Powyżej podsumowane ryby były gromadzone głównie w nad-stosach znajdujących się bezpośrednio w grze, w świecie pierwszym. Ich łączna waga w zapisie XML wynosi około 17,9 MB. Aby je przenieść musiałem przed transferem na stronę, z pliku houseitems.xml usunąć znaczniki odpowiadające za informacje o definicji koordynatów i kilka innych rodzajów znaczników. Takie transfery wykonywałem wielokrotnie chociażby na poprzedniej edycji, natomiast w tej był to już ogólnie drugi transfer. Dlaczego więc o tym wspominam z takimi szczegółami, skoro jest to w pewnym sensie naturalny proces ów serii blogowej?

Mianowicie chodzi o większą skalę w tym przypadku. Dawniej transfery wykonywałem przy wadze houseitems.xml do kilku megabajtów. Pierwszy raz nazbierało się bezpośrednio w grze na jednym serwerze tak dużo ryb i okazało się, że wydajność przeglądania / edycji tak dużego pliku tekstowego (przez Notepad++) jest fatalna. Opóźnienia jakiejkolwiek operacji na pliku trwały kilka sekund (oczywiście to wszystko może być też zależne od parametrów sprzętu, na którym się pracuje). Samo wczytywanie pliku zajmowało nawet do kilkunastu sekund z towarzyszącym przy tym powolnym przeładowywaniem się części interfejsu graficznego programu Notepad++.

Co ciekawe w pewnym momencie podczas pracy nad plikiem napotkałem na “dolnych” fragmentach gigantycznej ściany tekstu taki błąd wizualny związany prawdopodobnie z limitem formatowania i poprawnego pozycjonowania graficznego zawartości:

Błąd wizualny w Notepad++ z dużym pliku tekstowym

Jak widać zawartość znaczników przestaje wyświetlać się prawidłowo i towarzyszy temu również błędne zaznaczanie wybranego obszaru przez co cały proces transferu został przedłużony w czasie przez wspomniany błąd.

Finalnie cały transfer udało się zrealizować chociaż jak widać z licznymi trudnościami. To nauczyło mnie, aby kolejne transfery realizować chociaż raz na około 10 MB, gdyż później czas opóźnień wzrasta o wiele bardziej niż proporcjonalnie rozmiar dużego pliku tekstowego, na którym chce się coś zrobić, bądź nawet go tylko załadować do edytora tekstu.

Limit wielkości pliku houseitems.xml

W testowym świecie wykonałem liczne testy dotyczące maksymalnego możliwego limitu pliku houseitems.xml .

Wszystko zaczęło się od obserwacji coraz dłuższego czasu zapisu serwera, gdy plik houseitems.xml jest coraz większy. W międzyczasie zwróciłem uwagę również na zużycie pamięci operacyjnej procesu z serwerem gry w momencie zapisywania serwera. Przy rosnącej wadze pliku houseitems.xml wzrasta nie tylko czas oczekiwania na pełne wykonanie zapisu, ale również rośnie zużycie pamięci operacyjnej.

Dysponuję wieloma szablonami postaci, które przygotowałem dawniej do m.in. tworzenia w sposób bardziej uproszczony kolejnych postaci. Powstał niedawno również szablon do fabularnego wydarzenia spotkania z Rybakiem.

Aby wygenerować bezpośrednio z poziomu gry dużą ilość przedmiotów najłatwiej mi było wpierw zdefiniować odpowiednio duży łup z jakiegoś przykładowo testowego potwora (do tego trzeba stworzyć wiele zagnieżdżonych pojemników z przedmiotami w kodzie XML potwora). Następnie zgromadzić takich zdefiniowanych obszernych łupów jak najwięcej w ekwipunku danej postaci (z poziomu gry) i ów postać potraktować jako szablon. Szablon ten posłużył mi do wspomnianego wydarzenia fabularnego, ale szybko zorientowałem się, że też mogę go użyć do wspomnianych wcześniej testów z maksymalnym rozmiarem pliku houseitems.xml.

Nic prostszego jak wyrzucić zawartość przedmiotów np. do temple (ang. świątynia – punkt centralnego odradzania się postaci w grze), wylogować postać, następnie z poziomu plików podmienić wcześniejszy szablon i ponownie się zalogować.

W tym momencie opisywania całej tej “procedury” zauważyłem, że za bardzo rozdrabniam cały proces na mniej istotne szczegóły, zatem czyniąc historię krótszą, po przeniesieniu przedmiotów do domku i zapisie gry zobaczyłem jak zużycie pamięci operacyjnej “puchnie”. Dorzuciłem zatem jeszcze więcej przedmiotów generowanych według powyższej, bądź podobnej metody i okazało się, że można w ten sposób bardzo szybko doprowadzić do wyczerpania maksymalnej pamięci operacyjnej procesu serwera gry. Po szybkim “zalewaniu” pamięci operacyjnej podczas zapisu, gdy ów pamięć zbliży się do około 2 GB jeszcze “wlewa” się do około 2,1 GB ale już dużo wolniej po czym przestaje rosnąć rozmiar procesu i w konsoli serwera pojawia się komunikat:


“error : out of memory error”

Błąd braku pamięci.

Jednakże, aby do tego doszło rozmiar houseitems.xml nie wynosił nawet blisko 2 GB lecz dużo, dużo mniej, bowiem ów limit pojawia się przy rozmiarze około 100 MB tego pliku. Wynika to z całą pewnością z tego, iż definicja wartości w znacznikach XML w formie tekstowej waży dużo mniej niż informacja przetwarzana przez silnik gry znajdująca się w pamięci operacyjnej, które to wcześniej wartości zakodowane w XML stają się w silniku gry zmiennymi zużywającymi więcej pamięci. Troszkę może to brzmieć w zagmatwany sposób, gdyż sam nie znam się biegle w tych kwestiach, ale powiedzmy, że kojarzę na bardzo podstawowym poziomie ten temat.

Przykładowo, aby zapisać wartość “100” potrzeba w formie tekstowej jedynie 3 bajty danych w większości znanych mi popularnych, powszechnie używanych systemów kodowania znaków. Natomiast, gdy ów wartość 100 zostaje przeniesiona do jakiejś zmiennej przetwarzanej przez silnik gry, wówczas taka wartość bazuje na danym zakresie zmiennej, np. typu mogącego przechować od 0 - 255 znaków i wówczas naturalnie może przez to ważyć więcej niż w formie ściśle tekstowego pliku, gdyż jest interpretowana w zupełnie inny sposób. Jest to jednak konieczne w celu przetwarzania.

Wracając do limitu ~2,1 GB RAMu. Prawdopodobnie, gdyby silnik gry został napisany w 64-bitach to wówczas takiego problemu by nie było, a dokładniej nie wystąpiłby on w tym momencie tylko ewentualnie kilka, kilkanaście rzędów wielkości później lecz tylko teoretycznie, gdyż w rzeczywistości w moim przypadku i tak nie starczyłoby pamięci operacyjnej już po około 6-7 GB (gdyż z mojego ośmio-gigabajtowego RAMu należy uwzględnić margines na system operacyjny i kilka innych stale włączonych programów).

Wykonałem podobny test na pojedynczej postaci i problem jest identyczny. Po około 100 MB rozmiaru pliku XML z postacią, wykorzystanie pamięci RAM w procesie silnika gry dochodzi do maksymalnej wartości ~2,1 GB po czym pojawia się błąd braku pamięci w konsoli aplikacji serwera lub podczas logowania taką postacią na serwer, ten po prostu się crashuje (proces aplikacji nagle się zatrzymuje).

Poszukiwanie limitu, na który mam już sposób

Po co tak dokładnie przyjrzałem się kolejnemu możliwemu limitowi archaicznego silnika gry, skoro właściwie już wcześniej wprowadziłem do serii transfery przedmiotów z gry do innych źródeł powiązanych z całą serią MyOTS, ale jednak technicznie znajdujących się poza grą.

Wynika to z wcześniejszych prób długoterminowego testowania limitów houseitems.xml w świecie drugim. Założenie było takie, że akurat na tym świecie przedmioty nie będą transferowane, wszystko będzie znajdowało się bezpośrednio w grze, aż do pojawienia się potencjalnie jakiegoś ograniczenia.

Zwyczajnie ów założenie przyspieszyłem w świecie testowym i teraz już wiem, że nie ma sensu dalej tracić czasu na coś co jest z góry dość mocno ograniczone, a sam limit jest dość dobrze przeze mnie w tym momencie znany, sprawdzony i opisany.

MyOTS v6 s2 – Prawdopodobnie ostatnia skrzynka z tak dużą ilością przedmiotów bezpośrednio w grze

Jeszcze nie tak dawno, gdyż na przestrzeni tego tygodnia (19.02.2022) przeznaczyłem kilka godzin na przenoszenie ryb w pełnych stackach bezpośrednio w grze do skrzynki, a dokładniej do zagnieżdżonych plecaków w skrzynce. Większość tego procesu i tak realizowałem poprzez macro w BicTrainerze, ale jednak całe przedsięwzięcie wymagało ode mnie również wielu działań wykonywanych ręcznie.

Później w międzyczasie odkryłem limit opisany w rozdziale “Limit wielkości pliku houseitems.xml” i zniechęciło mnie to dalszego gromadzenia i układania ryb w ten sposób.

W skrzynce znajdują się ryby złowione bezpośrednio w świecie drugim i część ryb otrzymanych od Rybaka.

Skrzynka z rybami w MyOTS świecie drugim

Większość ryb od Rybaka i tak nie włożyłem do powyżej ukazanej skrzynki, gdyż należy w niej zachować inny standard ułożenia przedmiotów niż w pojemnikach od postaci fabularnej. Na całe to przedsięwzięcie trzeba by przeznaczyć wiele kolejnych godzin, a tak jak wspomniałem wcześniej, gdy już znam limit houseitems.xml to absolutnie nie mam chęci, aby to dalej kontynuować.

W przyszłości raczej wszystkie obecnie znajdujące się ryby w świecie drugim będę transferować do świata minus pierwszego lub do kolejnej skrzynki w świecie peryferyjnym.

Świat peryferyjny

Świat minus pierwszy jest światem zewnętrznym względem całości uniwersum MyOTS v6. Bazuje on na kilku graficznych elementach gry i kilku regułach ów gry. Jednak nie posiada on silnika i wielu innych standardowych plików serwerowych, lecz jest osadzony bezpośrednio na stronie (w formie webowej).

Świat peryferyjny byłby światem jeszcze bardziej abstrakcyjnym. “Fizycznie” osadzony również w formie strony / stron HTML podobnie jak świat minus pierwszy, ale nie byłby publikowany tak jak świat -1. Stąd też nazwa – peryferyjny.

Dlaczego świat poza stroną blogint?

Problemem obecnych elementów w świecie minus jeden (zamienne rozwinięcie nazwy do świata minus pierwszego) jest to, że jestem zmuszony publikować wiele powtarzających się elementów na stronie. Może być to w przyszłości problematyczne dla pozycjonowania (w tym wypadku dotyczy to jakości strony z “punktu widzenia” popularnych wyszukiwarek). Co prawda staram się dla każdego mniej jakościowego elementu strony informować boty indeksujące, aby nie brały pod uwagę tego typu treści, ale jednak najlepszym gwarantem będzie po prostu nie publikowanie kolejnego tego typu kontentu.

Mogę więc to realizować z większą jakością dla potencjalnych botów indeksujących (a więc w dłuższej perspektywie czasowej potencjalnie również dla bezpośrednich czytelników) poprzez dalsze tworzenie artykułów z tej serii w podobnym charakterze jak obecnie i nie publikowanie elementów uznanych możliwie za gorszej jakości (takich jak np. zawartość skrzynek w formie webowej).

Struktura publikacji pewnych magazynów / pojemników w świecie peryferyjnym zapewne będzie odbywać się za pośrednictwem uproszczonych schematów, screenów, opisów i podsumowań, a nie poprzez bezpośrednie ukazanie danej zawartości danego pojemnika tak jak to ma miejsce w świecie minus pierwszym.

Limity, limity i jeszcze razy limity

W czwartej części serii, w rozdziale “Błąd graficzny dotyczący technicznie maksymalnej ilości prób” wspomniałem o błędnym pokazywaniu postępu na pasku progresu od pewnego poziomu łowienia.

Niedawno na głównej postaci doświadczyłem pierwszy raz tego błędu. Wcześniejsze wspomnienie o nim było jedynie lekkim dotknięciem tematu bazującym na małej ilości informacji. Przez to, że zjawisko zaczęło dotyczyć również głównej postaci zaobserwowałem i wydedukowałem dokładniejsze źródło tego wizualnego błędu.

Wiadomo, że techniczny limit ilości prób w grze wynosi maksymalnie 4 294 967 295. 1% tej wartości wynosi ~42 949 673. I właśnie po przekroczeniu ~42 949 673 prób pasek postępu przestaje pokazywać poprawną wizualnie wartość i zaczyna odliczać od nowa. Pomimo iż wartość 42 949 673 jest o dwa rzędy wielkości mniejsza niż znany limit (4 294 967 295) to jednak jest to właśnie 1% tego limitu. Najwidoczniej jakiś wzór w silniku gry dzieli wspomniany limit przez 100, aby wyznaczyć wartość procentową (to tylko moja hipoteza, możliwe, że działa to nieco inaczej, ale końcowy efekt jest ten sam). Jeśli wartość wyznaczająca procenty bazuje na zmiennej, która jest “przepełniona” to wartość procentowa zaczyna odliczać od nowa podobnie jak zmienna, na której bazuje.

Ciekawe jest również to, iż z tego co zaobserwowałem wyliczanie wartości do 42 949 673 prób jest wizualnie poprawne, natomiast dopiero przekroczenie tej ilości prób zawsze pokaże niepoprawną wartość pozostałego progresu do osiągnięcia kolejnego poziomu łowienia.

Konkretny przykład:

Na fishingu 173. i 69 481 901 próbach pasek postępu wskazuje na 23% zapełnienia ów paska. Prawdopodobnie od ~38% pasek zaczął zapełniać się na nowo (42 949 673 / 111 696 014 = ~0,38). Od 173. fishingu do 174. potrzeba 111 696 014 prób, więc w tym przypadku powinno to być tak naprawdę ~62% do 174. poziomu łowienia (69 481 901 / 111 696 014 = ~0,62).

Kontynuując…

~42 949 673 – Maksymalna ilość prób liczona wizualnie w poprawny sposób.
111 696 014 – Ilość prób potrzebna od fishingu 173 do 174.
42 949 673 * 2 = 85 899 346
111 696 014 - 85 899 346 = 25 796 668
25 796 668 / 111 696 014 = ~0,23

Według powyższych obliczeń pasek postępu przez błąd wizualny związany z technicznie maksymalną ilością prób będzie liczył do 38% dwa razy, a następnie zacznie liczyć trzeci raz i po przekroczeniu wartości ~23% – ~24% otrzymam awans na kolejny poziom łowienia.

Jako, że błąd wizualny pojawia się przy ~42 949 673 próbach to ów anomalia występuje od 163. poziomu łowienia, a dokładniej przy jego końcu, gdyż łączna ilość prób od 163. fishingu do 164. wynosi 43 063 649 prób i faktycznie został ten błąd zaobserwowany przeze mnie pod koniec wypełnienia paska postępu na tym fishingu.

Kolejny transfer

(21.02.2022) Przeniosłem 39 215 100 ryb ze świata drugiego do świata peryferyjnego. Wcześniej ów świat stworzyłem bazując na wcześniej zaprezentowanym koncepcie. W świecie peryferyjnym znajduje się mały magazyn z jedną skrzynką. Do skrzynki zostały przeniesione ryby.

System przechowywania dla pierwszej skrzynki w świecie peryferyjnym jest inny niż wcześniejsze systemy, które Państwu pokazywałem i opisywałem; Zawartość skrzyni jest podzielona na strony, w każdej stronie znajduje się 10 000 ryb bez dodatkowych pojemników. Stronicowanie odbywa się automatycznie, ale wprowadzanie zawartości wymaga mojej ręcznej ingerencji. Oryginalnie wygenerowane ryby w formie XML pochodzące ze świata drugiego zostały również “zabezpieczone” na podobnej zasadzie jak w nad-stosie znajdującym się w świecie minus jeden.

Tak wygląda przykładowa strona w pełni zapełniona rybami według powyżej omawianego systemu przechowywania:

Przykładowa strona kolejnego systemu przechowywania przedmiotów

Początkowo wydawało mi się, że 10 000 ryb na jedną stronę bez zagnieżdżania przedmiotów będzie stanowiło właściwą alternatywę dla wcześniejszych sposobów magazynowania przedmiotów, które bardziej bazowały na regułach gry. Po przetransferowaniu blisko 40 milionów ryb okazało się, że jednak ten sposób nie jest najbardziej optymalny, gdyż potrzeba w formie HTML przeznaczyć stosunkowo dużo pamięci masowej, aby to wszystko zakodować w ten sposób. Używam w tego typu projektach i tak proste skrypty JavaScriptowe, dzięki którym wiele powtarzających się elementów nie muszę za każdym razem wpisywać w sam plik HTML, ale jednak w tym przypadku pomimo zastosowania wszystkich możliwych mi znanych optymalizacji jednak same końcowe pliki są dość duże. Całość w samych plikach HTML zajmuje 607 KB nie wliczając przy tym używanych arkuszy styli, grafik, czy innych potrzebnych elementów.

Skąd właściwie tak duża waga plików?

Na 39 215 100 ryb potrzebne było aż 3 922 stron ekwipunku. Na każde tysiąc stron przypada jedna podstrona (podstrona oznacza plik HTML w tym wypadku). Ostatnia podstrona zawiera 922 stron ekwipunku / zawartości skrzyni. Ostatnia strona 922. zawiera 5 100.

Biorąc pod uwagę fakt iż jest to dopiero pierwszy duży transfer stwierdziłem, że nie jest to przyszłościowa metoda wirtualnego magazynowania.

Prawdopodobnie w świecie peryferyjnym, w magazynie powstanie kolejna skrzynka, w której będę magazynował ryby na podobnej zasadzie jak w skrzynce w świecie minus jeden, a więc poprzez liczne zagnieżdżenia pojemników (głównie plecaków) bazujących bezpośrednio na regułach i ułożeniu z gry. Przypomnę, że na każdy główny plecak (główne zagnieżdżenie) można używając “dawniejszej” metody przechować aż 760 000 ryb. W każdym pełnym plecaku jest 2 000 ryb, w pełnym nad-plecaku 40 000 ryb (2 000 ryb * 20 plecaków). W jednym głównym zagnieżdżeniu znajduje się maksymalnie 19. takich plecaków (40 000 ryb * 19 nad-plecaków = 760 000 ryb).

Transfer #4

(23.02.2022) Dodałem kolejną skrzynię w magazynie znajdującym się w świecie peryferyjnym. Skrzynia #2 zawiera system magazynowania bazujący na regułach zagnieżdżania pochodzących z gry, ale z pewną małą różnicą. Mianowicie w jednym głównym plecaku zamiast 760 000 ryb, może znajdować się w nim równe milion ryb. Z uwagi na transfer na stronę, nie ogranicza mnie gra do jakiejkolwiek definicji w tym wypadku, ale stwierdziłem, że równe 1 000 000 ryb znacząco ułatwi dodawanie kolejnych ryb do tej skrzynki. Akurat równe 25 nad-plecaków według definicji bazującej na grze mieści 1 000 000 ryb, ponieważ 25 nad-plecaków * 40 000 ryb = 1 000 000 ryb. Więc w praktyce zmieniłem jedynie maksymalną ilość slotów plecaków z głównym zagnieżdżeniem w porównaniu z regułami gry w tej kwestii.

Po wykonaniu podstawy zawartości skrzynki przeniosłem 46 134 200 ryb ze świata pierwszego do świata peryferyjnego, do omawianej powyżej drugiej skrzyni. Swoją prezentację w formie HTML ma równe 46 000 000 ryb (46 głównych, zielonych plecaków). 134 200 ryb jest jedynie w formie oryginalnie wygenerowanych znaczników, nie mają jeszcze swojego odpowiednika w formie HTML. W przyszłości prawdopodobnie dodam kolejną skrzynkę, w której takie “resztki” będą w pełni konwertowane. Póki co jedynie zostaje zapis XML i dla informacji publicznej zapis liczbowy podany w tym artykule.

Wszystkie 46 134 200 ryb jest również składowane w formie oryginalnie zachowanych znaczników XML.

Screen pokazujący wygląd ostatnich plecaków w skrzyni #2:

Ostatnie strony skrzyni pierwszej w świecie peryferyjnym

Plecaki zielone pełnią rolę głównego poziomu zagnieżdżenia, plecaki szare to nad-plecaki zawierające 40 000 ryb (20 brązowych plecaków, w każdym po 2 000 ryb, 20 * 2 000 = 40 000 ryb).

Pozostałości – aktualizacja 23.02.2022

Podczas transferu #3 nie został przeniesiony jeden niepełny stos, w którym znajduje się 81 ryb.
Podczas wykonywania czwartego transferu nie został przeniesiony również jeden niepełny stos, w którym znajdują się 63 ryby.

Z transferów #1 – #4 pozostało do przeniesienia: 36 081 ryb. W przyszłości dla tych pozostałości również planuję wprowadzić skrzynkę omawianą w poprzednim rozdziale.

Awanse w łowieniu głównej postaci

Awans na 160. poziom łowienia (12.02.2022):

Awans na 160. poziom łowienia

Awans na 170. poziom łowienia (16.02.2022):

Awans na 170. poziom łowienia

Od 175. poziomu łowienia będę starał się robić zrzut ekranu każdego awansu aż do planowanego 212. fishingu.

Więcej awansów w załącznikach serii.

Autor artykułu: Danys-Ynfi.
Rozpoczęcie serii blogowej MyOTS v6: 31.01.2022.
Część 5 opublikowano: 24.02.2022.

Seria bazuje na dawnych elementach gry Tibia (wersja 7.6) oraz na wielu moich modyfikacjach głównie około-gameplayowych i fabularnych.
Oficjalna gra najnowszej wersji bez modyfikacji: Tibia .