====== Łączenie języka C++ i Lua ====== Szymon Jabłoński M1ISI ==== Wstęp ==== Głównym celem stosowania języków skryptowych jest możliwość ingerencji w istniejący system(np. silnik gry) bez konieczność znajomości i ingerencji w kod źródłowy. Przykładami języków skryptowych są np. Bash, Python, Perl, PHP, UnrealScript czy interesująca nas Lua. Języki skryptowe znalazły swoje zastosowanie w procesie tworzenia gier komputerowych np. w agorytmach sztucznej inteligencji(sterowanie postaciami Non-Playable Characters), obsługi interfejsu gry czy sterowaniem przebiegiem fabuły(np. dialogi). Jednym z najczęściej wybieranym językiem okazał się język Lua. Zastosowany został w takich tytułach jak: Crysis, FarCry, Baldur's Gate, World of Warcraft czy nasz rodzimy Wiedźmin. Innym popularną aplikacją wykorzystującą język Lua jest Adobe Lightroom, którego ok. 40% kodu źródłowego napisanego jest w Lua. Pełna lista gier i programów wykorzystujących Lua dostępna jest na stronie http://www.lua.org/uses.html Lua jest językiem skryptowym łączącym ze sobą elementy języka proceduralnego o składni zbliżonej do języka Pascal, rozszerzonego o bardziej złożone konstrukcje opisu danych opartych na tablicach asocjacyjnych. Język został zaprojektowany w celu rozszerzenia funkcjonalności aplikacji, często jednak jest wykorzystywany jako samodzielny język. Lua jest projektem open-source zaimplementowanym w języku C zgodnie ze standardem ANSI C,którego główną zaletą jest prostota, wydajność i przenośność kodu. ==== Wymagania ==== W celu rozpoczęcia pracy z językiem Lua musimy pobrać ze strony http://luabinaries.sourceforge.net/download.html skompilowane wersje bibliotek dla naszego systemu operacyjnego(do wyboru Windows, Linux oraz MacOS). Istnieje również możliwość pobrania kodu źródłowego i samodzielnej kompilacji : http://www.lua.org/ftp/. Interesującymi projektami są również Lua for Windows : http://code.google.com/p/luaforwindows/, Lua for Linux: http://luaforge.net/projects/luaforunix/ oraz LuaRocks: http://www.luarocks.org/. Poza binarkami zawierają również edytory do tworzenie i debugowania skryptów Lua. ==== Podstawy języka Lua ==== Strona ma na celu ukazanie sposobów i możliwości integracji języków C++ oraz Lua, jednak przedstawię krótko podstawy języka Lua wraz z przykładami. W celu dogłębnego poznania składni i możliwość języka polecam dokumentacje umieszczoną na stronie internetowej. Na początek standardowy Hello World (Listing 1) **Listing 1** - program Hello World w Lua -- HelloWorld.lua print("Hello from Lua!"); --[[ Przykład komentarza umieszczonego w kilku liniach ]] === Konwencje leksykalne === * W języku Lua identyfikatory mogą się składać z dowolnego łańcucha znaków alfanumerycznych lub znaku _. * Ograniczeniami są: zakaz rozpoczynania identyfikatora od cyfry oraz słowa zastrzeżone jak //and//, //or//, //break//, //else// itp. * Lua rozróżnia wielkość liter, więc For będzie oznaczał identyfikator zaś for będzie dotyczył pętli. * Komentarze rozpoczyna się podwójnym myślnikiem i działa analogicznie jak w C++, ignorowane są wszystkie znaki, aż do końca linii. * Istnieje również możliwość komentowania całego bloku kodu na kształt znanych z języka C, które w Lua zastąpione jest ciągiem --[[". === Typy danych i wyrażenia === W Lua podobnie jak w innych językach skryptowych nie deklarujemy typów zmiennych. Zamiast tego każda zmienna ma dynamiczny typ określany przez jej wartość. Istnieje 8 podstawowych typów danych: * //nil// - przyjmuje jedną wartość nil, która określa brak konkretnej wartości (często nil jest stosowany do określenia zmienną niezainicjalizowaną); * //boolean// - standardowe wartości boolowskie true/false; * //number// - liczba rzeczywista, zmiennoprzecinkowa; * //string// - standardowy łańcuch znaków; * //userdata// - typ danych pozwalajacy na przetrzymywanie dowolnych danych z języka C(np. wskaźników); * //function// - ciąg wykonywalnych instrukcji, funkcje mogą być deklarowane wewnątrz innych funkcji lub być zwracane z innej funkcji *// thread// - wątek w Lua; * //table// - tablica asocjacyjna(więcej informacji w dalszej części strony); Ciekawym mechanizmem języka jest poleceniem //type// pozwalające na sprawdzenie typu zmiennej. Przykład pokazano na Listingu 2. **Listing 2** - przykład polecenia type w Lua. print(type(a)) --> nil (zmienna 'a' nie została zainicjalizowana) a = 10 print(type(a)) --> number a = print a(type(a)) --> function W Lua dostępne są następujące operatory: * Operatory arytmetyczne: +, -, *, /; * Operatory porównania: ==, ~=, <, >, <=, >=- * Operatory logiczne: //and//, //or//, //not// === Instrukcje i funkcje === Lua posiada standardowe instrukcje znane z języków Pascal/C: * instrukcja przypisania =; * instrukcje warunkowe, if/elseif/else/end; * pętle, for/do/end, while,do,end, repeat/until; * wywołanie funkcji; * lokalna deklaracja zmiennych; **Listing 3** - przykład stosowania funkcji i warunków logicznych, funkcja rekurencyjna obliczająca silnie. -- obliczenie n! function factorial (n) if n == 1 then return 1; else return n * factorial(n-1); end end -- obliczenie 5! print(factorial(5)); --> 120 === Tablice === Najistotniejszymi strukturami danych w Lua są tablice asocjacyjne reprezentowane przez kolekcję par (klucz, wartość). Kluczem może być wszystko poza wartością //nil//. Elementami tablicy mogą być wszystkie wartości podstawowych typów Lua. Oznacza to, że również mogą to być omawiane w tym miejscu Tablice. Pozwala to na tworzenie takich struktur jak lista, wektor, kolejka czy stos. Listing 4 pokazuje przykłady wykorzystania tablic w Lua realizując proste zbiory danych, listę jednokierunkową, czy strukturę zawierającą zarówno atrybuty jak i metody. **Listing 4** - przykład wykorzystania tablic do realizacji zbiorów danych. -- prosty wektor liczb squares = {1, 4, 9, 16, 25, 36, 49, 64, 81} -- zbiór danych creature = { name = "Ghoul", health = 100 } print(creature["name"]); --> Ghoul print(creature.name); --> Ghoul -- lista list = nil; list = {next = list, value = 2}; list = {next = list, value = "test"}; list = {next = list, value = math.pi}; ptr = list; while ptr do print(ptr.value); ptr = ptr.next; end; --> 3.141592 test 2 -- struktura/klasa Vec3 = {}; Vec3.new = function(x, y, z) return {x=x, y=y, z=z, length=function() return math.sqrt(x*x+y*y+z*z) end;} end; v = Vec3.new(4, 2, 4); print(v:length()); --> 6 === Biblioteka standardowa Lua === Lua jak każdy porządny język, wyposażona została w bogatą standardową bibliotekę zawierającą bogaty zestaw narzędzi wyższego poziomu. Należą do niej następujące moduły: * funkcje wejścia/wyjścia; * funkcje systemowe; * operacje na łańcuchach znaków; * operacje na tablicach; * obsługa wątków; * funkcje matematyczne; Poniższy kod pokazuje kilka przykładów wykorzystania biblioteki standardowej. **Listing 5** Wykorzystanie biblioteki standardowej Lua -- losowanie liczb math.randomseed(os.time()) print(math.random(10)); -- wyświetlenie liczby wylosowanej z przedziału [1,10] -- obliczenie wartości cos(pi/5) print(math.cos(math.pi*0.2)); -- zapis do pliku f = assert(io.open("output.txt", "w")) f:write("test"); f:close(); ==== Integracja z C++ ==== Zapoznaliśmy się już z podstawami języka Lua, część przejść do najważniejszej sprawy czyli integracji z językiem C++. === Przygotowanie do pracy z Lua === W celu rozpoczęcia pracy z Lua musimy wykonać dwie następujące operacje: - Dołączenie do projektu skompilowaną bibliotekę Lua oraz dołączyć w kodzie nagłówki Lua (lua.h, lualib.h i lauxlib.h) - Należy zapamiętać, że skrypty Lua wykonywane są na maszynie wirtualnej, musimy więc ją zainicjować. Prosty przykład ukazujący wspomniane operacje i wywołujący z poziomu kodu C++ skrypt Lua pokazany jest na Listingu 6. **Listing 6** Inicjalizacja maszyny wirtualnej Lua i wywołanie skryptu Lua z poziomu kodu C++ #include // dołączenie nagłówków Lua extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } int main() { // utworzenie maszyny wirtualnej Lua. lua_State* L = lua_open(); // inicjalizacja standardowych bibliotek luaL_openlibs(L); // wczytanie skryptu Lua if (luaL_dofile(L, "zpr.lua")) { // Walidacja wcztania skryptu std::cout << "Error while running script: " << lua_tostring(L, -1) << std::endl; return 1; } // zamknięcie maszyny wirtualnej lua_close(L); return 0; } === Wywoływanie funkcji Lua w kodzie C++ === Potrafimy już z poziomu kodu C++ zainicjalizować maszynę wirtualną Lua i uruchomić skrypt napisany w Lua. Przejdźmy krok dalej - spróbujmy wywołać funkcję zdefiniowaną w skrypcie Lua z poziomu kodu C++. Zacznijmy od przykładowej funkcji obliczającą różnicę dwóch liczb. **Listing 7** Funkcja Lua obliczającą różnicę dwóch liczb. --sub.lua function(x,y) return x - y end Aby umożliwić wywoływanie funkcji ze skryptu Lua musimy zdefiniować kodzie C++ funkcję, w której odłożymy na stos parametry funkcji, a następnie zdejmiemy ze stosu wartość zwracaną przez funkcję. Ilustruje to poniższy przykład: **Listing 8** Przykład wywołania funkcji Lua w kodzie C++. #include // dołączenie nagłówków Lua extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } // Interpretator Lua lua_State* L; // Funkcja wywołującą funkcję ze skryptu Lua i zwracająca otrzymany wynik int luaSub(int x, int y) { int sub = 0; // nazwa funkcji w pliku sub.lua lua_getglobal(L, "sub"); // pierwszy argument lua_pushnumber(L, x); // drugi agrument lua_pushnumber(L, y); // wywołanie funkcji z dwoma argumentami i jedną wartością zwracaną lua_call(L, 2, 1); // pobranie wyniku i zdjęcie ze stosu sub = static_cast(lua_tointeger(L, -1)); lua_pop(L, 1); return sub; } int main() { int sub = 0; // utworzenie maszyny wirtualnej Lua. L = lua_open(); // inicjalizacja standardowych bibliotek luaL_openlibs(L); // wczytanie skryptu Lua if (luaL_dofile(L, "sub.lua")) { // Walidacja wczytania skryptu std::cout << "Error while running script: " << lua_tostring(L, -1) << std::endl; return 1; } // wywołanie funkcji sub = luaSub( 10, 15 ); std::cout << "Substraction: " << sub << std::endl; // zamknięcie maszyny wirtualnej lua_close(L); return 0; } === Wywoływanie funkcji C++ w kodzie Lua === Spróbujmy zrobić wykonać to samo zadanie odwrotni. Wywołajmy funkcję napisaną w C++ w kodzie Lua. Funkcje w Lua są wywoływane używając wskaźnika na funkcję: ''typedef int (*lua_CFunction) (lua_State *L);'' Wynika z tego, że funkcje przyjmują jako argument interpretator Lua i zwracają wartość int. Napiszmy funkcję w C++, która będzie liczyła sumę liczb przekazanych do funkcji, których liczba będzie zmienna: **Listing 9** Funkcja liczącą sumę podanych liczb w C++ wywoływana z poziomu kodu Lua. #include // dołączenie nagłówków Lua extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } // Interpretator Lua lua_State* L; // Funkcja przygotowana do wywołania w kodzie Lua static int suma(lua_State *L) { // pobranie liczby argumentów int n = lua_gettop(L); int suma = 0; // sumowanie argumentów for (int i = 0; i < n; ++i) suma += lua_tonumber(L, i); // zwrócenie sumy lua_pushnumber(L, suma) // zwracamy ilość wartości zwracanych przez funkcję return 1; } int main() { int sub = 0; // utworzenie maszyny wirtualnej Lua. L = lua_open(); // inicjalizacja standardowych bibliotek luaL_openlibs(L); // rejestracja funkcji suma pod nazwą f_suma lua_register(L, "f_suma", suma); // wczytanie skryptu Lua if (luaL_dofile(L, "zpr.lua")) { // Walidacja wczytania skryptu std::cout << "Error while running script: " << lua_tostring(L, -1) << std::endl; return 1; } // zamknięcie maszyny wirtualnej lua_close(L); return 0; } **Listing 10** Przykład wywołania w skrypcie Lua funkcji napisanych w C++ --zpr.lua suma = f_suma(10, 20, 30, 40, 50) print("Suma wynosi: ", suma) ==== Więcej informacji ==== http://www.lua.org - strona domowa Lua. http://www.lua.org/manual/5.1 - dokumentacja on-line Lua. http://lua-users.org/wiki/ - zbiór artykułów, dodatków i przykładów Lua http://www.inf.puc-rio.br/~roberto/pil2/ - programowanie w Lua http://code.google.com/p/luaforwindows/ - biblioteki i narzędzi dla systemu Windows. http://luaforge.net/projects/luaforunix/ - biblioteki i narzędzi dla systemu Unix. http://www.luarocks.org/ - deployment and management system for Lua modules.