Strona główna
Witaj, nieznajomy!

Funkcje SCM

Korzystając z okazji, że na forum pojawił się poradnik o jumpach i gosubach, chcę objaśnić coś, co jest używane dość rzadko, a ma ogromne możliwości i jest nieco spokrewnione z w/w opcodami. Mowa tu o funkcjach SCM (SCM functions). Można je porównać do gosubu, jednak taka funkcja posiada swoje własne zmienne, potrafi otrzymywać wartości z "macierzystego" wątku, a także je zwracać.

Standardowy opcode uruchamiający funkcję SCM wygląda tak:

Kod:
0AB1: call_scm_func @GetSQR 1 10 $result


Co jest czym? Więc po kolei:


  • @GetSQR - nagłówek, do którego funkcja ma skoczyć - podobnie jak w przypadku gosubu, funkcja wróci do miejsca, z którego została wywołana, poprzez opcode 0AB2 (w przypadku zwykłego gosubu było to 0051);
  • 1 - liczba wartości, które zostają przesłane do funkcji;
  • 10 - wartość przesyłana (może to być liczba całkowita [int], liczba zmiennoprzecinkowa [float], a także zmienna lokalna lub globalna);
  • $result - zmienna, do której funkcja zwraca określone wartości.





Tak, wszystko wydaje się niezrozumiałe, i na razie takie jest. A więc, na czym polega przesyłanie wartości do funkcji? Domyślnie, taka wywołana funkcja SCM wszystkie wartości ma wyzerowane (a przynajmniej powinien - zauważyłem, że CLEO3 nie zawsze to respektuje, CLEO4 nie ma tego błędu), a w większości przypadków chcemy je samemu ustawić już w momencie jej wywoływania. Z racji, że ta funkcja ma je wszystkie osobne i zmienne z normalnego wątku w żaden sposób nie są nadpisywane, trzeba to zrobić właśnie w opcodzie wywołującym, czyli 0AB1. Oznaczamy liczbą ilość wartości, które chcemy przekazać funkcji (w przykładzie powyżej jest to 1), a następnie definiujemy, co mamy przesłać (w przykładzie jest to zmienna 0@ zostanie ustawiona na 10; jeśli ustawilibyśmy np. 8 wartości do przekazania, przesłalibyśmy wartości do zmiennych 0@-7@). Po zdefiniowaniu tego możemy także ustalić, gdzie funkcja ma zwrócić swoje wartości - liczby nie ustawiamy w opcode 0AB1, ustalamy za to przy 0AB2, ale o tym za momencik.


Zastosowanie


Podam przykład funkcji SCM, która wyliczy nam długość przeciwprostokątnej trójkąta prostokątnego o znanych obu przyprostokątnych, czyli sławne twierdzenie Pitagorasa:

Gdzieś w skrypcie:

Kod:
0AB1: call_scm_func @Pitagoras 2 12.0 4@ 0@


12.0 i 4@ to długości przyprostokątnych, które w funkcji SCM będą odpowiednio w zmiennych 0@ i 1@.
0@, czyli zmienna wątku głównego, będzie zawierała zwróconą wartość przeciwprostokątnej.

Sama funkcja:

Kod:
:Pitagoras
006B: 0@ *= 0@  // (float)

006B: 1@ *= 1@  // (float)

005B: 0@ += 1@  // (float)

01FB: 2@ = square_root 0@ // pierwiastek

0AB2: ret 1 2@


Zakładając, że w zmiennej 4@ mamy wartość 5.0, funkcja wyliczy nam przeciwprostokątną równą 13.0. Ale jak ją zwróci? Robi to opcode 0AB2, w którym ustalamy, ile i jakie zmienne mają zostać zwrócone. Wartość pierwsza musi być równa liczbie zmiennych, które "przygotowaliśmy" w czasie wywoływania opcodu 0AB1. 2@ zawiera wartość, która z funkcji zostanie zwrócona do pierwszej zmiennej odbierającej (w tym przypadku pierwszej i jedynej, czyli 0@).

Główne pytanie - czemu nie gosub? Odpowiedź jest prosta - gosub naruszy nam nasze zmienne z normalnego wątku. Twierdzenie Pitagorasa nie wymagało ich wiele, więc nie jest to najlepszy przykład - najlepiej te funkcje sprawdzają się w przypadku, gdy mamy niewiele wolnych zmiennych lokalnych, a potrzebujemy obliczyć coś naprawdę skomplikowanego (bez funkcji SCM GPS nie mógłby istnieć :) ).

