Archive | computação RSS for this section

Boost.Http scheduled for review

February last year, I was writing an email to the Boost user mailing list, asking for feedback on an HTTP server library proposal. Later that year, I got the news that I was to receive funds through Google to work on such proposal. And I had delivered a library by the asked timeframe. However, getting the library into Boost is another game. You must pass through the review process if you want to integrate some library into Boost. And I’d like to announce that my library is finally going to be reviewed starting on Friday.

Some links:

After working on this project for more than a year, I’m pretty glad it finally reached this milestone. And I’m very confident about the review.

I had written about this project so much here and there that when the time to write about it in my own blog comes, I don’t have many words left. It’s a good thing to leave as much info on the documentation itself and don’t spread info everywhere or try to lure users into my blog by providing little information on the documentation.

A fantástica Rust e o meu novo emprego fantástico

Minha camisa do orgulho Rust

Esse não é o primeiro post no meu blog onde menciono Rust, mas a partir de agora, as postagens sobre essa linguagem fantástica devem se tornar mais frequentes. Rust é uma linguagem de programação de sistemas e o que eu tenho para dizer, em uma linha, sobre Rust, é que eu acredito em Rust.

Muitas linguagens para diminuir erros e aumentar a produtividade foram criadas, mas elas costumam ser mais lentas. E para se sobressair e causar hype, uma abordagem de marketing recente foi criar novas linguagens afirmando que elas são linguagens de programação de sistema, apesar de serem tão produtivas quanto as linguagens mais lentas. Essas ditas linguagens de programação de sistema costumam (1) depender de um Garbage Collector e (2) produzir binários gigantes. O problema #1 as torna uma não-opção para mim e eu deixei minha crítica registrada em um texto separado. O problema #2 as impede de serem utilizadas em projetos embarcados, onde costumamos utilizar linguagens de programação de sistemas de verdade, como C.

Enfim, Rust é a linguagem de programação de sistemas que, não só afirma ser uma linguagem de programação de sistemas, como também, de fato, é uma linguagem de programação de sistemas. E ela tenta aumentar sua produtividade de duas formas:

  • Fornecendo construções comuns em linguagens de alto nível.
  • Aumentando a segurança, diminuindo o tempo que você iria passar depurando problemas.

Entretanto, para aumentar a segurança, Rust move muitos erros, que antes aconteciam em tempo de execução, para tempo de compilação. E para escrever código Rust que compile, você precisa aprender alguns conceitos que não são tão comuns. Logo, os erros diminuem, mas o tempo que você passa batalhando contra o compilador aumenta. Em outras linguagens, você ia gastar esse tempo escrevendo testes. Escrever testes ainda é legal, mas você não vai escrever testes para garantir segurança de memória e coerência de threads, porque o compilador garante isso para você. E, como o compilador garante isso para você, você vai gastar muito menos tempo ponderando se seu código faz sentido ou não, o que também aumenta a produtividade. Um depoimento que eu gosto, é o seguinte, feito por Mitchell Nordine:

I’ve found that Rust has forced me to learn many of the things that I was slowly learning as “good practise” in C/C++ before I could even compile my code. Rather than learning by trying to solve segfaults, debugging my pointer messes, etc, Rust tells me at compile time why what I’m trying to do is probably not a wise choice.

Se você simplesmente não se importa em produzir código correto, então outras linguagens ainda devem oferecer uma produtividade melhor. Claro que outra complicação na curva de aprendizado de Rust vem do fato que ela também não quer abrir mão de performance.

Eu não consigo levar a sério, linguagens que não levam performance a sério. E eu não consigo considerar o uso de linguagens que dão significado a espaço em branco (ex.: blocos definidos por identação, como em Python, mas tem até coisa pior em Haskell) ou ao estilo de capitalização dos nomes (i.e. se começar com maiúscula, é uma classe). Enfim, pela primeira vez, apareceu uma linguagem que conseguiu me fazer deixar C++ de lado. E Rust acerta muitas vezes, então a propaganda feita varia de acordo com a combinação anunciante/ouvinte.

