Common optimization

In fact, whether it is Web games or other ordinary Web pages, how to make the user can see the page faster, faster operation experience, is an ongoing topic of discussion. Personally, there are two directions for optimization:

  • Technical direction: such as caching (CDN), compression of resources to reduce transmission volume, etc., which is basically not related to specific business

  • Business direction: It is customized and optimized for a specific project, but for a specific business, it may be possible to further subdivide technology (a series of businesses with the same technology, such as Cocos mentioned in this article) and business direction

Aside from the very generic optimizations, the common technical optimizations for Web mini-games developed using Cocos Creator are pretty much the same

  • Remove engine modules that are not needed. There are several big heads that can be removed completely if they are not used, such as 3D modules, physics engine related, bone animation related
  • When packaging, you can set the main package compression type to merge all JSON files. After all, the Cocos package generates too many broken files

Both of the above are built-in capabilities of Cocos Creator, which sounds pretty good. What other problems need to be optimized?

The problem

Let’s take a look at the v2.4.6 version of Cocos Creator and package an empty project (_ with a map, a TS file for testing, project address: cocos-web) to see how it loads:

As you can see from the image above, it took a dozen requests before the main scene started loading. In this process, many interfaces are blocked, which also leads to the case of Fast 3G, the main scene takes about 5.8s to start loading, and all resources need about 8.1s to finish loading! The ceiling is too low!

So let’s take a look at each of these requests one by one.

Inline

At first, of course, an Html is loaded, and then a corresponding CSS is loaded, which is a simple implementation of a load page. After all, the engine file of Cocos Creator is very large (see cocos2D-js-min.xxx. js in the picture above, there is still 260KB after gzip), so it cannot be downloaded in a short time. Therefore, a loading interface is implemented in Html first, which does not make users wait for a long time or the screen is blank.

For this loading interface, the actual development will definitely replace it, and the CSS here is not large enough to be Inline to the Html, thus saving one request.

The settings.xxx.js and main.xxx.js files are also very small and can be inline to Html to start loading engines and other large files more quickly.

  • settings.xxx.js: It’s just herewindowThere’s one on the hook_CCSettingsVariables, which basically define the entry scene and eachbundlethehashvalue
  • main.xxx.js: it is inwindowThere’s one on the hookbootMethod, which is executed after the engine has finished loading

So after inlining, what’s the effect?

It is obvious that DOMContentLoaded is about 0.6s faster, the main scene starts loading around 5.2s, and it takes about 7.5s for all resources to load

The next step is to load the engine and the images displayed on the loading page. This can be optimized in a general way, for example, for the engine, that is to reduce unnecessary modules, and for images, it is to use a tool compression, such as tinypng(tinypng.com/).

Bundle

After that, you’ll see three loads of config.xxx.json and index.xxx.js. If you’ve looked at main.xxx.js before, it’s easy to see that this is loading the basic information and code of several bundles

In addition, the boot method of main.xxx.js will load the internal and resources bundles first. MainBundle will load the resources and internal bundles before mainBundle is loaded, and the other three bundles will load after the engine is loaded.

That is, from the perspective of the existing loading process, there may be sequential dependencies between them. Can we break this dependency? Get relevant resources loaded ahead of time. The simple reason is that JSON files can be preloaded, cached by the browser, and then automatically loaded when the actual Bundle is loaded.

But first take a closer look at what’s in these files to see what the dependencies are.

Js in internal and Resources is an empty function that does nothing but load it. Only the index.xxx.js in main registers the code we wrote in the project. It is also possible that we did not add code to the relevant Bundle. This looks like it could be optimized, so let’s look at it and see how we can optimize it.

If you looked at the loading mechanism from Cocos support GIF in the previous article, you have a general idea of the loading process for Cocos Creator. How does Cocos Creator download the Bundle

var downloadBundle = function (nameOrUrl, options, onComplete) {
    let bundleName = cc.path.basename(nameOrUrl);
    let url = nameOrUrl;
    if(! REGEX.test(url)) url ='assets/' + bundleName;
    var version = options.version || downloader.bundleVers[bundleName];
    var count = 0;
    var config = `${url}/config.${version ? version + '. ' : ' '}json`;
    let out = null, error = null;
    downloadJson(config, options, function (err, response) {
        if (err) {
            error = err;
        }
        out = response;
        out && (out.base = url + '/');
        count++;
        if (count === 2) { onComplete(error, out); }});var js = `${url}/index.${version ? version + '. ' : ' '}js`;
    downloadScript(js, options, function (err) {
        if (err) {
            error = err;
        }
        count++;
        if (count === 2) { onComplete(error, out); }}); };Copy the code

As you can see from the above code, when loading the Bundle, it does not care what the code in index.xxx.js is, only that it is loaded, so it will load even useless empty functions, which is wasteful.

