====== Łą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.