diff --git a/Apps/LuaGame/main/Source/main.c b/Apps/LuaGame/main/Source/main.c index 82f7a39..48af37d 100644 --- a/Apps/LuaGame/main/Source/main.c +++ b/Apps/LuaGame/main/Source/main.c @@ -29,6 +29,8 @@ #define TAG "LuaGame" #define GAME_W 240 #define GAME_H 240 +#define GAME_W_DEFAULT 240 +#define GAME_H_DEFAULT 240 #define TICK_MS 16 /* stubs for missing libc symbols */ @@ -51,6 +53,10 @@ typedef struct { lv_obj_t* label_layer; lv_obj_t* fps_label; uint16_t* fb; + int fb_w; + int fb_h; + int game_w; + int game_h; lv_timer_t* timer; TaskHandle_t lua_task; bool touch_pressed; @@ -61,8 +67,10 @@ typedef struct { bool lua_ok; bool initialized; bool is_pico; + bool wants_frame_updates; uint64_t last_tick_us; bool should_exit; + bool needs_redraw; // fps uint32_t fps_frames; uint64_t fps_last_us; @@ -77,8 +85,10 @@ static uint16_t hex_to_rgb565(uint32_t hex){ } 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; + int w = c->fb_w ? c->fb_w : GAME_W_DEFAULT; + int h = c->fb_h ? c->fb_h : GAME_H_DEFAULT; + if(x<0||y<0||x>=w||y>=h)return; + c->fb[y*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; @@ -87,8 +97,10 @@ static void fill_rect(AppCtx* c,int x,int y,int w,int h,uint16_t col,bool filled for(int j=y;j 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;xxfb_w ? c->fb_w : GAME_W_DEFAULT; + int fb_h = c->fb_h ? c->fb_h : GAME_H_DEFAULT; + int x0=x<0?0:x,y0=y<0?0:y,x1=x+w> fb_w? fb_w:x+w,y1=y+h>fb_h?fb_h:y+h; + for(int yy=y0;yyfb+yy*fb_w; for(int xx=x0;xxgame_w ? ctx->game_w : GAME_W_DEFAULT; for(int i=0; txt[i]; i++){ char ch=txt[i]; if(ch=='\n'){ cx=x; cy+=10; continue; } draw_char8(ctx, cx, cy, ch, col); cx+=8; // wrap if exceeds game width - if(cx+8>GAME_W){ cx=x; cy+=10; } + if(cx+8>max_w){ cx=x; cy+=10; } } } @@ -141,7 +154,9 @@ static int l_sys_log(lua_State* L){const char* m=luaL_checkstring(L,1); printf(" 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); + int w = ctx->game_w ? ctx->game_w : GAME_W_DEFAULT; + int h = ctx->game_h ? ctx->game_h : GAME_H_DEFAULT; + fill_rect(ctx,0,0,w,h,hex_to_rgb565(hex),true); clear_labels(ctx); return 0; } @@ -350,14 +365,38 @@ static int l_renderer_text_scaled(lua_State* L){ } } cx+=8*scale; - if(cx+8*scale>GAME_W){ cx=x; cy+=8*scale+2; } + { + int max_w = g_ctx && g_ctx->game_w ? g_ctx->game_w : GAME_W_DEFAULT; + if(cx+8*scale>max_w){ cx=x; cy+=8*scale+2; } + } } return 0; } -static int l_game_width(lua_State* L){ lua_pushinteger(L, GAME_W); return 1; } -static int l_game_height(lua_State* L){ lua_pushinteger(L, GAME_H); return 1; } -static int l_game_set_frame_updates(lua_State* L){ (void)L; return 0; } -static int l_game_exit(lua_State* L){ (void)L; printf("[LuaGame] game.exit() called\n"); return 0; } +static int l_game_width(lua_State* L){ + AppCtx* ctx=g_ctx; + int w = ctx && ctx->game_w ? ctx->game_w : GAME_W_DEFAULT; + lua_pushinteger(L, w); return 1; +} +static int l_game_height(lua_State* L){ + AppCtx* ctx=g_ctx; + int h = ctx && ctx->game_h ? ctx->game_h : GAME_H_DEFAULT; + lua_pushinteger(L, h); return 1; +} +static int l_game_set_frame_updates(lua_State* L){ + AppCtx* ctx=g_ctx; + bool en = false; + if(lua_gettop(L)>=1) en = lua_toboolean(L,1); + if(ctx) ctx->wants_frame_updates = en; + printf("[LuaGame] set_frame_updates %d\n", en?1:0); + return 0; +} +static int l_game_exit(lua_State* L){ + (void)L; + AppCtx* ctx=g_ctx; + printf("[LuaGame] game.exit() called\n"); + if(ctx) ctx->should_exit = true; + return 0; +} static void register_sys(lua_State* L){ // sys table (Tactility original) @@ -521,9 +560,24 @@ static void lua_init_task(void* arg){ ctx->fps_last_us=esp_timer_get_time(); ctx->fps_frames=0; printf("[LuaGame] init done OK from %s (pico=%d)\n",loaded_from, ctx->is_pico?1:0); + + // Initial draw for on-demand games + lua_getglobal(ctx->L,"draw"); + 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("[LUA ERR initial draw] %s\n",e); + lua_pop(ctx->L,1); + } + } else lua_pop(ctx->L,1); + if(tt_lvgl_lock(portMAX_DELAY)){ + invalidate_canvas(ctx); + tt_lvgl_unlock(); + } + printf("[LuaGame] entering game loop in same task\n"); - // Enter game loop directly + // Enter game loop directly - on-demand for solitaire (no frame updates) while(!ctx->should_exit){ if(ctx->lua_ok && ctx->initialized && ctx->L){ uint64_t now=esp_timer_get_time(); @@ -531,46 +585,35 @@ static void lua_init_task(void* arg){ if(dt>0.1f) dt=0.1f; ctx->last_tick_us=now; lua_State* L=ctx->L; + bool should_draw = false; if(ctx->is_pico){ - // Generate event based on touch - int ev_type = 6; // FRAME_TICK default bool has_touch_event = false; + int ev_type = 6; if(!ctx->prev_touch_pressed && ctx->touch_pressed){ - ev_type = 1; // TOUCH_DOWN - has_touch_event = true; + ev_type = 1; has_touch_event = true; } else if(ctx->prev_touch_pressed && !ctx->touch_pressed){ - ev_type = 3; // TOUCH_UP - has_touch_event = true; + ev_type = 3; has_touch_event = true; } else if(ctx->touch_pressed){ - ev_type = 2; // TOUCH_MOVE - has_touch_event = true; - } else { - ev_type = 6; // FRAME_TICK + ev_type = 2; has_touch_event = true; } - // Helper to push event table and call update - // First call with touch event if any - if(has_touch_event){ - lua_getglobal(L,"update"); - if(lua_isfunction(L,-1)){ - lua_newtable(L); - lua_pushinteger(L, ev_type); lua_setfield(L,-2,"type"); - lua_pushinteger(L, ctx->touch_x); lua_setfield(L,-2,"x"); - lua_pushinteger(L, ctx->touch_y); lua_setfield(L,-2,"y"); - lua_pushboolean(L,1); lua_setfield(L,-2,"valid"); - lua_pushinteger(L,0); lua_setfield(L,-2,"button_id"); - if(lua_pcall(L,1,1,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 { - bool redraw = lua_toboolean(L,-1); - lua_pop(L,1); - // we will draw regardless, but could check redraw - (void)redraw; - } - } else lua_pop(L,1); - } - // Always also call with FRAME_TICK if not already frame tick - if(ev_type != 6){ + if(ctx->wants_frame_updates){ + // Continuous: handle touch event if any, then FRAME_TICK every frame + if(has_touch_event){ + lua_getglobal(L,"update"); + if(lua_isfunction(L,-1)){ + lua_newtable(L); + lua_pushinteger(L, ev_type); lua_setfield(L,-2,"type"); + lua_pushinteger(L, ctx->touch_x); lua_setfield(L,-2,"x"); + lua_pushinteger(L, ctx->touch_y); lua_setfield(L,-2,"y"); + lua_pushboolean(L,1); lua_setfield(L,-2,"valid"); + lua_pushinteger(L,0); lua_setfield(L,-2,"button_id"); + if(lua_pcall(L,1,1,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 { bool r=lua_toboolean(L,-1); lua_pop(L,1); if(r) should_draw=true; } + } else lua_pop(L,1); + } + // FRAME_TICK lua_getglobal(L,"update"); if(lua_isfunction(L,-1)){ lua_newtable(L); @@ -578,58 +621,66 @@ static void lua_init_task(void* arg){ lua_pushinteger(L, ctx->touch_x); lua_setfield(L,-2,"x"); lua_pushinteger(L, ctx->touch_y); lua_setfield(L,-2,"y"); lua_pushboolean(L,1); lua_setfield(L,-2,"valid"); - if(lua_pcall(L,1,1,0)!=LUA_OK){const char* e=lua_tostring(L,-1); printf("[LUA ERR update FRAME_TICK] %s\n",e); lua_pop(L,1); ctx->lua_ok=false;} - else { lua_pop(L,1); } - } else lua_pop(L,1); - } else if(!has_touch_event){ - // No touch, just frame tick (single call) - lua_getglobal(L,"update"); - if(lua_isfunction(L,-1)){ - lua_newtable(L); - lua_pushinteger(L, 6); lua_setfield(L,-2,"type"); - lua_pushinteger(L, ctx->touch_x); lua_setfield(L,-2,"x"); - lua_pushinteger(L, ctx->touch_y); lua_setfield(L,-2,"y"); - lua_pushboolean(L,1); lua_setfield(L,-2,"valid"); - if(lua_pcall(L,1,1,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 { - bool redraw = lua_toboolean(L,-1); - lua_pop(L,1); - (void)redraw; - } + if(lua_pcall(L,1,1,0)!=LUA_OK){const char* e=lua_tostring(L,-1); printf("[LUA ERR update FRAME] %s\n",e); lua_pop(L,1); ctx->lua_ok=false;} + else { bool r=lua_toboolean(L,-1); lua_pop(L,1); if(r) should_draw=true; } } else lua_pop(L,1); + } else { + // On-demand: only when touch event + if(has_touch_event){ + lua_getglobal(L,"update"); + if(lua_isfunction(L,-1)){ + lua_newtable(L); + lua_pushinteger(L, ev_type); lua_setfield(L,-2,"type"); + lua_pushinteger(L, ctx->touch_x); lua_setfield(L,-2,"x"); + lua_pushinteger(L, ctx->touch_y); lua_setfield(L,-2,"y"); + lua_pushboolean(L,1); lua_setfield(L,-2,"valid"); + lua_pushinteger(L,0); lua_setfield(L,-2,"button_id"); + if(lua_pcall(L,1,1,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 { bool r=lua_toboolean(L,-1); lua_pop(L,1); if(r) should_draw=true; } + } else lua_pop(L,1); + } } ctx->prev_touch_pressed = ctx->touch_pressed; } else { - // Original sys API + // Original sys API - continuous 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); + should_draw = true; } - 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); + + if(should_draw){ + 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 (on-demand)\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; } - ctx->fps_frames=0; - ctx->fps_last_us=now; + tt_lvgl_unlock(); } - tt_lvgl_unlock(); } } - vTaskDelay(pdMS_TO_TICKS(TICK_MS)); + // For on-demand games, sleep longer to save CPU + if(ctx->is_pico && !ctx->wants_frame_updates){ + vTaskDelay(pdMS_TO_TICKS(50)); + } else { + vTaskDelay(pdMS_TO_TICKS(TICK_MS)); + } } printf("[LuaGame] game loop exit\n"); vTaskDelete(NULL); @@ -683,14 +734,16 @@ static void touch_event_cb(lv_event_t* 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); + int max_w = ctx->game_w ? ctx->game_w : GAME_W_DEFAULT; + int max_h = ctx->game_h ? ctx->game_h : GAME_H_DEFAULT; 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(lx>=max_w) lx=max_w-1; if(ly<0) ly=0; - if(ly>=GAME_H) ly=GAME_H-1; + if(ly>=max_h) ly=max_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; @@ -711,13 +764,27 @@ static void app_on_show(AppHandle app,void* data,lv_obj_t* parent){ g_ctx=ctx; ctx->app=app; ctx->lua_ok=false; ctx->initialized=false; ctx->should_exit=false; ctx->fps=0; ctx->fps_frames=0; + + // Fullscreen: no toolbar, no statusbar (manifest HideStatusBar) 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->toolbar=NULL; ctx->root=parent; + + // Get display resolution for dynamic framebuffer (320x480 on ES3C35P) + lv_coord_t disp_w = lv_display_get_horizontal_resolution(NULL); + lv_coord_t disp_h = lv_display_get_vertical_resolution(NULL); + if(disp_w<=0) disp_w = 320; + if(disp_h<=0) disp_h = 480; + ctx->game_w = disp_w; + ctx->game_h = disp_h; + ctx->fb_w = disp_w; + ctx->fb_h = disp_h; + printf("[LuaGame] display %dx%d fullscreen, no toolbar\n", (int)disp_w, (int)disp_h); + 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); @@ -728,12 +795,12 @@ static void app_on_show(AppHandle app,void* data,lv_obj_t* parent){ 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); + size_t fb_bytes=(size_t)ctx->fb_w * ctx->fb_h * sizeof(uint16_t); ctx->fb=alloc_psram(fb_bytes); - if(!ctx->fb){printf("[LuaGame] FB alloc fail\n"); return;} + if(!ctx->fb){printf("[LuaGame] FB alloc fail %dx%d\n", ctx->fb_w, ctx->fb_h); 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_size(cont,ctx->fb_w,ctx->fb_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); diff --git a/Apps/LuaGame/manifest.properties b/Apps/LuaGame/manifest.properties index e4c53b3..4d0bd04 100644 --- a/Apps/LuaGame/manifest.properties +++ b/Apps/LuaGame/manifest.properties @@ -2,7 +2,8 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32s3 app.id=one.tactility.luagame -app.version.name=0.1.0 -app.version.code=1 +app.version.name=0.2.0 +app.version.code=2 app.name=Lua Game -app.description=Lua interpreter game engine (runs breakout.lua and custom games) +app.description=Lua interpreter game engine - fullscreen, no chrome +app.flags=HideStatusBar