Essa semana o progresso foi um pouco mais lento, mas isso faz parte do processo. Tenho estado com o tempo bem restrito e parece que vai continuar assim por mais algumas semanas. Mas não se preocupem, o desenvolvimento continua firme e forte! 💪
🔄 Ao invés de trabalhar com renderização 2D, decidi investir em alguns testes de renderização 3D. Os benefícios a longo prazo são enormes para o desenvolvimento do projeto, e estou empolgado com as possibilidades!
Além disso, voltei a desenvolver no Godot. 🌱 Durante essa jornada, experimentei várias engines como Unity, Godot e Stride (Xenko). Apesar de todas terem suas vantagens, percebi que ao usar Mono, estava mais focado em criar ferramentas para facilitar o desenvolvimento do que realmente focando no jogo em si. Por isso, estou retornando ao Godot, que me permite concentrar mais na criação do jogo que quero construir. 🎮
O caminho é longo, mas estamos dando um passo de cada vez.
Mono é um respiro novo pra algo antigo :sweat_smile:
Depois que fazer um refactor das coisas que tinha (desde maio de 2020) e enviar pro mono
achei até que seria complicado fazer toda a alteração, mas não… como eu já tinha separado o Leben.Core
a transição foi bem simples.
a única dificuldade foi re-lembrar como fazer Draw no mono, como não é uma Engine tive que começar do zero algumas coisas.
no dia 39
só foi o refactor, fazer draw de object, alguns itens de interface,
viewport draw, um relógio pra esboçar um comportamento, estações e recursos, provávelmente teve outras coisas, mas eu acabei esquecendo.
hoje foi implementar as animações, caching de texturas e como eu quero que exista uma possibilidade de modificar o jogo nas texturas também.
eu simplifiquei o jogo para carregar PNGs, não é o ideal em alguns dispositivos haverá perda de performance, mas eu penso que por enquanto não é um problema.
também implementei um sprite tipo atlas que tinha no godot (eu gostei disso, daquele engine)
consigo configurar todas texturas num arquivo Yaml:
a melhor parte nisso é a customização externa, ainda vou deixar uma forma de apertar um botão e as texturas serem carregadas de novo, assim posso simplesmente ir montando com placeholders e atualizando conforme vou achando que funciona.
o jeito que implementei é muito tranquilo ir de um objeto sem animação alguma pra algo que se move.
então posso deixar tudo com um única célula e assim que for conveniente apenas mudo o png e atualizo o yaml
fiquei vários dias olhando pra tela sem vontade de escrever nada, fazer o desenho do mapa, estava me deixando maluco, o perlin não estava funcionando como eu queria, até que joguei Swag and Sorcery, acabei me lembrando de como eu gosto de 2D side-scrolling
então vou me forçar simplificar mais o Leben pra algum dia terminar ele :sweat_smile:
O que fiz de novo?
estou começando desenhar alguns tilesets, havia falado que ia usar Krita, mas acabei preferindo aseprite, comprei no steam mesmo, se não me engano usei o grátis e me apaixonei.
nada muito completo, apenas o background e o chão, vou fazer o render do céu de outra forma pra facilitar transição de dia/noite, mas por enquanto, é o suficiente.
ainda tem muito trabalho, mas eu estou gostando:
estive sincronizando com a cloud os assets e os códigos que estive fazendo.
vou montar agora algumas construções e começar “brincar” com o jogo
ainda há muito trabalho, aproveitei minhas férias pra descansar e agora que estou um pouco menos estressado e consegui um cadin de energia
acabei voltando pro Leben.
O que fiz de novo?
acabei implementando o backbone de estruturas no mapa, algo simples onde toda entidade do mundo tenha algum tipo de input e output, por enquanto pensei pequeno com um poço, como havia citado no Leben – 25-2, eu estou implementando tudo via BDD, quero continuar neste formato
ainda não tem tantos detalhes, primeiro estou criando um poço, então indicando que há um ator interagindo com o poço e forço a conclusão da tarefa do ator, então uma referência de “balde de água” é adicionada no inventário do poço.
mas está funcional
eu gosto muito de usar o BDDFy
somente por isso:
futuramente caso necessário eu terei uma comportamento esperado em vários pontos do jogo, evitando que eu quebre :sweat_smile: alguma coisa.
O resultado é que agora todos os pontos que eu separei nos biomas (desert, mountain, deepforest, forest, beach, nearwater, deepSea, nearwater, beach) estavam quebrados, a única mudança foi que troquei de PC, provávelmente.
mas não tem problema, eu já queria criar um sistema separado pra gerar mapas e poder “pintar” um mapa.
na minha cabeça irei usar a técnica de geração de mapa procedural para fazer maior parte do trabalho, indicando biomas, setando onde é possível ter determinado tipo de recurso e tal, depois dele gerar o mapa eu dô os “tapas” para fazer a finalização.
Em breve mostro uma ferramenta que acabei dazendo para visualizar o mapa de maneira rápida.
Calma, leia até o final :sweat_smile: juro que fará sentido o título, bom… pra mim fez…..
Não sei o quanto vocês conhecem de Async e a famosa maquina de estado no contexto de C#
particularmente eu já tive vários problemas e cheguei odiar porque sempre tive problema e sempre culpei o compilador….
bom, depois de criar vergonha na cara e meter a fuça em como as coisas funcionam aprendi um pouquinho, o suficiente pra não cair em algumas armadilhas.
tenho alguns Links de referência como meu book of reference, tudo que achei útil quando tive problemas eu acabei adicionando nele pra estudar aos poucos.
A importância de saber sobre async e sua máquina de estado.
Recentemente fui convidado pra fazer um Code Review em um código, particularmente devido o projeto que toco é complicado fazer code-review e quando acontece é algo bem pontual onde preciso separar na agenda.
e encontrei alguns itens que me chamaram atenção, foi removido paralelismo, remoção de async e foi adicionado um tal de GC.Collect().
Não me entenda mal, mas se você faz isso, talvez seja importante ler o livro: Pro .Net Memory Management: For Better Code, Performance, and Scalability by Kokosa ele dá um background ótimo sobre com funciona GC e convenhamos… A linguagem já tem mais de 20 anos, com engenheiros super competentes que pensaram durante anos da forma mais eficiênte deste processo, vamos ter humildade :sweat_smile:, existem pouquíssimos casos que você realmente precisa fazer isso, pelo que me recordo em debugging ou fazendo benchmark, com excessão desses pontos, você vai apenas piorar a heurística e se ele está sendo o problema… novamente, leia o livro do Kokosa e comece usar o Stack.
bom pulando pra parte que eu queria falar :sleeping:
Async at last!
não sei se vocês tem o costume de ler o que o código vai gerar ao escrever o código, não é sempre que você precisa disso, na verdade você ignora essa parte porque são poucos cenários que você precisará entender o que acontece por trás do código gerado ou IL pra determinar uma nova forma de escrever código
um código simples :
using System;
using System.Threading.Tasks;
public class TestSubject {
public void DoSomething() {
}
public async Task DoSomethingAsync() {
}
}
e o compilador irá traduzir isso para:
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
public class TestSubject
{
[CompilerGenerated]
private sealed class <DoSomethingAsync>d__1 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
public TestSubject <>4__this;
private void MoveNext()
{
int num = <>1__state;
try
{
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult();
}
void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}
public void DoSomething()
{
}
[AsyncStateMachine(typeof(<DoSomethingAsync>d__1))]
[DebuggerStepThrough]
public Task DoSomethingAsync()
{
<DoSomethingAsync>d__1 stateMachine = new <DoSomethingAsync>d__1();
stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
stateMachine.<>4__this = this;
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
}
é possível perceber que toda vez que você deixa uma assinatura de “async” no método ele gera uma máquina de estado, é até um dos motivos que eu tento não criar nested async, afinal invariávelmente será alocado em memória, então em cenários críticos eu penso bem… Continuando….
Precisava dar esse contexto caso alguém não tivesse ainda a oportunidade de saber o que acontece por baixo dos panos, então foi um resumo pra continuar
em minha memória do código de algum tempo atrás era o seguinte:
public async Task SomeMainTask(){
var id = 0;
var processoUm = Task.Run(()=>{ id = DoSomething();});
var processoDois = DoOtherThing();
await processoUm;
(... outros processos sincronos e assíncronos)
var processoDois = DoOtherThing(id);
Task.WaitAll(
processoDois,
(...)
);
}
public async Task<id> DoSomething(){
(... alguma chamada pro banco)
///código apenas pra contexto
return Task.FromResult(id);
}
e quando eu li o código:
public void SomeMainTask(){
var id = 0;
var processoUm = DoSomething();
var processoDois = DoOtherThing();
(... outros processos sincronos e assíncronos)
var processoDois = DoOtherThing(id);
}
public id DoSomething(){
(... alguma chamada pro banco)
///código apenas pra contexto
return id;
}
eu fiquei meio intrigado, eu queria entender a motivação… particularmente eu faço tudo sincrono mesmo e eventualmente quando estou fazendo meu próprio code-review eu penso se faz ou não sentido colocar um async.
quando questionei, a resposta foi que async e sincrono seriam as mesmas coisas no final das contas ou seja :
isso
var id = DoSomething();
public int DoSomething(){
(...chamada pro banco)
return someid;
}
é igual isso:
var id = await DoSomething();
public Task<int> DoSomething(){
(...chamada pro banco)
using(var someconn = new SqlConnection(connectionstring)){
return someconn.FirstOrDefaultAsync<int>("select 1");
}
}
será?
tentei explicar que não era… afinal na própria documentação da microsoft (inclusive… que maravilha de doc ) explica exatamente o que eu disse. Apesar do meu esforço de mostrar que ele vai bloquear uma thread até ter resposta…
achei mais simples criar um código muito simples pra apresentar o problema:
Apenas pra dar contexto, eu sou um usuário assíduo do LinqPad, logo quando estiver escrito “someText”.Dump(); no código abaixo, entenda como Console.WriteLine(“sometext”);
async System.Threading.Tasks.Task Main()
{
int nWorkers;
int nCompletions;
///aqui estou forçando que apenas exista uma única Thread no meu sistema pra apresentar um cenário que dá pra observar o comportamento
ThreadPool.SetMinThreads(1, 1).Dump();
ThreadPool.SetMaxThreads(1, 1).Dump();
/// apenas me assegurando que é apenas uma thread..... vai que...
ThreadPool.GetMaxThreads(out nWorkers, out nCompletions);
"workers: {nWorkers}".Dump();"nCompletions: {nWorkers}".Dump();
///estou deixando uma thread executando sem parar, com um intervalo de 2 segundos, como se fosse um heart-beat.
System.Threading.Tasks.Task.Run(async ()=>{
while(true){
"Heartbeat".Dump();
await System.Threading.Tasks.Task.Delay(2000);
}
});
for(var i = 0; i<=4; i++){
/// aqui estou criando algumas Tasks assíncronas para executar alguma ação representando um insert no banco.
await System.Threading.Tasks.Task.Run(async ()=>{
await System.Threading.Tasks.Task.Delay(5000);"{Thread.CurrentThread.ManagedThreadId}-{DateTime.Now.ToString("HH:MM:ss")}".Dump();
});
}
"-----------------------Async Done-----------------------".Dump();
for(var i = 0; i<=4; i++){
/// aqui estou criando as mesmas Tasks mas desta vez estou forçando a execução síncrona para executar a mesma ação no banco.
System.Threading.Tasks.Task.Run(()=>{
System.Threading.Tasks.Task.Delay(5000).Wait();
$"{i} - {Thread.CurrentThread.ManagedThreadId}-{DateTime.Now.ToString("HH:MM:ss")}".Dump();
});
}
}
O que você acha que aconteceu?
eu tive que gravar um vídeo…:sweat_smile:
como esperado a thread que está resposável pelo heart-beat continua a execução, é liberada e então a task Async de “persistência no banco” consegue trabalhar, termina e assim o fluxo continua..
agora quando chega no síncrono… nenhum funciona, infelizmente não consegui compreender o motivo, a thread acabou ficando em lock no Task.Delay, mas isso eu não vou tentar entender hoje.
mas espero que esse post traga a luz para alguém que assim como eu também já sofreu com async.
p.s. eu sei que acontecer um thread starvation em alguns cenários é difícil dependendo do seu cenário, mas no cenário que eu verifiquei isso pode ser a diferênça entre aumentar a capacidade do servidor pra dar vazão no processamento, sendo que o custo pode ser evitado com um uso inteligênte dos recursos.
p.s² Pegadinha! eu não falei sobre paralelismo ainda :laughing:, mas o que eu falaria não escapa da gloriosa doc da mãecrosoft:
Estou sem desenvolver no Leben, dá um pouco de preguiça e existe muitos projetos que quero fazer e ainda preciso me distrair pra manter a sanidade.
estive jogando Raft, mindustry, this war of mine e autonauts.
Mas principalmente estou dando suporte á minha ferramenta para gerenciar mods pro jogo Kenshi
Conforme ela está crescendo percebo como é legal ajudar outras pessoas gerenciar seus mods e melhorar a experiência de um jogo tão legal, por isso vou continuar melhorando ela.
hoje recebi um comentário no meu youtube de um mod manager que eu havia feito, era uma pessoa com dificuldade, então enquanto estava respondendo ele de como fazer funcionar e percebi que havia outro vídeo explicando como minha ferramenta funcionava, fiquei bem feliz e orgulhoso.
Então parei um pouco pra dar um update no Leben, só pre adicionar alguns recursos no mod manager.
ah! o manager é pro jogo Kenshi, por enquanto parei um pouco de jogar, mas é muito bom, um dos jogos que mais joguei no Steam.
Ah! finalmente internet normal, ainda ruim, porém é pelo que pago, então é o “justo”.
mas não tô afim de trabalhar no Leben, me empolguei com gnomoria e vou ficar alguns dias jogando isso, por enquanto.
Ah! me peguei olhando blogs de algumas pessoas, existem bons escritores de poemas e artigos por aqui, fico feliz por isso, apesar de nunca ter procurado esse tipo de conteúdo, hoje me parece um ótimo passatempo aside podcast 😀
ainda vou fazer um link á um blog que me fez pensar, por algumas horas, na verdade, me pego pensando nele ainda.
só tenho que me organizar para escrever, sobre o assunto, não quero fazer um link com uma péssima apresentação, sendo que o conteúdo foi tão… singular 😛
Na minha casa estou com uma internet que eu pago caro e é um péssimo serviço pra não falar que é uma merda 😡 , enfim é tenso, monopólio da vivo aqui em minha região e outras operadoras não aparecem (triste pra mim)
mas enfim, já que a internet não ajuda e estou cansado demais pra ir ao Leben, estou aqui ouvindo a cantora que sou fã desde 2003 ou 2004, é a pessoa que nasceu no mesmo dia que minha mãe, muita coincidência, é ela, Utada Hikaru, que está em Hiato, mas ainda tenho um grande amor por ela S2 😛
pelo menos existem outros cantores que chegam perto do estilo de música que eu gosto, como a Yui Yoshioka, IU e Miwa.
Enquanto minha internet está ruim não vou poder ver algumas coisas no meu pc, e enquanto estiver com cansaço será difícil mexer no Leben, pelo menos vem ai o feriado, vou usar algum dia pra compensar.
vou jogar Gnomoria, acho que eu disse que tinha desistido dele, mas é o único jogo mais próximo (hoje) do que eu gostaria de jogar (chuckle)