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.
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?
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.
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:
Incluir o shell do Mongo na minha interface gráfica
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.
Agora tudo que eu tinha que fazer é chamar o shell pelo Godot.
Para isto eu iria precisar usar o método OS.execute().
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.
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?
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:
Godot executa o shell, passando a query
Shell escreve X documentos em um arquivo
Godot solicita mais documentos
Note que você deve repetir etapa 2 e 3 até acabar os documentos ou Godot mandar parar.
Em outras palavras, eu preciso acoplar o código do usuário ao 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:
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.
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:
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.
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:
defcode(): # 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:
Análisar o código do usuário com AST
Inserir o conteúdo do módulo dentro de uma função pré montada
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.
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.
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.
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 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.
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.
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:
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.
(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:
Cada lado com uma cor.
Dois lados com a mesma cor.
Todos os lados com a mesma cor.
Dois lados adjacentes com a mesma cor.
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:
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.
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:
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.
É importante notar que não há problema criar variações do mesmo tile. Por exemplo:
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:
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:
Alguns destes tiles são variações dos anteriores porém rotacionados ou refletidos. Levando isto em conta, podemos minimizar para 6 tiles apenas:
É 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:
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:
Utilizando estes tiles com suas rotações/relexões, podemos criar em segundos o nosso labirinto:
note
Este labirinto está com cara de circuitos da placa mãe. 🤔
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...
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.
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.
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.
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.
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...
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.
Enquanto estava explorando as aplicações do Murena Cloud acabei parando para olhar o serviço de email deles. Nisso acabei descobrindo que eles utilizam a linguagem de programação Sieve para filtro de email.
O que me deixou a desejar que Gmail usasse pois deixaria fácil de automatizar algumas tarefas. Além disso seria interessante os servidores de email usarem a mesma linguagem para filtros de email.
Infelizmente a linguagem parece pouco popular e não acho que vá ficar tanto tempo viva... Mas me chamou tanta atenção que tentei fazer um pull request para adicionar ela no Learn X in Y minutes.
Meu plano sempre foi fazer algo simples, e originalmente a ideia seria pixel art com Aseprite, porém com o tempo eu percebi que mesmo coisas "simples" em pixel art me dariam trabalho 😆.
Infelizmente eu não lembro dos problemas que tive durante a utilização mas acredito que seja pelo fato de eu não conhecer boas práticas de arte para jogos em geral (e eu não tinha planos de me aventurar em detalhes sobre assunto).
Eu não queria entender como fazer para a arte não perde qualidade depois de alterar a escala, ou como a fazer animações boas, ... O foco para mim sempre foi aprender a utilizar bem Godot.
O interesse começou pelo fato de SVG conseguir escalar para diversos tamanhos sem perda de qualidade. Isto quer dizer que eu poderia fazer em um tamanho pequeno e escalar no jogo sem que ficasse esquisito.
Isso pode ser fácilmente demonstrado alterando duas propriedades da imagem SVG, que por sua vez é algo simples de fazer (abrir a imagem com um editor de texto e alterar).
Estas 3 imagems possuem 16 pixel para width e height.
<svg width="16" height="16" ... ></svg>
Agora alterando elas para respectivamente 64, 128 e 256:
Zero perda de qualidade e tudo que eu fiz foi alterar a propriedades dentro da imagem.
Eu tive muita aversão ao Inkscape no início e passei um bom tempo procurando por ferramenta alternativa. Não que Inkscape seja uma ferramenta ruim, mas ao me ver é ferramenta avançada que a muito tempo se tornou visualmente instável.
O visual de uma ferramenta precisa ser flexível a ponto de aprender o como certas features não estão sendo bem entregues a novos usuários.
Como ferramenta, seu objetivo é ajudar o usuário a alcançar o seu objetivo com o mínimo de esforço. Isto inclui o usuário não ter que buscar tutoriais ou videos na internet para utilizar a ferramenta.
Eu passei um dia inteiro curioso para saber o como funcionava contagem por referência no GDScript (não o conceito mas sim os detalhes tecnicos). Triste por ver que a documentação não conseguiu tirar as dúvidas que eu tinha e até pensei que só saberia mais testando ou olhando o código em C++.
Até que por acaso esbarrei em um comentário de um video que simplesmente explica muito bem.
I've been following along in the course, but this particular video contains a lot of misinformation about Godot's internals. I'll clarify below:
All variables defined in Godot's scripting API are wrapped in an object called a Variant. Each Variant is capable of storing different data types (int, string, bool, Object, Array, Dictionary, Color, Transform, etc.). You can see a list of the supported types TYPE_* enum values in the @GlobalScope class API documentation. This Variant class is why GDScript is able to change variable data types dynamically. Internally, every variable is a Variant. When you specify that a variable is typed and can only contain a single type, it is the GDScript language implementation, not Godot Engine, that blocks the type change.
Now, if a Variant B is assigned to another Variant A, then MOST of the time, the B's value is "copied by value", meaning that the direct value is copied from B to A. There is no reference counting of any kind. They are primitive values. In fact, reference counting primitive values is generally a waste of time and less performant than just copying them directly. This is because, for reference counting, you have to pass around a memory address in order to refer to the variable (because there is only one instance of the variable), and then getting the variable involves looking up the memory address. But for primitive numeric values such as int, float, or bool, passing the direct value takes just as much memory (less than 64 bits or 8 bytes) as passing a memory address. And if you pass a direct value, then the other Variant doesn't have to look up the value in the first place; they already have it.
There are only three data types in Godot 3.2.x and prior that "copy by reference", i.e. pass a memory address when assigning to a new variable: Object, Array, and Dictionary. EVERYTHING else will copy by value. In Godot 4.0 and beyond, the various Pool*Array classes, such as PoolIntArray, PoolStringArray, etc., will also be updated to copy by reference. With "copy by reference" data types, if you create one of those values and assign it to two variables, then modifying either one of the variables' values will modify both variables' values, since they both refer to the same memory address.
var x = [1, 2, 3] var y = x x.push_back(4) # y is now also [1,2,3,4]
As for reference counting, that is ONLY supported in Object classes that extend the Reference class. The top of the class hierarchy looks a bit like this...
Object
Node
CanvasItem
Node2D
Control
Spatial
Reference
Resource
So, Object has only direct new/free methods for allocating and deleting memory. Node and its subtypes can allocate memory, but 1) they can .free() immediately just like Objects or .queue_free() to schedule their deletion till the next frame (where you can more safely delete a large group of nodes at once), and 2) if you delete a Node, it also deletes all of that Node's children, so an entire tree of nodes can be deleted just by deleting the root node of that tree. With Reference, you never delete it directly. It just auto-deletes when you stop having any references to it due to the fact that it actually DOES do reference-counting.
var ref = Reference.new() ref = null # the Reference object has now been freed
Resources behave just like References, except in their case, they can track their reference by their filepath as well. That is, if you do load("res://my_file.gd"), then you may end up loading a cached instance rather than allocating an entirely new object. If the engine's internal ResourceCache finds that the resource has already been loaded, then the load() function will just return the memory address of the existing object rather than creating a new one.
Also note that the practice of creating an object pool for nodes and the like can be useful, but for different reasons than in other engines. This topic is quite advanced for people who may be learning programming for the first time, but: in stuff like C# (Unity), memory is "garbage collected", i.e. the program tracks memory for you and auto-deletes it on your behalf. This can lead to random pauses in a game if the garbage collector suddenly starts up and interrupts gameplay to clean up memory. Object pooling, i.e. creating a group of objects and then just cycling through them rather than deleting and creating constantly, was a strategy to re-use existing memory for objects so as to stop the garbage collector from needing to run in the first place. But Godot does memory allocation manually and in small increments. It is designed in such a way that constantly creating and deleting objects doesn't lead to long-term memory fragmentation, i.e. it doesn't mess up your computer. You can read about this in the "development" section of the official documentation. Anyway, object pooling IS useful if, for performance reasons, you don't want to waste TIME deleting and creating large objects, but you won't need to worry about stuttering or memory issues, so most of the time, it's perfectly fine in Godot to just create and delete objects as you need them.
*Algumas adaptações foram feitas para melhorar aleitura (adicionar nova linha, transformar em bullet points, etc) mas nenhuma alteração em palavras ou frases foram feitas.