terça-feira, 17 de março de 2009

Brincando com o PHP 5.3 beta 2

Bom, pensando um pouco sobre a arquitetura da Framework que eu pretendo criar, eu decidi dar uma pesquisada sobre as novas versões do PHP para ver oque vem por ai. Fiquei feliz em saber que o PHP 5.3 deve ter um release oficial dentro dos próximos 3 mêses.

Como minha Framework deve demorar muito mais que isso para ter um release oficial, então eu resolvi instalar o PHP 5.3 Beta aqui na minha máquina para ver como está ficando. Para quem quizer instalar, eu encontrei um tutorial muito legal de como instalar ele na porta 81 a partir de uma instalação XAMPP padrão (não se preocupe, ele é igualmente útil mesmo que você não utilize o XAMPP).

Depois de instalado, vamos aos testes :)

Late Static Binding


Esse é um recurso que já me fez perder bastante a paciência (na verdade a ausência dele...). O problema do que o Late Static Binding resolve é mais fácil de ser descrito com exemplos, então vamos a um exemplo:

class A
{
public static function metodo()
{
return self::outro_metodo();
}

public static function outro_metodo()
{
return "classe A chamando";
}
}

class B
{
public static function outro_metodo()
{
return "classe B sobreescreveu";
}
}

echo B::metodo();


Dado o código acima, o natural (na minha opinião) seria que a saída fosse: "classe B sobreescreveu"

Mas esse não é o comportamento que o PHP adota, para esse caso o retorno seria: "classe A chamando"

Isso ocorre pelo local da chamada no PHP, ele sabe que o método "metodo" foi definido na classe A, então mesmo chamando a partir de B, ele vai ser executado como um membro de A.

Isso pode gerar grandes problemas quando se quer fazer com métodos estáticos com um certo nível de complexidade, no meu caso a classe do ActiveRecord.

Com o Late Static Binding esse problema é resolvido da seguinte forma:

class A
{
public static function metodo()
{
return static::outro_metodo();
}

public static function outro_metodo()
{
return "classe A chamando";
}
}

class B
{
public static function outro_metodo()
{
return "classe B sobreescreveu";
}
}

echo B::metodo();


O código está quase totalmente igual, a diferença é na hora da chamada do "outro_metodo" que antes era "self::outro_metodo()" e agora é "static::outro_metodo()". Com isso o PHP consegue manter a compatibilidade com códigos antigos.

Essa é uma grande adição e vai fazer com que possamos criar classes como o ActiveRecord com uma interface mais limpa :)

So pra finalizar, uma dica que também veio junto com esse recurso, que é o novo método: get_called_class()

esse método retorna a classe atual usando o mesmo esquema do static no exemplo anterior.

Namespaces


Esse aqui, não sei como o PHP passou tanto tempo sem ter... De qualquer forma, para os "Javistas" agora temos pacotes, para "Rubistas" temos módulos, e por ai vai.

Os namespaces não são nada além de uma forma para organizar suas classes (e como consequência, ajudando a evitar conflito de nomes).

A verdade é que não fiquei muito satisfeito como o PHP está usando isso no momento (pode mudar, espero que mudem...).

Vou citar alguns pontos, a começar pela definição/uso:

namespace MeuNamespace\SubNamespace\Aninhando;

sinceramente... quem foi que teve a idéia de usar a barra invertida para separar os namespaces? achei feio... mas dos males esse é o menor (para o uso as barras são usadas da mesma forma)

Quem conhece Java/.NET (ou outros, vocês entenderam...), sabe que ao importarmos um pacote, as classes ficam diretamente acessiveis; o PHP comeca falhando nesse ponto... não é possível importar um pacote e ter as classes diretamente acessiveis... se você quizer ter acesso direto a classe (como se ela estivesse no pacote atual) você deve importar as classes uma a uma, exemplo:

use MeuNamespace\A;
use MeuNamespace\B;

$a = new A();
$b = new B();


