diff options
27 files changed, 2271 insertions, 559 deletions
diff --git a/builtin/async_env.lua b/builtin/async_env.lua new file mode 100644 index 000000000..afc69219c --- /dev/null +++ b/builtin/async_env.lua @@ -0,0 +1,19 @@ +engine.log("info","Initializing Asynchronous environment") + +dofile(SCRIPTDIR .. DIR_DELIM .. "misc_helpers.lua") + +function engine.job_processor(serialized_function, serialized_data) + + local fct = marshal.decode(serialized_function) + local params = marshal.decode(serialized_data) + local retval = marshal.encode(nil) + + if fct ~= nil and type(fct) == "function" then + local result = fct(params) + retval = marshal.encode(result) + else + engine.log("error","ASYNC WORKER: unable to deserialize function") + end + + return retval,retval:len() +end diff --git a/builtin/async_event.lua b/builtin/async_event.lua new file mode 100644 index 000000000..f4c7d2449 --- /dev/null +++ b/builtin/async_event.lua @@ -0,0 +1,59 @@ +local tbl = engine or minetest + +tbl.async_jobs = {} + +if engine ~= nil then + function tbl.async_event_handler(jobid, serialized_retval) + local retval = nil + if serialized_retval ~= "ERROR" then + retval= marshal.decode(serialized_retval) + else + tbl.log("error","Error fetching async result") + end + + assert(type(tbl.async_jobs[jobid]) == "function") + tbl.async_jobs[jobid](retval) + tbl.async_jobs[jobid] = nil + end +else + + minetest.register_globalstep( + function(dtime) + local list = tbl.get_finished_jobs() + + for i=1,#list,1 do + local retval = marshal.decode(list[i].retval) + + assert(type(tbl.async_jobs[jobid]) == "function") + tbl.async_jobs[list[i].jobid](retval) + tbl.async_jobs[list[i].jobid] = nil + end + end) +end + +function tbl.handle_async(fct, parameters, callback) + + --serialize fct + local serialized_fct = marshal.encode(fct) + + assert(marshal.decode(serialized_fct) ~= nil) + + --serialize parameters + local serialized_params = marshal.encode(parameters) + + if serialized_fct == nil or + serialized_params == nil or + serialized_fct:len() == 0 or + serialized_params:len() == 0 then + return false + end + + local jobid = tbl.do_async_callback( serialized_fct, + serialized_fct:len(), + serialized_params, + serialized_params:len()) + + tbl.async_jobs[jobid] = callback + + return true +end diff --git a/builtin/mainmenu.lua b/builtin/mainmenu.lua index 0032017ac..6c0aaf252 100644 --- a/builtin/mainmenu.lua +++ b/builtin/mainmenu.lua @@ -24,6 +24,7 @@ dofile(scriptpath .. DIR_DELIM .. "modstore.lua") dofile(scriptpath .. DIR_DELIM .. "gamemgr.lua") dofile(scriptpath .. DIR_DELIM .. "mm_textures.lua") dofile(scriptpath .. DIR_DELIM .. "mm_menubar.lua") +dofile(scriptpath .. DIR_DELIM .. "async_event.lua") menu = {} local tabbuilder = {} @@ -43,10 +44,10 @@ end -------------------------------------------------------------------------------- function menu.render_favorite(spec,render_details) local text = "" - + if spec.name ~= nil then text = text .. engine.formspec_escape(spec.name:trim()) - + -- if spec.description ~= nil and -- engine.formspec_escape(spec.description):trim() ~= "" then -- text = text .. " (" .. engine.formspec_escape(spec.description) .. ")" @@ -54,51 +55,51 @@ function menu.render_favorite(spec,render_details) else if spec.address ~= nil then text = text .. spec.address:trim() - + if spec.port ~= nil then text = text .. ":" .. spec.port end end end - + if not render_details then return text end - + local details = "" if spec.password == true then details = details .. "*" else details = details .. "_" end - + if spec.creative then details = details .. "C" else details = details .. "_" end - + if spec.damage then details = details .. "D" else details = details .. "_" end - + if spec.pvp then details = details .. "P" else details = details .. "_" end details = details .. " " - + local playercount = "" - + if spec.clients ~= nil and spec.clients_max ~= nil then playercount = string.format("%03d",spec.clients) .. "/" .. string.format("%03d",spec.clients_max) .. " " end - + return playercount .. engine.formspec_escape(details) .. text end @@ -106,7 +107,7 @@ end os.tempfolder = function() local filetocheck = os.tmpname() os.remove(filetocheck) - + local randname = "MTTempModFolder_" .. math.random(0,10000) if DIR_DELIM == "\\" then local tempfolder = os.getenv("TEMP") @@ -122,7 +123,7 @@ end function init_globals() --init gamedata gamedata.worldindex = 0 - + worldlist = filterlist.create( engine.get_worlds, compare_worlds, @@ -139,7 +140,7 @@ function init_globals() return false end --filter fct ) - + filterlist.add_sort_mechanism(worldlist,"alphabetic",sort_worlds_alphabetic) filterlist.set_sortmode(worldlist,"alphabetic") end @@ -148,12 +149,12 @@ end function update_menu() local formspec - + -- handle errors if gamedata.errormessage ~= nil then formspec = "size[12,5.2]" .. "field[1,2;10,2;;ERROR: " .. - gamedata.errormessage .. + gamedata.errormessage .. ";]".. "button[4.5,4.2;3,0.5;btn_error_confirm;" .. fgettext("Ok") .. "]" else @@ -166,14 +167,14 @@ end -------------------------------------------------------------------------------- function menu.render_world_list() local retval = "" - + local current_worldlist = filterlist.get_list(worldlist) - + for i,v in ipairs(current_worldlist) do if retval ~= "" then retval = retval .."," end - + retval = retval .. engine.formspec_escape(v.name) .. " \\[" .. engine.formspec_escape(v.gameid) .. "\\]" end @@ -197,23 +198,38 @@ function menu.render_texture_pack_list(list) end -------------------------------------------------------------------------------- +function menu.asyncOnlineFavourites() + menu.favorites = {} + engine.handle_async( + function(param) + return engine.get_favorites("online") + end, + nil, + function(result) + menu.favorites = result + engine.event_handler("Refresh") + end + ) +end + +-------------------------------------------------------------------------------- function menu.init() --init menu data gamemgr.update_gamelist() - + menu.last_game = tonumber(engine.setting_get("main_menu_last_game_idx")) - + if type(menu.last_game) ~= "number" then menu.last_game = 1 end if engine.setting_getbool("public_serverlist") then - menu.favorites = engine.get_favorites("online") + menu.asyncOnlineFavourites() else menu.favorites = engine.get_favorites("local") end - - menu.defaulttexturedir = engine.get_texturepath() .. DIR_DELIM .. "base" .. + + menu.defaulttexturedir = engine.get_texturepath() .. DIR_DELIM .. "base" .. DIR_DELIM .. "pack" .. DIR_DELIM end @@ -222,12 +238,12 @@ function menu.lastgame() if menu.last_game > 0 and menu.last_game <= #gamemgr.games then return gamemgr.games[menu.last_game] end - + if #gamemgr.games >= 1 then menu.last_game = 1 return gamemgr.games[menu.last_game] end - + --error case!! return nil end @@ -238,11 +254,11 @@ function menu.update_last_game() local current_world = filterlist.get_raw_element(worldlist, engine.setting_get("mainmenu_last_selected_world") ) - + if current_world == nil then return end - + local gamespec, i = gamemgr.find_by_gameid(current_world.gameid) if i ~= nil then menu.last_game = i @@ -255,17 +271,17 @@ function menu.handle_key_up_down(fields,textlist,settingname) if fields["key_up"] then local oldidx = engine.get_textlist_index(textlist) - + if oldidx > 1 then local newidx = oldidx -1 engine.setting_set(settingname, filterlist.get_raw_index(worldlist,newidx)) end end - + if fields["key_down"] then local oldidx = engine.get_textlist_index(textlist) - + if oldidx < filterlist.size(worldlist) then local newidx = oldidx + 1 engine.setting_set(settingname, @@ -293,7 +309,7 @@ function tabbuilder.dialog_create_world() end mglist = mglist:sub(1, -2) - local retval = + local retval = "label[2,0;" .. fgettext("World name") .. "]".. "field[4.5,0.4;6,0.5;te_world_name;;]" .. @@ -335,7 +351,7 @@ function tabbuilder.gettab() if buildfunc ~= nil then retval = retval .. buildfunc() end - + retval = retval .. modmgr.gettab(tabbuilder.current_tab) retval = retval .. gamemgr.gettab(tabbuilder.current_tab) retval = retval .. modstore.gettab(tabbuilder.current_tab) @@ -345,18 +361,18 @@ end -------------------------------------------------------------------------------- function tabbuilder.handle_create_world_buttons(fields) - + if fields["world_create_confirm"] or fields["key_enter"] then - + local worldname = fields["te_world_name"] local gameindex = engine.get_textlist_index("games") - + if gameindex > 0 and worldname ~= "" then - + local message = nil - + if not filterlist.uid_exists_raw(worldlist,worldname) then engine.setting_set("mg_name",fields["dd_mapgen"]) message = engine.create_world(worldname,gameindex) @@ -365,28 +381,28 @@ function tabbuilder.handle_create_world_buttons(fields) end engine.setting_set("fixed_map_seed", fields["te_seed"]) - + if message ~= nil then gamedata.errormessage = message else menu.last_game = gameindex engine.setting_set("main_menu_last_game_idx",gameindex) - + filterlist.refresh(worldlist) engine.setting_set("mainmenu_last_selected_world", filterlist.raw_index_by_uid(worldlist,worldname)) end else - gamedata.errormessage = + gamedata.errormessage = fgettext("No worldname given or no game selected") end end - + if fields["games"] then tabbuilder.skipformupdate = true return end - + --close dialog tabbuilder.is_dialog = false tabbuilder.show_buttons = true @@ -395,16 +411,16 @@ end -------------------------------------------------------------------------------- function tabbuilder.handle_delete_world_buttons(fields) - + if fields["world_delete_confirm"] then - if menu.world_to_del > 0 and + if menu.world_to_del > 0 and menu.world_to_del <= #filterlist.get_raw_list(worldlist) then engine.delete_world(menu.world_to_del) menu.world_to_del = 0 filterlist.refresh(worldlist) end end - + tabbuilder.is_dialog = false tabbuilder.show_buttons = true tabbuilder.current_tab = engine.setting_get("main_menu_tab") @@ -412,12 +428,12 @@ end -------------------------------------------------------------------------------- function tabbuilder.handle_multiplayer_buttons(fields) - + if fields["te_name"] ~= nil then gamedata.playername = fields["te_name"] engine.setting_set("name", fields["te_name"]) end - + if fields["favourites"] ~= nil then local event = explode_textlist_event(fields["favourites"]) if event.typ == "DCL" then @@ -429,12 +445,12 @@ function tabbuilder.handle_multiplayer_buttons(fields) gamedata.password = fields["te_pwd"] end gamedata.selected_world = 0 - + if menu.favorites ~= nil then gamedata.servername = menu.favorites[event.index].name gamedata.serverdescription = menu.favorites[event.index].description end - + if gamedata.address ~= nil and gamedata.port ~= nil then engine.setting_set("address",gamedata.address) @@ -443,53 +459,53 @@ function tabbuilder.handle_multiplayer_buttons(fields) end end end - + if event.typ == "CHG" then if event.index <= #menu.favorites then local address = menu.favorites[event.index].address local port = menu.favorites[event.index].port - + if address ~= nil and port ~= nil then engine.setting_set("address",address) engine.setting_set("remote_port",port) end - + menu.fav_selected = event.index end end return end - + if fields["key_up"] ~= nil or fields["key_down"] ~= nil then - + local fav_idx = engine.get_textlist_index("favourites") - + if fields["key_up"] ~= nil and fav_idx > 1 then fav_idx = fav_idx -1 else if fields["key_down"] and fav_idx < #menu.favorites then fav_idx = fav_idx +1 end end - + local address = menu.favorites[fav_idx].address local port = menu.favorites[fav_idx].port - + if address ~= nil and port ~= nil then engine.setting_set("address",address) engine.setting_set("remote_port",port) end - + menu.fav_selected = fav_idx return end - + if fields["cb_public_serverlist"] ~= nil then engine.setting_set("public_serverlist", fields["cb_public_serverlist"]) - + if engine.setting_getbool("public_serverlist") then - menu.favorites = engine.get_favorites("online") + menu.asyncOnlineFavourites() else menu.favorites = engine.get_favorites("local") end @@ -502,27 +518,27 @@ function tabbuilder.handle_multiplayer_buttons(fields) engine.delete_favorite(current_favourite) menu.favorites = engine.get_favorites() menu.fav_selected = nil - + engine.setting_set("address","") engine.setting_set("remote_port","30000") - + return end if fields["btn_mp_connect"] ~= nil or fields["key_enter"] ~= nil then - + gamedata.playername = fields["te_name"] gamedata.password = fields["te_pwd"] gamedata.address = fields["te_address"] gamedata.port = fields["te_port"] - + local fav_idx = engine.get_textlist_index("favourites") - + if fav_idx > 0 and fav_idx <= #menu.favorites and menu.favorites[fav_idx].address == fields["te_address"] and menu.favorites[fav_idx].port == fields["te_port"] then - + gamedata.servername = menu.favorites[fav_idx].name gamedata.serverdescription = menu.favorites[fav_idx].description else @@ -531,10 +547,10 @@ function tabbuilder.handle_multiplayer_buttons(fields) end gamedata.selected_world = 0 - + engine.setting_set("address",fields["te_address"]) engine.setting_set("remote_port",fields["te_port"]) - + engine.start() return end @@ -547,7 +563,7 @@ function tabbuilder.handle_server_buttons(fields) if fields["srv_worlds"] ~= nil then local event = explode_textlist_event(fields["srv_worlds"]) - + if event.typ == "DCL" then world_doubleclick = true end @@ -556,13 +572,13 @@ function tabbuilder.handle_server_buttons(fields) filterlist.get_raw_index(worldlist,engine.get_textlist_index("srv_worlds"))) end end - + menu.handle_key_up_down(fields,"srv_worlds","mainmenu_last_selected_world") - + if fields["cb_creative_mode"] then engine.setting_set("creative_mode", fields["cb_creative_mode"]) end - + if fields["cb_enable_damage"] then engine.setting_set("enable_damage", fields["cb_enable_damage"]) end @@ -570,7 +586,7 @@ function tabbuilder.handle_server_buttons(fields) if fields["cb_server_announce"] then engine.setting_set("server_announce", fields["cb_server_announce"]) end - + if fields["start_server"] ~= nil or world_doubleclick or fields["key_enter"] then @@ -581,19 +597,20 @@ function tabbuilder.handle_server_buttons(fields) gamedata.port = fields["te_serverport"] gamedata.address = "" gamedata.selected_world = filterlist.get_raw_index(worldlist,selected) - + engine.setting_set("port",gamedata.port) + menu.update_last_game(gamedata.selected_world) engine.start() end end - + if fields["world_create"] ~= nil then tabbuilder.current_tab = "dialog_create_world" tabbuilder.is_dialog = true tabbuilder.show_buttons = false end - + if fields["world_delete"] ~= nil then local selected = engine.get_textlist_index("srv_worlds") if selected > 0 and @@ -611,7 +628,7 @@ function tabbuilder.handle_server_buttons(fields) end end end - + if fields["world_configure"] ~= nil then selected = engine.get_textlist_index("srv_worlds") if selected > 0 then @@ -639,7 +656,7 @@ function tabbuilder.handle_settings_buttons(fields) if fields["cb_opaque_water"] then engine.setting_set("opaque_water", fields["cb_opaque_water"]) end - + if fields["cb_mipmapping"] then engine.setting_set("mip_map", fields["cb_mipmapping"]) end @@ -652,7 +669,7 @@ function tabbuilder.handle_settings_buttons(fields) if fields["cb_trilinear"] then engine.setting_set("trilinear_filter", fields["cb_trilinear"]) end - + if fields["cb_shaders"] then if (engine.setting_get("video_driver") == "direct3d8" or engine.setting_get("video_driver") == "direct3d9") then engine.setting_set("enable_shaders", "false") @@ -683,23 +700,23 @@ function tabbuilder.handle_singleplayer_buttons(fields) if fields["sp_worlds"] ~= nil then local event = explode_textlist_event(fields["sp_worlds"]) - + if event.typ == "DCL" then world_doubleclick = true end - + if event.typ == "CHG" then engine.setting_set("mainmenu_last_selected_world", filterlist.get_raw_index(worldlist,engine.get_textlist_index("sp_worlds"))) end end - + menu.handle_key_up_down(fields,"sp_worlds","mainmenu_last_selected_world") - + if fields["cb_creative_mode"] then engine.setting_set("creative_mode", fields["cb_creative_mode"]) end - + if fields["cb_enable_damage"] then engine.setting_set("enable_damage", fields["cb_enable_damage"]) end @@ -711,19 +728,19 @@ function tabbuilder.handle_singleplayer_buttons(fields) if selected > 0 then gamedata.selected_world = filterlist.get_raw_index(worldlist,selected) gamedata.singleplayer = true - + menu.update_last_game(gamedata.selected_world) - + engine.start() end end - + if fields["world_create"] ~= nil then tabbuilder.current_tab = "dialog_create_world" tabbuilder.is_dialog = true tabbuilder.show_buttons = false end - + if fields["world_delete"] ~= nil then local selected = engine.get_textlist_index("sp_worlds") if selected > 0 and @@ -741,7 +758,7 @@ function tabbuilder.handle_singleplayer_buttons(fields) end end end - + if fields["world_configure"] ~= nil then selected = engine.get_textlist_index("sp_worlds") if selected > 0 then @@ -768,7 +785,7 @@ function tabbuilder.handle_texture_pack_buttons(fields) if #list >= current_index then local new_path = engine.get_texturepath()..DIR_DELIM..list[current_index] if list[current_index] == "None" then new_path = "" end - + engine.setting_set("texture_path", new_path) end end @@ -781,15 +798,15 @@ function tabbuilder.tab_header() if tabbuilder.last_tab_index == nil then tabbuilder.last_tab_index = 1 end - + local toadd = "" - + for i=1,#tabbuilder.current_buttons,1 do - + if toadd ~= "" then toadd = toadd .. "," end - + toadd = toadd .. tabbuilder.current_buttons[i].caption end return "tabheader[-0.3,-0.99;main_tab;" .. toadd ..";" .. tabbuilder.last_tab_index .. ";true;false]" @@ -802,21 +819,21 @@ function tabbuilder.handle_tab_buttons(fields) local index = tonumber(fields["main_tab"]) tabbuilder.last_tab_index = index tabbuilder.current_tab = tabbuilder.current_buttons[index].name - + engine.setting_set("main_menu_tab",tabbuilder.current_tab) end - + --handle tab changes if tabbuilder.current_tab ~= tabbuilder.old_tab then if tabbuilder.current_tab ~= "singleplayer" and not tabbuilder.is_dialog then menu.update_gametype(true) end end - + if tabbuilder.current_tab == "singleplayer" then menu.update_gametype() end - + tabbuilder.old_tab = tabbuilder.current_tab end @@ -832,24 +849,24 @@ function tabbuilder.tab_multiplayer() "field[6.75,5.25;2.25,0.5;te_port;;" ..engine.setting_get("remote_port") .."]" .. "checkbox[1,3.6;cb_public_serverlist;".. fgettext("Public Serverlist") .. ";" .. dump(engine.setting_getbool("public_serverlist")) .. "]" - + if not engine.setting_getbool("public_serverlist") then - retval = retval .. + retval = retval .. "button[6.45,3.95;2.25,0.5;btn_delete_favorite;".. fgettext("Delete") .. "]" end - + retval = retval .. "button[9,4.95;2.5,0.5;btn_mp_connect;".. fgettext("Connect") .. "]" .. "field[9.3,3.75;2.5,0.5;te_name;;" ..engine.setting_get("name") .."]" .. "pwdfield[9.3,4.5;2.5,0.5;te_pwd;]" .. "textarea[9.3,0.25;2.5,2.75;;" - if menu.fav_selected ~= nil and + if menu.fav_selected ~= nil and menu.favorites[menu.fav_selected].description ~= nil then - retval = retval .. + retval = retval .. engine.formspec_escape(menu.favorites[menu.fav_selected].description,true) end - - retval = retval .. + + retval = retval .. ";]" .. "textlist[1,0.35;7.5,3.35;favourites;" @@ -857,7 +874,7 @@ function tabbuilder.tab_multiplayer() if #menu.favorites > 0 then retval = retval .. menu.render_favorite(menu.favorites[1],render_details) - + for i=2,#menu.favorites,1 do retval = retval .. "," .. menu.render_favorite(menu.favorites[i],render_details) end @@ -878,8 +895,8 @@ function tabbuilder.tab_server() local index = filterlist.get_current_index(worldlist, tonumber(engine.setting_get("mainmenu_last_selected_world")) ) - - local retval = + + local retval = "button[4,4.15;2.6,0.5;world_delete;".. fgettext("Delete") .. "]" .. "button[6.5,4.15;2.8,0.5;world_create;".. fgettext("New") .. "]" .. "button[9.2,4.15;2.55,0.5;world_configure;".. fgettext("Configure") .. "]" .. @@ -895,27 +912,27 @@ function tabbuilder.tab_server() "field[0.8,3.2;3,0.5;te_playername;".. fgettext("Name") .. ";" .. engine.setting_get("name") .. "]" .. "pwdfield[0.8,4.2;3,0.5;te_passwd;".. fgettext("Password") .. "]" .. - "field[0.8,5.2;3,0.5;te_serverport;".. fgettext("Server Port") .. ";" .. + "field[0.8,5.2;3,0.5;te_serverport;".. fgettext("Server Port") .. ";" .. engine.setting_get("port") .."]" .. "textlist[4,0.25;7.5,3.7;srv_worlds;" .. menu.render_world_list() .. ";" .. index .. "]" - + return retval end -------------------------------------------------------------------------------- function tabbuilder.tab_settings() return "vertlabel[0,0;" .. fgettext("SETTINGS") .. "]" .. - "checkbox[1,0.75;cb_fancy_trees;".. fgettext("Fancy trees") .. ";" + "checkbox[1,0.75;cb_fancy_trees;".. fgettext("Fancy trees") .. ";" .. dump(engine.setting_getbool("new_style_leaves")) .. "]".. - "checkbox[1,1.25;cb_smooth_lighting;".. fgettext("Smooth Lighting") + "checkbox[1,1.25;cb_smooth_lighting;".. fgettext("Smooth Lighting") .. ";".. dump(engine.setting_getbool("smooth_lighting")) .. "]".. "checkbox[1,1.75;cb_3d_clouds;".. fgettext("3D Clouds") .. ";" .. dump(engine.setting_getbool("enable_3d_clouds")) .. "]".. "checkbox[1,2.25;cb_opaque_water;".. fgettext("Opaque Water") .. ";" .. dump(engine.setting_getbool("opaque_water")) .. "]".. - + "checkbox[4,0.75;cb_mipmapping;".. fgettext("Mip-Mapping") .. ";" .. dump(engine.setting_getbool("mip_map")) .. "]".. "checkbox[4,1.25;cb_anisotrophic;".. fgettext("Anisotropic Filtering") .. ";" @@ -924,7 +941,7 @@ function tabbuilder.tab_settings() .. dump(engine.setting_getbool("bilinear_filter")) .. "]".. "checkbox[4,2.25;cb_trilinear;".. fgettext("Tri-Linear Filtering") .. ";" .. dump(engine.setting_getbool("trilinear_filter")) .. "]".. - + "checkbox[7.5,0.75;cb_shaders;".. fgettext("Shaders") .. ";" .. dump(engine.setting_getbool("enable_shaders")) .. "]".. "checkbox[7.5,1.25;cb_pre_ivis;".. fgettext("Preload item visuals") .. ";" @@ -933,13 +950,13 @@ function tabbuilder.tab_settings() .. dump(engine.setting_getbool("enable_particles")) .. "]".. "checkbox[7.5,2.25;cb_finite_liquid;".. fgettext("Finite Liquid") .. ";" .. dump(engine.setting_getbool("liquid_finite")) .. "]".. - + "button[1,4.25;2.25,0.5;btn_change_keys;".. fgettext("Change keys") .. "]" end -------------------------------------------------------------------------------- function tabbuilder.tab_singleplayer() - + local index = filterlist.get_current_index(worldlist, tonumber(engine.setting_get("mainmenu_last_selected_world")) ) @@ -966,19 +983,19 @@ function tabbuilder.tab_texture_packs() "vertlabel[0,-0.25;".. fgettext("TEXTURE PACKS") .. "]" .. "textlist[4,0.25;7.5,5.0;TPs;" - local current_texture_path = engine.setting_get("texture_path") - local list = filter_texture_pack_list(engine.get_dirlist(engine.get_texturepath(), true)) + local current_texture_path = engine.setting_get("texture_path") + local list = filter_texture_pack_list(engine.get_dirlist(engine.get_texturepath(), true)) local index = tonumber(engine.setting_get("mainmenu_last_selected_TP")) - + if index == nil then index = 1 end - + if current_texture_path == "" then retval = retval .. menu.render_texture_pack_list(list) .. ";" .. index .. "]" return retval end - + local infofile = current_texture_path ..DIR_DELIM.."info.txt" local infotext = "" local f = io.open(infofile, "r") @@ -988,7 +1005,7 @@ function tabbuilder.tab_texture_packs() infotext = f:read("*all") f:close() end - + local screenfile = current_texture_path..DIR_DELIM.."screenshot.png" local no_screenshot = nil if not file_exists(screenfile) then @@ -1009,7 +1026,7 @@ function tabbuilder.tab_credits() local logofile = menu.defaulttexturedir .. "logo.png" return "vertlabel[0,-0.5;CREDITS]" .. "label[0.5,3;Minetest " .. engine.get_version() .. "]" .. - "label[0.5,3.3;http://minetest.net]" .. + "label[0.5,3.3;http://minetest.net]" .. "image[0.5,1;" .. engine.formspec_escape(logofile) .. "]" .. "textlist[3.5,-0.25;8.5,5.8;list_credits;" .. "#FFFF00" .. fgettext("Core Developers") .."," .. @@ -1064,40 +1081,40 @@ function tabbuilder.init() } tabbuilder.current_tab = engine.setting_get("main_menu_tab") - + if tabbuilder.current_tab == nil or tabbuilder.current_tab == "" then tabbuilder.current_tab = "singleplayer" engine.setting_set("main_menu_tab",tabbuilder.current_tab) end - + --initialize tab buttons tabbuilder.last_tab = nil tabbuilder.show_buttons = true - + tabbuilder.current_buttons = {} table.insert(tabbuilder.current_buttons,{name="singleplayer", caption=fgettext("Singleplayer")}) table.insert(tabbuilder.current_buttons,{name="multiplayer", caption=fgettext("Client")}) table.insert(tabbuilder.current_buttons,{name="server", caption=fgettext("Server")}) table.insert(tabbuilder.current_buttons,{name="settings", caption=fgettext("Settings")}) table.insert(tabbuilder.current_buttons,{name="texture_packs", caption=fgettext("Texture Packs")}) - + if engine.setting_getbool("main_menu_game_mgr") then table.insert(tabbuilder.current_buttons,{name="game_mgr", caption=fgettext("Games")}) end - + if engine.setting_getbool("main_menu_mod_mgr") then table.insert(tabbuilder.current_buttons,{name="mod_mgr", caption=fgettext("Mods")}) end table.insert(tabbuilder.current_buttons,{name="credits", caption=fgettext("Credits")}) - - + + for i=1,#tabbuilder.current_buttons,1 do if tabbuilder.current_buttons[i].name == tabbuilder.current_tab then tabbuilder.last_tab_index = i end end - + if tabbuilder.current_tab ~= "singleplayer" then menu.update_gametype(true) else @@ -1112,18 +1129,24 @@ function tabbuilder.checkretval(retval) if retval.current_tab ~= nil then tabbuilder.current_tab = retval.current_tab end - + if retval.is_dialog ~= nil then tabbuilder.is_dialog = retval.is_dialog end - + if retval.show_buttons ~= nil then tabbuilder.show_buttons = retval.show_buttons end - + if retval.skipformupdate ~= nil then tabbuilder.skipformupdate = retval.skipformupdate end + + if retval.ignore_menu_quit == true then + tabbuilder.ignore_menu_quit = true + else + tabbuilder.ignore_menu_quit = false + end end end @@ -1134,54 +1157,54 @@ end -------------------------------------------------------------------------------- engine.button_handler = function(fields) --print("Buttonhandler: tab: " .. tabbuilder.current_tab .. " fields: " .. dump(fields)) - + if fields["btn_error_confirm"] then gamedata.errormessage = nil end - + local retval = modmgr.handle_buttons(tabbuilder.current_tab,fields) tabbuilder.checkretval(retval) - + retval = gamemgr.handle_buttons(tabbuilder.current_tab,fields) tabbuilder.checkretval(retval) - + retval = modstore.handle_buttons(tabbuilder.current_tab,fields) tabbuilder.checkretval(retval) - + if tabbuilder.current_tab == "dialog_create_world" then tabbuilder.handle_create_world_buttons(fields) end - + if tabbuilder.current_tab == "dialog_delete_world" then tabbuilder.handle_delete_world_buttons(fields) end - + if tabbuilder.current_tab == "singleplayer" then tabbuilder.handle_singleplayer_buttons(fields) end - + if tabbuilder.current_tab == "texture_packs" then tabbuilder.handle_texture_pack_buttons(fields) end - + if tabbuilder.current_tab == "multiplayer" then tabbuilder.handle_multiplayer_buttons(fields) end - + if tabbuilder.current_tab == "settings" then tabbuilder.handle_settings_buttons(fields) end - + if tabbuilder.current_tab == "server" then tabbuilder.handle_server_buttons(fields) end - + --tab buttons tabbuilder.handle_tab_buttons(fields) - + --menubar buttons menubar.handle_buttons(fields) - + if not tabbuilder.skipformupdate then --update menu update_menu() @@ -1194,6 +1217,10 @@ end engine.event_handler = function(event) if event == "MenuQuit" then if tabbuilder.is_dialog then + if tabbuilder.ignore_menu_quit then + return + end + tabbuilder.is_dialog = false tabbuilder.show_buttons = true tabbuilder.current_tab = engine.setting_get("main_menu_tab") @@ -1203,6 +1230,10 @@ engine.event_handler = function(event) engine.close() end end + + if event == "Refresh" then + update_menu() + end end -------------------------------------------------------------------------------- diff --git a/builtin/modmgr.lua b/builtin/modmgr.lua index 1f19ac673..cc5e09513 100644 --- a/builtin/modmgr.lua +++ b/builtin/modmgr.lua @@ -22,7 +22,7 @@ function get_mods(path,retval,modpack) for i=1,#mods,1 do local toadd = {} local modpackfile = nil - + toadd.name = mods[i] toadd.path = path .. DIR_DELIM .. mods[i] .. DIR_DELIM if modpack ~= nil and @@ -33,7 +33,7 @@ function get_mods(path,retval,modpack) local error = nil modpackfile,error = io.open(filename,"r") end - + if modpackfile ~= nil then modpackfile:close() toadd.is_modpack = true @@ -52,9 +52,9 @@ modmgr = {} function modmgr.extract(modfile) if modfile.type == "zip" then local tempfolder = os.tempfolder() - + if tempfolder ~= nil and - tempfodler ~= "" then + tempfolder ~= "" then engine.create_dir(tempfolder) engine.extract_zip(modfile.name,tempfolder) return tempfolder @@ -80,7 +80,7 @@ function modmgr.getbasefolder(temppath) path=temppath } end - + testfile = io.open(temppath .. DIR_DELIM .. "modpack.txt","r") if testfile ~= nil then testfile:close() @@ -89,9 +89,9 @@ function modmgr.getbasefolder(temppath) path=temppath } end - + local subdirs = engine.get_dirlist(temppath,true) - + --only single mod or modpack allowed if #subdirs ~= 1 then return { @@ -100,7 +100,7 @@ function modmgr.getbasefolder(temppath) } end - testfile = + testfile = io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."init.lua","r") if testfile ~= nil then testfile:close() @@ -109,8 +109,8 @@ function modmgr.getbasefolder(temppath) path= temppath .. DIR_DELIM .. subdirs[1] } end - - testfile = + + testfile = io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."modpack.txt","r") if testfile ~= nil then testfile:close() @@ -131,7 +131,7 @@ function modmgr.isValidModname(modpath) if modpath:find("-") ~= nil then return false end - + return true end @@ -142,20 +142,20 @@ function modmgr.parse_register_line(line) if pos1 ~= nil then pos2 = line:find("\"",pos1+1) end - + if pos1 ~= nil and pos2 ~= nil then local item = line:sub(pos1+1,pos2-1) - + if item ~= nil and item ~= "" then local pos3 = item:find(":") - + if pos3 ~= nil then local retval = item:sub(1,pos3-1) if retval ~= nil and retval ~= "" then return retval - end + end end end end @@ -169,10 +169,10 @@ function modmgr.parse_dofile_line(modpath,line) if pos1 ~= nil then pos2 = line:find("\"",pos1+1) end - + if pos1 ~= nil and pos2 ~= nil then local filename = line:sub(pos1+1,pos2-1) - + if filename ~= nil and filename ~= "" and filename:find(".lua") then @@ -187,37 +187,37 @@ function modmgr.identify_modname(modpath,filename) local testfile = io.open(modpath .. DIR_DELIM .. filename,"r") if testfile ~= nil then local line = testfile:read() - + while line~= nil do local modname = nil - + if line:find("minetest.register_tool") then modname = modmgr.parse_register_line(line) end - + if line:find("minetest.register_craftitem") then modname = modmgr.parse_register_line(line) end - - + + if line:find("minetest.register_node") then modname = modmgr.parse_register_line(line) end - + if line:find("dofile") then modname = modmgr.parse_dofile_line(modpath,line) end - + if modname ~= nil then testfile:close() return modname end - + line = testfile:read() end testfile:close() end - + return nil end @@ -231,29 +231,29 @@ function modmgr.tab() if modmgr.selected_mod == nil then modmgr.selected_mod = 1 end - - local retval = + + local retval = "vertlabel[0,-0.25;".. fgettext("MODS") .. "]" .. "label[0.8,-0.25;".. fgettext("Installed Mods:") .. "]" .. "textlist[0.75,0.25;4.5,4;modlist;" .. - modmgr.render_modlist(modmgr.global_mods) .. + modmgr.render_modlist(modmgr.global_mods) .. ";" .. modmgr.selected_mod .. "]" retval = retval .. - "label[0.8,4.2;" .. fgettext("Add mod:") .. "]" .. + "label[0.8,4.2;" .. fgettext("Add mod:") .. "]" .. -- TODO Disabled due to upcoming release 0.4.8 and irrlicht messing up localization -- "button[0.75,4.85;1.8,0.5;btn_mod_mgr_install_local;".. fgettext("Local install") .. "]" .. "button[2.45,4.85;3.05,0.5;btn_mod_mgr_download;".. fgettext("Online mod repository") .. "]" - + local selected_mod = nil - + if filterlist.size(modmgr.global_mods) >= modmgr.selected_mod then selected_mod = filterlist.get_list(modmgr.global_mods)[modmgr.selected_mod] end - + if selected_mod ~= nil then local modscreenshot = nil - + --check for screenshot beeing available local screenshotfilename = selected_mod.path .. DIR_DELIM .. "screenshot.png" local error = nil @@ -262,40 +262,40 @@ function modmgr.tab() screenshotfile:close() modscreenshot = screenshotfilename end - + if modscreenshot == nil then modscreenshot = modstore.basetexturedir .. "no_screenshot.png" end - - retval = retval + + retval = retval .. "image[5.5,0;3,2;" .. engine.formspec_escape(modscreenshot) .. "]" .. "label[8.25,0.6;" .. selected_mod.name .. "]" - + local descriptionlines = nil error = nil local descriptionfilename = selected_mod.path .. "description.txt" descriptionfile,error = io.open(descriptionfilename,"r") if error == nil then descriptiontext = descriptionfile:read("*all") - + descriptionlines = engine.splittext(descriptiontext,42) descriptionfile:close() else descriptionlines = {} table.insert(descriptionlines,fgettext("No mod description available")) end - - retval = retval .. + + retval = retval .. "label[5.5,1.7;".. fgettext("Mod information:") .. "]" .. "textlist[5.5,2.2;6.2,2.4;description;" - + for i=1,#descriptionlines,1 do retval = retval .. engine.formspec_escape(descriptionlines[i]) .. "," end - - + + if selected_mod.is_modpack then - retval = retval .. ";0]" .. + retval = retval .. ";0]" .. "button[10,4.85;2,0.5;btn_mod_mgr_rename_modpack;" .. fgettext("Rename") .. "]" retval = retval .. "button[5.5,4.85;4.5,0.5;btn_mod_mgr_delete_mod;" @@ -304,11 +304,11 @@ function modmgr.tab() --show dependencies retval = retval .. ",Depends:," - + toadd = modmgr.get_dependencies(selected_mod.path) - + retval = retval .. toadd .. ";0]" - + retval = retval .. "button[5.5,4.85;4.5,0.5;btn_mod_mgr_delete_mod;" .. fgettext("Uninstall selected mod") .. "]" end @@ -320,15 +320,15 @@ end function modmgr.dialog_rename_modpack() local mod = filterlist.get_list(modmgr.global_mods)[modmgr.selected_mod] - - local retval = + + local retval = "label[1.75,1;".. fgettext("Rename Modpack:") .. "]".. "field[4.5,1.4;6,0.5;te_modpack_name;;" .. mod.name .. "]" .. - "button[5,4.2;2.6,0.5;dlg_rename_modpack_confirm;".. + "button[5,4.2;2.6,0.5;dlg_rename_modpack_confirm;".. fgettext("Accept") .. "]" .. - "button[7.5,4.2;2.8,0.5;dlg_rename_modpack_cancel;".. + "button[7.5,4.2;2.8,0.5;dlg_rename_modpack_cancel;".. fgettext("Cancel") .. "]" return retval @@ -340,15 +340,15 @@ function modmgr.precheck() if modmgr.world_config_selected_world == nil then modmgr.world_config_selected_world = 1 end - + if modmgr.world_config_selected_mod == nil then modmgr.world_config_selected_mod = 1 end - + if modmgr.hide_gamemods == nil then modmgr.hide_gamemods = true end - + if modmgr.hide_modpackcontents == nil then modmgr.hide_modpackcontents = true end @@ -357,27 +357,27 @@ end -------------------------------------------------------------------------------- function modmgr.render_modlist(render_list) local retval = "" - + if render_list == nil then if modmgr.global_mods == nil then modmgr.refresh_globals() end render_list = modmgr.global_mods end - + local list = filterlist.get_list(render_list) local last_modpack = nil - + for i,v in ipairs(list) do if retval ~= "" then retval = retval .."," end local color = "" - + if v.is_modpack then local rawlist = filterlist.get_raw_list(render_list) - + local all_enabled = true for j=1,#rawlist,1 do if rawlist[j].modpack == list[i].name and @@ -386,14 +386,14 @@ function modmgr.render_modlist(render_list) break end end - + if all_enabled == false then color = mt_color_grey else color = mt_color_dark_green end end - + if v.typ == "game_mod" then color = mt_color_blue else @@ -408,34 +408,34 @@ function modmgr.render_modlist(render_list) end retval = retval .. v.name end - + return retval end -------------------------------------------------------------------------------- function modmgr.dialog_configure_world() modmgr.precheck() - + local worldspec = engine.get_worlds()[modmgr.world_config_selected_world] local mod = filterlist.get_list(modmgr.modlist)[modmgr.world_config_selected_mod] - + local retval = "size[11,6.5]" .. "label[0.5,-0.25;" .. fgettext("World:") .. "]" .. "label[1.75,-0.25;" .. worldspec.name .. "]" - + if modmgr.hide_gamemods then retval = retval .. "checkbox[0,5.75;cb_hide_gamemods;" .. fgettext("Hide Game") .. ";true]" else retval = retval .. "checkbox[0,5.75;cb_hide_gamemods;" .. fgettext("Hide Game") .. ";false]" end - + if modmgr.hide_modpackcontents then retval = retval .. "checkbox[2,5.75;cb_hide_mpcontent;" .. fgettext("Hide mp content") .. ";true]" else retval = retval .. "checkbox[2,5.75;cb_hide_mpcontent;" .. fgettext("Hide mp content") .. ";false]" end - + if mod == nil then mod = {name=""} end @@ -447,11 +447,11 @@ function modmgr.dialog_configure_world() modmgr.get_dependencies(mod.path) .. ";0]" .. "button[9.25,6.35;2,0.5;btn_config_world_save;" .. fgettext("Save") .. "]" .. "button[7.4,6.35;2,0.5;btn_config_world_cancel;" .. fgettext("Cancel") .. "]" - + if mod ~= nil and mod.name ~= "" and mod.typ ~= "game_mod" then if mod.is_modpack then local rawlist = filterlist.get_raw_list(modmgr.modlist) - + local all_enabled = true for j=1,#rawlist,1 do if rawlist[j].modpack == mod.name and @@ -460,7 +460,7 @@ function modmgr.dialog_configure_world() break end end - + if all_enabled == false then retval = retval .. "button[5.5,-0.125;2,0.5;btn_mp_enable;" .. fgettext("Enable MP") .. "]" else @@ -474,15 +474,15 @@ function modmgr.dialog_configure_world() end end end - + retval = retval .. "button[8.5,-0.125;2.5,0.5;btn_all_mods;" .. fgettext("Enable all") .. "]" .. "textlist[5.5,0.5;5.5,5.75;world_config_modlist;" - + retval = retval .. modmgr.render_modlist(modmgr.modlist) - + retval = retval .. ";" .. modmgr.world_config_selected_mod .."]" - + return retval end @@ -490,23 +490,23 @@ end function modmgr.handle_buttons(tab,fields) local retval = nil - + if tab == "mod_mgr" then retval = modmgr.handle_modmgr_buttons(fields) end - + if tab == "dialog_rename_modpack" then retval = modmgr.handle_rename_modpack_buttons(fields) end - + if tab == "dialog_delete_mod" then retval = modmgr.handle_delete_mod_buttons(fields) end - + if tab == "dialog_configure_world" then retval = modmgr.handle_configure_world_buttons(fields) end - + return retval end @@ -516,13 +516,13 @@ function modmgr.get_dependencies(modfolder) if modfolder ~= nil then local filename = modfolder .. DIR_DELIM .. "depends.txt" - + local dependencyfile = io.open(filename,"r") - + if dependencyfile then local dependency = dependencyfile:read("*l") while dependency do - if toadd ~= "" then + if toadd ~= "" then toadd = toadd .. "," end toadd = toadd .. dependency @@ -542,11 +542,11 @@ function modmgr.get_worldconfig(worldpath) DIR_DELIM .. "world.mt" local worldfile = Settings(filename) - + local worldconfig = {} worldconfig.global_mods = {} worldconfig.game_mods = {} - + for key,value in pairs(worldfile:to_table()) do if key == "gameid" then worldconfig.id = value @@ -554,7 +554,7 @@ function modmgr.get_worldconfig(worldpath) worldconfig.global_mods[key] = engine.is_yes(value) end end - + --read gamemods local gamespec = gamemgr.find_by_gameid(worldconfig.id) gamemgr.get_game_mods(gamespec, worldconfig.game_mods) @@ -573,11 +573,11 @@ function modmgr.handle_modmgr_buttons(fields) local event = explode_textlist_event(fields["modlist"]) modmgr.selected_mod = event.index end - + if fields["btn_mod_mgr_install_local"] ~= nil then engine.show_file_open_dialog("mod_mgt_open_dlg",fgettext("Select Mod File:")) end - + if fields["btn_mod_mgr_download"] ~= nil then modstore.update_modlist() retval.current_tab = "dialog_modstore_unsorted" @@ -585,26 +585,26 @@ function modmgr.handle_modmgr_buttons(fields) retval.show_buttons = false return retval end - + if fields["btn_mod_mgr_rename_modpack"] ~= nil then retval.current_tab = "dialog_rename_modpack" retval.is_dialog = true retval.show_buttons = false return retval end - + if fields["btn_mod_mgr_delete_mod"] ~= nil then retval.current_tab = "dialog_delete_mod" retval.is_dialog = true retval.show_buttons = false return retval end - + if fields["mod_mgt_open_dlg_accepted"] ~= nil and fields["mod_mgt_open_dlg_accepted"] ~= "" then modmgr.installmod(fields["mod_mgt_open_dlg_accepted"],nil) end - + return nil; end @@ -612,27 +612,27 @@ end function modmgr.installmod(modfilename,basename) local modfile = modmgr.identify_filetype(modfilename) local modpath = modmgr.extract(modfile) - + if modpath == nil then gamedata.errormessage = fgettext("Install Mod: file: \"$1\"", modfile.name) .. fgettext("\nInstall Mod: unsupported filetype \"$1\"", modfile.type) return end - - + + local basefolder = modmgr.getbasefolder(modpath) - + if basefolder.type == "modpack" then local clean_path = nil - + if basename ~= nil then clean_path = "mp_" .. basename end - + if clean_path == nil then clean_path = get_last_folder(cleanup_path(basefolder.path)) end - + if clean_path ~= nil then local targetpath = engine.get_modpath() .. DIR_DELIM .. clean_path if not engine.copy_dir(basefolder.path,targetpath) then @@ -642,19 +642,19 @@ function modmgr.installmod(modfilename,basename) gamedata.errormessage = fgettext("Install Mod: unable to find suitable foldername for modpack $1", modfilename) end end - + if basefolder.type == "mod" then local targetfolder = basename - + if targetfolder == nil then targetfolder = modmgr.identify_modname(basefolder.path,"init.lua") end - + --if heuristic failed try to use current foldername if targetfolder == nil then targetfolder = get_last_folder(basefolder.path) - end - + end + if targetfolder ~= nil and modmgr.isValidModname(targetfolder) then local targetpath = engine.get_modpath() .. DIR_DELIM .. targetfolder engine.copy_dir(basefolder.path,targetpath) @@ -662,7 +662,7 @@ function modmgr.installmod(modfilename,basename) gamedata.errormessage = fgettext("Install Mod: unable to find real modname for: $1", modfilename) end end - + engine.delete_dir(modpath) modmgr.refresh_globals() @@ -671,7 +671,7 @@ end -------------------------------------------------------------------------------- function modmgr.handle_rename_modpack_buttons(fields) - + if fields["dlg_rename_modpack_confirm"] ~= nil then local mod = filterlist.get_list(modmgr.global_mods)[modmgr.selected_mod] local oldpath = engine.get_modpath() .. DIR_DELIM .. mod.name @@ -681,7 +681,7 @@ function modmgr.handle_rename_modpack_buttons(fields) modmgr.selected_mod = filterlist.get_current_index(modmgr.global_mods, filterlist.raw_index_by_uid(modmgr.global_mods, fields["te_modpack_name"])) end - + return { is_dialog = false, show_buttons = true, @@ -698,25 +698,25 @@ function modmgr.handle_configure_world_buttons(fields) modmgr.world_config_enable_mod(nil) end end - + if fields["key_enter"] ~= nil then modmgr.world_config_enable_mod(nil) end - + if fields["cb_mod_enable"] ~= nil then local toset = engine.is_yes(fields["cb_mod_enable"]) modmgr.world_config_enable_mod(toset) end - + if fields["btn_mp_enable"] ~= nil or fields["btn_mp_disable"] then local toset = (fields["btn_mp_enable"] ~= nil) modmgr.world_config_enable_mod(toset) end - + if fields["cb_hide_gamemods"] ~= nil then local current = filterlist.get_filtercriteria(modmgr.modlist) - + if current == nil then current = {} end @@ -728,13 +728,13 @@ function modmgr.handle_configure_world_buttons(fields) current.hide_game = false modmgr.hide_gamemods = false end - + filterlist.set_filtercriteria(modmgr.modlist,current) end - + if fields["cb_hide_mpcontent"] ~= nil then local current = filterlist.get_filtercriteria(modmgr.modlist) - + if current == nil then current = {} end @@ -746,21 +746,21 @@ function modmgr.handle_configure_world_buttons(fields) current.hide_modpackcontents = false modmgr.hide_modpackcontents = false end - + filterlist.set_filtercriteria(modmgr.modlist,current) end - + if fields["btn_config_world_save"] then local worldspec = engine.get_worlds()[modmgr.world_config_selected_world] - + local filename = worldspec.path .. DIR_DELIM .. "world.mt" - + local worldfile = Settings(filename) local mods = worldfile:to_table() - + local rawlist = filterlist.get_raw_list(modmgr.modlist) - + local i,mod for i,mod in ipairs(rawlist) do if not mod.is_modpack and @@ -773,42 +773,42 @@ function modmgr.handle_configure_world_buttons(fields) mods["load_mod_"..mod.name] = nil end end - + -- Remove mods that are not present anymore for key,value in pairs(mods) do if key:sub(1,9) == "load_mod_" then worldfile:remove(key) end end - + if not worldfile:write() then engine.log("error", "Failed to write world config file") end - + modmgr.modlist = nil modmgr.worldconfig = nil - + return { is_dialog = false, show_buttons = true, current_tab = engine.setting_get("main_menu_tab") } end - + if fields["btn_config_world_cancel"] then - + modmgr.worldconfig = nil - + return { is_dialog = false, show_buttons = true, current_tab = engine.setting_get("main_menu_tab") } end - + if fields["btn_all_mods"] then local list = filterlist.get_raw_list(modmgr.modlist) - + for i=1,#list,1 do if list[i].typ ~= "game_mod" and not list[i].is_modpack then @@ -816,9 +816,9 @@ function modmgr.handle_configure_world_buttons(fields) end end end - - + + return nil end -------------------------------------------------------------------------------- @@ -849,9 +849,9 @@ end -------------------------------------------------------------------------------- function modmgr.handle_delete_mod_buttons(fields) local mod = filterlist.get_list(modmgr.global_mods)[modmgr.selected_mod] - + if fields["dlg_delete_mod_confirm"] ~= nil then - + if mod.path ~= nil and mod.path ~= "" and mod.path ~= engine.get_modpath() then @@ -863,7 +863,7 @@ function modmgr.handle_delete_mod_buttons(fields) gamedata.errormessage = fgettext("Modmgr: invalid modpath \"$1\"", mod.path) end end - + return { is_dialog = false, show_buttons = true, @@ -875,8 +875,8 @@ end function modmgr.dialog_delete_mod() local mod = filterlist.get_list(modmgr.global_mods)[modmgr.selected_mod] - - local retval = + + local retval = "field[1.75,1;10,3;;" .. fgettext("Are you sure you want to delete \"$1\"?", mod.name) .. ";]".. "button[4,4.2;1,0.5;dlg_delete_mod_confirm;" .. fgettext("Yes") .. "]" .. "button[6.5,4.2;3,0.5;dlg_delete_mod_cancel;" .. fgettext("No of course not!") .. "]" @@ -887,10 +887,10 @@ end -------------------------------------------------------------------------------- function modmgr.preparemodlist(data) local retval = {} - + local global_mods = {} local game_mods = {} - + --read global mods local modpath = engine.get_modpath() @@ -898,31 +898,31 @@ function modmgr.preparemodlist(data) modpath ~= "" then get_mods(modpath,global_mods) end - + for i=1,#global_mods,1 do global_mods[i].typ = "global_mod" table.insert(retval,global_mods[i]) end - + --read game mods local gamespec = gamemgr.find_by_gameid(data.gameid) gamemgr.get_game_mods(gamespec, game_mods) - + for i=1,#game_mods,1 do game_mods[i].typ = "game_mod" table.insert(retval,game_mods[i]) end - + if data.worldpath == nil then return retval end - + --read world mod configuration local filename = data.worldpath .. DIR_DELIM .. "world.mt" local worldfile = Settings(filename) - + for key,value in pairs(worldfile:to_table()) do if key:sub(1, 9) == "load_mod_" then key = key:sub(10) @@ -948,17 +948,17 @@ end function modmgr.init_worldconfig() modmgr.precheck() local worldspec = engine.get_worlds()[modmgr.world_config_selected_world] - + if worldspec ~= nil then --read worldconfig modmgr.worldconfig = modmgr.get_worldconfig(worldspec.path) - + if modmgr.worldconfig.id == nil or modmgr.worldconfig.id == "" then modmgr.worldconfig = nil return false end - + modmgr.modlist = filterlist.create( modmgr.preparemodlist, --refresh modmgr.comparemod, --compare @@ -966,13 +966,13 @@ function modmgr.init_worldconfig() if element.name == uid then return true end - end, + end, function(element,criteria) if criteria.hide_game and element.typ == "game_mod" then return false end - + if criteria.hide_modpackcontents and element.modpack ~= nil then return false @@ -982,15 +982,15 @@ function modmgr.init_worldconfig() { worldpath= worldspec.path, gameid = worldspec.gameid } ) - + filterlist.set_filtercriteria(modmgr.modlist, { hide_game=modmgr.hide_gamemods, hide_modpackcontents= modmgr.hide_modpackcontents }) filterlist.add_sort_mechanism(modmgr.modlist, "alphabetic", sort_mod_list) filterlist.set_sortmode(modmgr.modlist, "alphabetic") - - return true + + return true end return false @@ -1013,34 +1013,34 @@ function modmgr.comparemod(elem1,elem2) if elem1.modpack ~= elem2.modpack then return false end - + if elem1.path ~= elem2.path then return false end - + return true end -------------------------------------------------------------------------------- function modmgr.gettab(name) local retval = "" - + if name == "mod_mgr" then retval = retval .. modmgr.tab() end - + if name == "dialog_rename_modpack" then retval = retval .. modmgr.dialog_rename_modpack() end - + if name == "dialog_delete_mod" then retval = retval .. modmgr.dialog_delete_mod() end - + if name == "dialog_configure_world" then retval = retval .. modmgr.dialog_configure_world() end - + return retval end @@ -1054,7 +1054,7 @@ function modmgr.mod_exists(basename) if filterlist.raw_index_by_uid(modmgr.global_mods,basename) > 0 then return true end - + return false end @@ -1064,7 +1064,7 @@ function modmgr.get_global_mod(idx) if modmgr.global_mods == nil then return nil end - + if idx < 1 or idx > filterlist.size(modmgr.global_mods) then return nil end @@ -1081,7 +1081,7 @@ function modmgr.refresh_globals() if element.name == uid then return true end - end, + end, nil, --filter {} ) @@ -1098,7 +1098,7 @@ function modmgr.identify_filetype(name) type = "zip" } end - + if name:sub(-6):lower() == "tar.gz" or name:sub(-3):lower() == "tgz"then return { @@ -1106,14 +1106,14 @@ function modmgr.identify_filetype(name) type = "tgz" } end - + if name:sub(-6):lower() == "tar.bz2" then return { name = name, type = "tbz" } end - + if name:sub(-2):lower() == "7z" then return { name = name, diff --git a/builtin/modstore.lua b/builtin/modstore.lua index b364ce6bd..acaff871b 100644 --- a/builtin/modstore.lua +++ b/builtin/modstore.lua @@ -23,19 +23,20 @@ modstore = {} -------------------------------------------------------------------------------- function modstore.init() modstore.tabnames = {} - + table.insert(modstore.tabnames,"dialog_modstore_unsorted") table.insert(modstore.tabnames,"dialog_modstore_search") - + modstore.modsperpage = 5 - - modstore.basetexturedir = engine.get_texturepath() .. DIR_DELIM .. "base" .. + + modstore.basetexturedir = engine.get_texturepath() .. DIR_DELIM .. "base" .. DIR_DELIM .. "pack" .. DIR_DELIM - + + modstore.lastmodtitle = "" + modstore.current_list = nil - - modstore.details_cache = {} end + -------------------------------------------------------------------------------- function modstore.nametoindex(name) @@ -51,30 +52,34 @@ end -------------------------------------------------------------------------------- function modstore.gettab(tabname) local retval = "" - + local is_modstore_tab = false - + if tabname == "dialog_modstore_unsorted" then retval = modstore.getmodlist(modstore.modlist_unsorted) is_modstore_tab = true end - + if tabname == "dialog_modstore_search" then - - + retval = modstore.getsearchpage() is_modstore_tab = true end - + if is_modstore_tab then return modstore.tabheader(tabname) .. retval end - + if tabname == "modstore_mod_installed" then - return "size[6,2]label[0.25,0.25;Mod: " .. modstore.lastmodtitle .. + return "size[6,2]label[0.25,0.25;Mod(s): " .. modstore.lastmodtitle .. " installed successfully]" .. "button[2.5,1.5;1,0.5;btn_confirm_mod_successfull;ok]" end - + + if tabname == "modstore_downloading" then + return "size[6,2]label[0.25,0.25;Dowloading " .. modstore.lastmodtitle .. + " please wait]" + end + return "" end @@ -84,18 +89,16 @@ function modstore.tabheader(tabname) retval = retval .. "tabheader[-0.3,-0.99;modstore_tab;" .. "Unsorted,Search;" .. modstore.nametoindex(tabname) .. ";true;false]" - + return retval end -------------------------------------------------------------------------------- function modstore.handle_buttons(current_tab,fields) - modstore.lastmodtitle = "" - if fields["modstore_tab"] then local index = tonumber(fields["modstore_tab"]) - + if index > 0 and index <= #modstore.tabnames then return { @@ -104,59 +107,102 @@ function modstore.handle_buttons(current_tab,fields) show_buttons = false } end - + modstore.modlist_page = 0 end - + if fields["btn_modstore_page_up"] then if modstore.current_list ~= nil and modstore.current_list.page > 0 then modstore.current_list.page = modstore.current_list.page - 1 end end - + if fields["btn_modstore_page_down"] then - if modstore.current_list ~= nil and + if modstore.current_list ~= nil and modstore.current_list.page <modstore.current_list.pagecount-1 then modstore.current_list.page = modstore.current_list.page +1 end end - + + if fields["btn_hidden_close_download"] then + return { + current_tab = "modstore_mod_installed", + is_dialog = true, + show_buttons = false + } + end + if fields["btn_confirm_mod_successfull"] then + modstore.lastmodtitle = "" return { current_tab = modstore.tabnames[1], is_dialog = true, show_buttons = false } end - + for i=1, modstore.modsperpage, 1 do local installbtn = "btn_install_mod_" .. i - + if fields[installbtn] then - local modlistentry = + local modlistentry = modstore.current_list.page * modstore.modsperpage + i - - local moddetails = modstore.get_details(modstore.current_list.data[modlistentry].id) - - local fullurl = engine.setting_get("modstore_download_url") .. - moddetails.download_url - local modfilename = os.tempfolder() .. ".zip" - - if engine.download_file(fullurl,modfilename) then - - modmgr.installmod(modfilename,moddetails.basename) - - os.remove(modfilename) - modstore.lastmodtitle = modstore.current_list.data[modlistentry].title - + + if modstore.modlist_unsorted.data[modlistentry] ~= nil and + modstore.modlist_unsorted.data[modlistentry].details ~= nil then + + local moddetails = modstore.modlist_unsorted.data[modlistentry].details + + if modstore.lastmodtitle ~= "" then + modstore.lastmodtitle = modstore.lastmodtitle .. ", " + end + + modstore.lastmodtitle = modstore.lastmodtitle .. moddetails.title + + engine.handle_async( + function(param) + local fullurl = engine.setting_get("modstore_download_url") .. + param.moddetails.download_url + + if engine.download_file(fullurl,param.filename) then + return { + moddetails = param.moddetails, + filename = param.filename, + successfull = true + } + else + return { + modtitle = param.title, + successfull = false + } + end + end, + { + moddetails = moddetails, + filename = os.tempfolder() .. ".zip" + }, + function(result) + if result.successfull then + modmgr.installmod(result.filename,result.moddetails.basename) + os.remove(result.filename) + else + gamedata.errormessage = "Failed to download " .. result.moddetails.title + end + + engine.button_handler({btn_hidden_close_download=true}) + end + ) + return { - current_tab = "modstore_mod_installed", + current_tab = "modstore_downloading", is_dialog = true, - show_buttons = false + show_buttons = false, + ignore_menu_quit = true } + else - gamedata.errormessage = "Unable to download " .. - moddetails.download_url .. " (internet connection?)" + gamedata.errormessage = + "Internal modstore error please leave modstore and reopen! (Sorry)" end end end @@ -165,110 +211,195 @@ end -------------------------------------------------------------------------------- function modstore.update_modlist() modstore.modlist_unsorted = {} - modstore.modlist_unsorted.data = engine.get_modstore_list() - - if modstore.modlist_unsorted.data ~= nil then - modstore.modlist_unsorted.pagecount = - math.ceil((#modstore.modlist_unsorted.data / modstore.modsperpage)) - else - modstore.modlist_unsorted.data = {} - modstore.modlist_unsorted.pagecount = 1 - end + modstore.modlist_unsorted.data = {} + modstore.modlist_unsorted.pagecount = 1 modstore.modlist_unsorted.page = 0 + + engine.handle_async( + function(param) + return engine.get_modstore_list() + end, + nil, + function(result) + if result ~= nil then + modstore.modlist_unsorted = {} + modstore.modlist_unsorted.data = result + + if modstore.modlist_unsorted.data ~= nil then + modstore.modlist_unsorted.pagecount = + math.ceil((#modstore.modlist_unsorted.data / modstore.modsperpage)) + else + modstore.modlist_unsorted.data = {} + modstore.modlist_unsorted.pagecount = 1 + end + modstore.modlist_unsorted.page = 0 + modstore.fetchdetails() + engine.event_handler("Refresh") + end + end + ) +end + +-------------------------------------------------------------------------------- +function modstore.fetchdetails() + + for i=1,#modstore.modlist_unsorted.data,1 do + engine.handle_async( + function(param) + param.details = engine.get_modstore_details(tostring(param.modid)) + return param + end, + { + modid=modstore.modlist_unsorted.data[i].id, + listindex=i + }, + function(result) + if result ~= nil and + modstore.modlist_unsorted ~= nil + and modstore.modlist_unsorted.data ~= nil and + modstore.modlist_unsorted.data[result.listindex] ~= nil and + modstore.modlist_unsorted.data[result.listindex].id ~= nil then + + modstore.modlist_unsorted.data[result.listindex].details = result.details + engine.event_handler("Refresh") + end + end + ) + end end +-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- function modstore.getmodlist(list) local retval = "" retval = retval .. "label[10,-0.4;" .. fgettext("Page $1 of $2", list.page+1, list.pagecount) .. "]" - + retval = retval .. "button[11.6,-0.1;0.5,0.5;btn_modstore_page_up;^]" retval = retval .. "box[11.6,0.35;0.28,8.6;#000000]" local scrollbarpos = 0.35 + (8.1/(list.pagecount-1)) * list.page retval = retval .. "box[11.6," ..scrollbarpos .. ";0.28,0.5;#32CD32]" retval = retval .. "button[11.6,9.0;0.5,0.5;btn_modstore_page_down;v]" - - + + if #list.data < (list.page * modstore.modsperpage) then return retval end - + local endmod = (list.page * modstore.modsperpage) + modstore.modsperpage - + if (endmod > #list.data) then endmod = #list.data end for i=(list.page * modstore.modsperpage) +1, endmod, 1 do --getmoddetails - local details = modstore.get_details(list.data[i].id) - + local details = list.data[i].details + +-- if details == nil then +-- details = modstore.get_details(list.data[i].id) +-- end + + if details == nil then + details = {} + details.title = list.data[i].title + details.author = "" + details.rating = -1 + details.description = "" + end + if details ~= nil then local screenshot_ypos = (i-1 - (list.page * modstore.modsperpage))*1.9 +0.2 - + retval = retval .. "box[0," .. screenshot_ypos .. ";11.4,1.75;#FFFFFF]" - - --screenshot - if details.screenshot_url ~= nil and - details.screenshot_url ~= "" then - if list.data[i].texturename == nil then - local fullurl = engine.setting_get("modstore_download_url") .. - details.screenshot_url - local filename = os.tempfolder() - - if engine.download_file(fullurl,filename) then - list.data[i].texturename = filename + + if details.basename then + --screenshot + if details.screenshot_url ~= nil and + details.screenshot_url ~= "" then + if list.data[i].texturename == nil then + local fullurl = engine.setting_get("modstore_download_url") .. + details.screenshot_url + local filename = os.tempfolder() .. "_MID_" .. list.data[i].id + list.data[i].texturename = "in progress" + engine.handle_async( + function(param) + param.successfull = engine.download_file(param.fullurl,param.filename) + return param + end, + { + fullurl = fullurl, + filename = filename, + listindex = i, + modid = list.data[i].id + }, + function(result) + if modstore.modlist_unsorted and + modstore.modlist_unsorted.data and + #modstore.modlist_unsorted.data >= result.listindex and + modstore.modlist_unsorted.data[result.listindex].id == result.modid then + if result.successfull then + modstore.modlist_unsorted.data[result.listindex].texturename = result.filename + else + modstore.modlist_unsorted.data[result.listindex].texturename = modstore.basetexturedir .. "no_screenshot.png" + end + engine.event_handler("Refresh") + end + end + ) + end + else + if list.data[i].texturename == nil then + list.data[i].texturename = modstore.basetexturedir .. "no_screenshot.png" end end + + if list.data[i].texturename ~= nil and + list.data[i].texturename ~= "in progress" then + retval = retval .. "image[0,".. screenshot_ypos .. ";3,2;" .. + engine.formspec_escape(list.data[i].texturename) .. "]" + end end - - if list.data[i].texturename == nil then - list.data[i].texturename = modstore.basetexturedir .. "no_screenshot.png" - end - - retval = retval .. "image[0,".. screenshot_ypos .. ";3,2;" .. - engine.formspec_escape(list.data[i].texturename) .. "]" - + --title + author - retval = retval .."label[2.75," .. screenshot_ypos .. ";" .. + retval = retval .."label[2.75," .. screenshot_ypos .. ";" .. engine.formspec_escape(details.title) .. " (" .. details.author .. ")]" - + --description local descriptiony = screenshot_ypos + 0.5 - retval = retval .. "textarea[3," .. descriptiony .. ";6.5,1.55;;" .. + retval = retval .. "textarea[3," .. descriptiony .. ";6.5,1.55;;" .. engine.formspec_escape(details.description) .. ";]" --rating local ratingy = screenshot_ypos + 0.6 - retval = retval .."label[10.1," .. ratingy .. ";" .. - fgettext("Rating") .. ": " .. details.rating .."]" - - --install button - local buttony = screenshot_ypos + 1.2 - local buttonnumber = (i - (list.page * modstore.modsperpage)) - retval = retval .."button[9.6," .. buttony .. ";2,0.5;btn_install_mod_" .. buttonnumber .. ";" - - if modmgr.mod_exists(details.basename) then - retval = retval .. fgettext("re-Install") .."]" - else - retval = retval .. fgettext("Install") .."]" + retval = retval .."label[9.1," .. ratingy .. ";" .. + fgettext("Rating") .. ":]" + retval = retval .. "label[11.1," .. ratingy .. ";" .. details.rating .."]" + + if details.basename then + --install button + local buttony = screenshot_ypos + 1.2 + local buttonnumber = (i - (list.page * modstore.modsperpage)) + retval = retval .."button[9.1," .. buttony .. ";2.5,0.5;btn_install_mod_" .. buttonnumber .. ";" + + if modmgr.mod_exists(details.basename) then + retval = retval .. fgettext("re-Install") .."]" + else + retval = retval .. fgettext("Install") .."]" + end end end end - + modstore.current_list = list - + return retval end -------------------------------------------------------------------------------- -function modstore.get_details(modid) +function modstore.getsearchpage() + local retval = "" - if modstore.details_cache[modid] ~= nil then - return modstore.details_cache[modid] - end - - local retval = engine.get_modstore_details(tostring(modid)) - modstore.details_cache[modid] = retval - return retval + --TODO implement search! + + return retval; end diff --git a/doc/menu_lua_api.txt b/doc/menu_lua_api.txt index ca815862e..90b2d01fd 100644 --- a/doc/menu_lua_api.txt +++ b/doc/menu_lua_api.txt @@ -33,9 +33,9 @@ engine.close() Filesystem: engine.get_scriptdir() ^ returns directory of script -engine.get_modpath() +engine.get_modpath() (possible in async calls) ^ returns path to global modpath -engine.get_modstore_details(modid) +engine.get_modstore_details(modid) (possible in async calls) ^ modid numeric id of mod in modstore ^ returns { id = <numeric id of mod in modstore>, @@ -47,7 +47,7 @@ engine.get_modstore_details(modid) license = <short description of license>, rating = <float value of current rating> } -engine.get_modstore_list() +engine.get_modstore_list() (possible in async calls) ^ returns { [1] = { id = <numeric id of mod in modstore>, @@ -55,19 +55,21 @@ engine.get_modstore_list() basename = <basename for mod> } } -engine.get_gamepath() +engine.get_gamepath() (possible in async calls) ^ returns path to global gamepath -engine.get_dirlist(path,onlydirs) +engine.get_texturepath() (possible in async calls) +^ returns path to default textures +engine.get_dirlist(path,onlydirs) (possible in async calls) ^ path to get subdirs from ^ onlydirs should result contain only dirs? ^ returns list of folders within path -engine.create_dir(absolute_path) +engine.create_dir(absolute_path) (possible in async calls) ^ absolute_path to directory to create (needs to be absolute) ^ returns true/false -engine.delete_dir(absolute_path) +engine.delete_dir(absolute_path) (possible in async calls) ^ absolute_path to directory to delete (needs to be absolute) ^ returns true/false -engine.copy_dir(source,destination,keep_soure) +engine.copy_dir(source,destination,keep_soure) (possible in async calls) ^ source folder ^ destination folder ^ keep_source DEFAULT true --> if set to false source is deleted after copying @@ -76,11 +78,11 @@ engine.extract_zip(zipfile,destination) [unzip within path required] ^ zipfile to extract ^ destination folder to extract to ^ returns true/false -engine.download_file(url,target) +engine.download_file(url,target) (possible in async calls) ^ url to download ^ target to store to ^ returns true/false -engine.get_version() +engine.get_version() (possible in async calls) ^ returns current minetest version engine.sound_play(spec, looped) -> handle ^ spec = SimpleSoundSpec (see lua-api.txt) @@ -105,10 +107,10 @@ engine.get_game(index) DEPRECATED: addon_mods_paths = {[1] = <path>,}, } -engine.get_games() -> table of all games in upper format +engine.get_games() -> table of all games in upper format (possible in async calls) Favorites: -engine.get_favorites(location) -> list of favorites +engine.get_favorites(location) -> list of favorites (possible in async calls) ^ location: "local" or "online" ^ returns { [1] = { @@ -128,21 +130,21 @@ engine.get_favorites(location) -> list of favorites engine.delete_favorite(id, location) -> success Logging: -engine.debug(line) +engine.debug(line) (possible in async calls) ^ Always printed to stderr and logfile (print() is redirected here) -engine.log(line) -engine.log(loglevel, line) +engine.log(line) (possible in async calls) +engine.log(loglevel, line) (possible in async calls) ^ loglevel one of "error", "action", "info", "verbose" Settings: engine.setting_set(name, value) -engine.setting_get(name) -> string or nil +engine.setting_get(name) -> string or nil (possible in async calls) engine.setting_setbool(name, value) -engine.setting_getbool(name) -> bool or nil +engine.setting_getbool(name) -> bool or nil (possible in async calls) engine.setting_save() -> nil, save all settings to config file Worlds: -engine.get_worlds() -> list of worlds +engine.get_worlds() -> list of worlds (possible in async calls) ^ returns { [1] = { path = <full path to world>, @@ -174,7 +176,7 @@ engine.gettext(string) -> string fgettext(string, ...) -> string ^ call engine.gettext(string), replace "$1"..."$9" with the given ^ extra arguments, call engine.formspec_escape and return the result -engine.parse_json(string[, nullvalue]) -> something +engine.parse_json(string[, nullvalue]) -> something (possible in async calls) ^ see minetest.parse_json (lua_api.txt) dump(obj, dumped={}) ^ Return object serialized as a string @@ -182,9 +184,24 @@ string:split(separator) ^ eg. string:split("a,b", ",") == {"a","b"} string:trim() ^ eg. string.trim("\n \t\tfoo bar\t ") == "foo bar" -minetest.is_yes(arg) +minetest.is_yes(arg) (possible in async calls) ^ returns whether arg can be interpreted as yes +Async: +engine.handle_async(async_job,parameters,finished) +^ execute a function asynchronously +^ async_job is a function receiving one parameter and returning one parameter +^ parameters parameter table passed to async_job +^ finished function to be called once async_job has finished +^ the result of async_job is passed to this function + +Limitations of Async operations + -No access to global lua variables, don't even try + -Limited set of available functions + e.g. No access to functions modifying menu like engine.start,engine.close, + engine.file_open_dialog + + Class reference ---------------- Settings: see lua_api.txt diff --git a/src/guiEngine.cpp b/src/guiEngine.cpp index d5f528f3b..7acc00ef1 100644 --- a/src/guiEngine.cpp +++ b/src/guiEngine.cpp @@ -286,6 +286,8 @@ void GUIEngine::run() cloudPostProcess(); else sleep_ms(25); + + m_script->Step(); } } @@ -576,3 +578,9 @@ void GUIEngine::stopSound(s32 handle) { m_sound_manager->stopSound(handle); } + +/******************************************************************************/ +unsigned int GUIEngine::DoAsync(std::string serialized_fct, + std::string serialized_params) { + return m_script->DoAsync(serialized_fct,serialized_params); +} diff --git a/src/guiEngine.h b/src/guiEngine.h index 484459395..6b1281546 100644 --- a/src/guiEngine.h +++ b/src/guiEngine.h @@ -166,6 +166,9 @@ public: return m_scriptdir; } + /** pass async callback to scriptengine **/ + unsigned int DoAsync(std::string serialized_fct,std::string serialized_params); + private: /** find and run the main menu script */ @@ -244,7 +247,7 @@ private: * @param url url to download * @param target file to store to */ - bool downloadFile(std::string url,std::string target); + static bool downloadFile(std::string url,std::string target); /** array containing pointers to current specified texture layers */ video::ITexture* m_textures[TEX_LAYER_MAX]; diff --git a/src/jthread/CMakeLists.txt b/src/jthread/CMakeLists.txt index aa438eaaf..6c29671e6 100644 --- a/src/jthread/CMakeLists.txt +++ b/src/jthread/CMakeLists.txt @@ -2,10 +2,12 @@ if( UNIX ) set(JTHREAD_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/pthread/jmutex.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pthread/jthread.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pthread/jsemaphore.cpp PARENT_SCOPE) else( UNIX ) set(JTHREAD_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/win32/jmutex.cpp ${CMAKE_CURRENT_SOURCE_DIR}/win32/jthread.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/win32/jsemaphore.cpp PARENT_SCOPE) endif( UNIX ) diff --git a/src/jthread/jsemaphore.h b/src/jthread/jsemaphore.h new file mode 100644 index 000000000..70318d5da --- /dev/null +++ b/src/jthread/jsemaphore.h @@ -0,0 +1,50 @@ +/* +Minetest +Copyright (C) 2013 sapier, < sapier AT gmx DOT net > + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef JSEMAPHORE_H_ +#define JSEMAPHORE_H_ + +#if defined(WIN32) +#include <windows.h> +#define MAX_SEMAPHORE_COUNT 1024 +#else +#include <pthread.h> +#include <semaphore.h> +#endif + +class JSemaphore { +public: + JSemaphore(); + ~JSemaphore(); + JSemaphore(int initval); + + void Post(); + void Wait(); + + int GetValue(); + +private: +#if defined(WIN32) + HANDLE m_hSemaphore; +#else + sem_t m_semaphore; +#endif +}; + +#endif /* JSEMAPHORE_H_ */ diff --git a/src/jthread/jthread.h b/src/jthread/jthread.h index ec1eafaeb..92b05f1c5 100644 --- a/src/jthread/jthread.h +++ b/src/jthread/jthread.h @@ -43,6 +43,7 @@ public: JThread(); virtual ~JThread(); int Start(); + void Stop(); int Kill(); virtual void *Thread() = 0; bool IsRunning(); @@ -63,12 +64,12 @@ private: HANDLE threadhandle; #else // pthread type threads static void *TheThread(void *param); - + pthread_t threadid; #endif // WIN32 void *retval; bool running; - + JMutex runningmutex; JMutex continuemutex,continuemutex2; bool mutexinit; diff --git a/src/jthread/pthread/jsemaphore.cpp b/src/jthread/pthread/jsemaphore.cpp new file mode 100644 index 000000000..963ac83cf --- /dev/null +++ b/src/jthread/pthread/jsemaphore.cpp @@ -0,0 +1,48 @@ +/* +Minetest +Copyright (C) 2013 sapier, < sapier AT gmx DOT net > + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "jthread/jsemaphore.h" + +JSemaphore::JSemaphore() { + sem_init(&m_semaphore,0,0); +} + +JSemaphore::~JSemaphore() { + sem_destroy(&m_semaphore); +} + +JSemaphore::JSemaphore(int initval) { + sem_init(&m_semaphore,0,initval); +} + +void JSemaphore::Post() { + sem_post(&m_semaphore); +} + +void JSemaphore::Wait() { + sem_wait(&m_semaphore); +} + +int JSemaphore::GetValue() { + + int retval = 0; + sem_getvalue(&m_semaphore,&retval); + + return retval; +} + diff --git a/src/jthread/pthread/jthread.cpp b/src/jthread/pthread/jthread.cpp index 0ef250825..2980e26b1 100644 --- a/src/jthread/pthread/jthread.cpp +++ b/src/jthread/pthread/jthread.cpp @@ -42,6 +42,12 @@ JThread::~JThread() Kill(); } +void JThread::Stop() { + runningmutex.Lock(); + running = false; + runningmutex.Unlock(); +} + int JThread::Start() { int status; @@ -65,7 +71,7 @@ int JThread::Start() } mutexinit = true; } - + runningmutex.Lock(); if (running) { @@ -73,27 +79,27 @@ int JThread::Start() return ERR_JTHREAD_ALREADYRUNNING; } runningmutex.Unlock(); - + pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); - + continuemutex.Lock(); - status = pthread_create(&threadid,&attr,TheThread,this); + status = pthread_create(&threadid,&attr,TheThread,this); pthread_attr_destroy(&attr); if (status != 0) { continuemutex.Unlock(); return ERR_JTHREAD_CANTSTARTTHREAD; } - + /* Wait until 'running' is set */ - - runningmutex.Lock(); + + runningmutex.Lock(); while (!running) { runningmutex.Unlock(); - + struct timespec req,rem; req.tv_sec = 0; @@ -103,9 +109,9 @@ int JThread::Start() runningmutex.Lock(); } runningmutex.Unlock(); - + continuemutex.Unlock(); - + continuemutex2.Lock(); continuemutex2.Unlock(); return 0; @@ -113,7 +119,7 @@ int JThread::Start() int JThread::Kill() { - runningmutex.Lock(); + runningmutex.Lock(); if (!running) { runningmutex.Unlock(); @@ -128,8 +134,8 @@ int JThread::Kill() bool JThread::IsRunning() { bool r; - - runningmutex.Lock(); + + runningmutex.Lock(); r = running; runningmutex.Unlock(); return r; @@ -138,7 +144,7 @@ bool JThread::IsRunning() void *JThread::GetReturnValue() { void *val; - + runningmutex.Lock(); if (running) val = NULL; @@ -157,17 +163,17 @@ void *JThread::TheThread(void *param) { JThread *jthread; void *ret; - + jthread = (JThread *)param; - + jthread->continuemutex2.Lock(); jthread->runningmutex.Lock(); jthread->running = true; jthread->runningmutex.Unlock(); - + jthread->continuemutex.Lock(); jthread->continuemutex.Unlock(); - + ret = jthread->Thread(); jthread->runningmutex.Lock(); diff --git a/src/jthread/win32/jsemaphore.cpp b/src/jthread/win32/jsemaphore.cpp new file mode 100644 index 000000000..8eca6d247 --- /dev/null +++ b/src/jthread/win32/jsemaphore.cpp @@ -0,0 +1,64 @@ +/* +Minetest +Copyright (C) 2013 sapier, < sapier AT gmx DOT net > + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "jthread/jsemaphore.h" + +JSemaphore::JSemaphore() { + m_hSemaphore = CreateSemaphore( + 0, + 0, + MAX_SEMAPHORE_COUNT, + 0); +} + +JSemaphore::~JSemaphore() { + CloseHandle(&m_hSemaphore); +} + +JSemaphore::JSemaphore(int initval) { + m_hSemaphore = CreateSemaphore( + 0, + initval, + MAX_SEMAPHORE_COUNT, + 0); +} + +void JSemaphore::Post() { + ReleaseSemaphore( + m_hSemaphore, + 1, + 0); +} + +void JSemaphore::Wait() { + WaitForSingleObject( + m_hSemaphore, + INFINITE); +} + +int JSemaphore::GetValue() { + + long int retval = 0; + ReleaseSemaphore( + m_hSemaphore, + 0, + &retval); + + return retval; +} + diff --git a/src/jthread/win32/jthread.cpp b/src/jthread/win32/jthread.cpp index 48b83b894..1cf4f93a3 100644 --- a/src/jthread/win32/jthread.cpp +++ b/src/jthread/win32/jthread.cpp @@ -43,6 +43,12 @@ JThread::~JThread() Kill(); } +void JThread::Stop() { + runningmutex.Lock(); + running = false; + runningmutex.Unlock(); +} + int JThread::Start() { if (!mutexinit) @@ -63,7 +69,7 @@ int JThread::Start() return ERR_JTHREAD_CANTINITMUTEX; } mutexinit = true; } - + runningmutex.Lock(); if (running) { @@ -71,7 +77,7 @@ int JThread::Start() return ERR_JTHREAD_ALREADYRUNNING; } runningmutex.Unlock(); - + continuemutex.Lock(); #ifndef _WIN32_WCE threadhandle = (HANDLE)_beginthreadex(NULL,0,TheThread,this,0,&threadid); @@ -83,10 +89,10 @@ int JThread::Start() continuemutex.Unlock(); return ERR_JTHREAD_CANTSTARTTHREAD; } - + /* Wait until 'running' is set */ - runningmutex.Lock(); + runningmutex.Lock(); while (!running) { runningmutex.Unlock(); @@ -94,18 +100,18 @@ int JThread::Start() runningmutex.Lock(); } runningmutex.Unlock(); - + continuemutex.Unlock(); - + continuemutex2.Lock(); continuemutex2.Unlock(); - + return 0; } int JThread::Kill() { - runningmutex.Lock(); + runningmutex.Lock(); if (!running) { runningmutex.Unlock(); @@ -121,8 +127,8 @@ int JThread::Kill() bool JThread::IsRunning() { bool r; - - runningmutex.Lock(); + + runningmutex.Lock(); r = running; runningmutex.Unlock(); return r; @@ -131,7 +137,7 @@ bool JThread::IsRunning() void *JThread::GetReturnValue() { void *val; - + runningmutex.Lock(); if (running) val = NULL; @@ -156,23 +162,23 @@ DWORD WINAPI JThread::TheThread(void *param) void *ret; jthread = (JThread *)param; - + jthread->continuemutex2.Lock(); jthread->runningmutex.Lock(); jthread->running = true; jthread->runningmutex.Unlock(); - + jthread->continuemutex.Lock(); jthread->continuemutex.Unlock(); - + ret = jthread->Thread(); - + jthread->runningmutex.Lock(); jthread->running = false; jthread->retval = ret; CloseHandle(jthread->threadhandle); jthread->runningmutex.Unlock(); - return 0; + return 0; } void JThread::ThreadStarted() diff --git a/src/main.cpp b/src/main.cpp index 373e1ca96..2833bdcf7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -85,6 +85,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database-leveldb.h" #endif +#if USE_CURL +#include "curl.h" +#endif + /* Settings. These are loaded from the config file. @@ -309,7 +313,7 @@ public: { return keyIsDown[keyCode]; } - + // Checks whether a key was down and resets the state bool WasKeyDown(const KeyPress &keyCode) { @@ -361,7 +365,7 @@ public: private: IrrlichtDevice *m_device; - + // The current state of keys KeyList keyIsDown; // Whether a key has been pressed or not @@ -405,7 +409,7 @@ public: { return m_receiver->right_active; } - + virtual bool getLeftClicked() { return m_receiver->leftclicked; @@ -656,7 +660,7 @@ void SpeedTests() } } } - + infostream<<"All of the following tests should take around 100ms each." <<std::endl; @@ -668,7 +672,7 @@ void SpeedTests() tempf += 0.001; } } - + { TimeTaker timer("Testing floating-point vector speed"); @@ -682,7 +686,7 @@ void SpeedTests() { TimeTaker timer("Testing std::map speed"); - + std::map<v2s16, f32> map1; tempf = -324; const s16 ii=300; @@ -702,7 +706,7 @@ void SpeedTests() { infostream<<"Around 5000/ms should do well here."<<std::endl; TimeTaker timer("Testing mutex speed"); - + JMutex m; m.Init(); u32 n = 0; @@ -753,7 +757,7 @@ int main(int argc, char *argv[]) /* Parse command line */ - + // List all allowed options std::map<std::string, ValueSpec> allowed_options; allowed_options.insert(std::make_pair("help", ValueSpec(VALUETYPE_FLAG, @@ -806,7 +810,7 @@ int main(int argc, char *argv[]) #endif Settings cmd_args; - + bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options); if(ret == false || cmd_args.getFlag("help") || cmd_args.exists("nonopt1")) @@ -843,11 +847,11 @@ int main(int argc, char *argv[]) dstream<<"Build info: "<<minetest_build_info<<std::endl; return 0; } - + /* Low-level initialization */ - + // If trace is enabled, enable logging of certain things if(cmd_args.getFlag("trace")){ dstream<<_("Enabling trace level debug output")<<std::endl; @@ -865,7 +869,7 @@ int main(int argc, char *argv[]) porting::signal_handler_init(); bool &kill = *porting::signal_handler_killstatus(); - + porting::initializePaths(); // Create user data directory @@ -880,7 +884,7 @@ int main(int argc, char *argv[]) // Debug handler BEGIN_DEBUG_EXCEPTION_HANDLER - + // List gameids if requested if(cmd_args.exists("gameid") && cmd_args.get("gameid") == "list") { @@ -890,7 +894,7 @@ int main(int argc, char *argv[]) dstream<<(*i)<<std::endl; return 0; } - + // List worlds if requested if(cmd_args.exists("world") && cmd_args.get("world") == "list"){ dstream<<_("Available worlds:")<<std::endl; @@ -904,25 +908,25 @@ int main(int argc, char *argv[]) " "<<_("with")<<" SER_FMT_VER_HIGHEST_READ="<<(int)SER_FMT_VER_HIGHEST_READ <<", "<<minetest_build_info <<std::endl; - + /* Basic initialization */ // Initialize default settings set_default_settings(g_settings); - + // Initialize sockets sockets_init(); atexit(sockets_cleanup); - + /* Read config file */ - + // Path of configuration file in use g_settings_path = ""; - + if(cmd_args.exists("config")) { bool r = g_settings->readConfigFile(cmd_args.get("config").c_str()); @@ -958,12 +962,12 @@ int main(int argc, char *argv[]) break; } } - + // If no path found, use the first one (menu creates the file) if(g_settings_path == "") g_settings_path = filenames[0]; } - + // Initialize debug streams #define DEBUGFILE "debug.txt" #if RUN_IN_PLACE @@ -973,7 +977,7 @@ int main(int argc, char *argv[]) #endif if(cmd_args.exists("logfile")) logfile = cmd_args.get("logfile"); - + log_remove_output(&main_dstream_no_stderr_log_out); int loglevel = g_settings->getS32("debug_log_level"); @@ -986,13 +990,18 @@ int main(int argc, char *argv[]) debugstreams_init(false, logfile.c_str()); else debugstreams_init(false, NULL); - + infostream<<"logfile = "<<logfile<<std::endl; // Initialize random seed srand(time(0)); mysrand(time(0)); +#if USE_CURL + CURLcode res = curl_global_init(CURL_GLOBAL_DEFAULT); + assert(res == CURLE_OK); +#endif + /* Run unit tests */ @@ -1020,7 +1029,7 @@ int main(int argc, char *argv[]) port = g_settings->getU16("port"); if(port == 0) port = 30000; - + // World directory std::string commanded_world = ""; if(cmd_args.exists("world")) @@ -1031,12 +1040,12 @@ int main(int argc, char *argv[]) commanded_world = cmd_args.get("nonopt0"); else if(g_settings->exists("map-dir")) commanded_world = g_settings->get("map-dir"); - + // World name std::string commanded_worldname = ""; if(cmd_args.exists("worldname")) commanded_worldname = cmd_args.get("worldname"); - + // Strip world.mt from commanded_world { std::string worldmt = "world.mt"; @@ -1048,7 +1057,7 @@ int main(int argc, char *argv[]) 0, commanded_world.size()-worldmt.size()); } } - + // If a world name was specified, convert it to a path if(commanded_worldname != ""){ // Get information about available worlds @@ -1268,7 +1277,7 @@ int main(int argc, char *argv[]) } server.start(port); - + // Run server dedicated_server_loop(server, kill); @@ -1280,17 +1289,17 @@ int main(int argc, char *argv[]) /* More parameters */ - + std::string address = g_settings->get("address"); if(commanded_world != "") address = ""; else if(cmd_args.exists("address")) address = cmd_args.get("address"); - + std::string playername = g_settings->get("name"); if(cmd_args.exists("name")) playername = cmd_args.get("name"); - + bool skip_main_menu = cmd_args.getFlag("go"); /* @@ -1298,7 +1307,7 @@ int main(int argc, char *argv[]) */ // Resolution selection - + bool fullscreen = g_settings->getBool("fullscreen"); u16 screenW = g_settings->getU16("screenW"); u16 screenH = g_settings->getU16("screenH"); @@ -1312,7 +1321,7 @@ int main(int argc, char *argv[]) // Determine driver video::E_DRIVER_TYPE driverType; - + std::string driverstring = g_settings->get("video_driver"); if(driverstring == "null") @@ -1419,7 +1428,7 @@ int main(int argc, char *argv[]) if (device == 0) return 1; // could not create selected driver. - + /* Continue initialization */ @@ -1434,10 +1443,10 @@ int main(int argc, char *argv[]) // Create time getter g_timegetter = new IrrlichtTimeGetter(device); - + // Create game callback for menus g_gamecallback = new MainGameCallback(device); - + /* Speed tests (done after irrlicht is loaded to get timer) */ @@ -1448,7 +1457,7 @@ int main(int argc, char *argv[]) device->drop(); return 0; } - + device->setResizable(true); bool random_input = g_settings->getBool("random_input") @@ -1458,7 +1467,7 @@ int main(int argc, char *argv[]) input = new RandomInputHandler(); else input = new RealInputHandler(device, &receiver); - + scene::ISceneManager* smgr = device->getSceneManager(); guienv = device->getGUIEnvironment(); @@ -1488,7 +1497,7 @@ int main(int argc, char *argv[]) // If font was not found, this will get us one font = skin->getFont(); assert(font); - + u32 text_height = font->getDimension(L"Hello, world!").Height; infostream<<"text_height="<<text_height<<std::endl; @@ -1556,7 +1565,7 @@ int main(int argc, char *argv[]) Clear everything from the GUIEnvironment */ guienv->clear(); - + /* We need some kind of a root node to be able to add custom gui elements directly on the screen. @@ -1564,7 +1573,7 @@ int main(int argc, char *argv[]) */ guiroot = guienv->addStaticText(L"", core::rect<s32>(0, 0, 10000, 10000)); - + SubgameSpec gamespec; WorldSpec worldspec; bool simple_singleplayer_mode = false; @@ -1588,13 +1597,13 @@ int main(int argc, char *argv[]) break; } first_loop = false; - + // Cursor can be non-visible when coming from the game device->getCursorControl()->setVisible(true); // Some stuff are left to scene manager when coming from the game // (map at least?) smgr->clear(); - + // Initialize menu data MainMenuData menudata; menudata.address = address; @@ -1643,7 +1652,7 @@ int main(int argc, char *argv[]) infostream<<"Waited for other menus"<<std::endl; GUIEngine* temp = new GUIEngine(device, guiroot, &g_menumgr,smgr,&menudata,kill); - + delete temp; //once finished you'll never end up here smgr->clear(); @@ -1683,7 +1692,7 @@ int main(int argc, char *argv[]) // Break out of menu-game loop to shut down cleanly if(device->run() == false || kill == true) break; - + current_playername = playername; current_password = password; current_address = address; @@ -1705,7 +1714,7 @@ int main(int argc, char *argv[]) server["description"] = menudata.serverdescription; ServerList::insert(server); } - + // Set world path to selected one if ((menudata.selected_world >= 0) && (menudata.selected_world < (int)worldspecs.size())) { @@ -1713,7 +1722,7 @@ int main(int argc, char *argv[]) infostream<<"Selected world: "<<worldspec.name <<" ["<<worldspec.path<<"]"<<std::endl; } - + // If local game if(current_address == "") { @@ -1828,11 +1837,11 @@ int main(int argc, char *argv[]) #endif #endif // !SERVER - + // Update configuration file if(g_settings_path != "") g_settings->updateConfigFile(g_settings_path.c_str()); - + // Print modified quicktune values { bool header_printed = false; @@ -1850,9 +1859,9 @@ int main(int argc, char *argv[]) } END_DEBUG_EXCEPTION_HANDLER(errorstream) - + debugstreams_deinit(); - + return retval; } diff --git a/src/script/lua_api/CMakeLists.txt b/src/script/lua_api/CMakeLists.txt index 08960d2ad..0b89df6a3 100644 --- a/src/script/lua_api/CMakeLists.txt +++ b/src/script/lua_api/CMakeLists.txt @@ -16,6 +16,8 @@ set(common_SCRIPT_LUA_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/l_util.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_vmanip.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_settings.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/l_async_events.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/marshall.c PARENT_SCOPE) # Used by client only diff --git a/src/script/lua_api/l_async_events.cpp b/src/script/lua_api/l_async_events.cpp new file mode 100644 index 000000000..cc4644cdf --- /dev/null +++ b/src/script/lua_api/l_async_events.cpp @@ -0,0 +1,396 @@ +/* +Minetest +Copyright (C) 2013 sapier, <sapier AT gmx DOT net> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern "C" { +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" +int luaopen_marshal(lua_State *L); +} +#include <stdio.h> + +#include "l_async_events.h" +#include "log.h" +#include "filesys.h" +#include "porting.h" + +//TODO replace by ShadowNinja version not yet merged to master +static int script_error_handler(lua_State *L) { + lua_getfield(L, LUA_GLOBALSINDEX, "debug"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return 1; + } + lua_getfield(L, -1, "traceback"); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 2); + return 1; + } + lua_pushvalue(L, 1); + lua_pushinteger(L, 2); + lua_call(L, 2, 1); + return 1; +} + +/******************************************************************************/ +static void scriptError(const char *fmt, ...) +{ + va_list argp; + va_start(argp, fmt); + char buf[10000]; + vsnprintf(buf, 10000, fmt, argp); + va_end(argp); + errorstream<<"ERROR: "<<buf; + fprintf(stderr,"ERROR: %s\n",buf); +} + +/******************************************************************************/ +AsyncEngine::AsyncEngine() : + m_initDone(false), + m_JobIdCounter(0) +{ + assert(m_JobQueueMutex.Init() == 0); + assert(m_ResultQueueMutex.Init() == 0); +} + +/******************************************************************************/ +AsyncEngine::~AsyncEngine() +{ + /** request all threads to stop **/ + for (std::vector<AsyncWorkerThread*>::iterator i= m_WorkerThreads.begin(); + i != m_WorkerThreads.end();i++) { + (*i)->Stop(); + } + + + /** wakeup all threads **/ + for (std::vector<AsyncWorkerThread*>::iterator i= m_WorkerThreads.begin(); + i != m_WorkerThreads.end();i++) { + m_JobQueueCounter.Post(); + } + + /** wait for threads to finish **/ + for (std::vector<AsyncWorkerThread*>::iterator i= m_WorkerThreads.begin(); + i != m_WorkerThreads.end();i++) { + (*i)->Wait(); + } + + /** force kill all threads **/ + for (std::vector<AsyncWorkerThread*>::iterator i= m_WorkerThreads.begin(); + i != m_WorkerThreads.end();i++) { + (*i)->Kill(); + delete *i; + } + + m_JobQueueMutex.Lock(); + m_JobQueue.clear(); + m_JobQueueMutex.Unlock(); + m_WorkerThreads.clear(); +} + +/******************************************************************************/ +bool AsyncEngine::registerFunction(const char* name, lua_CFunction fct) { + + if (m_initDone) return false; + m_FunctionList[name] = fct; + return true; +} + +/******************************************************************************/ +void AsyncEngine::Initialize(unsigned int numengines) { + m_initDone = true; + + for (unsigned int i=0; i < numengines; i ++) { + + AsyncWorkerThread* toadd = new AsyncWorkerThread(this,i); + m_WorkerThreads.push_back(toadd); + toadd->Start(); + } +} + +/******************************************************************************/ +unsigned int AsyncEngine::doAsyncJob(std::string fct, std::string params) { + + m_JobQueueMutex.Lock(); + LuaJobInfo toadd; + toadd.JobId = m_JobIdCounter++; + toadd.serializedFunction = fct; + toadd.serializedParams = params; + + m_JobQueue.push_back(toadd); + + m_JobQueueCounter.Post(); + + m_JobQueueMutex.Unlock(); + + return toadd.JobId; +} + +/******************************************************************************/ +LuaJobInfo AsyncEngine::getJob() { + + m_JobQueueCounter.Wait(); + m_JobQueueMutex.Lock(); + + LuaJobInfo retval; + + if (m_JobQueue.size() != 0) { + retval = m_JobQueue.front(); + m_JobQueue.erase((m_JobQueue.begin())); + } + m_JobQueueMutex.Unlock(); + + return retval; +} + +/******************************************************************************/ +void AsyncEngine::putJobResult(LuaJobInfo result) { + m_ResultQueueMutex.Lock(); + m_ResultQueue.push_back(result); + m_ResultQueueMutex.Unlock(); +} + +/******************************************************************************/ +void AsyncEngine::Step(lua_State *L) { + m_ResultQueueMutex.Lock(); + while(!m_ResultQueue.empty()) { + + LuaJobInfo jobdone = m_ResultQueue.front(); + m_ResultQueue.erase(m_ResultQueue.begin()); + lua_getglobal(L, "engine"); + + lua_getfield(L, -1, "async_event_handler"); + + if(lua_isnil(L, -1)) + assert("Someone managed to destroy a async callback in engine!" == 0); + + luaL_checktype(L, -1, LUA_TFUNCTION); + + lua_pushinteger(L, jobdone.JobId); + lua_pushlstring(L, jobdone.serializedResult.c_str(), + jobdone.serializedResult.length()); + + if(lua_pcall(L, 2, 0, 0)) { + scriptError("Async ENGINE step: %s", lua_tostring(L, -1)); + } + + lua_pop(L,1); + } + m_ResultQueueMutex.Unlock(); +} + +/******************************************************************************/ +void AsyncEngine::PushFinishedJobs(lua_State* L) { + //Result Table + m_ResultQueueMutex.Lock(); + + unsigned int index=1; + lua_newtable(L); + int top = lua_gettop(L); + + while(!m_ResultQueue.empty()) { + + LuaJobInfo jobdone = m_ResultQueue.front(); + m_ResultQueue.erase(m_ResultQueue.begin()); + + lua_pushnumber(L,index); + + lua_newtable(L); + int top_lvl2 = lua_gettop(L); + + lua_pushstring(L,"jobid"); + lua_pushnumber(L,jobdone.JobId); + lua_settable(L, top_lvl2); + + lua_pushstring(L,"retval"); + lua_pushstring(L, jobdone.serializedResult.c_str()); + lua_settable(L, top_lvl2); + + lua_settable(L, top); + index++; + } + + m_ResultQueueMutex.Unlock(); + +} +/******************************************************************************/ +void AsyncEngine::PrepareEnvironment(lua_State* L, int top) { + for(std::map<std::string,lua_CFunction>::iterator i = m_FunctionList.begin(); + i != m_FunctionList.end(); i++) { + + lua_pushstring(L,i->first.c_str()); + lua_pushcfunction(L,i->second); + lua_settable(L, top); + + } +} + +/******************************************************************************/ +int async_worker_ErrorHandler(lua_State *L) { + lua_getfield(L, LUA_GLOBALSINDEX, "debug"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return 1; + } + lua_getfield(L, -1, "traceback"); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 2); + return 1; + } + lua_pushvalue(L, 1); + lua_pushinteger(L, 2); + lua_call(L, 2, 1); + return 1; +} + +/******************************************************************************/ +AsyncWorkerThread::AsyncWorkerThread(AsyncEngine* jobdispatcher, + unsigned int numthreadnumber) : + m_JobDispatcher(jobdispatcher), + m_luaerrorhandler(-1), + m_threadnum(numthreadnumber) +{ + // create luastack + m_LuaStack = luaL_newstate(); + + // load basic lua modules + luaL_openlibs(m_LuaStack); + + // load serialization functions + luaopen_marshal(m_LuaStack); +} + +/******************************************************************************/ +AsyncWorkerThread::~AsyncWorkerThread() { + + assert(IsRunning() == false); + lua_close(m_LuaStack); +} + +/******************************************************************************/ +void* AsyncWorkerThread::worker_thread_main() { + + //register thread for error logging + char number[21]; + snprintf(number,sizeof(number),"%d",m_threadnum); + log_register_thread(std::string("AsyncWorkerThread_") + number); + + /** prepare job lua environment **/ + lua_newtable(m_LuaStack); + lua_setglobal(m_LuaStack, "engine"); + + lua_getglobal(m_LuaStack, "engine"); + int top = lua_gettop(m_LuaStack); + + lua_pushstring(m_LuaStack, DIR_DELIM); + lua_setglobal(m_LuaStack, "DIR_DELIM"); + + lua_pushstring(m_LuaStack, + std::string(porting::path_share + DIR_DELIM + "builtin").c_str()); + lua_setglobal(m_LuaStack, "SCRIPTDIR"); + + + m_JobDispatcher->PrepareEnvironment(m_LuaStack,top); + + std::string asyncscript = + porting::path_share + DIR_DELIM + "builtin" + + DIR_DELIM + "async_env.lua"; + + lua_pushcfunction(m_LuaStack, async_worker_ErrorHandler); + m_luaerrorhandler = lua_gettop(m_LuaStack); + + if(!runScript(asyncscript)) { + infostream + << "AsyncWorkderThread::worker_thread_main execution of async base environment failed!" + << std::endl; + assert("no future with broken builtin async environment scripts" == 0); + } + /** main loop **/ + while(IsRunning()) { + //wait for job + LuaJobInfo toprocess = m_JobDispatcher->getJob(); + + if (!IsRunning()) { continue; } + + //first push error handler + lua_pushcfunction(m_LuaStack, script_error_handler); + int errorhandler = lua_gettop(m_LuaStack); + + lua_getglobal(m_LuaStack, "engine"); + if(lua_isnil(m_LuaStack, -1)) + assert("unable to find engine within async environment" == 0); + + lua_getfield(m_LuaStack, -1, "job_processor"); + if(lua_isnil(m_LuaStack, -1)) + assert("Someone managed to destroy a async worker engine!" == 0); + + luaL_checktype(m_LuaStack, -1, LUA_TFUNCTION); + + //call it + lua_pushlstring(m_LuaStack, + toprocess.serializedFunction.c_str(), + toprocess.serializedFunction.length()); + lua_pushlstring(m_LuaStack, + toprocess.serializedParams.c_str(), + toprocess.serializedParams.length()); + + if (!IsRunning()) { continue; } + if(lua_pcall(m_LuaStack, 2, 2, errorhandler)) { + scriptError("Async WORKER thread: %s\n", lua_tostring(m_LuaStack, -1)); + toprocess.serializedResult="ERROR"; + } + else { + //fetch result + const char *retval = lua_tostring(m_LuaStack, -2); + unsigned int lenght = lua_tointeger(m_LuaStack,-1); + toprocess.serializedResult = std::string(retval,lenght); + } + + if (!IsRunning()) { continue; } + //put job result + m_JobDispatcher->putJobResult(toprocess); + } + log_deregister_thread(); + return 0; +} + +/******************************************************************************/ +bool AsyncWorkerThread::runScript(std::string script) { + + int ret = luaL_loadfile(m_LuaStack, script.c_str()) || + lua_pcall(m_LuaStack, 0, 0, m_luaerrorhandler); + if(ret){ + errorstream<<"==== ERROR FROM LUA WHILE INITIALIZING ASYNC ENVIRONMENT ====="<<std::endl; + errorstream<<"Failed to load and run script from "<<std::endl; + errorstream<<script<<":"<<std::endl; + errorstream<<std::endl; + errorstream<<lua_tostring(m_LuaStack, -1)<<std::endl; + errorstream<<std::endl; + errorstream<<"=================== END OF ERROR FROM LUA ===================="<<std::endl; + lua_pop(m_LuaStack, 1); // Pop error message from stack + lua_pop(m_LuaStack, 1); // Pop the error handler from stack + return false; + } + return true; +} + +/******************************************************************************/ +void* AsyncWorkerThread::worker_thread_wrapper(void* thread) { + return ((AsyncWorkerThread*) thread)->worker_thread_main(); +} diff --git a/src/script/lua_api/l_async_events.h b/src/script/lua_api/l_async_events.h new file mode 100644 index 000000000..4aaf3bdfe --- /dev/null +++ b/src/script/lua_api/l_async_events.h @@ -0,0 +1,228 @@ +/* +Minetest +Copyright (C) 2013 sapier, <sapier AT gmx DOT net> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef C_ASYNC_EVENTS_H_ +#define C_ASYNC_EVENTS_H_ + +#include <vector> +#include <map> +#include <unistd.h> + +/******************************************************************************/ +/* Includes */ +/******************************************************************************/ +#include "jthread/jthread.h" +#include "jthread/jmutex.h" +#include "jthread/jsemaphore.h" +#include "debug.h" +#include "lua.h" + +/******************************************************************************/ +/* Typedefs and macros */ +/******************************************************************************/ +#define MAINMENU_NUMBER_OF_ASYNC_THREADS 4 + +/******************************************************************************/ +/* forward declarations */ +/******************************************************************************/ +class AsyncEngine; + +/******************************************************************************/ +/* declarations */ +/******************************************************************************/ + +/** a struct to encapsulate data required to queue a job **/ +struct LuaJobInfo { + /** function to be called in async environment **/ + std::string serializedFunction; + /** parameter table to be passed to function **/ + std::string serializedParams; + /** result of function call **/ + std::string serializedResult; + /** jobid used to identify a job and match it to callback **/ + unsigned int JobId; +}; + +/** class encapsulating a asynchronous working environment **/ +class AsyncWorkerThread : public JThread { +public: + /** + * default constructor + * @param pointer to job dispatcher + */ + AsyncWorkerThread(AsyncEngine* jobdispatcher, unsigned int threadnumber); + + /** + * default destructor + */ + virtual ~AsyncWorkerThread(); + + /** + * thread function + */ + void* Thread() { + ThreadStarted(); + return worker_thread_wrapper(this); + } + + /** + * wait for thread to stop + */ + void Wait() { + while(IsRunning()) { + sleep(1); + } + } + +private: + /** + * helper function to run a lua script + * @param path of script + */ + bool runScript(std::string script); + + /** + * main function of thread + */ + void* worker_thread_main(); + + /** + * static wrapper for thread creation + * @param this pointer to the thread to be created + */ + static void* worker_thread_wrapper(void* thread); + + /** + * pointer to job dispatcher + */ + AsyncEngine* m_JobDispatcher; + + /** + * the lua stack to run at + */ + lua_State* m_LuaStack; + + /** + * lua internal stack number of error handler + */ + int m_luaerrorhandler; + + /** + * thread number used for debug output + */ + unsigned int m_threadnum; + +}; + +/** asynchornous thread and job management **/ +class AsyncEngine { + friend AsyncWorkerThread; +public: + /** + * default constructor + */ + AsyncEngine(); + /** + * default destructor + */ + ~AsyncEngine(); + + /** + * register function to be used within engines + * @param name function name to be used within lua environment + * @param fct c-function to be called + */ + bool registerFunction(const char* name, lua_CFunction fct); + + /** + * create async engine tasks and lock function registration + * @param numengines number of async threads to be started + */ + void Initialize(unsigned int numengines); + + /** + * queue/run a async job + * @param fct serialized lua function + * @param params serialized parameters + * @return jobid the job is queued + */ + unsigned int doAsyncJob(std::string fct, std::string params); + + /** + * engine step to process finished jobs + * the engine step is one way to pass events back, PushFinishedJobs another + * @param L the lua environment to do the step in + */ + void Step(lua_State *L); + + + void PushFinishedJobs(lua_State* L); + +protected: + /** + * Get a Job from queue to be processed + * this function blocks until a job is ready + * @return a job to be processed + */ + LuaJobInfo getJob(); + + /** + * put a Job result back to result queue + * @param result result of completed job + */ + void putJobResult(LuaJobInfo result); + + /** + * initialize environment with current registred functions + * this function adds all functions registred by registerFunction to the + * passed lua stack + * @param L lua stack to initialize + * @param top stack position + */ + void PrepareEnvironment(lua_State* L, int top); + +private: + + /** variable locking the engine against further modification **/ + bool m_initDone; + + /** internal store for registred functions **/ + std::map<std::string,lua_CFunction> m_FunctionList; + + /** internal counter to create job id's **/ + unsigned int m_JobIdCounter; + + /** mutex to protect job queue **/ + JMutex m_JobQueueMutex; + /** job queue **/ + std::vector<LuaJobInfo> m_JobQueue; + + /** mutext to protect result queue **/ + JMutex m_ResultQueueMutex; + /** result queue **/ + std::vector<LuaJobInfo> m_ResultQueue; + + /** list of current worker threads **/ + std::vector<AsyncWorkerThread*> m_WorkerThreads; + + /** counter semaphore for job dispatching **/ + JSemaphore m_JobQueueCounter; +}; + +#endif /* C_ASYNC_EVENTS_H_ */ diff --git a/src/script/lua_api/l_internal.h b/src/script/lua_api/l_internal.h index 14215ee5d..5936ac046 100644 --- a/src/script/lua_api/l_internal.h +++ b/src/script/lua_api/l_internal.h @@ -30,14 +30,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_internal.h" #define luamethod(class, name) {#name, class::l_##name} -#define API_FCT(name) registerFunction(L,#name,l_##name,top) +#define API_FCT(name) registerFunction(L, #name, l_##name,top) +#define ASYNC_API_FCT(name) engine.registerFunction(#name, l_##name) #if (defined(WIN32) || defined(_WIN32_WCE)) #define NO_MAP_LOCK_REQUIRED #else #include "main.h" #include "profiler.h" -#define NO_MAP_LOCK_REQUIRED ScopeProfiler nolocktime(g_profiler,"Scriptapi: unlockable time",SPT_ADD) +#define NO_MAP_LOCK_REQUIRED \ + ScopeProfiler nolocktime(g_profiler,"Scriptapi: unlockable time",SPT_ADD) #endif #endif /* L_INTERNAL_H_ */ diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 498ac0383..42ddd0b14 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_mainmenu.h" #include "lua_api/l_internal.h" #include "common/c_content.h" +#include "lua_api/l_async_events.h" #include "guiEngine.h" #include "guiMainMenu.h" #include "guiKeyChangeMenu.h" @@ -200,9 +201,6 @@ int ModApiMainMenu::l_get_textlist_index(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_get_worlds(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); - std::vector<WorldSpec> worlds = getAvailableWorlds(); lua_newtable(L); @@ -237,9 +235,6 @@ int ModApiMainMenu::l_get_worlds(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_get_games(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); - std::vector<SubgameSpec> games = getAvailableGames(); lua_newtable(L); @@ -365,9 +360,6 @@ int ModApiMainMenu::l_get_modstore_details(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_get_modstore_list(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); - std::string listtype = "local"; if (!lua_isnone(L,1)) { @@ -421,9 +413,6 @@ int ModApiMainMenu::l_get_modstore_list(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_get_favorites(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); - std::string listtype = "local"; if (!lua_isnone(L,1)) { @@ -545,9 +534,6 @@ int ModApiMainMenu::l_get_favorites(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_delete_favorite(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); - std::vector<ServerListSpec> servers; std::string listtype = "local"; @@ -599,9 +585,6 @@ int ModApiMainMenu::l_show_keys_menu(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_create_world(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); - const char *name = luaL_checkstring(L, 1); int gameidx = luaL_checkinteger(L,2) -1; @@ -632,9 +615,6 @@ int ModApiMainMenu::l_create_world(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_delete_world(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); - int worldidx = luaL_checkinteger(L,1) -1; std::vector<WorldSpec> worlds = getAvailableWorlds(); @@ -962,9 +942,6 @@ int ModApiMainMenu::l_sound_stop(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_download_file(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); - const char *url = luaL_checkstring(L, 1); const char *target = luaL_checkstring(L, 2); @@ -972,7 +949,7 @@ int ModApiMainMenu::l_download_file(lua_State *L) std::string absolute_destination = fs::RemoveRelativePathComponents(target); if (ModApiMainMenu::isMinetestPath(absolute_destination)) { - if (engine->downloadFile(url,absolute_destination)) { + if (GUIEngine::downloadFile(url,absolute_destination)) { lua_pushboolean(L,true); return 1; } @@ -991,6 +968,28 @@ int ModApiMainMenu::l_gettext(lua_State *L) } /******************************************************************************/ +int ModApiMainMenu::l_do_async_callback(lua_State *L) +{ + GUIEngine* engine = getGuiEngine(L); + + const char* serialized_fct_raw = luaL_checkstring(L, 1); + unsigned int lenght_fct = luaL_checkint(L, 2); + + const char* serialized_params_raw = luaL_checkstring(L, 3); + unsigned int lenght_params = luaL_checkint(L, 4); + + assert(serialized_fct_raw != 0); + assert(serialized_params_raw != 0); + + std::string serialized_fct = std::string(serialized_fct_raw,lenght_fct); + std::string serialized_params = std::string(serialized_params_raw,lenght_params); + + lua_pushinteger(L,engine->DoAsync(serialized_fct,serialized_params)); + + return 1; +} + +/******************************************************************************/ void ModApiMainMenu::Initialize(lua_State *L, int top) { API_FCT(update_formspec); @@ -1024,4 +1023,27 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(sound_play); API_FCT(sound_stop); API_FCT(gettext); + API_FCT(do_async_callback); +} + +/******************************************************************************/ +void ModApiMainMenu::InitializeAsync(AsyncEngine& engine) +{ + + ASYNC_API_FCT(get_worlds); + ASYNC_API_FCT(get_games); + ASYNC_API_FCT(get_favorites); + ASYNC_API_FCT(get_modpath); + ASYNC_API_FCT(get_gamepath); + ASYNC_API_FCT(get_texturepath); + ASYNC_API_FCT(get_dirlist); + ASYNC_API_FCT(create_dir); + ASYNC_API_FCT(delete_dir); + ASYNC_API_FCT(copy_dir); + //ASYNC_API_FCT(extract_zip); //TODO remove dependency to GuiEngine + ASYNC_API_FCT(get_version); + ASYNC_API_FCT(download_file); + ASYNC_API_FCT(get_modstore_details); + ASYNC_API_FCT(get_modstore_list); + //ASYNC_API_FCT(gettext); (gettext lib isn't threadsafe) } diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index d0f3d6f72..e185f0a37 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_base.h" +class AsyncEngine; + /** Implementation of lua api support for mainmenu */ class ModApiMainMenu : public ModApiBase { @@ -125,6 +127,8 @@ private: static int l_download_file(lua_State *L); + // async + static int l_do_async_callback(lua_State *L); public: /** @@ -134,6 +138,8 @@ public: */ static void Initialize(lua_State *L, int top); + static void InitializeAsync(AsyncEngine& engine); + }; #endif /* L_MAINMENU_H_ */ diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index af9c19210..fe10e4f57 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "lua_api/l_async_events.h" #include "debug.h" #include "log.h" #include "tool.h" @@ -257,3 +258,18 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(is_yes); } +void ModApiUtil::InitializeAsync(AsyncEngine& engine) +{ + ASYNC_API_FCT(debug); + ASYNC_API_FCT(log); + + //ASYNC_API_FCT(setting_set); + ASYNC_API_FCT(setting_get); + //ASYNC_API_FCT(setting_setbool); + ASYNC_API_FCT(setting_getbool); + //ASYNC_API_FCT(setting_save); + + ASYNC_API_FCT(parse_json); + + ASYNC_API_FCT(is_yes); +} diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index bb99e1562..d91c880cf 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_base.h" +class AsyncEngine; + class ModApiUtil : public ModApiBase { private: /* @@ -77,6 +79,8 @@ private: public: static void Initialize(lua_State *L, int top); + static void InitializeAsync(AsyncEngine& engine); + }; #endif /* L_UTIL_H_ */ diff --git a/src/script/lua_api/marshall.c b/src/script/lua_api/marshall.c new file mode 100644 index 000000000..ef70566cb --- /dev/null +++ b/src/script/lua_api/marshall.c @@ -0,0 +1,551 @@ +/* +* lmarshal.c +* A Lua library for serializing and deserializing Lua values +* Richard Hundt <richardhundt@gmail.com> +* +* License: MIT +* +* Copyright (c) 2010 Richard Hundt +* +* Permission is hereby granted, free of charge, to any person +* obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following +* conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +* OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" + + +#define MAR_TREF 1 +#define MAR_TVAL 2 +#define MAR_TUSR 3 + +#define MAR_CHR 1 +#define MAR_I32 4 +#define MAR_I64 8 + +#define MAR_MAGIC 0x8e +#define SEEN_IDX 3 + +typedef struct mar_Buffer { + size_t size; + size_t seek; + size_t head; + char* data; +} mar_Buffer; + +static int mar_encode_table(lua_State *L, mar_Buffer *buf, size_t *idx); +static int mar_decode_table(lua_State *L, const char* buf, size_t len, size_t *idx); + +static void buf_init(lua_State *L, mar_Buffer *buf) +{ + buf->size = 128; + buf->seek = 0; + buf->head = 0; + if (!(buf->data = malloc(buf->size))) luaL_error(L, "Out of memory!"); +} + +static void buf_done(lua_State* L, mar_Buffer *buf) +{ + free(buf->data); +} + +static int buf_write(lua_State* L, const char* str, size_t len, mar_Buffer *buf) +{ + if (len > UINT32_MAX) luaL_error(L, "buffer too long"); + if (buf->size - buf->head < len) { + size_t new_size = buf->size << 1; + size_t cur_head = buf->head; + while (new_size - cur_head <= len) { + new_size = new_size << 1; + } + if (!(buf->data = realloc(buf->data, new_size))) { + luaL_error(L, "Out of memory!"); + } + buf->size = new_size; + } + memcpy(&buf->data[buf->head], str, len); + buf->head += len; + return 0; +} + +static const char* buf_read(lua_State *L, mar_Buffer *buf, size_t *len) +{ + if (buf->seek < buf->head) { + buf->seek = buf->head; + *len = buf->seek; + return buf->data; + } + *len = 0; + return NULL; +} + +static void mar_encode_value(lua_State *L, mar_Buffer *buf, int val, size_t *idx) +{ + size_t l; + int val_type = lua_type(L, val); + lua_pushvalue(L, val); + + buf_write(L, (void*)&val_type, MAR_CHR, buf); + switch (val_type) { + case LUA_TBOOLEAN: { + int int_val = lua_toboolean(L, -1); + buf_write(L, (void*)&int_val, MAR_CHR, buf); + break; + } + case LUA_TSTRING: { + const char *str_val = lua_tolstring(L, -1, &l); + buf_write(L, (void*)&l, MAR_I32, buf); + buf_write(L, str_val, l, buf); + break; + } + case LUA_TNUMBER: { + lua_Number num_val = lua_tonumber(L, -1); + buf_write(L, (void*)&num_val, MAR_I64, buf); + break; + } + case LUA_TTABLE: { + int tag, ref; + lua_pushvalue(L, -1); + lua_rawget(L, SEEN_IDX); + if (!lua_isnil(L, -1)) { + ref = lua_tointeger(L, -1); + tag = MAR_TREF; + buf_write(L, (void*)&tag, MAR_CHR, buf); + buf_write(L, (void*)&ref, MAR_I32, buf); + lua_pop(L, 1); + } + else { + mar_Buffer rec_buf; + lua_pop(L, 1); /* pop nil */ + if (luaL_getmetafield(L, -1, "__persist")) { + tag = MAR_TUSR; + + lua_pushvalue(L, -2); /* self */ + lua_call(L, 1, 1); + if (!lua_isfunction(L, -1)) { + luaL_error(L, "__persist must return a function"); + } + + lua_remove(L, -2); /* __persist */ + + lua_newtable(L); + lua_pushvalue(L, -2); /* callback */ + lua_rawseti(L, -2, 1); + + buf_init(L, &rec_buf); + mar_encode_table(L, &rec_buf, idx); + + buf_write(L, (void*)&tag, MAR_CHR, buf); + buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); + buf_write(L, rec_buf.data, rec_buf.head, buf); + buf_done(L, &rec_buf); + lua_pop(L, 1); + } + else { + tag = MAR_TVAL; + + lua_pushvalue(L, -1); + lua_pushinteger(L, (*idx)++); + lua_rawset(L, SEEN_IDX); + + lua_pushvalue(L, -1); + buf_init(L, &rec_buf); + mar_encode_table(L, &rec_buf, idx); + lua_pop(L, 1); + + buf_write(L, (void*)&tag, MAR_CHR, buf); + buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); + buf_write(L, rec_buf.data,rec_buf.head, buf); + buf_done(L, &rec_buf); + } + } + break; + } + case LUA_TFUNCTION: { + int tag, ref; + lua_pushvalue(L, -1); + lua_rawget(L, SEEN_IDX); + if (!lua_isnil(L, -1)) { + ref = lua_tointeger(L, -1); + tag = MAR_TREF; + buf_write(L, (void*)&tag, MAR_CHR, buf); + buf_write(L, (void*)&ref, MAR_I32, buf); + lua_pop(L, 1); + } + else { + mar_Buffer rec_buf; + int i; + lua_Debug ar; + lua_pop(L, 1); /* pop nil */ + + lua_pushvalue(L, -1); + lua_getinfo(L, ">nuS", &ar); + if (ar.what[0] != 'L') { + luaL_error(L, "attempt to persist a C function '%s'", ar.name); + } + tag = MAR_TVAL; + lua_pushvalue(L, -1); + lua_pushinteger(L, (*idx)++); + lua_rawset(L, SEEN_IDX); + + lua_pushvalue(L, -1); + buf_init(L, &rec_buf); + lua_dump(L, (lua_Writer)buf_write, &rec_buf); + + buf_write(L, (void*)&tag, MAR_CHR, buf); + buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); + buf_write(L, rec_buf.data, rec_buf.head, buf); + buf_done(L, &rec_buf); + lua_pop(L, 1); + + lua_newtable(L); + for (i=1; i <= ar.nups; i++) { + lua_getupvalue(L, -2, i); + lua_rawseti(L, -2, i); + } + + buf_init(L, &rec_buf); + mar_encode_table(L, &rec_buf, idx); + + buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); + buf_write(L, rec_buf.data, rec_buf.head, buf); + buf_done(L, &rec_buf); + lua_pop(L, 1); + } + + break; + } + case LUA_TUSERDATA: { + int tag, ref; + lua_pushvalue(L, -1); + lua_rawget(L, SEEN_IDX); + if (!lua_isnil(L, -1)) { + ref = lua_tointeger(L, -1); + tag = MAR_TREF; + buf_write(L, (void*)&tag, MAR_CHR, buf); + buf_write(L, (void*)&ref, MAR_I32, buf); + lua_pop(L, 1); + } + else { + mar_Buffer rec_buf; + lua_pop(L, 1); /* pop nil */ + if (luaL_getmetafield(L, -1, "__persist")) { + tag = MAR_TUSR; + + lua_pushvalue(L, -2); + lua_pushinteger(L, (*idx)++); + lua_rawset(L, SEEN_IDX); + + lua_pushvalue(L, -2); + lua_call(L, 1, 1); + if (!lua_isfunction(L, -1)) { + luaL_error(L, "__persist must return a function"); + } + lua_newtable(L); + lua_pushvalue(L, -2); + lua_rawseti(L, -2, 1); + lua_remove(L, -2); + + buf_init(L, &rec_buf); + mar_encode_table(L, &rec_buf, idx); + + buf_write(L, (void*)&tag, MAR_CHR, buf); + buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); + buf_write(L, rec_buf.data, rec_buf.head, buf); + buf_done(L, &rec_buf); + } + else { + luaL_error(L, "attempt to encode userdata (no __persist hook)"); + } + lua_pop(L, 1); + } + break; + } + case LUA_TNIL: break; + default: + luaL_error(L, "invalid value type (%s)", lua_typename(L, val_type)); + } + lua_pop(L, 1); +} + +static int mar_encode_table(lua_State *L, mar_Buffer *buf, size_t *idx) +{ + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + mar_encode_value(L, buf, -2, idx); + mar_encode_value(L, buf, -1, idx); + lua_pop(L, 1); + } + return 1; +} + +#define mar_incr_ptr(l) \ + if (((*p)-buf)+(l) > len) luaL_error(L, "bad code"); (*p) += (l); + +#define mar_next_len(l,T) \ + if (((*p)-buf)+sizeof(T) > len) luaL_error(L, "bad code"); \ + l = *(T*)*p; (*p) += sizeof(T); + +static void mar_decode_value + (lua_State *L, const char *buf, size_t len, const char **p, size_t *idx) +{ + size_t l; + char val_type = **p; + mar_incr_ptr(MAR_CHR); + switch (val_type) { + case LUA_TBOOLEAN: + lua_pushboolean(L, *(char*)*p); + mar_incr_ptr(MAR_CHR); + break; + case LUA_TNUMBER: + lua_pushnumber(L, *(lua_Number*)*p); + mar_incr_ptr(MAR_I64); + break; + case LUA_TSTRING: + mar_next_len(l, uint32_t); + lua_pushlstring(L, *p, l); + mar_incr_ptr(l); + break; + case LUA_TTABLE: { + char tag = *(char*)*p; + mar_incr_ptr(MAR_CHR); + if (tag == MAR_TREF) { + int ref; + mar_next_len(ref, int); + lua_rawgeti(L, SEEN_IDX, ref); + } + else if (tag == MAR_TVAL) { + mar_next_len(l, uint32_t); + lua_newtable(L); + lua_pushvalue(L, -1); + lua_rawseti(L, SEEN_IDX, (*idx)++); + mar_decode_table(L, *p, l, idx); + mar_incr_ptr(l); + } + else if (tag == MAR_TUSR) { + mar_next_len(l, uint32_t); + lua_newtable(L); + mar_decode_table(L, *p, l, idx); + lua_rawgeti(L, -1, 1); + lua_call(L, 0, 1); + lua_remove(L, -2); + lua_pushvalue(L, -1); + lua_rawseti(L, SEEN_IDX, (*idx)++); + mar_incr_ptr(l); + } + else { + luaL_error(L, "bad encoded data"); + } + break; + } + case LUA_TFUNCTION: { + size_t nups; + int i; + mar_Buffer dec_buf; + char tag = *(char*)*p; + mar_incr_ptr(1); + if (tag == MAR_TREF) { + int ref; + mar_next_len(ref, int); + lua_rawgeti(L, SEEN_IDX, ref); + } + else { + mar_next_len(l, uint32_t); + dec_buf.data = (char*)*p; + dec_buf.size = l; + dec_buf.head = l; + dec_buf.seek = 0; + lua_load(L, (lua_Reader)buf_read, &dec_buf, "=marshal"); + mar_incr_ptr(l); + + lua_pushvalue(L, -1); + lua_rawseti(L, SEEN_IDX, (*idx)++); + + mar_next_len(l, uint32_t); + lua_newtable(L); + mar_decode_table(L, *p, l, idx); + nups = lua_objlen(L, -1); + for (i=1; i <= nups; i++) { + lua_rawgeti(L, -1, i); + lua_setupvalue(L, -3, i); + } + lua_pop(L, 1); + mar_incr_ptr(l); + } + break; + } + case LUA_TUSERDATA: { + char tag = *(char*)*p; + mar_incr_ptr(MAR_CHR); + if (tag == MAR_TREF) { + int ref; + mar_next_len(ref, int); + lua_rawgeti(L, SEEN_IDX, ref); + } + else if (tag == MAR_TUSR) { + mar_next_len(l, uint32_t); + lua_newtable(L); + mar_decode_table(L, *p, l, idx); + lua_rawgeti(L, -1, 1); + lua_call(L, 0, 1); + lua_remove(L, -2); + lua_pushvalue(L, -1); + lua_rawseti(L, SEEN_IDX, (*idx)++); + mar_incr_ptr(l); + } + else { /* tag == MAR_TVAL */ + lua_pushnil(L); + } + break; + } + case LUA_TNIL: + case LUA_TTHREAD: + lua_pushnil(L); + break; + default: + luaL_error(L, "bad code"); + } +} + +static int mar_decode_table(lua_State *L, const char* buf, size_t len, size_t *idx) +{ + const char* p; + p = buf; + while (p - buf < len) { + mar_decode_value(L, buf, len, &p, idx); + mar_decode_value(L, buf, len, &p, idx); + lua_settable(L, -3); + } + return 1; +} + +static int mar_encode(lua_State* L) +{ + const unsigned char m = MAR_MAGIC; + size_t idx, len; + mar_Buffer buf; + + if (lua_isnone(L, 1)) { + lua_pushnil(L); + } + if (lua_isnoneornil(L, 2)) { + lua_newtable(L); + } + else if (!lua_istable(L, 2)) { + luaL_error(L, "bad argument #2 to encode (expected table)"); + } + lua_settop(L, 2); + + len = lua_objlen(L, 2); + lua_newtable(L); + for (idx = 1; idx <= len; idx++) { + lua_rawgeti(L, 2, idx); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + continue; + } + lua_pushinteger(L, idx); + lua_rawset(L, SEEN_IDX); + } + lua_pushvalue(L, 1); + + buf_init(L, &buf); + buf_write(L, (void*)&m, 1, &buf); + + mar_encode_value(L, &buf, -1, &idx); + + lua_pop(L, 1); + + lua_pushlstring(L, buf.data, buf.head); + + buf_done(L, &buf); + + lua_remove(L, SEEN_IDX); + + return 1; +} + +static int mar_decode(lua_State* L) +{ + size_t l, idx, len; + const char *p; + const char *s = luaL_checklstring(L, 1, &l); + + if (l < 1) luaL_error(L, "bad header"); + if (*(unsigned char *)s++ != MAR_MAGIC) luaL_error(L, "bad magic"); + l -= 1; + + if (lua_isnoneornil(L, 2)) { + lua_newtable(L); + } + else if (!lua_istable(L, 2)) { + luaL_error(L, "bad argument #2 to decode (expected table)"); + } + lua_settop(L, 2); + + len = lua_objlen(L, 2); + lua_newtable(L); + for (idx = 1; idx <= len; idx++) { + lua_rawgeti(L, 2, idx); + lua_rawseti(L, SEEN_IDX, idx); + } + + p = s; + mar_decode_value(L, s, l, &p, &idx); + + lua_remove(L, SEEN_IDX); + lua_remove(L, 2); + + return 1; +} + +static int mar_clone(lua_State* L) +{ + mar_encode(L); + lua_replace(L, 1); + mar_decode(L); + return 1; +} + +static const luaL_reg R[] = +{ + {"encode", mar_encode}, + {"decode", mar_decode}, + {"clone", mar_clone}, + {NULL, NULL} +}; + +int luaopen_marshal(lua_State *L) +{ + lua_newtable(L); + luaL_register(L, "marshal", R); + return 1; +} + + + + + diff --git a/src/script/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp index 31581a1bf..a4619e9da 100644 --- a/src/script/scripting_mainmenu.cpp +++ b/src/script/scripting_mainmenu.cpp @@ -28,8 +28,9 @@ with this program; if not, write to the Free Software Foundation, Inc., extern "C" { #include "lualib.h" + int luaopen_marshal(lua_State *L); } - +/******************************************************************************/ MainMenuScripting::MainMenuScripting(GUIEngine* guiengine) { setGuiEngine(guiengine); @@ -37,6 +38,7 @@ MainMenuScripting::MainMenuScripting(GUIEngine* guiengine) //TODO add security luaL_openlibs(getStack()); + luaopen_marshal(getStack()); SCRIPTAPI_PRECHECKHEADER @@ -58,6 +60,7 @@ MainMenuScripting::MainMenuScripting(GUIEngine* guiengine) infostream << "SCRIPTAPI: initialized mainmenu modules" << std::endl; } +/******************************************************************************/ void MainMenuScripting::InitializeModApi(lua_State *L, int top) { // Initialize mod api modules @@ -66,4 +69,23 @@ void MainMenuScripting::InitializeModApi(lua_State *L, int top) // Register reference classes (userdata) LuaSettings::Register(L); + + // Register functions to async environment + ModApiMainMenu::InitializeAsync(m_AsyncEngine); + ModApiUtil::InitializeAsync(m_AsyncEngine); + + // Initialize async environment + //TODO possibly make number of async threads configurable + m_AsyncEngine.Initialize(MAINMENU_NUMBER_OF_ASYNC_THREADS); +} + +/******************************************************************************/ +void MainMenuScripting::Step() { + m_AsyncEngine.Step(getStack()); +} + +/******************************************************************************/ +unsigned int MainMenuScripting::DoAsync(std::string serialized_fct, + std::string serialized_params) { + return m_AsyncEngine.doAsyncJob(serialized_fct,serialized_params); } diff --git a/src/script/scripting_mainmenu.h b/src/script/scripting_mainmenu.h index 7592c8e23..f4d78f664 100644 --- a/src/script/scripting_mainmenu.h +++ b/src/script/scripting_mainmenu.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_base.h" #include "cpp_api/s_mainmenu.h" +#include "lua_api/l_async_events.h" /*****************************************************************************/ /* Scripting <-> Main Menu Interface */ @@ -37,8 +38,16 @@ public: // use ScriptApiBase::loadMod() or ScriptApiBase::loadScript() // to load scripts + /* global step handler to pass back async events */ + void Step(); + + /* pass async events from engine to async threads */ + unsigned int DoAsync(std::string serialized_fct, + std::string serialized_params); private: void InitializeModApi(lua_State *L, int top); + + AsyncEngine m_AsyncEngine; }; |