Logos é uma palavra forte de origem grega, ela pode significar razão, palavra ou discurso. Os próprios gregos foram responsáveis pela Trigonometria, cuja etimologia é a composição das palavras trigōnon (triângulo) e metron (medida) – algo como o estudo das medidas do triângulo (3 lados e 3 ângulos). Eles também foram responsáveis pelas inserções da Geometria (γεωμετρία = terra + medida) Euclidiana. Ambas as disciplinas são ramificações da Matemática, sendo que elas, juntamente com Análise Vetorial e Quaternion são os elementos fundamentais da Matemática para Computação Gráfica.
Escola de Atenas e os Elementos de Euclides
Se juntarmos todos estes ingredientes, misturarmos e adicionarmos algumas palavras mágicas (spells) – dentro de contexto da Computação, obviamente. Podemos extrair um produto fundamental composto por um software gráfico e educacional cujo “logo” (de logotipo) é uma tartaruga.
Sim, é sobre isto mesmo que estou falando, da linguagem de programação Logo. Que possui uma fundação, que a promove: Logo Foundation. O mais interessante, é que Logo é influenciada pela linguagem de John McCarthy, e ele dispensa apresentação. Fora isto, existe outra ferramenta educacional criada pela Microsoft, o Small Basic, que promove a linguagem Basic.
O Basic aquele que deixou Gates e Allen famosos. E também foi minha primeira linguagem de programação (que com o passar do tempo houve uma relação entre amor e ódio, hoje existe muito respeito para com ela). O ambiente do Microsoft Small Basic possui um objeto chamado Turtle, que permite você fazer a mesma coisa que um ambiente Logo, só que em Basic – muito recomendado para a garotada, ou para os profissionais que ainda acham que programar para SharePoint (ou qualquer outro produto) é programar. (Nada pessoal, com SharePoint foi apenas uma escolha aleatória baseado em popularidade e falácia, poderia ser BizTalk, sei lá ).
The rise of Turtle Graphics
Há duas semanas, no mesmo dia que o Fates Warning tocou no Brasil, tive a oportunidade de ministrar uma palestra sobre Scala. E uma das minhas ideias era criar um exemplo especial fora do contexto do Scala. (O Fates Warning é uma das minhas bandas favoritas desde a adolescência).
O exemplo especial foi construir um ambiente de Turtle Graphics para Windows usando C++, Direct2D, Java e Scala. E estendi este exemplo para minha segunda linguagem favorita, o F#. Ou seja, programei o engine e seu modelo de scripting compatíveis com a JVM e com a CLR.
Na verdade, todo este trabalho surgiu pela oportunidade da palestra (Palestra Fundamentos de Scala). No entanto, criar este ambiente era uma idéia antiga, impulsionada pelo livro que está ai em cima: An Integrated Introduction to Computer Graphics and Geometric Modeling de Ronald Goldman. Neste livro, ele começa dizendo algo do tipo: “vamos começar aprender Computação Gráfica e sua Matemática usando o Turtle Graphics”. Assim, ele sugere o uso do Logo ou que você crie seu ambiente. Bem, eu optei pelo segundo, seguindo meu instinto de programador C++.
Após fazer um rápido protótipo com Excel para validar as equações trigonométricas, li rapidamente a documentação do Direct2D para extrair o que eu precisava codificar e sai codificando. Com engine finalizado, parti para a parte do scripting e fiz uma coisa inédita – usei Java Native Interfaces (JNI), para expor o código nativo para Scala – sinceramente, foi bem tranquilo e divertido – e para aqueles que gostariam de saber: Sim, estou programando em Java, afinal (agora) sou um programador sem fronteiras! A mesma coisa eu fiz para o F#, que no caso pode usar P/Invoke diretamente.
Camada de interoperabilidade do scripting com o Java:
import java.nio.ByteBuffer; final class JNITurtle { private native ByteBuffer create(); private native void rotate(ByteBuffer hTurtle, float angle); private native void resize(ByteBuffer hApp, float size); private native void move(ByteBuffer hApp, int distance); private native void speed(ByteBuffer hApp, int value); private native void destroy(ByteBuffer hApp); private boolean disposed; private ByteBuffer hTurtle; JNITurtle() { disposed = false; hTurtle = create(); } static { System.loadLibrary("Turtle"); } public void rotate(float angle) { rotate(hTurtle, angle); } public void resize(float size) { resize(hTurtle, size); } public void move(int distance) { move(hTurtle, distance); } public void speed(int value) { speed(hTurtle, value); } public void dispose() { if(!disposed) { disposed = true; destroy(hTurtle); } } protected void finalize() { dispose(); } }
Camada de interoperabilidade do scripting com o F#:
namespace global open System open System.Runtime.InteropServices module private API = [<DllImport(@"Turtle.dll", CallingConvention = CallingConvention.Cdecl)>] extern nativeint create() [<DllImport(@"Turtle.dll", CallingConvention = CallingConvention.Cdecl)>] extern void rotate(nativeint hApp, float32 angle) [<DllImport(@"Turtle.dll", CallingConvention = CallingConvention.Cdecl)>] extern void resize(nativeint hApp, float32 size) [<DllImport(@"Turtle.dll", CallingConvention = CallingConvention.Cdecl)>] extern void move(nativeint hApp, int distance) [<DllImport(@"Turtle.dll", CallingConvention = CallingConvention.Cdecl)>] extern void speed(nativeint hApp, int distance) [<DllImport(@"Turtle.dll", CallingConvention = CallingConvention.Cdecl)>] extern void destroy(nativeint hApp) [<Sealed>] type private PInvokeTurtle() = let mutable disposed = false let mutable hTurtle = API.create() override this.Finalize() = this.close() member this.rotate(angle: float32) = API.rotate(hTurtle, angle) member this.resize(size: float32) = API.resize(hTurtle, size) member this.move(distance: int) = API.move(hTurtle, distance) member this.speed(value: int) = API.speed(hTurtle, value) member private this.close() = if not disposed then disposed <- true API.destroy(hTurtle) interface IDisposable with member this.Dispose() = GC.SuppressFinalize(this); this.close()
Estes trechos de código interagem com a fachada que exporta o modelo de scripting: jnimain.cpp e dllmain.cpp do código nativo. Tanto o JNITurtle quanto o PInvokeTurtle implementam suas estratégias para liberação de recursos via dispose pattern.
jnimain.cpp:
#include "scriptable.hpp" #include <jni.h> extern "C" { JNIEXPORT jobject JNICALL Java_JNITurtle_create(JNIEnv* env, jobject) { return env->NewDirectByteBuffer(scriptable::create(), 0); } JNIEXPORT void JNICALL Java_JNITurtle_rotate(JNIEnv* env, jobject, jobject hTurtle, jfloat angle) { auto turtlePtr = reinterpret_cast<scriptable::TurtlePtr>(env->GetDirectBufferAddress(hTurtle)); scriptable::rotate(turtlePtr, angle); } JNIEXPORT void JNICALL Java_JNITurtle_resize(JNIEnv* env, jobject, jobject hTurtle, jfloat size) { auto turtlePtr = reinterpret_cast<scriptable::TurtlePtr>(env->GetDirectBufferAddress(hTurtle)); scriptable::resize(turtlePtr, size); } JNIEXPORT void JNICALL Java_JNITurtle_move(JNIEnv* env, jobject, jobject hTurtle, jint distance) { auto turtlePtr = reinterpret_cast<scriptable::TurtlePtr>(env->GetDirectBufferAddress(hTurtle)); scriptable::move(turtlePtr, distance); } JNIEXPORT void JNICALL Java_JNITurtle_speed(JNIEnv* env, jobject, jobject hTurtle, jint speed) { auto turtlePtr = reinterpret_cast<scriptable::TurtlePtr>(env->GetDirectBufferAddress(hTurtle)); scriptable::speed(turtlePtr, speed); } JNIEXPORT void JNICALL Java_JNITurtle_destroy(JNIEnv* env, jobject, jobject hTurtle) { auto turtlePtr = reinterpret_cast<scriptable::TurtlePtr>(env->GetDirectBufferAddress(hTurtle)); scriptable::destroy(turtlePtr); } }
dllmain.cpp:
#include "scriptable.hpp" extern "C" { __declspec(dllexport) scriptable::TurtlePtr create() { return scriptable::create(); } __declspec(dllexport) void rotate(scriptable::TurtlePtr hTurtle, float angle) { scriptable::rotate(hTurtle, angle); } __declspec(dllexport) void resize(scriptable::TurtlePtr hTurtle, float size) { scriptable::resize(hTurtle, size); } __declspec(dllexport) void move(scriptable::TurtlePtr hTurtle, int distance) { scriptable::move(hTurtle, distance); } __declspec(dllexport) void speed(scriptable::TurtlePtr hTurtle, int value) { scriptable::speed(hTurtle, value); } __declspec(dllexport) void destroy(scriptable::TurtlePtr hTurtle) { scriptable::destroy(hTurtle); } }
Você poderá carregar o Turtle.dll (o engine do Turtle Graphics) nos respectivos ambientes REPL (F# e Scala) usando uma fachada de código gerenciado, no meu caso TurtleFSharp.dll e turtle.jar.
E com isto, produzir uma sequência de riscos variados, ou seja, desenhos:
Aqui um exemplo de script em Scala:
Se tiver curiosidade. A Turtle.dll, o engine do meu Turtle Graphics, é composto das seguintes dependências:
D2D1.DLL é o Direct2D, MSCRV100.DLL é a C Runtime Library (CRT) do Visual C++ 10 e MSVCP100.DLL é a Standard C++ Library (SCL) do Visual C++ 10. O resto já é conhecido do sistema operacional Windows.
Se quiser digerir a parte que interage com o Direct2D, você encontrará no program.cpp definições como:
Construtor e os recursos a serem utilizados (estes caras que precisarão ser liberados pela ação de dispose):
Inicializador da Janela:
O message loop:
Método que desenha o caminho da tartaruga:
O “renderizador”:
Responsável por carregar o “cursor” da tartaruga:
As funções matemáticas de auxilio:
E o componente de script:
Enfim, vai encontrar algumas pérolas.
Faça o download do código fonte e binário do Turtle Graphics e assim como eu divirta-se. Na verdade, acho que você vai preferir mesmo o Small Basic ou o Logo, mas isto não é mais problema meu – mas o meu funciona nos REPLs do Scala e do F# (e C#, quando o Roslyn estiver ok).