tendo em mente o mesmo caso, o código abaixo NÃO funciona:

use MeuNamespace;

$a = new A();
$b = new B();


Além de receber um erro de ClassNotFound você ainda ganha de presente um Warning: The use statement with non-compound name 'MeuNamespace' has no effect

Para resumir um pouco o resto dos recursos de use (para importar) vou citar alguns exemplos (esses funcionam):

use MeuNamespace\SubNamespace;
use MeuNamespace as MN;

$a = new MN\A();
$b = new MN\B();
$c = SubNamespace\C();


Outra coisa que imcomoda é quando se está desenvolvendo dentro de um namespace... Entendam que o PHP considera os namespaces como pastas do seu HD, então, se você estiver trabalhando em um namespace, e precisar acessar o namespace global, você precisa avisar isso, exemplo:

namespace MeuNamespace; //informa o namespace do arquivo

class A
{
public function __construct($a)
{
$a = \trim($a);
}
}


Ou seja, nada limpo pra se programar... espero que a turma do PHP também ache isso... senão vamos ter códigos PHP entupidos com essas barras invertidas no meio...

RFC: Namespace Separator
RFC: Namespace Resolution

Traits


Os traits no PHP, vamos resumir isso de forma simples, sabe aquelas interfaces que você implementa no objeto, você diz que o objeto vai usar a interface, e então você cria a implementação para os respectivos métodos. Os traits são como interfaces, mas com uma grande diferença: eles vem com implementação.

Isso resolve o problema de herança múltipla, para quem é do mundo Ruby, os Traits são os Mixins do PHP :P

Isso é legal para várias coisas, quem programa em Ruby sabe o poder que podemos ter com isso (falo da turma do Ruby porque para eles isso já é muito comum), um exemplo simples seria para quebrar a implementação de uma classe em várias partes (pretendo usar isso no ActiveRecord visto o tamanho da mesma...).

Eu achei estranho pois não consegui utilizar os traits, embora no RFC do PHP esteja dizendo que eles foram inclusos no PHP 5.3, quem sabe no proximo beta release?

RFC: Traits

Lambda e Closures


Com certeza esse é um dos recursos mais esperados por mim. Com isso será possível executar uma programação de high-order no PHP.

Nas versões atuais já é possível algo semelhante utilizando o método create_function() do PHP, mas além de acabar com a organização do código, não suporte sintaxe highlight, ainda é algo bastante limitado.

Agora com funções lambda e closures é bem mais prático criar uma programação high-order utilizando funções como blocos.

Eu fiz alguns testes aqui para uma criação de enumeraveis como em Javascript e Ruby:

class ArrayData implements IteratorAggregate
{
private $data;

public function __construct($data = array())
{
$this->data = $data;
}

public function getIterator()
{
return new ArrayIterator($this->data);
}

public function push()
{
$args = func_get_args();

foreach ($args as $arg) {
array_push($this->data, $arg);
}
}

public function each($block)
{
foreach ($this as $value) {
$block($value);
}
}

public function map($block)
{
return new static(array_map($block, $this->data));
}

public function inject($base, $block)
{
$this->each(function($value) use (&$base, $block) {
$base = $block($base, $value);
});

return $base;
}

public function reject($block)
{
$new_data = new static;

$this->each(function($value) use ($block, $new_data) {
if (!$block($value)) $new_data->push($value);
});

return $new_data;
}
}


Esse foi apenas um exemplo básico para fins de teste, com o código acima foi possível criar algo assim:

$data = new ArrayData(array(1, 2, 3));
$data->push(4, 5);

$data->map(function($value) {
return $value * 2;
})->reject(function($value) {
return $value > 5;
})->each(function($value) {
echo "$value
";
});

echo $data->inject(0, function($sum, $value) {
return $sum + $value;
});


Apesar de bastante legal, a performance não foi essas coisas, fiz o seguinte teste de beenchmark:

