/
Mr.Stalin
/
FOnline-Engine
Обзор
Документация
Войти
/
Mr.Stalin
/
FOnline-Engine
Код
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
Source/Tools/ProtoBaker.cpp
342 строки
13 KB
cvet
Naming fixes (#201)
10 авг 2026, 13:17
Не верифицирован
10 авг 2026, 13:17
dcde7a3
Код
Авторство
О чём код?
// __________ ___ ______ _ // / ____/ __ \____ / (_)___ ___ / ____/___ ____ _(_)___ ___ // / /_ / / / / __ \/ / / __ \/ _ \ / __/ / __ \/ __ `/ / __ \/ _ ` // / __/ / /_/ / / / / / / / / / __/ / /___/ / / / /_/ / / / / / __/ // /_/ \____/_/ /_/_/_/_/ /_/\___/ /_____/_/ /_/\__, /_/_/ /_/\___/ // /____/ // FOnline Engine // https://fonline.ru // https://github.com/cvet/fonline // // MIT License // // Copyright (c) 2006 - 2026, Anton Tsvetinskiy aka cvet <cvet@tut.by> // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // #include "ProtoBaker.h" #include "AngelScriptScripting.h" #include "AnyData.h" #include "ConfigFile.h" #include "EngineBase.h" #include "EntityProtos.h" #include "ScriptSystem.h" FO_BEGIN_NAMESPACE ProtoBaker::ProtoBaker(shared_ptr<BakingContext> ctx) : BaseBaker(std::move(ctx), NAME) { FO_STACK_TRACE_ENTRY(); } ProtoBaker::~ProtoBaker() { FO_STACK_TRACE_ENTRY(); } void ProtoBaker::BakeFiles(const FileCollection& files, string_view target_path) const { FO_STACK_TRACE_ENTRY(); if (!target_path.empty() && !strex(target_path).get_file_extension().starts_with("fopro-")) { return; } vector<File> filtered_files; uint64_t max_write_time = 0; for (const auto& file_header : files) { string ext = strex(file_header.GetPath()).get_file_extension(); auto it = std::ranges::find(_context->Settings->ProtoFileExtensions, ext); if (it == _context->Settings->ProtoFileExtensions.end()) { continue; } max_write_time = std::max(max_write_time, file_header.GetWriteTime()); filtered_files.emplace_back(File::Load(file_header)); } if (filtered_files.empty()) { return; } vector<std::future<void>> file_bakings; if (!_context->BakeChecker || _context->BakeChecker(_context->PackName + ".fopro-bin-server", max_write_time)) { file_bakings.emplace_back(run_async(GetAsyncMode(), "BakeProto-Server", [&]() FO_DEFERRED { auto engine = BakerServerEngine(*_context->BakedFiles); engine.MapScriptTypes(&engine); #if FO_ANGELSCRIPT_SCRIPTING InitAngelScriptScripting(&engine, *_context->Settings, *_context->BakedFiles); #endif auto data = BakeProtoFiles(&engine, &engine, filtered_files); _context->WriteData(_context->PackName + ".fopro-bin-server", data); })); } if (!_context->BakeChecker || _context->BakeChecker(_context->PackName + ".fopro-bin-client", max_write_time)) { file_bakings.emplace_back(run_async(GetAsyncMode(), "BakeProto-Client", [&]() FO_DEFERRED { auto engine = BakerClientEngine(*_context->BakedFiles); auto data = BakeProtoFiles(&engine, nullptr, filtered_files); _context->WriteData(_context->PackName + ".fopro-bin-client", data); })); } if (!_context->BakeChecker || _context->BakeChecker(_context->PackName + ".fopro-bin-mapper", max_write_time)) { file_bakings.emplace_back(run_async(GetAsyncMode(), "BakeProto-Mapper", [&]() FO_DEFERRED { auto engine = BakerMapperEngine(*_context->BakedFiles); auto data = BakeProtoFiles(&engine, nullptr, filtered_files); _context->WriteData(_context->PackName + ".fopro-bin-mapper", data); })); } size_t errors = 0; for (auto& file_baking : file_bakings) { try { file_baking.get(); } catch (const std::exception& ex) { WriteLog("Proto baking error: {}", ex.what()); errors++; } } if (errors != 0) { throw ProtoBakerException("Errors during protos parsing"); } } auto ProtoBaker::BakeProtoFiles(ptr<EngineMetadata> meta, nptr<const ScriptSystem> script_sys, const vector<File>& files) const -> vector<uint8_t> { FO_STACK_TRACE_ENTRY(); hstring proto_rule_name = meta->Hashes.ToHashedString("Proto"); // Collect data unordered_map<hstring, unordered_map<hstring, map<string, string>>> all_file_protos; for (const auto& file : files) { // Nested ($Name/...-addressed) sections carry map content, never proto declarations auto fopro = ConfigFile(file.GetStr(), ConfigFileOption::SkipNestedSections); for (const auto& [section_name, section_kv_view] : *fopro.GetSections()) { // Skip default section if (section_name.empty()) { continue; } hstring type_name; if (strvex(section_name).starts_with("Proto") && section_name.length() > "Proto"_len) { type_name = meta->Hashes.ToHashedString(section_name.substr("Proto"_len)); } else if (meta->IsFixedType(section_name)) { type_name = meta->Hashes.ToHashedString(section_name); } else { throw ProtoBakerException("Invalid proto section name", section_name, file.GetPath()); } if (meta->IsValidEntityType(type_name)) { if (!meta->GetEntityType(type_name).HasProtos) { throw ProtoBakerException("Invalid proto type", section_name, file.GetPath()); } } else if (!meta->IsFixedType(type_name)) { throw ProtoBakerException("Invalid proto type", section_name, file.GetPath()); } map<string, string> section_kv; for (const auto& [key, value] : section_kv_view) { section_kv.emplace(string(key), string(value)); } auto name = section_kv.count("$Name") != 0 ? section_kv.at("$Name") : file.GetNameNoExt(); if (name.find('/') != string::npos || name.find('$') != string::npos) { throw ProtoBakerException("Proto name must not contain a slash or dollar sign, they are reserved for nested section addressing", name, file.GetPath()); } hstring pid = meta->Hashes.ToHashedString(name); pid = meta->CheckMigrationRule(proto_rule_name, type_name, pid).value_or(pid); auto& file_protos = all_file_protos[type_name]; if (file_protos.count(pid) != 0) { throw ProtoBakerException("Proto already loaded", type_name, pid, file.GetPath()); } file_protos.emplace(pid, section_kv); } } unordered_map<hstring, unordered_map<hstring, refcount_ptr<ProtoEntity>>> all_protos; auto create_empty_proto = [&](hstring type_name, hstring pid) -> refcount_ptr<ProtoEntity> { auto registrar = meta->GetPropertyRegistrar(type_name); FO_VERIFY_AND_THROW(registrar, "Missing property registrar"); if (type_name == ProtoLocation::ENTITY_TYPE_NAME) { return SafeAlloc::MakeRefCounted<ProtoLocation>(pid, registrar, nullptr); } if (type_name == ProtoMap::ENTITY_TYPE_NAME) { return SafeAlloc::MakeRefCounted<ProtoMap>(pid, registrar, nullptr); } if (type_name == ProtoCritter::ENTITY_TYPE_NAME) { return SafeAlloc::MakeRefCounted<ProtoCritter>(pid, registrar, nullptr); } if (type_name == ProtoItem::ENTITY_TYPE_NAME) { return SafeAlloc::MakeRefCounted<ProtoItem>(pid, registrar, nullptr); } return SafeAlloc::MakeRefCounted<ProtoCustomEntity>(pid, registrar, nullptr); }; for (const auto& [type_name, file_protos] : all_file_protos) { for (const auto& pid : file_protos | std::views::keys) { auto proto = create_empty_proto(type_name, pid); meta->RegisterProto(type_name, proto); bool inserted = all_protos[type_name].emplace(pid, std::move(proto)).second; FO_VERIFY_AND_THROW(inserted, "Prototype id is registered more than once for the same entity type", type_name, pid); } } meta->FinalizeRegistration(); // Processing auto insert_map_values = [](const map<string, string>& from_kv, map<string, string>& to_kv) { for (auto&& [key, value] : from_kv) { FO_VERIFY_AND_THROW(!key.empty(), "Prototype key/value map contains an empty key while merging inherited data", value); if (key.front() != '$') { to_kv[key] = value; } } }; for (const auto& file_protos : all_file_protos) { const auto& type_name = file_protos.first; const auto& file_proto_pids = file_protos.second; for (auto&& [pid, file_kv] : file_proto_pids) { string_view base_name = pid.as_str(); // Fill content from parents map<string, string> proto_kv; function<void(string_view, const map<string, string>&)> fill_parent_recursive = [&](string_view name, const map<string, string>& cur_kv) { auto parent_name_line = cur_kv.count("$Parent") != 0 ? cur_kv.at("$Parent") : string(); for (auto& parent_name : strex(parent_name_line).split(' ')) { hstring parent_pid = meta->Hashes.ToHashedString(parent_name); parent_pid = meta->CheckMigrationRule(proto_rule_name, type_name, parent_pid).value_or(parent_pid); auto it_parent = file_proto_pids.find(parent_pid); if (it_parent == file_proto_pids.end()) { if (base_name == name) { throw ProtoBakerException("Proto fail to load parent", base_name, parent_name); } throw ProtoBakerException("Proto fail to load parent for another proto", base_name, parent_name, name); } fill_parent_recursive(parent_name, it_parent->second); insert_map_values(it_parent->second, proto_kv); } }; fill_parent_recursive(base_name, file_kv); // Actual content insert_map_values(file_kv, proto_kv); auto& proto = all_protos[type_name].at(pid); proto->GetPropertiesForEdit()->ApplyFromText(proto_kv); } } // Validation size_t errors = 0; for (auto&& [type_name, protos] : all_protos) { for (auto& proto : protos | std::views::values) { errors += ValidateProperties(*proto->GetProperties(), strex("proto {} {}", type_name, proto->GetName()), script_sys); } } if (errors != 0) { throw ProtoBakerException("Errors during proto validation"); } // Binary representation vector<uint8_t> protos_data; set<hstring> str_hashes; { auto writer = DataWriter(protos_data); vector<uint8_t> props_data; writer.Write<uint32_t>(numeric_cast<uint32_t>(all_protos.size())); for (auto&& [type_name, protos] : all_protos) { writer.Write<uint32_t>(numeric_cast<uint32_t>(protos.size())); writer.Write<uint16_t>(numeric_cast<uint16_t>(type_name.as_str().length())); writer.WriteStringBytes(type_name.as_str()); for (auto& proto : protos | std::views::values) { string_view proto_name = proto->GetName(); writer.Write<uint16_t>(numeric_cast<uint16_t>(proto_name.length())); writer.WriteStringBytes(proto_name); proto->GetProperties()->StoreAllData(props_data, str_hashes); writer.Write<uint32_t>(numeric_cast<uint32_t>(props_data.size())); auto writer_ptr = make_ptr(&writer); writer_ptr->WriteByteVector(props_data); } } } vector<uint8_t> final_data; { auto final_writer = DataWriter(final_data); final_writer.Write<uint32_t>(numeric_cast<uint32_t>(str_hashes.size())); for (const auto& hstr : str_hashes) { string_view str = hstr.as_str(); final_writer.Write<uint32_t>(numeric_cast<uint32_t>(str.length())); final_writer.WriteStringBytes(str); } auto final_writer_ptr = make_ptr(&final_writer); final_writer_ptr->WriteByteVector(protos_data); } return final_data; } FO_END_NAMESPACE