Live Updates

Hello everyone!

We’ve been working a lot with two new and really big features called LiveUpdate and NativeExtensions. Since they will change, and improve, the engine substantially, we are focusing on getting them ready as soon as possible and therefore the release of 1.2.97 will come at a later date. I thought that I should share some information with all of you about the LiveUpdate feature, what it is, why it is and how it’s intended to work.

Background
The original problem behind the LiveUpdate feature is that (almost) every game grows over time as more content is added to it, and even though the Defold engine itself is really small, a complete game such as Blossom Blast Saga can easily require hundreds of megabytes of assets. During 2016 we improved the texture compression algorithms a lot and implemented webp compression to enable developers to reduce their bundle size. Though this mitigates the problem with a growing application size, it doesn’t solve it.

We first looked at the possibility to ship a complete game with reduced asset quality where you could add very compressed images and only a few mipmap levels with the original bundle, and then download the high resolution assets once the game has been launched. This would have mitigated the problem even further, but there would still be problems with other assets that can’t be compressed as easily. When discussing this approach with internal teams the idea was scrapped since artists wouldn’t want to show low quality assets and give the player an unpleasant experience in case there was no internet connection available, or a sudden, unexplained, increase of graphics quality in the middle of a game.

The advantage of shipping a complete game from the engines point of view is that there would be no “dangling references” to resources that couldn’t be loaded. This presents a quite serious problem in terms of testing since the functionality in a game would differ depending on which resources had already been downloaded when a script is executed, and the developer would experience constant race conditions depending on network, connectivity, bandwidth and device storage.

Because of this, we decided to integrate downloadable content to collectionproxies. Since a collectionproxy is not loaded when the engine launches, and a collectionproxy will either load completely, or fail to load at all, failure to download a subset of required assets will not affect the number of possible states in the game, and scripts will execute with the same result every time.

LiveUpdate
LiveUpdate enables the developer to exclude all resources that belongs to a specific collectionproxy. When the resources within a collectionproxy is excluded they will not be bundled with the game, but instead exported as separate files for each resource. The actual collectionproxy will still be included, which allows a script to post messages such as “load” and “enable” to that collectionproxy. This will allow a developer to bundle only the first few episodes, levels, dungeons or monsters with the game that is published, and then download additional content as the player progress through the game, which will allow for a very small, and fixed, initial download size.

Before loading a collectionproxy, it is now possible to check whether there are any resources missing from that collectionproxy. If there are any resources missing, an indexed table of hashes will be returned, which corresponds to the exported resources. These resources should then be downloaded and added to the data archive before attempting to load the collectionproxy.

Defold is currently identifying resources by their path, or URL. This means that if there would be a change to a specific resource with the URL /main/defold.script it wouldn’t be possible to determine which version of the script the developer intended to use, which as mentioned in the background, would cause problems during testing where some resources from version 1 was loaded, and some resources from version 2. Because of this we’ve changed the resource management system completely so that it now identifies resources based on cryptographic hashes of their content, rather than their URL. This means that every version of a game have a distinct set of resources that cannot be incorrectly matched with a modified version of the same resource.

Further more, with this distinct addressing system it is possible to send another index to the game which represents a new version, and through collectionproxies, update the game with new content that wasn’t available when the game was first bundled. This feature could be used to hotfix issues, enable A/B testing, and more. But more about that in a later post…

Finally, before showing some code, I should add that we aim to provide all of this functionality to every platform that Defold supports. So there will be no specific settings required for different platforms, just data.

Code example
Please be aware that the API will be subject to change

function init(self)
    self.manifest = resource.get_current_manifest()
    self.http_requests = 0
    self.missing_resources = 0
    self.target = nil
end

local function callback_download_resource(self, id, response)
    if response.status == 200 then
        if resource.verify_resource(self.manifest, response.response) then
            resource.store_resource(response.response)
            self.missing_resources = self.missing_resources - 1
        else
            -- The resource was corrupted during transmission
        end
    else
        -- The download did not succeed
    end

    self.http_requests = self.http_requests - 1
    if self.http_requests == 0 then
        -- All requests to download resources have been completed
        local target = self.target
        self.target = nil
        if self.missing_resources == 0 then
            -- We are ready to load the collectionproxy
            msg.post(target, "load")
        else
            -- We've failed to download one or more resource(s)
        end
    end
