-
Notifications
You must be signed in to change notification settings - Fork 1
/
lua_api.c
2011 lines (1679 loc) · 62.8 KB
/
lua_api.c
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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "game_api.h"
#include "lua_api.h"
#include "lua_api_dict.h"
#ifdef ENABLE_WORD_DICT
#include "dictionary.h"
#endif
#include "utils/str_eq_literal.h"
#include "lua_api_utils.h"
static const bool include_file_line_before_lua_prints = false;
// TODO rearrange this
extern const struct game_api lua_game_api;
#define LOG_ALL_LUA_ENTRY_EXIT 0
// TODO put this in a header or remove entirely
// right now I'm just using them for debugging a non-WASM client.
// I don't think we necessarily want to rely on mutexes here, the APIs
// should be mostly asynchronous.
// But for debugging, taking mutexes can make it easier to catch threading issues
// (e.g. if the "mouse event" API would cause deadlock with the graphics APIs)
extern void alexgames_mutex_take();
extern void alexgames_mutex_release();
#define LUA_API_ENTER(return_val) { \
if (LOG_ALL_LUA_ENTRY_EXIT) { \
alex_log("LUA API %s: enter\n", __func__); \
} \
alexgames_mutex_take(); \
if (lua_dead) { \
alex_log_err("%s: returning because lua_dead\n", __func__); \
return return_val; \
} \
if (lua_busy_protection_enabled && lua_api_busy) { \
alex_log_err("%s: already busy processing lua API\n", __func__); \
return return_val; \
} \
if (lua_gettop(L) != 0) { \
alex_log("%s: %d items on stack before starting!\n", __func__, lua_gettop(L)); \
} \
lua_api_busy = true; \
}
#define LUA_API_EXIT() { \
if (LOG_ALL_LUA_ENTRY_EXIT) { \
alex_log("LUA API %s: exit\n", __func__); \
} \
if (lua_gettop(L) > 0) { \
alex_log("%s: %d items left on stack after completion!\n", __func__, lua_gettop(L)); \
} \
lua_api_busy = false; \
alexgames_mutex_release(); \
}
#ifdef ENABLE_GAME_LUA_TRACES
#define GAME_LUA_TRACE(...) \
alex_log(__VA_ARGS__);
#else
#define GAME_LUA_TRACE(...)
#endif
static const char *lua_tolstring_notnil(lua_State* L, int idx, size_t *len);
static int lua_draw_graphic(lua_State *L);
static int lua_draw_line(lua_State *L);
static int lua_draw_text(lua_State *L);
static int lua_draw_rect(lua_State *L);
static int lua_draw_triangle(lua_State *L);
static int lua_draw_circle(lua_State *L);
static int lua_draw_clear(lua_State *L);
static int lua_draw_refresh(lua_State *L);
static int lua_send_message(lua_State *L);
static int lua_create_btn(lua_State *L);
static int lua_set_btn_enabled(lua_State *L);
static int lua_set_btn_visible(lua_State *L);
static int lua_show_popup(lua_State *L);
static int lua_add_game_option(lua_State *L);
static int lua_prompt_string(lua_State *L);
static int lua_hide_popup(lua_State *L);
static int lua_set_status_msg(lua_State *L);
static int lua_set_status_err(lua_State *L);
static int lua_set_timer_update_ms(lua_State *L);
static int lua_delete_timer(lua_State *L);
static int lua_enable_evt(lua_State *L);
static int lua_get_time_ms(lua_State *L);
static int lua_get_time_of_day(lua_State *L);
static int lua_store_data(lua_State *L);
static int lua_get_new_session_id(lua_State *L);
static int lua_get_last_session_id(lua_State *L);
static int lua_save_state(lua_State *L);
static int lua_has_saved_state_offset(lua_State *L);
static int lua_get_saved_state_offset(lua_State *L);
static int lua_read_stored_data(lua_State *L);
static int lua_draw_extra_canvas(lua_State *L);
static int lua_new_extra_canvas(lua_State *L);
static int lua_set_active_canvas(lua_State *L);
static int lua_delete_extra_canvases(lua_State *L);
static int lua_get_user_colour_pref(lua_State *L);
static int lua_is_feature_supported(lua_State *L);
#ifdef ENABLE_WORD_DICT
#if 0
static int lua_get_words(lua_State *L);
#endif
#endif
static int lua_my_print(lua_State* L);
int lua_err_handler(struct lua_State *L);
static void handle_lua_err(void *L);
static void lua_push_error_handler(void *L);
static bool lua_api_busy = false;
static bool lua_busy_protection_enabled = true;
static bool stop_lua_on_err = false;
static bool lua_dead = false;
#ifdef ENABLE_WORD_DICT
//static void *g_dict_handle = NULL;
#endif
// TODO rename to api_callbacks
// TODO remove global variable and move to a wrapper handle that points to this and
// the lua state `L`
static const struct game_api_callbacks *api;
static const struct luaL_Reg lua_c_api[] = {
{"draw_graphic", lua_draw_graphic },
{"draw_line", lua_draw_line },
{"draw_text", lua_draw_text },
{"draw_rect", lua_draw_rect },
{"draw_triangle", lua_draw_triangle },
{"draw_circle", lua_draw_circle },
{"draw_clear", lua_draw_clear },
{"draw_refresh", lua_draw_refresh },
{"send_message", lua_send_message },
{"create_btn", lua_create_btn },
{"set_btn_enabled", lua_set_btn_enabled },
{"set_btn_visible", lua_set_btn_visible },
{"show_popup", lua_show_popup },
{"add_game_option", lua_add_game_option },
{"prompt_string", lua_prompt_string },
{"hide_popup", lua_hide_popup },
{"set_status_msg", lua_set_status_msg },
{"set_status_err", lua_set_status_err },
{"set_timer_update_ms", lua_set_timer_update_ms },
{"delete_timer", lua_delete_timer },
{"enable_evt", lua_enable_evt },
{"get_time_ms", lua_get_time_ms },
{"get_time_of_day", lua_get_time_of_day },
{"get_new_session_id", lua_get_new_session_id },
{"get_last_session_id", lua_get_last_session_id },
{"store_data", lua_store_data },
{"read_stored_data", lua_read_stored_data },
{"save_state", lua_save_state },
{"has_saved_state_offset", lua_has_saved_state_offset },
{"adjust_saved_state_offset", lua_get_saved_state_offset },
{"draw_extra_canvas", lua_draw_extra_canvas },
{"new_extra_canvas", lua_new_extra_canvas },
{"set_active_canvas", lua_set_active_canvas },
{"delete_extra_canvases", lua_delete_extra_canvases },
{"get_user_colour_pref", lua_get_user_colour_pref},
{"is_feature_supported", lua_is_feature_supported},
{NULL, NULL}
};
static const struct luaL_Reg lua_printlib[] = {
// Override print so that we can still see them even on platforms
// where printf isn't visible (e.g. android)
{"print", lua_my_print},
};
// note that using "do ... while (0)" is basically just a trick
// to require a semi-colon after the call to the macro,
// so that it behaves slightly more like a normal function.
//
// note that putting the number sign `#` before the macro parameter
// converts it to a string, so that if field is `MOUSE_EVT_DOWN`,
// `field` is equal to the value of `MOUSE_EVT_DOWN`, but `#field`
// is equal to a string: "MOUSE_EVT_DOWN"
#define LUA_SET_FIELD_INTEGER_DEFINE(L, field) \
do { \
lua_pushinteger(L, field); \
lua_setfield(L, -2, #field); \
} while (0)
static int luaopen_alexlib(lua_State *L) {
luaL_newlib(L, lua_c_api);
LUA_SET_FIELD_INTEGER_DEFINE(L, MOUSE_EVT_DOWN);
LUA_SET_FIELD_INTEGER_DEFINE(L, MOUSE_EVT_UP);
LUA_SET_FIELD_INTEGER_DEFINE(L, MOUSE_EVT_LEAVE);
LUA_SET_FIELD_INTEGER_DEFINE(L, MOUSE_EVT_ALT_DOWN);
LUA_SET_FIELD_INTEGER_DEFINE(L, MOUSE_EVT_ALT_UP);
LUA_SET_FIELD_INTEGER_DEFINE(L, MOUSE_EVT_ALT2_DOWN);
LUA_SET_FIELD_INTEGER_DEFINE(L, MOUSE_EVT_ALT2_UP);
LUA_SET_FIELD_INTEGER_DEFINE(L, TEXT_ALIGN_LEFT);
LUA_SET_FIELD_INTEGER_DEFINE(L, TEXT_ALIGN_CENTRE);
LUA_SET_FIELD_INTEGER_DEFINE(L, TEXT_ALIGN_CENTER);
LUA_SET_FIELD_INTEGER_DEFINE(L, TEXT_ALIGN_RIGHT);
LUA_SET_FIELD_INTEGER_DEFINE(L, POPUP_ITEM_TYPE_MSG);
LUA_SET_FIELD_INTEGER_DEFINE(L, POPUP_ITEM_TYPE_BTN);
LUA_SET_FIELD_INTEGER_DEFINE(L, POPUP_ITEM_TYPE_DROPDOWN);
LUA_SET_FIELD_INTEGER_DEFINE(L, OPTION_TYPE_BTN);
LUA_SET_FIELD_INTEGER_DEFINE(L, OPTION_TYPE_TOGGLE);
// TODO this causes a crash on windows wxWidgets, why?
#if 0
lua_getglobal(L, "_G");
luaL_setfuncs(L, lua_printlib, 0);
lua_pop(L, 1);
#else
(void)lua_printlib;
#endif
return 1;
}
#ifdef ENABLE_WORD_DICT
#if 0
static int luaopen_alexdictlib(lua_State *L) {
static const struct luaL_Reg dict_c_api[] = {
{"get_words", lua_get_words },
};
// TODO put init in here
luaL_newlib(L, dict_c_api);
return 1;
}
#endif
#endif
//#define LUA_SCRIPT_DIR ROOT_DIR "src/lua_scripts"
//#define LUA_SCRIPT_DIR ROOT_DIR ""
#define LUA_PRELOAD_DIR "preload/"
#define LUA_UPLOAD_DIR "upload/"
#define lua_getglobal_checktype_or_return_statement(L, name, type, err, return_statement) \
{ \
int rc = lua_getglobal(L, (name)); \
if (rc != (type)) { \
/*lua_pop(L, 1);*/ \
lua_settop(L, 0); \
LUA_API_EXIT(); \
if (err) { \
char msg[1024]; \
int msg_len = snprintf(msg, sizeof(msg), "%s is type %s, expected %s", \
name, \
lua_typename(L, rc), \
lua_typename(L, type)); \
alex_log_err(msg); \
api->set_status_err(msg, msg_len); \
} \
return_statement; \
} \
}
#define lua_getglobal_checktype_or_return(L, name, type) \
lua_getglobal_checktype_or_return_statement(L, name, type, /*err=*/ true, return)
#define lua_getglobal_checktype_or_return_no_err(L, name, type) \
lua_getglobal_checktype_or_return_statement(L, name, type, /*err=*/ false, return)
#define lua_getglobal_checktype_or_return_val(L, name, type, return_val) \
lua_getglobal_checktype_or_return_statement(L, name, type, /*err=*/ true, return return_val)
#define lua_getglobal_checktype_or_return_val_no_err(L, name, type, return_val) \
lua_getglobal_checktype_or_return_statement(L, name, type, /*err=*/ false, return return_val)
// fprintf(stderr, "global \"%s\" not defined with type %d in " \
// "lua main, is type %d\n", \
// (name), (type), (rc)); \
//
#define lua_checkstack_or_return_statement(L, stack_size, return_statement) \
{ \
if (!lua_checkstack(L, stack_size)) { \
alex_log_err("stack size can not fit %d more " \
"elements\n", stack_size); \
LUA_API_EXIT() \
return_statement; \
} \
int stack_depth = lua_gettop(L); \
if (stack_depth >= 20) { \
alex_log_err("lua stack depth is %d\n", stack_depth); \
LUA_API_EXIT() \
return_statement; \
} \
}
#define lua_checkstack_or_return(L, stack_size) \
lua_checkstack_or_return_statement(L, stack_size, return)
#define lua_checkstack_or_return_val(L, stack_size, val) \
lua_checkstack_or_return_statement(L, stack_size, return val)
#if 0
static void handle_panic(void) {
alex_log_err("lua panic, setting lua_dead");
lua_dead = true;
}
static void handle_warning(void *ud, const char *msg, int tocount) {
alex_log_err("lua warning: %s", msg);
}
#endif
const char *get_lua_game_path(const char *game_id, size_t game_id_len) {
if (str_eq_literal(game_id, "go", game_id_len)) {
return LUA_PRELOAD_DIR "games/go/go_main.lua";
} else if (str_eq_literal(game_id, "wu", game_id_len)) {
return LUA_PRELOAD_DIR "games/wu/wu_main.lua";
} else if (str_eq_literal(game_id, "card_test", game_id_len)) {
return LUA_PRELOAD_DIR "/libs/cards/card_test.lua";
} else if (str_eq_literal(game_id, "31s", game_id_len)) {
return LUA_PRELOAD_DIR "games/31s/31s_main.lua";
} else if (str_eq_literal(game_id, "life", game_id_len)) {
return LUA_PRELOAD_DIR "games/life/life_main.lua";
} else if (str_eq_literal(game_id, "checkers", game_id_len)) {
return LUA_PRELOAD_DIR "games/checkers/checkers_main.lua";
} else if (str_eq_literal(game_id, "crib", game_id_len)) {
return LUA_PRELOAD_DIR "games/crib/crib_main.lua";
} else if (str_eq_literal(game_id, "card_sim", game_id_len)) {
return LUA_PRELOAD_DIR "games/card_sim/card_generic_main.lua";
} else if (str_eq_literal(game_id, "touch_test", game_id_len)) {
return LUA_PRELOAD_DIR "games/touch_test/touch_test.lua";
} else if (str_eq_literal(game_id, "draw_graphics_test", game_id_len)) {
return LUA_PRELOAD_DIR "games/test/draw_graphics_test.lua";
} else if (str_eq_literal(game_id, "snake", game_id_len)) {
return LUA_PRELOAD_DIR "games/snake/snake_main.lua";
} else if (str_eq_literal(game_id, "solitaire", game_id_len)) {
return LUA_PRELOAD_DIR "games/solitaire/solitaire_main.lua";
} else if (str_eq_literal(game_id, "card_angle_test", game_id_len)) {
return LUA_PRELOAD_DIR "games/test/card_angle_test.lua";
} else if (str_eq_literal(game_id, "minesweeper", game_id_len)) {
return LUA_PRELOAD_DIR "games/minesweeper/minesweeper_main.lua";
} else if (str_eq_literal(game_id, "hospital", game_id_len)) {
return LUA_PRELOAD_DIR "games/hospital/hospital_main.lua";
} else if (str_eq_literal(game_id, "bound", game_id_len)) {
return LUA_PRELOAD_DIR "games/bound/bound_main.lua";
} else if (str_eq_literal(game_id, "sudoku", game_id_len)) {
return LUA_PRELOAD_DIR "games/sudoku/sudoku_main.lua";
} else if (str_eq_literal(game_id, "backgammon", game_id_len)) {
return LUA_PRELOAD_DIR "games/backgammon/backgammon_main.lua";
} else if (str_eq_literal(game_id, "chess", game_id_len)) {
return LUA_PRELOAD_DIR "games/chess/chess_main.lua";
} else if (str_eq_literal(game_id, "blue", game_id_len)) {
return LUA_PRELOAD_DIR "games/blue/blue_main.lua";
} else if (str_eq_literal(game_id, "thrust", game_id_len)) {
return LUA_PRELOAD_DIR "games/thrust/thrust_main.lua";
} else if (str_eq_literal(game_id, "swarm", game_id_len)) {
return LUA_PRELOAD_DIR "games/swarm/swarm_main.lua";
} else if (str_eq_literal(game_id, "spider_swing", game_id_len)) {
return LUA_PRELOAD_DIR "games/spider_swing/spider_swing_main.lua";
} else if (str_eq_literal(game_id, "poker_chips", game_id_len)) {
return LUA_PRELOAD_DIR "games/poker_chips/poker_chips_main.lua";
} else if (str_eq_literal(game_id, "word_mastermind", game_id_len)) {
return LUA_PRELOAD_DIR "games/word_mastermind/word_mastermind_main.lua";
} else if (str_eq_literal(game_id, "crossword_letters", game_id_len)) {
return LUA_PRELOAD_DIR "games/crossword_letters/crossword_letters_main.lua";
} else if (str_eq_literal(game_id, "crossword_builder", game_id_len)) {
return LUA_PRELOAD_DIR "games/crossword_builder/crossword_builder_main.lua";
} else if (str_eq_literal(game_id, "fluid_mix", game_id_len)) {
return LUA_PRELOAD_DIR "games/fluid_mix/fluid_mix_main.lua";
} else if (str_eq_literal(game_id, "timer_test", game_id_len)) {
return LUA_PRELOAD_DIR "games/test/timer_test.lua";
} else if (str_eq_literal(game_id, "endless_runner", game_id_len)) {
return LUA_PRELOAD_DIR "games/endless_runner/endless_runner_main.lua";
} else if (str_eq_literal(game_id, "minesweeper_life", game_id_len)) {
return LUA_PRELOAD_DIR "games/minesweeper_life/minesweeper_life_main.lua";
} else {
return NULL;
}
}
void *start_lua_game(const struct game_api_callbacks *api_callbacks, const char *game_path) {
#if ENABLE_WORD_DICT
#if 0
printf("Initializing dictionary...\n");
g_dict_handle = init_dict();
printf("done Initializing dictionary...?\n");
if (g_dict_handle == NULL) {
char msg[] = "Error initializing dictionary. Word games will not work.";
fprintf(stderr, "%s:%d %s\n", __FILE__, __LINE__, msg);
alex_log_err("%s", msg);
api_callbacks->set_status_err(msg, sizeof(msg));
}
printf("it even returned %p!\n", g_dict_handle);
#endif
#endif
set_game_api(&lua_game_api);
void *L = init_lua_game(api_callbacks, game_path);
if (L == NULL) {
char err_msg[4096];
snprintf(err_msg, sizeof(err_msg), "Failed to load game \"%s\"", game_path);
alex_log_err("%.*s", (int)sizeof(err_msg), err_msg);
api_callbacks->set_status_err(err_msg, sizeof(err_msg));
return L;
}
//game_api->start_game(L);
return L;
}
static void lua_push_error_handler(void *L) {
lua_pushcfunction(L, lua_err_handler);
}
static void lua_pop_error_handler(void *L) {
int type = lua_type(L, -1);
if (type != LUA_TFUNCTION) {
luaL_error(L, "%s: expected last stack value to be error handler func, was type %d (%s)", __func__, type, lua_typename(L, type));
}
lua_pop(L, -1);
}
// Must call `lua_pop_error_handler` after calling this (and removing
// the other stuff you need from the stack)
static int pcall_handle_error(void *L, int nargs, int nresults) {
int rc = lua_pcall(L, nargs, nresults, 1);
if (rc != LUA_OK) {
alex_log_err("pcall_handler_err: lua_pcall returned %d\n", rc);
handle_lua_err(L);
// TODO should I clean up the stack or anything?
//lua_settop(L, 0);
}
return rc;
}
// TODO should rename this file to lua_adapter or something
// void *init_lua_api(const struct game_api_callbacks *api_callbacks_arg, const char *game_str, int game_str_len) {
void *init_lua_game(const struct game_api_callbacks *api_callbacks_arg, const char *lua_fpath_arg) {
GAME_LUA_TRACE("init_lua_game\n");
//set_game_api(&lua_game_api);
api = api_callbacks_arg;
char lua_script_dir[4096];
{
int rc = alex_get_root_dir(lua_script_dir, sizeof(lua_script_dir));
if (rc) {
alex_log_err("Failed to get root dir");
return NULL;
}
}
char lua_fpath[1024];
lua_fpath[0] = '\0';
// TODO this doesn't actually protect against buffer overrun
// strlcat is better, I think, but I get linker errors when trying to use that in windows
strncat(lua_fpath, lua_script_dir, sizeof(lua_fpath)-1);
strncat(lua_fpath, lua_fpath_arg, sizeof(lua_fpath)-1);
alex_log("[lua_init] Trying to load lua script at \"%s\"...\n", lua_fpath);
alex_log("[lua_init] init_lua called...\n");
lua_State *L = luaL_newstate();
alex_log("[lua_init] lua_State is %p\n", L);
luaL_openlibs(L);
#ifdef ENABLE_WORD_DICT
// TODO why does alexgames.dict have to be initialized before alexgames?
// maybe I have a bug causing it to fail when they're reversed.
// TODO remove this, this is the old dictionary API
//luaL_requiref(L, "alexgames.dict", luaopen_alexdictlib, 0);
init_lua_alex_dict(L, "alexgames.dict", get_game_dict_api());
#endif
luaL_requiref(L, "alexgames", luaopen_alexlib, 0);
// luaL_requiref leaves a copy of the library on the stack, so we need to pop it
lua_pop(L, -1);
lua_pop(L, -1);
alex_log("[lua_init] called luaL_openlibs\n");
int rc;
//rc = luaL_dofile(L, "lua_script/go.lua");
//printf("rc = %d\n", rc);
//rc = luaL_dofile(L, "src/lua_scripts/games/go/go_main.lua");
//rc = luaL_dofile(L, "games/go/go_main.lua");
#if 0
alex_log("Setting package.path to %s\n", LUA_SCRIPT_DIR);
lua_getglobal(L, "package");
lua_pushlstring(L, LUA_SCRIPT_DIR, strlen(LUA_SCRIPT_DIR));
lua_setfield(L, -2, "path");
lua_pop(L, 1);
#endif
lua_getglobal(L, "package");
lua_getfield(L, -1, "path");
const char *current_path = lua_tostring(L, -1);
char new_path[4096];
//snprintf(new_path, sizeof(new_path), "%s;%s/?.lua", current_path, ROOT_DIR);
//snprintf(new_path, sizeof(new_path), "%s;%s/?.lua;%s/?.lua;%s/?.lua", current_path, LUA_SCRIPT_DIR, LUA_PRELOAD_DIR, LUA_UPLOAD_DIR);
char preload_path[4096];
{
char root_dir[4096];
int rc = alex_get_root_dir(root_dir, sizeof(root_dir));
if (rc) {
alex_log_err("Error getting root_dir");
return NULL;
}
snprintf(preload_path, sizeof(preload_path), "%s%s", root_dir, LUA_PRELOAD_DIR);
alex_log("[init] preload_path = \"%s\"\n", preload_path);
}
// TODO include the game's dirname() directory here
int new_path_len = snprintf(new_path, sizeof(new_path),
"%s;%s/?.lua;%s/?.lua;%s/?.lua;%s/?.lua",
current_path, lua_script_dir, preload_path,
LUA_UPLOAD_DIR, GAME_UPLOAD_PATH);
if (new_path_len >= sizeof(new_path)) {
alex_log_err("Lua package.path too big, max size %d, actual %d\n", sizeof(new_path), new_path_len);
const char path_too_big_err[] = "Lua package.path too big";
api->set_status_err(path_too_big_err, sizeof(path_too_big_err));
return NULL;
}
lua_pop(L, 1);
alex_log("[lua_init] Including preload path: \"%s\"\n", preload_path);
alex_log("[lua_init] Including upload path: \"%s\"\n", GAME_UPLOAD_PATH);
lua_pushstring(L, new_path);
lua_setfield(L, -2, "path");
lua_pop(L, 1);
// TODO put this somewhere better
// and don't call it for non lua games
//set_game_api(&lua_game_api);
lua_api_busy = false;
alex_log("[lua_init] setting lua panic function\n");
//lua_atpanic(L, handle_panic);
//lua_setwarnf(L, handle_warning, NULL);
//lua_setglobal(L, "package.path");
//#warning "TODO revert this: disabling garbage collection"
//alex_log_err("TODO FIX THIS: disabling garbage collection to debug");
//lua_gc(L, LUA_GCSTOP);
alex_log("[lua_init] about to call luaL_dofile\n");
//rc = luaL_dofile(L, lua_fpath);
const bool enable_err_handler = true;
if (enable_err_handler) {
lua_pushcfunction(L, lua_err_handler);
}
rc = luaL_loadfile(L, lua_fpath);
if (rc == LUA_OK) {
if (enable_err_handler) {
rc = lua_pcall(L, 0, 0, 1);
} else {
rc = lua_pcall(L, 0, 0, 0);
}
}
alex_log("[lua_init] done calling luaL_dofile\n");
if (rc != LUA_OK) {
alex_log_err("line %d: rc = %d\n", __LINE__, rc);
//lua_err_handler(L);
//handle_lua_err(L);
const char *err_msg = lua_tostring(L, -1);
alex_log_err("[lua_init] err: %s\n", err_msg);
api->set_status_err(err_msg, strlen(err_msg));
return 0;
} else {
alex_log("[lua_init] luaL_dofile returned rc = %d (LUA_OK)\n", rc);
if (enable_err_handler) {
lua_pop(L, 1);
}
}
// This is so that when switching games, if the new game doesn't
// draw anything (e.g. while showing a popup like "waiting for players"),
// the old game won't still be shown.
// Can't clear game on old game teardown, because history_browse relies on
// the game remaining drawn.
GAME_LUA_TRACE("calling draw_clear and draw_refresh on init\n");
api->draw_clear();
api->draw_refresh();
if (lua_gettop(L) != 0) {
alex_log_err("[lua_init] %s: before finishing, lua_gettop(L) returned %d. Should be zero.\n", __func__, lua_gettop(L));
}
GAME_LUA_TRACE("init_lua_game completed\n");
return L;
}
#define CHECK_NON_NULL_AND_CALL(ptr, func) \
if (ptr == NULL) { \
alex_log_err("lua_api.c:%d %s is null\n", __LINE__, #ptr); \
} else if (ptr->func == NULL) { \
alex_log_err("lua_api.c:%d %s->%s() is null\n", __LINE__, #ptr, #func); \
} else { \
ptr->func(); \
}
void destroy_lua_game(void *L) {
alex_log("destroy_lua_game\n");
// Don't call "draw_clear" and "draw_refresh" here,
// because history_browse relies on the graphics remaining drawn when it
// runs through multiple games.
// Instead, draw_clear and draw_refresh should be called when initializing a new game.
CHECK_NON_NULL_AND_CALL(api, hide_popup);
CHECK_NON_NULL_AND_CALL(api, destroy_all);
lua_close(L);
#ifdef ENABLE_WORD_DICT
// TODO remove this. The dictionary is now loaded by the game, not the game API itself.
// This is so that games who don't use the dictionary don't need to waste memory loading it.
//if (g_dict_handle != NULL) {
// teardown_dict(g_dict_handle);
// g_dict_handle = NULL;
//}
#endif
}
// TODO better name for this.
// This is meant to be called by Lua when there is an error,
// and to do a stack trace.
//
// `handle_lua_err` is meant to be called from C when there is an error returned by lua.
// It should probably be removed and be replaced by this one, some day.
//
int lua_err_handler(struct lua_State *L) {
//const char *err_msg = lua_tostring(L, -1);
//alex_log_err("%s: %s\n", __func__, err_msg);
//api->set_status_err(err_msg, strnlen(err_msg, strnlen(err_msg, 1000)));
//lua_getfield(L, LUA_GLOBALSINDEX, "debug");
if (!lua_isstring(L, 1)) {
alex_log_err("%s: arg 1 is not a string?", __func__);
return 1;
}
lua_getglobal(L, "debug");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
alex_log_err("%s: debug is not a table?", __func__);
return 1;
}
lua_getfield(L, -1, "traceback");
if (!lua_isfunction(L, -1)) {
lua_pop(L, 2);
alex_log_err("%s: debug.traceback is not a function?", __func__);
return 1;
}
lua_pushvalue(L, 1);
lua_pushinteger(L, 2);
lua_call(L, 2, 1);
return 1;
}
static void handle_lua_err(void *L) {
if (L == NULL) {
alex_log_err("%s: L == null\n", __func__);
return;
}
const char *err_msg = lua_tostring(L, -1);
if (err_msg == NULL) {
alex_log_err("%s: err_msg == null\n", __func__);
return;
}
alex_log_err("lua error: %s\n", err_msg);
api->set_status_err(err_msg, strnlen(err_msg, 1000));
lua_pop(L, 1);
if (stop_lua_on_err) {
lua_dead = true;
}
// handle_lua_err(L);
}
static int lua_my_print(lua_State* L) {
if (include_file_line_before_lua_prints) {
lua_Debug ar;
lua_getstack(L, 1, &ar);
lua_getinfo(L, "nSl", &ar);
alex_log("[%s:%d]: ", ar.short_src, ar.currentline);
}
int nargs = lua_gettop(L);
if (nargs == 1) {
// It's ideal to just call alex_log a single time.
// On android this corresponds to a single logcat log, which already includes a newline
alex_log("%s\n", lua_tostring(L, 1));
} else {
for (int i = 1; i <= nargs; i++) {
if (lua_isstring(L, i)) {
alex_log("%s", lua_tostring(L, i));
} else {
alex_log("print arg %d type is %d, unhandled", i, lua_type(L, i));
}
if (i < nargs) {
alex_log(" ");
}
}
alex_log("\n");
}
return 0;
}
static void update(void *L, int dt_ms) {
GAME_LUA_TRACE("update\n");
if (L == NULL) {
fprintf(stderr, "%s: L == NULL\n", __func__);
return;
}
LUA_API_ENTER();
lua_checkstack_or_return(L, 1);
lua_push_error_handler(L);
lua_getglobal_checktype_or_return(L, "update", LUA_TFUNCTION);
lua_pushnumber(L, dt_ms);
pcall_handle_error(L, 1, 0);
lua_pop_error_handler(L);
LUA_API_EXIT();
GAME_LUA_TRACE("update completed\n");
}
static void handle_user_string_input(void *L, char *user_line, int str_len, bool is_cancelled) {
LUA_API_ENTER();
printf("%s: \"%.*s\", is_cancelled=%d\n", __func__, str_len, user_line, is_cancelled);
lua_checkstack_or_return(L, 2);
lua_push_error_handler(L);
lua_getglobal_checktype_or_return(L, "handle_user_string_input", LUA_TFUNCTION);
lua_pushlstring(L, user_line, str_len);
lua_pushboolean(L, is_cancelled);
pcall_handle_error(L, 2, 0);
lua_pop_error_handler(L);
LUA_API_EXIT();
}
static void handle_user_clicked(void *L, int pos_y, int pos_x) {
LUA_API_ENTER();
lua_checkstack_or_return(L, 3);
lua_push_error_handler(L);
lua_getglobal_checktype_or_return_no_err(L, "handle_user_clicked", LUA_TFUNCTION);
lua_pushinteger(L, pos_y);
lua_pushinteger(L, pos_x);
pcall_handle_error(L, 2, 0);
lua_pop_error_handler(L);
LUA_API_EXIT();
}
// TODO should probably only have this enabled if it's defined
static void handle_mousemove(void *L, int pos_y, int pos_x, int buttons) {
LUA_API_ENTER();
lua_checkstack_or_return(L, 4);
lua_push_error_handler(L);
lua_getglobal_checktype_or_return(L, "handle_mousemove", LUA_TFUNCTION);
lua_pushinteger(L, pos_y);
lua_pushinteger(L, pos_x);
lua_pushinteger(L, buttons);
pcall_handle_error(L, 3, 0);
lua_pop_error_handler(L);
LUA_API_EXIT();
}
static void handle_mouse_evt(void *L, int mouse_evt_id, int pos_y, int pos_x, int buttons) {
LUA_API_ENTER();
lua_checkstack_or_return(L, 5);
lua_push_error_handler(L);
lua_getglobal_checktype_or_return(L, "handle_mouse_evt", LUA_TFUNCTION);
lua_pushinteger(L, mouse_evt_id);
lua_pushinteger(L, pos_y);
lua_pushinteger(L, pos_x);
lua_pushinteger(L, buttons);
pcall_handle_error(L, 4, 0);
lua_pop_error_handler(L);
LUA_API_EXIT();
}
static void handle_wheel_changed(void *L, int delta_y, int delta_x) {
LUA_API_ENTER();
lua_checkstack_or_return(L, 3);
lua_push_error_handler(L);
lua_getglobal_checktype_or_return(L, "handle_wheel_changed", LUA_TFUNCTION);
lua_pushinteger(L, delta_y);
lua_pushinteger(L, delta_x);
pcall_handle_error(L, 2, 0);
lua_pop_error_handler(L);
LUA_API_EXIT();
}
static bool handle_key_evt(void *L, const char *evt_id, const char *key_code) {
LUA_API_ENTER(false);
lua_checkstack_or_return_val(L, 3, false);
lua_push_error_handler(L);
lua_getglobal_checktype_or_return_val(L, "handle_key_evt", LUA_TFUNCTION, false);
lua_pushstring(L, evt_id);
lua_pushstring(L, key_code);
int rc = pcall_handle_error(L, 2, 1);
if (rc == LUA_OK && lua_gettop(L) <= 0) {
char msg[2048];
snprintf(msg, sizeof(msg),
"handle_key_evt(evt_id=\"%s\", code=\"%s\") returned nil! "
"(top was %d) "
"Should return true or "
"false to indicate if key was handled or not",
evt_id, key_code, lua_gettop(L));
alex_log_err(msg);
// TODO may want to have the option to suppress this...
// after some number of logs, should always suppress it
api->set_status_err(msg, sizeof(msg));
}
bool return_value = lua_toboolean(L, -1);
lua_pop(L, 1);
lua_pop_error_handler(L);
LUA_API_EXIT();
return return_value;
}
// TODO maybe change the void *changed_touches type to struct touch_info[]
static void handle_touch_evt(void *L,
const char *evt_id_str, int evt_id_str_len,
void *changed_touches_ptr, int changed_touches_len) {
LUA_API_ENTER();
lua_checkstack_or_return(L, 3);
lua_push_error_handler(L);
lua_getglobal_checktype_or_return(L, "handle_touch_evt", LUA_TFUNCTION); // stack index 1
lua_pushlstring(L, evt_id_str, evt_id_str_len); // stack idx 2
lua_createtable(L, changed_touches_len, 0); // stack idx 3
//printf("Recvd touch evt id \"%.*s\", changed_touches_len = %d\n",
// evt_id_str_len, evt_id_str, changed_touches_len);
struct touch_info *changed_touches = (struct touch_info *)changed_touches_ptr;
for (int i=0; i<changed_touches_len; i++) {
struct touch_info *touch = changed_touches + i;
lua_checkstack_or_return(L, 2);
lua_createtable(L, 0, 3); // stack idx 4
lua_pushnumber(L, touch->id); // stack idx 5
lua_setfield(L, -2, "id");
lua_pushnumber(L, touch->y); // stack idx 5
lua_setfield(L, -2, "y");
lua_pushnumber(L, touch->x); // stack idx 5
lua_setfield(L, -2, "x");
lua_seti(L, -2, i+1); // Lua arrays are 1 indexed
}
pcall_handle_error(L, 2, 0);
lua_pop_error_handler(L);
LUA_API_EXIT();
}
static void handle_msg_received(void *L, const char *msg_src, int msg_src_len, const char *msg, int len) {
LUA_API_ENTER();
lua_checkstack_or_return(L, 3);
lua_push_error_handler(L);
lua_getglobal_checktype_or_return(L, "handle_msg_received", LUA_TFUNCTION);
lua_pushlstring(L, msg_src, msg_src_len);
lua_pushlstring(L, msg, len);
pcall_handle_error(L, 2, 0);
lua_pop_error_handler(L);
LUA_API_EXIT();
}
static void handle_btn_clicked(void *L, const char *btn_id) {
LUA_API_ENTER();
lua_checkstack_or_return(L, 2);
lua_push_error_handler(L);
lua_getglobal_checktype_or_return(L, "handle_btn_clicked", LUA_TFUNCTION);
lua_pushstring(L, btn_id);
pcall_handle_error(L, 1, 0);
lua_pop_error_handler(L);
LUA_API_EXIT();
}
static void handle_popup_btn_clicked(void *L, const char *popup_id, int btn_idx,
const struct popup_state *popup_state) {
LUA_API_ENTER();
lua_checkstack_or_return(L, 3);
lua_push_error_handler(L);
lua_getglobal_checktype_or_return(L, "handle_popup_btn_clicked", LUA_TFUNCTION);
lua_pushstring(L, popup_id);
lua_pushinteger(L, btn_idx);
if (popup_state == NULL) {
// This is only possible if the platform implementation (e.g. html/wasm, android, wxWidgets)
// didn't implement this functionality.
alex_log_err("WARNING: popup_state is NULL");
lua_pushnil(L);
} else {
lua_createtable(L, popup_state->items_count, 0);
int i;
for (i = 0; i < popup_state->items_count; i++) {
const struct popup_state_item *item = &popup_state->items[i];
lua_newtable(L);
lua_pushinteger(L, item->id);
lua_setfield(L, -2, "id");
lua_pushinteger(L, item->selected);
lua_setfield(L, -2, "selected");
lua_seti(L, -2, i + 1); // Lua is 1 indexed
}
}
pcall_handle_error(L, 3, 0);
lua_pop_error_handler(L);
LUA_API_EXIT();
}
static void handle_game_option_evt(void *L, enum option_type option_type, const char *option_id, int value) {
LUA_API_ENTER();
lua_checkstack_or_return(L, 2);
lua_push_error_handler(L);
lua_getglobal_checktype_or_return(L, "handle_game_option_evt", LUA_TFUNCTION);
lua_pushstring(L, option_id);
if (option_type == OPTION_TYPE_TOGGLE) {
lua_pushboolean(L, value);
} else {
lua_pushnil(L);
}
pcall_handle_error(L, 2, 0);
lua_pop_error_handler(L);
LUA_API_EXIT();
}
static void start_game(void *L, int session_id, const uint8_t *state, size_t state_len) {
LUA_API_ENTER();
lua_checkstack_or_return(L, 4);
lua_push_error_handler(L);
lua_getglobal_checktype_or_return(L, "start_game", LUA_TFUNCTION);
lua_pushinteger(L, session_id);
if (state != NULL) {
lua_pushlstring(L, (const char*)state, state_len);
} else {
lua_pushnil(L);
}
pcall_handle_error(L, 2, 0);
lua_pop_error_handler(L);
LUA_API_EXIT();
}
static lua_Integer read_bytearray(void *L, uint8_t *byteary_out, size_t byteary_out_len, const char *func_name) {
lua_Integer byte_len;
{
lua_len(L, -1);
// ensure that value isn't nil?
{
int is_int = 0;
byte_len = lua_tointegerx(L, -1, &is_int);
if (!is_int) {
luaL_error(L, "why the hell is the length not an integer???");
}
}
lua_pop(L, 1);
}
int i;
for (i=1; i<=byte_len; i++) {
if (i >= byteary_out_len) {