Emprego novo

GitHub Orgs

E, para combinar com a linguagem fantástica, tenho a declarar que eu consegui um emprego fantástico, no projeto MaidSafe (que é um projeto fantástico que merece seu próprio post). Estarei trabalhando de home office e ainda irei trabalhar com assuntos que me excitam: Rust, software livre e redes descentralizadas.

Eu amei a descrição do emprego feita por um twitteiro aleatório:

“surely the most dev hipster job out there right now. @maidsafe. programming in rust, blockchain distributed ID”

Uma nota rápida: tecnicamente, eu estou trabalhando para mim mesmo, oferecendo serviços de consultoria, e o projeto MaidSafe é meu cliente. Só esclarecendo, pois não posso fazer afirmações de que trabalho para o projeto MaidSafe no sentido clássico (e é basicamente assim com o resto dos funcionários).

Eu queria ir com mais calma e terminar mais projetos antes de assumir tamanha responsabilidade, mas minha vida é simplesmente muito maluca para que eu preveja seu futuro.

Para ser honesto, o pessoal da MaidSafe é bem amigável e compreensível (pelo menos até agora), e me deu bastante liberdade para ter horários flexíveis que vão me permitir terminar projetos paralelos e continuar trabalhando no meu fantástico estágio envolvendo bindings de JavaScript para as bibliotecas EFL (tem até um rascunho de um post sobre isso que estou postergando em terminar e publicar já faz muito tempo).

E alguém importante para que eu conseguisse esse emprego foi o Francisco Lopes, por ter me convencido a fazer a entrevista, e fazer logo, aumentando minha confiança. Logo, eu devo a ele um ou quatro copos de cerveja, que pretendo pagar em algum dos encontros da lista de C/C++ Brasil.

BEER!

No tópico de Garbage Collector

Você quer que sua linguagem tenha Garbage Collector. Isso é uma mentira.

Você quer que sua linguagem tenha gerenciamento automático de memória. Isso ainda é uma mentira, mas pelo menos é uma mentira menor.

Você quer que sua linguagem tenha gerenciamento automático de recursos, não importando se eles são memória, file descriptors, socket descriptors ou qualquer outro. Isso NÃO é mentira.

Gerenciamento de memória (e outros recursos) seguro, automático, barato, performático e sem Garbage Collector é possível, como a linguagem Rust vem provando. Rust é a linguagem formidável que realmente se preocupa com performance e é capaz até de detectar race condition em tempo de compilação.

Problemas de usabilidade com Garbage Collector

Garbage Collectors não possuem semânticas fortes o suficiente para permitir destrutores. O mais próximo que existe são os finalizers, que possuem uma lista bem grande de possíveis problemas.

É justamente devido ao Garbage Collector e aos finalizers que você está tendo mais trabalho, tendo que gerenciar blocos finally após cada tratemento de exceção. Na verdade, eu também não acredito em tratamento de erros usando exceções (leia o link para não entender errado!).

Outros problemas com Garbage Collector

O uso de um Garbage Collector diminui a utilidade do seu programa. Se seu programa possuir requisitos de tempo real, baixa latência, sistemas embarcados, uso eficiente de bateria ou alguns outros relacionados, Garbage Collector se torna indesejável.

Uma crítica inicial a Garbage Collector é que seu programa congela aleatoriamente, prejudicando a responsividade, mas esse problema é até mais prejudicial no caso do programa ter requisitos de tempo real, pois nenhuma garantia de tempo máximo gasto com coleta de lixo é oferecida. Foram então, criados algoritmos de GC que rodam em paralelo e algoritmos incrementais de GC, permitindo o uso de GC nesses cenários, mas outros problemas persistem. Primeiro, que uma solução dessas continua a consumir mais recursos.

