{warning.fa-warning} Aviso, esse texto foi escrito pensando em C#. Alguns dos métodos podem possuir nomes diferentes em rEduc/BlockEduc.
Como você já deve ter percebido pelo texto, as inúmeras mudanças na programação criam um novo paradigma completamente diferente de desenvolvimento no qual os usuários da plataforma estão acostumados e vêm realizando pelos últimos 2 anos. Isso óbviamente irá requerer alguma adaptação, e possivelmente muita "frustração" envolvida em aprender o novo método. Entretanto, isso pelo menos por enquanto nivelará todos, que estarão juntos aprendendo essa norma forma de lidar com o sBotics e se ajudando para alcançar novos patamares com os novos métodos.
A programação do sBotics agora se dá na mesma 'thread' do processador que o próprio simulador (melhor explicado abaixo). Isso causa algumas mudanças fundamentais na forma que sua programação é feita, principalmente em comparação com os anos anteriores. Mas para melhor entender essa diferença, é melhor entender como fazíamos anteriormente a execução do código do aluno.
Antes:
O código do usuário antigamente funcionava em uma Thread separada, como se fosse um processo separado no seu computador, um "outro programa". Por esse motivo, era possível antigamente realizar códigos como:
while(true)
{
bot.Move(200, 200);
}
Isso não era ideal por múltiplos motivos, o primeiro deles é que a plataforma que utilizamos para desenvolver, a Unity, não possui suporte a multiplas threads, já que sua implementação não é Thread-Safe. Isso gerava alguns problemas de rotinas não encerrando da forma correta, ou outros problemas relacionados, pelo próprio Unity não ter as ferramentas necessárias para isso.
Segundo que ao realizar esse código mencionado acima, o programa estará centenas de vezes por segundo executando o comando bot.Move(200, 200);
, deixando a thread em questão "ocupada" (busy waiting), o que de um ponto de vista pedagógico/desenvolvimento, não é ideal/interessante a se ensinar.
Outro problema é o fato do programa principal sBotics estar "dessincronizado" ao seu código (já que está em outra thread), fazendo com que um FPS mais baixo fizesse com que seu robô perdesse performance. Para mitigar isso, implementamos um sistema de "deturpação temporal", que deixava a thread principal do simulador em camera lenta para dar tempo de todos os comandos serem executados normalmente, mas é só uma "implementação paliativa" a o que é essencialmente um 'problema arquitetural' do simulador.
Agora:
Pelos motivos citados acima e outros, resolvemos realizar a implementação de forma 'síncrona'. Sendo assim, seu programa roda na "mesma camada" do próprio sBotics. Assim, seu código estará diretamente atrelado à performance do programa, porém os efeitos de um FPS baixo não serão mais um problema como antes.
O mesmo código mostrado acima, por exemplo, iria causar um "crash" no simulador, já que ele estaria deixando o mesmo esperando um "enquanto(verdadeiro)" acabar para prosseguir, ou seja, nunca.
Na robótica entretanto, nada pode ser 100% síncrono, comandos demoram a ser realizados na vida real. Então no lugar de Threads estamos utilizando um sistema de Tarefas (Tasks no C#), para que seja possível realizar o comando bot.Wait(int ms)
que tinhamos antigamente.
async Task Main()
{
IO.Print("Imprimi no console!");
await Time.Delay(2000);
IO.PrintLine("Dois segundos se passaram!");
}
O mesmo código que causa um crash no simulador agora pode ser escrito assim:
while(true)
{
IO.Print("Limpei o console! Note que o while true não quebra o simulador aqui por causa da espera, usada para carregar o resto do simulador");
await Time.Delay(100);
}
Dessa forma, o simulador poderá continuar sua execução normalmente nessa espera de 100 milissegundos.
Diferenças na prática:
A primeira diferença é que o Main()
deverá ser uma Task
assíncrona, para ser acessado pelo simulador. Para isso, utilizaremos as palavra-chaves async Task
, fazendo com que o main fique como na estrutura abaixo:
async Task Main()
{
// seus comandos aqui
}
Segunda diferença é que agora para esperar para prosseguir a execução do código, é necessário utilizar a palavra-chave await
. O await separará uma tarefa em segundo plano para a execução de algum comando, e retomará a execução normal ao fim dessa tarefa. Atualmente o simulador dispôe apenas de uma única tarefa padrão, a tarefa "Time.Delay(double ms)", que pausa a execução pelo tempo especificado, embora o Main() implementado pelo usuário também é uma tarefa e o mesmo pode criar múltiplas tarefas para serem executadas.
await Time.Delay(1000); // espera 1 segundo
Qualquer método que for criado que precisará utilizar o Delay (ou seja, será uma sequência de comandos ao robô que precisam esperar um certo tempo), necessitará ser uma tarefa assíncrona. Dessa forma, seu código pode até ser mais legível, separando métodos/funções que existem apenas para realizar simples cálculos daqueles que realizam rotinas inteiras, que vão precisar esperar e certamente terão um async
.
async Task Main()
{
await LerSensores();
}
async Task LerSensores()
{
IO.PrintLine($"Irei ler {ComponentsCount()} componentes!");
await Time.Delay(500);
// Outros comandos aqui
// ...
}
// Como a função abaixo não é uma subrotina, apenas realiza cálculos que podem funcionar de forma "sícrona", ela não precisa ser uma 'aync Task'
int ComponentsCount()
{
// Retorna a quantidade de componentes no robô
return Bot.Components.Length;
}
Como qualquer desenvolvedor sBotics C# deve ter percebido, no passado métodos que envolviam números possuiam tipagens numéricas diferentes: bot.Wait(int)
, bot.Move(double, double)
, ou bot.MoveFrontalAngles(float, double)
. Dessa forma estaremos padronizando toda a tipagem numérica para double
. Sabemos que não é o ideal em relação a uso de memória, já que o construtor de uma classe como Color não precisa de nada acima de um byte
, já que Vermelho, Verde e Azul não passam de 255 (e utilizar um double seria gastar poder de processamento removendo casas decimais e valores abaixo de 0 e 255). Entretanto, para facilitar o aprendizado e considerando que o rEduc só tem um tipo numérico - padronizamos tudo em double, fazendo com que o construtor da classe cor seja, por exemplo:
double valorMaximo = 255;
Color corExemplo = new Color(valorMaximo, valorMaximo, valorMaximo);