Referencje - jak GameMaker przechowuje informacje o zasobach

Czwartek, 17 Listopada 2011, 19:59
Czas czytania 5 minut, 6 sekund
Zgodne z GM: gm5 gm6 gm7 gm8 gms1 gms2
Kilka informacji o tym czym są GMowe referencje. Mam nadzieję, że artykuł trochę wyjaśni wam jak GM numeruje zasoby i że to nie zmienne przechowują zasoby, a jedynie zapamiętują ich numer.
W GameMakerze każdy zasób oznaczany jest kolejną liczbą naturalną. Dla ułatwienia pod większość z nich podkładana jest nazwa (zmienna globalna), tak aby łatwiej było nam operować. Każda z tych zmiennych, które są nazwami zasobów (obiekty, poziomy, grafiki) tak naprawdę zwraca jakiś numer.

Widać to dobrze tworząc nowe zasoby. Kolejne obiekty nazywane są object0, object1, object2, skrypty script0, script1, script2 itd. Końcowa liczba to właśnie id owego zasobu. Wszystkie zasoby mają swój własny licznik, do tego usuwając już istniejący zasób licznik ten nie cofa się, a istniejące zasoby nie zmieniają numeracji, aby nie generować problemów. Poza identyfikatorami zasobów, mamy też identyfikatory struktur map, particli, plików czy modeli 3D. W każdym miejscu w dokumentacji gdzie widzimy "Returns its index" - otrzymujemy jako wartość konkretne id zasobu - czyli jego referencję.

Referencje zasobów

Jak już wspomniałem, nazwy obiektów to po prostu globalne zmienne, które przetrzymują id danego zasobu. I tak np. object45 = 45. Jeśli zmienimy nazwę tego obiektu na np. objPilka, to w tym momencie id zasobu się nie zmienia - zatem poprawne jest stwierdzenie, że objPilka = 45. Warto też pamiętać, że nazwanie tegoż obiektu object99 nadal będzie zwracać 45 - ta zasada dotyczy tylko automatycznie generowanych nazw.
Zatem poniższe zapisy są równoważne:

kodobject2.x = 17;
(2).x = 17;

Identyfikator zasobu nazywamy "referencją". Referencja odnosi się do konkretnego zasobu.

Dowód:

kodshow_message(string(object2));
Referencje struktur danych

Jak już wspomniałem, swoje identyfikatory zwracają też np. struktury danych. Tak samo jak w przypadku drzewa zasobów (grafiki, obiekty, skrypty, czcionki itd) są to kolejne liczby naturalne, przy czym skasowanie jakiejś struktury nie powoduje cofnięcia licznika, ale jest on nadal inkrementowany.

koda = ds_list_create(); // zwraca 1
Zatem poniższe zasoby są sobie równoznaczne:

kodds_list_add(a,'wartosc');
ds_list_add(1,'wartosc');

W podobny sposób działają referencje na tworzone particle, modele 3D, czy otwierane pliki.

To nie zmienna przetrzymuje zasób

Nieprawdą jest, że zasób zostaje przypisany do zmiennej. Zasoby w GM są "gdzieś" w pamięci, a my dostajemy jedynie ich referencje i modyfikujemy je posługując się tymi referencjami.
To znaczy, że jeśli jakaś zmienna zostanie usunięta (najczęściej gdy znika obiekt) - zasób którego referencję ta zmienna przetrzymywała wcale nie zniknie - dla GM taka zmienna jest po prostu jakąś zmienną przetrzymującą liczbę. To programista wykorzystuje ten fakt, aby edytować zasoby.

Zatem dodając nową grafikę czy obiekt:

kod//jakiś kod
{
var a,b;
a = sprite_add(...);
b = instance_create(...);
}
// zmienne a i b zostały usunięte, ponieważ ich zasięg ogranicza się do bloku kodu i podrzędnych mu bloków

Po tak napisanym kodzie ani obiekt, ani grafika nie znikną - mimo, iż zmienne które miały ich referencje przestały istnieć. Dzieje się tak dla tego, że zmienna jedynie przechowuje wartość liczbową pod jakim id znajduje się dany zasób.

Wady referencji
Wadami referencji w GM jest to, że musimy pamiętać o zwalnianiu zasobów. GM nie posiada licznika referencji oraz garbage collectora który usuwałby zasoby do których nie trzymane są już referencje. Oznacza to, że dodajac w trakcie gry nową grafikę, otwierając plik czy tworząc strukturę danych (ds_xxx) zasób taki zajmuje pamięć tak długo, aż go nie usuniemy, nawet pomiędzy kolejnymi planszami (nie dotyczy to jedynie instancji obiektów).