However, since it doesn’t care about the code in index.xxx.js, we can simply preload the useful code and wait until the engine has finished loading. Besides, all the code in the project seems to have been merged into the Bundle’s index.xxx.js (unverified), and these files are the only ones we care about.

Going back to JSON files, I mentioned earlier that you could have relied on the browser’s cache to preload them, but the files were still pretty crumbled. Looking at this near-empty project, you would have had to load 7 JSON files, some of which were too crumbled (too small) to enjoy gzip’s compression benefits.

In addition, Cocos Creator will obtain the resources it depends on through JSON files. That is to say, the image used in the main scene in this example needs to be found after the main scene JSON is downloaded, and then it needs to load the description JSON (meta meta JSONšŸ¶) of this image. Finally to load the real image resources, which are actually serial, sounds slow.

So can we just merge all the JSON and preload it, and when we use it, no network requests at all! However, this also depends on the size of the project; if the project is too large, a more refined merge may be required.

After merging all JS and JSON, we need to modify the engine’s loading method in the boot method to support our new form

var jsonDownloader = cc.assetManager.downloader._downloaders[".json"];
var bundleDownloader = cc.assetManager.downloader._downloaders["bundle"];
var REGEX = / ^ (? :\w+:\/\/|\.+\/).+/;
var newJsonDownloader = function (url, options, onComplete) {
  var name = cc.path.basename(url);
  var pack = "";
  if (url.startsWith("assets/")) {
    pack = url.split("/") [1];
  }
  if (window.allJSON[pack] && window.allJSON[pack][name]) {
    onComplete && onComplete(null.window.allJSON[pack][name]);
  } else{ jsonDownloader(url, options, onComplete); }};var newBundleDownloader = function (nameOrUrl, options, onComplete) {
  var bundleName = cc.path.basename(nameOrUrl);
  if (window.allJSON[bundleName]) {
    var version = settings.bundleVers[bundleName];
    var url = nameOrUrl;
    if(! REGEX.test(url)) url ="assets/" + bundleName;
    var out =
      window.allJSON[bundleName][
        "config." + (version ? version + "." : "") + "json"
      ];
    out && (out.base = url + "/");
    onComplete && onComplete(null, out);
  } else{ bundleDownloader(nameOrUrl, options, onComplete); }}; cc.assetManager.downloader._downloaders[".json"] = newJsonDownloader;
cc.assetManager.downloader._downloaders["bundle"] = newBundleDownloader;
Copy the code

Look at the results:

The world is instantly quiet! The number of requests dropped dramatically, and the main scene started loading in about 3.5 seconds, while all resources took about 4.1 seconds to load

default_sprite_splash

When the number of requests is low, you will find that an image is requested that you are not using at all (2×2 by 2). If only one more image is requested, it is ok, but it will block the loading of the main scene! In other words, this image, which is completely unknown, slows down the loading process.

After careful study, it was found that this image was actually default_sprite_splash under internal/image, and its dependency could not be directly found in the project. It was estimated that some material resources depended on by the engine indirectly referenced this image.

Besides, I do not recommend directly using images from internal/image in the project. After all, these are single images. On the one hand, it requires an extra load request from the network. On the other hand, it doesn’t merge into a graph, so the DrawCall increases. Of course, if you use Texture2D directly (such as trailing) instead of SpriteFrame, the images referenced will be the same everywhere and will not be merged into an atlas.

After searching for a circle, I did not find a suitable method for the time being, but fortunately the figure is very small (the original figure 82B, but the network transmission needs 358B). There is a last way, that is to directly inline into THE Html, and directly change the engine image loading process

var twoPxPng = 'data:image/png; base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAFklEQVQImWP8//8/AwMDEwMDAwMDAwAkBgMBmjCi+wAAAABJRU5ErkJggg==';
var pngDownloader = cc.assetManager.downloader._downloaders['.png'];
var newPngDownloader = function(url, options, onComplete) {
  var pngName = cc.path.basename(url);
  // Write to death for now, optimize in actual project
  if (pngName === '0275e94c-56a7-410f-bd1a-fc7483f7d14a.cea68.png') {
    cc.assetManager.downloader.downloadDomImage(twoPxPng, options, onComplete);
  } else {
    pngDownloader(url, options, onComplete);
  }
}
cc.assetManager.downloader._downloaders['.png'] = newPngDownloader;
Copy the code

Let’s see what it looks like:

Another 0.6 seconds faster! It takes about 3.5s for all resources to load, so inlining seems like a smart choice.

conclusion

The above optimization can be considered as a loading optimization idea for Cocos to develop small Web games, which may need more verification in actual projects. In addition, this is only purely technical thinking, the actual project, business optimization is the main, after all, the use of too many pictures, easy to erase the technical optimization.

According to the optimized data, 8.1/3.5 ā‰ˆ 2.3, the speed is 2.3 times of that before optimization. Of course, under the condition of good network condition, the speed increase is not so obvious.

This involves the relevant code, in Cocos-Web can be viewed here, interested can have a look.