Skip to main content

Thiago Lages de Alencar

Ao começar a estudar Rust percebi o quão a relação deste dois são próxima, porém primeiro precisamos rever unions e enums.

Union

Se trata de utilizar o mesmo espaço de memória para armazenar um entre diversos tipos.

union Content {
int i;
float f;
char c;
};

union Content content;

Nesse exemplo eu declarei um union que pode conter um dos seguintes valores: int, float, char.

Então eu posso escrever qualquer um dos tipos nele:

content.i = 10;
content.f = 5.0;
content.c = 'a';

O valor final de content vai ser a pois foi o último valor que botamos.

Porém a parte importante é justamente o fato de usarmos o mesmo espaço de memória para armazenar qualquer um destes tipos.

content.c = 'a';

printf("%c\n", content.c); // print "a"
printf("%c\n", content.i); // print "a"

Quando você declara uma union, o tamanho dela é definido pelo maior tamanho entre os tipos que ela precisa conseguir armazenar.

TipoTamanho
int4 bytes
float4 bytes
char1 byte

No nosso caso o melhor tamanho seria 4 bytes, pois com ele você consegue armazenar o char também.

Nada impede de armazenarmos um char e tentarmos ler aquele espaço da memória como um int.

content.c = 'a';

printf("%c\n", content.c); // print "a"
printf("%c\n", content.i); // print "a"

// O character 'a' é nada mais que o número 97 na memória.
printf("%i\n", content.c); // print "97"
printf("%i\n", content.i); // print "97"

Resta a nós utilizar corretamente o valor daquele espaço de memória.

Afinal não queremos armazenar um char na union e mais tarde no código tentar utilizar como um int, né?

content.c = 'a';

// ...

printf("%i\n", content.i + 5); // print "102"

Não só isso como quando você armazena um valor naquele espaço de memória, ele apenas escreve no espaço que ele usaria.

content.c = 'a';

printf("%i\n", content.c); // print "97"
printf("%i\n", content.i); // print "97"

content.i = -10;
content.c = 'a';

printf("%i\n", content.c); // print "97"
printf("%i\n", content.i); // print "-159"

O que aconteceu aqui?

  • Início do programa os 4 bytes de content estão com zero
  • Content recebeu no primeiro byte o valor 97 (pois a == 97)
  • Printamos utilizando content.c
    • Isto nos faz utilizar um byte
  • Printamos utilizando content.i
    • Isto nos faz utilizar 4 bytes
  • Content recebeu encheu os 4 bytes para formar o valor de -10
  • Content recebeu no primeiro byte o valor 97
  • Printamos utilizando content.c
    • Isto nos faz utilizar um byte (primeiro byte está com o valor de 97)
  • Printamos utilizando content.i
    • Isto nos faz utilizar 4 bytes (primeiro byte + os outros 3 bytes que não foram limpos)

Podemos confirmar isto settando para zero antes de preenchermos.

content.c = 'a';

printf("%i\n", content.c); // print "97"
printf("%i\n", content.i); // print "97"

content.i = -10;
content.i = 0;
content.c = 'a';

printf("%i\n", content.c); // print "97"
printf("%i\n", content.i); // print "97"

Como podemos ver, é essencial termos uma maneira de identificar qual é o tipo atual na union.

Enum

Se trata de ligar um identificador a um número único dentro de um agrupamento.

enum Type {
Integer,
Floating,
Character,
};

enum Type type;

Neste caso está ligando:

IdentificadorNúmero
Integer0
Floating1
Character2

Isto poupa trabalho de criarmos manualmente uma variável para cada valor, por exemplo:

int Integer = 0;
int Floating = 1;
int Character = 2;

Além de deixarmos claro o tipo de variável durante a criação dela (como um valor dentro daquele agrupamento).

enum Type type = Integer;

printf("%i\n", type); // print "0"

Enfim, a essa altura você pode já ter notado a importância de enum para unions.

Com eles podemos criar ligar um identificador a um tipo, como se fosse uma tag para aquele union.

union content {
int i;
float f;
char c;
} content;

enum Types {
Integer,
Floating,
Character,
} type;

content.f = 5.0;
type = Floating;

Note que precisamos atualizar type sempre que mudarmos o tipo de content, porém ganhamos a capacidade de tratar corretamente a union.

if (type == Floating) {
printf("%f\n", content.f);
} else if (type == Character) {
printf("%c\n", content.c);
} else {
printf("%i\n", content.i);
}

Este tipo de estrutura é tão comum que tem o nome de Tagged Union.

Rust Enum

Acredito que o enum do Rust seja nada mais que um tagged union.

#[derive(Debug)]
enum Type {
Integer,
Floating,
Character,
}

println!("{:#?}", Type::Integer);

Em outras lugares enum ligaria apenas identificadores a números, porém em Rust você pode armazenar estrutura/tipos juntos aos enums.

#[derive(Debug)]
enum Type {
Integer(i32),
Floating(f32),
Character(char),
}

