Neste post, vou apresentar um pequeno projeto de exemplo que mostra uma integração de Lua com uma aplicação Android.
O que é Lua?
Lua é uma “embed language”, ou seja, tem como objetivo ser usada dentro de uma aplicação escrita com outra linguagem (para estender a app, configurar, etc.). Quando foi criada na PUC-Rio, por exemplo, era usada por engenheiros da Petrobras (que nem mesmo eram programadores) para configurar o funcionamento de alguns softwares usados nas plataformas de petróleo.
Mais detalhes na página oficial: http://www.lua.org
Como um programa escrito em Lua roda dentro de uma aplicação?
Lua nada mais é que uma biblioteca escrita em C. É multiplataforma, pois usa apenas as funções e tipos da biblioteca padrão do C. É possível executar programas escritos em Lua (scripts) através da linha de comando, mas isso nada mais é que um pequeno executável que tem a biblioteca Lua dentro.
Então, para incluir Lua na sua aplicação, basta incluir essa biblioteca no seu programa e usar suas funções. É possível carregar arquivos com os scripts, executá-los e integrar funções e dados; por exemplo, funções escritas em C que podem ser chamadas pelo script Lua, e funções do script podem ser chamadas pelo código em C.
Lua e Android
No link https://github.com/marciodrosa/LuaAndAndroidSample há um sample que mostra como integrar Lua em uma aplicação mobile. No caso, Android. Teoricamente, deve ser possível também em outras plataformas, como iOS e Windows Phone 8 (o Windows Phone 7 não tem suporte a programação com C, logo, só é possível executar Lua caso exista alguma implementação não oficial da biblioteca em C#).
O objetivo é mostrar como uma aplicação poderia ser estendida com um plug-in escrito pelo usuário.
Ao executar a app, são exibidos três caixas de texto. Ao alterar valores das duas primeiras, a soma destes valores aparece na terceira. Essa lógica e manipulação do terceiro campo é feita por um script Lua.
Mas as aplicações Android são escritas em Java, não em C!
Java permite integração com código escrito em C, a chamada Java Native Interface (JNI). O código escrito em C precisa ser compilado em uma lib, e por isso existem as ferramentas do Android para isso: o Android NDK (http://developer.android.com/tools/sdk/ndk/index.html). Além do compilador, há algumas bibliotecas disponíveis no NDK.
Assim, no Android, ocorre uma conversa entre três linguagens: Java <—> C <—> Lua.
Como funciona a app de exemplo LuaAndAndroidSample
Dentro do projeto há um arquivo “myscript.lua”, que é empacotado junto com a aplicação. Essa foi a maneira utilizada por este projeto de exemplo, mas é claro que poderia ser feito de outras maneiras, afinal, o script é apenas um texto; poderia ser recebido pela rede, lido de um arquivo, ou até mesmo criado dentro da app, em uma caixa de texto. O conteúdo do script é o seguinte:
return { onfieldvaluechangedbyuser = function(context) local op1 = tonumber(context.fields["Field one"].value) or 0 local op2 = tonumber(context.fields["Field two"].value) or 0 return { newfieldvalue = { field = "Field three", value = op1 + op2, } } end }
Este script é como um plugin, ele altera o comportamento da aplicação para mostrar uma nova informação na terceira caixa de texto. Quando a app é iniciada, ela carrega e executa o script, que apenas retorna uma tabela (objeto) com as funções de callback para serem chamadas em determinados eventos. No exemplo, esta tabela tem uma função chamada “onfieldvaluechangedbyuser”, que é chamada pela app quando uma caixa de texto tem o valor alterado pelo usuário.
A função onfieldvaluechangedbyuser recebe um objeto de contexto, que possui os dados da aplicação (como os valores das três caixas de texto). Na implementação, ela retorna um outro objeto (result), que é então processado pela app. Este outro objeto pode possuir um novo valor para algum campo. No myscript.lua, o script retorna um novo valor para ser setado na terceira caixa de texto.
Resumidamente, este é o fluxo do que ocorre:
Usuário altera um valor na caixa de texto –> app chama o callback do script, passando o contexto –> script retorna um resultado –> app processa o resultado.
A idéia dessa arquitetura é isolar a execução do script em uma sandbox: não há interação entre a app e o script (por exemplo, o script não altera o valor da terceira caixa de texto; ao invés disso, ele retorna um objeto solicitando que a app faça isso). Esse isolamento é feito para deixar a execução do script mais unitária, facilitar os testes e simplificar o fluxo.
Além disso, usei um pouco da idéia dos plugins do Blender (blender.org), que também são executados assim que a aplicação é iniciada, e que possui funções de callback para serem chamadas mais tarde, quando ocorrem eventos (também recebendo um objeto de contexto como parâmetro).
Como foi feita a integração na app (a “engine” de plug-ins)
A arquitetura básica da “engine” é exemplificada na seguinte imagem:
- A biblioteca em C foi nomeada de “Bridge”, já que é uma ponte entre a app e os scripts.
- A aplicação Java e o “Bridge” em C conversam entre si (Java solicita para a Bridge que o plugin execute; a Bridge retorna resultados para a aplicação Java).
- A Bridge e uma engine Lua conversam entre si. Essa engine não é o plugin em si. Ela é usada para fazer a lógica de chamada do plugin, processar erros, resultados, etc. Isso mostra como Lua pode ser usado não apenas para estender a aplicação: neste caso, foi usada para implementar uma lógica que pertence nativamente à aplicação. Isso pode ser muito útil para aplicações multiplataforma. Por exemplo, se a mesma app for implementada para outro sistema, como iOS, o código dessa engine em Lua pode ser reaproveitado.
- Por fim, a engine em Lua faz o plugin executar. O plugin não se comunica com ninguém, ele executa em uma sandbox isolada, evitando que a lógica implementada por um usuário possa agir de maneira inesperada na aplicação. Toda a comunicação é feita entre a engine Lua e a Brigde C.
Algumas informações adicionais
- Embora Lua utilize ANSI C (apenas as bibliotecas padrão), nem tudo são flores: o NDK, por exemplo, não possui a API de internacionalização do C (utilizada pelo Lua para saber se um número decimal usa ponto ou vírgula). Então, pequenos ajustes são necessários. O código fonte do Lua 5.2 pronto para ser compilado para Android pode ser pego no meu repositório do Github: https://github.com/marciodrosa/LuaAndroid. Além dos ajustes necessários, ele também direciona as mensagens de erro e log para o logcat.
- Para fazer o build de uma lib para Android usando código-fonte C, os arquivos de código devem ser colocados dentro de uma pasta chamada “jni” e o comando “ndk-build” deve ser executado. Dentro da pasta “jni” também há alguns arquivos de configuração, como “Android.mk” e “Application.mk”, e outras libs pré-compiladas, caso sejam usadas pelo projeto. O NDK automaticamente move os binários gerados para dentro da pasta “lib”. No projeto Java do Android, as libs que serão usadas devem ser carregadas usando System.loadLibrary (ver a classe StartActivity do projeto LuaAndAndroidSample). O build pode parecer um pouco difícil de entender no começo. Para mais detalhes, é necessário ler a documentação do NDK. A maior parte da documentação não está disponível online; ao invés disso, vem junto com o projeto do Android NDK, quando ele é baixado.
- No projeto AndroidAndLuaSample, há um método que gera o objeto “context”, que é passado para o callback do script. Ele gera o context concatenando scripts. Obviamente, isso não é uma boa prática, não estamos lidando com linguagens orientadas a string! Que fique claro que isso foi feito apenas para demonstração. O correto seria criar o objeto de contexto usando a API C do Lua.
- Este sample funciona apenas em dispositivos com processador ARM. É necessário fazer outro build das bibliotecas C para que elas funcionem com outros processadores, como Intel.
Links
Página oficial do Lua:
http://www.lua.org/
Documentação do Lua (manual, referência da API e referência da API C):
http://www.lua.org/manual/5.2/
Livro online gratuito sobre Lua (mas é de uma versão antiga do Lua, com algumas features desatualizadas):
http://www.lua.org/pil/contents.html
Android NDK:
http://developer.android.com/tools/sdk/ndk/index.html
Lua 5.2 para Android:
https://github.com/marciodrosa/LuaAndroid
Código-fonte do projeto de exemplo “LuaAndAndroid”:
https://github.com/marciodrosa/LuaAndAndroidSample
Interessante, mas eu posso desenvolver qualquer app usando lua? Tipo, fazer chamadas de banco de dados, mandar para impressora? A engine consegue fazer essa comunicação com o ndk? E a performance com fica? pq lua na comunica diretamente com o java, essa requisição torna o app lento? ou não se nota isso.
Rodrigo, essa engine que você vê aí no meu post é só uma prova de conceito, ela faz apenas o que eu exemplifiquei no post. Mas, na teoria, isso que você falou pode ser implementado, você teria que criar os métodos que você quisesse expôr e fazer a comunicação entre o Java, NDK e Lua, como eu fiz. Quanto a performance, nesse exemplo o script roda durante o input do usuário e não nenhuma lentidão acontece. Você falou em chamadas ao banco de dados e comunicação com a impressora: o script e se comunicar com o Java não deverá ser mais lento que essas operações de I/O. Eu me preocuparia mais com a memória nesses casos: digamos que você carregue 1000 registros de um banco de dados, transforme em 1000 objetos Java, para depois transformar em 1000 objetos Lua, o que obviamente resultará em 2000 objetos ao mesmo tempo na memória, ao menos que você tome os devidos cuidados (por exemplo, descartando o objeto Java logo após criar o seu correspondente em Lua, se for possível).
Entendi, mas vc sabe me informar se existe algum bom framework para eu usar com c++? e que estou pesquisando e estou meio perdido, tipo, se eu configurar o eclipse com ndk c++ e usar lua, existe um framework em que eu possa programar em lua e ela comunicar com o c++? isso na ide eclipse? se sim, vc me indicaria algum?
grato
Não tenho muita informação sobre isso. O Corona, talvez? Corona usava Lua para games, mas, pelo que vi no site, também pode ser usado para outros tipos de apps.