Korzystanie z referencji do podróżowania pomiędzy zasobami
Postawienie kropki po zmiennej/referencji w GM powoduje, że odnosimy się do właściwości (zmiennej lokalnej) danej instancji/obiektu. Pisząc więc taki kod:

kod
var a,b;
a = -1;
b = a;
repeat(10) {
a = instance_create(x,y,test);
a.nadrzedny = b;
b = a;
}

z ostatniego stworzonego obiektu możemy znaleźć pierwszy:

kod
var a,b;
a = nadrzedny;
while (a!= -1)
{
a = a.nadrzedny;
if (a != -1) b = a;
}

show_message('Pierwszy stworzony obiekt ma id: ' + string(b));

// powyzszy kod jest rownoznaczny z:
b = nadrzedny.nadrzedny.nadrzedny.nadrzedny.nadrzedny.nadrzedny.nadrzedny.nadrzedny.nadrzedny;
show_message('Pierwszy stworzony obiekt ma id: ' + string(b));



W ten sposób można tez wkładać w siebie struktury danych. Załóżmy, że nasza gra ma 100 różnych itemów, numerowanych od 0 do 100. Każdy item ma nazwę, cenę oraz ikonkę. Do przechowywania tych danych zamiast tablic można użyć ds_mapy:

kod//skrypt: stworz_item
var m;
m = ds_map_create();
ds_map_add(m, 'nazwa', argument0);
ds_map_add(m, 'cena', argument1);
ds_map_add(m, 'ikona', argument2);
return m; //zwarca referencję na tę mapę

a samą listę tworzymy:

kod
global.itemy = ds_list_create();
ds_list_add( global.itemy, stworz_item('Miecz', 500, spriteMiecz) );
ds_list_add( global.itemy, stworz_item('Potion', 100, sprPotion) );
ds_list_add( global.itemy, stworz_item('Gazeta', 10, sprGazeta) );

teraz, aby pobrać np. 2 item wystarzczy:

kod
var mapa;

mapa = ds_list_find_value(global.itemy, pozycja-1); //pozycje numerowane są od 0, zatem drugi item ma pozycję 1.
show_message( ds_map_find_value(mapa, 'nazwa') ); //pokaże komunikat "Potion"

Oczywiście zamiast korzystania ze stringów typu 'nazwa', 'cena', 'ikona' proponuję dodać sobie jakieś stałe i przypisać im kolejne wartości jako liczby naturalne. Raz, że takie stałe będą podpowiadane w kodzie, a dwa, że zajmują mniej pamięci.

Pamiętajcie jednak, że:

kodds_list_destroy(global.itemy);
Tak naprawdę kasuje tylko listę z cyferkami które odpowiadają jakimś tam referencjom na ds_mapy, ale te ds_mapy nadal istnieją w pamięci i nadal można się do nich odwoływać!
Można by powiedzieć, ze w GM referencje to po prostu kopie identyfikatora liczbowego na jakiś zasób i przypisanie do zmiennych powoduje po prostu przypisanie jakiejś liczby, która tak naprawdę nie wpływa na ten zasób. Referencje nie są wskaźnikami na miejsce w pamięci.
Komentarze (łącznie 19, wyświetlam 1 - 15):
Borek (Czw., 17 Lis. 11, 21:35)
#1

Bardzo fajny artykuł. Wszystko dobrze opisane, myślę że nie jednemu użytkownikowi się przyda :)

HuderLord (Czw., 17 Lis. 11, 22:05)
#2

Już wcześniej się spotkałem z zagnieżdżaniem struktur w jakimś silniku Minecrafto-podobnym ale miałem spore problemy ze zrozumieniem jak to działa, teraz już wiem dzięki :P

T
123tomek (Pią., 18 Lis. 11, 07:36)
#3

Wadą odwoływania się w kodzie bezpośrednio do referencji jest to, że mimo iż po skasowaniu danego obiektu referencje kolejnych są numerowane dalej, to jednak po zaimportowaniu ponownie resources do nowego projektu, następuje przenumerowanie referencji.
Warto o tym pamiętać bo można się zdziwić po przeprowadzeniu exportu i ponownego importu resources i scripts do nowego projektu.

gnysek (Pią., 18 Lis. 11, 10:50)
#4

Tak, bo są numerowane wg. tego nowego projektu, jako nowo dodane zasoby.

Muuuuczek567 (Pią., 18 Lis. 11, 17:11)
#5

Wszystko to wiedziałem już wcześniej. Mimo to, fajny artykuł, ładnie napisany i przejrzyście wyjaśniony (ta, powtarzam się) ; )

kt1117 (Pią., 18 Lis. 11, 17:18)
#6

Tak samo jak Muuuuuczek567.

p
pablo1517 (Nie., 27 Lis. 11, 02:33)
#7