Outra crítica a GC é que ele é um consumidor exagerado de recursos. Se o algoritmo é paralelo, você já depende de uma segunda thread. A depender do algoritmo adotado, você já vai precisar do dobro de memória RAM que uma versão do programa sem GC. E o programa estará usando mais memória que o que precisa na maior parte do tempo, então não podemos chamá-lo de “leve”. Tudo isso contribuindo para o mal uso da bateria do dispositivo.

Mais uma crítica ao GC é que, quando ele executa, vai caminhar aleatoriamente na RAM, navegando de ponteiro em ponteiro, poluindo a cache da CPU e impedindo que você crie um programa de baixa latência, onde responsividade é importante (i.e. jogos).

Um tweeteiro aleatório me deixou ciente de um diagrama legal que pode facilmente lhe fazer pensar que talvez “C++ sempre vença no desafio de performance”. Acho tal gráfico relevante para o tema:

Conclusão

Eu não tenho nada para concluir de verdade. Só criei essa seção para ter a oportunidade de usar uma frase: C++ foi minha escola.

EDIT:

É legal ver a cara de “WTF” do Alexandrescu ao escutar o Stephan T. Lavavej falar “I don’t think garbage collection can ever solve it”.

Coleções para MongoDB – parte 2

Muitos meses atrás, eu fiz um post com algumas poucas dicas para conseguir bases para testar a API do MongoDB. Após esse tempo, eu adquiri novas estratégias para alimentar uma base de dados para começar a brincar com o MongoDB. Esse post é a continuação que detalha essas novas dicas.

um projeto que mantém uma base de dados com os trajetos dos taxis de New York. Esses dados estão salvos no formato CSV e eu fiz um script para baixar (~50G) e converter esses dados para MongoDB. O script depende do bash para funcionar.

Um pouco apelação, pois os dados não são muito relevantes, mas eu fiz um script em Python para transformar a base de dados dos pacotes do ArchLinux instalados em uma base do MongoDB.

Uma outra fonte de dados interessante é a base de dados que o projeto DataViva mantém e pode ser importado via csv. A base de dados contém informações interessantes sobre, entre outros (visite a página!), salários. Vale a pena, também, visitar a página principal do projeto DataViva.

E, como última dica, há também as bases de dados que o curso OpenIntro Statistics utiliza. OpenIntro Statistics é uma iniciativa nobre de oferecer material de ensino de qualidade para qualquer um com acesso a internet. O meu atual professor de probabilidade e estatística desistiu do material anteriormente adotado ao descobrir essa iniciativa.

Tratamento de erros usando exceções sob a perspectiva de um programador de C++

Um tópico sobre o qual sua compreensão pode evoluir muito com o tempo, pois pouca experiência tem uma grande contribuição para impedir que os conceitos se tornem claros é o tópico de tratamento de erros usando exceções. Em C++, esse é um assunto até mais polêmico que em muitas outras linguagens e eu resolvi compartilhar um pouco de minha compreensão nesse texto.

Eu quero que você se esforce muito para não pensar nas implicações de performance que exceções introduzem no sistema, pelo menos até terminarmos de analisar as outras facetas dessa ideia, pois há muitos argumentos irracionais, que, na minha experiência, vêm principalmente de pessoas que estão falsamente preocupadas com performance. Se esforce para manter em mente as frases “um erro é uma exceção?” e “o que é um caso excepcional?”. Elas vão ajudar a entender motivações e serão discutidas mais adiante no texto.

Exceções – resumo/revisão rápida

Exceções formam um conceito de programação idealizado para tornar difícil o ato de ignorar erros, separar código do fluxo normal do programa do código de tratamento de erros — aumentando a legibilidade, diminuindo erros e melhorando a manutenção do sistema — e transportar tanta informação sobre o erro quanto possível.