end

local function download_resource(self, target)
    local missing_resources = collectionproxy.missing_resources(target)
    if #missing_resources ~= 0 then
        self.http_requests = #missing_resources
        self.missing_resources = #missing_resources
        self.target = target
        for k, v in pairs(missing_resources) do
            local url = "http://example.com/defold/" .. v
            http.request(url, "GET", callback_download_resource)
        end
    end
end

function on_message(self, message_id, message, sender)
    if message_id == hash("download_content") then
        if self.target == nil then
            download_resource(self, message.collectionproxy)
        else
            msg.post(sender, "download_busy")
        end
    end
end

Regards,
The defold team through Jakob

17 Likes

And one more question about Live Update on IOS and their rules about loading a code files?

1 Like

Hello everyone!

It’s Friday and time for another LiveUpdate teaser! This time it’ll be quite a short one showing (some of) the configuration that’ll be available in Editor 1 for LiveUpdate. We have some more really exciting news regarding the configuration that we’ll hold on to for a while, but what I can say is that since this is quite an advanced feature and not everyone might want to administer their own servers to host resource content, we’re looking into additional options to publish and host your content apart from publishing resource data as a zip file…

As you can see from the movie there’ll be no new components required, but the (optional) configuration is placed at the collectionproxies, this ensures that games that has already been developed using Defold will be able to take advantage of the new feature with minimal configuration.

Link to movie in case embedding not working

Also, we have a small teaser showing loading of LiveUpdate content in runtime on OSX. In this example we have excluded a collectionproxy from the game when bundling. Before loading the collectionproxy we check if we have all the resource required. If not, we will download the needed resources. And finally when all resources are fetched we can go ahead and load the collection proxy.

Have a great weekend!

Regards,
Johan & the defold team

15 Likes

Very nice!

2 Likes

Hello!

With Defold 1.2.97 we’ve released a first version of LiveUpdate. The current functionality supports excluding resources through collectionproxies which can significantly reduce the initial download size of an application. Resources for a collectionproxy can then be downloaded as needed as the player progresses through the game.

To make this feature as easy as possible to use, without the need for you to manage your own servers, we’ve integrated support for Amazon S3 within the editor. @sicher is currently working on a manual that will describe the workflow, configuration and some technical details about LiveUpdate as well as how to configure your AWS account, but in the meantime I’ll provide a very brief description of what is needed below.

When you configure your project’s LiveUpdate settings you specify a credential profile, once this has been done the list of available buckets on your AWS account will be populated for you to choose from, and finally you must specify a prefix (similar to a directory structure) where the resources should be stored. A credential profile is stored within a credential file, on OSX/Linux the file will be ~/.aws/credentials and it should be written in the below format.

[<credential-profile>]
aws_access_key_id = <access-key>
aws_secret_access_key = <secret-key>

To setup your AWS account to work with LiveUpdate you should do 5 things.

1 - Create a new bucket that should be used to store your resources.

2 - Edit the bucket policy to allow anonymous access of resources, this is required in order for the game client to access the resources as the player progress through the game. The below policy is an example from our own bucket (defold-resources-qa) that we’re using for testing.

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "AddPerm",
			"Effect": "Allow",
			"Principal": "*",
			"Action": "s3:GetObject",
			"Resource": "arn:aws:s3:::defold-resources-qa/*"
		}
	]
}

3 - If you intend to publish your game for HTML5, edit the CORS Configuration to allow other websites to access the content on your bucket through JavaScript. The below configuration is an example from our own bucket (defold-resources-qa) that we’re using for testing, it allows wildcard access but this can of course be modified to only allow a specific website.

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
    </CORSRule>
</CORSConfiguration>