Nic nowego. Jednakże ciekawi mnie jak to jest w momencie game_end(); ? Czy gdy gra zostanie wyłączona, to nieskasowane przez nas "leaki" same się zwolnią czy może uratuje nas dopiero reset komputera :D?

Sernat19 (Nie., 27 Lis. 11, 15:28)
#8

Tak jak w maszynie Javy - my mamy to gdzieś, niech się martwi za nas.

p
pablo1517 (Nie., 27 Lis. 11, 17:54)
#9

To nie jest odpowiedź na moje pytanie.

Sernat19 (Nie., 27 Lis. 11, 21:23)
#10

Ależ oczywiście, że jest. Mówimy o instrukcji delete odpowiedzialnej za usuwanie konkretnych elementów z pamięci, która nie istnieje ani w GMie, ani w Javie. Jeśli masz coś innego na myśli, to sorry.

p
pablo1517 (Pon., 05 Gru. 11, 21:36)
#11

No to ty chyba tego artykułu nie zrozumiałeś... No i w javie masz odpowiednik delete. Tak samo w GM MOŻNA zwalniać zasoby takie jak grafika, dźwieki itp. Ja zaś zapytałem, co w przypadku, gdy tego się nie zrobi, a wyłączy się grę.

k
karolo320 (Pon., 05 Gru. 11, 21:39)
#12

powinno usunąć przy wyłączaniu

Muuuuczek567 (Pon., 05 Gru. 11, 21:47)
#13

GM w czasie wyłączania zwalnia wszelkie załadowane zasoby (choć niektóre DLL-e trzeba zwalniać ręcznie), funkcje zwalniania/ładowania zasobów w czasie gry są po to, żeby odciążyć pamięć i nie przepełniać pamięci wszystkimi grafikami, dźwiękami itd.

gnysek (Pon., 05 Gru. 11, 23:27)
#14

System powinien zwolnić obszar chroniony pamięci zarezerwowany przez program.

Sernat19 (Pon., 05 Gru. 11, 23:30)
#15

"No to ty chyba tego artykułu nie zrozumiałeś... No i w javie masz odpowiednik delete. Tak samo w GM MOŻNA zwalniać zasoby takie jak grafika, dźwieki itp. Ja zaś zapytałem, co w przypadku, gdy tego się nie zrobi, a wyłączy się grę."
Weź zamilcz, jeśli twierdzisz, że w Javie mamy operator "delete". Gdyby tak było, zmieniłoby to bardzo oblicze całej ideologii Javy. Widzę, że nie dojdziemy do porozumienia ;) . Czy wiesz na czym polega zwalnianie zasobów w GMie?

Najnowsze wersje GameMakera:

Stabilna
2024.2.0.132 • 2024.2.0.163
wydana 15 dni temu
LTS
2022.0.2.51 • 2022.0.2.49
wydana 154 dni temu
Beta
2024.400.0.516 • 2024.400.0.537
wydana  5 dni temu
= IDE, = Runtime
Użytkownicy online
1 użytkownik aktywny:
gości: 1,
(~ostatnie 15 minut)
Discord
18 użytkowników online na discordzie:
Carl-bot, p..., Wielki Druid, Kowu, fervi, RogerDodg3r, YoungKrystian, PhysX ᴺⱽᴵᴰᴵᴬ, antek, Uzjel, Moldis, LeD, Dyno, LadyLush, bagno, l..., TobiasM (Morgo), xVANiLL
Shoutbox
I am Lord (19:15, 17.03.24)
6h mam na to hmmm
I am Lord (19:06, 17.03.24)
Ale temat fajny
gnysek (01:33, 13.03.24)
Powinno działać, jest w kodzie sortowanie wg. najbliższego startu :)
Uzjel (21:59, 11.03.24)
Nie, ale za pierwszym razem zrobiłem fuckup, że było "Tura testowa" X_X
I am Lord (16:58, 11.03.24)
A co Uzjel już masz nawymyślane 100 tematów? 😅
Uzjel (20:08, 10.03.24)
@gnysek a jak bym dodał kilka lig na raz to walnie?
Uzjel (20:08, 10.03.24)
Liga będzie zawsze od piątku 16:00 do poniedziałku 23:59, zawsze w środku miesiąca.
gnysek (08:48, 10.03.24)
Tak, to też jest do poprawy X_X
Adriann (18:22, 09.03.24)
Tylko myślę czy nie leiej gdyby mówiło że zostało tyle i tyle dni i ileś godzin a nie tylko w godzinach ;d Albo konkretna data obok, byłoby czytelniej
I am Lord (15:08, 08.03.24)
o super z tą ligą :)
Starsze wpisy znajdziesz w Archiwum.
Ankieta
Ile zarobiłeś do tej pory na grach stworzonych w GM?