fix(LuaGame): PSRAM Lua alloc, conditional audio, exit gesture, on-demand

- Custom lua_psram_alloc uses heap_caps_malloc(SPIRAM|8BIT) fallback to reduce internal heap pressure
  - Fixes sdmmc_read_sectors not enough mem (0x101) and MemoryChecker low 359 bytes
- Audio skipped for solitaire (no sfx/tone in source) to save RAM: 'audio not needed, skipping SfxEngine'
- Exit: game.exit() now calls tt_app_stop() + sets should_exit, top-right 40x40 tap gesture calls tt_app_stop()
- Fullscreen 320x480 no toolbar/status bar, dynamic fb_w/h, touch clamping uses game_w/h
- On-demand refresh for solitaire (wants_frame_updates false): only redraw on touch, FPS 0.1-1.5 vs 16 continuous, no FPS spam
- Initial draw forced after init for on-demand games (was black screen)
- Screenshot now 220KB full color vs 1.2K 4-bit, shows solitaire cards with optimized LEFT=4 GAP=4 TOP_Y=4 TAB_Y=60 (was 37/10/30/98) - no window chrome
- Tested solitaire loads external 24430 bytes, pico=1, no Lua errors
This commit is contained in:
Adolfo
2026-07-31 13:23:40 -04:00
parent 4e4e815995
commit ca1202345e
+56 -18
View File
@@ -44,6 +44,26 @@ FILE* tmpfile(void){return NULL;}
char* tmpnam(char* s){if(s)s[0]=0;return s;} char* tmpnam(char* s){if(s)s[0]=0;return s;}
int ungetc(int c,FILE* st){(void)st;return c;} int ungetc(int c,FILE* st){(void)st;return c;}
/* Custom Lua allocator using PSRAM to reduce internal memory pressure */
static void* lua_psram_alloc(void *ud, void *ptr, size_t osize, size_t nsize){
(void)ud; (void)osize;
if(nsize==0){
if(ptr) heap_caps_free(ptr);
return NULL;
}
void* newptr = heap_caps_malloc(nsize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if(!newptr){
newptr = heap_caps_malloc(nsize, MALLOC_CAP_8BIT);
}
if(!newptr) return NULL;
if(ptr){
size_t copy = osize < nsize ? osize : nsize;
memcpy(newptr, ptr, copy);
heap_caps_free(ptr);
}
return newptr;
}
typedef struct { typedef struct {
AppHandle app; AppHandle app;
lv_obj_t* root; lv_obj_t* root;
@@ -393,8 +413,9 @@ static int l_game_set_frame_updates(lua_State* L){
static int l_game_exit(lua_State* L){ static int l_game_exit(lua_State* L){
(void)L; (void)L;
AppCtx* ctx=g_ctx; AppCtx* ctx=g_ctx;
printf("[LuaGame] game.exit() called\n"); printf("[LuaGame] game.exit() called -> stopping app\n");
if(ctx) ctx->should_exit = true; if(ctx) ctx->should_exit = true;
tt_app_stop();
return 0; return 0;
} }
@@ -484,9 +505,9 @@ static char* load_file_to_mem(const char* path, size_t* out_len){
} }
static void lua_init_task(void* arg){ static void lua_init_task(void* arg){
AppCtx* ctx=(AppCtx*)arg; AppCtx* ctx=(AppCtx*)arg;
printf("[LuaGame] init task start\n"); printf("[LuaGame] init task start (PSRAM alloc)\n");
audio_init(); // Use PSRAM allocator for Lua to reduce internal memory pressure
ctx->L=luaL_newstate(); ctx->L=lua_newstate(lua_psram_alloc, NULL);
if(!ctx->L){ printf("[LuaGame] lua newstate fail\n"); vTaskDelete(NULL); return;} if(!ctx->L){ printf("[LuaGame] lua newstate fail\n"); vTaskDelete(NULL); return;}
luaL_openlibs(ctx->L); luaL_openlibs(ctx->L);
register_sys(ctx->L); register_sys(ctx->L);
@@ -527,14 +548,27 @@ static void lua_init_task(void* arg){
} }
// Detect Pico API by content before loading // Detect Pico API by content before loading
bool detected_pico = false; bool detected_pico = false;
bool needs_audio = false;
if(src_buf){ if(src_buf){
if(strstr(src_buf,"renderer.")!=NULL || strstr(src_buf,"INPUT.")!=NULL){ if(strstr(src_buf,"renderer.")!=NULL || strstr(src_buf,"INPUT.")!=NULL){
detected_pico = true; detected_pico = true;
} }
if(strstr(src_buf,"sfx")!=NULL || strstr(src_buf,"tone")!=NULL){
needs_audio = true;
}
} else { } else {
if(strstr(embedded_breakout_lua_src,"renderer.")!=NULL){ if(strstr(embedded_breakout_lua_src,"renderer.")!=NULL){
detected_pico = true; detected_pico = true;
} }
if(strstr(embedded_breakout_lua_src,"sfx")!=NULL || strstr(embedded_breakout_lua_src,"tone")!=NULL){
needs_audio = true;
}
}
if(needs_audio){
printf("[LuaGame] audio needed, init SfxEngine\n");
audio_init();
} else {
printf("[LuaGame] audio not needed, skipping SfxEngine to save RAM\n");
} }
int st; int st;
@@ -746,6 +780,14 @@ static void touch_event_cb(lv_event_t* e){
if(ly>=max_h) ly=max_h-1; if(ly>=max_h) ly=max_h-1;
ctx->touch_x=lx; ctx->touch_y=ly; ctx->touch_x=lx; ctx->touch_y=ly;
}else{ctx->touch_x=pt.x; ctx->touch_y=pt.y;} }else{ctx->touch_x=pt.x; ctx->touch_y=pt.y;}
if(code==LV_EVENT_PRESSED){
// Top-right corner 40x40 as exit gesture (since no toolbar)
if(ctx->touch_x > max_w - 40 && ctx->touch_y < 40){
printf("[LuaGame] exit gesture top-right tap -> stopping app\n");
tt_app_stop();
return;
}
}
if(code==LV_EVENT_PRESSING||code==LV_EVENT_PRESSED||code==LV_EVENT_CLICKED) ctx->touch_pressed=true; 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; else if(code==LV_EVENT_RELEASED||code==LV_EVENT_PRESS_LOST) ctx->touch_pressed=false;
} }
@@ -807,22 +849,18 @@ static void app_on_show(AppHandle app,void* data,lv_obj_t* parent){
lv_obj_remove_flag(cont,LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(cont,LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_style_radius(cont,0,0); lv_obj_set_style_radius(cont,0,0);
ctx->canvas=lv_canvas_create(cont); ctx->canvas=lv_canvas_create(cont);
lv_canvas_set_buffer(ctx->canvas,ctx->fb,GAME_W,GAME_H,LV_COLOR_FORMAT_RGB565); lv_canvas_set_buffer(ctx->canvas,ctx->fb,ctx->fb_w,ctx->fb_h,LV_COLOR_FORMAT_RGB565);
lv_obj_set_style_bg_opa(ctx->canvas,LV_OPA_COVER,0); lv_obj_set_style_bg_opa(ctx->canvas,LV_OPA_COVER,0);
lv_obj_remove_flag(ctx->canvas,LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(ctx->canvas,LV_OBJ_FLAG_SCROLLABLE);
ctx->label_layer=lv_obj_create(cont); // Label layer removed to save RAM - text now drawn directly to FB
lv_obj_set_size(ctx->label_layer,GAME_W,GAME_H); ctx->label_layer=NULL;
lv_obj_set_pos(ctx->label_layer,0,0); // FPS label kept small but optional - remove to save RAM for solitaire
lv_obj_set_style_bg_opa(ctx->label_layer,LV_OPA_TRANSP,0); // For now, keep disabled to save internal memory
lv_obj_set_style_border_width(ctx->label_layer,0,0); ctx->fps_label=NULL;
lv_obj_set_style_pad_all(ctx->label_layer,0,0); // If you want FPS, uncomment:
lv_obj_remove_flag(ctx->label_layer,LV_OBJ_FLAG_SCROLLABLE); // ctx->fps_label=lv_label_create(ctx->game_area);
lv_obj_remove_flag(ctx->label_layer,LV_OBJ_FLAG_CLICKABLE); // lv_label_set_text(ctx->fps_label,"FPS --");
// FPS label outside canvas, top of game_area // lv_obj_set_style_text_color(ctx->fps_label, lv_color_white(),0);
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_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_PRESSING,ctx);