Slackware 12.2: Instalando MySQL 5.1

   Pra quem não sabe como instalar o MySQL no Slackware aqui vai a dica. É muito simples e não há nenhum segredo. Baixe o arquivo mysql-5.1.x-linux-i686-glibc23.tar.gz no endereço http://dev.mysql.com/downloads/mysql/5.1.html (aqui, baixei a versão 5.1.31). Dentro do arquivo compactado há um documento chamado INSTALL-BINARY (http://d.1asphost.com/jeancarone/INSTALL-BINARY). Neste arquivo estão todas as instruções de instalação listadas aqui.
   O Slackware 12.2 já vem com a versão 5.0.67 do MySQL, portanto, vamos retirá-la.

# pkgtool

   Dentro do pkgtool, escolha a opção Remove e procure pelo mysql-5.0.67-i486-1. Pressione barra de espaço para selecionar o pacote e Enter para excluí-lo. Após a exclusão vamos a instalação.
   Primeiramente temos que criar um grupo e um usuário para a base de dados. Lembrando que ambos não precisam ser necessariamente mysql (provavelmente o usuário e grupo mysql já estarão criados).

# groupadd mysql
# useradd -g mysql mysql
# cd /usr/local/

   Note que após criados, mudamos para o diretório /usr/local. É para este diretório que iremos extrair o MySQL. Este caminho também não é obrigatório.

# gunzip < /caminho/para/mysql-5.1.x-linux-i686-glibc23.tar.gz | tar xvf –

   Pronto, os arquivos foram descompactados para a pasta /usr/local/mysql-5.1.x-linux-i686-glibc23. Vamos criar um link simbólico com um nome mais simples para a pasta.

# ln -s /usr/local/mysql-5.1.x-linux-i686-glibc23/ mysql

   Link criado. Para ter certeza de o conteúdo da distribuição esteja disponível para o usuário mysql temos que ajustar alumas permissões (a não ser que você tenha extraído o conteúdo como usuário mysql; em meu caso, estou como root). Entraremos na pasta através do link e executamos:

# cd mysql
# chown -R mysql .
# chgrp -R mysql .

   Em seguinda, criaremos o diretório de dados do MySQL:

# scripts/mysql_install_db –user=mysql

   Basicamente toda a distribuição do MySQL pode ter o usuário root como own. A exceção é o diretório data que tem que ter como own o usuário mysql. Para isso, como root, execute os seguintes comandos:

# chown -R root .
# chown -R mysql data

   Inicializando a base de dados:

# ./support-files/mysql.server start

   Se tudo estiver OK, você receberá a mensagem Starting MySQL SUCCESS!. Para pará-lo apenas troque o start pelo stop:

# ./support-files/mysql.server stop

   Outra maneira de se inicializar o MySQL via shell (ainda dentro da pasta /usr/local/mysql):

# ./bin/mysqld_safe –user=mysql &

   Mensagem exibida:

090224 17:31:59 mysqld_safe Logging to ‘/usr/local/mysql/data/darkstar.err’.
090224 17:31:59 mysqld_safe Starting mysqld daemon with databases from /usr/local/mysql/

   Pronto, base de dados funcionando!

Posted in Linux (Slackware) | Leave a comment

Slackware 12.2: Habilitando Broadcom BCM4312 802.11b/g (Dell Inspiron 1525)

   Recentemente troquei meu velho companheiro HP Pavillion zv6000 por um Inspiron 1525 da Dell, e como já esperava, tive que brigar um pouco com o Slack pra colocar o novo hardware em perfeito funcionamento. Coincidentemente, no mesmo dia, é lançada a nova versão do Slackware (12.2). Aproveitei essa onda do "novo" e resolvi estrear o novo notebook com a nova versão do Slack.
   Enquanto baixava a nova versão, instalei o Ubuntu 8.10 para analizar o desempenho da máquina com recursos gráficos mais avançados como os do Compiz Fusion. Ao iniciar o Ubuntu (após a instalação) reparei que todos os drivers tinham sido reconhecidos e nenhum trabalho adicional foi necessário. Tudo estava OK, recursos como som e vídeo funcionavam perfeitamente. Foi ai que o alerta de drivers proprietários do Ubuntu apareceu, solicitando a confirmação da instalação do driver apropriado para a placa de rede Wi-Fi do notebook. Na mesma hora ativei e anotei o nome do mesmo (Broadcom 802.11 Linux STA driver), pois, sabia que isto poderia me economizar algumas dores de cabeça no futuro, já que o novo driver era desconhecido para mim (meu antigo zv6000 utilizava o b43-fwcutter-011 e o broadcom-wl-4.80.53.0.tar.bz2 no Slackware 12.1).
   Terminado o download, adeus Ubuntu, bem vindo Slackware 12.2.
   Abaixo, um lspci:

lspci | grep Broadcom

   Saída:

0b:00.0 Network controller: Broadcom Corporation BCM4312 802.11b/g (rev 01)

   Como visto na saída do comando lspci, minha placa de rede Wi-Fi é uma Broadcom BCM4312. Não utilizarei b43-fwcutter e afins pois, são drivers providos pela comunidade Linux, e não pela Broadcom. Também não utilizaremos Ndiswrapper para mantermos o Slack 0% Windows.
   A primeira coisa a se fazer é encontrar código fonte do driver correto para a BCM4312. Este pode ser obtido através do endereço http://www.broadcom.com/support/802.11/linux_sta.php (atenção para a escolha do source). Em meu caso, baixei o arquivo hybrid-portsrc-x86-32_5_10_27_11.tar.gz (no link 32-bit driver). Em seguida, baixe o arquivo README.txt. Este arquivo contém todas as instruções de instalação do driver STA e foi a principal fonte de informações deste artigo.
   Copie o arquivo hybrid-portsrc-**.tar.gz para /opt/ e descompacte-o em uma pasta separada. Entre na pasta recém criada execute:

make -C /lib/modules/2.6.27.7/build M=`pwd` clean
make -C /lib/modules/2.6.27.7/build M=`pwd

   Como estou utilizando a versão 12.2 do Slack, meu Kernel por padrão é o 2.6.27.7. Caso você esteja utilizando outro, apenas substitua a versão do mesmo no comando acima.
   Se houver um erro de compilação (o que provavelmente acontecerá) e o arquivo wl.ko não for gerado (na mesma pasta do source), haverá a necessidade de se utilizar um patch. Este patch pode ser encontrado no seguinte endereço: http://www.uluga.ubuntuforums.org/showpost.php?p=6361013&postcount=10 (baixe o arquivo hybrid_wl-5.10.27.11_patch-2.6.27.tar.gz).
   Copie o patch para a pasta do código fonte, a mesma pasta do Makefile, das pastas lib e src. Extraia o conteúdo do patch:

tar -xvvf hybrid_wl-5.10.27.11_patch-2.6.27.tar.gz

   Execute o patch:

patch -p1 -E < hybrid_wl-5.10.27.11_patch-2.6.27

   Por último, execute novamente (caso tenha usado o patch):

make -C /lib/modules/2.6.27.7/build M=`pwd` clean
make -C /lib/modules/2.6.27.7/build M=`pwd

   Verifique o conteúdo da pasta e certifique-se de que o driver foi gerado com sucesso, isto é, se o arquivo wl.ko foi criado. Agora, precisamos remover os módulos fornecidos pela comunidade Linux, caso estejam instalados:

rmmod bcm43xx
rmmod b43
rmmod b43legacy

   Tudo OK, finalize:

modprobe ieee80211_crypt_tkip
insmod /opt/source-do-driver/wl.ko

   Verifique agora se a luz indicadora de conexões wireless acendeu. Em meu caso não acendeu de imediato, e sim, após alguns segundos (entre 5 e 10). Caso haja algum problema tente remover (rmmod) e adicionar o módulo ieee80211_crypt_tkip novamente.
   Observação: Acrecente as duas linhas acima no arquivo /etc/rc.d/rc.local para que o driver seja carregado durante a inicialização do sistema operacional. Como minha encriptação WPA já esta configurada, acrecentei também o comando dhclient para obtenção automatica do endereço de IP. Desta forma após a inicialização já estou conectado a Internet.
   Pronto! Agora você pode executar um iwconfig e encontrar sua placa wireless. Aqui a interface mapeada foi a eth1. Se houver uma rede disponível ao alcance de seu computador e esta rede não exigir senha, você já estará pronto para navegar. Apenas lembre-se de executar o dhclient caso ainda não tenha obtido um endereço IP. Não se esqueça também do /etc/resolv.conf configurado com um servidor DNS válido.
   Até a próxima!

Posted in Linux (Slackware) | 1 Comment

Decorator (Java)

   Salve salve galera! Hoje vou apresentar pra vocês um padrão de projeto muito útil quando o assunto é variação de objetos e dinamismo de criação: O design pattern Decorator. Com este padrão, podemos “envolver” (você verá que realmente parece algo envolvendo outra coisa) um determinado objeto com determinado comportamento distinto em tempo de execução.
   Decorators são muito úteis quando realmente precisamos “decorar” alguma coisa sabendo que a quantidade ou variabilidade podem mudar. Vamos dar um exemplo bem típico. Você está desenvolvendo uma janela “na mão”, para visualização de texto. Então você percebe que nem sempre precisará de barras de rolagem horizontais ou verticais em sua janela, e sim, apenas quando necessário. Como colocá-las e remove-las, fazendo com que desapareçam e reapareçam apenas quando o texto ultrapassar os limites da janela. Decorator se encaixa perfeitamente aqui.

   Decorator design pattern de acordo com a Gang of Four:

   Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to sub-classing for extended functionality.

   Anexar responsabilidades adicionais a um objeto dinamicamente. Decorators fornecem uma alternativa flexível de herança para funcionalidades estendidas.

    Mas como encaixamos decoradores no exemplo da janela citado acima? Por instância, vamos imaginar que as janelas deste exemplo serão bem simples e sem nada demais, apenas uma apresentação do conteúdo para algum cliente. Dependendo da largura e altura de todo o texto, precisaremos das barras de rolagem para visualizá-lo completamente.
   Começamos a construção de um objeto decorado sempre com seus decoradores (no caso ConcreteDecoratorA [barra vertical] e/ou ConcreteDecoratorB [barra horizontal]) e terminamos com seu ConcreteComponent (a janela de visualização). An? Como assim? Vou explicar de forma conceitual, mas a maneira mais fácil de entender a lógica da solução, é debugando o código do exemplo prático que passarei a seguir e visualizando cada passo da aplicação (senão entender muito agora, não se assuste. Apenas releia a parte teórica novamente após executar o exercício prático).
   Antes de tudo, temos que criar uma interface em comum (AComponent). Em nosso caso, é requisito que todos os componentes saibam se desenhar (doStuff()). Criamos então nosso componente concreto (ConcreteComponent) a ser decorado (Janela, o personagem principal). Com este objeto criado, passamos esta referencia ao construtor de algum dos decoradores, não importando qual deles virá primeiro. Cada um saberá exatamente o que terá que fazer com o objeto recebido. Vamos supor que o passamos para a classe BarraVertical. BarraVertical receberá então em seu construtor o componente concreto recém criado (Janela) e inicializará seu pai (Decorator) com o objeto passado. Decorator então armazena uma Janela. Note que temos um objeto BarraVertical armazenando uma Janela em seu pai (que implementa o método de desencadeamento dos decoradores). Vamos agora adicionar a BarraHorizontal. Para isto devemos passar a referência da BarraVertical (que tem uma referência a uma Janela) para o construtor de BarraHorizontal. Dessa forma fazemos o encadeamento de nossos objetos. Temos então uma BarraHorizontal que armazena um objeto do tipo BarraVertical que armazena um objeto Janela.
   Decorator, pai de ConcreteDecoratorA e ConcreteDecoratorB, guarda o estado de construção do objeto passado, e implementa o método desencadeador (neste caso, doStuff(), que chama doStuff() dos demais decoradores).
   Ao criar um Decorator, a primeira classe instanciada deve ser o componente concreto, seguido de seus demais decoradores. O interessante é que não importa a ordem em que iremos chamar estes decoradores. Cada um sabe devidamente o que deve ser feito e aonde deve ser feito. Um exemplo de encadeamento de construção:

// AComponent == Componente
// Decorator == Decorator
// ConcreteComponent == Janela
// ConcreteDecoratorA == BarraVertical
// ConcreteDecoratorB == BarraHorizontal
// Obs: A classe Decorator, armazena o próximo objeto a ser chamado na seqüência e implementa o método
// polimórfico responsável.

Componente c = new BarraHorizontal(new BarraVertical(new Janela()));

Outra forma de se chamar o código acima seria:

Componente c;
c = new Janela();
c = new BarraVertical(c);
c = new BarraHorizontal(c);
c.doStuff();

   O que permite o tipo de chamada encadeada no primeiro trecho, são os construtores das classes que armazenam quaisquer componentes do tipo AComponent. Quando chegamos à chamada c.doStuff(), primeiramente chamamos o método de BarraHorizontal que faz seu trabalho e chama seu pai, que chama doStuff() em BarraVertical (armazenado no pai de BarraHorizontal), que faz seu trabalho e chama seu pai (que armazena uma Janela), que chama Janela, que se desenha e "volta", revertendo a seqüência anterior (agora BarraVertical e em seguida BarraHorizontal), executando tarefas (se existirem) ainda não executadas.

Um exemplo prático:

   Vamos imaginar a seguinte situação. Você é um desenvolvedor e precisa de uma tabela dinâmica que trabalhe de acordo com o tipo de usuário logado. Assim, para cada tipo de tabela você terá um detalhe diferente. Bom, poderíamos resolver este problema facilmente adicionando uma simples flag para cada tipo de usuário e apenas perguntar com um bloco if qual tipo de tabela será impresso na tela do cliente. Aqui seria interessante fazer uma pergunta: E se os requerimentos mudarem? E se estes detalhes mudarem de posição, lugar ou quantidade? Como em nosso caso, os tipos de usuário são vários, e novos tipos podem ser adicionados, seria legal se tivéssemos uma forma de desacoplar essa criação e evitarmos uma seqüência terrível de ifs ou switches. Assim, manteríamos uma tabela global original e facilmente adicionaríamos novas funcionalidades de acordo com o necessitado, de acordo com o tipo de usuário. Para isso podemos utilizar o Decorator pattern. Assim toda vez que novos tipos de usuários forem adicionados, você precisará apenas de uma nova cadeia de construção de objetos para a montagem do objeto final. Se novos recursos para os usuários forem criados, estes são facilmente adicionados, pois, você apenas precisará criar uma nova classe para a nova funcionalidade e não precisará mexer em nenhuma linha de seu código antigo. Isso preserva seu aplicativo de erros lógicos, freqüentes quando estamos trabalhando com cadeias de instruções condicionais, e também torna a manutenção uma brincadeira de criança. Sabemos que programar baseando-se em design patterns torna o desenvolvimento inicial um pouco mais trabalhoso. Mas imagine-se em um grande projeto, onde você acaba de entrar na equipe, e, então no primeiro dia você se depara com cadeias imensas de instruções condicionais. Ou então com módulos extremamente acoplados, onde a lógica se espalha em vários lugares distintos, causando redundância desnecessária e arquivos com códigos imensos (e ainda por cima com a lógica "batida" junto com esse "bolo"). Com certeza não seria uma boa recepção para mais um tripulante a bordo.
   Com os padrões de projeto, definimos não só uma organização estruturada e padrão, mas como também uma forma poderosíssima de reutilização de objetos e manutenção de código. Quando você mencionar para outro programador "esta classe faz um Bridge com esta outra classe", o entendimento do negócio (essencial para um bom projeto) será muito rápido, senão imediato. Assim toda a equipe falará apenas uma língua, e o desenvolvimento ocorrerá de maneira extremamente linda (risos) e eficiente.
   Para o nosso exemplo iremos ter inicialmente três tipos de usuários. Os visitantes, os usuários logados e os administradores logados (observação: todos os clientes não logados serão tratados com o perfil de visitantes). Os visitantes deverão visualizar além informações da tabela, um cabeçalho comum com o texto “Olá visitante!”. Os usuários registrados poderão ver além da tabela, um cabeçalho comum com o texto (“Olá ” + varNomeDoUsuarioLogado + "!") e um rodapé especial com a quantidade de registros encontrados. Por último, os administradores, verão os citados acima, e, em adição, um novo cabeçalho com opções de administração da consulta (como ordenação e etcetera) e abaixo do rodapé, uma pequena área de texto (como um segundo rodapé) com observações (se elas existirem) em relação à consulta realizada. Temos então a regra de nossa tabela:

 

   Vamos começar definindo nossa interface. Definimos apenas um método padrão para desenho, pois, este será o método polimórfico que todos os componentes da hierarquia irão realizar.

package decorator;

interface CompTabela
{    
   void desenha();
}

   Agora definiremos nosso componente concreto que será decorado (a tabela).

package decorator;

public class Tabela implements CompTabela
{
    public void desenha() {
        System.out.println("Tabela.");
    }
}

   Até aqui tudo bem. Temos uma interface definindo um comportamento padrão e uma implementação básica. A classe Tabela utiliza o método desenha() para desenhar a si mesma. Vamos agora criar a interface de nossos decoradores da tabela.

package decorator;

abstract public class Decorators implements CompTabela
{
    abstract public void desenha();

    CompTabela componente;

    public Decorators(CompTabela umComponente) {
        componente = umComponente;
    }

    public void desencadeia() {
        if(componente != null)
            componente.desenha();
    }
}

   A classe Decorators é o pai dos objetos decoradores. Ela é abstrata, portanto, não pode ser instanciada, além de ter uma variável de instância responsável por armazenar o objeto repassado ao filho. Repare que o método desenha(), foi definido como abstrato, diferente do diagrama UML que vimos acima. Tiramos dele a responsabilidade dada a desencadeia() justamente para defini-lo como abstract e obrigar as novas versões de decoradores concretos herdeiros de Decorators, a implementarem o método desenha() (particularmente, prefiro esta abordagem). Assim, teremos certeza de que todos os novos decorators implementarão o método correto para desenho (desencadeia() poderia ser desenha()). Repare no método desencadeia(). Ele guarda todo o segredo do desencadeamento (acalme-se, estamos chegando lá).
   Agora que definimos nossa interface decoradora podemos então criar os objetos desejados para nossa tabela. Esses objetos podem ser qualquer coisa que você desejar e aonde você desejar. Você poderia criar qualquer forma de decoração em sua tabela que ela seria facilmente aplicada ao layout. O código de nosso primeiro decorador concreto:

package decorator;

public class CabecalhoVisitante extends Decorators
{
    public CabecalhoVisitante(CompTabela componente) {
        super(componente);
    }

    public void desenha() {
        System.out.println("Olá visitante!");
        super.desencadeia();
    }
}

   Muita atenção de agora em diante. Veja primeiramente o construtor da classe e repare que ela repassa diretamente o objeto recebido para o pai. Este é um comportamento obrigatório da linguagem Java e você sempre deve inicializar o pai com o objeto recebido no filho. Esta chamada deve sempre ser a primeira dentro do construtor. Se você tentar colocar algo como System.out.println("Qualquer saída!"); antes da chamada super(), um erro de compilação aconteceria (call to super must be first statement in constructor). Outra coisa a se observar é que mesmo os construtores vazios, implicitamente chamam este método. A chamada aos pais é obrigatória, pois, ao criar um objeto concreto, qualquer objeto repassado ao seu construtor subirá primeiramente à super classe "paizão", e só depois descerá a hierarquia novamente executando as chamadas após as chamadas a super().
   Repare agora o método desenha(). Primeiro desenhamos o cabeçalho de nossa tabela e logo em seguida invocamos o método super.desencadeia(). Como a classe Decorators guarda o componente decorador que repassado a CabecalhoVisitante, quando chamarmos este método, chamaremos na verdade o método desenha() do outro objeto decorador armazenado em Decorators. Este outro objeto decorador se desenhará (ou não, ainda) e mais uma vez chamará o método super.desencadeia() que invocará novamente desenha(), mas agora, para outro objeto que este decorador estará armazenando em seu pai (e assim por diante até chegarmos ao componente concreto [Tabela]). Dentro do método desencadeia(), primeiramente checamos se a variável é nula, senão, continuamos com a seqüência de desencadeamento.
   Veja o código de nossos outros decoradores (poderiamos modificar CabecalhoVisitante e perguntar se o usuário está logado, mas, não desejamos misturar regras de login com objetos de decoração. Deixamos esta responsabilidade para outro objeto):

package decorator;

public class CabecalhoVisitanteLogado extends Decorators
{
    public CabecalhoVisitanteLogado(CompTabela componente) {
        super(componente);
    }

    public void desenha() {
        System.out.println(
            new StringBuilder("Olá ").append(/*varNomeDoUsuarioLogado*/ "varNomeDoUsuarioLogado").append("!"));

        super.desencadeia();
    }
}

package decorator;

public class CabecalhoAdmLogado extends Decorators
{
    public CabecalhoAdmLogado(CompTabela componente) {
        super(componente);
    }

    public void desenha() {
        System.out.println("Cabeçaçho de opções da consulta…");
        super.desencadeia();
    }
}

package decorator;

public class Rodape extends Decorators
{
    public Rodape(CompTabela componente) {
        super(componente);
    }

    public void desenha() {
        super.desencadeia();
        System.out.println("Quantidade de registros encontrados: X");
    }
}

package decorator;

public class RodapeDetalhes extends Decorators
{
    public RodapeDetalhes(CompTabela componente) {
        super(componente);
    }

    public void desenha() {
        super.desencadeia();
        System.out.println("Detalhes da consulta…");
    }
}

   Classe Main:

package decorator;

public class Main
{
    public static void main(String[] args) {
        System.out.println("\nVisitantes:");
        CompTabela c = new CabecalhoVisitante(new Tabela());
        c.desenha();

        System.out.println("\nVisitantes logados:");
        c = new CabecalhoVisitanteLogado(new Rodape(new Tabela()));
        c.desenha();

        System.out.println("\nAdministradores logados:");
        c = new CabecalhoVisitanteLogado(new CabecalhoAdmLogado(new RodapeDetalhes(new Rodape(new Tabela()))));
        c.desenha();
    }
}

   Saída:

Visitantes:
Olá visitante!
Tabela

Visitantes logados:
Olá varNomeDoUsuarioLogado!
Tabela
Quantidade de registros encontrados: X

Administradores logados:
Olá varNomeDoUsuarioLogado!
Cabeçaçho de opções da consulta…
Tabela
Quantidade de registros encontrados: X
Detalhes da consulta…

   Recomendo que você crie o projeto com o código acima e o compile em modo de debug (F7 atrás de F7 caso esteja utilizando o NetBeans). Assim você verá passo a passo por onde seu aplicativo passará durante o processo de criação das decorações.
   O padrão Decorator realmente não é fácil e exigirá um pouco mais de estudo e dedicação para que você possa entende-lo perfeitamente. Ele passa por diversos construtores e classes além de utilizar enfaticamente os recursos de orientação a objeto. Mas não se esqueça que com isto ficou muito mais fácil adicionar novos recursos a tabela. Basta criarmos um novo objeto herdeiro de Decorators e extender a funcionalidade exigida pela interface. Depois podemos controlar a nova criação de objetos facilmente, apenas adicionando um novo operador new a um objeto decorador selecionado. Ele também é muito versátil. Observe os trechos abaixo:

System.out.println("\nTrecho 1:");
CompTabela c = new CabecalhoVisitante(new CabecalhoVisitante(new Rodape(new Rodape(new Tabela()))));
c.desenha();/

System.out.println("\nTrecho 2:");
c = new Rodape(new Rodape(new CabecalhoVisitante(new CabecalhoVisitante(new Tabela()))));
c.desenha();

   Não importa a ordem de chamada dos objetos. A saída será sempre