function beenchmark($block)
{
$start = microtime(true);
$block();
echo "
Executed in " . (microtime(true) - $start);
}

$a = range(0, 100000);
$b = new ArrayData($a);

beenchmark(function() use ($a) {
$s = 0;

foreach ($a as $value) {
$s += $value;
}
});

beenchmark(function() use ($b) {
$s = 0;

$b->each(function($value) use (&$s) {
$s += $value;
});
});

beenchmark(function() use ($b) {
$b->inject(0, function($s, $value) {
return $s + $value;
});
});


Que resultou respectivamente em:

Executed in 0.0579040050507
Executed in 0.131916046143
Executed in 0.254753112793

Como pode-se ver, uma operação de inject teve um desempenho 5 vezes pior que uma versão linear simples com arrays.

Obviamente a classe pode ter algumas coisas mais "raw" na implementação para melhorar esse cenário, estou apenas demostrando oque acontece no caso de uso excessivo de lambda, então devemos utilizar com cautela.

Fiz um teste onde o resultado saiu em desacordo com o RFC do PHP, usando o seguinte código:

class A
{
public function my_method()
{
return "ok, reach here";
}

public function inner_lambda()
{
$proc = function() {
return $this->my_method();
};

return $proc();
}
}

$a = new A();

echo $a->inner_lambda();

Isso me resultou em: Fatal error: Using $this when not in object context

Mas de acordo com o RFC do PHP (link abaixo) o uso de $this deveria ser possível para funções lambda criadas dentro de objetos, isso deve ser corrigido nos próximos releases.

RFC: closures

Conclusão


O PHP está melhorando bastante, isso vai suprir muitas coisas para poder criar uma framework mais ágil e prática de se usar, espero melhoras até o release final do PHP 5.3, mas acho que o PHP está indo no caminho certo.

segunda-feira, 9 de março de 2009

Bow and Arrow em Ruby com Shoes

Quando o professor da faculdade pediu um projeto para ser apresentado na cadeira de Linguagem de Programação 2 (que na minha faculdade é considerado orientação a objetos, ensinado com Java), eu perguntei se poderia fazer o projeto em outra linguagem que não fosse Java, e felizmente o professor aceitou.

Decidi então programar em Ruby, sobre o projeto, eu não tenho muita criatividade para softwares esporádicos, então minha decisão foi fazer um remake de um antigo jogo para windows 95, o "Bow and Arrow" (ou barrow para os mais chegados =P). Decidi que seria um bom desafio e me ajudaria a entender melhor o Ruby (já estudo Ruby a cerca e 1 ano, mas nunca fiz mais do que pequenos scripts e testes).

Depois de roubar todos os sprites do jogo original, eu comecei a programar, para criar a camada visual eu decidi utilizar o Shoes, porque eu ja tinha algum conhecimento sobre o mesmo, e também porque tem muito jogo feito com ele, então a comodidade me fez ficar nele.

Antes de começar eu decidi ler algums fontes de jogos feitos com Shoes, baixei vários jogos no shoesbox para ver como as pessoas estavam lhe dando com frame-rate, sprites animados... Infelizmente essa parte da missão não teve muito resultado, pouquissimas aplicações utilizavam algum tipo de engine para seus jogos e as soluções não tinham nada de muito sofisticado, a única conclusão foi que todo mundo usada o metodo "animate" para o main loop do jogo.

No começo do desenvolvimento eu fiz tudo em apenas um arquivo Ruby, apenas para fazer alguns testes, nesse ponto era possível controlar o arqueiro com o mouse, e atirar as flechas (que eram imediatamente repostas após atiradas). Depois de vários testes feitos eu vi que era hora de organizar aquela bagunça, então comecei a criar uma engine própria para o jogo.