let content: Type = Type::Floating(5.0);

println!("{:#?}", content);

Isso nos da uma estrutura só que possue a capacidade de fazer o mesmo que tagged union e com menos chance do desenvolvedor cometer um erro.

Por exemplo, não precisamos mais atualizar o tipo armazenado na variável toda vez que alteramos:

// C
content.f = 5.0;
type = Floating;

// Rust
let content: Type = Type::Floating(5.0);

Rust é linguagem que preza bastante segurança, então faria sentido tratar como se fosse uma única estrutura para evitar os problemas de union (embora Rust tenha o tipo union).

Comparison

C

union content {
int i;
float f;
char c;
} content;

enum Types {
Integer,
Floating,
Character,
} type;

content.f = 5.0;
type = Floating;

if (type == Floating) {
// Fazer algo com float
} else if (type == Character) {
// Fazer algo com char
} else {
// Fazer algo com int
}

Rust

#[derive(Debug)]
enum Type {
Integer(i32),
Floating(f32),
Character(char),
}

let content: Type = Type::Floating(5.0);

match content {
Type::Floating(f) => // Fazer algo com float
Type::Character(c) => // Fazer algo com char
Type::Integer(i) => // Fazer algo com int
}

Thiago Lages de Alencar

Esses últimos meses eu tenho gastado um grande tempo (mais do que gostaria) vendo o código de inverse kinematics 2D do Godot.

E eu preciso passar a limpo o conhecimento básico que eu possuo, para melhor garantir que não estou cagando tudo no código deles.

Mesma cara do meme "awkward look monkey puppet"

Robotic Arms

É difícil falar de forward kinematics (FK) e inverse kinematics (IK) sem entender braços robóticos, pois foi o primeiro uso destas lógicas.

Para entender melhor um braço, vamos dividi-lo em 4 partes:

  • Base
  • Limbs (membros)
  • Joints (articulações)
  • End effector (mão do braço)

Braço robótico

  • Base, providência estabilidade para o braço
  • Limbs, separa as partes entre si
  • Joints, são responsáveis por se rotacionar
  • End effector, interage com o objeto

A parte que mais damos atenção quando falamos de FK e IK são joints, pois elas providência a lógica de movimentação do braço.

FK & IK

Se tivessemos que resumir cada assunto, seria algo por parte de:

  • Forward kinematics
    • Foca em descobrir o estado da mão, dado que o braço está em certo estado.
  • Inverse kinematics
    • Foca em descobrir o estado do braço, dado que deseja a mão em certa estado.
note

Quando eu digo "estado", estou me referindo a posição e rotação dos respectivos componentes.

FK

FK

IK

IK

Forward Kinematic

Minha kinematic favorita por ser a mais simples de calcular, onde tudo se resume a um grande somatório.

Braço robótico totalmente vertical

Adicionei setas nessa imagem para represetarem as rotações locais das joints (articulações), essa rotação se refere ao quanto aquela joint (articulação) se rotacionou.

Braço robótico totalmente vertical com ângulos clássicos

Agora adicionei uma circuferência mostrando os ângulos clássicos e podemos usar eles como referência para a rotação global, essa rotação é sempre relacionada à direita (eixo X).

Para começar nosso experimento, podemos rotacionar o end effector (mão) por 90 graus e ver o que podemos concluir.

Mão rotacionada 90 graus

  • End effector
    • Rotação local: 90º
    • Rotação global: 90º
  • Joint 2
    • Rotação local: 0º
    • Rotação global: 0º
  • Joint 1
    • Rotação local: 0º
    • Rotação global: 0º

Podemos ver que rotacionar um ponto localmente não afeta os anteriores (os pais). Também importante notar que rotação local sempre irá afetar a rotação global daquele ponto.

Mas se tivessemos rotacionado a joint 2 por 90 graus?

Joint 2 rotacionada 90 graus

  • End effector
    • Rotação local: 0º
    • Rotação global: 90º
  • Joint 2
    • Rotação local: 90º
    • Rotação global: 90º
  • Joint 1
    • Rotação local: 0º
    • Rotação global: 0º

Notamos que rotacionar localmente um ponto, afeta a rotação global dos pontos seguintes pela mesma quantidade.

Se rotacionarmos localmente o end effector por -90 graus?

End effector rotacionada -90 graus

  • End effector
    • Rotação local: -90º
    • Rotação global: 0º
  • Joint 2
    • Rotação local: 90º
    • Rotação global: 90º
  • Joint 1
    • Rotação local: 0º
    • Rotação global: 0º

Nós conseguimos voltar a rotação global do end effector para 0º pois rotacionar localmente sempre afeta o global do mesmo ponto.

Nesse pequeno experimento já podemos começar a notar uma formula bem simples:
Rotação global = Rotação global do ponto anterior + Rotação local

Utilizando o end effector como referência:
Rotação global = 90º + (-90º) = 0º

info