Trechos:
Olá visitante!
Olá visitante!
Tabela
Quantidade de registros encontrados: X
Quantidade de registros encontrados: X

a não ser se, por exempo, estivermos trabalhando com dois cabeçalhos diferentes concorrendo a mesma posição de desenho.

System.out.println("\nTrecho 1:");
CompTabela c = new CabecalhoVisitante(new CabecalhoAdmLogado(new Rodape(new Tabela())));
c.desenha();

System.out.println("\nTrecho 2:");
c = new CabecalhoAdmLogado(new CabecalhoVisitante(new Rodape(new Tabela())));
c.desenha();

   Saída:

Trecho 1:
Olá visitante!
Cabeçaçho de opções da consulta…
Tabela
Quantidade de registros encontrados: X

Trecho 2:
Cabeçaçho de opções da consulta…
Olá visitante!
Tabela
Quantidade de registros encontrados: X

   Percebeu o poder que objetos decoradores podem nos dar? Desacoplamos totalmente lógica da criação e construimos componentes extremamente fáceis de se manusear, modificar e excluir. Imagine-se em um mar de possibilidades, e então de repente, seu cliente resolve fazer algumas modificações (acredite, isto acontece com freqüência). Pode ter certeza que essas pequenas modificações podem lhe dar muito trabalho lógico. Utilizando decorators, você apenas adicionaria algum novo tipo de dado ou modificaria algum tipo já existentes, sem mexer em regras de negócio que não se relacionam com os objetos em questão. Como mandam os princípios dos padrões de projetos, sempre veja primeiro a "grande foto" e separe todas as responsabilidades. Desacople. Programe de forma a estender funcionalidade, não de modificar classes já existentes. E é para isto que utilizamos este conjunto de ótimas soluções chamadas design patterns. Com eles, estamos prevenidos de diversas situações e ganhamos uma meio excelente de reutilização de componentes. Se você ainda estiver um pouco perdido não se assuste (se tiver entendido, excelente). Estude outros padrões e se acostume com a verdadeira e poderosa orientação a objetos. Uma boa semana a todos.

Posted in Design Patterns | Leave a comment

Entendendo as classes String, StringBuilder e StringBuffer.


   Fala galera! Vocês já pararam pra pensar em como as strings funcionam em Java? Pois é, um amigo meu resolveu parar essa semana e acabou se confundindo mais do que se explicando. Como a conversa com ele rendeu bastante, achei interessante escrever um artigo que aborda este assunto que pode lhe surpreender se você ainda não conhece a linguagem Java mais profundamente.
   A classe String representa um conjunto de caracteres Unicode, cada um contendo 16 bits (curiosidade: um char não possui bit de sinal [negativo ou positivo], então, 8 bits são para armazenar valor [(2 elevado a 8) – 1 que é igual a 255 {-1 pois o zero é considerado um inteiro positivo} caracteres {que utilizamos no teclado}] e mais 8 bits para internacionalização, dando suporte assim, a diversos idiomas e a meros 65535 caracteres diferentes). Ela é declarada como final e, portanto não pode ser estendida. Isto preserva este tipo de dado, reforçando sua imutabilidade e assegurando que uma String sempre será uma String (eles decidiram que você não pode herdar de char, nem de double e nem de String, além de diversas outras).
   A primeira coisa que temos que entender é que a classe String é imutável. Mas como assim imutável? É exatamente o que a palavra diz. Imutável, isto é, não muda, seu estado é único e imodificável. Quando criamos um objeto do tipo String, estamos na verdade criando um objeto que jamais terá seu valor modificado, porém, sua referência pode ser compartilhada com demais variáveis. Assim, podemos passar este valor único e imutável de nossa string criada para diversas outras variáveis de referência a strings. Para entendermos melhor, vamos começar com um simples exemplo:

String str = new String(); // String vazia.

   Aqui, declaramos e inicializamos uma string vazia, ou seja, "", e atribuímos este valor a uma variável de referência a strings chamada str. Mas existe uma maneira mais prática e não redundante de se inicializar um objeto String. Você poderia fazer simplesmente

String str = ""; // String vazia.

   Considerando que as duas linhas apresentadas acima representem apenas uma linha (desconsidere uma delas), observe o trecho a seguir:

str += "minha String"; // É igual: String str = str + "minha String";

   Agora str é um "ponteiro" para "minha string". Se criássemos outra variável chamada outraStr e fizéssemos

String outraStr = str;

outraStr então referenciaria também o objeto "minha String". Mas e nossa string vazia criada anteriormente? Ela não é imutável? Então como modifiquei seu valor para "minha String" com concatenação?

str += " imutável"; // O valor de str agora é "minha String imutável".

   É aqui onde mora o segredo. O objeto antigo de str ("minha String", isto mesmo, str agora referência outro objeto) foi simplesmente abandonado. Como a string é única, este tipo de concatenação "joga fora" o objeto antigo e cria um novo objeto (neste caso, "minha String imutável") com a nova string. Assim se nós não tivéssemos criado a variável outraString para armazenar o caminho da antiga string, esta estaria perdida na memória e seria irrecuperável.
   Aqui vai um pequeno desafio. Você conseguiria me dizer quantos objetos do tipo String foram criados no código abaixo?

String str1 = "minha String ";
String str2 = str1.concat("imutável ");
str1.concat(" teste");
str2.concat("imutável ");
str2 = "ok";
str2.toUpperCase();

Cria o objeto "minha String ".
str1 aponta para "minha String ".
Cria o objeto "imutável " (perdido na memória).
Cria o objeto "minha String imutável ".
str2 aponta para "minha String imutável ".
Cria o objeto " teste" (perdido na memória).
Cria o objeto "minha String teste" (perdido na memória, pois, nenhuma variável recebe o retorno de str1.concat(" teste");).
Tenta criar "imutável ", mas, consta que esta já existe no pool constante de Strings, isto é, já foi criado antes. Assim, reutiliza o objeto "imutável " criado anteriormente.
Cria o objeto "minha String imutável imutável " (perdido na memória).
Cria o objeto "ok".
str2 aponta para "ok".
"minha String imutável " (antigamente referenciada por str2) se perde na memória.
Cria o objeto "OK" (perdido na memória) e str2 ainda referência "ok".

Resposta: 8.

   Vale lembrar também que anteriormente declaramos

String str = new String();

e

String str = "minha String";

   Apenas lembre-se de que toda vez que utilizar o operador new, um novo objeto estará sendo criado. Portanto, na primeira linha criamos dois objetos e uma referência enquanto na segunda criamos apenas um objeto e uma referência.

Classes mutáveis: StringBuilder e StringBuffer.

   Antes de tudo lembre-se disto: StringBuilder é equivalente a StringBuffer porém, StringBuffer é sincronizado enquanto StringBuilder não. Assim, StringBuilder é mais eficiente que StringBuffer e deve ser usado sempre que possível, a não ser que você esteja escrevendo um programa multi-thread. Por instância, estarei pronunciando apenas StringBuilder, mas o mesmo também é válido para a classe StringBuffer.
   A grande diferença entre strings comuns e string builders é a mutabilidade. Com StringBuilder, você cria apenas um objeto para manipulação de strings ao invés de criar diversos como no exemplo mostrado acima. Considere o exemplo:

StringBuilder strBuilder = new StringBuilder("Um");
strBuilder.append(" valor!");
System.out.println(strBuilder); // Saída: Um valor!.

   Como você pode ver, StringBuilder opera em seu próprio valor, evitando a criação de diversos objetos indesejados. Ela fornece uma interface fácil e de rápido aprendizado, assim você pode tranquilamente modificar os valores da string dentro do objeto em questão. Note que o cast do tipo StringBuilder para o tipo String na terceira linha é feito automaticamente. Isso nem sempre poderá ser feito, por isto o método toString() também estará disponível para você.
   Notou como pode ser perigoso trabalhar com strings em Java? Imagine-se desenvolvendo um aplicativo com grande volume de entrada e saída de dados, concatenação, escrita em arquivos e etcetera. Você precisa se assegurar, não só neste caso, mas sempre, de que suas técnicas de programação sempre estarão atualizadas e eficientes. Só assim você conseguirá escrever código com qualidade de verdade. Um grande abraço e até a próxima!

Posted in Java | Leave a comment