A primeira parte era deixar o arquivo principal o mais enxuto possível, então deixei ele apenas iniciando a aplicação em Shoes, criando uma instância da classe do jogo, iniciando o "animate" e chamando o loop do jogo a cada iteração do animate. Eu sabia que eu precisaria separar as fases do jogo, mas eu decidi começar o desenvolvimento pelos elementos do jogo, então comecei pela classe básica dos elementos, essa classe não tinha muita coisa, apenas definição de variáveis básicos do elemento (x, y, largura, altura), teste de colisão (que também fiz a sobrecarga do operador & para testar colisão entre objetos, hehehe), e um método draw vazio. Sem muitas delongas, a partir dessa classe eu fiz a classe do herói, das flechas e dos balões.

Para o herói de inicio a coisa foi um pouco mais complicada, a inicar pelo problema de "estados", afinal o herói deveria poder ter vários "estágios":
  • stand -> estágio padrao, onde ele pode puxar a flecha para atirar
  • armed -> estágio armado, onde ele está preparado para atirar uma flecha
  • waiting -> estágio de aguardo, onde ele tem que aguardar um tempo para poder voltar ao stand para poder atirar novamente
Se eu estivesse em outra linguagem eu provavelmente teria feito um "switch/case" (ou case/when para ruby =P) dentro do método draw que iria fazer um teste para cada estado. Mas, eu to no mundo do Ruby, e eu já sabia que eu precisaria controlar estados em vários objetos diferentes. Então eu fui a lugar para criar uma StateMachine.

A idéia básica era simples, seria um módulo, onde teria métodos de classe para adicionar estados (onde os parâmetros seriam o nome do estado e um bloco para a execução do estado), e também o módulo já deveria sobrever o método draw para acionar o estado correto, e por final, métodos para ler/modificar o estado atual.

A idéia não teve problemas... já para implementar... Eu gastei umas 3 horas para fazer essa classe, não por ser difícil ou trabalhosa, mas por não conhecer direito essa parte do Ruby. Depois de alguns estudos eu vi que o correto seria usar algo do tipo:

module StateMachine
def self.included(base)
base.extend ClassMethods
end

module ClassMethods
# aqui vem os métodos da classe
end
end


Até ai tudo bem, mas meu primeiro erro foi conceitual, em não pensar na classe como uma instância, isso me "comeu" algum tempo, quando eu me dei conta disso, então eu sabia que deveria salvar os estados no "objeto da classe" e depois acessa-los a partir das instâncias. Depois de várias e várias tentativas, eu descobri que poderia fazer isso usando variáveis de instância dentro do ClassMethods, e acessando elas atravez de self.class dentro da instância (até agora não sei se foi o caminho mais correto, mas funcionou).

detalhe traumatizante: o Shoes não existe mensagens no console padrão, e quando tinha algum erro de execução ele simplesmente deixava a tela branca ou travava o jogo, sem dar nenhuma mensagem de erro... so depois de desenvolver cerca de 70% do projeto eu descobri que apertando "alt+/" o Shoes abre um console onde apareciam as mensagens de erro... agora imagina testar recursos de linguagem que você não conhece sem conseguir ver mensagens de erro (é algo parecido como depurar JavaScript em IE 6)

Depois disso a StateMachine ficou ok, e usei ela para implementar os estados no herói. Outro detalhe legal nesse ponto foi o uso de um hook, pois existiam ações do herói que eram comuns a todos os estados, então para resolver isso fiz um alias para o método draw da StateMachine, e redefini o método draw executando as ações padrão e depois chamando o draw do StateMachine (agora chamado de old_draw). Com isso eu já tinha feito várias coisas do jogo, tava indo tudo bem, então eu fui desenvolver usando o "Hackintosh" do meu notebook (tenho windows e leopard no meu HP), então me dei conta de um problema, no Mac o jogo estava rodando mais rápido... Isso significava que o "animate" do Shoes não garantia o frame-rate (oque pra mim até o momento era verdade...). Com isso foi nescessário a criação de calculos sobre tempo, eu fiz isso dentro do loop principal do jogo, calculanto o tempo passado entre o atual momento e a ultima iteração, tive que mudar muita coisa no jogo inteiro para sair passando esse "tempo passado" por entre todas as classes (em verdade... ontem depois de ter o jogo pronto eu pensei seriamente se não deveria ter deixado isso numa variável global ou em um ponto de acesso geral, para não ficar repassando isso por todo mundo, mas agora ja foi, que sabe num próximo refactoring?), e fora isso também foi nescessário mudar os cálculos de velocidade (que já estavam sendo usados nas flechas e nos balões) para trabalharem de acordo com o tempo.