A motivação para exceções terem sido idealizadas é inspiradora, principalmente quando se pensa em estilos de notificações de erros alternativos que eram/são comuns, como variáveis globais (errno), valores de retorno onde alguns valores possíveis são reservados para indicar erro (normalmente -1), valores de retorno representando um código de erro, valores de retorno indicando sucesso ou erro e outros. Algumas dessas técnicas alternativas poluem abstrações, como no caso onde o valor de retorno da função torna-se reservado para o erro e a função precisa ser refatorada para enviar o resultado através dos argumentos passados para ela.

Algumas das técnicas alternativas evoluíram para soluções que chegaram a adquirir o meu selo de “minimamente respeitável”, e agora nós temos outras soluções para se levar a sério, como, por exemplo, verificações forçadas estaticamente através do sistema de tipos (veja o exemplo de Rust). Mas, esse texto é sobre exceções, então, a partir de agora, evitarei menções desnecessárias a essas abordagens alternativas.

Uma proposta de “expected” para C++ sugere quatro características importantes para um sistema de tratamento de erros:

  • Visibilidade do erro.
  • Informação nos erros.
  • Código limpo.
  • Erros não intrusivos.

Eu recomendo a leitura da parte inicial da proposta para entender essas quatro características. Elas ajudam a julgar o que é um bom sistema de tratamento de erros, ressaltando indicativos importantes.

Isso é introdução mais que o suficiente para se começar a descrever o que são e como funcionam exceções. Exceções definem construções para o controle de fluxo de execução do programa onde separamos código “normal” de código “excepcional”. E existe a exceção, usada para representar e transportar informações sobre um erro. Quando um erro é detectado e a exceção correspondente é gerada, o fluxo de execução normal é abortado e o fluxo de execução excepcional é iniciado, recebendo a exceção lançada para que se possa fazer a decisão de tratamento de erro apropriada.

Sintaticamente, envolvemos o fluxo de código normal para o qual somos capazes de tratar alguma exceção usando um bloco “try” e, logo em seguida, introduzimos um bloco “catch”, que contém o código para o fluxo excepcional. Um exemplo é dado abaixo:

Bem simples e, para muitos novatos, epifanicamente elegante.

Onde exceções decepcionam

O principal problema de usabilidade com exceções, tal como é implementado na linguagem C++, é que você nunca está certo de que está tratando todas as exceções possíveis. Você chama uma função, verifica a assinatura dela, e não sabe quais possíveis exceções serão lançadas. E se, após uma atualização, a função passar a lançar um novo tipo de exceção, todo o código que a chama vai continuar compilando normalmente, sem nem mesmo um aviso sobre a necessidade de atualizar o código chamador, mas causará um erro em tempo de execução nos piores momentos — que são os momentos quando o programador está confiante que o sistema está pronto para produção.

Em Java, há uma funcionalidade chamada de exceções verificadas, em que as possíveis exceções a serem lançadas por uma função fazem parte de sua assinatura. Ou seja, o sistema de exceções é integrado ao sistema de tipos para aumentar a segurança do sistema. Com tal funcionalidade, se, para uma dada função, não há a declaração de que uma exceção X é possivelmente lançada, e essa possibilidade existe no corpo da função, um erro de compilação é gerado. Ou você trata toda as exceções que podem ser lançadas dentro da própria função, ou informa ao código chamador que é possível que algumas exceções sejam lançadas.

Há quem considere catch(…) com rethrow um devido tratamento de exceção, mas a exceção não está sendo tratada. Você está meramente ignorando toda a informação sobre o erro e transferindo a responsabilidade de tratá-la para algum outro lugar. Esse padrão é útil, no entanto, para fornecer garantias de exceções básicas e fortes, dois conceitos presentes na STL.