Singleton (Java)

 

   Olá pessoal! Para quem não conhece, estarei apresentando hoje um padrão de projeto muito utilizado entre os desenvolvedores de sistemas: o padrão Singleton. Um padrão de projeto é um template que define um comportamento específico (ou alguma técnica) para determinado problema, o tornando mais flexível e reutilizável. O design pattern Singleton (um ponto de acesso global a um objeto) nos garante que apenas uma e somente uma instância de uma classe será criada.
 
   Quando utilizar?
 
   Utilizamos este padrão, quando desejamos ter certeza de que queremos restringir a criação de um objeto a apenas uma instância. Isso é bastante útil quando precisamos de apenas um objeto para determinar as coordenadas do sistema fazendo com que o mesmo trabalhe mais eficientemente. Utilizar uma classe Singleton apenas para armazenar variáveis globais é considerado um princípio anti-padrão de projetos. Mas por que então não utilizamos uma variável  ou método estático? Pois variáveis estáticas não nos dão as possibilidades que um Singleton nos oferece. Podemos por exemplo, configurar nossa classe para armazenar não só uma, mas duas, três ou mais instâncias de um determinado objeto, nos dando a possibilidade de escolha de quantidade de instâncias possíveis em nosso sistema. Podemos também, criar nosso objeto apenas quando necessário evitando sua inicialização quando não utilizamos o mesmo, diminuindo assim o consumo de memória da aplicação. Outra opção que este padrão nos dá, é a possibilidade de herança e implementação de novos métodos, possibilidades que uma simples variável ou classe estática não nos dariam. Imagine que você venda um sistema onde cada conexão com o banco de dados custará um preço X. Você poderia aumentar o número de instâncias permitidas após o pagamento de uma nova conexão utilizando um simples Singleton. As situações são diversas. O importante é conhecer o pattern e entende-lo perfeitamente. Entender o contexto de uso é essencial.
   Suponha que você está construindo um aplicativo e precisa carregar e manipular as configurações globais do mesmo. Duas instâncias deste mesmo objeto de configuração seriam desnecessárias, pois, como o próprio nome diz, estas configurações são globais e únicas do aplicativo como um todo. Para demonstrarmos esta regra sendo executada, criaremos uma classe chamada SingletonTest que definirá nosso template de software para que este nos obrigue a criar no máximo uma instância da própria classe.
   A implementação de Singleton é composta de três passos básicos: bloquear o acesso a seu construtor, assegurar que apenas uma instância da mesma seja referenciada e impedir a clonagem da própria classe. Estes três passos nos asseguram que apenas uma referência de nossa classe será criada.
   O primeiro passo é entender como bloquear nosso construtor para que o mesmo não possa ser instanciado. Observe o código abaixo:
 
   public class SingletonTest {
      private SingletonTest() {
         // TODO: Código do construtor aqui.
      }
   }
 
   No código acima, declaramos uma classe SingletonTest e um construtor vazio para a mesma. Note que definimos o método de acesso a este construtor como private (isto é, só podemos instanciar um Singleton dentro da própria classe [note que um construtor protected nos dá a possibilidade de herança do pattern]). É aqui que esta a grande diferença dos demais construtores comumente usados (em sua maioria, publics). Como definimos nosso construtor como privado, o código cliente nunca instanciará esta classe em nenhum lugar do programa. Qualquer tentativa irá gerar um erro de compilação. Mas então quer dizer que criamos uma classe impossível de se instanciar? Qual o sentido disso? O sentido é que assim, temos certeza que nenhuma instância desta classe será criada diretamente com o operador new. Mas então como obter o objeto? Para isto, criaremos um método estático chamado getInstance que retornará uma referência para um objeto SingletonTest. Sendo este método estático, não precisaremos de um novo objeto para chamar o mesmo.
 
   public class SingletonTest {
      private static SingletonTest referencia;
 
      private SingletonTest() {
         // TODO: Código do construtor aqui.
      }

      public static SingletonTest getInstance() {
         if (referencia == null)        

            referencia = new SingletonTest();
     
         return referencia;
      }

   }
 
    Modificamos nossa classe SingletonTest adicionando uma variável de instância chamada referencia. Esta variável será a responsável por armazenar a referência do único objeto SingletonTest utilizado em nosso exemplo. Logo em seguida adicionamos o método getInstance que por sua vez, checa se o campo referencia está vazio ou já está instanciado com algum objeto SingletonTest. Se esta condição for verdade, isto é, referencia for igual à null, podemos então criar um novo SingletonTest e retorná-lo ao seu chamador. Se a condição for falsa, concluímos que referencia já foi inicializada como um SingletonTest e retornamos então o objeto já referenciado atualmente pelo campo. Assim asseguramos que o mesmo objeto sempre seja repassado a demais variáveis de referencia que chamam o método getInstance.
   Mas e se estivermos trabalhando com threads? Suponha agora que tenhamos duas threads concorrentes. Suponha que a thread A tenha que modificar o valor da referência através de uma função antes da thread B, para que este possa funcionar corretamente (thread B então depende de thread A). Como faremos para sincronizar estas duas threads e impedir que thread A seja concorrente de thread B no mesmo método? Como saber se a thread B será chamada apenas quando thread A terminar sua função? Para obtermos este comportamento, utilizamos a palavra reservada synchronized que "trancará" o método utilizado, tornando assim impossível a manipulação do mesmo objeto por threads diferentes.
 
   public static synchronized SingletonTest getInstance() {
      if(referencia == null)        
         referencia = new SingletonTest();
     
      return referencia;
   }
 
   Para terminarmos nosso padrão, precisamos nos prevenir de mais uma situação: a clonagem de métodos e classes com o método clone. O método clone (herdado da classe Object) é uma implementação da interface Cloneable, que nos dá o poder de implementar nossa própria versão do método de acordo com as nossas necessidades. Esta interface é bastante utilizada para implementação de clonagens Shallow Copy e Deep Copy (não explicarei estas aqui para não perdermos o foco do artigo). A maioria dos códigos e artigos encontrados na Internet, falham justamente neste ponto. Observe o código abaixo:
 
   public class Clone {
      public static void main(String args[]) throws Exception {
         SingletonTest teste = SingletonTest.getInstance();
         SingletonTest clone = (SingletonObject)teste.clone();
      }
   }
 
   Note que criamos primeiramente um objeto teste para armazenar um objeto SingletonTest e logo em seguida clonamos o mesmo para a variável clone. Como o método clone (herdado de java.lang.Object) retorna um objeto Object, precisamos fazer um cast explícito ((SingletonObject)) para obtermos uma instância do mesmo tipo do antigo objeto. Desta forma a clonagem ocorre perfeitamente. Para nos livrarmos deste "buraco" e impedir a clonagem de nossa classe, sobrescrevemos então este (clone é protected por default) para que o mesmo "jogue" uma exceção caso haja uma tentativa de clonagem. Nosso método clone sobrescrito se torna então:
 
   public Object clone() throws CloneNotSupportedException {
      throw new CloneNotSupportedException(); 
   }

   Note que no código acima, utilizamos a exceção CloneNotSupportedException que lança uma exceção a cada tentativa de clonagem do objeto atual.

   Se você quiser implementar herança em sua classe Singleton, terá que modificar seu construtor de private para protected, permitindo assim que o construtor seja chamado na classe filha. Mas se por um acaso seu Singleton for composto de apenas uma classe, podemos utilizar o comando final que impede a classe de ser herdada por outra classe. Assim, o compilador pode ajustar otimizações já tendo certeza de que nossa classe sempre será a classe concreta. Apenas modifique a declaração da classe para:
 
   public final class SingletonTest {
      …
 
   Com todas as precauções básicas, e com um Singleton 100% Singleton, vamos conferir o código de exemplo.
 
   public final class SingletonTest {
      private static SingletonTest referencia;

      private SingletonTest() {
         // TODO: Código do construtor aqui.
      }
 
      public static synchronized SingletonTest getInstance() {
         if(referencia == null)       
            referencia = new SingletonTest();
     
         return referencia;
      }
 
      public Object clone() throws CloneNotSupportedException {
         throw new CloneNotSupportedException();
      }
   }
 
   Vamos conferir outra forma de inicialização de um Singleton. Esta forma utiliza uma inner class que armazenará um campo chamado _Instance (trocamos o nome referencia) e a criação do objeto. Utilizei o código acima pois o mesmo oferece melhor entendimento do negócio. Particularmente, prefiro utilizar a abordagem abaixo que também é threadsafe e lazyloaded (baseado no código de Bill Pugh). Note que o objeto de criação única pode ser qualquer classe de escolha do programador. Esta não precisa ser necessáriamente um SingletonTest e tão pouco estar em uma inner class.
 
   public final class SingletonTest {
      private SingletonTest()
{
      }

      final static class SingletonContainer {
         final static SingletonTest _Instance = new SingletonTest();
      }

      public static synchronized SingletonTest getInstance() {
         return SingletonContainer._Instance;
      }
    
      public Object clone() throws CloneNotSupportedException {
         throw new CloneNotSupportedException();
      }

   }
 
   O código acima é específico para um Singleton (apenas lembrando que um Multiton não é um padrão especificado pela Gang-Of-Four [não que seja errado não ser da Gang-Of-Four]). Então estamos tratando apenas de uma instância do objeto. Note o uso da keyword final em diferentes contextos. SingletonTest não mais aceitará herança (e todos os seus métodos se tornam finalizadores) e _Instance se torna um membro global constante da inner class SingletonContainer. Um último detalhe para os que não sabem, a palavra final utilizada em métodos, "tranca" o mesmo para que sua implementação não seja redefinida em outras classes. Aos amantes de C++ (como eu), todos os métodos em Java por default são virtuais.
   Existe uma outra variação de Singleton para métodos sincronizados chamado Double-Checked Locking. Esta abordagem tenta sincronizar apenas a criação do objeto, sem a necessidade de sincronização de todo o método, diminuindo a penalidade da sincronização. Você pode conferir mais deste pattern em http://www.google.com.br/search?hl=pt-BR&rlz=1B3GGGL_pt-BRBR226BR226&q=double-checked+Locking&btnG=Pesquisar&meta= (pesquisa Google) mas há vários indícios de que este método é "quebrado". Futuramente escreverei uma matéria sobre este.
   Percebeu então como a implementação de um Singleton é simples. Com algumas linhas de código conseguimos definir uma classe bastante útil para manipulação de apenas uma instância de objeto. Este padrão não termina por aqui. Você pode modificá-lo e reorganizá-lo de acordo com suas necessidades específicas. O importante é entender como o padrão funciona e onde exatamente utilizá-lo. Procure mais informações sobre o mesmo, veja implementações diferentes. Só praticando você conseguirá dominar a regra e utilização deste pattern para que este se aplique aos seus interesses perfeitamente. Estude também os outros design patterns existentes e aprenda a programar em cima destes que são ótimas formas de reutilização de software e de organização do mesmo. Se você souber o que realmente é e como funciona um Singleton, você será capaz de implementá-lo em qualquer linguagem. Uma boa semana a todos e bons estudos. Um grande abraço.
Posted in Design Patterns | 1 Comment

Entendendo a classe PresentParameters.


   
 Fala galera! Mês passado apresentei pra vocês meu primeiro artigo sobre desenvolvimento de jogos utilizando o framework do DirectX. Abordamos todos os tópicos básicos e teóricos da metodologia 3D. Aprendemos também na prática como criar nosso dispositivo, um handle para a placa de vídeo, e limpamos nossa tela com uma cor especificada. Hoje começo meu segundo artigo explicando a classe PresentParameters que contém diversas propriedades que definem os parâmetros de apresentação do nosso Device window. Como estou buscando mais detalhes, procurando ir o mais fundo possível no assunto, decidi mergulhar nesta classe e conhecer suas diversas propriedades para que possamos pegar todos os detalhes possíveis desta fase inicial de criação. É muito importante entender os aspectos básicos do sistema, seu funcionamento e lógica encapsulada. Por isso quanto mais você conhecer da biblioteca, mais chances terá de ter sucesso com a mesma.
   Logo em seguida introduziremos um exemplo prático onde setamos diversas propriedades da classe, inicializando nosso aplicativo pela primeira vez em tela cheia. Confira a declaração da classe:

   public sealed class PresentParameters : ICloneable

   PresentParameters é uma classe pública que define os parâmetros de apresentação de nosso dispositivo. Ela não pode ser herdada pois esta definida como sealed. Ela implementa a interface ICloneable que contém o método Clone, utilizada para clonagens do tipo shallow copy e deep copy.

   Microsoft.DirectX.Direct3D.PresentParameters.

  • Windowed: Se true, a aplicação será executada em modo de janela. Se false, em tela cheia.
  • SwapEffect: Como descrito no artigo anterior, selecionaremos Discard pois, deixamos assim que o compilador escolha o melhor método de swap chain a ser utilizado. É importante lembrar que Discard é o único swap effect que pode ser utilizado quando setamos algum valor diferente de None da enumeration MultiSampleType para PresentParameters.MultiSample (um método de anti-aliasing, veremos mais detalhes em artigos futuros). Assim como em Flip, Discard pode incluir múltiplos back buffers, podendo estes serem acessados pelos métodos Device.GetBackBuffer ou SwapChain.GetBackBuffer. Outro detalhe importante, é que como escolhemos esta opção, devemos atualizar todo o conteúdo de nosso back buffer antes de chamarmos o método Present da classe Device. Já para os debuggers, a versão debug de tempo de execução sobrescreve o conteúdo de back buffers descartados com dados randomicos, permitindo que os desenvolvedores verifiquem se a superfície de suas aplicações estão sendo atualizadas corretamente. Não se esqueça que em um swap chain em tela cheia, o presentation rate (vamos chamá-lo assim ao invés de frame rate) pode ser obtido por Caps.PresentationInterval assim que o Device ou swap chain é criado. A menos que seu valor seja Immediate, o rate é sincronizado com o vertical sync do monitor. Em modo janela este ocorre sempre através da cópia imediata. (Para maiores detalhes sobre esta, leia meu artigo Introdução à programação de jogos e aplicativos 3D com DirectX para código gerenciado [07/04/2007]).
  • EnableAutoDepthStencil: Se true, o Direct3D será o gerenciador de depth buffers de nossa aplicação. Note que escolhendo esta opção, você obrigatoriamente terá que setar um valor para o formato do depth buffer definido na classe AutoDepthStencilFormat. Quando o nosso Device é criado, ele automaticamente cria o depth buffer e o seta como alvo de renderização. Quando o mesmo é resetado, ele também é automaticamente destruído é recriado com seu novo tamanho. Este membro é falso por padrão.
  • AutoDepthStencilFormat: Obtém ou ajusta o DepthFormat que configura o depth stencil surface criado pelo dispositivo. Esta surface, é uma área na memória bastante similar ao back buffer mas, ela trabalha com informações diferentes e é ignorada a menos que a propriedade EnableAutoDepthStencil esteja setada como true. A primeira vista, os membros desta enum parecem ser bem estranhos. Mas conhecendo-os melhor, você verá que não passam de simples acrônimos para uma nomeação mais simples. Por exemplo o membro D15S1. Este membro contém um z-buffer de 16 bits sendo que 15 bits são reservados para o canal de profundidade enquanto 1 bit é reservado para o canal stencil. Veja a documentação em http://msdn2.microsoft.com/en-us/library/microsoft.windowsmobile.directx.direct3d.depthformat(VS.80).aspx. Note que no canal de profundidade (D), o driver pode consumir mais bits do que o especificado pelo programador.
  • DeviceWindow: Um Control que representa a janela de saída. Para aplicações em tela cheia, a janela de desenho padrão é aquela que o aplicativo está sendo executado. Para aplicações que utilizam múltiplos Devices de tela cheia (como sistemas que utilizam múltiplos monitores), apenas um Device pode usar a janela em foco enquanto os outros demais, devem conter Devices de janelas únicos. Para aplicações em modo de janela, a janela de desenho padrão é aquela referida pelo método Present.
  • DeviceWindowHandle: Um ponteiro para um objeto que representa uma janela de saída. Os mesmos detalhes de DeviceWindow são válidos aqui.
  • BackBufferWidth: A largura do nosso back buffer em pixels. Note que este valor representa a resolução do jogo. Se nosso aplicativo estiver sendo executado em tela cheia, certifique-se que este valor corresponde aos valores suportados pela classe DisplayModeCollection que manipula uma coleção de structures do tipo DisplayMode. Caso PresentParameters.Windowed seja true, este valor deverá ser 0 que corresponde à largura atual do formulário.
  • BackBufferHeight: O mesmo de BackBufferWidth com a diferença de que definimos um valor para a altura, e não para a largura.
  • BackBufferFormat: Define o formato do back buffer. Caso esteja executando sua aplicação em modo janela, você pode especificar o formato Unknow para que o runtime utilize o display mode atual evitando a chamada DisplayMode para o Device da janela. É muito importante lembrar que em full screen mode, é impossível fazer conversão de cores.
  • BackBufferCount: O número de back buffers. Este valor pode ser 0, 1, 2 ou 3 (não se esqueça que 0 é tratado como 1). O método falha se pelo menos um back buffer não for criado. Não se esqueça que Copy exige a presença de apenas 1 buffer.
  • MultiSample: Define os níveis de multisampling de anti-aliasing na cena atual. Trataremos de anti-aliasing em artigos futuros.
  • MultiSampleQuality: O nível de qualidade do anti-aliasing. Este valor varia de 0 até (1 – [o valor retornado pelo método CheckDeviceMultiSampleType]).
  • FullScreenRefreshRateInHz: Obtém ou ajusta o valor indicando a taxa em que o monitor atualizará a tela. Esta propriedade recebe a taxa de atualização em hertz definida na coleção DisplayModeCollection, ou 0 caso estivermos em modo de janela. 
  • PresentationInterval: Define a taxa máxima de presentation rate para um swap chain de back buffers. Em modo janela este valor deve ser igual a PresentInterval.Default (0) que é igual a 1. Você poderá encontrar mais informações sobre a enum PresentInterval em http://msdn2.microsoft.com/en-us/library/ms858180.aspx mas por enquanto não trabalharemos com vertical retraces. Em tela cheia, podemos ajustá-lo como 0, ou definir um valor igual a um dos valores definidos nas flags da enumeração Present. Para saber quais valores suportados pelo dispositivo, utilize Caps.PresentationInterval.
  • ForceNoMultiThreadedFlag: Um bool que indica quando a aplicação deverá ou não usar mais de uma thread.
  • PresentFlag: O present flag da aplicação. Esta flag controla a operação Device.Present. Setamos valores para esta propriedade através da enum PresentFlag que por enquanto manteremos como 0, ou None, que discarta qualquer flag de apresentação. Se escolhermos LockableBackBuffer (1), damos a aplicação a habilidade de "trancar" o back buffer diretamente. Chamamos este evento, "trancar", de lock, garantindo-nos o acesso aos buffers para que possamos por exemplo, aplicar efeitos diversos em cada surface. Aplicações não são lockables por padrão, a menos que você  escolha esta opção toda vez que criar um Device ou quando resetá-lo. Note que haverá uma queda no desempenho escolhendo este valor. Escolhendo o membro DiscardDepthStencil (2), deixaremos que o z-buffer descarte os ajustes do Device ou da swap chain quando estes forem criados. Ao setarmos esta flag, os dados do stencil z-buffer são invalidados logo após a chamada dos métodos Device.Present e Device.DepthStencilSurface com uma surface diferente. Descartando os dados do z-buffer podemos aumentar a performace dependendo do driver utilizado. O depurador em tempo de execução nos garantirá a definição de um valor constante para o buffer de profundidade após a chamada dos métodos mencionados acima. Lembre-se que não podemos escolher esta opção para tipos de buffers lockables. O penúltimo membro, DeviceClip (4), obtém um blt de Device.Present para a área do cliente atual. Esta flag só funciona no Windows 2000 e XP. O último membro Video (16) apenas informa ao driver que o back buffer conterá dados de vídeo.

   Ufa! Depois de um pouquinho de leitura conseguimos entender os principais aspectos da classe PresentParameters. Setamos estes valores antes da criação de nosso Device para que ele possa ser inicializado corretamente de acordo com nossas necessidades. Podemos excluir alguns aspectos desnecessários para aumentarmos nosso desempenho, ou incluir diversos que nos trazem dezenas de outras possibilidades. Agora que temos um overview básico sobre esta classe, vamos criar nossa segunda telinha preta, só que agora em tela cheia (rs). Note que também modificaremos o código da criação do Device, tratando possíveis erros de suporte a vídeo antes da criação do mesmo. Deixarei a explicação para os comentários dentro código. Confira a nova versão do método InicializaGraficos:

public bool InicializaGraficos(bool isWindowed)

{

PresentParameters presentParameters = new PresentParameters();

// Decidimos se executaremos em plano janela ou em tela cheia.

if (isWindowed)

{

presentParameters.Windowed = true;

presentParameters.SwapEffect = SwapEffect.Discard;

}

else

{

presentParameters.SwapEffect = SwapEffect.Discard;

presentParameters.BackBufferWidth = 800;

presentParameters.BackBufferHeight = 600;

presentParameters.BackBufferFormat = Format.A8R8G8B8;

}

// Estes parâmetros estão comentados pois são desnecessários no contexto atual de criação do

// Device e suas inicializações padrão são originais como as ajustadas abaixo. Para os

// perfeccionistas do desempenho, ocultamos estas configurações pois, elas já são conhecidas

// inicialmente pelo compilador.

//presentParameters.EnableAutoDepthStencil = false;

//presentParameters.AutoDepthStencilFormat = Unknow;

//presentParameters.Windowed = false;

//presentParameters.DeviceWindow = this;

//presentParameters.DeviceWindowHandle = this.Handle;

//presentParameters.BackBufferCount = 0; // 0 = 1.

//presentParameters.MultiSample = MultiSampleType.None;

//presentParameters.MultiSampleQuality = 0;

//presentParameters.FullScreenRefreshRateInHz = 0;

//presentParameters.PresentationInterval = PresentInterval.Default;

//presentParameters.ForceNoMultiThreadedFlag = false;

//presentParameters.PresentFlag = PresentFlag.None;

// Pega o número ordinal do Device padrão.

// Geralmente este número é 0.

int inteiroAdapter = Manager.Adapters.Default.Adapter;

//Obtém as capacidades da placa através da struct Caps.

Caps capacidades = Manager.GetDeviceCaps(inteiroAdapter, DeviceType.Hardware);

// Verifica as capacidades do dispositivo.

// Verifica suporte a transformações e luzes utilizadas por HardwareVertexProcessing.

CreateFlags flagCapacidadeDevice;

if (capacidades.DeviceCaps.SupportsHardwareTransformAndLight)

{

flagCapacidadeDevice = CreateFlags.HardwareVertexProcessing;

Console.WriteLine("GameEngine.cs -> HardwareVertexProcessing");

}

else // Senão utilizamos por SoftwareVertexProcessing.

{

flagCapacidadeDevice = CreateFlags.SoftwareVertexProcessing;

Console.WriteLine("GameEngine.cs -> SoftwareVertexProcessing");

}

// Se a placa de vídeo suportar HardwareVertexProcessing, checa se a mesma suporta rasterização,

// transformações de matriz, luz e etc… Essa combinação é a mais rápida.

if (capacidades.DeviceCaps.SupportsPureDevice && flagCapacidadeDevice == CreateFlags.HardwareVertexProcessing)

{

flagCapacidadeDevice |= CreateFlags.PureDevice;

Console.WriteLine("GameEngine.cs -> PureDevice");

}

// Cria o Device.

try

{

this.device = new Device(

inteiroAdapter,

DeviceType.Hardware,

this,

flagCapacidadeDevice,

presentParameters

);

return true;

}

catch (DirectXException)

{

this.device = new Device(

inteiroAdapter,

DeviceType.Reference,

this,

CreateFlags.SoftwareVertexProcessing,

presentParameters

);

return true;

}

}

   Note como agora temos um método muito mais robusto e completo para a inicialização de nosso Device. Este código ainda não trata todas as possibilidades mais é bastante útil e flexível. Não pense que em um jogo profissional teriamos uma função gigantesca para a criação do Device a toda hora. Configurações de execução são setadas antes de começarmos o negócio de nosso aplicativo. De pouco em pouco, ensinarei novas técnicas e soluções para diversas situações que encontraremos no caminho. Por hoje ficamos por aqui. Não se esqueça de continuar seus estudos pois a cada matéria, o nível técnico dos artigos tenderá a subir (assim como a dificuldade de utilização do framework). Então sem preguiça e bons estudos galera. Inté!

Posted in C# .NET (DirectX) | Leave a comment

Chrono Trigger FAQ / Walkthrough

 
Autor: Eu!
Jogo: Chrono Trigger
Produtora: Squaresoft (1995)
Console: Super Nintendo (SNES)
Versão: Americana
Percentual: 100%
 
 
 
 
   Obs: Como o Spaces do MSN tem limite de caracteres muito pequeno, postei no Blogspot que ao contrário, aceita uma quantidade gigantesca (no meu caso 155 Kb [arquivo de texto]).
Posted in Chrono Trigger | Leave a comment

Introdução à programação de jogos e aplicativos 3D com DirectX para código gerenciado.

 

   Olá pessoal. Este é o primeiro de uma série de artigos sobre desenvolvimento de jogos usando o framework do DirectX. Quem curte jogos de computador com certeza alguma vez já se deparou com algum pedido de instalação ou atualização do DirectX para que o jogo pudesse funcionar. Isto acontece porque o jogo utiliza determinadas classes e métodos do framework que são necessários para o correto funcionamento do mesmo. Mas o que é um framework? É apenas um pacote de componentes, uma estrutura de suporte bem organizada e desenvolvida que servirá de base para outras aplicações. Você utiliza os componentes de um framework para desenvolver seus próprios aplicativos. O framework do DirectX é chamado de DirectX SDK (Software Development Kit [Obs: apenas para o sistema operacional Windows]) e pode ser obtido gratuitamente no site da Microsoft em http://msdn2.microsoft.com/en-us/xna/aa937788.aspx. A versão mais recente do mesmo até a data de publicação deste artigo é a de abril de 2007 (a que estou usando). Então sempre mantenha seu framework do DirectX (também não se esqueça de seu .NET Framework) atualizado para que os jogos mais recentes e suas aplicações DirectX possam ser executadas com a maxima eficiência de sua placa de vídeo. Mas como assim a máxima eficiência da placa de vídeo? É isso mesmo. O framework do DirectX é uma abstração da placa de vídeo. As APIs (Application Programming Interface [Interface De Programação De Aplicativos]) do DirectX nos dão classes e métodos essenciais para a manipulação destes dispositivos. São eles:

   DirectX Graphics
   – Direct3D
   – DirectDraw (Depreciado)
   DirectInput
   DirectPlay (Depreciado)
   DirectSound
   – DirectSound3D
   DirectMusic
   DirectSetup
   DirectX Media
   DirectX Media Objects

DirectX Graphics
——————
   – Direct3D
   ———–
      Um DDI (Device Driver Interface [Interface De Driver De Dispositivo]) para manipular e mostrar objetos tridimensionais. Nos dá uma interface para trabalharmos com programas 3D que podem utilizar qualquer recurso de aceleração gráfica externa instalada na máquina do usuário. Virtualmente todas as placas 3D para PCs (Personal Computers) suportam o Direct3D.
  
   – DirectDraw

   ————–
     Uma API 2D utilizada para aplicativos onde o alto desempenho é muito importante. Também utiliza aceleração de hardware mas não dá suporte para gráficos 3D. Você até pode utilizá-la para isto mas com certeza notará a perda de desempenho comparando-se ao Direct3D. Está API está depreciada e seu uso é desencorajado pela Microsoft, mas, muito programadores ainda a utilizam. A partir da versão 8.0 do DirectX, esta API foi incluida em um novo pacote chamado DirectX Graphics, que pode ser descrito como o Direct3D com adições do DirectDraw.

DirectInput
————-
   As entradas de nosso aplicativo se dão através desta interface (controles, teclado, mouse e etc).

DirectIPlay
————
   Para a comunicação on-line e em geral dos jogos. Também depreciada.

DirectSound
————–
   Uma interface entre a aplicação e a placa de som dando aos aplicativos a capacidade de reproduzir sons e músicas. Além de enviar informações para a placa de som, também possui funções como mixagem e controle de volume.

   – DirectSound3D
   ——————
      Interface para manipulação de sons tridimensionais.

DirectMusic
————-

   Para músicas e sons compostos no DirectMusic Producer. Um software de composição de conteúdo musical para profissionais da área que desejam compor audio para DirectX.

DirectSetup
————-

   Uma biblioteca simples que comanda a instalação do DirectX em sua máquina.

DirectX Media
—————

   Inclui componentes necessários para aplicações de audio do Windows que usam DirectAnimation, DirectShow e DirectX Transform. Estes componentes dão suporte a animação, media streams (transmissão de vídeo e audio pela Internet por exemplo) e interatividade.

DirectX Media Objects
————————
  
Suporte para objetos de streaming como codificadores, decodificadores e efeitos. 

   Como estaremos desenvolvendo aplicações 3D utilizaremos na maior parte do tempo o Direct3D para executarmos nossas tarefas. O Direct3D é um namespace um "pouquinho" grande onde estão armazenadas todas as classes que trabalham em baixo nível, especiais para manipularmos diretamente nossa placa de vídeo. Ele é composto de 44 classes, 52 structures e 47 enumerations (agora imaginem as ramificações de quase 150 membros).
   Como o namespace é grande e complexo, estudar DirectX não é coisa pra qualquer um e nada que se aprenda da noite para o dia. Exigirá de você muito estudo e esforço, pois, você estará lidando com funções que traballham direto com o hardware. Funções complexas com parâmetros complexos e que exigem um raciocínio forte e muita persistência. Também, grande parte dos tutoriais e e-Books na Internet estão em inglês, o que facilitará muito a busca de conhecimento para quem tem familiaridade com o ídioma, senão, hora de aprender. Você terá que se familiarizar com um mundo novo de conceitos e regras que abrangem o mundo 3D e que não estavam presentes no mundo 2D de antigamente. Não bastando, a matemática se torna também um dos fatores fundamentais para o desenvolvimento de jogos. Recomendo conhecimentos em geometria analítica, cálculo diferencial e integral 1, 2, 3 e se for possível (e louco o suficiente), 4 (podem ter certeza, criação de jogos é um investimento a longo prazo).
   Um destes novos conceitos do mundo 3D é um novo eixo em nosso plano cartesiano. Antigamente em aplicações 2D, podiamos desfrutar apenas de duas dimensões. São elas X e Y. O eixo X é representado por uma linha horizontal e o eixo Y por uma linha vertical. Através destas linhas e suas marcações numéricas, somos capazes de chegar a determinados lugares do plano, seguindo suas coordenadas, onde podemos marcar pontos e criar vértices, que por sua vez formarão figuras geométricas por exemplo (não é possível que você não fez um destes na escola).

   Esquerda, direita, cima, baixo e diagonais. Estas eram nossas possibilidades em jogos como Mario Bros., Donkey Kong, Super Metroid, Megaman X, Street Fighter e etc (também chamados de jogos de plataforma). Como vimos na figura acima, poderiamos criar por exemplo, um mecanismo de colisão na reta azul traçada no plano e fazer um objeto Moto ou Carro se movimentar sobre a mesma. Mas, e se não quiséssemos mais ir para frente ou para trás. Ao invés disso gostaríamos que nossa moto desse as costas para tela e seguisse em frente, rumo ao fundo do cenário (ou então imagine o Ken ou o Ryu dando as costas para o monitor da TV e seguir tela adentro para desviar de um "Alek Full" [rs]). Aí está a grande diferença de objetos 2D e 3D. Em ambientes 3D temos um terceiro eixo chamado Z (ou o eixo de fundo). Observe com bastante atenção a figura abaixo:

  
  
O que você nota de diferente nesse plano? Percebeu que as coordenadas X e Y estão "deitadas". E que nosso eixo Y deu lugar ao eixo Z. Mas não é bem assim. O que aconteceu de verdade é que nosso plano foi realmente "deitado" para que possamos enxergar com clareza a existência do eixo Z. Imagine o seguinte. Você é apenas a sua cabeça, mais nada. A ponta da seta do eixo Z é uma flecha que está apontada diretamente para seus olhos, pronta para te acertar, e seu nariz está na mesma direção do eixo Y. Concorda então que você está vendo diretamente a ponta da flecha (eixo Z), e que ela está bem de frente a você? Percebeu que olhando dessa perspectiva, os eixos X e Y voltam para os lugares corretos. O eixo Z é justamente isso. A distância que existe entre você (a câmera) e o objeto em questão. Quanto maior o valor de Z, mais distante o objeto está de você. Quanto menor o valor, mais perto.
   Agora que você entendeu os conceitos básicos de como funciona o ambiente 3D, mão à obra. Vamos nos arriscar nos nossos primeiros códigos DirectX.
   Para o nosso primeiro exemplo, faremos algo bastante simples. Apenas iniciaremos nosso dispositivo físico e apresentaremos uma tela limpa e vazia, da cor que você escolher. Para uma tela vazia e sem conteúdo, vocês verão que o código se torna relativamente médio e de dificuldade média também (para quem está começando). Os métodos utilizados também deverão ser todos novidade para você, caso ainda não tenha alguma prática em desenvolvimento com o framework do DirectX. Antes de postar todo o código, entenderemos os aspectos fundamentais de sua formação.
   Para começarmos, iremos adicionar as referências das bibliotecas do DirectX SDK instaladas no seu computador. Adicione Microsoft.DirectX e Microsoft.DirectX.Direct3D ao seu projeto/solução. Por enquanto precisaremos apenas destas. Referências ajustadas, podemos agora utilizar os dois namespaces necessários para construir nossa aplicação:

   using Microsoft.DirectX;

   using Microsoft.DirectX.Direct3D;

   Havíamos visto anteriormente que o framework do DirectX é uma abstração da placa de vídeo. Então como obtermos uma instância da mesma e começar a manipulá-la? Para isso, o namespace do Direct3D nos fornece a classe Device.
   A classe Device é o objeto pai de todos os objetos gráficos em uma cena. Ela executa renderizações (geometrias e desenhos) básicos, cria recursos, armazena variáveis de nível de sistema e obtém/ajusta paletas de cor. Criaremos uma classe que terá uma variável de instância chamada device do tipo Device. Compartilharemos o objeto Device criado em nossa classe através desta variável.
 
   Device device = null;

   Iniciamos a variável device como null porque ela ainda não aponta para nenhum objeto do tipo Device. Annn? Como assim aponta? Achei que eu tava livre disso! (rs) Quando você cria uma variável de tipo não primitivo (tipos primitivos são int, float e etc), isto é, uma variável de um tipo de objeto qualquer (Device por exemplo), o compilador cria o objeto na memória mas a variável que manipulamos (device) é apenas uma referência para o objeto Device recém criado. Assim podemos compartilhar este mesmo objeto com outras referências à um objeto Device e você não precisa se preocupar com a criação de ponteiros. Mas lembre-se que até aqui, device é apenas uma referência do tipo Device que está vazio. Ainda não existe nenhum objeto Device na memória e a variável device não faz referência a lugar algum.

   Uma dica de desempenho neste ponto, é que não há necessidade de inicializar o membro device como null. Quando criamos variáveis de instância, elas são implícitamente inicializadas pelo compilador, fazendo-se desnecessária a inicialização explícita dos mesmos. Variáveis do tipo objeto são automaticamente ajustadas para null, variáveis do tipo bool para false e numéricas para 0.
   O próximo passo para a criação de nosso objeto Device é entender a estrutura PresentParameters. Esta classe é muito importante, pois, definirá os parâmetros de apresentação da janela do nosso aplicativo. Neste exemplo mudaremos apenas duas propriedades desta classe. São elas Windowed e SwapEffect (mudamos essas duas propriedades porque executaremos nossa aplicação no modo de janela, e neste modo, a aplicação não tem total controle sobre o buffer [diferente do modo tela cheia], exigindo assim, certas particularidades). A primeira indica se nossa aplicação será executada em tela cheia ou em uma janela comum. Ajustaremos como true para que a segunda opção prevaleça, pois, ainda, não queremos um aplicativo em tela cheia. Aplicativos em tela cheia, manipulam diretamente a memória de vídeo. Por isso, podemos tomar vantagens exclusivas neste modo que não seriam eficientes no modo de janela. A segunda indica o "efeito de trocas" (swap effect) do programa. Para entendermos esta propriedade teremos que fugir um pouco dos códigos.
 
Rasterização
————–
 
   Rasterização (conversão de imagens descritas em gráficos vetoriais e etc para imagens no formato Raster) é uma técnica de renderização utilizada atualmente por todas as empresas fabricantes de placa de vídeo no mercado (Ray Casting, Radiosity e Ray Tracing são outros exemplos). Ela é mais rápida pois utiliza as primitivas 3D (triângulos e poligonos) para criar "espaços vazios". Assim ela consegue enxergar apenas os pixels que precisam ser modificados e interage apenas nos mesmos, diferente de uma renderização pixel por pixel que modifica todo o espaço da cena. Deste modo, o rasterizador pega um stream de vértices 3D e os transforma em suas respectivas coordenadas 2D criando assim uma imagem. Logo em seguida ele preenche os polígonos com suas cores apropriadas. É na a rasterização que nossa imagem é enviada ao back buffer (explicado logo abaixo). Também é importante lembrar que a conversão pixel por pixel frequentemente traz uma melhor qualidade de imagem, apesar do baixo desempenho. Agora apenas por curiosidade, vamos comparar duas imagens. São dois carros. Um renderizado por rasterização (New Beetle vermelho) e o outro renderizado por traçado de raios (Ray Tracing) (New Beetle azul). Veja como realmente a renderização faz a diferença. Note que a rasterização, apesar de muito pior graficamente, comparando-se ao traçado de raios, é utilizada por todas as fabricantes devido a seu alto desempenho. O problema do Ray Tracing é a complexidade de seus cálculos. Imagens podem demorar minutos, ou até horas para serem renderizadas. Mas a qualidade se torna impecável. Repare nos detalhes, reflexos e efeitos de luz.

                      
 
   Na IDF Spring de 2004, o professor de computação gráfica Phillip Slusallek da universidade de Saarland na Alemanha, demonstrou um sistema onde ele conseguia renderizar em tempo real, imagens 3D que utilizavam o algorítimo de Ray Tracing. Só que para isso, ele utilizou uma "singela" configuração: 22 racks, cada um contendo 2 processadores Xeon de 2,2 Ghz. O sistema tinha uma capacidade total de processamento de 400 Giga FLOPS.

Double buffering e page flipping
———————————–
 
   Double Buffering ou Ping-Pong Buffering é uma técnica utilizada para reduzir ou remover "vestígios" durante o processo de desenho. Mas como assim? A maioria dos monitores utilizam a frequência de 60 Hz para redesenho. Isto é, a tela é redesenhada cerca de 60 vezes por segundo. Durante estas "redesenhadas", operações mais complexas (como criação de movimento por exemplo) podem ser enviadas ao monitor antes que o processo gráfico das mesmas termine completamente, causando assim, efeitos indesejáveis ao jogo como cortes e algumas "piscadas". 
   Entender a lógica do double buffer é bastante simples. Vamos nos referir ao seu monitor como sendo o primary surface (mais chamado de front buffer, uma região na memória para armazenar as imagens). Abaixo desta, outro espaço de memória em "off" chamado back buffer. É no back buffer que estão as imagens já prontas para serem enviadas para o front buffer (processo chamado blit ou blitting, escreve-se blt [Block Line Transfer]) evitando assim, milhares de acessos consecutivos a memória. 

   Podemos utilizar mais de um back buffer se quisermos para aumentarmos o desempenho da aplicação. Suponha que tenhamos agora mais um back buffer (agora dois) e também um ponteiro que chamaremos de video pointer. Agora imagine que se, ao invés de fazermos um blt, apenas trocarmos os ponteiros de vídeo entre os back buffers 1 e 2. Assim, no próximo refresh, a placa de vídeo apenas usará a imagem para qual o video pointer está apontando, e enquanto isso, o outro back buffer já estará se preparando para enviar sua surface para o front buffer quando este mesmo apontar para ele. Esse processo é chamado de page flipping (e essa corrente [chain] de trocas [swaps] de back buffers é chamada de swap chain).
   Não se esqueça que a quantidade de back buffers é medida de acordo com a quantidade de memória disponível na placa de vídeo.
   
   Agora que entendemos alguns conceitos básicos, vamos voltar para a propriedade SwapEffect e seus parâmetros que, por sua vez, manipulam o efeito de troca de nosso aplicativo através de uma enumeration composta de três membros: Copy, Flip e Discard. Vamos mais a fundo nessa questão.
   O membro Copy, pode ser especificado apenas para swap chains que utilizam apenas um back buffer. Não importa se a aplicação está em tela cheia ou rodando em janela comum. O runtime garante que a semântica utilizada no método Present sempre seja baseada na cópia. Mais especificamente, este membro deixa o conteúdo do back buffer inalterado (ao invés de preenche-lo com o conteúdo do front buffer, como em Flip) copiando seu conteúdo direto para janela alvo. Tenha cuidado ao utilizar esta opção no modo janela pois, nenhuma tentativa de sincronizar a cópia com o tempo de vertical retrace do monitor será feito, podendo assim, causar efeitos indesejáveis como "rasgados" por exemplo. Se a utilizarmos em modo full screen, o runtime utilizará uma combinação de Copy e Flip que podem utilizar back buffers escondidos para realizar corretamente o método Present (libera a fila de back buffers) que por sua vez, é sincronizado com o vertical retrace do monitor. Não se esqueça que uma swap chain sinalizada com a flag Immediate é a única exceção. Neste caso, Present copia o conteúdo do back buffer diretamente para o front buffer sem esperar por vertical retraces. Uma exigência para o uso de Copy, é que a propriedade BackBufferCount seja igual a 0 ou 1 (0 equivale a 1 em BackBufferCount [já definidos por padrão]).
   Outra opção, CopyVSync, se comporta exatamente como o SwapEffect Copy, exceto que ele sincroniza sua cópia com o vertical retrace do monitor.
   O membro Flip, pode incluir múltiplos back buffers em uma fila circular (incluindo o front buffer) numerada de 0 a (n – 1 [onde n é a quantidade de back buffers]) sendo 0, o último buffer apresentado. Quando a "roda gira", o front buffer se torna o back buffer (n – 1) , e o back buffer 0 se torna o novo front buffer. Vale lembrar que o presentation rate de um full screen swap chain é determinado pela propriedade Caps.PresentationInterval quando um Device ou uma swap chain é criada. Já em modo janela, o flipping é implementado através de Copy.
   Quando utilizamos os métodos Copy e Flip, o runtime garante que o método Present não afetará o conteúdo de nenhum dos back buffers. Entretando, com esta garantia, podemos consumir uma quantidade significativa de memória de vídeo. Para evitar certos overheads, especialmente quando implementamos semânticas de flip para um swap chain em modo de janela, escolhemos a opção Discard que deixa para a placa, a escolha da melhor técnica de swap chain a ser utilizada.
 
   PresentParameters parametros = new PresentParameters();
   parametros.Windowed = true;
   parametros.SwapEffect = SwapEffect.Discard;
 
   Por enquanto ainda não darei mais detalhes sobre esta opção. O importante é saber que agora podemos instanciar o dispositivo do Direct3D utilizando a classe Device.
 
   device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, parametros);
 
   Agora sim inicializamos nossa variável de instância com um ponteiro válido para um objeto do tipo Device. O primeiro parâmetro, 0, indica a placa de vídeo a ser utilizada. O valor 0 indica o dispositivo padrão (seguido de 1, 2 e etc, caso outras placas estejam disponíveis).
   DeviceType.Hardware é uma enumeração que especifica o tipo de rasterização do Device que estamos usando. Escolhemos Hardware pois, estamos trabalhando com um dispositivo físico e gostaríamos de toda a sua aceleração. Software, como o próprio nome diz, rasterização por software (podendo também ser proprietário ou de terceiros), que traz baixo desempenho. Reference indica que estaremos usando recursos de software da biblioteca do Direct3D, entretanto, o rasterizador utilizará a CPU sempre que possível. NullReference como próprio nome indica é uma referência nula.
   O terceiro parâmetro, this, indica o controle onde a renderização irá acontecer. No nosso caso, o próprio formulário.
   Em seguida, temos um enumeration chamado CreateFlags que define algumas flags para a criação do nosso Device. Como essa enumeration é composta de muitos membros, vamos explicar apenas o valor selecionado e mais dois valores afins. Uma definição mais específica da enumeração pode ser encontrada em http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/directx9_m_dec_2004/directx/ref/ns/microsoft.directx.direct3d/e/createflags/createflags.asp. Para uma descrição mais detalhada de cada membro, utilize a busca com o nome de cada valor (muitos métodos tem nomes iguais e classes diferentes [métodos abstratos e virtuais]. Por isso tome cuidado ao ler a definição de alguma classe, método e etc. Certifique-se que o caminho do namespace [Microsoft.DirectX.Direct3D por exemplo] está correto). Para entender o valor SoftwareVertexProcessing, temos que entender primeiramente o que é um vertex (Latim: plural vertexes ou vertices), ou simplesmente um vértice. Não passa de um ponto no espaço 3D. A unidade básica. Um ponto que está em qualquer lugar do mundo e tem as coordenadas X, Y e Z. Os vertexes são utilizados para a formação dos polígonos necessários para a rasterização de nossa cena 3D. SoftwareVertexProcessing, HardwareVertexProcessing e MixedVertexProcessing afetam justamente o processamento de nossos vértices. Estas três constantes são exclusivas e pelo menos uma delas deve ser chamada na criação do objeto Device podendo elas, ser combinadas com outras flags usando-se o operador bitwise OR (|). Como queremos processamento de vértices feito por software, escolhemos SoftwareVertexProcessing (não podemos mais mudá-lo). Se quiséssemos processamento por hardware, escolheriamos HardwareVertexProcessing (não poderíamos mais mudá-lo) e se quiséssemos processamento de vértices por ambos, hardware e software, escolheriamos MixedVertexProcessing, que nos dá a capacidade de troca entre esses dois modos de processamento. Mas porque SoftwareVertexProcessing? Por que apenas queremos garantir que nosso exemplo execute em todos os computadores, tendo eles uma placa de vídeo ou não. Como esse é nosso primeiro tutorial, não precisaremos utilizar recursos mais avançados, que exigem tratamento de erros e exceções.
   O nosso último argumento é o objeto PresentParameters criado recentemente. Passamos para nosso dispositivo os parâmetros de apresentação da tela definidos anteriormente.
   Colocaremos a criação do nosso dispositivo dentro de um bloco try/catch para que possamos parar mais elegantemente nossa aplicação em caso de erro na inicialização do nosso objeto. Utilizaremos a exceção DirectXException que é o pai de todas as exceções do DirectX. Caso nosso método seja inicializado com sucesso, retornamos true, senão, false.