Timing ok agora, o jogo no windows rodava com um FPS menor que no Mac, mas a velocidade do jogo era a mesma :)

Outro detalhe no herói, eu decidi que nessa versão não seria nescessário clicar com o botão direito do mouse para recarregar a flecha (nunca gostei desse detalhe no jogo original), mas para também não ser uma "metralhadora ambulante" eu coloquei um pequeno delay para ele poder atirar uma nova flecha (a troca entre os estados). Oque estava me incomodando era a implementação desse delay... no momento salvava o tempo ao entrar no estado de waiting, e no estado de waiting verificava o tempo passado a cara iteração até passar o tempo nescessário para então voltar ao estado stand. Me lembrei então do método setTimeout do Javascript, que me seria extremamente conveniente nesse caso, mas que não existe no Ruby. Então foi a hora de criar uma TimerMachine!

Como eu já tinha o dado do tempo passado a cada ciclo isso não seria difícil de implementar, e de fato não foi. Quando pronto, era apenas incluir o módulo TimerMachine, e dentro do ciclo do objeto deveria ser chamado o método update_timers(elapsed) para atualizar o tempo dos timers. Para utilizar era apenas fazer "add_timer(time, repeats, &block)", e o bloco seria executado após o tempo (em segundos). O repeats por padrão era 1 (executar apenas 1 vez) e em caso de 0 seria repetido indefinidamente. Essa implementação já estava adequada, mas me liguei que poderia ir mais longe :)

Como por padrão o método de update de todos os objetos era draw (de fato essa era a interface para o acesso externo), então eu criei um hook automático no módulo do TimerMachine, que verificava se a classe onde o mesmo foi incluido tinha o método draw definido, e em caso de sua existência, ele mesmo ja criava o hook chamando o update_timers automaticamente (Ruby's Magic!!)

Acho que posso dizer que depois desse ponto o desenvolvimento ficou mais fácil, basicamente eu criei a abstração para as fases e separei a parte básica do jogos dos níveis, fui criando as fases 1 a 1, sempre fazendo refactoring quando nescessário até o projeto sair.

Para quem quizer ver "mais de perto" os problemas que falei, por favor veja o histórico de código do projeto: http://github.com/wilkerlucio/bow_and_arrow/tree/master

O projeto também foi enviado para o ShoesBox (coleção de aplicações feitas em Shoes): http://the-shoebox.org/apps/139

Quem quizer dar uma olhada no gameplay do jogo eu enviei um vídeo para o youtube:



Para rodar o jogo é preciso ter o Shoes instalado na maquina, você pode baixar o Shoes em: http://www.shoooes.net/

Com o shoes instalado, apenas faça um clone do projeto na sua maquina, e rode com: shoes bow_and_arrow.rb

Como ainda me considero iniciante em Ruby, programadores mais experientes podem encontrar várias coisas no código que poderiam ter sido melhor implementadas com Ruby (tenho certeza que existem muitos pontos assim no codigo...), mas mesmo assim acho que pode ser útil para quem quizer dar uma estudada ;)

Quem quizer dar mais algum progresso para o jogo (eu ainda não implementei todas as fases do jogo original, como por exemplo a fase do Bull's Eye onde você tem que acertar o meio do alvo) por favor usem o GitHub como várias de vocês já devem conhecer ;)

Espero ter conseguido contribuir com algo para a comunidade, qualquer dúvida sobre o jogo podem perguntar pelos comentários ;)