Um Loop Infinito, até que…
************************************ Tutorial Avançado *************************************
Esse tutorial nasceu por causa de um problema com que eu já me deparei mais de uma vez, não só em LSL, mas em outras linguagem de programação, como Java. Quando você sabe apenas uma ou duas linguagens de programação, seu pensamento tende a se limitar aos recursos daquela linguagem específica. Quando você já sabe mais de 4, começa a sentir falta de determinadas funções específicas que não existem em outras linguagens… Além das linguagens comuns de PC, como Pascal/Delphi, C++, Java, Lua, eu programo em Assembly Language, inclusive de microcontroladores, como 8085, 8053, etc… Por que isso é importante pra este tutorial? Bom, Assembly tem uma coisa chamada “interruption”, ou interrupção, que eu sempre senti falta no PC. São eventos que literalmente interrompem tudo, seja o que for, e o programa salta para o tratamento daquela interrupção. Até mesmo loops infinitos são quebrados em alguns casos! E é aí que entra o problema com que me deparo de vez em quando: às vezes é preciso que nós continuemos executando determinado comando ou função até algo aconteça. Não, um loop “for” não funciona, porque não existe um número fixo de vezes que determinado comando deve ser executado… Um loop “while” nem sempre encaixa também, por que nem sempre fazer “polling” dentro de um while é eficaz. Aliás “polling” é uma coisa que eu evito ao máximo, eu acho que a programação fica deselegante. Gasta-se um tempo precioso com polling, e quando o programa deve ficar atento a diversas coisas, o que fazer? Diversos pollings?
Eu estava programando um passeio de barco pela Ilha Marajó. Usei uma forma de comandar o barco remotamente, por piloto automático. O piloto automático então não passa de uma lista, com a sequência de comandos do barco… “Vá pra frente, vire à direita, vá pra frente, vire à esquerda…” Muito simples, certo? Só que o barco precisava ser movido não mais do que 0.1m de cada vez, ou o movimento ficava “pipocado”. A tudo bem… É só escrever “vá pra frente, vá pra frente, vá frente…”… Imagine o tamnaho que minha lista de comandos ficaria, para um passeio de mais de meia hora de duração… Então, deu saudade de assembly: “seu eu pudesse programar uma interrupção, poderia, sem polling, fazer com que o barco continuasse indo em determianda direção até eu mandasse mudar de direção ou parar…”.Lembrei de repente que LSL é orientado a eventos, e foi exatamente o que fiz: programei uma espécie de interrupção. E a coisa virou assim um loop que é infinito, até que… Ou seja, enquanto não mudar a direção, ou parar, o barco fica indo sempre na mesma direção.
O resultado foi excelente, e embora exista um certo atraso, funciona perfeitamente, não provoca lag, e não depende tanto do clientside, o que em LSL, é sempre um downside.
Então, mãos à obra! Aqui começa a explicação prática.
Qual foi o truque?
O voodoo que eu usei pode parecer muito complicado, por isso vou tentar explicar devagar.
Pense comigo: o que é um loop while infinito?
while (TRUE)
{
move_para_frente();
}
“mova para a frente, pra sempre”. Isso é um loop infinito. Mas como saio daí para mudar a direção? A primeira opção: se eu usasse um flag, que eu pudesse mudar de valor para FALSE, o loop sairia. E como o flag muda pra FALSE? Polling.
O que é Polling? Polling é cosntante perguntar, a intervalos sempre regulares de determinada condição aconteceu:
Entrou mais comando? Não? Continua… Entrou mais comando? Não? Continua…
Porque Polling é ruim? Em LSL, a resposta é simples: causa lag.
Continue pensando: O ideal então, é que conseguisse repetir o último comando até que um novo aparecesse. Sem loop… Apenas precisaria saber qual foi o último comando, e colocá-lo de novo na fila, caso não entrasse um novo… *estalo* Fila? Comando? Onde eu já vi isso antes? Quem já programou “socket connection” sabe do que eu estou falando. As informações chegam ao servidor por uma interupção, e são encaminhas em fila para serem tratadas. Será que dá pra fazer isso com LSL? Por que aí, os comandos do meu barco, seriam sempre tratados, sempre na ordem que foram enviados, e mais: dá pra saber facilmente se chegou um novo comando!
A técnica que eu usei foi a Técnica FIFO (First In First Out) de tratamento de comandos. A idéia é guardar os comandos em uma lista, e tratá-los a intervá-los regulares. Não existe polling, por que eu não fico verificando se houve comando, eu apenas os trato, a intervalos regulares, e não faço nada se não houver comando, mantendo o programa em aguardo.
Primeiro, precisamos saber quais os comandos que eu posso executar:
list commands = ["forward","back","left","right","stop"];
Também precisamos de um lugar para guardar os comandos que chegam, nossa fila FIFO:
list queue;
Agora, existem duas coisas a fazer com os comandos que chegam: guardá-los, independentemente do que exatamente fazem e depois executá-los, garantindo que todos os comandos serão executados.
A função abaixo guarda os comandos, na fila FIFO:
queueCommand(string command)
{
queue = queue + [command];
}
A fila em si, é uma variável “list”, que uma lista de qualquer coisa. O que vai torná-la uma lista de comandos FIFO é o jeito como vamos ler a lista, e tratar os dados dentro dela.
A função abaixo lê a lista, usando técnica FIFO, tratando a lista por “flush”, já eliminando o dado que foi tratado, e encaminhando o comando para ser executado, apropriadamente. Depois de executado a função que o comando chama, o programa volta para encerrar esta função, o que nos garante que o dado foi tratado, e que apenas um comando é executado de cada vez, a cada operação “flush”. Repare que existe uma variável de retorno, que pode ou não ser armazenada.
string flushCommand()
{
string Command = llList2String(queue, 0);
queue = llDeleteSubList(queue, 0,0);
if (Command == "forward") forward(); if (Command == "back") back(); if (Command == "left") left(); if (Command == "right") right(); if (Command == "stop") stop();
Command = llList2String(queue, llGetListLength(queue)-1);
return Command; }
Por que retornamos o último comando da lista? Por que se enviarmos um comando novo, este entrará no fim da fila, certo? Então, se não houver comando, queremos repetir o último comando enviado (que está no fim da fila) até que algum novo comando seja enviado. É esse comando retornado que garante o nosso “loop infinito até que”.
Agora, temos uma lista e podemos dar “flush” nela. Mas como populamos a lista? Enviando comandos, claro… No meu caso, usei “listen” para receber comandos, pois isso foi útil para mim. Por que? Eu queria poder falar comandos no chat para testar, e dessa maneira, o barco pode ser controlado remotamente, por um outro objeto, via piloto automático, ou por mim mesma. Eu fiz um outro script que fica falando os comandos, que chamei de “autopilot”. O barco apenas responde. Usei o listen do barco para colocar novos comandos à lista, assim que forem recebidos. O Listen tem o poder de interromper, da mesma maneira que “touch”, pois é um evento, e acontece sempre que existir texto no canal escolhido.
listen(integer channel, string name, key who, string msg)
{
integer index = llListFindList(commands, [msg]);
if (index != -1)
{
queueCommand(msg);
}
else
{
llSay(0,msg+": Invalid Command");
}
}
Basicamente, o que a função faz é: “se for um comando, coloque na fila”. Dessa maneira, não precisaremos verificar se um comando é válido antes de executá-lo, pois se entrou na fila, com certeza é válido.
Agora só falta executar os comandos, não é? Eles, chegam, entram na fila, e quando são executados (flush)? Lembra que eu disse que seriam executados a intervalos regulares, sem polling? Faremos o flush via timer, assim, a intervalos regulares, os comandos são executados, garantindo o fluxo contínuo de instruções, o que dá ao barco uma movimentação “aveludada”, contínua, sem “pulinhos” e independente do clientside, e o que é melhor, 100% lagfree.
timer()
{
string lastCommand = flushCommand();
queueCommand(lastCommand);
}
Executamos o primeiro comando da fila com flush, guardando o último da fila, e encaminhando-o de volta à fila. Lembre-se de que sempre que o “listen” receber um comando, ele também será adicionado a fila, dessa forma, basta que o intervalo do timer seja regulado de modo que dê tempo do listen encaminhar comandos também. Como o movimento de um barco é lento por natureza, e as instruções llSetPos() e llRotLookAt() que eu usei provoca sleep de 0.2 segundos, usei um timer de 2.0 segundos, o que significa, que sempre vai demorar 2.0 segundos para que o barco mude de direção, mas se você precisa de um intervalo menor, é só ajustar o timer, e garantir que tudo que está dentro do seu “listen” demore menos para executar do que o intervalo do timer!
Ah, isso não é um script completo… é só uma explicação…
Sim, isso mesmo! Dividir informações não é necessariamente “dar” scripts prontos. Neste caso específico, o meu script está sendo usado numa aplicação comercial, e o dono da Ilha Marajó não permite a divulgação. Acredito porém, que a informação aqui prestada seja suficiente para replicar, desde que o programador esteja disposto a trabalhar…
Espero ter ajudado, e da próxima vez que você pensar “puxa seria mais fácil se isso pudesse continuar até eu mandar parar” seu desejo terá sido realizado!
Oi, ficou legal o seu tutorial, mais deveria fazer um tutorial de como criar scripts, pois no Second Life interio pelos lugares q andei apenas duas pessoas sabe fazer scripts.
Então me mande uns comandos(um tipo de tutorial)ou bote um tutorial aqui.
Meu e-mail é: ericcm@bol.com.br
Obrigado.
Show de bola, vc fez uma adaptação do comando para uma “interrupção”, usando recursos da LSL. Parabéns. Voce é um craque criativo, que utilizou um comando que está dentro da propria LSL e que nem todo programador encherga como possibilidade para atingir o mesmo propósito. Você é do tipo, “se num der desse jeito vou tentar com esse outro…”. Você é um pesquisador nato, deve ter sido aluno nota 10, nas aulas de logaritmo.
Abração…