PresentParameters parametros = new PresentParameters();

parametros.Windowed = true;

parametros.SwapEffect = SwapEffect.Discard;

device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, parametros);

   Apenas um bloco de nosso código final pois, teremos que criar agora, o método que limpará a nossa cena com a cor selecionada. Chamaremos esse método de Render pois, como o próprio nome diz, será o responsavel pela inicialização do processo de renderização. 


device.Clear(ClearFlags.Target, Color.Black, 1.0f, 1);

device.BeginScene();

// Aplicar renderizações 3D aqui.

device.EndScene();

device.Present();

   Parece complicado mas não é. O método Clear da classe Device irá limpar a tela para que possamos inicializar nossa aplicação 3D. Ela também pode fazer isso através de retângulos de tamanhos diferentes setados pelo programador em determinados pontos na tela, sempre com uma cor de fundo no padrão RGBA. O primeiro parâmetro deve ser um valor da enumeration ClearFlags que limpará o buffer do tipo escolhido. Se escolhermos Stencil (muito utilizado em áreas de mascaramento, onde certas regiões são proibidas a certos objetos), estaremos limpando todos os dados de tipo stencil de nosso Device. Se escolhermos ZBuffer estaremos especificando um buffer que usa um valor de fundo (Z) para cada pixel na cena. Lembre-se que pixels com z-value pequeno sobrescrevem pixels com z-values mais altos. Escolhemos a opção Target pois, esta especifica uma superfície renderizada. Assim estamos indicando o "alvo" (target) de nossa renderização. O segundo parâmetro apenas define a cor de nossa tela utilizando a classe Color (utilizamos preto [Black], mas fique à vontade para escolher sua cor). O terceiro parâmetro representa um novo valor float para o zdepth. Esse valor pode variar entre 0.0 e 1.0 onde 0.0 representa a distância mais próxima do observador. O quarto e último parâmetro limpa o stencil buffer para o valor especificado.
   Logo em seguida, o método EndScene sempre deverá ser chamado se o método BeginScene for utilizado. É entre estes dois métodos que definimos o ambiente antes de liberarmos nossa fila de back buffers com o método Present. Aqui definimos diversas renderizações. O método Present então, preenche o conteúdo da tela com a sua sequência respectiva de back buffers (detalhe, este método não pode ser chamado entre os métodos BeginScene e EndScene, a menos que o alvo da renderização não seja a renderização atual).
   Vamos postar agora o código que compõe nossa partial class e inicializa nosso ambiente 3D. Para este exemplo, criaremos um evento OnPaint que chamará o método Render assim que o evento for disparado no início da execução.

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using Microsoft.DirectX;