No caso do primeiro ponto (joint 1), a rotação global anterior seria 0 graus.
Rotação global = 0 + Rotação local

O que aconteceria se rotacionarmos a joint 1 por 90 graus?

Joint 1 rotacionada 90 graus

  • End effector
    • Rotação local: -90º
    • Rotação global: 90º
  • Joint 2
    • Rotação local: 90º
    • Rotação global: 180º
  • Joint 1
    • Rotação local: 90º
    • Rotação global: 90º

Podemos ver que afetamos a rotação global de todos os pontos seguintes, aumentando eles por 90 graus.

Conclusion

Lembra quando falei que forward kinematics "foca em descobrir o estado da mão, dado que o braço está em certo estado". É exatamente esse somatório que nos ajuda a resolver o problema.

Vamos supor que temos 5 pontos (ponto 1 é a base):

  • Ponto 5
    • Rotação local: -90º
    • Rotação global: ?
  • Ponto 4
    • Rotação local: 0º
    • Rotação global: ?
  • Ponto 3
    • Rotação local: 180º
    • Rotação global: ?
  • Ponto 2
    • Rotação local: 0º
    • Rotação global: ?
  • Ponto 1
    • Rotação local: 90º
    • Rotação global: ?

E que nós queremos descobrir para onde cada ponto está apontando.

Se a gente começar da base, podemos ir calculando a rotação global de cada ponto usando a formula:
Rotação global = Rotação global do ponto anterior + Rotação local

Rotação global do ponto 1: 0º + 90º = 90º
Rotação global do ponto 2: 90º + 0º = 90º
Rotação global do ponto 3: 90º + 180º = 270º
Rotação global do ponto 4: 270º + 0º = 270º
Rotação global do ponto 5: 270º + -90º = 180º

note

Isto também deixa bem visível o como a rotação global de um ponto anterior afeta o próximo ponto. Por exemplo:

Somar 90 graus ao ponto 1 faria com que ponto 2 tivesse mais 90 graus...
Ponto 2 com 90 graus a mais faria com que ponto 3 tivesse mais 90 graus...
Ponto 3 com 90 graus a mais faria com que ponto 4 tivesse mais 90 graus...

  • Ponto 5
    • Rotação local: -90º
    • Rotação global: 180º
  • Ponto 4
    • Rotação local: 0º
    • Rotação global: 270º
  • Ponto 3
    • Rotação local: 180º
    • Rotação global: 270º
  • Ponto 2
    • Rotação local: 0º
    • Rotação global: 90º
  • Ponto 1
    • Rotação local: 90º
    • Rotação global: 90º

References

Thiago Lages de Alencar

Definições utilizadas para informar em que ordem os bytes vão ser escritos na memória.

É preciso lembrar que a memória tem endereços, existe início e final.
Para o nosso exemplo iremos usar 16 bits (2 bytes):

| Endereço da memória |
| ------------------- |
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
| 11 |
| 12 |
| 13 |
| 14 |
| 15 |

O computador apenas trabalha com bytes (modificações são feitas em bytes, não bits).
Então vamos representar uma divisão entre os bytes.

| Endereço da memória |
| ------------------- |
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| ------------------- |
| 8 |
| 9 |
| 10 |
| 11 |
| 12 |
| 13 |
| 14 |
| 15 |

Se pegarmos um inteiro (unsigned) como 5351, vamos precisar de 2 bytes para armazena-lo (pois seu binário é 00010100 11100111).
Existe duas maneiras famosas de se armazenar eles.

Little-endian: Byte menos significativo primeiro ("little end first").
Big-endian: Byte mais significativo primeiro ("big end first").

| Endereço da memória | Little-endian | Big-endian |
| ------------------- | ------------- | ---------- |
| 0 | 1 | 0 |
| 1 | 1 | 0 |
| 2 | 1 | 0 |
| 3 | 0 | 1 |
| 4 | 0 | 0 |
| 5 | 1 | 1 |
| 6 | 1 | 0 |
| 7 | 1 | 0 |
| ------------------- | ------------- | ---------- |
| 8 | 0 | 1 |
| 9 | 0 | 1 |
| 10 | 0 | 1 |
| 11 | 1 | 0 |
| 12 | 0 | 0 |
| 13 | 1 | 1 |
| 14 | 0 | 1 |
| 15 | 0 | 1 |

Note que os bits não mudam de ordem, apenas os bytes.

Thiago Lages de Alencar

O bit mais significativo é o bit que representa a maior valor do número.
O bit menos significativo é o bit que representa o menor valor do número.

O número 149 em binário é 10010101, cada bit é responsável por representar um número.

10010101
1286432168421

Nós sabemos que isto é equivalente a 149 pois somando o valor de todos os bits que são 1 obtemos 149.

128 + 16 + 4 + 1 = 149

Thiago Lages de Alencar
var sprite := Sprite2D.new()
sprite.texture = load("res://image.png") as Texture2D
var sprite := Sprite2D.new()
var image := Image.load_from_file("user://image.png")
sprite.texture = ImageTexture.create_from_image(image) as Texture2D

