Optimizations
GM-I18n aims to become a performance-oriented i18n system, and as such, it provides several optimizations to ensure that your game runs smoothly even with extensive localization.
Automatic
Some optimizations are applied automatically by the system, so you don't have to worry about them. These optimizations include:
Flattened Message Keys
When you're adding message data to the system, GM-I18n always flattens the keys automatically. This means that if you have a message with a nested structure, it will be stored in a flat format, which can significantly speed up lookups and reduce memory usage.
For example, if you have a message like this in en.json
locale file:
{
"greeting": {
"morning": "Good morning!",
"evening": "Good evening!"
}
}
It will be stored as:
{
"greeting.morning": "Good morning!",
"greeting.evening": "Good evening!"
}
The messages you add using i18n_add_messages
function also follow this flattened structure. So, if you add a message like this:
// assume you've already initialized the i18n system
i18n_add_messages("en", {
greeting : {
morning: "Good morning!",
evening: "Good evening!"
}
});
// will be stored as:
// greeting.morning: "Good morning!",
// greeting.evening: "Good evening!"
Hashed Message Keys
To further optimize performance, you can enable hashed message keys. This feature allows the system to use a hash of the message key instead of the full key string, which can significantly speed up lookups, especially in large projects with many messages.
To enable hashed message keys, you can set hashed: true
option in the i18n_create
function when initializing the i18n system.
// Initialize the i18n system with hashed message keys
global.i18n = i18n_create("g.i18n", "en", [
// array of locales to load
new I18nLocaleInit("en", "English", "~/langs/en.json"),
new I18nLocaleInit("id", "Bhs Indonesia", "~/langs/id.json"),
new I18nLocaleInit("ja", "日本語", "~/langs/ja.json")
], {
// this struct is used to configure the i18n options
hashed: true // enable hashed message keys
});
hashed: false
in the i18n_create
function if you want to.Optimized Reference Update
If you're using the i18n_create_ref_message()
function to create references to messages, you're actually optimizing the way you manage messages in your game. Because the GM-I18n system will only update all of the references in one go with i18n_update_refs()
or i18n_set_locale()
function, instead of updating each reference individually (or even worse, updating the message/reference individually every step in the game loop).
This means that if you change a message, you can simply call i18n_update_refs()
or i18n_set_locale()
to update all references at once, rather than having to update each reference individually. This can significantly reduce the overhead of updating messages in your game, especially if you have a lot of references in your code.
Moreover, if the reference you created using i18n_create_ref_message()
or i18n_create_ref_asset()
is not used anymore, the system will automatically remove it from the list of references, so you don't have to worry about memory leaks or performance issues caused by unused references.
You don't need to worry about the performance impact of updating messages, as the system is designed to handle this efficiently.
i18n_create_ref_*
functions are the recommended way to create references to messages and assets in GM-I18n. They are designed to be efficient and easy to use, allowing you to manage your localization data effectively without sacrificing performance.Chunk Loading
Let's say you have a large number of locale files to load, such as langs/en.json
, langs/id.json
, and langs/ja.json
files, which contain a lot of messages. If you load them all at once, it can cause a performance issue, especially if the files are large or if you have many locales.
Fortunately, GM-I18n provides a way to load locale files in chunks, which can help reduce the performance impact of loading large locale files. You can do this by using the time
option when initializing the i18n system with i18n_create()
function.
This option allows you to specify an interval in seconds between loading each locale. Look at the following example:
// Initialize the i18n system without chunk loading
global.i18n = i18n_create("g.i18n", "en", [ // the `time` option is not set
new I18nLocaleInit("en", "English", "~/langs/en.json"), // so all locale files (langs/en.json, langs/id.json, langs/ja.json) will be loaded at once after 1 step of the game
new I18nLocaleInit("id", "Bhs Indonesia", "~/langs/id.json"), // it's the same if you set `time: 0`
new I18nLocaleInit("ja", "日本語", "~/langs/ja.json") // may cause a performance issue if you have a lot of messages in these locale files, or you have a lot of locales to load
]);
// Initialize the i18n system with chunk loading
global.i18n = i18n_create("g.i18n", "en", [ // the `time` option is set to 0.5 seconds
new I18nLocaleInit("en", "English", "~/langs/en.json"), // so the locales will be loaded in chunks, with 0.5 seconds interval between each locale
new I18nLocaleInit("id", "Bhs Indonesia", "~/langs/id.json"),
new I18nLocaleInit("ja", "日本語", "~/langs/ja.json")
], {
time: 0.5 // load each locale in chunks with 0.5 seconds interval
}); // 0.5 seconds -> load langs/en.json -> 0.5 seconds -> load langs/id.json -> 0.5 seconds -> load langs/ja.json
The example above shows how to initialize the i18n system with and without chunk loading. When you set the time
option, the system will load each locale file in chunks.
You can do more with chunk loading, such as splitting a large locale file into multiple chunks, so that each chunk can be loaded in a separate step. Look at the following example:
// you can split the locale files into multiple chunks
global.i18n = i18n_create("g.i18n", "en", [
new I18nLocaleInit("en", "English", ["~/langs/en1.json", "~/langs/en2.json"]), // the splitted files will be loaded in chunks, with 0.5 seconds interval between each chunk
new I18nLocaleInit("id", "Bhs Indonesia", "~/langs/id.json"), // it's useful if you have a lot of messages in the locale
new I18nLocaleInit("ja", "日本語", "~/langs/ja.json")
], {
time: 0.5 // load each locale in chunks with 0.5 seconds interval
}); // 0.5 sec -> load langs/en1.json -> 0.5 sec -> load langs/en2.json -> 0.5 sec -> load langs/id.json -> 0.5 sec -> load langs/ja.json
// you can also control how the chunks are loaded
global.i18n = i18n_create("g.i18n", "en", [
new I18nLocaleInit("en", "English", ["~/langs/en1.json", "~/langs/en2.json"]),
new I18nLocaleInit("id", "Bhs Indonesia", "~/langs/id.json"),
new I18nLocaleInit("ja", "日本語", "~/langs/ja.json")
], {
time: [0.2, 0.2, 0.5, 1] // set individual loading times for each locale
}); // 0.2 sec -> load langs/en1.json -> 0.2 sec -> load langs/en2.json -> 0.5 sec -> load langs/id.json -> 1 sec -> load langs/ja.json
// use 0 value in the time array to load the locale file after 1 step of the game
global.i18n = i18n_create("g.i18n", "en", [
new I18nLocaleInit("en", "English", ["~/langs/en1.json", "~/langs/en2.json"]),
new I18nLocaleInit("id", "Bhs Indonesia", "~/langs/id.json"),
new I18nLocaleInit("ja", "日本語", "~/langs/ja.json")
], {
time: [0, 0, 0.5, 0.5] // set individual loading times for each locale
}); // 1 step -> load langs/en1.json -> 1 step -> load langs/en2.json -> 0.5 sec -> load langs/id.json -> 0.5 sec -> load langs/ja.json
// the 0 value in the time array is special
// it will be shifted to the front of the array, so that the locale file will be loaded first
global.i18n = i18n_create("g.i18n", "en", [
new I18nLocaleInit("en", "English", ["~/langs/en1.json", "~/langs/en2.json"]),
new I18nLocaleInit("id", "Bhs Indonesia", "~/langs/id.json"),
new I18nLocaleInit("ja", "日本語", "~/langs/ja.json")
], {
time: [0, 0.2, 0, 0.3] // set individual loading times for each locale
}); // 1 step -> load langs/en1.json -> 1 step -> load langs/id.json -> 0.2 sec -> load langs/en2.json -> 0.3 sec -> load langs/ja.json
I18nLocaleInit
array, and the time
option will control how long it takes to load each chunk. You can use
0
value in the time
array to load the locale file after 1 step of the game, which can be useful if you want to load the locale file with important messages first, and then load the rest of the messages in chunks.time
option to control how the chunks are loaded, you should be careful not to set the interval too low, as it can cause performance issues if the locale files are large or if you have many locales to load. You should also avoid setting the interval to
0
for all locales, as it won't have any effect and will cause all locale files to be loaded at once after 1 step of the game. Beware that the
0
value in the time
array won't be loaded immediately. So, if you get a message or creating a message reference before the locale file is loaded, it will return a string in default_message
option (default is an empty string). You need to hardcode the message if you want to use it before the locale file is loaded. Please read the System Flow for more information about how the locale files are loaded.Other Automatic Optimizations
Don't worry about the other optimizations, I'll take care of them for you as the development of GM-I18n continues. The system is designed to be efficient and will automatically apply various optimizations to ensure that your game runs smoothly, even with extensive localization.
Manual
While the automatic optimizations provided by GM-I18n are generally sufficient for most projects, there are some additional manual optimizations you can apply to further enhance performance:
Drawing Templates
If you're using the drawing preset feature, you can optimize the way you draw messages by mixing drawing presets with the regular drawings (GameMaker's draw_*
functions).
Basically, a drawing template is a drawing preset with more abstracted data, which can be used to create multiple drawings with the same structure but different content. This can help reduce the number of individual drawings you need to manage, and can also improve performance by reusing the same drawing settings for multiple messages.
Same as the drawing preset, you can create a drawing template using i18n_add_drawings()
function, but with simpler data structure. For example, you can create a drawing template like this:
// assume you've already initialized the i18n system
// here's some drawing templates
// you must include all available locales in the first argument (locale), so that the drawing template can be used in all locales
i18n_add_drawings(["en", "id", "ja"], ["t_button", "t_header", "t_body"], [ // the `t_` prefix is used to indicate that this is a drawing template
new I18nDrawings(, fa_center, fa_middle, #000000, 0.65), // just fill the parameters that have the same value in all locales
new I18nDrawings(, fa_left, fa_middle, #FFFFFF, 0.65), // and skip the parameters that have different values in each locale
new I18nDrawings(, fa_left, fa_top, #CCCCCC, 0.49, 0, 1, -1, 1198)
]);
// then you can add the missing parameters in previous drawing templates
// this missing parameters will be used as the dynamic data for the drawing template
i18n_add_drawings(["en", "id"], "font1", new I18nDrawings(fnNotoSans));
i18n_add_drawings(["ja"], "font1", new I18nDrawings(global.font_ja));
// create some messages that will be drawn
my_msg = i18n_get_messages("desc", , "en"); // "This is a description of the game." static message in en
my_msg_ref = i18n_create_ref_message("my_msg_ref", "header", [
"John" // this is a dynamic message with indexed data, will be used in the drawing template
]);
// and later you can mix the drawing templates with the regular drawings
// see the Draw/Draw GUI Event example for more details
For more information about the drawing, please read the Direct Drawings section. It may help you to understand how to use drawing templates in your game.