using Microsoft.DirectX.Direct3D;

namespace Inicializando_O_Direct3D

{

public partial class Form1 : Form

{

//==============================================================================

// Variável de instância da classe.

Device device = null;

//==============================================================================

// Função criada pelo Visual Studio 2005.

public Form1()

{

InitializeComponent();

}

//==============================================================================

public bool InicializaGraficos()

{

try

{

PresentParameters parametros = new PresentParameters();

parametros.Windowed = true;

parametros.SwapEffect = SwapEffect.Discard;

device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, parametros);

return true;

}

catch (DirectXException)

{

return false;

}

}

//==============================================================================

public void Render()

{

// Se o Device não for criado, termina o método.

if (device == null)

return;

device.Clear(ClearFlags.Target, Color.Black, 1.0f, 1);

device.BeginScene();

// Aplicar renderizações 3D aqui.

device.EndScene();

device.Present();

}

//==============================================================================

private void Form1_Paint(object sender, PaintEventArgs e)

{

Render();

}

//==============================================================================

}

}

   Agora vamos ao nosso método Main.

using System;

using System.Collections.Generic;

using System.Windows.Forms;

namespace Inicializando_O_Direct3D

{

static class Program

{

///

/// The main entry point for the application.

///

[STAThread]

static void Main()

{

Application.EnableVisualStyles();

Application.SetCompatibleTextRenderingDefault(false);

// Usamos o bloco using para que o objeto DeviceForm exista apenas neste escopo.

using (Form1 DeviceForm = new Form1())

{

// Se InicializaGraficos() retornar false.

if (!DeviceForm.InicializaGraficos())

{

MessageBox.Show("Não pode inicializar o Direct3D.",

"Erro!",

MessageBoxButtons.OK,

MessageBoxIcon.Error);

return;

}

// Garantimos que o formulário exista para que o ponteiro this

// sempre aponte para o formulário.

DeviceForm.Show();

// Roda a aplicação.

Application.Run(DeviceForm);

}

}

}

}
 

   Depois de um pouquinho de trabalho, temos então nosso tão satisfatório resultado. 

    Bom, o resultado pode não ser lá tão satisfatório como vocês imaginavam, mas infelizmente este é o começo (poxa, mas é só uma telinha preta???). Como havia citado anteriormente e reforçando mais uma vez, programação pra jogos é um investimento a longo prazo. Já pararam pra pensar que um jogo, é um software que utiliza quase todas (senão todas) as áreas da ciência da computação. Vamos desde a física até os pontos mais baixos da programação, sem contar os designs gráficos, hardware, matemática extremamente complexa, criação de sons e filmes computacionais e mais uma série de áreas específicas que exigem profissionais técnicos extremamente qualificados. É um aplicativo muito mais complexo do que a grande maioria dos softwares comerciais disponíveis no mercado. Por isso, a partir de hoje pense duas vezes antes de falar que um jogo original (lançamentos variam entre R$ 100,00 e R$ 200,00) é caro. É justamente o oposto se formos comparar a sua complexidade ao preço. Como dizia um amigo: "Nós é que ainda não temos dinheiro o suficiente". 🙂
   Espero que este artigo tenha sido útil para você, principalmente se você é um amante de jogos como eu, e sonha um dia, trabalhar nessa magnífica área que com certeza é para muito poucos (e muitíssimo poucos). Bons estudos e um grande abraço a todos!

