#include #include #include #include #include #include #include #include #include #include #include #include #include #include "lua.h" #include "lauxlib.h" #include "lualib.h" #include "audio.h" #include "font8x8.h" #undef ESP_LOGI #undef ESP_LOGW #undef ESP_LOGE #define ESP_LOGI(t,f,...) printf("[I] " f "\n",##__VA_ARGS__) #define ESP_LOGW(t,f,...) printf("[W] " f "\n",##__VA_ARGS__) #define ESP_LOGE(t,f,...) printf("[E] " f "\n",##__VA_ARGS__) #define TAG "LuaGame" #define GAME_W 240 #define GAME_H 240 #define TICK_MS 16 /* stubs for missing libc symbols */ void clearerr(FILE* f){(void)f;} clock_t clock(void){return 0;} char* getenv(const char* n){(void)n;return NULL;} struct tm* gmtime(const time_t* t){(void)t; static struct tm d={0};return &d;} char* setlocale(int c,const char* l){(void)c;(void)l;return NULL;} int setvbuf(FILE* s,char* b,int m,size_t z){(void)s;(void)b;(void)m;(void)z;return 0;} FILE* tmpfile(void){return NULL;} char* tmpnam(char* s){if(s)s[0]=0;return s;} int ungetc(int c,FILE* st){(void)st;return c;} typedef struct { AppHandle app; lv_obj_t* root; lv_obj_t* toolbar; lv_obj_t* game_area; lv_obj_t* canvas; lv_obj_t* label_layer; lv_obj_t* fps_label; uint16_t* fb; lv_timer_t* timer; TaskHandle_t lua_task; bool touch_pressed; int touch_x; int touch_y; lua_State* L; bool lua_ok; bool initialized; uint64_t last_tick_us; bool should_exit; // fps uint32_t fps_frames; uint64_t fps_last_us; float fps; } AppCtx; static AppCtx* g_ctx=NULL; static uint16_t hex_to_rgb565(uint32_t hex){ uint8_t r=(hex>>16)&0xFF,g=(hex>>8)&0xFF,b=hex&0xFF; return ((r>>3)<<11)|((g>>2)<<5)|(b>>3); } static inline void set_px(AppCtx* c,int x,int y,uint16_t col){ if(!c||!c->fb)return; if(x<0||y<0||x>=GAME_W||y>=GAME_H)return; c->fb[y*GAME_W+x]=col; } static void fill_rect(AppCtx* c,int x,int y,int w,int h,uint16_t col,bool filled){ if(w<=0||h<=0)return; if(!filled){ for(int i=x;i GAME_W? GAME_W:x+w,y1=y+h>GAME_H?GAME_H:y+h; for(int yy=y0;yyfb+yy*GAME_W; for(int xx=x0;xx=y){set_px(c,cx+x,cy+y,col);set_px(c,cx+y,cy+x,col);set_px(c,cx-y,cy+x,col);set_px(c,cx-x,cy+y,col);set_px(c,cx-x,cy-y,col);set_px(c,cx-y,cy-x,col);set_px(c,cx+y,cy-x,col);set_px(c,cx+x,cy-y,col); y++; if(err<=0) err+=2*y+1; if(err>0){x--; err-=2*x+1;}} } } static void draw_line(AppCtx* c,int x0,int y0,int x1,int y1,uint16_t col){ int dx=abs(x1-x0),dy=-abs(y1-y0),sx=x0=dy){err+=dy;x0+=sx;} if(e2<=dx){err+=dx;y0+=sy;}} } static void clear_labels(AppCtx* ctx){ // Now text is drawn directly to framebuffer, no LVGL labels to clean (void)ctx; } static void invalidate_canvas(AppCtx* ctx){if(!ctx||!ctx->canvas)return; lv_obj_invalidate(ctx->canvas);} static void draw_char8(AppCtx* ctx, int x, int y, char ch, uint16_t col){ unsigned char uch = (unsigned char)ch; if(uch>=128) return; // font8x8_basic is 8x8, each byte is row, bit 0 is leftmost? Actually font8x8_basic stores rows as bits for(int row=0; row<8; row++){ uint8_t bits = font8x8_basic[(int)ch][row]; for(int colb=0; colb<8; colb++){ if(bits & (1<GAME_W){ cx=x; cy+=10; } } } /* Lua bindings */ static int l_sys_log(lua_State* L){const char* m=luaL_checkstring(L,1); printf("[LUA] %s\n",m); return 0;} static int l_sys_clear(lua_State* L){ AppCtx* ctx=g_ctx; if(!ctx)return 0; uint32_t hex=0; if(lua_gettop(L)>=1) hex=(uint32_t)(luaL_checknumber(L,1)); fill_rect(ctx,0,0,GAME_W,GAME_H,hex_to_rgb565(hex),true); clear_labels(ctx); return 0; } static int l_sys_print(lua_State* L){ AppCtx* ctx=g_ctx; if(!ctx)return 0; int x=(int)luaL_checknumber(L,1); int y=(int)luaL_checknumber(L,2); const char* txt=luaL_checkstring(L,3); uint32_t hex=0xFFFFFF; if(lua_gettop(L)>=4) hex=(uint32_t)luaL_checknumber(L,4); uint16_t col = hex_to_rgb565(hex); draw_text8(ctx, x, y, txt, col); return 0; } static int l_sys_rect(lua_State* L){ AppCtx* ctx=g_ctx; if(!ctx)return 0; int x=(int)luaL_checknumber(L,1); int y=(int)luaL_checknumber(L,2); int w=(int)luaL_checknumber(L,3); int h=(int)luaL_checknumber(L,4); uint32_t hex=(uint32_t)luaL_checknumber(L,5); bool f=false; if(lua_gettop(L)>=6) f=lua_toboolean(L,6); fill_rect(ctx,x,y,w,h,hex_to_rgb565(hex),f); return 0; } static int l_sys_circle(lua_State* L){ AppCtx* ctx=g_ctx; if(!ctx)return 0; int x=(int)luaL_checknumber(L,1); int y=(int)luaL_checknumber(L,2); int r=(int)luaL_checknumber(L,3); uint32_t hex=(uint32_t)luaL_checknumber(L,4); bool f=false; if(lua_gettop(L)>=5) f=lua_toboolean(L,5); draw_circle(ctx,x,y,r,hex_to_rgb565(hex),f); return 0; } static int l_sys_line(lua_State* L){ AppCtx* ctx=g_ctx; if(!ctx)return 0; int x0=(int)luaL_checknumber(L,1); int y0=(int)luaL_checknumber(L,2); int x1=(int)luaL_checknumber(L,3); int y1=(int)luaL_checknumber(L,4); uint32_t hex=(uint32_t)luaL_checknumber(L,5); draw_line(ctx,x0,y0,x1,y1,hex_to_rgb565(hex)); return 0; } static int l_sys_touch(lua_State* L){ AppCtx* ctx=g_ctx; if(!ctx){lua_pushboolean(L,0); lua_pushinteger(L,0); lua_pushinteger(L,0); return 3;} lua_pushboolean(L,ctx->touch_pressed?1:0); lua_pushinteger(L,ctx->touch_x); lua_pushinteger(L,ctx->touch_y); return 3; } static int l_sys_tone(lua_State* L){ int note = (int)luaL_checknumber(L,1); int dur = 50; const char* wave = "sine"; float vol = 0.5f; if(lua_gettop(L)>=2) dur = (int)luaL_checknumber(L,2); if(lua_gettop(L)>=3) wave = luaL_checkstring(L,3); if(lua_gettop(L)>=4) vol = (float)luaL_checknumber(L,4); audio_play_tone(note, dur, wave, vol); return 0; } static int l_sys_sfx(lua_State* L){ const char* name = luaL_checkstring(L,1); printf("[LUA SFX] %s\n",name); audio_play_sfx(name); return 0; } static void register_sys(lua_State* L){ lua_newtable(L); lua_pushcfunction(L,l_sys_log); lua_setfield(L,-2,"log"); lua_pushcfunction(L,l_sys_clear); lua_setfield(L,-2,"clear"); lua_pushcfunction(L,l_sys_print); lua_setfield(L,-2,"print"); lua_pushcfunction(L,l_sys_rect); lua_setfield(L,-2,"rect"); lua_pushcfunction(L,l_sys_circle); lua_setfield(L,-2,"circle"); lua_pushcfunction(L,l_sys_line); lua_setfield(L,-2,"line"); lua_pushcfunction(L,l_sys_touch); lua_setfield(L,-2,"touch"); lua_pushcfunction(L,l_sys_tone); lua_setfield(L,-2,"tone"); lua_pushcfunction(L,l_sys_sfx); lua_setfield(L,-2,"sfx"); lua_setglobal(L,"sys"); } /* Embedded breakout - faster ball: 3.5 instead of 2 */ static const char* breakout_lua_src = "local PADDLE_W=40 local PADDLE_H=8 local BALL_SIZE=6 local BRICK_ROWS=4 local BRICK_COLS=6 local BRICK_W=34 local BRICK_H=8 local BRICK_GAP=4\n" "local score=0 local lives=3 local game_state='serve' local px,py=100,210 local bx,by=120,150 local bdx,bdy=4.5,-4.5 local bricks={}\n" "function init() sys.log('Init Breakout fast v2!') reset_bricks() reset_ball() end\n" "function reset_bricks() bricks={} for r=1,BRICK_ROWS do bricks[r]={} for c=1,BRICK_COLS do bricks[r][c]=true end end end\n" "function reset_ball() bx=120 by=150 bdx=4.5 bdy=-4.5 game_state='serve' end\n" "function update(dt) local pressed,tx,ty=sys.touch() if pressed then px=tx-PADDLE_W/2 if px<0 then px=0 end if px>240-PADDLE_W then px=240-PADDLE_W end if game_state=='serve' then game_state='play' sys.sfx('confirm') elseif game_state=='gameover' or game_state=='win' then score=0 lives=3 reset_bricks() reset_ball() end end if game_state=='play' then bx=bx+bdx by=by+bdy if bx<=0 or bx>=240-BALL_SIZE then bdx=-bdx sys.tone(60,80,'sine',0.4) end if by<=0 then bdy=-bdy sys.tone(64,80,'sine',0.4) end if by>=240 then lives=lives-1 sys.sfx('hurt') if lives<=0 then game_state='gameover' sys.sfx('gameover') else reset_ball() end end if by+BALL_SIZE>=py and by<=py+PADDLE_H then if bx+BALL_SIZE>=px and bx<=px+PADDLE_W then bdy=-math.abs(bdy) local hp=(bx+BALL_SIZE/2)-(px+PADDLE_W/2) bdx=hp*0.25 sys.tone(72,100,'triangle',0.5) end end local hit=false for r=1,BRICK_ROWS do for c=1,BRICK_COLS do if bricks[r][c] then local bmin=(c-1)*(BRICK_W+BRICK_GAP)+BRICK_GAP local bymin=r*(BRICK_H+BRICK_GAP)+20 if bx+BALL_SIZE>=bmin and bx<=bmin+BRICK_W and by+BALL_SIZE>=bymin and by<=bymin+BRICK_H then bricks[r][c]=false bdy=-bdy score=score+10 sys.sfx('coin') hit=true break end end end if hit then break end end local has=false for r=1,BRICK_ROWS do for c=1,BRICK_COLS do if bricks[r][c] then has=true end end end if not has then game_state='win' sys.sfx('levelup') end end end\n" "function draw() sys.clear(0x1e1e2e) sys.print(4,4,'SCORE: '..score,0xf5e0dc) sys.print(160,4,'LIVES: '..lives,0xf5e0dc) sys.line(0,16,240,16,0x585b70) sys.rect(px,py,PADDLE_W,PADDLE_H,0x89b4fa,true) sys.circle(bx+BALL_SIZE/2,by+BALL_SIZE/2,BALL_SIZE/2,0xf9e2af,true) for r=1,BRICK_ROWS do for c=1,BRICK_COLS do if bricks[r][c] then local bmin=(c-1)*(BRICK_W+BRICK_GAP)+BRICK_GAP local bymin=r*(BRICK_H+BRICK_GAP)+20 local col=0xf38ba8 if r==2 then col=0xf9e2af elseif r==3 then col=0xa6e3a1 elseif r==4 then col=0x89b4fa end sys.rect(bmin,bymin,BRICK_W,BRICK_H,col,true) end end end if game_state=='serve' then sys.print(40,120,'TAP TO SERVE',0xa6e3a1) elseif game_state=='gameover' then sys.print(80,110,'GAME OVER',0xf38ba8) sys.print(45,130,'TAP AGAIN',0xa6e3a1) elseif game_state=='win' then sys.print(90,110,'YOU WIN!',0xa6e3a1) sys.print(45,130,'TAP AGAIN',0x89b4fa) end end\n" ; static void* alloc_psram(size_t sz){ void* p=heap_caps_malloc(sz,MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT); if(!p) p=heap_caps_malloc(sz,MALLOC_CAP_8BIT); if(!p) p=malloc(sz); return p; } static void game_loop_task(void* arg); static char* load_file_to_mem(const char* path, size_t* out_len){ FILE* f=fopen(path,"rb"); if(!f) return NULL; fseek(f,0,SEEK_END); long sz=ftell(f); fseek(f,0,SEEK_SET); if(sz<=0 || sz>200*1024){ fclose(f); return NULL; } char* buf=(char*)alloc_psram(sz+1); if(!buf){ fclose(f); return NULL; } size_t r=fread(buf,1,sz,f); fclose(f); if(r!=(size_t)sz){ heap_caps_free(buf); return NULL; } buf[sz]=0; if(out_len) *out_len=sz; return buf; } static void lua_init_task(void* arg){ AppCtx* ctx=(AppCtx*)arg; printf("[LuaGame] init task start\n"); audio_init(); ctx->L=luaL_newstate(); if(!ctx->L){ printf("[LuaGame] lua newstate fail\n"); vTaskDelete(NULL); return;} luaL_openlibs(ctx->L); register_sys(ctx->L); const char* external_paths[]={ "/sdcard/lua/breakout.lua", "/sdcard/lua/game.lua", NULL }; char* src_buf=NULL; size_t src_len=0; const char* loaded_from="embedded"; // For this build, force embedded to demonstrate faster ball + audio. // To test external file, uncomment loop below. bool use_external = false; // set true to load SD file if(use_external){ for(int i=0; external_paths[i]; i++){ src_buf=load_file_to_mem(external_paths[i], &src_len); if(src_buf){ loaded_from=external_paths[i]; printf("[LuaGame] loaded external %s %d bytes\n",loaded_from,(int)src_len); break; } } } int st; if(src_buf){ st=luaL_loadbuffer(ctx->L,src_buf,src_len,loaded_from); heap_caps_free(src_buf); }else{ printf("[LuaGame] loading embedded lua %d bytes\n",(int)strlen(breakout_lua_src)); st=luaL_loadstring(ctx->L,breakout_lua_src); loaded_from="embedded"; } if(st!=LUA_OK){const char* e=lua_tostring(ctx->L,-1); printf("[LuaGame] load err (%s) %s\n",loaded_from,e); lua_pop(ctx->L,1); vTaskDelete(NULL); return;} if(lua_pcall(ctx->L,0,0,0)!=LUA_OK){const char* e=lua_tostring(ctx->L,-1); printf("[LuaGame] pcall err %s\n",e); lua_pop(ctx->L,1); vTaskDelete(NULL); return;} lua_getglobal(ctx->L,"init"); if(lua_isfunction(ctx->L,-1)){ if(lua_pcall(ctx->L,0,0,0)!=LUA_OK){const char* e=lua_tostring(ctx->L,-1); printf("[LuaGame] init err %s\n",e); lua_pop(ctx->L,1);} }else lua_pop(ctx->L,1); ctx->lua_ok=true; ctx->initialized=true; ctx->last_tick_us=esp_timer_get_time(); ctx->fps_last_us=esp_timer_get_time(); ctx->fps_frames=0; printf("[LuaGame] init done OK from %s\n",loaded_from); // Instead of creating new task (which may fail due to memory), reuse this init task as game loop printf("[LuaGame] entering game loop in same task\n"); // Enter game loop directly while(!ctx->should_exit){ if(ctx->lua_ok && ctx->initialized && ctx->L){ uint64_t now=esp_timer_get_time(); float dt= ctx->last_tick_us==0? (TICK_MS/1000.0f) : (now-ctx->last_tick_us)/1000000.0f; if(dt>0.1f) dt=0.1f; ctx->last_tick_us=now; lua_State* L=ctx->L; lua_getglobal(L,"update"); if(lua_isfunction(L,-1)){ lua_pushnumber(L,dt); if(lua_pcall(L,1,0,0)!=LUA_OK){const char* e=lua_tostring(L,-1); printf("[LUA ERR update] %s\n",e); lua_pop(L,1); ctx->lua_ok=false;} }else lua_pop(L,1); lua_getglobal(L,"draw"); if(lua_isfunction(L,-1)){ if(lua_pcall(L,0,0,0)!=LUA_OK){const char* e=lua_tostring(L,-1); printf("[LUA ERR draw] %s\n",e); lua_pop(L,1); ctx->lua_ok=false;} }else lua_pop(L,1); if(tt_lvgl_lock(portMAX_DELAY)){ invalidate_canvas(ctx); ctx->fps_frames++; uint64_t elapsed = now - ctx->fps_last_us; if(elapsed >= 1000000){ ctx->fps = (float)ctx->fps_frames * 1000000.0f / (float)elapsed; printf("[LuaGame] FPS %.1f\n", ctx->fps); if(ctx->fps_label){ char buf[32]; snprintf(buf,sizeof(buf),"FPS %.0f",ctx->fps); lv_label_set_text(ctx->fps_label, buf); } ctx->fps_frames=0; ctx->fps_last_us=now; } tt_lvgl_unlock(); } } vTaskDelay(pdMS_TO_TICKS(TICK_MS)); } printf("[LuaGame] game loop exit\n"); vTaskDelete(NULL); } static void game_loop_task(void* arg){ AppCtx* ctx=(AppCtx*)arg; printf("[LuaGame] game loop task start\n"); while(!ctx->should_exit){ if(ctx->lua_ok && ctx->initialized && ctx->L){ uint64_t now=esp_timer_get_time(); float dt= ctx->last_tick_us==0? (TICK_MS/1000.0f) : (now-ctx->last_tick_us)/1000000.0f; if(dt>0.1f) dt=0.1f; // clamp ctx->last_tick_us=now; lua_State* L=ctx->L; lua_getglobal(L,"update"); if(lua_isfunction(L,-1)){ lua_pushnumber(L,dt); if(lua_pcall(L,1,0,0)!=LUA_OK){const char* e=lua_tostring(L,-1); printf("[LUA ERR update] %s\n",e); lua_pop(L,1); ctx->lua_ok=false;} }else lua_pop(L,1); lua_getglobal(L,"draw"); if(lua_isfunction(L,-1)){ if(lua_pcall(L,0,0,0)!=LUA_OK){const char* e=lua_tostring(L,-1); printf("[LUA ERR draw] %s\n",e); lua_pop(L,1); ctx->lua_ok=false;} }else lua_pop(L,1); // Invalidate canvas with LVGL lock if(tt_lvgl_lock(portMAX_DELAY)){ invalidate_canvas(ctx); // fps tracking inside lock for label update ctx->fps_frames++; uint64_t elapsed = now - ctx->fps_last_us; if(elapsed >= 1000000){ ctx->fps = (float)ctx->fps_frames * 1000000.0f / (float)elapsed; printf("[LuaGame] FPS %.1f\n", ctx->fps); if(ctx->fps_label){ char buf[32]; snprintf(buf,sizeof(buf),"FPS %.0f",ctx->fps); lv_label_set_text(ctx->fps_label, buf); } ctx->fps_frames=0; ctx->fps_last_us=now; } tt_lvgl_unlock(); } } vTaskDelay(pdMS_TO_TICKS(TICK_MS)); } printf("[LuaGame] game loop task exit\n"); vTaskDelete(NULL); } static void touch_event_cb(lv_event_t* e){ AppCtx* ctx=(AppCtx*)lv_event_get_user_data(e); if(!ctx)return; lv_event_code_t code=lv_event_get_code(e); lv_point_t pt; lv_indev_get_point(lv_indev_active(),&pt); if(ctx->canvas){ lv_area_t area; lv_obj_get_coords(ctx->canvas,&area); int lx=pt.x-area.x1; int ly=pt.y-area.y1; if(lx<0) lx=0; if(lx>=GAME_W) lx=GAME_W-1; if(ly<0) ly=0; if(ly>=GAME_H) ly=GAME_H-1; ctx->touch_x=lx; ctx->touch_y=ly; }else{ctx->touch_x=pt.x; ctx->touch_y=pt.y;} if(code==LV_EVENT_PRESSING||code==LV_EVENT_PRESSED||code==LV_EVENT_CLICKED) ctx->touch_pressed=true; else if(code==LV_EVENT_RELEASED||code==LV_EVENT_PRESS_LOST) ctx->touch_pressed=false; } static void* app_create_data(void){return calloc(1,sizeof(AppCtx));} static void app_destroy_data(void* d){ AppCtx* ctx=d; if(!ctx)return; ctx->should_exit=true; if(ctx->L) lua_close(ctx->L); if(ctx->fb) heap_caps_free(ctx->fb); free(ctx); } static void app_on_create(AppHandle a,void* d){AppCtx* c=d; if(c) c->app=a;} static void app_on_destroy(AppHandle a,void* d){(void)a;(void)d;} static void app_on_show(AppHandle app,void* data,lv_obj_t* parent){ AppCtx* ctx=data; if(!ctx)return; g_ctx=ctx; ctx->app=app; ctx->lua_ok=false; ctx->initialized=false; ctx->should_exit=false; ctx->fps=0; ctx->fps_frames=0; lv_obj_remove_flag(parent,LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_pad_all(parent,0,0); lv_obj_set_style_bg_color(parent,lv_color_black(),0); lv_obj_set_flex_flow(parent,LV_FLEX_FLOW_COLUMN); lv_obj_set_style_pad_row(parent,0,0); ctx->toolbar=tt_lvgl_toolbar_create_for_app(parent,app); ctx->root=parent; ctx->game_area=lv_obj_create(parent); lv_obj_set_width(ctx->game_area,LV_PCT(100)); lv_obj_set_flex_grow(ctx->game_area,1); lv_obj_set_style_bg_color(ctx->game_area,lv_color_black(),0); lv_obj_set_style_border_width(ctx->game_area,0,0); lv_obj_set_style_pad_all(ctx->game_area,0,0); lv_obj_set_flex_flow(ctx->game_area,LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(ctx->game_area,LV_FLEX_ALIGN_CENTER,LV_FLEX_ALIGN_CENTER,LV_FLEX_ALIGN_CENTER); lv_obj_remove_flag(ctx->game_area,LV_OBJ_FLAG_SCROLLABLE); lv_obj_add_flag(ctx->game_area,LV_OBJ_FLAG_CLICKABLE); size_t fb_bytes=GAME_W*GAME_H*sizeof(uint16_t); ctx->fb=alloc_psram(fb_bytes); if(!ctx->fb){printf("[LuaGame] FB alloc fail\n"); return;} memset(ctx->fb,0,fb_bytes); lv_obj_t* cont=lv_obj_create(ctx->game_area); lv_obj_set_size(cont,GAME_W,GAME_H); lv_obj_set_style_bg_color(cont,lv_color_black(),0); lv_obj_set_style_border_width(cont,0,0); lv_obj_set_style_pad_all(cont,0,0); lv_obj_remove_flag(cont,LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_radius(cont,0,0); ctx->canvas=lv_canvas_create(cont); lv_canvas_set_buffer(ctx->canvas,ctx->fb,GAME_W,GAME_H,LV_COLOR_FORMAT_RGB565); lv_obj_set_style_bg_opa(ctx->canvas,LV_OPA_COVER,0); lv_obj_remove_flag(ctx->canvas,LV_OBJ_FLAG_SCROLLABLE); ctx->label_layer=lv_obj_create(cont); lv_obj_set_size(ctx->label_layer,GAME_W,GAME_H); lv_obj_set_pos(ctx->label_layer,0,0); lv_obj_set_style_bg_opa(ctx->label_layer,LV_OPA_TRANSP,0); lv_obj_set_style_border_width(ctx->label_layer,0,0); lv_obj_set_style_pad_all(ctx->label_layer,0,0); lv_obj_remove_flag(ctx->label_layer,LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(ctx->label_layer,LV_OBJ_FLAG_CLICKABLE); // FPS label outside canvas, top of game_area ctx->fps_label=lv_label_create(ctx->game_area); lv_label_set_text(ctx->fps_label,"FPS --"); lv_obj_set_style_text_color(ctx->fps_label, lv_color_white(),0); lv_obj_set_style_text_font(ctx->fps_label, lv_font_get_default(),0); lv_obj_add_event_cb(ctx->game_area,touch_event_cb,LV_EVENT_PRESSED,ctx); lv_obj_add_event_cb(ctx->game_area,touch_event_cb,LV_EVENT_PRESSING,ctx); lv_obj_add_event_cb(ctx->game_area,touch_event_cb,LV_EVENT_RELEASED,ctx); lv_obj_add_event_cb(ctx->game_area,touch_event_cb,LV_EVENT_PRESS_LOST,ctx); lv_obj_add_event_cb(ctx->game_area,touch_event_cb,LV_EVENT_SHORT_CLICKED,ctx); lv_obj_add_event_cb(cont,touch_event_cb,LV_EVENT_PRESSED,ctx); lv_obj_add_event_cb(cont,touch_event_cb,LV_EVENT_PRESSING,ctx); lv_obj_add_event_cb(cont,touch_event_cb,LV_EVENT_RELEASED,ctx); lv_obj_add_event_cb(cont,touch_event_cb,LV_EVENT_PRESS_LOST,ctx); printf("[LuaGame] spawning init task\n"); xTaskCreate(lua_init_task,"lua_init",16384,ctx,5,&ctx->lua_task); fill_rect(ctx,0,0,GAME_W,GAME_H,hex_to_rgb565(0x1e1e2e),true); if(tt_lvgl_lock(portMAX_DELAY)){ invalidate_canvas(ctx); tt_lvgl_unlock(); } printf("[LuaGame] on_show done\n"); } static void app_on_hide(AppHandle app,void* data){ (void)app; AppCtx* ctx=data; if(!ctx)return; ctx->should_exit=true; vTaskDelay(200/portTICK_PERIOD_MS); audio_deinit(); if(ctx->L){lua_close(ctx->L); ctx->L=NULL;} ctx->canvas=NULL; ctx->label_layer=NULL; ctx->fps_label=NULL; ctx->game_area=NULL; ctx->root=NULL; ctx->toolbar=NULL; g_ctx=NULL; printf("[LuaGame] on_hide\n"); } int main(int argc,char* argv[]){ (void)argc;(void)argv; tt_app_register((AppRegistration){ .createData=app_create_data, .destroyData=app_destroy_data, .onCreate=app_on_create, .onDestroy=app_on_destroy, .onShow=app_on_show, .onHide=app_on_hide }); return 0; }