Fix modstore/favourites hang by adding asynchronous lua job support
authorsapier <Sapier at GMX dot net>
Tue, 26 Nov 2013 17:15:31 +0000 (18:15 +0100)
committersapier <Sapier at GMX dot net>
Fri, 29 Nov 2013 21:09:14 +0000 (22:09 +0100)
27 files changed:
builtin/async_env.lua [new file with mode: 0644]
builtin/async_event.lua [new file with mode: 0644]
builtin/mainmenu.lua
builtin/modmgr.lua
builtin/modstore.lua
doc/menu_lua_api.txt
src/guiEngine.cpp
src/guiEngine.h
src/jthread/CMakeLists.txt
src/jthread/jsemaphore.h [new file with mode: 0644]
src/jthread/jthread.h
src/jthread/pthread/jsemaphore.cpp [new file with mode: 0644]
src/jthread/pthread/jthread.cpp
src/jthread/win32/jsemaphore.cpp [new file with mode: 0644]
src/jthread/win32/jthread.cpp
src/main.cpp
src/script/lua_api/CMakeLists.txt
src/script/lua_api/l_async_events.cpp [new file with mode: 0644]
src/script/lua_api/l_async_events.h [new file with mode: 0644]
src/script/lua_api/l_internal.h
src/script/lua_api/l_mainmenu.cpp
src/script/lua_api/l_mainmenu.h
src/script/lua_api/l_util.cpp
src/script/lua_api/l_util.h
src/script/lua_api/marshall.c [new file with mode: 0644]
src/script/scripting_mainmenu.cpp
src/script/scripting_mainmenu.h

diff --git a/builtin/async_env.lua b/builtin/async_env.lua
new file mode 100644 (file)
index 0000000..afc6921
--- /dev/null
@@ -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 (file)
index 0000000..f4c7d24
--- /dev/null
@@ -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
index 0032017acc2b932048be1e22b1a1f1b0382ffffd..6c0aaf252b042c33a8187f3d1910992eb5227b72 100644 (file)
@@ -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
@@ -196,24 +197,39 @@ function menu.render_texture_pack_list(list)
        return retval
 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
 
 --------------------------------------------------------------------------------
index 1f19ac67359f304ea0b8a76634b242d6d58ada6b..cc5e09513bb280f277d1c9147720c41d98fe52d9 100644 (file)
@@ -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,
index b364ce6bd08e68b137829019ab1432ce594114db..acaff871b753872ea9d0a1a4d97a58fd92e37f23 100644 (file)
@@ -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
 
index ca815862e29e08f7ed9a47472d643cb51c5136c6..90b2d01fd99211726fd88d34b37d288ac4c68b50 100644 (file)
@@ -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
index d5f528f3b0ed30501a9de0399380b474ee38be6a..7acc00ef1d4550f090c10f38a0ac1c4f21ab6d58 100644 (file)
@@ -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);
+}
index 4844593954c98fcf679cc12ed48049eac8b83c4e..6b128154676117ba9e6eb22921436ff07f8e8424 100644 (file)
@@ -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];
index aa438eaaf636093e7c1809c27686fd73b9d231d8..6c29671e69b5b33873997aed1ee53807358e575b 100644 (file)
@@ -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 (file)
index 0000000..70318d5
--- /dev/null
@@ -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_ */
index ec1eafaeb23dee7ea0a60c322f640c7aef4b572e..92b05f1c51cfb6f538636e272ce70e468f2049a4 100644 (file)
@@ -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 (file)
index 0000000..963ac83
--- /dev/null
@@ -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;
+}
+
index 0ef2508255536000ff86218919fbbc8f33c42f1d..2980e26b1cf447cf8a82219099aceacd2eaad0ae 100644 (file)
@@ -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 (file)
index 0000000..8eca6d2
--- /dev/null
@@ -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;
+}
+
index 48b83b8949777427731697ea947989bf7d959672..1cf4f93a3c8b57967c2f2391a980b1d156da208e 100644 (file)
@@ -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()
index 373e1ca9678bedcca525d0e5e73679812982a689..2833bdcf78bc57c2f40c57cc8c6aa26f3e4c5197 100644 (file)
@@ -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;
 }
 
index 08960d2ad739d2904c01420015a8b7f5e09fb2a7..0b89df6a38c1a2681bd68977d110a75f25cb835a 100644 (file)
@@ -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 (file)
index 0000000..cc4644c
--- /dev/null
@@ -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 (file)
index 0000000..4aaf3bd
--- /dev/null
@@ -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_ */
index 14215ee5df431a7bae998ad9a3d15d64a6ccdf2f..5936ac046dbb86da222c7a38585047495cf209c7 100644 (file)
@@ -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_ */
index 498ac038394eb6920fa221d4c7472296d8eec2f8..42ddd0b14101c70976e969cd1ac42b47ff046419 100644 (file)
@@ -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;
                }
@@ -990,6 +967,28 @@ int ModApiMainMenu::l_gettext(lua_State *L)
        return 1;
 }
 
+/******************************************************************************/
+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)
 {
@@ -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)
 }
index d0f3d6f72efda27cfa71e1095717e6bc0cbee1db..e185f0a378c14fc5a23ac9001c3db3cc36b5297d 100644 (file)
@@ -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_ */
index af9c19210360bb321b7b693e097462ab4da24960..fe10e4f5716cdb75569f813c78d0ad44d07d4b72 100644 (file)
@@ -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);
+}
index bb99e1562827fe39ff19330e2f4ba3759c2ee337..d91c880cf9099da67839e61b7ecfff7c033e6c5f 100644 (file)
@@ -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 (file)
index 0000000..ef70566
--- /dev/null
@@ -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;
+}
+
+
+
+
+
index 31581a1bfdab3b0593a40ccec6b891a4ffbb9fcf..a4619e9da3cdf2012bdb2a88a0206148106eeb66 100644 (file)
@@ -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);
 }
index 7592c8e23de720cfa14f5b96ed581e2836a756de..f4d78f6646a388d7b935b8f50cfddbbf9594f9f8 100644 (file)
@@ -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;
 };