![]() |
Lua Utils
Biblioteca utilitária para facilitar a integração de Lua com C++
|
A biblioteca LuaUtils foi desenvolvida para auxiliar na interface entre aplicações C++ e scripts Lua 5.1 . Esta biblioteca utiliza o frmework Qt e portanto seu uso é apropriado apenas para aplicações Qt.
A biblioteca não tem como objetivo suplantar o uso da API padrão de Lua 5.1, mas sim complementar esta API, facilitando a interface quando necessário. Seu uso não foi atualizado para a versão 5.2 e requer (pequenas) mudanças.
Seu desenvolvimento tem sido pautado pela necessidade de uso, assim algumas funcionalidades "que obviamente deveriam estar incluidas", não estão, apenas porque ainda não foram necessárias nos contextos em que a biblioteca já foi utilizada.
Além disso, algumas funcionalidades poderiam ter sua sintaxe um pouco modificada para unificar a sintaxe de algumas operações. Um trabalho a ser feito!
As seções a seguir apresentam exemplos de como utilizar a biblioteca em tarefas comuns de interface entre o universo C++ e o universo Lua. Cabe salientar que a descrição assume que o leitor já está familiarizado com a API de Lua e com o mecanismo de passagem de parâmetros entre os ambientes C e Lua através de uma pilha.
A criação de um novo ambiente Lua é feita através da instanciação de um objeto LuaEnv, seguida pela chamada ao método newState() que cria o ambiente e abre as bibliotecas padrão. O ambiente NÃO é fechado quando o objeto LuaEnv for destruído. Para isto é necessário uma chamada a closeState().
Exemplo:
Se for desejada a criação de um ambiente Lua temporário que será fechado quando o objeto LuaEnv sair de escopo, pode ser utilizado o padrão RAII através do uso de um objeto AutoLuaEnv que se encarrega de efetuar a chamada a newState() em sua construção e chamar closeState() em seu destrutor.
Para executar scripts no novo ambiente, utilize os métodos runScript() ou runScriptStr().
Para trabalhar com um ambiente Lua existente, basta instanciar um objeto LuaEnv passando como parâmetro o lua_State ao qual este embiente deverá estar associado. Isto é importante, por exemplo, para transformar o parâmetro lua_State recebido em todas as funções C chamadas de Lua para um LuaEnv.
Exemplo:
A leitura do valor de uma variável global Lua é feita simplesmente através da chamada do método LuaEnv::getGlobal() que recebe como parâmetro o nome da variável. O valor obtido é retornado como um objeto QVariant.
Esta função pode ser utilizada para obter dados dos tipos mais comuns de Lua, i.e. números, strings, booleans, tabelas, userdata, funções e nil. Demais tipos (threads e light userdata) não são suportados, e neste caso a função retorna um QVariant nulo.
Para variáveis do tipo numérico, string e booleano, basta utilizar as funções da classe QVariant para obtermos o valor desejado, verificar se o valor é nulo (QVariant::isNull()) ou mesmo descobrir seu tipo (QVariant::type() == QVariant::Double, QVariant::String ou QVariant::Bool).
Exemplos de uso:
Para objetos do tipo tabela, userdata e função, consulte as seções Trabalhando com tabelas, Trabalhando com userdata, Trabalhando com tipos registrados via tolua++ e Chamando funções Lua.
Por default, o objeto lido não é mantido na pilha de Lua, a não ser que o objeto seja uma tabela ou uma função. Este comportamento pode ser alterado com um segundo parâmetro passado para getGlobal(). Veja a documentação da função LuaEnv::getGlobal() para mais detalhes.
A escrita de um valor em uma variável global Lua é feita simplesmente através da chamada do método LuaEnv::setGlobal() que recebe como parâmetro o nome da variável e seu valor como um QVariant.
Esta função pode ser utilizada para escrever dados dos tipos mais comuns de Lua, i.e. números, strings, booleans, tabelas, userdata, funções e nil.
Valores numéricos, booleanos e strings são convertidos para um QVariant automaticamente. Tabelas, userdata e funções devem ser convertidos explicitamente via QVariant::setValue() (tabelas e valores do tipo tolua++ possuem funções auxiliares para isto - ver Trabalhando com tabelas e Trabalhando com tipos registrados via tolua++).
Exemplos de uso:
Tabelas são representadas por objetos da classe LuaTable. Estes objetos podem representar uma tabela nova ou uma tabela existente e provêem diversos métodos para sua manipulação.
Para criar uma nova tabela e colocá-la no topo da pilha, utilize o construtor LuaTable(LuaEnv*). Já para criar um objeto LuaTable que se refere a uma tabela existente, utilize o construtor LuaTable(LuaEnv*, int, bool). Este construtor recebe como segundo parâmetro o indice da pilha de Lua que se refere à tabela, e como terceiro parâmetro um booleano que indica se o objeto LuaTable deve criar uma referência para a tabela (true) ou apenas armazenar o índice da pilha (false). Esta última opção (false) é mais eficiente, porém o objeto LuaTable se mantém válido apenas enquanto a tabela estiver neste indice da pilha.
Quando uma tabela é retornada via uma chamada a LuaEnv::getGlobal(), por default, a tabela é mantida na pilha de Lua e o objeto LuaTable construído simplesmente armazena o indice da tabela. Este comportamento pode ser mudado através de parâmetro da chamada a LuaEnv::getGlobal().
Dado um objeto LuaTable, os métodos getIndex(), getField(), setIndex() e setField() podem ser chamados para ler e escrever na tabela. Seu funcionamento é análogo ao dos métodos LuaEnv::getGlobal() e LuaEnv::setGlobal() (ver Lendo valores de variáveis globais e Escrevendo valores em variáveis globais). Consulte a documentação da classe para demais opções disponíveis.
Para converter um QVariant v em um LuaTable use:
Um exemplo mais completo, obtendo a tabela de uma variavel global e verificando o tipo do retorno antes da criação da tabela pode ser feito com:
Para converter um LuaTable em um QVariant use o método toVariant(). Exemplo:
Para colocar uma tabela na pilha use o método push(). Exemplo:
Para criar um LuaTable a partir de uma entrada da pilha use:
Um exemplo mais completo, obtendo uma tabela dos parâmetros passados por Lua para uma função C:
A classe LuaProxy provê um mecanismo para facilitar a exportação de objetos C++ para o ambiente Lua. Sob este framework, para exportar uma classe C++ MyClass para o ambiente Lua, deve ser criada uma segunda classe MyClassProxy que herda de LuaProxy::Base e define os metametodos utilizados para acessar o conteúdo de um objeto no ambiente Lua.
A classe LuaSimpleProxy provê uma implementação que permite passar para o ambiente Lua e recuperar novamente em C++ objetos de qualquer tipo, sem possibilidade de operar sobre o valor em Lua.
Consulte a documentação das classes LuaProxy e LuaSimpleProxy para maiores detalhes. Nos exemplos abaixo, considere o cenário acima de um objeto C++ de tipo MyClass com um Proxy correspondente de tipo MyClassProxy.
Para converter um QVariant v em um objeto do tipo Proxy, use o método estático toObjectOfClass().
Para converter um Proxy em um QVariant, use o método QVariant::setValue()
Para colocar um Proxy na pilha, use o método estático pushObject. Exemplo:
Para criar um Proxy a partir de uma entrada da pilha use os métodos estáticos toObjectOfClass() ou checkObjectOfClass() que opera de maneira similar às funções luaL_checkXXXX():
Um exemplo mais completo, obtendo um Proxy dos parâmetros passados por Lua para uma função C:
Veja também em Associando métodos a um userdata um exemplo completo de um objeto Proxy que exporta uma série de métodos para o ambiente Lua.
Para trabalhar com userdatas do tipo "light", a classe LuaLightUserdata fornece um framework similar àquele apresentado pela classe ToluaUserdata descrito na próxima seção.
A ferramenta tolua++ é um gerador de código fonte utilizado para criar automaticamente um "binding" Lua para um conjunto de classes em C++. A biblioteca Sg (Smart Graphics) utiliza esta ferramenta para exportar seus objetos para Lua.
Para facilitar o trabalho com objetos exportados via tolua++, a biblioteca LuaUtils fornece a classe ToluaUserdata. Nos exemplos abaixo, considere uma classe chamada SgGraphic exportada para o ambiente lua via toloua++;
Para converter um QVariant v no objeto tolua++ contido por este, use a macro ToluaUserdataValue(). Exemplo:
Outra alternativa que fornece mais controle sobre verificação de tipos consiste em:
Para criar um QVariant a partir de um objeto exportável para Lua via tolua++, crie um objeto do tipo ToluaUserdata e use o método toVariant() deste objeto. Exemplo:
De maneira análoga, para colocar na pilha um objeto exportado via tolua++, crie um objeto do tipo ToluaUserdata e use o método push() deste objeto.
Exemplo:
Para criar um objeto a partir de um dado na pilha de Lua, podem ser utilzados tanto a função padrão tolua_tousertype() quanto a criação de um objeto ToluaUserdata que já integra uma verificação de tipos. Exemplos:
A chamada de funções em Lua a partir de código em C++ pode ser feita através do uso da classe LuaFunction que modela uma função em Lua. Seu uso é análogo ao uso da classe LuaTable, na medida em que mantém internamente uma referência para uma função Lua (que pode ser uma referência real ou apenas um indice na pilha). Operações para transformar um LuaFunction de/para QVariant e para incluir ou retirar uma LuaFunction da pilha seguem os mesmos passos descritos para a classe LuaTable (ver Trabalhando com tabelas).
Dado um objeto do tipo LuaFunction, a chamada da função se dá através do método call() que recebe os parâmetros a serem passados como um QVariant. Todas as chamadas são efetuadas em modo protegido. Em caso de erro a chamada a call() retorna false e uma mensagem de erro. Os resultados da chamada são colocados na pilha de Lua e devem ser acessados a partir desta.
A classe provê ainda métodos estáticos LuaFunction::callFunction() que recebem o nome da função e efetuam sua chamada instanciando um objeto LuaFunction internamente. Em geral esta é a maneira mais simples de efetuar uma chamada. Exemplos:
A função LuaTable disponibiliza a função callMethod() para efetuar a chamada de uma função que reside como um campo da tabela. Estas chamadas são feitas da mesma maneira que uma chamada a LuaFunction::call(), porém adicionam automaticamente como primeiro parâmetro da chamada a própria tabela. Desta forma, seu uso é apropriado para a execução de funções em Lua que devem ser chamadas via a sintaxe de chamada de método tabela:metodo().
O cadastro de funções em C para que estas possam ser chamadas a partir do ambiente Lua é feito através do uso da função LuaEnv::registerFunctions().
Para o cadastro de métodos, é importante lembrar que estes devem ser estáticos. Todas as funções cadastradas devem retornar um inteiro e receber como único parâmetro um lua_State*. Exemplo:
A classe auxiliar LuaMethod provê um mecanismo interessante para auxiliar na exportação de métodos para serem chamados através de um objeto exportado para o ambiente Lua. Para funcionar, o objeto deve ter sido exportado para Lua através do uso da classe LuaProxy, ou seja, os métodos exportados devem pertencer a um objeto que herda de LuaProxy::Base.
Dentro destas condições, LuaMethod permite que métodos NÃO estáticos sejam cadastrados e os objetos corretos sejam ativados sempre que um método for chamado em Lua via a sintaxe objeto:metodo().
O exemplo abaixo ilustra o processo completo de criação de uma classe LuaImage que encapsula um objeto do tipo Image (obviamente o exemplo foi reduzido para incluir apenas uma parte não repetitiva do código).
Quando uma função registrada é chamada a aprtir do ambiente Lua, seus parâmetros residem na pilha. Para obter valores para tipos tradicionais, utilize as funções padrão luaL_checkXXX(). No caso de tipos especiais, tabelas, userdata ou funções, podem ser utilizados os procedimentos para converter um valor na pilha para um LuaTable, para um proxy, para um objeto exportado via tolua++ ou para um LuaFunction, conforme mostrado nas seções Trabalhando com tabelas, Trabalhando com userdata e Trabalhando com tipos registrados via tolua++.
A classe LuaStackBalancer pode ser utilizada para garantir que o estado da pilha se mantenha igual (com o mesmo número de entradas) após uma sequência de operações sobre a mesma. Esta classe utiliza o padrão RAII, salvando o topo da pilha quando o objeto é criado e retaurando seu topo para a posição salva quando o objeto é destruido. Seu uso é particularmente útil em situações onde o gerenciamento de erro leva a saídas prematuras da função com objetos tendo sido colocados na pilha.
As macros LUA_SAVE_TOP() e LUA_CHECK_TOP() podem ser utilizadas para verificar a posição do topo da pilha em ambiente de debug.