LUA - prosty sposób na zaawansowaną konfigurację

06.09.2010 - Łukasz Milewski
TrudnośćTrudność

Co łączy grę SimCity 4, grę MMO World of Warcraft, CryENGINE 2 - jeden z najlepszych silników graficznych oraz program Adobe Photoshop Lightroom? To Lua - język programowania bardzo popularny wśród osób zajmujących się modami. Podczas lektury tego artykułu nauczysz się wykorzystywać to narzędzie.

Czym jest Lua?

Lua to przenośny, potężny, szybki i lekki skryptowy język programowania, który można osadzić w aplikacji. Język Lua jest dynamicznie typowany. Kod jest kompilowany do bajtkodu, który następnie jest wykonywany przez maszynę wirtualną. Lua posiada automatyczne zarządzanie pamięcią (nowoczesny garbage collector).

Te cechy powodują, że jest to idealne narzędzie do konfiguracji, pisania skryptów i szybkiego prototypowania. W tym artykule skupimy się na konfiguracji. W zaledwie kilku linijkach kodu zaprogramujemy system konfiguracji, który będzie pozwalał na bardzo wiele (w tym także na wykonywanie kodu!).

Dlaczego konfiguracja jest tak ważna?

Każdy program daje się w pewnym stopniu konfigurować poprzez dobieranie stałych. Taką stałą może być np. nazwa programu, prędkość poruszania się postaci gracza w grze, dokładność obliczeń matematycznych czy adres strony www, z której należy pobrać dane.

Wszystkie te informacje można pamiętać w kodzie programu w postaci stałych zadeklarowanych w osobnym nagłówku. Niestety wówczas trzeba ponownie kompilować program gdy zachodzi potrzeba zmiany konfiguracji. Jednocześnie ograniczamy użytkownikom naszego programu możliwość zmiany ustawień, przez co musimy przygotowywać wiele różnych wersji (być może nawet jedną dla każdego użytkownika!).

Stąd znana zasada głosi, że: "jeżeli pewne wartości można wyrzucić poza kod programu, należy to zrobić". Oszczędzamy sobie w ten sposób mnóstwo pracy.

Czy warto wykorzystywać skrypty do konfiguracji? Oczywiście! Proste pliki konfiguracyjne są dobre na początek, jednak szybko okazują się niewystarczające. Gdy nasz projekt trochę urośnie, obowiązkowe staje się podzielenie pliku na kilka mniejszych, a każdego z nich - na sekcje. Wraz z coraz większą liczbą użytkowników wykorzystujących nasze oprogramowanie staje się przydatne, aby pliki konfiguracyjne same reagowały na zmiany w środowisku (np. zmiana systemu operacyjnego, zainstalowane inne oprogramowanie).

Każdą z tych funkcjonalności trzeba zaimplementować, a na to potrzeba czasu i umiejętności. Można ten proces uprościć i wykorzystać gotowy parser konfiguracji - język Lua.

Potrzebne biblioteki, instalacja, kompilacja

Aby zacząć przygodę z Lua, potrzebujemy kilku bibliotek. Przede wszystkim powinniśmy zainstalować następujące:

W przypadku LuaBIND należy pobrać i zainstalować odpowiednie pliki ze strony internetowej. Nie należy używać np. pakietów z repozytorium w systemie Linux. Jest tam stara wersja biblioteki.

Instalacja i kompilacja

BOOST i LuaBIND, jak każdą bibliotekę, należy zainstalować w systemie. Użytkownicy Windows powinni dodatkowo zaopatrzyć się w narzędzia MinGW. Przygotowanie Lua polega na skompilowaniu jej przy pomocy polecenia make. Wywołane bez argumentów pokaże listę dostępnych platform. Najważniejsze to:

  • linux - dla użytkowników systemu GNU/Linux
  • mingw - dla użytkowników systemu MS Windows, którzy zainstalowali MinGW
  • local - tworzy lokalną instalację (nie instaluje Lua w systemie)

Powstaje katalog lua-5.1.4. Zróbmy do niego link o nazwie lua. Aby skompilować program dodajemy flagi do naszego kompilatora i linkera: -I lua/include -L lua/lib -llua5.1 -lluabindd [d na końcu jest dwukrotnie].

Oprócz biblioteki w paczce ze źródłami znajdziemy także dokumentację (doc/contents.html). Możemy również przeanalizować wiele ciekawych przykładów w katalogu test - np. life.lua lub fib.lua

Uruchomienie maszyny Lua i wczytanie pliku

Prosty przykład

Zacznijmy od prostego wczytania wartości globalnych. Stwórzmy plik z konfiguracją (config_1.lua):

1
2
APPLICATION_NAME = "informatyka.wroc.pl hello ;-)"; 
APPLICATION_VERSION = 13; -- numerujemy wersje liczbami pierwszymi