Não é possível deferir o tratamento de exceções, situação que já podemos encontrar em casos apenas um pouco mais distantes que os imaginados e idealizados. Em programação concorrente (assíncrona ou paralela), o ponto onde uma operação é iniciada e onde é completada podem ser diferentes. Tais pontos sendo diferentes, a exceção não é retornada para o código que requisitou a realização da operação, e você simplesmente usa outro paradigma para tratamento de erros. É até mais complicado quando se envolve threads — problema para o qual o padrão C++11 adicionou exception_ptr. Um dos paradigmas, o paradigma de futures & promises, restaura o suporte a exceções nesses casos, fazendo com que a operação de desembrulhar o valor, através da função-mebro get, possivelmente lance uma exceção, mas, para funcionar, esse truque se apoia no fato de que as exceções em C++ não são verificadas.

Um ponto final sobre a complexidade que exceções introduzem está relacionada a código de finalização, que sempre precisa ser chamado, independente de uma exceção ter sido lançada ou não. Se a linguagem não suporta RAII, adiciona blocos “finally” para resolver o problema. Se a linguagem suporta RAII, um conjunto de convenções e regras é criado e a possibilidade de se lançar uma exceção enquanto outra já está sendo lançada nasce, o que normalmente provoca o programa a ser terminado.

Basicamente, exceções podem se tornar o novo goto e, elas são até abusadas, para, por exemplo, se implementar sistema de pattern matching em C++.

Performance

Finalmente, o tópico que causa polêmica e que desencoraja o uso de exceções em qualquer caso que não seja excepcional, mas cuja preocupação ajuda a definir o que é um caso excepcional.

A primeira observação a se fazer sobre exceções é que elas não são lentas. Citando outro texto de minha própria autoria:

O documento TR18015 detalha duas abordagens principais para a implementação de suporte a exceções e o que o documento menciona como “abordagem tabela” é muito barata, realizando boa parte do que precisa ser computado em tempo de compilação e estruturando o código de forma que não há penalidade em tempo de execução para o fluxo de execução normal do programa. O custo, nessa abordagem, é que o programa fica maior, mas a parte extra pode ser movida para não interferir negativamente no uso de cache e, em sistemas com suporte a memória virtual, essa parte pode até ficar na swap até que torne-se necessária.

uma resposta no StackOverflow, caso você não queira se dar ao trabalho de passar pelo TR18015. A implementação do suporte a exceções é bem elegante e a discussão deveria deixar de ser se exceções são lentas e passar a ser quando usar exceções.

E cuidado com os benchmarks injustos que alimentem esse mito, assim como o P. alerta.

Então usar exceções pode conduzir a um código até mais rápido do que manualmente verificar se erros ocorreram, mas caso um erro aconteça, haverá uma drástica redução na performance. E, como exceções dependem do mecanismo de RTTI, pode ser que tratar erros usando exceções deixe de ser uma opção em sistemas de tempo real.

A solução para esse problema está em colocar uma linha divisória entre erros e exceções. Ou, mais objetivamente, o que é um caso excepcional. Se você usar exceções apenas em casos excepcionais, não há problema nenhum em utilizá-las devido a alguma degradação de performance, pois você atingiu um caso excepcional de qualquer forma.

Um exemplo de um caso excepcional é quando o seu sistema encontra um erro de lógica, pois continuar executando o programa pode causar corrupção do seu estado interno e ele deveria ser encerrado o mais cedo possível, parando somente para mostrar uma mensagem de erro ao usuário.

Outro exemplo de caso excepcional é um cliente de um jogo online, onde, caso a conexão caia, você não se importa com o tempo gasto para lançar a exceção, pois o sistema não pode fazer nada de útil enquanto o erro não for tratado, e reconectar ao servidor é uma operação que vai custar mais tempo que o tratamento da exceção, de qualquer forma. Esse mesmo exemplo de “queda de conexão”, entretanto, pode virar um caso não excepcional em um contexto diferente. No contexto de um servidor que está servindo milhares de clientes, por exemplo, quedas de conexões são esperadas o tempo todo e você não quer que o gargalo de lançar milhares de exceções diminua a capacidade do seu servidor.

O usuário do reddit @sazzer ilustra mais alguns casos do que é uma situação excepcional.

