Web liveupdate and mounts

The new version of Defold 1.5.0 has expanded the liveupdate functionality.
I had some questions when adding mounts. Perhaps this example will help you avoid some mistakes.

Steps:

  1. Check if our archive is mounted. If the archive is mounted, then your data can already be used.
  2. Download archive. There may be problems when downloading a file if the speed is low or the connection is poor. For example, during tests, I sometimes received a response with status 0. (I don’t know why exactly 0). In order to fix this, you need to add several download attempts.
  3. Save archive to the IndexedDB. io.write() will help us with this.
    When creating a file, you must specify the path. The path must have a string at the beginning of /data/. For example /data/external_res.zip. Otherwise there will be errors.
    In the example I use the function sys.get_save_file(“my_key”, “external_res.zip”). This function will generate the correct name. It looks like: /data/.my_key/external_res.zip
  4. Add mount. In the uri parameter you need to specify your path described above and add zip: at the beginning. It looks like: zip:/data/.my_key/external_res.zip.
  5. Your data is ready. You can download a proxy.

When the files are deleted from the cache, the added mounts themselves will be deleted.

However, now you need to monitor the changed external files yourself. Once a mount is created, it will always take data from the cache. If you leave the same name for the archive and change the data, the mount will use the old data. You need to change the name and add a new mount. Or you can remove the old mount and reload the data. Now it’s up to you to choose.

Example:

local external_data = {
    name = "external_res.zip",                           ---Name for zip archive
    proxy = msg.url("example:/example#collectionproxy"), ---Excluded proxy
    path = "./"                                          ---Here we indicate the path to the file. I set root folder
}

---Check if the data already mounted
---@param archive_name string
---@return boolean
local function is_in_mount(archive_name)
    local mounts = liveupdate.get_mounts()
    for key, mount in pairs(mounts) do
        if archive_name == mount.name then
            return true
        end
    end
    return false
end

---Start proxy loading
---@param proxy url
local function load_proxy(proxy)
    msg.post(proxy, "async_load")
end

---Handle error
---@param data external_data
---@param error string
local function on_error(data, error)
    pprint("Error", error)
    ---TODO: handle error
end

---Save file and mount data
---@param data external_data
---@param response string
---@param priority number
local function on_file_received(data, response, priority)
    local new_path = sys.get_save_file("my_key", data.name)
    ---Create a file object to work with IndexedDB
    local file, err = io.open(new_path, "w+")
    if err then
        on_error(data, err)
        return
    end
    ---Write our received data to IndexedDB
    local f, err = file:write(response)
    if err then
        on_error(data, err)
        return
    end
    ---Close connection
    file:close()
    ---Add new mount
    liveupdate.add_mount(data.name, "zip:" .. new_path, priority, function(self, name, uri, priority)
        ---Our data loaded. Now we can load proxy.
        load_proxy(data.proxy)
    end)
end

local ATTEMPT_COUNT = 50

---We make requests for data until we receive it
---@param data external_data
---@param index number
---@param attempt number|nil
local function request_data(data, index, attempt)
    attempt = attempt or 1
    http.request(data.path .. data.name, "GET", function(self, id, response)
        if (response.status == 200 or response.status == 304) and response.error == nil and response.response ~= nil then
            on_file_received(data, response.response, index)
        elseif attempt <= ATTEMPT_COUNT then
            ---If unsuccessful, I make several attempts to load the data.
            attempt = attempt + 1
            request_data(data, index, attempt)
        else
            on_error(data, response.error)
        end
    end)
end

---Start our loading
---@param self userdata
function init(self)
    ---I check that it is a web and check the created mounts
    if not html5 or is_in_mount(external_data.name) then
        ---Data already loaded
        load_proxy(external_data.proxy)
    else
        request_data(external_data, 1)
    end
end

local PROXY_LOADED = hash("proxy_loaded")

---Handle proxy message
---@param self userdata
---@param message_id hash
---@param message any
---@param sender url
function on_message(self, message_id, message, sender)
    if message_id == PROXY_LOADED then
        if external_data.proxy == sender then
            msg.post(sender, "init")
            msg.post(sender, "enable")
            pprint("Success! Proxy loaded!")
        end
    end
end
10 Likes

Small update. From version Defold 1.6.0 you need to check if liveupdate exists. If you don’t do this there will be an error, when building in the editor.

Also, when creating a bundle without liveupdate content, there will be an error when trying to download the mount, because there is no archive and there is no need to download anything. You can check if resources are missing via collectionproxy.missing resources.

function init(self)
    if not liveupdate then
        load_proxy(external_data.proxy)
    else
        local missing_resources = collectionproxy.missing_resources(external_data.proxy)
        if next(missing_resources) == nil or is_in_mount(external_data.name) then
            ---Data already loaded
            load_proxy(external_data.proxy)
        else
            request_data(external_data, 1)
        end
    end
end
6 Likes