Posted in C# .NET (DirectX) | Leave a comment

A frequência e probabilidade dos números aleatórios da classe Random.

 

   Em diversas situações precisamos fazer o uso de números aleatórios para o correto funcionamento do aplicativo que estamos desenvolvendo. Um lançamento de dados por exemplo. Você nunca saberá qual lado irá cair, tudo depende do fator sorte. Mas como simular isso em C#? Para isso utilizamos a classe Random do namespace System (Assembly mscorlib.dll).
  
Como ela funciona?
———————
 
   A classe Random gera números pseudo-aleatórios em igual probabilidade através de um algorítimo de cálculos complexos e depende de um valor de propagação para a criação de seus números. Este valor também é conhecido como semente, ou seed, sua tradução em inglês. Dizemos que estes números são pseudo-aleatórios por que se a mesma semente for fornecida, a mesma sequência de números será criada. Então como a classe resolve este problema? Simples. Ou passamos este valor de semente como parâmetro em seu construtor ou deixamos que a própria classe utilize o tempo para obter este número. Assim a cada segundo um novo seed e uma nova sequência aleatória será criada. É importante lembrar que a cada instância de Random, uma nova sequência será produzida. Para melhor desempenho, crie uma instância para gerar muitos números ao longo do tempo, em vez de criar um novo Random para gerar um número aleatório repetidamente.
   Para acessarmos nossa lista de números, utilizamos o método Next que retorna nosso inteiro não negativo com o valor máximo definido pela constante de System, Int32.MaxValue (2.147.483.647). O método Next também retorna números aleatórios entre uma valor máximo e um valor mínimo. Por exemplo, considere um sorteio entre 100 pessoas com bilhetes numerados de 1 a 100 respectivamente. Não seria nada elegante termos o bilhete 103855485 sorteado. Então criamos o intervalo: randomObjVar.Next(1, 101).
 
     Random randomObjVar1 = New Random(); // Semente automática.
     Random randomObjVar2 = New Random(100); // Semente fixa.
     int randomNum1 = randomObjVar1.Next(6); // Valores entre 0 e 5.
     int randomNum2 = randomObjVar1.Next(1, 101); // Valores entre 1 e 100.
     int randomNum3 = randomObjVar1.Next(); // Valores entre 0 e Int32.MaxValue (no meu caso. Consulte Int16 e Int64).
 
   Note que para criarmos valores entre 1 e 100, determinamos que o primeiro parâmetro é o (número mínimo)-> (1) e o segundo parâmetro o (número máximo + 1)-> (101), isto é, o número que apartir dele (inclusive o próprio), fechará nosso intervalo.
 
    Vamos ver um exemplo da classe Random. Crie um novo Console Application e compile o código abaixo: 

