Path: blob/master/core/string/optimized_translation.cpp
20849 views
/**************************************************************************/1/* optimized_translation.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "optimized_translation.h"3132#include "core/templates/pair.h"3334extern "C" {35#include "thirdparty/misc/smaz.h"36}3738struct CompressedString {39int orig_len = 0;40CharString compressed;41int offset = 0;42};4344void OptimizedTranslation::generate(const Ref<Translation> &p_from) {45// This method compresses a Translation instance.46// Right now, it doesn't handle context or plurals.47#ifdef TOOLS_ENABLED48ERR_FAIL_COND(p_from.is_null());4950List<StringName> keys;51{52List<StringName> raw_keys;53p_from->get_message_list(&raw_keys);5455for (const StringName &key : raw_keys) {56const String key_str = key.operator String();57int p = key_str.find_char(0x04);58if (p == -1) {59keys.push_back(key);60} else {61const String &msgctxt = key_str.substr(0, p);62const String &msgid = key_str.substr(p + 1);63WARN_PRINT(vformat("OptimizedTranslation does not support context, ignoring message '%s' with context '%s'.", msgid, msgctxt));64}65}66}6768int size = Math::larger_prime(keys.size());6970Vector<Vector<Pair<int, CharString>>> buckets;71Vector<HashMap<uint32_t, int>> table;72Vector<uint32_t> hfunc_table;73Vector<CompressedString> compressed;7475table.resize(size);76hfunc_table.resize(size);77buckets.resize(size);78compressed.resize(keys.size());7980int idx = 0;81int total_compression_size = 0;8283for (const StringName &E : keys) {84//hash string85CharString cs = E.operator String().utf8();86uint32_t h = hash(0, cs.get_data());87Pair<int, CharString> p;88p.first = idx;89p.second = cs;90buckets.write[h % size].push_back(p);9192//compress string93CharString src_s = p_from->get_message(E).operator String().utf8();94CompressedString ps;95ps.orig_len = src_s.size();96ps.offset = total_compression_size;9798if (ps.orig_len != 0) {99CharString dst_s;100dst_s.resize_uninitialized(src_s.size());101int ret = smaz_compress(src_s.get_data(), src_s.size(), dst_s.ptrw(), src_s.size());102if (ret >= src_s.size()) {103//if compressed is larger than original, just use original104ps.orig_len = src_s.size();105ps.compressed = src_s;106} else {107dst_s.resize_uninitialized(ret);108//ps.orig_len=;109ps.compressed = dst_s;110}111} else {112ps.orig_len = 1;113ps.compressed.resize_uninitialized(1);114ps.compressed[0] = 0;115}116117compressed.write[idx] = ps;118total_compression_size += ps.compressed.size();119idx++;120}121122int bucket_table_size = 0;123124for (int i = 0; i < size; i++) {125const Vector<Pair<int, CharString>> &b = buckets[i];126HashMap<uint32_t, int> &t = table.write[i];127128if (b.is_empty()) {129continue;130}131132int d = 1;133int item = 0;134135while (item < b.size()) {136uint32_t slot = hash(d, b[item].second.get_data());137if (t.has(slot)) {138item = 0;139d++;140t.clear();141} else {142t[slot] = b[item].first;143item++;144}145}146147hfunc_table.write[i] = d;148bucket_table_size += 2 + b.size() * 4;149}150151ERR_FAIL_COND(bucket_table_size == 0);152153hash_table.resize(size);154bucket_table.resize(bucket_table_size);155156int *htwb = hash_table.ptrw();157int *btwb = bucket_table.ptrw();158159uint32_t *htw = (uint32_t *)&htwb[0];160uint32_t *btw = (uint32_t *)&btwb[0];161162int btindex = 0;163164for (int i = 0; i < size; i++) {165const HashMap<uint32_t, int> &t = table[i];166if (t.is_empty()) {167htw[i] = 0xFFFFFFFF; //nothing168continue;169}170171htw[i] = btindex;172btw[btindex++] = t.size();173btw[btindex++] = hfunc_table[i];174175for (const KeyValue<uint32_t, int> &E : t) {176btw[btindex++] = E.key;177btw[btindex++] = compressed[E.value].offset;178btw[btindex++] = compressed[E.value].compressed.size();179btw[btindex++] = compressed[E.value].orig_len;180}181}182183strings.resize(total_compression_size);184uint8_t *cw = strings.ptrw();185186for (int i = 0; i < compressed.size(); i++) {187memcpy(&cw[compressed[i].offset], compressed[i].compressed.get_data(), compressed[i].compressed.size());188}189190ERR_FAIL_COND(btindex != bucket_table_size);191set_locale(p_from->get_locale());192193#endif194}195196bool OptimizedTranslation::_set(const StringName &p_name, const Variant &p_value) {197String prop_name = p_name.operator String();198if (prop_name == "hash_table") {199hash_table = p_value;200} else if (prop_name == "bucket_table") {201bucket_table = p_value;202} else if (prop_name == "strings") {203strings = p_value;204} else if (prop_name == "load_from") {205generate(p_value);206} else {207return false;208}209210return true;211}212213bool OptimizedTranslation::_get(const StringName &p_name, Variant &r_ret) const {214String prop_name = p_name.operator String();215if (prop_name == "hash_table") {216r_ret = hash_table;217} else if (prop_name == "bucket_table") {218r_ret = bucket_table;219} else if (prop_name == "strings") {220r_ret = strings;221} else {222return false;223}224225return true;226}227228StringName OptimizedTranslation::get_message(const StringName &p_src_text, const StringName &p_context) const {229// p_context passed in is ignore. The use of context is not yet supported in OptimizedTranslation.230231int htsize = hash_table.size();232233if (htsize == 0) {234return StringName();235}236237CharString str = p_src_text.operator String().utf8();238uint32_t h = hash(0, str.get_data());239240const int *htr = hash_table.ptr();241const uint32_t *htptr = (const uint32_t *)&htr[0];242const int *btr = bucket_table.ptr();243const uint32_t *btptr = (const uint32_t *)&btr[0];244const uint8_t *sr = strings.ptr();245const char *sptr = (const char *)&sr[0];246247uint32_t p = htptr[h % htsize];248249if (p == 0xFFFFFFFF) {250return StringName(); //nothing251}252253const Bucket &bucket = *(const Bucket *)&btptr[p];254255h = hash(bucket.func, str.get_data());256257int idx = -1;258259for (int i = 0; i < bucket.size; i++) {260if (bucket.elem[i].key == h) {261idx = i;262break;263}264}265266if (idx == -1) {267return StringName();268}269270if (bucket.elem[idx].comp_size == bucket.elem[idx].uncomp_size) {271return String::utf8(&sptr[bucket.elem[idx].str_offset], bucket.elem[idx].uncomp_size);272} else {273CharString uncomp;274uncomp.resize_uninitialized(bucket.elem[idx].uncomp_size + 1);275smaz_decompress(&sptr[bucket.elem[idx].str_offset], bucket.elem[idx].comp_size, uncomp.ptrw(), bucket.elem[idx].uncomp_size);276return String::utf8(uncomp.get_data());277}278}279280Vector<String> OptimizedTranslation::get_translated_message_list() const {281Vector<String> msgs;282283const int *htr = hash_table.ptr();284const uint32_t *htptr = (const uint32_t *)&htr[0];285const int *btr = bucket_table.ptr();286const uint32_t *btptr = (const uint32_t *)&btr[0];287const uint8_t *sr = strings.ptr();288const char *sptr = (const char *)&sr[0];289290for (int i = 0; i < hash_table.size(); i++) {291uint32_t p = htptr[i];292if (p != 0xFFFFFFFF) {293const Bucket &bucket = *(const Bucket *)&btptr[p];294for (int j = 0; j < bucket.size; j++) {295if (bucket.elem[j].comp_size == bucket.elem[j].uncomp_size) {296String rstr = String::utf8(&sptr[bucket.elem[j].str_offset], bucket.elem[j].uncomp_size);297msgs.push_back(rstr);298} else {299CharString uncomp;300uncomp.resize_uninitialized(bucket.elem[j].uncomp_size + 1);301smaz_decompress(&sptr[bucket.elem[j].str_offset], bucket.elem[j].comp_size, uncomp.ptrw(), bucket.elem[j].uncomp_size);302String rstr = String::utf8(uncomp.get_data());303msgs.push_back(rstr);304}305}306}307}308return msgs;309}310311StringName OptimizedTranslation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {312// The use of plurals translation is not yet supported in OptimizedTranslation.313return get_message(p_src_text, p_context);314}315316Vector<String> OptimizedTranslation::_get_message_list() const {317WARN_PRINT_ONCE("OptimizedTranslation does not store the message texts to be translated.");318return {};319}320321void OptimizedTranslation::get_message_list(List<StringName> *r_messages) const {322WARN_PRINT_ONCE("OptimizedTranslation does not store the message texts to be translated.");323}324325int OptimizedTranslation::get_message_count() const {326WARN_PRINT_ONCE("OptimizedTranslation does not store the message texts to be translated.");327return 0;328}329330void OptimizedTranslation::_get_property_list(List<PropertyInfo> *p_list) const {331p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "hash_table", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));332p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "bucket_table", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));333p_list->push_back(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "strings", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));334p_list->push_back(PropertyInfo(Variant::OBJECT, "load_from", PROPERTY_HINT_RESOURCE_TYPE, Translation::get_class_static(), PROPERTY_USAGE_EDITOR));335}336337void OptimizedTranslation::_bind_methods() {338ClassDB::bind_method(D_METHOD("generate", "from"), &OptimizedTranslation::generate);339}340341342