Conclusão

Responder a pergunta “esse é um caso excepcional?” é uma tarefa complicada, mas, pode se tornar mais fácil, fazendo uso de bom senso, das ferramentas certas e de um bom entendimento de suas implicações.

Além disso, um mesmo erro pode ser excepcional ou não a depender do contexto, então é prefirível que se evite a dependência em exceções.

Aliado a todos os problemas que exceções trazem, eu sugiro que seu uso, em C++, seja reduzido. Entretanto, você simplesmente não pode se livrar de exceções usando C++ idiomático, então compreendê-las é importante.

Um sistema de tratamento de erros unificado é atraente, pois elimina a chance de escolhermos a abordagem errada em algum ponto. Entretanto, um sistema de tratamento de erros unificado deveria ter todas as vantagens que exceções possuem e se livrar de seus problemas mais sérios, inclusive os problemas de performance que impossibilita seu uso em sistemas de tempo real.

Pessoalmente, o sistema de tratamento de erros que eu vejo como ideal é a abordagem que é adotada em Rust, que emprega uso de conceitos como algebraic data types e pattern matching para oferecer um sistema de tratamento de erros bem expressivo e que é difícil de usar erroneamente.

Representação do mundo de jogos Bomberman-like

Existem aqueles problemas não-imediatos como desempenho, elegância ou outros que apenas ficam no caminho para a primeira solução funcional. Há aqueles que condenam a prática de preocupação prévia e insistem em resultados imediatos. Estou trabalhando no desenvolvimento de um jogo similar ao Bomberman e precisei desenvolver uma solução para representar o mapa onde o mundo do jogo é executado. E, é claro, acabei tendo o luxo de escolher a abordagem de desenvolvimento onde a preocupação com problemas não-imediatos ocorre mais cedo que a entrega do produto.

Introdução

Para que o problema não fique muito abstrato, considere a imagem abaixo, retirada do jogo BlowThemAll.

shot-2015-05-09_14-08-52

O mapa do jogo possui uma implementação similar ao conceito de uma matriz, onde os elementos do mapa possuem uma posição <x, y> tal que essa posição não pode ultrapassar os limites da matriz e os índices x e y são números discretos, naturais. Cada posição no mapa é um tile e cada tile pode conter vários elementos.

O mapa contém elementos imóveis, tais como blocos a serem explodidos, e elementos móveis, tais como bombas e avatares. Avatar é um personagem que representa a conexão do jogador com o mundo virtual de BlowThemAll.

Há ainda blocos eternos, que não podem ser explodidos.

No jogo original, Bomberman, uma bomba explode e a explosão tem o alcance baseado em tiles. Itens aparecem somente em tiles específicos. Há muitas observações que sugerem fortemente o uso de uma matriz como uma representação eficiente para o jogo.

Problema

Jogando-se Bomberman é possível observar que um avatar pode estar entre dois tiles, então você não pode representá-los como um elemento em uma sequência que faz parte de algum tile, pois um avatar pode estar entre dois tiles. Logo, o avatar precisa ser independente dessa matriz e não pode ter sua posição representada por números discretos.

Abordagens iniciais

A primeira abordagem para o problema foi utilizar uma lista separada para esses elementos do mapa que podem assumir posições entre dois tiles e armazenar suas respectivas posições usando valores do tipo double (ou parecidos).

Entretanto, eu não me sinto confortável usando valores do tipo double em um jogo em rede. Primeiro, porque o comportamento de doubles não é trivial, e segundo, porque é difícil garantir reprodutibilidade/previsibilidade usando doubles, então  eu não quero lidar com o problema desses números sendo utilizados numa parte principal de um jogo em rede. Lembrando que o double que você está usando pode nem ser o IEEE floating point. É claro que eu iria ler o “what every computer scientist should know about floating point numbers” caso o projeto precisasse, mas eu queria, ao menos, considerar uma abordagem alternativa.