using System;

using System.Threading;

public class TesteRandom

{

// Gera os números aleatórios a partir do objeto Random passado via parâmetro.

static void RandomsIntAndDouble(Random randomObj)

{

// Gera os seis primeiros inteiros aleatórios. Next() por padrão retorna um tipo de dado inteiro.

for (int i = 0; i < 6; i++)

Console.Write(" {0, 10} ", randomObj.Next());

Console.WriteLine();

// Gera os seis primeiros aleatórios de ponto flutuante.

for (int i = 0; i < 6; i++)

Console.Write(" {0:F8} ", randomObj.NextDouble());

Console.WriteLine();

}

//#############################################################################

// Cria um objeto Random com uma semente específica.

static void RandomsComSeedFixo(int seed)

{

Console.WriteLine("\nNúmero aleatórios com a seed {0}:", seed);

RandomsIntAndDouble(new Random(seed));

}

//#############################################################################

// Cria um objeto Random usando o tempo como seed.

static void RandomsComSeedAuto()

{

// Espera para permitir o tempo avançar.

Thread.Sleep(8);

Console.WriteLine("\nNúmeros aleatórios a partir de um objeto Random com semente automática:");

RandomsIntAndDouble(new Random());

}

//#############################################################################

static void Main()

{

Console.WriteLine("Teste de números aleatórios.");

RandomsComSeedFixo(151);

RandomsComSeedFixo(151);

RandomsComSeedFixo(869);

RandomsComSeedFixo(869);

RandomsComSeedAuto();

RandomsComSeedAuto();

RandomsComSeedAuto();

Console.WriteLine();

}

}

 

Saída gerada por este programa:
 
Teste de números aleatórios.
 
Número aleatórios com a seed 151:
 1315260102   265292862   676906713  1692310980   816859526  1111223152
 0,56258840  0,19652270  0,65877152  0,95512020  0,53821600  0,35519592
 
Número aleatórios com a seed 151:
 1315260102   265292862   676906713  1692310980   816859526  1111223152
 0,56258840  0,19652270  0,65877152  0,95512020  0,53821600  0,35519592
 
Número aleatórios com a seed 869:
 1532962519  1713915386   404986241    86045096  1174976372  2096113265
 0,26729727  0,40592392  0,48736241  0,71840831  0,51073507  0,90816756
 
Número aleatórios com a seed 869:
 1532962519  1713915386   404986241    86045096  1174976372  2096113265
 0,26729727  0,40592392  0,48736241  0,71840831  0,51073507  0,90816756
 
Números aleatórios a partir de um objeto Random com semente automática:
 1819153459  1068198033   168249426  1046646997  1985159213  1388087901
 0,30496350  0,96699432  0,53056181  0,95221150  0,57154056  0,50930968
 
Números aleatórios a partir de um objeto Random com semente automática:
  153249767   926863922   146516687  1335331733   943323408   827547568
 0,78553047  0,98383835  0,76772881  0,63972248  0,93884584  0,31645413
 
Números aleatórios a partir de um objeto Random com semente automática:
  634829722   785529811   124783948  1624016469  2048971250   267007235
 0,26609745  0,00068238  0,00489582  0,32723346  0,30615112  0,12359858

Pressione qualquer tecla para continuar. . .
 
Frequência dos números sorteados
—————————————
 
   Como mencionamos anteriormente, a classe Random cria números aleatórios com probabilidade aproximadamente igual. Para provarmos isto, criaremos agora uma nova Windows Application que será composta de 12 PictureBoxes, 1 Button e 1 TextBox. As imagens que serão colocadas em cada PictureBox poderão ser obtidas juntamente com o projeto em um link que estarei disponibilizando em alguns dias.
   O botão deste exemplo substituirá a cada clique, cada um dos doze PictureBoxes do formulário e imprimirá na tela a quantidade de vezes que cada lado foi sorteado. A saída deste programa mostra o resultado de dez cliques e como esta frequência é aproximada.
   A cada clique no botão o método button1_Click é chamado executando o método MostraDado doze vezes. Este método substituirá a imagem de cada dado do formulário e incrementará a frequência de cada face sorteada.
   Não esqueça também de adicionar uma referência a System.Windows.Forms para que possamos utilizar os métodos da classe MessageBox. Este exemplo pode ser encontrado no livro "C# – Como Programar (2003)" de H. M. Deitel, um ótimo livro para quem está começando e para usuários avançados da linguagem.
 
 
Código do formulario:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.IO;

using System.Windows.Forms;

namespace RollDie

{

public partial class Dados : Form

{

// Cria o atributo Random da nossa classe.

private Random randomNumber = new Random();

// Lados de um dado.

private int

ones = 0,

twos = 0,

threes = 0,

fours = 0,

fives = 0,

sixes = 0;

public Dados()

{

InitializeComponent();

}

// Atualiza as imagens de acordo com o lado sorteado.

public void MostraDado(PictureBox pictureBoxDie)

{

// Gera o lado aleatório (entre 1 e 6).

int face = randomNumber.Next(1, 7);

// Faz a substituição da imagem concatenando o número sorteado com o nome da imagem.

pictureBoxDie.Image = Image.FromFile(

Directory.GetCurrentDirectory() + "/Images/die" + face + ".gif");

// Incrementa a frequência do lado sorteado.

switch (face)

{

case 1:

ones++;

break;

case 2:

twos++;

break;

case 3:

threes++;

break;

case 4:

fours++;

break;

case 5:

fives++;

break;

case 6:

sixes++;

break;

default:

// Apesar de não necessário aqui, utilizar o identificador default é

// uma boa prática de programação pois ele "tranca" outras possibilidades

// e o ajudará a ter um melhor entendimento lógico da estrutura switch empregada.

MessageBox.Show(

"Erro na randomização! Estes dados devem conter" +

" seis lados (1 a 6). Nem mais, nem menos.");

break;

}

}

// Ao clicarmos no botão "<< Roll >>".

private void button1_Click(object sender, EventArgs e)

{

// Atualiza cada PictureBox.

MostraDado(dado1);

MostraDado(dado2);

MostraDado(dado3);

MostraDado(dado4);

MostraDado(dado5);

MostraDado(dado6);

MostraDado(dado7);

MostraDado(dado8);

MostraDado(dado9);

MostraDado(dado10);

MostraDado(dado11);

MostraDado(dado12);

double total = ones + twos + threes + fours + fives + sixes;

// Saída.

textBox1.Text = "Face\t\tFrequency\tPercent" +

"\r\n1\t\t" + ones + "\t\t" +

String.Format("{0:F2}", ones / total * 100) +

"%\r\n2\t\t" + twos + "\t\t" +

String.Format("{0:F2}", twos / total * 100) +

"%\r\n3\t\t" + threes + "\t\t" +

String.Format("{0:F2}", threes / total * 100) +

"%\r\n4\t\t" + fours + "\t\t" +

String.Format("{0:F2}", fours / total * 100) +

"%\r\n5\t\t" + fives + "\t\t" +

String.Format("{0:F2}", fives / total * 100) +

"%\r\n6\t\t" + sixes + "\t\t" +

String.Format("{0:F2}", sixes / total * 100) + "%";

}

}

}

 
 
   Como podemos ver então, quando um grande número de lançamentos de dados é feito, as faces aparecem em probabilidade aproximada, isto é, cerca de 1/6.
   Portanto, a partir de hoje fique atento ao usar números randomicos. Tenha cuidado com a semente usada e depure, faça teste. Pense em todas as possibilidades. Programas que exigem randomização devem ser testados diversas vezes, por diversas pessoas dependendo do tamanho do projeto. Bons estudos a todos. Grande abraço!
Posted in C# .NET | Leave a comment