4 - Create a new IAM Policy that allows limited access to your AWS account. This policy should be applied to accounts that you intend to use when publishing LiveUpdate content to Amazon using the Defold editor. The below policy is an example of our own policy to access the bucket we’re using for testing (defold-resources-qa).

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListAllMyBuckets"
            ],
            "Resource": "arn:aws:s3:::*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetBucketAcl"
            ],
            "Resource": "arn:aws:s3:::defold-resources-qa"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::defold-resources-qa/*"
        }
    ]
}

5 - Create a new user with access type Programmatic Access and assign the policy you created in step 4 to the user. Make sure to note the Access Key and Secret Key you get when creating the account and write those down as a Credential Profile in your credentials file.

That’s it! Once you’ve performed the above steps you should be able to specify the Credential Profile you created when configuring LiveUpdate for your project, and if you choose to Publish LiveUpdate content when you bundle the resources will automatically be uploaded to Amazon S3.

Take a look at the new API collectionproxy.missing_resources, resource.get_current_manifest and resource.store_resource for additional information about how to load excluded collectionproxies during runtime.

Regards,
Jakob & Johan

8 Likes

I’ve used Live Update to create a level based runner game and I’m super impressed by how easy it is to use LiveUpdate. Kudos to @jakob.pogulis and @Johan_Beck-Noren for shipping this awesome feature!

9 Likes

And here comes the Friday entertainment!

10 Likes

Magic! You sure know how to entertain an audience!

4 Likes

Is it possible to use relative path in http.request (e.g. “levels/”) to download resources from the current host?

No, this is a http client that requires a full url with protocol, host etc:

htp.request("test.html", "GET", function(self, _, response)
		pprint(response)
end)

results in:

ERROR:SCRIPT: Unable to create HTTP connection to 'test.html'. No route to host?
INFO:DLIB: SSDP: Started on address 172.19.12.77
DEBUG:SCRIPT: 
{
  status = 0,
  response = ,
  headers = {
  }
}

And we have no api to get the current host? E.g. any way to pass the data from html (js) ?

I’m not sure what you mean by “any way to pass the data from html (js)”. Can you elaborate?

The IP of the device/machine can be retrieved using this snippet:

local function get_ip()
	for i, a in ipairs(sys.get_ifaddrs()) do
		if a.up and a.address then
			return a.address
		end
	end
	return nil
end

Or do you men for communication between two processes/apps on the same machine? In that case you should be able to use 127.0.0.1 for localhost.

Well to use some script on html page to pass data to defold binary, e.g. here: Module.runApp(“canvas”, extra_params) - in that “extra_params” to read in the game later

Or hostname with luasocket:

socket = require 'builtins/scripts/socket'
local hostname = socket.dns.gethostname()	
2 Likes

I’m not sure I follow what you want to do. The `extra_params´ for Module.runApp() are (from dmloader.js):

*     'splash_image':
*         Path to an image that should be used as a background image for
*         the canvas element.
*
*     'archive_location_filter':
*         Filter function that will run for each archive path.
*
*     'unsupported_webgl_callback':
*         Function that is called if WebGL is not supported.
*
*     'engine_arguments':
*         List of arguments (strings) that will be passed to the engine.
*
*     'persistent_storage':
*         Boolean toggling the usage of persistent storage.
*
*     'custom_heap_size':
*         Number of bytes specifying the memory heap size.

Are you talking about passing engine_arguments?

Can you explain what you are trying to achieve?

Very simple, I want to publish the game on different hosts (eg. itch & newgrounds) without paying for CDN to host live updates or recompiling binaries.

Hello @Ivan_Lytkin

Just to clarify the confusion above, if the game is running on forum.defold.com/games/mygame you would need a way to retrieve forum.defold.com from within the game so you can retrieve resources from https://forum.defold.com/resources/mygame, and if the game would be running on github.com/myuser/myproject you would need a way to retrieve github.com. Attempting to get the IP or DNS of the client will not work.

There is currently no way to do this from the API, but as you pointed out it is possible to set a config variable when launching the engine in HTML5 where you can pass in the URL. You can retrieve these config variables in runtime through sys.get_config.

2 Likes

You can set config values via engine arguments like this:

--config=test.my_value=4711

and retrieve it from Lua:

local value = sys.get_config("test.my_value") --> 4711
1 Like