Sprite2D

Sprite2D é um Node que já possui informações do que quer exibir e apenas fica responsável por administrar como exibir.

O que ele quer exiber? Texture.

Texture

Texture possui a informação daquilo que quer exibir e já foi carregado na placa de video.

O que ele quer exibir? Image.

Image

Image possui a informação daquilo que quer exibir e já foi carregado na memória RAM.

O que ele quer exibir? File.

File

File possui os bytes daquilo que quer exibir porém ainda está no HD/SSD.

load() vs load_from_file()

load()

Utilizado para carregar imagens que foram comprimidas e armazenadas juntas ao executável do jogo (as imagens que você tem que referênciar com res://).

Está função também mantém um cache das imagens carregadas, toda chamada irá retornar a mesma imagem já carregada anteriormente.

load_from_file()

Utilizado para carregar imagens novas, sem prévio conhecimento (imagens referênciadas com user:// ou que você possui os bytes em uma variável).

Está função não mantém cache, cada imagem gerada por ela irá ocupar mais espaço na memória.

References

Thiago Lages de Alencar

No post anterior declarei que busquei por um tempo uma interface gráfica (GUI) para MongoDB porém não fiquei satisfeita com nenhuma.

Neste post vou falar do desenvolvimento do Mondot, interface gráfica para o banco MongoDB.

Por favor, levar em conta que eu posso tomar decisões ruins 🤣

Reason

Em 2021, eu tinha duas coisas em mente:

  • Nenhuma GUI de Mongo me agrada
  • Quero aprender Godot

Bastou essas duas coisas para eu querer começar este projeto.
Eu não queria fazer a melhor GUI do mundo, eu queria me divertir com Godot ao mesmo tempo que resolvia um incomodo que eu tinha com GUIs.

Start

Abri o Godot, comecei a criar containers, janelas, botões, etc. Dias depois me veio a pergunta:

Como diabos eu vou me comunicar com o Mongo?

Rosto de idiota com o olhar torto para fora

Eu vou pegar o texto que o usuário escrever e fazer o que com ele?
Passar para um Node.js?
Eu vou ter que instalar Node.js na máquina da pessoa?
Como eu pego a resposta?
Eu conseguiria usar GDNative?
Como que os outros projetos fazem isto?

Agora eu precisava descobrir como faria isto acontecer.

Cheating

Colando do colega do lado

Robomongo! Também conhecido como Robo 3T.
Um projeto open-source que em 2017 foi adquirido pelos criadores do Studio 3T.

O que importa é que eu tenho um projeto para copiar estudar!

Robo 3T possui dois repositórios:

  • robomongo
    • Responsável pela interface gráfica
  • robomongo-shell
    • Responsável pela interação com o Mongo.

Importante notar que o segundo é um fork do Mongo oficial, justamente pois nele é incluido um shell interativo para se comunicar com o banco.

Isso faz sentido, já que a interface de linha de comando geralmente é a primeira coisa a ser feita para interagir com bancos. Como mais as pessoas interagiriam com o banco antes de GUI existirem? Poderia ser por código mas seria um baita trabalho cada operação.

Então era isso, tudo que eu tinha que fazer era:

  1. Incluir o shell do Mongo na minha interface gráfica
  2. Utilizar o modo iterativo do shell

Problems

Eu não tinha ideia de como fazer para incluir o shell na interface gráfica.
Decidi aceitar que passaria como o shell separado para o usuário e dei essa parte como dada.

Eu que não vou reclamar das minhas decisões idiotas no meu projeto pessoal.

Shrug face

Agora tudo que eu tinha que fazer é chamar o shell pelo Godot.
Para isto eu iria precisar usar o método OS.execute().

int execute (
String path,
PoolStringArray arguments,
bool blocking=true,
Array output=[],
bool read_stderr=false,bool open_console=false
)

O problema é que essa função possui dois comportamentos dependendo se blocking for true ou false, porém nenhum dos dois era o que eu buscava.

Outro rosto de idiota com o olhar torto para fora

blocking -> true: Godot vai pausar enquanto a saída não é escrita em output.

Não queremos isto pois uma busca pode resultar em milhares de documentos. Queremos retornar mais documentos conforme o usuário pedir por mais documentos.

blocking -> false: Godot vai continuar executando e o comando irá rodar em um processo separado. Porém não será possível recuperar o output do comando.

Nós queremos receber a saída aos poucos se estivermos falando de pesquisas que resultam em muitos documentos.

Função do Godot não é interativa, como vou usar o shell interativo?
Posso ler input e output usando algum conhecimento do sistema operacional?
Eu vou ter que conhecer bem todos sistemas operacionais?
Vou ter que descobrir como Robo 3T linka o shell?

Solution

Criar meu próprio shell.

Shell

Vantagens:

  • Utilizar a linguagem que mais estou em dia Python
    • Em outras palavras: Nenhum tempo perdido por errar algo de uma linguagem que não estou acostumado (JavaScript)
  • Utilizar a mesma linguagem com qual trabalho para fazer queryies
  • Utilizar o padrão de comunicação que eu quiser
    • No próximo tópico você vai entender o que eu quero dizer com isto

Se estivessemos falando de um produto para diversos usuários, definitivamente JavaScript seria a melhor escolha.

Para projeto pessoal com interesse em Godot? Utilizar a linguagem que mais tenho conhecimento no momento.

Communication

Shell

De todas as maneiras para duas aplicações se comunicarem, eu escolhi a mais simples.

Arquivos... :D

Em vez de quebrar a cabeça para entender como eu poderia reproduzir a interatividade do shell do Mongo, eu poderia apenas fazer com que ambos escrevessem em arquivos quando quisessem se comunicar um com o outro.

O processo em si é bem simples:

  1. Godot executa o shell, passando a query
  2. Shell escreve X documentos em um arquivo
  3. Godot solicita mais documentos

Note que você deve repetir etapa 2 e 3 até acabar os documentos ou Godot mandar parar.

Start

Primeira parte da comunicação entre shell e Mondot

  1. Godot escreve o código Python em um arquivo
    • xxxxx representa o nome do arquivo
    • O nome do arquivo é aleatório
  2. Godot executa o shell passando o caminho para o arquivo como parâmetro

Output

Segunda parte da comunicação entre shell e Mondot

  • Godot fica periodicamente conferindo se o arquivo de saída existe
    • xxxxx_out_0 representa o primeiro arquivo de saída
      • xxxxx_out_1 segundo arquivo de saída
      • xxxxx_out_2 terceiro arquivo de saída
      • etc
  • Shell escreve no arquivo de saída um JSON com o resultado da query
    • O resultado de uma query pode ser partido em diversos arquivos
      • xxxxx_out_0, xxxxx_out_1, xxxxx_out_2, ...
      • Esses arquivos são criados conforme o usuário solicita mais documentos do resultado

Input

Terceira parte da comunicação entre shell e Mondot

  • Godot escreve no arquivo de entrada solicitando mais documentos do resultado da query
  • Shell fica periodicamente conferindo se tem algo escrito no arquivo de entrada
    • xxxxx_in representa o arquivo de entrada
    • Ele lê o arquivo e remove o conteúdo do arquivo
      • Justamente para receber futuras solicitações pelo mesmo arquivo

Shell

O que o meu shell precisa fazer?

  • Receber o código do usuário
  • Executar o código do usuário
  • Iterar sobre o resultado do código
    • Iterar em partes (pegando X documentos por vez)

Em outras palavras, eu preciso acoplar o código do usuário ao código do shell.

Código do usuário sendo acoplado no meio do código do shell

Note que existem operações que meu shell irá fazer antes e após a execução do código do usuário. Por causa disso, o desenho mostra o código no meio do código shell.

O plano era receber um código simples do usuário, parecido com o do shell do Mongo:

db.test.find({})

Attempt 1

eval(expression, globals=None, locals=None)

Bom: A função faz o parser e avalia a expressão, retornando o resultado dela.

result = eval("""
db.test.find({})
""")

Ruim: Precisa ser uma expressão. Uma ou mais declarações não funcionam.

# Error
result = eval("""
db.test.find({})
db.test.find({})
""")

Attempt 2

exec(object, globals=None, locals=None, /, *, closure=None)

Bom: Executa múltiplas declarações.

exec("""
db.test.find({})
db.test.find({})
""")

Ruim: Cada declaração pode ter ou não um resultado, então essa função não retornar nada.

# result is None
result = exec("""
db.test.find({})
db.test.find({})
""")

Problem

Nas tentativas acima, para obter o resultado desejado eu teria que alterar a string. Porém, isto apresenta grande risco da alteração dar erro pois cada caso pode requer uma alteração diferente.

No final eu seria forçado a ler o código, entende-lo para depois alterar sem grande chance de erros. Mas sabe quem já faz isso de ler e entender código? O compilador.

AST

Desenho de uma árvore para representar AST

A compilação de uma linguagem envolve diversas etapas. Uma delas envolve gerar uma árvore sintática abstrata, uma árvore que garante que os objetos estão ligados corretamente.

Podemos usar este conhecimento para compilar parcialmente o código do usuário. Por exemplo, o seguinte código:

for x in db.test.find({}):
db.test.update({"_id": x["_id"]}, {"code": x["code"] + 1})
db.test.find({})

Gera uma árvore com a seguinte raiz:

Simples AST

Note que eu só mostrei o início da árvore pois todo o resto é irrelevante para nós.
Não vamos alterar nada que esteja a fundo do código do usuário, apenas na raiz.

Com esse conhecimento em mão, podemos finalmente alterar o código do usuário!

info

Utilizei um módulo do próprio Python chamado ast para analisar e reestruturar a AST.

Rewriting code

Vamos criar um código inútil apenas para usar como exemplo:

# User code
doc = db.test.find_one({})
db.test.update_one({}, {"valid": True})
db.test.find({})

A idéia é transformar este código em uma função que o shell poderá chamar e receber de volta o valor da última expressão. Em outras palavras, queremos isto:

def code():
# User code
doc = db.test.find_one({})
db.test.update_one({}, {"valid": True})
return db.test.find({})

Vamos ao passo a passo de como obter isto:

  1. Análisar o código do usuário com AST
  2. Inserir o conteúdo do módulo dentro de uma função pré montada
  3. Encapsular a última expressão em um return
    • Apenas fazer isto se for uma expressão que retorna valor

O video seguinte demonstra a transformação que está sendo feita de certa forma.

Conclusion

Com isto eu consegui preparar justamente uma interface gráfica para o Mongo em Godot. Esse foi meu projeto de 2021!


Em 2023 eu decidi dar uma refatorada e criei uma segunda versão. Mas toda essa base vista do Mondot ainda é a mesma!


Agora já estou cansado de falar deste projeto hahahaha, foi bom enquanto durou.

References

Thiago Lages de Alencar

Em 2019 eu comecei a trabalhar em uma startup que usava banco de dados MongoDB.

Na época mantermos diversas licenças da interface gráfica Studio 3T não era uma boa ideia, não eramos uma empresa lucrativa e o uso dela por pessoa não iria valer a pena.

E por isso comecei a buscar por um interface gráfica que fosse me satisfazer.

Pessoa caminhando com uma madeira carregando seus pertences na ponta

Robot 3T

Uma alternativa considerada foi Robo 3T/Robomongo mas pelo fato dele ter sido adquirido pelo Studio 3T já não era um bom sinal para mim.

É muito normal ver empresas grandes comprando concorrentes e depois deixando eles aos poucos sumirem, então meu palpite era que Robot 3T não iria receber tantas atualizações e não cresceria como ferramenta.

Hoje em em 2023: Fazendo 6 anos desde que foi adquirido e uma versão de graça do Studio 3T foi lançada para substituir o Robo 3T que foi descontinuado.

Mongo-Express

Utilizei mongo-express por um tempo e satisfazia o mínimo que eu precisava mas a interface gráfica não me agradava.

Hoje em em 2023: Parece ter mantido a mesma interface, o que não me atrai.

QueryAssist

Em 2020, QueryAssist foi a ferramenta que eu decidi utilizar sempre para interagir com o Mongo.

Ela possuia uma versão gratuita mas você podia comprar a versão vitalícia e ganhar mais funcionalidades. A versão gratuita era mais que o suficiente para mim e funcionava muito bem.

Infelizmente durou pouco, fui notando a falta de atualização no blog e nos posts do Twitter. Claramente um sinal que a ferramenta iria desaparecer, por causa disso decidi retornar a minha busca por outra interface gráfica antes que está parece de funcionar sem aviso prévio.

Hoje em em 2023: Site deles foi desligado e eles não tem postagens novas desde 2020.

MongoDB Compass

MongoDB Compass parece estar em construção desde 2015, mas na época que eu estava buscando interfaces gráficas era como se ele não existisse para mim. Meu palpite era que não estava divulgando já que não estava pronto para produção 🤔.

Tudo que posso dizer é que quando finalmente descobri a existência dele, a ausência de alguma funcionalidade não me agradou...

Hoje em em 2023: Aposto que hoje em dia já devem ter implementado a funcionalidade que eu queria mas não estou em dia com as ferramentas do Mongo para saber.

MongoDB for VS Code

Quando MongoDB for VS Code lançou oficialmente para mim era um sinal que minha busca tinha terminado, pois era uma ferramenta desenvolvida pelos mesmos criadores do MongoDB e que era só uma questão de tempo até implementarem diversas funcionalidades legais.

Nesta época eu até parei o desenvolvimento de uma ferramenta que eu estava construindo.

Hoje em em 2023: Parece que a extensão apenas foi criada para fazer o mínimo e não deve evoluir muito.

Thiago Lages de Alencar

Na primeira parte nós focamos exclusivamente em tiles que precisavam apenas satisfazer uma relação com os adjacentes. Porém existe casos onde relação com as diagonais também é importante.

Vamos organizar em 3 tipos:

  • Sides (visto anteriormente)
    • O foco era casar a adjacente.
  • Corners (será visto)
    • O foco vai ser casar duas adjacentes e uma diagonal.
  • Corners and Sides (será visto)
    • O foco vai ser resolver corners e sides.

A seguinte imagem desmonstra como cada tipo deve casar:

Comparação entre os 3 tipos de tiles

Tirando isto a lógica principal de Wang tiles permanece, ou seja, não precisamos falar dos mesmos assuntos vistos na primeira parte pois você só precisa adaptar a maneira de casar tiles.

Porém iremos mostrar para cada um dos tipos:

  • Todos os possiveis tiles
  • Mínimo de tiles considerando rotação e reflexão

Sides

Todos os possiveis tiles para "sides"

16 tiles

Minimal

O mínimo tiles para "sides"

6 tiles


Corners

Todos os possiveis tiles para "corners"

16 tiles

Minimal

Todos os possiveis tiles para "corners"

6 tiles


Corners and Sides

note

Clique nas imagens para abrir em outra janela e depois de bastante zoom.
E lembre que para restar o zoom existe o atalho Ctrl+0.

Todos possiveis tiles para "corners and sides"

256 tiles

Minimal

Todos os possiveis tiles para "corners"

51 tiles


References

Thiago Lages de Alencar

(Wang tiles foi proposto pelo matemático Hao Wang em 1961)

Assuma que teremos um conjunto de tiles onde cada lado está pintado de apenas uma cor. Por exemplo:

Exemplo de 5 tiles diferentes

  1. Cada lado com uma cor.
  2. Dois lados com a mesma cor.
  3. Todos os lados com a mesma cor.
  4. Dois lados adjacentes com a mesma cor.
  5. Variação das cores já vistas só que em posições diferentes.
note

Por simplicidade mudaremos para duas cores apenas.

A ideia é reutilizar os mesmos tiles quantas vezes quisermos para botar eles lado a lado e formar um plano, porém com as cores laterais do tiles sempre casando. No exemplo seguinte temos 5 tiles e 2 exemplos de planos formados por eles:

Exemplo utilizando 5 tiles diferentes para criar 2 combinações de plano

Importante notar que:

  • Tiles não podem se sobrepor.
  • Tiles não podem ser rotacionados ou refletidos.

Pois não é possível saber se seriam tiles válidos sem conhecer a imagem utilizada neles.

Automation

Embora reutilização de tiles para gerar diversos planos/mapas não seja especial, Wang tiles adiciona a lógica de relacionar os tiles entre si. Isto nos permite verificar se um tile é válido numa determinada posição.

Por exemplo, possuindo 2 cores e 4 lados, podemos formar 16 (24) tiles diferentes:

Todas os possíveis tiles com duas cores e quatro lados

note

Adicionamos um quadrado cinza no centro de cada tile.

Botamos estes tiles na game engine Godot e nela definimos a relação entre os tiles.

Ao começar a pintar tiles dentro da game engine, podemos ver que ela consegue verificar qual tile é válido naquela posição ou se precisa alterar os vizinhos.

Em segundos conseguimos construir planos onde os tiles tem uma conexão entre si.

Variations

É importante notar que não há problema criar variações do mesmo tile. Por exemplo:

Mesmos 16 porém com a adição de 3 tiles com todos os lados verdes

No momento de preencher por um tile válido naquela posição, a ferramenta iria ter que escolher entre 4 tiles diferentes. Algumas ferramentas como Godot escolheram aleatoriamente:

Antes
Exemplo de plano criado utilizando os 16 tiles

Depois
Exemplo 3 de plano criado utilizando os tiles, porém com árvores

Rotation & Reflection

Na proposta de Wang não se pode rotacionar e refletir tiles pois não existe garantia que a imagem continuará fazendo sentido após rotacionada ou refletida.

Porém como criador dos tiles, somos capazes de deduzir está informação e apenas fazer os tiles necessários. Vamos pegar este conjunto de tiles:

Todas os possíveis tiles com duas cores e quatro lados

Alguns destes tiles são variações dos anteriores porém rotacionados ou refletidos. Levando isto em conta, podemos minimizar para 6 tiles apenas:

Seis tiles que conseguem representar os mesmo tiles acima

É importante notar que só é possível se conhecermos a imagem. Botando a mesma árvore utilizada anteriormente em um dos tiles, podemos ver o tile perder o sentido quando rotacionado mas não quando refletido:

Árvore adicionada a um tile e suas variações de rotação e reflexão

Example

Todos nossos tiles tem sido com cores, porém as cores apenas servem para representar a relação entre os tiles.

Para deixar isto claro, vamos substituir o desenho dos tiles por desenhos que melhor representem um labirinto. Vamos trocar azul por arbustos e verde por terra:

Comparação do tiles bases com tiles do labirinto

Utilizando estes tiles com suas rotações/relexões, podemos criar em segundos o nosso labirinto:

Tiles do labirinto

note

Este labirinto está com cara de circuitos da placa mãe. 🤔

References

Thiago Lages de Alencar

Estou experimentando alternativas já que o Tumblr me deixou puto.
E a pior parte é que foram funcionalidades importantes para um site de blogs!

Text Editor

Imagem ilustrativa. Uma caneta escrevendo num papel, no papel tem diversas linhas e uma delas é um video 🤣

Primeiro preciso falar dos 3 editores de texto que o Tumblr fornece:

  • Rich Text (default)
    • Positivo: Os criadores do site gastaram tempo planejando como melhor te passar a habilidade de escrever texto em negrito/itálico ou como inserir links/imagens/videos.
    • Negativo: Não existe garantia que possa mover seu texto para outros blogs sem perder qualidade, as chances de estar preso ao Tumblr são altas.
  • HTML
    • Positivo: Todos os websites são feitos utilizando HTML, então o único limite do que se pode fazer é o quanto o Tumblr limitou de utilização.
    • Negativo: Não é prático de escrever ou alterar sem ajuda de editores de texto modernos.
  • Markdown
    • Positivo: Criada para providênciar formatação ao texto sem dificultar a legibilidade. Por baixo dos panos faz uma conversão para HTML, então também é normal ver essa linguagem aceitar funcionalidade do HTML.
    • Negativo: Relativamente limitado de como se pode formatar seu texto.

Na minha cabeça HTML e Markdown parecem existir apenas para enfeitar, eles não parecem dar suporte para nada que não seja possível no Rich Text. Funcionalidades simples como a linha horizontal não são implementadas e apenas recebemos o aviso:

Contents contains unsupported HTML, the post may not be what you expected.

Contents contains unsupported Markdown, the post may not be what you expected.

Eu entendo filtrar funcionalidades do HTML é aceitável pois poderia ser perigoso permitir tudo. Porém filtrar funcionalidade do Markdown não faz sentido já que a linguagem já foi feita para ser bem limitada e não oferecer nenhum risco (estou considerando Markdown sem HTML).

No final ambos acabam sendo uma versão mais limitada que o próprio Rich Text. Se tentarmos ser otimistas, podemos dizer que agora podemos exportar para HTML/Markdown e levar para outros sites.

Agora vamos ver os próximos detalhes que me incomodaram, tipo o fato do Tumblr gostar de alterar o seu texto...

Switching Text Editors

Setas apontando para os 3 tipos de formatos permitidos, sinalizando que está indo de um para o outro

Primeiro vamos deixar claro que é um bom senso na vida não alterar algo que você vá precisar da versão original mais tarde. E no caso do Tumblr eu não tenho certeza de qual formato é o original entre Rich Text, HTML e Markdown (eu apostaria no Rich Text por ser o formato padrão).

O problema que me deparei foi que ao trocar de editor de texto, nem todas as funcionalidades dos editores de texto são conversiveis entre eles, e não parece ter uma regra do que vai acontecer com a funcionalidade quando você trocar. Exemplos:

  • Rich text to HTML/Markdown
    • A funcionalidade "read more" vai ser removido.
  • Rich text to Markdown
    • Texto em Lucille vai ficar invisível durante a edição porém ainda vai estar presente quando publicado.

Se você decidir voltar para o editor de texto anterior, você pode ter perdido algo da formatação ou pode ter se mantido.

Preview

Imagem ilustrativa do post entregando como ⭐ e saindo como 💩

Normalmente visualização prévia seria apenas para demonstrar como o seu trabalho irá ficar quando finalizado, sem alterar em nada o escrito...

Por algum motivo Tumblr decide alterar o seu trabalho durante a preparação da visualização, ou seja, grande chance de alterarem seu HTML ou Markdown. Nestes casos diga adeus a qualquer organização que tivesse sido usada, pois agora terá que lidar com o código da maneira que o Tumblr deixou.

Hide Request Referer

Pessoa se escondendo atrás de um muro

Quando você clica em um link de um site, o seu navegador irá fazer a requisição do conteúdo daquele link para depois exibir ele na sua tela. Neste caso é normal enviar para o link que você clicou de onde você está vindo, para que o site que vai te enviar o conteúdo saber de onde está vindo o tráfico.

Tumblr obrigatoriamente altera todos seus links que levem a sites exteriores, justamente para que consiga esconder este tipo de informação. Por exemplo:

Existem casos que você realmente não quer que o outro site saiba de onde está vindo o tráfico. Mas eu imagino que exista casos onde você quer que o outro lado saiba que você está levando tráfico a eles.

Minha configuração de privacidade no tumblr

Note como ambos "Hide thiagola92 from people without an account" e "Discourage searching of thiagola92" estão desligados mas mesmo assim eu recebo aquela proteção de privacidade.

Undo?

Pessoa confusa

Aviso logo que eu não consegui reproduzir esse problema que eu tive um dia qualquer, mas basicamente quando eu apertei Ctrl+z o post desfez tudo até a minha última inserção de imagem.

Como não conseguir reproduzir, pode ter sido um bug do dia...

Conclusion

Pessoa surpresa

No final das contas, todas essas chatisses me levaram a experimentar outras maneiras de compartilhar conteúdo. Ainda não estou completamente feliz mas está me satisfazendo.

Observação: Recentemente eu descobri que Tumblr tinha sido comprado pela mesma empresa do Wordpress então isso deve explicar o pouco carinho que o website tem recebido em questão de funcionalidade.