A minha ideia foi usar números discretos para as posições dos elementos não-fixos no mapa, e fazer numa escala diferente (se o mapa era 13×11 tiles, os elementos não-fixos estariam em uma matriz virtual 130×110) para permitir um valor mais granular (que emularia o fato do avatar poder estar entre dois tiles). Essa abordagem não me contentou, pois ela levantava outras perguntas (ex.: quão granular devo permitir a posição para permitir uma experiência de jogo agradável?), não era elegante e tirava a simplicidade que eu tinha na solução inicial, onde, era barata, a operação de encontrar quais os avatares eram atingidos por uma explosão.

Pensando nas restrições

Eu voltei ao problema inicial para pensar nas restrições que ele apresentava. A depender de suas restrições, o algoritmo poderia resolver um problema menor e mais simples. Foi essa a abordagem que me permitiu desenvolver a libdepixelize com menos código e mais rápido, e é isso que decidi também fazer no BlowThemAll, entender melhor o problema.

Daí, percebi algo enquanto jogava bomberman. O avatar pode sim, estar entre duas posições, mas caso ele esteja entre duas posições, ainda há um “tile principal” ao qual ele pertence. Isto é, ele só é atingido por uma explosão caso apenas um dos tiles, o “tile principal”, seja incendiado. Além disso, há a restrição de que ele só pode estar entre dois tiles, não entre quatro tiles. Todas essas informações me inspiraram o suficiente para pensar numa solução alternativa.

Solução final

A ideia-chave da solução que acabei adotando é armazenar os elementos na “matriz”, como estava inicialmente planejado, porém adicionar um atributo “filled”, que representava o quanto  daquele tile o elemento ocupa, e um atributo direção, que representa qual o próximo tile a ser ocupado, quando o elemento atual chegar no limite de desocupação do tile atual.

Conceitualmente, 1 representa o estado onde o elemento está ocupando somente aquele tile, 0 representa o estado onde ele não está ocupando nada daquele tile e temos também todos os valores intermediários (como ocorre em contas de probabilidade). Entretanto, queremos usar valores discretos, então usamos inteiros simples e usamos seus limites (0 e 255) para representar os limites conceituais (0 e 1).

Um elemento é transferido para o próximo tile, quando, conceitualmente, ele desocupa mais da metade do tile atual. Se mapearmos o comportamento conceitual para o comportamento em código, então haverá um desperdício de bits, pois o valor de filled nunca seria inferior a 127. Assim sendo, o valor 255 continua representando o conceitual 1, mas o valor 0 representa o conceitual 1/2.

E a solução ficou bem legal até, mas eu ainda queria mais. Uma granularidade de 256 valores possíveis parecia um desperdício para mim e eu queria usá-los para mais informações. Foi então que eu decidi separar preenchimento/ocupação/”filled” por dimensão. “dir” se transformou nos atributos “is_up” e “is_left”, enquanto “filled” se transformou nos atributos “y_filled” e “x_filled”. Usando campos de bits, consegui armazenar toda essa informação em um único byte.

Os atributos “is_up” e “is_left” podem lembrar o bit de sinal da representação de inteiros por complemento de 1, que desperdiça um valor possível, porém eu quero preservar a simetria, então esse desperdício é aceitável.

Essa separação por dimensão, entretanto, faz com que o objeto sempre assuma uma direção para cada dimensão. Se antes as opções de direção eram norte, sul, leste e oeste, agora as direções são nordeste, noroeste, sudeste, sudoeste. Essa característica não é um problema, mas é preciso manter ela em mente quando for implementar funcionalidades que dependem da direção do personagem (socos, por exemplo).

Outra consequência da separação é que o jogo volta a apresentar a possibilidade inicial onde um personagem pode assumir uma posição de estar entre quatro tiles. Essa é uma característica positiva, pois impõe menos restrições e dá mais liberdade artística aos autores de mapas. Essa possibilidade também está presente no jogo Bomberman Tournament e eu não quero fazer menos que os competidores.