Napisaliśmy dwie linijki. W pierwszej przypisujemy do zmiennej napis. W kolejnej liczbę. Na tym przykładzie widzimy również, że możemy tworzyć komentarze (zaczynają się od znaków -- i trwają aż do końca linii).

Aby obsłużyć taką konfigurację, piszemy następujący program (config_1.cpp):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
 
// Te dwa nagłówki są potrzebne abyśmy mogli korzystać z luabind.
// Zwróć uwagę na końcówkę .hpp (nie .h)
#include <luabind/luabind.hpp>
#include "lua.hpp"
 
int main() {
    // Włączmy maszynę lua
    lua_State* lua = luaL_newstate();
    luaL_openlibs(lua);
    luabind::open(lua);
 
    // Wykonujemy plik config_1.lua. Od razu sprawdzamy czy się to
    // udało. Jeżeli nie to wypisujemy błąd
    if (luaL_dofile(lua, "config_1.lua") != 0) {
        std::cout << lua_tostring(lua, -1) << "\n";
        lua_pop(lua, 1);
        return 0;
    }
 
    // Odczytujemy zmienną z konfiguracji 
    // i wyświetlamy ją na ekranie
    std::cout << "Witaj w kursie lua: " 
              << luabind::globals(lua)["APPLICATION_NAME"] << "\n";
 
    // Odczytujemy zmienną z konfiguracji 
    // i zapamiętujemy ją w zmiennej w C++
    int lucky_number = luabind::object_cast<int>(
                      luabind::globals(lua)["APPLICATION_VERSION"]);
    std::cout << "Szczęśliwy numerek to: " << lucky_number << "\n";
 
    // Wyłączamy maszynę Lua
    lua_close(lua);
}

Przykład z tablicami

Zobaczyliśmy jak niewiele pracy jest potrzebne aby zacząć korzystać ze skryptów Lua. Jednak obecnie wczytujemy tylko najprostsze pliki. W każdej linijce jest nazwa zmiennej, znak równości i wartość zmiennej. Chcielibyśmy uporządkować nasz plik konfiguracyjny. W tym celu dodamy sekcje. Jest to bardzo łatwe.

W sekcji 'network' umieścimy takie stałe jak 'port' czy 'hostname'. Z kolei w sekcji 'debug' będą takie wartości jak 'filename' i 'on'. Dodatkową cechą, którą zyskujemy za darmo dzięki użyciu LuaBIND, jest fakt, że sekcje mogą być zagnieżdżone. Przykładowa konfiguracja (config_2.lua):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- sekcja z opcjami do debuggowania
debug = {
   on = 1, -- czy wypisywanie komunikatów ma być włączone
   filename = "", -- do jakiego pliku wypisywać komunikaty
                  -- "" oznacza stdout
}
 
-- opcje związane z siecią
network = {
   mode = 'tcp', -- informacje czy połączenie będzie przez 'tcp' czy 'udp'
 
   -- informacje o komputerze, z którym się łączymy
   host = {
      port = 1026, -- port na który mamy się połączyć
      hostname = "localhost", -- host, z którym nawiązujemy połączenia
   }
}

Aby móc odczytać tak zbudowany plik konfiguracyjny potrzebujemy zmodyfikować kod naszego programu w C++. Spójrzmy na zmienioną wersję (config_2.cpp):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
 
#include <luabind/luabind.hpp>
#include "lua.hpp"
 
int main() {
    lua_State* lua = luaL_newstate();
    luaL_openlibs(lua);
    luabind::open(lua);
 
    if (luaL_dofile(lua, "config_2.lua") != 0) {
        std::cout << lua_tostring(lua, -1) << "\n";
        lua_pop(lua, 1);
        return 0;
    }
 
    // pobieramy zmienną zagnieżdżoną w sekcji
    luaL_dostring(lua, "print(debug.on);");
    bool debug = luabind::object_cast<int>(
        luabind::globals(lua)["debug"]["on"]);
 
    std::string debug_filename = 
        luabind::object_cast<std::string>(
            luabind::globals(lua)["debug"]["filename"]);
 
    if (debug) {
        std::cout << "debug.filename = " 
                  <<(debug_filename == "" ? "<stdout>" : debug_filename)
                  << "\n";
    }
 
    
    // a tak możemy pobrać zmienną wielokrotnie zagnieżdżoną
    int port = luabind::object_cast<int>(
        luabind::globals(lua)["network"]["host"]["port"]);
    std::string hostname = luabind::object_cast<std::string>(
        luabind::globals(lua)["network"]["host"]["hostname"]);
 
    std::cout << "Łączymy się z " << hostname
              << " na porcie " << port << "\n";
 
 
    lua_close(lua);
}
5
Twoja ocena: Brak Ocena: 5 (2 ocen)

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com