-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathweaver_api.tex
1963 lines (1658 loc) · 61.4 KB
/
weaver_api.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\font\sixteen=cmbx16
\font\twelve=cmr12
\font\fonteautor=cmbx12
\font\fonteemail=cmtt10
\font\twelvenegit=cmbxti12
\font\twelvebold=cmbx12
\font\trezebold=cmbx13
\font\twelveit=cmsl12
\font\monodoze=cmtt12
\font\it=cmti12
\voffset=0,959994cm % 3,5cm de margem superior e 2,5cm inferior
\parskip=6pt
\def\titulo#1{{\noindent\sixteen\hbox to\hsize{\hfill#1\hfill}}}
\def\autor#1{{\noindent\fonteautor\hbox to\hsize{\hfill#1\hfill}}}
\def\email#1{{\noindent\fonteemail\hbox to\hsize{\hfill#1\hfill}}}
\def\negrito#1{{\twelvebold#1}}
\def\italico#1{{\twelveit#1}}
\def\monoespaco#1{{\monodoze#1}}
\def\iniciocodigo{\lineskip=0pt\parskip=0pt}
\def\fimcodigo{\twelve\parskip=0pt plus 1pt\lineskip=1pt}
\long\def\abstract#1{\parshape 10 0.8cm 13.4cm 0.8cm 13.4cm
0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm
0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm
\noindent{{\twelvenegit Abstract: }\twelveit #1}}
\def\resumo#1{\parshape 10 0.8cm 13.4cm 0.8cm 13.4cm
0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm
0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm
\noindent{{\twelvenegit Resumo: }\twelveit #1}}
\def\secao#1{\vskip12pt\noindent{\trezebold#1}\parshape 1 0cm 15cm}
\def\subsecao#1{\vskip12pt\noindent{\twelvebold#1}}
\def\referencia#1{\vskip6pt\parshape 5 0cm 15cm 0.5cm 14.5cm 0.5cm 14.5cm
0.5cm 14.5cm 0.5cm 14.5cm {\twelve\noindent#1}}
%@* .
\twelve
\vskip12pt
\titulo{A API Weaver}
\vskip12pt
\autor{Thiago Leucz Astrizi}
\vskip6pt
\email{thiago@@bitbitbit.com.br}
\vskip6pt
\abstract{This article describes using literary programming the
Weaver API. Weaver is a game engine and this API are how programmers
interact with the engine in their game projects. Besides the API,
this article also covers how the configuration file is interpreted
and how game loops should be managed in a game project. The API is
portable code which should work under OpenBSD, Linux, Windows and
Web Assembly environments.}
\vskip 0.5cm plus 3pt minus 3pt
\resumo{Este artigo utiliza programação literária para descrever a
API Weaver. Weaver é um motor de jogos e esta API é como os
programadores interagem com ela em seus projetos. Além da API, este
artigo também cobre como o arquivo de configuração é interpretado e
como os laços de execução do jogo devem ser organizados. A API é
código portável que deve funcionar sob ambientes OpenBSD, Linux,
Windows e Web Assembly.}
\secao{1. Introdução}
\subsecao{1.1. Organização de Arquivos}
Quando um usuário digita \monoespaco{weaver PROJETO} na linha de
comando, um diretório com um novo projeto Weaver e criado. Dentro
deste diretório, o arquivo que contém o loop principal está
em \monoespaco{src/game.c} e nele encontramos:
\alinhaverbatim
#include "game.h"
void main_loop(void){ // Um laço principal do jogo
LOOP_INIT: // Código de inicialização
LOOP_BODY: // Código executado a cada iteração
if(W.keyboard[W_ANY])
Wexit_loop();
LOOP_END: // Código executado na finalização
return;
}
int main(void){
Winit(); // Initializes Weaver
Wloop(main_loop); // Enter a new game loop
return 0;
}
\alinhanormal
E dentro de \monoespaco{src/game.h}, nós encontramos:
\linha
\alinhaverbatim
#ifndef _game_h_
#define _game_h_
#include "weaver/weaver.h"
#include "includes.h"
struct _game_struct{
// Você pode personalizar esta estrutura declarando variáveis aqui.
// Mas não mude seu nome. E acesse ela por meioda variável W.game
int whatever; // <- Variável só pra prevenir erro em certo compilador
} _game;
void main_loop(void);
#endif
\alinhanormal
\linha
Notar que neste arquivo existe uma estrutura que pode ser
personalizada pelo usuário e cumpre o papel de centralizar algumas
variáveis com estados globais. O tipo de coisa que para um jogo deve
ser preservada ao salvarmos o progresso de um jogador. Ou que deve ser
acessível para plugins. Ou ainda que deve ser transmitida para
informar o estado do jogo a clientes conectados à rede. Segundo o
comentário acima, esta estrutura deve ser referenciada pela
variável \monoespaco{W.game}, o que já nos indica que a API será
organizada de modo a fornecer um \monoespaco{struct}
chamado \monoespaco{W} onde serão centralizados os recursos da API.
O arquivo \monoespaco{includes.h} é apenas um cabeçalho que inclui em
um projeto todos os cabeçalho de módulos criados pelo usuário (cada
módulo é um arquivo de código C e um cabeçalho).
Todo o código da API deve então estar presente ou ser incluída por
macros dentro dos arquivos \monoespaco{weaver.c}
e \monoespaco{weaver.h}. A organização do \monoespaco{weaver.h} é:
\iniciocodigo
@(project/src/weaver/weaver.h@>=
#ifndef _weaver_h_
#define _weaver_h_
#ifdef __cplusplus
extern "C" {
#endif
@<Inclui Cabeçalhos (weaver.h)@>
@<Estruturas de Dados (weaver.h)@>
@<Declaração de Função (weaver.h)@>
@<Variáveis Externas (weaver.h)@>
@<Macros (weaver.h)@>
#ifdef __cplusplus
}
#endif
#endif
@
\fimcodigo
A parte em vermelho no código acima representa código que ainda será
definido. Por exemplo, na região reservada para incluirmos cabeçalhos
gerais, será importante incluirmos o
cabeçalho \monoespaco{sys/param.h} em alguns casos. No BSD isso é
essencial para podermos checar por meio da macro se estamos sendo
compilados para um sistema BSD. No Linux inserir tal cabeçalho não tem
problema algum. Mas o Windows não possui tal cabeçalho. Por outro
lado, no caso do Windows certamente iremos quierer inserir
o \monoespaco{windows.h}. Então podemos inserir condicionalmente
cabeçalhos se estamos ou não no Windows por meio do código abaixo:
\iniciocodigo
@<Inclui Cabeçalhos (weaver.h)@>=
#if !defined(_WIN32)
#include <sys/param.h>
#else
#include <windows.h>
#endif
@
\fimcodigo
Mais tarde, podemos querer inserir mais cabeçalhos à medida que eles
forem necessários. Por exemplo, o cabeçalho de entrada e saída padrão,
e da biblioteca padrão. Já o \monoespaco{stdint.h} também é útil para
quando queremos definir inteiros com um tamanho fixo de bits enquanto
o \monoespaco{stdbool.h} nos permite declarar e usar variáveis
booleanas:
\iniciocodigo
@<Inclui Cabeçalhos (weaver.h)@>+=
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
@
\fimcodigo
Também iremos definir aqui a estrutura geral do
arquivo \monoespaco{weaver.c} de nossa API:
\iniciocodigo
@(project/src/weaver/weaver.c@>=
#include "weaver.h"
#include "../game.h"
@<Cabeçalhos Locais (weaver.c)@>
@<Macros Locais (weaver.c)@>
@<Variáveis Globais (weaver.c)@>
@<Variáveis Estáticas (weaver.c)@>
@<Definição de Função (weaver.c)@>
@<Definição de Funções da API (weaver.c)@>
@
\fimcodigo
Como exemplo de macros locais, vamos definir macros que serão formas
portáveis de declarar e usar mutex, independente do sistema
operacional:
\iniciocodigo
@<Macros Locais (weaver.c)@>=
#if defined(__linux__) || defined(BSD)
#define STATIC_MUTEX_DECLARATION(mutex) static pthread_mutex_t mutex
#define MUTEX_INIT(mutex) pthread_mutex_init(mutex, NULL)
#define MUTEX_DESTROY(mutex) pthread_mutex_destroy(mutex);
#define MUTEX_WAIT(mutex) pthread_mutex_lock(mutex);
#define MUTEX_SIGNAL(mutex) pthread_mutex_unlock(mutex);
#elif defined(_WIN32)
#define STATIC_MUTEX_DECLARATION(mutex) static CRITICAL_SECTION mutex
#define MUTEX_INIT(mutex) InitializeCriticalSection(mutex)
#define MUTEX_DESTROY(mutex) DeleteCriticalSection(mutex);
#define MUTEX_WAIT(mutex) EnterCriticalSection(mutex);
#define MUTEX_SIGNAL(mutex) LeaveCriticalSection(mutex);
#elif defined(__EMSCRIPTEN__)
#define STATIC_MUTEX_DECLARATION(mutex)
#define MUTEX_INIT(mutex)
#define MUTEX_DESTROY(mutex)
#define MUTEX_WAIT(mutex)
#define MUTEX_SIGNAL(mutex)
#endif
@
\fimcodigo
Ao longo deste documento iremos expandir todas as outras partes em
vermelho apresentando o código que será inserido nelas. E assim a
nossa API será definida.
\subsecao{1.2. A Estrutura W}
A principal estrutura de dados que definiremos será
um \monoespaco{struct} chamado \monoespaco{W}. Esta estrutura
basicamente será um espaço de nomes (``namespace'') onde iremos
colocar as variáveis e funções oficiais de nossa API. Podemos então
começar a definir tal estrutura:
\iniciocodigo
@<Estruturas de Dados (weaver.h)@>=
// Esta estrutura conterá todas as variáveis e funções definidas pela
// API Weaver:
extern struct _weaver_struct{
struct _game_struct *game;
@<Namespace de Variáveis@>
@<Namespace de Funções@>
} W;
@
\fimcodigo
Notar que além de \monoespaco{W.game}, existirão outras variáveis
presentes dentro desta estrutura. Basicamente iremos centralizar
dentro dela todas as funções públicas da nossa API. Só não estarão
nela funções que começam com ``\_'', e que são consideradas internas e
não deveriam ser usadas por programadores utilizando a API. Desta
forma deixamos bem delimitado o que faz parte da API e também evitamos
poluir com nomes o ``namespace'' global de programas em C.
Colocaremos então esse nosso namespace como uma variável global:
\iniciocodigo
@<Variáveis Globais (weaver.c)@>=
struct _weaver_struct W;
@
\fimcodigo
\subsecao{1.3. Funções de Inicialização e Finalização}
Uma das coisas que a função de inicialização faz é inicializar os
valores da estrutura \monoespaco{W}. Justamente por isso, ela é uma
das poucas funções externas ao nosso namespace:
\iniciocodigo
@<Declaração de Função (weaver.h)@>=
void Winit(void);
@
\fimcodigo
\iniciocodigo
@<Definição de Funções da API (weaver.c)@>=
void Winit(void){
W.game = &_game;
@<API Weaver: Inicialização@>
}
@
\fimcodigo
A função de finalização deve desalocar qualquer memória pendente,
finalizar o uso de recursos, e deve também fechar o programa
informando que tudo correu bem se assim realmente ocorreu. Notar que a
memória deve ser a última coisa finalizada.
\iniciocodigo
@<Declaração de Função (weaver.h)@>=
void Wexit(void);
@
\fimcodigo
\iniciocodigo
@<Definição de Funções da API (weaver.c)@>=
void Wexit(void){
@<API Weaver: Finalização@>
@<API Weaver: Memória: Finalização@>
exit(0);
}
@
\fimcodigo
\secao{2. Contagem de Tempo}
Weaver mede o tempo em microssegundos ($10^{-6}$s) e armazena a sua
contagem de tempo em pelo menos 64 bits de memória. Weaver serve para
criar programas que executam dentro de um laço principal. Então além
do tempo total em microssegundos desde que o programa inicializou,
também armazenamos a diferença de tempo entre a iteração atual do
programa e a última.
Então para começar devemos ter um lugar onde devemos armazenar a
última medida de tempo que fizemos. Usaremos para isso uma variável
global. No Windows usamos um dos tipos específicos para representar
inteiros grandes (e incluimos o cabeçalho necessário para usá-lo)
e nos demais sistemas usamos uma estrutura de valor de tempo de
alta resolução.
Nossa variável global é declarada aqui:
\iniciocodigo
@<Variáveis Externas (weaver.h)@>=
#if defined(_WIN32)
extern LARGE_INTEGER _last_time;
#else
extern struct timeval _last_time;
#endif
@
\fimcodigo
E aqui:
\iniciocodigo
@<Variáveis Globais (weaver.c)@>=
#if defined(_WIN32)
LARGE_INTEGER _last_time;
#else
struct timeval _last_time;
#endif
@
\fimcodigo
Já o cabeçalho para usá-la pode ser colocado aqui:
\iniciocodigo
@<Inclui Cabeçalhos (weaver.h)@>=
#if !defined(_WIN32)
#include <sys/time.h>
#endif
@
\fimcodigo
A ideia é que esta variável armazene sempre a última medida de
tempo. Ela é inicializada com a primeira medida de tempo na
inicialização:
\iniciocodigo
@<API Weaver: Inicialização@>=
#if defined(_WIN32)
QueryPerformanceCounter(&_last_time);
#else
gettimeofday(&_last_time, NULL);
#endif
@
\fimcodigo
Após a inicialização, todas as outras atualizações desta variável
deverão ser feitas por meio da função declarada abaixo:
\iniciocodigo
@<Declaração de Função (weaver.h)@>+=
unsigned long _update_time(void);
@
\fimcodigo
Tal função irá ler o tempo atual e armazenar na variável. Ela irá
sempre retornar a diferença de tempo em microssegundos entre a leitura
atual e a última. Em sistemas Unix faremos isso exatamente da maneira
recomendada pelo manual da GNU Glbc de modo a tornar a subtração de
tempo mais portável e funcional mesmo que os elementos da
estrutura \monoespaco{timeval} sejam armazenadas como ``unsigned''. A
desvantagem é que o código se torna menos claro. O código fica sendo:
\iniciocodigo
@<Definição de Função (weaver.c)@>=
#if !defined(_WIN32)
unsigned long _update_time(void){
int nsec;
unsigned long result;
struct timeval _current_time;
gettimeofday(&_current_time, NULL);
// Aqui temos algo equivalente ao "vai um" do algoritmo da subtração:
if(_current_time.tv_usec < _last_time.tv_usec){
nsec = (_last_time.tv_usec - _current_time.tv_usec) / 1000000 + 1;
_last_time.tv_usec -= 1000000 * nsec;
_last_time.tv_sec += nsec;
}
if(_current_time.tv_usec - _last_time.tv_usec > 1000000){
nsec = (_current_time.tv_usec - _last_time.tv_usec) / 1000000;
_last_time.tv_usec += 1000000 * nsec;
_last_time.tv_sec -= nsec;
}
if(_current_time.tv_sec < _last_time.tv_sec){
// Overflow
result = (_current_time.tv_sec - _last_time.tv_sec) * (-1000000);
// Sempre positivo:
result += (_current_time.tv_usec - _last_time.tv_usec);
}
else{
result = (_current_time.tv_sec - _last_time.tv_sec) * 1000000;
result += (_current_time.tv_usec - _last_time.tv_usec);
}
_last_time.tv_sec = _current_time.tv_sec;
_last_time.tv_usec = _current_time.tv_usec;
return result;
}
#endif
@
\fimcodigo
Em sistema Windows, já existe uma função que trata o tempo como sendo
em microssegundos. Por causa disso, a função se torna muito mais
simples:
\iniciocodigo
@<Definição de Função (weaver.c)@>+=
#if defined(_WIN32)
unsigned long _update_time(void){
LARGE_INTEGER prev;
prev.QuadPart = _last_time.QuadPart;
QueryPerformanceCounter(&_last_time);
return (_last_time.QuadPart - prev.QuadPart);
}
#endif
@
\fimcodigo
\secao{3. Os loops principais.}
Todos os jogos são organizados dentro de loops, ou laços
principais. Eles são basicamente um código que fica iterando
indefinidamente até que uma condição nos leve a outro loop principal,
ou então ao fim do programa.
Como foi mostrado no código inicial do \monoespaco{game.c}, um loop
principal deve ser declarado como:
\alinhaverbatim
void nome\_do\_loop(void){
LOOP\_INIT: // Código executado na inicialização
LOOP\_BODY: // Código executado a cada iteração
if(W.keyboard[W\_ANY])
Wexit\_loop();
LOOP\_END: // Código executado na finalização
return;
}
\alinhanormal
Antes de entendermos como devemos entrar em um loop principal
corretamente, é importante descrever como este loop é
executado. Nota-se que ele possui uma região de inicialização, de
execução e finalização demarcada por rótulos escritos em letras
maiúsculas.
Interpretar isso é bastante simples. É perfeitamente possível
interpretar o código acima como:
\alinhaverbatim
void nome\_do\_loop(void){
// LOOP\_INIT
for(;;){
// LOOP\_BODY
if(W.keyboard[W\_ANY])
Wexit\_loop();
}
// LOOP\_END
}
\alinhanormal
Mas embora esta interpretação seja suficientemente adequada em certos
contextos, não é assim que este código será traduzido. Não é em todos
os ambientes em que é possível executar um loop infinito sem fazer com
que a interface do jogo trave. Um exemplo é o ambiente Web Assembly de
um navegador de Internet, onde os laços principais de um programa só
podem ser executados se forem corretamente declarados como tais e a
execução deles ocorre não por meio de um laço infinito, mas pela
contínua chamada de uma função de laço principal.
Por causa disso, para tornar o código mais portável devemos encarar
toda execução de um loop principal como no código abaixo:
\alinhaverbatim
for(;;)
nome\_do\_loop();
\alinhanormal
E dentro da função de loop principal nós não colocamos um laço
explícito. Ao invés disso, nós decidimos qual parte da função deve ser
executada com ajuda dos rótulos inseridos, os quais na verdade são
macros com lógica adicional embutida junto a alguns
comandos \monoespaco{goto} que decidem o que será executado a cada vez
que a função é chamada.
Uma consequência disso é que não é possível lidar com variáveis
declaradas dentro da inicialização de um loop principal. Elas ,só
teriam um valor correto dentro da inicialização, e dentro do corpo do
loop principal seu valor seria indefinido. Por exemplo, o seguinte
código terá resultado indefinido e talvez não imprima nada na tela:
\alinhaverbatim
// ERRADO
void loop(void){
LOOP\_INIT:
int var = 5;
LOOP\_BODY:
if(var == 5)
printf("var == 5\\n");
LOOP\_END:
}
\alinhanormal
Já o seguinte código iria imprimir na saída padrão a cada iteração do
loop:
\alinhaverbatim
// CERTO
static int var;
void loop(void){
LOOP\_INIT:
var = 5;
LOOP\_BODY:
if(var == 5)
printf("var == 5\\n");
LOOP\_END:
}
\alinhanormal
Outra coisa que deve ser levada em conta é que as macros utilizadas
escondem outro detalhe importante: não é apenas um laço principal que
executamos, mas existem dois simultâneos. Um laço principal executa
com uma frequência fixa: o laço que cuida da física e da lógica do
jogo. Outro laço, nós apenas fazemos executar o mais rápido que der no
hardware atual: o laço responsável por renderizar coisas na tela.
Idealmente para cada iteração do laço de física e lógica executamos uma
ou mais iterações de nosso laço de renderização. Isso significa que
podemos renderizar com uma frequência maior que executamos a iteração
responsável por realmente mover objetos, detectar colisões e ler
entrada do usuário. Para que a cada vez nós não renderizemos
exatamente a mesma imagem, o que derrotaria o propósito de fazermos
isso, nós interpolamos a posição dos objetos da tela de acordo com
seus valores de aceleração, velocidade e posição.
Garantindo que a nossa física e lógica do jogo execute sempre em
intervalos constantes, nós garantimos o determinismo necessário para
podermos sincronizar partidas por meio de redes como a Internet. E
renderizando os objetos na tela o mais rápido que podemos com ajuda de
interpolação nos dá a experiência visual mais suave e natural que for
possível.
Uma referência e maiores detalhes de como implementar isso pode ser
encontrado em [Fiedler 2004]. Nossa implementação será como mostrado
na referência, com exceção de que nosso código será muito menos
transparente por ter que estar contido dentro de macros sem usar loops
explícitos.
Vamos agora definir nas subseções seguintes o que exatamente deverá
existir em cada uma das macros que devem estar presentes em todo laço
principal.
\subsecao{3.1. Inicialização do Loop.}
Isso é o que fará a macro \monoespaco{LOOP\_INIT}:
Primeiro devemos checar variáveis que determinam se devemos encerrar o
laço. Se estivermos, mas ainda houverem recursos sendo carregados
(imagens, vídeos, shaders, sons), apenas retornamos da função. Se não
houver nada sendo carregado, mas ainda não executamos a finalização
deste laço, pulamos para executar a finalização. Se não há nada a ser
carregado e já executamos a finalização, aí sim encerramos de vez o
laço. Depois checamos se esta função está sendo chamada pela primeira
vez. Em caso afirmativo, apenas continuamos a execução. Em caso
negativo, fazemos um desvio incondicional para não termos que executar
novamente o código de inicialização. Por fim, se não fizemos o desvio,
faremos com que a variável |W.loop_name| passe a ser uma string com o
nome do laço principal atual.
Saber se devemos continuar executando ou não um laço é algo que pode
ser controlado por uma variável global, não sendo nem necessário se
preocupar com semáforos. Afinal, somente um laço irá executar em um
dado momento. O mesmo pode ser feito com a variável que determina se
estamos na primeira execução de um laço (ou o começo de um laço) e se
já executamos a finalização. Vamos declarar essas variáveis no
cabeçalho:
\iniciocodigo
@<Variáveis Externas (weaver.h)@>+=
extern bool _running_loop, _loop_begin, _loop_finalized;
@
\fimcodigo
E também no arquivo com as definições:
\iniciocodigo
@<Variáveis Globais (weaver.c)@>+=
bool _running_loop, _loop_begin, _loop_finalized;
@
\fimcodigo
Vamos também inicializar elas:
\iniciocodigo
@<API Weaver: Inicialização@>+=
_running_loop = false;
_loop_begin = false;
_loop_finalized = false;
@
\fimcodigo
Saber se ainda estamos carregando arquivos (ou melhor, quantos
arquivos pendentes ainda estamos carregando) ou o nome do laço em que
estamos são duas informações que são úteis não só para a lógica
interna do motor, mas também para o seu usuário. Saber se o laço ainda
não terminou de carregar é útil para fornecer uma tela de
carregamento. Saber durante a execução o nome do laço em que estamos é
útil tanto para depuração como para podermos carregar recursos
diferentes dependendo do laço em que estamos. Por causa disso, ambas
as variáveis devem ser declaradas na estrutura |W|. O tamanho máximo
de nome de um laço que podemos armazenar pode ser personalizado com a
macro \monoespaco{W\_MAX\_LOOP\_NAME}.
\iniciocodigo
@<Namespace de Variáveis@>+=
// Dentro da estrutura W:
#if !defined(W_MAX_LOOP_NAME)
#define W_MAX_LOOP_NAME 64
#endif
unsigned pending_files;
char loop_name[W_MAX_LOOP_NAME];
@
\fimcodigo
Na inicialização ajustamos tais variáveis como 0 e |NULL|
respectivamente:
\iniciocodigo
@<API Weaver: Inicialização@>+=
W.pending_files = 0;
W.loop_name[0] = '\0';
@
\fimcodigo
A função que usaremos para sair do laço será esta:
\iniciocodigo
@<Declaração de Função (weaver.h)@>+=
#if !defined(_MSC_VER)
void _exit_loop(void) __attribute__ ((noreturn));
#else
__declspec(noreturn) void _exit_loop(void);
#endif
@
\fimcodigo
Mas não iremos definir ela ainda. Pelo cabeçalho nota-se que é uma
função que nunca retorna, tendo isso especificado tanto pela convenção
do GCC e Clang como pela convenção do Visual Studio. Isso porque o que
ela fará é apenas chamar o código do laço anterior, ou sair do
programa dependendo do caso.
Após descrever tudo isso, podemos enfim definir a macro de início de
laço:
\iniciocodigo
@<Macros (weaver.h)@>+=
#define LOOP_INIT \
if(!_running_loop){ \
if(W.pending_files) \
return; \
if(!_loop_finalized){ \
_loop_finalized = true; \
goto _LOOP_FINALIZATION; \
} \
_exit_loop(); \
} \
if(!_loop_begin) \
goto _END_LOOP_INITIALIZATION; \
snprintf(W.loop_name, W_MAX_LOOP_NAME, "%s", __func__); \
_BEGIN_LOOP_INITIALIZATION
@
\fimcodigo
Terminamos com o
identificador \monoespaco{\_BEGIN\_LOOP\_INITIALIZATION}, o qual será
o verdadeiro nome do rótulo que existirá por trás de nossa macro.
\subsecao{3.2. Corpo do Loop.}
Isso é o que fará a macro \monoespaco{LOOP\_BODY}:
Ao chega nesta macro, devemos ajustar como falsa a informação de que é
nossa primeira execução do laço, pois assim não iremos executar a
inicialização novamente. Em seguida, aproveitamos para colocar um
desvio incondicional por trás de um \monoespaco{if} que garanta que
ele nunca seja executado para o rótulo que termina a macro
anterior. Esse desvio nunca irá ocorrer, mas isso previne que o
compilador reclame que o rótulo que encerra a última macro não é
usado. Em seguida, crimaos o verdadeiro rótulo que marca o fim da
inicialização e o começo da execução do corpo do laço. Neste começo de
corpo do laço nós medimos o tempo que passou desde o último laço e
armazenamos em um acumulador. Se este acumulador tiver um valor maior
que o período de tempo entre execuções do código de física e lógica,
então executamos o código presente no laço e o código associado à
física. Caso contrário, só ignoramos tudo e vamos para a finalização
onde apenas renderizamos na tela. Caso tenha passado um longo tempo
entre cada iteração de laço, executamos mais de uma vez o código do
corpo do laço.
O acumulador que usamos para saber se devemos executar a lógica e a
física do jogo será chamado de \monoespaco{\_lag}. Declaramos ele
globalmente:
\iniciocodigo
@<Variáveis Externas (weaver.h)@>+=
extern unsigned long _lag;
@
\fimcodigo
\iniciocodigo
@<Variáveis Globais (weaver.c)@>+=
unsigned long _lag;
@
\fimcodigo
E o inicializamos:
\iniciocodigo
@<API Weaver: Inicialização@>+=
_lag = 0;
@
\fimcodigo
Existirão variáveis que poderão ser lidas pelo usuário com informações
de tempo. Uma delas (\monoespaco{W.t}) conterá a quantidade de
microssegundos desde que o jogo inicializou. Outra delas
(\monoespaco{W.dt}) conterá o intervalo entre execuções do motor de
física e da lógica do jogo. Ambas as variáveis precisam ser declaradas
na estrutura \monoespaco{W}:
\iniciocodigo
@<Namespace de Variáveis@>+=
unsigned long long t;
unsigned long dt;
@
\fimcodigo
A primeira das variáveis, obviamente deve ser inicializada como
zero. A segunda deve ser inicializada como tendo o mesmo valor que uma
macro \monoespaco{W\_TIMESTEP} que pode ser definida pelo usuário para
que assim ele controle a granularidade de execução do código mais
pesado do motor de física e lógica do jogo. Se esta macro não for
definida, iremos assumir um valor de 40000 microssegundos. Isso
significa uma freuência de 25 Hz de execução do motor de física (25
vezes por segundo).
\iniciocodigo
@<API Weaver: Inicialização@>+=
#if !defined(W_TIMESTEP)
#define W_TIMESTEP 40000
#endif
W.dt = W_TIMESTEP;
W.t = 0;
@
\fimcodigo
O código da engine de física e lógica interna do jogo deve ficar
encapsulado em uma função chamada \monoespaco{\_update}:
\iniciocodigo
@<Declaração de Função (weaver.h)@>+=
void _update(void);
@
\fimcodigo
Por hora ainda não iremos definir o código a ser executado nesta
função:
\iniciocodigo
@<Definição de Função (weaver.c)@>+=
void _update(void){
@<Código a executar todo loop@>
}
@
\fimcodigo
Mas com as definições que fizemos já podemos definir a nossa macro que
marca o começo do código do laço principal:
\iniciocodigo
@<Macros (weaver.h)@>+=
#define LOOP_BODY \
_loop_begin = false; \
if(_loop_begin) \
goto _BEGIN_LOOP_INITIALIZATION; \
_END_LOOP_INITIALIZATION: \
_lag += _update_time(); \
while(_lag >= W.dt){ \
_update(); \
_LABEL_0
@
\fimcodigo
Notar que a macro acima abre um laço com um \monoespaco{while}, mas
não o encerra. Ele deverá ser encerrado pelo código inserido pela
macro que delimita o fim do corpo do laço principal. A qual também
deverá decrementar a variável \monoespaco{\_lag} para que este não
seja um laço infinito.
\subsecao{3.3. Finalização do Loop.}
Isso é o que fará a macro \monoespaco{LOOP\_END}:
Primeiro para fornecer uma condição de parada para o laço começado na
macro anterior, decrementaremos de \monoespaco{\_lag} o valor
de \monoespaco{W.dt}. Em seguida, incrementaremos \monoespaco{W.t} com
tal quantidade de microssegundos. Só então encerramos o bloco do laço
dentro do qual estávamos. Fora deste laço, seguimos realizando aquilo
que deve ser feito independente de estarmos executando o motor de
física e de lógica de jogo ou não. Como são sempre atividades
relacionadas à renderização na tela, isso estará dentro de uma função
chamada \monoespaco{\_render}. Em seguida, simplesmente retornamos. Se
estamos executando o código aqui, é porque estávamos terminando de
executar o corpo do link. Depois do retorno colocamos um desvio para
um rótulo que nunca será executado e apenas nos protege de aviso do
compilador. E enfim colocamos um rótulo imediatamente antes da
finalização e que pode ser atingido somente por um desvio se
detectarmos que o laço acabou e precisamos rodar a finalização.
A única coisa nova que temos aqui então é a função de renderização:
\iniciocodigo
@<Declaração de Função (weaver.h)@>+=
void _render(void);
@
\fimcodigo
Por hora ainda não iremos definir o código a ser executado nesta
função:
\iniciocodigo
@<Definição de Função (weaver.c)@>+=
void _render(void){
@<Código de renderização@>
}
@
\fimcodigo
E agora colocamos o código da macro:
\iniciocodigo
@<Macros (weaver.h)@>+=
#define LOOP_END \
_lag -= 40000; \
W.t += 40000; \
} \
_render(); \
return; \
goto _LABEL_0; \
_LOOP_FINALIZATION
@
\fimcodigo
\subsecao{3.4. Entrando em Laço Principal.}
Frequentemente estaremos trocando de laços principais ao longo de um
jogo. Mas nem sempre isso significa uma substituição completa. Alguns
laços principais ocorrem dentro de outros laços principais. Por
exemplo, quando acessamos um menu de configurações durante um jogo. Ou
quando em um RPG clássico por turnos mudamos para o modo de combate
após um encontro aleatório.
Podemos então formar uma pilha de laços principais, onde ao sairmos do
último laço voltamos para o que está empilhado imediatamente abaixo
dele ao invés de sairmos do jogo.
Sendo assim, existem duas formas de entrar em um laço principal. Uma
delas, por meio da função que definiremos chamada \monoespaco{Wloop} e
a segunda por meio da \monoespaco{Wsubloop}. A primeira envolve
substituirmos o laço principal atual por um novo. A segunda enolve
apenas criar um laço principal dentro do laço atual. A primeira é algo
que só poderemos fazer se não houverem arquivos pendentes sendo
carregados (possivelmente haverá uma tela de carregamento neste
caso). Por isso apenas nos asseguramos disso por mieo de um truque com
macros:
\iniciocodigo
@<Declaração de Função (weaver.h)@>+=
#if !defined(_MSC_VER)
void _Wloop(void (*f)(void)) __attribute__ ((noreturn));
void Wsubloop(void (*f)(void)) __attribute__ ((noreturn));
#else
__declspec(noreturn) void _Wloop(void (*f)(void));
__declspec(noreturn) void Wsubloop(void (*f)(void));
#endif
#define Wloop(a) ((W.pending_files)?(false):(_Wloop(a)))
@
\fimcodigo
Nenhum dos dois tipos de função irá retornar jamais. Então
especificamos isso tanto na convenção de compiladores como Clang e GCC
como na do Visual Studio.
Vamos precisar de uma pilha de laços principais, que representaremos
por meio de uma sequência de ponteiros para funções. Essa nossa
sequência será um vetor com \monoespaco{W\_MAX\_SUBLOOP}
posições. Esta macro poderá ser controlada pelo usuário, mas se não
estiver definida, assumiremos que será 3. Além da pilha, vamos
precisar de uma variável para nos informar em qual profundidade da
pilha estamos no momento (\monoespaco{\_number\_of\_loops}).
A declaração destas duas variáveis será:
\iniciocodigo
@<Variáveis Externas (weaver.h)@>+=
#if !defined(W_MAX_SUBLOOP)
#define W_MAX_SUBLOOP 3
#endif
extern int _number_of_loops;
extern void (*_loop_stack[W_MAX_SUBLOOP]) (void);
@
\fimcodigo
\iniciocodigo
@<Variáveis Globais (weaver.c)@>+=
int _number_of_loops;
void (*_loop_stack[W_MAX_SUBLOOP]) (void);
@
\fimcodigo
E inicializamos a contagem do número de laços como zero:
\iniciocodigo
@<API Weaver: Inicialização@>+=
_number_of_loops = 0;
@
\fimcodigo
Entrar em um novo laço principal por meio de \monoespaco{Wloop}
envolve verificar se já estamos antes em um laço. Se for o caso,
cancelamos ele. Em seguida, carregamos o novo laço para a pilha e
ajustamos o valor da contagem de laços em execução. Atualizamos o
nosso valor de contagem de tempo e finalmente executamos o laço. Em
ambiente Web Assembly em navegador de Internet isso envolve chamar
diretamente uma função que estabelece nossa função escolhida como laço
principal. Nos demais, basta executar a função em
um \monoespaco{while} comum:
\iniciocodigo
@<Definição de Função (weaver.c)@>+=
void _Wloop(void (*f)(void)){
if(_number_of_loops > 0){
@<Cancela Loop Principal@>
@<Ajuste de Memória Após Cancelar Loop@>
_number_of_loops --;
}
@<Código antes de Loop e Subloop@>
@<Código antes de Loop, não de Subloop@>
@<Ajuste de Memória antes de Loop e Subloop@>
_loop_stack[_number_of_loops] = f;
_number_of_loops ++;
#if defined(__EMSCRIPTEN__)
emscripten_set_main_loop(f, 0, 1);
#else
while(1)
f();
#endif
}
@
\fimcodigo