Może wydawać się to trudne. Na początku takie jest, ale należy przeczytać to minimum parę razy - to się naprawdę da opanować.


Funkcja SCM jako warunek


SA ma sporo możliwości tworzenia skryptów SCM w sposób, w jaki Rockstar nigdy nie działał. Jednym z takich trików jest użycie gosubu i funkcji SCM jako warunku.

Użycie tego jest bardzo, bardzo proste. Wszystko można objaśnić, używając kawałku funkcji z modu GPS.

Kod:
[...]
if
0AB1: call_scm_func @GPS__FUNKCJA 3 RequestedCoordsOffset RequestedCoordsY 0@ // Wszystkie trzy zmienne są przekazywane do funkcji kolejno jako 0@1@ i 2@

then
[...]

:GPS__FUNKCJA
[...]
if 
and
3.0 > DistanceXY
10.0 > DistanceZ
then
0485:  return_true
else
059A:  return_false
end
[...]




Jak nietrudno się domyśleć na podstawie tego przykładu, o tym, czy gosub zwróci prawdę lub fałsz decydują dwa opcody. I tak 0485 ustawia "wynik" na "prawda", a 059A na "fałsz". One mogą się wzajemnie nadpisywać, więc możliwa jest wielokrotna zmiana wyniku w trakcie jednego sprawdzenia.

Funkcja, której wyżej kawałek został użyty jako przykład, podczas sprawdzania używa prawie 20 zmiennych. Dzięki temu widać, jak opłacalne są funkcje SCM - bez nich taka rozrzutność byłaby niemożliwa.




Jedna adnotacja co do tej metody: z racji, że gosub i funkcja SCM same z siebie nie są warunkami, to nie można używać ich w jednym sprawdzeniu razem z normalnymi opcodami lub dwóch gosubów na raz. W takim razie odpadają sprawdzenia typu:

Kod:
if and
0050: gosub @A
0050: gosub @B


lub

Kod:
if or
00DF:   actor $PLAYER_ACTOR driving
0050: gosub @C


Takie konstrukcje muszą być rozłożone na oddzielne sprawdzenia.


Dla ciekawych - dlaczego tak?


W jaki sposób działa warunek złożony z funkcji SCM/gosubu?
Ten trik wykorzystuje specyficzny sposób pojmowania warunków poprzez GTA. A mianowicie, rezultat sprawdzenia takiego warunku jest przechowywany w nieskończoność (więcej informacji w języku angielskim TUTAJ) - tak więc gosuby w ogóle nie są warunkami, jedynie opcody return_true i return_false ustawiają rezultat sprawdzenia w odpowiedni sposób.
To wyjaśnia też, czemu gosuby/funkcje SCM nie mogą być użyte w "if and" oraz w "if or" - nie są one parsowane jako warunkowe.

Idąc tym tropem, w niektórych przypadkach możliwe jest pozbycie się opcodów 0485 i 059A. Kiedy można ich nie używać? Na przykład wtedy:
Kod:
if
    0019: 0@ > 0
then
    
0485: return_true
else
    
059A: return_false
end
0051: return


Tłumacząc na "ludzki", powyższy kod wygląda tak jakby

Kod:
jeśli wynik sprawdzenia to TAK
ustaw wynik sprawdzenia na TAK
w przeciwnym wypadku ustaw na NIE


Opcody ustawiające sprawdzenie są w tym momencie zbędne, gdyż wynik sprawdzenia 0019 będzie identyczny z ustawieniami return_true i return_false (powyższy kod wygląda po prostu idiotycznie, a ten SCMowy jest dokładnie tak samo idiotyczny dla gry ;) ). Możemy więc to skrócić do:

Kod:
0019: 0@ > 0
0051: return


Jeszcze jedna ciekawostka - działanie opcodów 0485 i 059A może być symulowane przez niewielki hack pamięci wątku, np.

Kod:
0A9F: 0@ = current_thread_pointer
000A: 0@ += 0xC5 // IF result

0A8C: write_memory 0@ size 1 value true virtual_protect 0


Wpisanie wartości true spowoduje oznaczenie sprawdzenia jako "prawda", a false - "fałsz". Ta operacja nie ma jednak zbyt dużego sensu (zajmuje więcej miejsca w pliku CS/SCM i jest wolniejsza).
Dodane przez: Silent
2011-02-08 12:57:03
Ile razy bym to nie czytał nic a nic nie rozumiem.
Poprzedni 1 Następny
Created & Powered by MakG
Wszelkie prawa zastrzeżone