Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/core/string/optimized_translation.cpp
20849 views
1
/**************************************************************************/
2
/* optimized_translation.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "optimized_translation.h"
32
33
#include "core/templates/pair.h"
34
35
extern "C" {
36
#include "thirdparty/misc/smaz.h"
37
}
38
39
struct CompressedString {
40
int orig_len = 0;
41
CharString compressed;
42
int offset = 0;
43
};
44
45
void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
46
// This method compresses a Translation instance.
47
// Right now, it doesn't handle context or plurals.
48
#ifdef TOOLS_ENABLED
49
ERR_FAIL_COND(p_from.is_null());
50
51
List<StringName> keys;
52
{
53
List<StringName> raw_keys;
54
p_from->get_message_list(&raw_keys);
55
56
for (const StringName &key : raw_keys) {
57
const String key_str = key.operator String();
58
int p = key_str.find_char(0x04);
59
if (p == -1) {
60
keys.push_back(key);
61
} else {
62
const String &msgctxt = key_str.substr(0, p);
63
const String &msgid = key_str.substr(p + 1);
64
WARN_PRINT(vformat("OptimizedTranslation does not support context, ignoring message '%s' with context '%s'.", msgid, msgctxt));
65
}
66
}
67
}
68
69
int size = Math::larger_prime(keys.size());
70
71
Vector<Vector<Pair<int, CharString>>> buckets;
72
Vector<HashMap<uint32_t, int>> table;
73
Vector<uint32_t> hfunc_table;
74
Vector<CompressedString> compressed;
75
76
table.resize(size);
77
hfunc_table.resize(size);
78
buckets.resize(size);
79
compressed.resize(keys.size());
80
81
int idx = 0;
82
int total_compression_size = 0;
83
84
for (const StringName &E : keys) {
85
//hash string
86
CharString cs = E.operator String().utf8();
87
uint32_t h = hash(0, cs.get_data());
88
Pair<int, CharString> p;
89
p.first = idx;
90
p.second = cs;
91
buckets.write[h % size].push_back(p);
92
93
//compress string
94
CharString src_s = p_from->get_message(E).operator String().utf8();
95
CompressedString ps;
96
ps.orig_len = src_s.size();
97
ps.offset = total_compression_size;
98
99
if (ps.orig_len != 0) {
100
CharString dst_s;
101
dst_s.resize_uninitialized(src_s.size());
102
int ret = smaz_compress(src_s.get_data(), src_s.size(), dst_s.ptrw(), src_s.size());
103
if (ret >= src_s.size()) {
104
//if compressed is larger than original, just use original
105
ps.orig_len = src_s.size();
106
ps.compressed = src_s;
107
} else {
108
dst_s.resize_uninitialized(ret);
109
//ps.orig_len=;
110
ps.compressed = dst_s;
111
}
112
} else {
113
ps.orig_len = 1;
114
ps.compressed.resize_uninitialized(1);
115
ps.compressed[0] = 0;
116
}
117
118
compressed.write[idx] = ps;
119
total_compression_size += ps.compressed.size();
120
idx++;
121
}
122
123
int bucket_table_size = 0;
124
125
for (int i = 0; i < size; i++) {
126
const Vector<Pair<int, CharString>> &b = buckets[i];
127
HashMap<uint32_t, int> &t = table.write[i];
128
129
if (b.is_empty()) {
130
continue;
131
}
132
133
int d = 1;
134
int item = 0;
135
136
while (item < b.size()) {
137
uint32_t slot = hash(d, b[item].second.get_data());
138
if (t.has(slot)) {
139
item = 0;
140
d++;
141
t.clear();
142
} else {
143
t[slot] = b[item].first;
144
item++;
145
}
146
}
147
148
hfunc_table.write[i] = d;
149
bucket_table_size += 2 + b.size() * 4;
150
}
151
152
ERR_FAIL_COND(bucket_table_size == 0);
153
154
hash_table.resize(size);
155
bucket_table.resize(bucket_table_size);
156
157
int *htwb = hash_table.ptrw();
158
int *btwb = bucket_table.ptrw();
159
160
uint32_t *htw = (uint32_t *)&htwb[0];
161
uint32_t *btw = (uint32_t *)&btwb[0];
162
163
int btindex = 0;
164
165
for (int i = 0; i < size; i++) {
166
const HashMap<uint32_t, int> &t = table[i];
167
if (t.is_empty()) {
168
htw[i] = 0xFFFFFFFF; //nothing
169
continue;
170
}
171
172
htw[i] = btindex;
173
btw[btindex++] = t.size();
174
btw[btindex++] = hfunc_table[i];
175
176
for (const KeyValue<uint32_t, int> &E : t) {
177
btw[btindex++] = E.key;
178
btw[btindex++] = compressed[E.value].offset;
179
btw[btindex++] = compressed[E.value].compressed.size();
180
btw[btindex++] = compressed[E.value].orig_len;
181
}
182
}
183
184
strings.resize(total_compression_size);
185
uint8_t *cw = strings.ptrw();
186
187
for (int i = 0; i < compressed.size(); i++) {
188
memcpy(&cw[compressed[i].offset], compressed[i].compressed.get_data(), compressed[i].compressed.size());
189
}
190
191
ERR_FAIL_COND(btindex != bucket_table_size);
192
set_locale(p_from->get_locale());
193
194
#endif
195
}
196
197
bool OptimizedTranslation::_set(const StringName &p_name, const Variant &p_value) {
198
String prop_name = p_name.operator String();
199
if (prop_name == "hash_table") {
200
hash_table = p_value;
201
} else if (prop_name == "bucket_table") {
202
bucket_table = p_value;
203
} else if (prop_name == "strings") {
204
strings = p_value;
205
} else if (prop_name == "load_from") {
206
generate(p_value);
207
} else {
208
return false;
209
}
210
211
return true;
212
}
213
214
bool OptimizedTranslation::_get(const StringName &p_name, Variant &r_ret) const {
215
String prop_name = p_name.operator String();
216
if (prop_name == "hash_table") {
217
r_ret = hash_table;
218
} else if (prop_name == "bucket_table") {
219
r_ret = bucket_table;
220
} else if (prop_name == "strings") {
221
r_ret = strings;
222
} else {
223
return false;
224
}
225
226
return true;
227
}
228
229
StringName OptimizedTranslation::get_message(const StringName &p_src_text, const StringName &p_context) const {
230
// p_context passed in is ignore. The use of context is not yet supported in OptimizedTranslation.
231
232
int htsize = hash_table.size();
233
234
if (htsize == 0) {
235
return StringName();
236
}
237
238
CharString str = p_src_text.operator String().utf8();
239
uint32_t h = hash(0, str.get_data());
240
241
const int *htr = hash_table.ptr();
242
const uint32_t *htptr = (const uint32_t *)&htr[0];
243
const int *btr = bucket_table.ptr();
244
const uint32_t *btptr = (const uint32_t *)&btr[0];
245
const uint8_t *sr = strings.ptr();
246
const char *sptr = (const char *)&sr[0];
247
248
uint32_t p = htptr[h % htsize];
249
250
if (p == 0xFFFFFFFF) {
251
return StringName(); //nothing
252
}
253
254
const Bucket &bucket = *(const Bucket *)&btptr[p];
255
256
h = hash(bucket.func, str.get_data());
257
258
int idx = -1;
259
260
for (int i = 0; i < bucket.size; i++) {
261
if (bucket.elem[i].key == h) {
262
idx = i;
263
break;
264
}
265
}
266
267
if (idx == -1) {
268
return StringName();
269
}
270
271
if (bucket.elem[idx].comp_size == bucket.elem[idx].uncomp_size) {
272
return String::utf8(&sptr[bucket.elem[idx].str_offset], bucket.elem[idx].uncomp_size);
273
} else {
274
CharString uncomp;
275
uncomp.resize_uninitialized(bucket.elem[idx].uncomp_size + 1);
276
smaz_decompress(&sptr[bucket.elem[idx].str_offset], bucket.elem[idx].comp_size, uncomp.ptrw(), bucket.elem[idx].uncomp_size);
277
return String::utf8(uncomp.get_data());
278
}
279
}
280
281
Vector<String> OptimizedTranslation::get_translated_message_list() const {
282
Vector<String> msgs;
283
284
const int *htr = hash_table.ptr();
285
const uint32_t *htptr = (const uint32_t *)&htr[0];
286
const int *btr = bucket_table.ptr();
287
const uint32_t *btptr = (const uint32_t *)&btr[0];
288
const uint8_t *sr = strings.ptr();
289
const char *sptr = (const char *)&sr[0];
290
291
for (int i = 0; i < hash_table.size(); i++) {
292
uint32_t p = htptr[i];
293
if (p != 0xFFFFFFFF) {
294
const Bucket &bucket = *(const Bucket *)&btptr[p];
295
for (int j = 0; j < bucket.size; j++) {
296
if (bucket.elem[j].comp_size == bucket.elem[j].uncomp_size) {
297
String rstr = String::utf8(&sptr[bucket.elem[j].str_offset], bucket.elem[j].uncomp_size);
298
msgs.push_back(rstr);
299
} else {
300
CharString uncomp;
301
uncomp.resize_uninitialized(bucket.elem[j].uncomp_size + 1);
302
smaz_decompress(&sptr[bucket.elem[j].str_offset], bucket.elem[j].comp_size, uncomp.ptrw(), bucket.elem[j].uncomp_size);
303
String rstr = String::utf8(uncomp.get_data());
304
msgs.push_back(rstr);
305
}
306
}
307
}
308
}
309
return msgs;
310
}
311
312
StringName OptimizedTranslation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
313
// The use of plurals translation is not yet supported in OptimizedTranslation.
314
return get_message(p_src_text, p_context);
315
}
316
317
Vector<String> OptimizedTranslation::_get_message_list() const {
318
WARN_PRINT_ONCE("OptimizedTranslation does not store the message texts to be translated.");
319
return {};
320
}
321
322
void OptimizedTranslation::get_message_list(List<StringName> *r_messages) const {
323
WARN_PRINT_ONCE("OptimizedTranslation does not store the message texts to be translated.");
324
}
325
326
int OptimizedTranslation::get_message_count() const {
327
WARN_PRINT_ONCE("OptimizedTranslation does not store the message texts to be translated.");
328
return 0;
329
}
330
331
void OptimizedTranslation::_get_property_list(List<PropertyInfo> *p_list) const {
332
p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "hash_table", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
333
p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "bucket_table", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
334
p_list->push_back(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "strings", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
335
p_list->push_back(PropertyInfo(Variant::OBJECT, "load_from", PROPERTY_HINT_RESOURCE_TYPE, Translation::get_class_static(), PROPERTY_USAGE_EDITOR));
336
}
337
338
void OptimizedTranslation::_bind_methods() {
339
ClassDB::bind_method(D_METHOD("generate", "from"), &OptimizedTranslation::generate);
340
}
341
342