E, assim, eu fiquei satisfeito, e surgiu o código do BlowThemAll para tratar mapas. É claro que eu não acho que usaria essa solução para um jogo estilo “The Legend of Zelda: A Link to the Past“, mas eu acho que ficou bem adequada a um jogo ao estilo de Bomberman. Pode ser que você ache interessante pesquisar também sobre Quadtree Collision detection.

Programação em tempo real

Caso não fosse um jogo em rede e que possui algumas características que eu não detalhei, eu estaria também mantendo a preocupação de fazer uma solução para programação em tempo real, afinal, é um jogo e queremos previsibilidade nos tempos de respostas máximos. Sem essa preocupação, a única modificação que pensei para melhorar a previsibilidade nos tempos de resposta seria usar a função vector::reserve para pré-alocar um pouco de memória para a lista de elementos em cada tile.

Final

E para acabar o post, deixo um vídeo de alguns segundos demonstrando o resultado:

Game Jam durante o final de semana

Esse final de semana participei de um evento de game jam. Sinceramente, não pesquisei sobre esse tipo de evento e posso estar sendo infiel aos valores-raízes e desvirtuando o mesmo, pois tudo que sei é informação de segunda-mão, mas, mesmo assim, tive uma experiência positiva com a minha visão do que é um game jam. Ao final do processo, acabei conhecendo como é um game jam e com uma nova sugestão de evento para quem gosta de programar.

O que é?

Game Jam é um evento onde pessoas se reúnem num mesmo local para fazer um jogo do zero. Elas se reúnem, formam times e criam um jogo. O desenvolvimento do jogo deveria durar três dias e, ao final do processo, deveria haver um produto “mostrável”.

Por que?

É divertido, e você se desapega daquelas preocupações de código elegante e reaproveitável que faz verificações de segurança para terminar algo rápido. Ter algo que você fez e está funcionando é algo legal. E o ambiente onde há outras pessoas programando, buscando o mesmo objetivo que você, estimula você a ficar determinado em sua tarefa. E também é uma desculpa para trabalhar com tecnologias novas e melhorar suas habilidades como programador.

Meu game jam

Esse game jam que participei foi uma coisa pequena que organizei na minha casa e chamei uns amigos. Foram três times ao total (sendo que dois times, incluindo o meu, eram formados por apenas um membro).

photo420830555499767723

Time azul

O time azul desenvolveu um jogo estilo Beat ’em up escrito em Python 3 e pygame.

Time vermelho

IMG_20150502_200110

Captura de tela de 2015-05-02 20_06_55

O time vermelho desenvolveu um jogo de asteroides que também foi escrito em pygame, mas utilizando Python 2.

Time laranja

photo284176472941373377

O meu time, que no caso sou só eu mesmo, fez um jogo estilo bomberman via rede, escrito em C++11, Qt5 e Tufão 1.x.

Dicas

Lembre-se de se divertir, é o mais importante. Eu podia ter formado time com a outra pessoa que também ficou sem time, mas nós tínhamos expectativas diferentes e ambos estaríamos frustrados caso tivéssemos formado um time. No final, acho que todo mundo achou legal. Se divertir é importante.

Veredito

A experiência que tive com o evento foi positiva e é um evento que eu recomendo a todo mundo que gosta de programar. Vale a pena.

Sobre os jogos, creio que os participantes vão continuar trabalhando neles por um pouco mais de tempo, pelo menos, para ter algo polido e tal. O meu jogo, pelo menos, tem código ainda não público e você precisa de um manual de instruções para rodar o jogo sem esbarrar nos glitches e bugs.

Para o próximo, planejo fazer algo usando Rust, SDL2 e Chipmunk, três tecnologias com as quais não tenho experiência significativa e, no máximo, apenas uma ligeira familiaridade.

%d blogueiros gostam disto: