From d1d24ac8763d2d8fc8a6200e4b991e765312996c Mon Sep 17 00:00:00 2001 From: Vladimir Khanayev Date: Thu, 16 Jul 2026 17:10:54 +0300 Subject: [PATCH 01/10] =?UTF-8?q?=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BB=D1=83?= =?UTF-8?q?=D0=B0=20=D0=BA=D0=BE=D0=B4=D0=B0=20=D0=B2=20OLC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #3570 --- .gitignore | 1 + docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md | 2 + meson.build | 13 +++ meson_options.txt | 1 + src/engine/scripting/dg_olc.cpp | 35 ++++++ src/engine/scripting/lua/lua_formatter.cpp | 86 ++++++++++++++ src/engine/scripting/lua/lua_formatter.h | 15 +++ src/engine/ui/cmd_god/do_luainfo.cpp | 3 + subprojects/emmyluacodestyle.wrap | 9 ++ .../packagefiles/emmyluacodestyle/meson.build | 106 ++++++++++++++++++ 10 files changed, 271 insertions(+) create mode 100644 src/engine/scripting/lua/lua_formatter.cpp create mode 100644 src/engine/scripting/lua/lua_formatter.h create mode 100644 subprojects/emmyluacodestyle.wrap create mode 100644 subprojects/packagefiles/emmyluacodestyle/meson.build diff --git a/.gitignore b/.gitignore index a3f1b5409..610909601 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ subprojects/fmt subprojects/gtest subprojects/sol2 subprojects/luajit +subprojects/emmyluacodestyle subprojects/yaml-cpp subprojects/zlib /1/ diff --git a/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md b/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md index 65068b264..d0c3116a3 100644 --- a/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md +++ b/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md @@ -40,6 +40,8 @@ script: | Lua-триггер можно писать как обычное тело функции: движок выполняет такой chunk с доступным `ctx` - контекстом владельца триггера, актера, объекта и других данных события. Старый явный формат `return function(ctx) ... end` продолжает работать. +В OLC-меню Lua-триггера доступен пункт `Автоформат Lua`. Он форматирует текущий текст встроенной библиотекой EmmyLuaCodeStyle с отступом в 2 пробела. Форматирование запускается только явно: просмотр и сохранение сами текст не меняют. Если исходник содержит синтаксическую ошибку или результат нельзя сохранить в KOI8-R, исходный OLC-буфер остается без изменений. В сборке без LuaJIT пункт не показывается; встроенный форматтер можно отключить опцией Meson `-Dlua_formatter=false`. + Номер строки в ошибке Lua соответствует номеру в `tstat -n ` и в нумерованном выводе `/n` текстового редактора OLC. Пустые строки учитываются. Пока изменения в OLC не сохранены, runtime-сообщения относятся к предыдущей сохраненной версии триггера. Номер строки в диагностике DG и Lua имеет разный смысл. Для DG строки команд нумерует сам движок. Для Lua номер сообщает LuaJIT: это строка, на которой парсер или выполняемый код обнаружил ошибку. Первопричина синтаксической ошибки может находиться раньше. Например, если на строке 7 написано незаконченное выражение `asdasd`, а на строке 8 начинается `mud.wait(1)`, Lua может сообщить об ошибке на строке 8: только встретив `mud`, парсер понял, что после `asdasd` ожидался оператор присваивания. Поэтому при синтаксической ошибке проверяйте не только указанную строку, но и несколько строк перед ней, включая незакрытые скобки, строки и блоки. diff --git a/meson.build b/meson.build index 4d3e1af95..310311ae7 100644 --- a/meson.build +++ b/meson.build @@ -212,6 +212,7 @@ threads_dep = dependency('threads') dependencies += threads_dep luajit_opt = get_option('luajit') +lua_formatter_enabled = false if luajit_opt != 'disabled' if luajit_opt == 'builtin' luajit_sub = subproject('luajit', default_options: ['default_library=static', 'luajit=false']) @@ -238,6 +239,16 @@ if luajit_opt != 'disabled' ) project_args += '-DWITH_LUAJIT_PROTOTYPE' dependencies += [luajit_dep, sol2_dep] + if get_option('lua_formatter') + emmy_dep = dependency( + 'emmyluacodestyle', + fallback: ['emmyluacodestyle', 'emmyluacodestyle_dep'], + default_options: ['warning_level=0'], + ) + dependencies += emmy_dep + project_args += '-DWITH_LUA_FORMATTER' + lua_formatter_enabled = true + endif endif # Admin API @@ -459,6 +470,7 @@ main_sources = files( 'src/engine/scripting/dg_triggers.cpp', 'src/engine/scripting/dg_wldcmd.cpp', 'src/engine/scripting/lua/lua_entity_bindings.cpp', + 'src/engine/scripting/lua/lua_formatter.cpp', 'src/engine/scripting/lua/lua_mud_api.cpp', 'src/engine/scripting/lua/lua_script_engine.cpp', 'src/engine/scripting/lua/lua_trigger_context.cpp', @@ -1071,6 +1083,7 @@ summary({ 'fmt': fmt_opt, 'gtest': gtest_opt, 'LuaJIT': luajit_opt, + 'Lua formatter': lua_formatter_enabled, 'SQLite': sqlite_opt, 'YAML': yaml_opt, 'Telegram': telegram_opt, diff --git a/meson_options.txt b/meson_options.txt index 7a750afc2..bf8598bb5 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -6,6 +6,7 @@ option('nocrypt', type: 'boolean', value: false, description: 'Disable use of cr option('has_epoll', type: 'boolean', value: true, description: 'Use epoll (Linux or freebsd)') option('scripting', type: 'boolean', value: false, description: 'Enable scripting support (Python + Boost.Python)') option('sol2_root', type: 'string', value: '', description: 'Optional path to sol2 root for luajit_prototype; empty uses Meson dependency/fallback') +option('lua_formatter', type: 'boolean', value: true, description: 'Build the embedded EmmyLuaCodeStyle formatter when LuaJIT is enabled') option('with_asan', type: 'boolean', value: false, description: 'Build with Address Sanitizer') option('use_pch', type: 'boolean', value: true, description: 'Use precompiled headers for circle.library') diff --git a/src/engine/scripting/dg_olc.cpp b/src/engine/scripting/dg_olc.cpp index 52989548d..30d262829 100644 --- a/src/engine/scripting/dg_olc.cpp +++ b/src/engine/scripting/dg_olc.cpp @@ -27,6 +27,7 @@ #include "engine/entities/zone.h" #include #include "engine/db/world_characters.h" +#include "engine/scripting/lua/lua_formatter.h" #include "engine/db/global_objects.h" #include "engine/db/world_data_source_manager.h" #include "dg_db_scripts.h" @@ -70,6 +71,26 @@ bool TrigeditIsLuaTrigger(const Trigger *trig) return trig && trig->get_script_language() == TriggerScriptLanguage::Lua; } +void TrigeditFormatLua(DescriptorData *d) +{ + std::string formatted; + std::string error; + const std::string source = OLC_STORAGE(d) ? OLC_STORAGE(d) : ""; + if (!lua_scripting::FormatLuaSource(source, formatted, error)) { + SendMsgToChar(d->character.get(), "Ошибка автоформата Lua: %s\r\n", error.c_str()); + return; + } + if (formatted.size() >= MAX_CMD_LENGTH) { + SendMsgToChar(d->character.get(), "Ошибка автоформата Lua: результат слишком большой.\r\n"); + return; + } + + RECREATE(OLC_STORAGE(d), MAX_CMD_LENGTH); + memcpy(OLC_STORAGE(d), formatted.c_str(), formatted.size() + 1); + OLC_VAL(d)++; + SendMsgToChar(d->character.get(), "Lua-скрипт отформатирован.\r\n"); +} + void TrigeditLoadStorageFromTrigger(Trigger *trig, char *storage) { if (!trig || !storage) { @@ -259,6 +280,9 @@ void trigedit_disp_menu(DescriptorData *d) { #if defined(WITH_LUAJIT_PROTOTYPE) out << "&g8)&n Язык скрипта : &y" << (TrigeditIsLuaTrigger(trig) ? "Lua" : "DG") << "&n\r\n"; #endif + if (TrigeditIsLuaTrigger(trig) && lua_scripting::LuaFormatterAvailable()) { + out << "&g9)&n Автоформат Lua\r\n"; + } out << "&gQ)&n Завершить редактирование\r\n" "Введите Выбранное :"; SendMsgToChar(out.str(), d->character.get()); OLC_MODE(d) = TRIGEDIT_MAIN_MENU; @@ -368,6 +392,17 @@ void trigedit_parse(DescriptorData *d, char *arg) { break; #endif + case '9': + if (!TrigeditIsLuaTrigger(OLC_TRIG(d))) { + SendMsgToChar("Автоформат доступен только для Lua-триггеров.\r\n", d->character.get()); + } else if (!lua_scripting::LuaFormatterAvailable()) { + SendMsgToChar("Форматтер Lua недоступен в этой сборке.\r\n", d->character.get()); + } else { + TrigeditFormatLua(d); + } + trigedit_disp_menu(d); + return; + default: trigedit_disp_menu(d); return; } diff --git a/src/engine/scripting/lua/lua_formatter.cpp b/src/engine/scripting/lua/lua_formatter.cpp new file mode 100644 index 000000000..c63499804 --- /dev/null +++ b/src/engine/scripting/lua/lua_formatter.cpp @@ -0,0 +1,86 @@ +#include "engine/scripting/lua/lua_formatter.h" + +#if defined(WITH_LUA_FORMATTER) +#include "CodeFormatCLib.h" +#include "utils/utils_encoding.h" + +#include +#include +#include +#endif + +namespace lua_scripting { + +bool LuaFormatterAvailable() { +#if defined(WITH_LUA_FORMATTER) + return true; +#else + return false; +#endif +} + +bool FormatLuaSource(const std::string& source, std::string& formatted, std::string& error) { + formatted.clear(); + error.clear(); + +#if defined(WITH_LUA_FORMATTER) + try { + if (source.find('\0') != std::string::npos) { + error = "Исходный Lua-код содержит нулевой байт"; + return false; + } + + std::vector koi_input(source.begin(), source.end()); + koi_input.push_back('\0'); + std::vector utf8_input(source.size() * 3 + 1, '\0'); + codepages::koi_to_utf8(koi_input.data(), utf8_input.data()); + + FormattingOptions options{}; + options.indent_size = 2; + options.use_tabs = false; + options.insert_final_newline = true; + options.non_standard_symbol = false; + + using FormatResultPtr = std::unique_ptr; + FormatResultPtr result(ReformatLuaCode(utf8_input.data(), "trigger.lua", options), FreeReformatResult); + if (!result) { + error = "Форматтер Lua не смог разобрать исходный код"; + return false; + } + + const std::string formatted_utf8(result.get()); + std::vector utf8_output(formatted_utf8.begin(), formatted_utf8.end()); + utf8_output.push_back('\0'); + std::vector koi_output(formatted_utf8.size() + 1, '\0'); + codepages::utf8_to_koi(utf8_output.data(), koi_output.data()); + formatted = koi_output.data(); + + std::vector round_trip_input(formatted.begin(), formatted.end()); + round_trip_input.push_back('\0'); + std::vector round_trip_utf8(formatted.size() * 3 + 1, '\0'); + codepages::koi_to_utf8(round_trip_input.data(), round_trip_utf8.data()); + if (formatted_utf8 != round_trip_utf8.data()) { + formatted.clear(); + error = "Результат форматирования Lua нельзя представить в KOI8-R"; + return false; + } + return true; + } catch (const std::exception&) { + formatted.clear(); + error = "внутренняя ошибка форматтера"; + return false; + } catch (...) { + formatted.clear(); + error = "внутренняя ошибка форматтера"; + return false; + } +#else + (void)source; + error = "Форматтер Lua недоступен в этой сборке"; + return false; +#endif +} + +} // namespace lua_scripting + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/scripting/lua/lua_formatter.h b/src/engine/scripting/lua/lua_formatter.h new file mode 100644 index 000000000..9fedd0fc4 --- /dev/null +++ b/src/engine/scripting/lua/lua_formatter.h @@ -0,0 +1,15 @@ +#ifndef BYLINS_SRC_ENGINE_SCRIPTING_LUA_LUA_FORMATTER_H_ +#define BYLINS_SRC_ENGINE_SCRIPTING_LUA_LUA_FORMATTER_H_ + +#include + +namespace lua_scripting { + +bool LuaFormatterAvailable(); +bool FormatLuaSource(const std::string& source, std::string& formatted, std::string& error); + +} // namespace lua_scripting + +#endif // BYLINS_SRC_ENGINE_SCRIPTING_LUA_LUA_FORMATTER_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/ui/cmd_god/do_luainfo.cpp b/src/engine/ui/cmd_god/do_luainfo.cpp index 8bd9cb8d1..31e81f8f8 100644 --- a/src/engine/ui/cmd_god/do_luainfo.cpp +++ b/src/engine/ui/cmd_god/do_luainfo.cpp @@ -4,6 +4,7 @@ #include #include "engine/entities/char_data.h" +#include "engine/scripting/lua/lua_formatter.h" #include "engine/scripting/lua/lua_internal.h" #include "engine/ui/modify.h" @@ -71,6 +72,8 @@ void AppendLuaRuntimeDiagnostics(std::ostringstream &out) { void DoLuaInfo(CharData *ch, char * /*argument*/, int /*cmd*/, int /*subcmd*/) { std::ostringstream out; out << "Информация Lua:\r\n"; + out << " форматтер: " + << (lua_scripting::LuaFormatterAvailable() ? "включен (EmmyLuaCodeStyle)" : "выключен") << "\r\n"; #if defined(WITH_LUAJIT_PROTOTYPE) out << " статус: включено\r\n"; diff --git a/subprojects/emmyluacodestyle.wrap b/subprojects/emmyluacodestyle.wrap new file mode 100644 index 000000000..5847e289a --- /dev/null +++ b/subprojects/emmyluacodestyle.wrap @@ -0,0 +1,9 @@ +[wrap-git] +directory = emmyluacodestyle +url = https://github.com/CppCXY/EmmyLuaCodeStyle.git +revision = fdbaa0fa411abda0dd172eb7e360a08f4500208b +depth = 1 +patch_directory = emmyluacodestyle + +[provide] +emmyluacodestyle = emmyluacodestyle_dep diff --git a/subprojects/packagefiles/emmyluacodestyle/meson.build b/subprojects/packagefiles/emmyluacodestyle/meson.build new file mode 100644 index 000000000..41be757cf --- /dev/null +++ b/subprojects/packagefiles/emmyluacodestyle/meson.build @@ -0,0 +1,106 @@ +project('emmyluacodestyle', ['c', 'cpp'], version: '1.7.3', default_options: ['cpp_std=c++20']) + +uri_inc = include_directories('3rd/uriparser/include', '3rd/uriparser/src') +uri_sources = files( + '3rd/uriparser/src/UriCommon.c', + '3rd/uriparser/src/UriCompare.c', + '3rd/uriparser/src/UriEscape.c', + '3rd/uriparser/src/UriFile.c', + '3rd/uriparser/src/UriIp4Base.c', + '3rd/uriparser/src/UriIp4.c', + '3rd/uriparser/src/UriMemory.c', + '3rd/uriparser/src/UriNormalizeBase.c', + '3rd/uriparser/src/UriNormalize.c', + '3rd/uriparser/src/UriParseBase.c', + '3rd/uriparser/src/UriParse.c', + '3rd/uriparser/src/UriQuery.c', + '3rd/uriparser/src/UriRecompose.c', + '3rd/uriparser/src/UriResolve.c', + '3rd/uriparser/src/UriShorten.c', +) +uri_lib = static_library( + 'emmy_uri', + uri_sources, + include_directories: uri_inc, + c_args: ['-DURI_LIBRARY_BUILD', '-DURI_STATIC_BUILD'], + pic: true, +) + +emmy_inc = include_directories( + 'CodeFormatCLib/include', + 'CodeFormatCLib/src', + 'CodeFormatCore/include', + 'CodeFormatCore/src', + 'LuaParser/include', + 'LuaParser/src', + 'Util/include', + 'Util/src', + '3rd/uriparser/include', + '3rd/wildcards/include', +) +emmy_sources = files( + 'CodeFormatCLib/src/CodeFormatCLib.cpp', + 'CodeFormatCLib/src/CodeFormat.cpp', + 'CodeFormatCore/src/Config/FunctionOption.cpp', + 'CodeFormatCore/src/Config/LuaStyle.cpp', + 'CodeFormatCore/src/Config/LuaEditorConfig.cpp', + 'CodeFormatCore/src/Config/LuaDiagnosticStyle.cpp', + 'CodeFormatCore/src/Config/LanguageTranslator.cpp', + 'CodeFormatCore/src/Config/EditorconfigPattern.cpp', + 'CodeFormatCore/src/Format/FormatBuilder.cpp', + 'CodeFormatCore/src/Format/FormatState.cpp', + 'CodeFormatCore/src/Format/Analyzer/FormatAnalyzer.cpp', + 'CodeFormatCore/src/Format/Analyzer/SpaceAnalyzer.cpp', + 'CodeFormatCore/src/Format/Analyzer/IndentationAnalyzer.cpp', + 'CodeFormatCore/src/Format/Analyzer/LineBreakAnalyzer.cpp', + 'CodeFormatCore/src/Format/Analyzer/FormatDocAnalyze.cpp', + 'CodeFormatCore/src/Format/Analyzer/AlignAnalyzer.cpp', + 'CodeFormatCore/src/Format/Analyzer/TokenAnalyzer.cpp', + 'CodeFormatCore/src/Format/Analyzer/SemicolonAnalyzer.cpp', + 'CodeFormatCore/src/Format/Analyzer/FormatResolve.cpp', + 'CodeFormatCore/src/Format/Analyzer/SyntaxNodeHelper.cpp', + 'CodeFormatCore/src/RangeFormat/RangeFormatBuilder.cpp', + 'CodeFormatCore/src/TypeFormat/LuaTypeFormat.cpp', + 'CodeFormatCore/src/TypeFormat/LuaTypeFormatFeatures.cpp', + 'CodeFormatCore/src/Diagnostic/DiagnosticBuilder.cpp', + 'CodeFormatCore/src/Diagnostic/NameStyle/NameStyleChecker.cpp', + 'CodeFormatCore/src/Diagnostic/NameStyle/NameStyleRuleMatcher.cpp', + 'CodeFormatCore/src/Diagnostic/Spell/CodeSpellChecker.cpp', + 'CodeFormatCore/src/Diagnostic/Spell/Util.cpp', + 'CodeFormatCore/src/Diagnostic/CodeStyle/CodeStyleChecker.cpp', + 'LuaParser/src/Parse/LuaParser.cpp', + 'LuaParser/src/Parse/Mark.cpp', + 'LuaParser/src/Lexer/LuaLexer.cpp', + 'LuaParser/src/Lexer/LuaIdentify.cpp', + 'LuaParser/src/Lexer/TextReader.cpp', + 'LuaParser/src/Ast/LuaSyntaxNode.cpp', + 'LuaParser/src/Ast/LuaSyntaxTree.cpp', + 'LuaParser/src/Ast/LuaSyntaxMultiKind.cpp', + 'LuaParser/src/Ast/LuaSyntaxNodeKind.cpp', + 'LuaParser/src/Types/TextRange.cpp', + 'LuaParser/src/File/LuaSource.cpp', + 'LuaParser/src/exception/LuaParseException.cpp', + 'Util/src/CommandLine.cpp', + 'Util/src/StringUtil.cpp', + 'Util/src/Utf8.cpp', + 'Util/src/Url.cpp', + 'Util/src/FileFinder.cpp', + 'Util/src/SymSpell/SymSpell.cpp', + 'Util/src/SymSpell/SuggestItem.cpp', + 'Util/src/SymSpell/EditDistance.cpp', + 'Util/src/InfoTree/InfoTree.cpp', + 'Util/src/InfoTree/InfoNode.cpp', +) +emmy_lib = static_library( + 'emmyluacodestyle', + emmy_sources, + include_directories: emmy_inc, + link_with: uri_lib, + cpp_args: ['-DURI_STATIC_BUILD'], + pic: true, +) + +emmyluacodestyle_dep = declare_dependency( + include_directories: include_directories('CodeFormatCLib/include'), + link_with: emmy_lib, +) From 28baae35c0620ca12d60568942001788f22cddd8 Mon Sep 17 00:00:00 2001 From: Vladimir Khanayev Date: Sat, 18 Jul 2026 12:50:34 +0300 Subject: [PATCH 02/10] =?UTF-8?q?=D0=90=D0=B2=D1=82=D0=BE=D1=84=D0=BE?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D1=82=D1=82=D0=B5=D1=80=20=D0=BB=D1=83=D0=B0?= =?UTF-8?q?=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=20=D0=B2=20=D0=BE?= =?UTF-8?q?=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=82=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #3570 --- docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md | 2 +- src/engine/core/comm.cpp | 2 + src/engine/core/heartbeat.cpp | 2 + src/engine/olc/olc.cpp | 2 + src/engine/olc/olc.h | 3 + src/engine/scripting/dg_olc.cpp | 66 ++++++-- src/engine/scripting/dg_olc.h | 1 + src/engine/scripting/lua/lua_formatter.cpp | 171 ++++++++++++++++++++- src/engine/scripting/lua/lua_formatter.h | 13 +- 9 files changed, 248 insertions(+), 14 deletions(-) diff --git a/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md b/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md index d0c3116a3..074fcb764 100644 --- a/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md +++ b/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md @@ -40,7 +40,7 @@ script: | Lua-триггер можно писать как обычное тело функции: движок выполняет такой chunk с доступным `ctx` - контекстом владельца триггера, актера, объекта и других данных события. Старый явный формат `return function(ctx) ... end` продолжает работать. -В OLC-меню Lua-триггера доступен пункт `Автоформат Lua`. Он форматирует текущий текст встроенной библиотекой EmmyLuaCodeStyle с отступом в 2 пробела. Форматирование запускается только явно: просмотр и сохранение сами текст не меняют. Если исходник содержит синтаксическую ошибку или результат нельзя сохранить в KOI8-R, исходный OLC-буфер остается без изменений. В сборке без LuaJIT пункт не показывается; встроенный форматтер можно отключить опцией Meson `-Dlua_formatter=false`. +В OLC-меню Lua-триггера доступен пункт `Автоформат Lua`. Он форматирует текущий текст встроенной библиотекой EmmyLuaCodeStyle с отступом в 2 пробела. Форматирование запускается только явно: просмотр и сохранение сами текст не меняют. Форматтер работает в отдельном рабочем потоке, поэтому игровой цикл не останавливается; запросы от разных редакторов выполняются последовательно в порядке очереди. До получения результата текущее меню редактора ожидает завершения операции, но из него можно выйти командой `Q`. Если исходник содержит синтаксическую ошибку или результат нельзя сохранить в KOI8-R, исходный OLC-буфер остается без изменений. В сборке без LuaJIT пункт не показывается; встроенный форматтер можно отключить опцией Meson `-Dlua_formatter=false`. Номер строки в ошибке Lua соответствует номеру в `tstat -n ` и в нумерованном выводе `/n` текстового редактора OLC. Пустые строки учитываются. Пока изменения в OLC не сохранены, runtime-сообщения относятся к предыдущей сохраненной версии триггера. diff --git a/src/engine/core/comm.cpp b/src/engine/core/comm.cpp index 3bde407b2..de8d3d3a5 100644 --- a/src/engine/core/comm.cpp +++ b/src/engine/core/comm.cpp @@ -51,6 +51,7 @@ #include "gameplay/mechanics/illumination.h" #include "gameplay/clans/house.h" #include "engine/olc/olc.h" +#include "engine/scripting/lua/lua_formatter.h" #include "engine/olc/vedun/vedun.h" #include "administration/ban.h" #include "administration/proxy.h" @@ -967,6 +968,7 @@ void stop_game(ush_int port) { while (descriptor_list) close_socket(descriptor_list, true); #endif + lua_scripting::ShutdownLuaFormatter(); // должно идти после дисконекта плееров FileCRC::save(true); diff --git a/src/engine/core/heartbeat.cpp b/src/engine/core/heartbeat.cpp index e1da594af..5ebfd4bbb 100644 --- a/src/engine/core/heartbeat.cpp +++ b/src/engine/core/heartbeat.cpp @@ -25,6 +25,7 @@ #include "gameplay/abilities/timed_abilities.h" #include "gameplay/ai/mobact.h" #include "engine/scripting/dg_event.h" +#include "engine/scripting/dg_olc.h" #include "engine/scripting/lua/lua_script_engine.h" #include "gameplay/mechanics/corpse.h" #include "engine/db/global_objects.h" @@ -276,6 +277,7 @@ Heartbeat::steps_t &pulse_steps() { 0, std::make_shared(GlobalDrop::reload_tables)), Heartbeat::PulseStep("Events processing", 1, 0, std::make_shared(process_events)), + Heartbeat::PulseStep("Lua formatter results", 1, 0, std::make_shared(ProcessLuaFormatterResults)), Heartbeat::PulseStep("Lua scripting cleanup", 1, 0, std::make_shared(lua_scripting::LuaScriptEngine::HeartbeatCleanup)), Heartbeat::PulseStep("Triggers check mobile", PULSE_DG_SCRIPT, diff --git a/src/engine/olc/olc.cpp b/src/engine/olc/olc.cpp index 32e05638f..26dd824f7 100644 --- a/src/engine/olc/olc.cpp +++ b/src/engine/olc/olc.cpp @@ -103,6 +103,7 @@ olc_data::olc_data() script_mode(0), trigger_position(0), item_type(0), + lua_format_request_id(0), script(0), storage(0) { @@ -497,6 +498,7 @@ void cleanup_olc(DescriptorData *d, byte cleanup_type) { true, d->character.get(), 0, 0, kToRoom); } delete d->olc; + d->olc = nullptr; } } diff --git a/src/engine/olc/olc.h b/src/engine/olc/olc.h index b6ffeb84a..caa241cb5 100644 --- a/src/engine/olc/olc.h +++ b/src/engine/olc/olc.h @@ -11,6 +11,8 @@ #ifndef _OLC_H_ #define _OLC_H_ +#include + #include "engine/db/db.h" #include "engine/structs/structs.h" @@ -109,6 +111,7 @@ struct olc_data { int script_mode; int trigger_position; int item_type; + std::uint64_t lua_format_request_id; ObjData::triggers_list_t script; char *storage; // for holding commands etc.. }; diff --git a/src/engine/scripting/dg_olc.cpp b/src/engine/scripting/dg_olc.cpp index 30d262829..d5ee28898 100644 --- a/src/engine/scripting/dg_olc.cpp +++ b/src/engine/scripting/dg_olc.cpp @@ -71,26 +71,40 @@ bool TrigeditIsLuaTrigger(const Trigger *trig) return trig && trig->get_script_language() == TriggerScriptLanguage::Lua; } -void TrigeditFormatLua(DescriptorData *d) +bool TrigeditFormatLua(DescriptorData *d) { - std::string formatted; - std::string error; const std::string source = OLC_STORAGE(d) ? OLC_STORAGE(d) : ""; - if (!lua_scripting::FormatLuaSource(source, formatted, error)) { - SendMsgToChar(d->character.get(), "Ошибка автоформата Lua: %s\r\n", error.c_str()); + const auto request_id = lua_scripting::QueueLuaFormat(source); + if (request_id == 0) { + SendMsgToChar("Очередь форматтера Lua переполнена. Повторите позже.\r\n", d->character.get()); + return false; + } + d->olc->lua_format_request_id = request_id; + SendMsgToChar("Форматирование Lua-скрипта запущено. Дождитесь результата.\r\n", d->character.get()); + return true; +} + +void TrigeditApplyFormattedLua(DescriptorData *d, const lua_scripting::LuaFormatResult& result) +{ + if (!result.success) { + SendMsgToChar(d->character.get(), "Ошибка автоформата Lua: %s\r\n", result.error.c_str()); return; } - if (formatted.size() >= MAX_CMD_LENGTH) { + if (result.formatted.size() >= MAX_CMD_LENGTH) { SendMsgToChar(d->character.get(), "Ошибка автоформата Lua: результат слишком большой.\r\n"); return; } - RECREATE(OLC_STORAGE(d), MAX_CMD_LENGTH); - memcpy(OLC_STORAGE(d), formatted.c_str(), formatted.size() + 1); + memcpy(OLC_STORAGE(d), result.formatted.c_str(), result.formatted.size() + 1); OLC_VAL(d)++; SendMsgToChar(d->character.get(), "Lua-скрипт отформатирован.\r\n"); } +bool TrigeditLuaFormatPending(const DescriptorData *d) +{ + return d->olc && d->olc->lua_format_request_id != 0; +} + void TrigeditLoadStorageFromTrigger(Trigger *trig, char *storage) { if (!trig || !storage) { @@ -194,6 +208,28 @@ void TrigeditFprintEscapedBody(FILE *fp, const std::string &body) } // namespace +void ProcessLuaFormatterResults() +{ + lua_scripting::LuaFormatResult result; + while (lua_scripting::TryPopLuaFormatResult(result)) { + DescriptorData *d = descriptor_list; + while (d && (d->state != EConState::kTrigedit + || !d->olc + || d->olc->lua_format_request_id != result.request_id)) { + d = d->next; + } + if (!d) { + continue; + } + d->olc->lua_format_request_id = 0; + if (d->state != EConState::kTrigedit || !TrigeditIsLuaTrigger(OLC_TRIG(d))) { + continue; + } + TrigeditApplyFormattedLua(d, result); + trigedit_disp_menu(d); + } +} + inline void fprint_script(FILE *fp, const ObjData::triggers_list_t &scripts) { for (const auto vnum : scripts) { fprintf(fp, "T %d\n", vnum); @@ -321,6 +357,16 @@ void trigedit_parse(DescriptorData *d, char *arg) { switch (OLC_MODE(d)) { case TRIGEDIT_MAIN_MENU: + if (TrigeditLuaFormatPending(d)) { + if (tolower(*arg) == 'q') { + d->olc->lua_format_request_id = 0; + } else { + SendMsgToChar("Форматирование Lua-скрипта еще выполняется. Для выхода используйте Q.\r\n", + d->character.get()); + trigedit_disp_menu(d); + return; + } + } switch (tolower(*arg)) { case 'q': if (OLC_VAL(d)) // Anything been changed? @@ -397,8 +443,8 @@ void trigedit_parse(DescriptorData *d, char *arg) { SendMsgToChar("Автоформат доступен только для Lua-триггеров.\r\n", d->character.get()); } else if (!lua_scripting::LuaFormatterAvailable()) { SendMsgToChar("Форматтер Lua недоступен в этой сборке.\r\n", d->character.get()); - } else { - TrigeditFormatLua(d); + } else if (TrigeditFormatLua(d)) { + return; } trigedit_disp_menu(d); return; diff --git a/src/engine/scripting/dg_olc.h b/src/engine/scripting/dg_olc.h index 8baf96549..db6ba5515 100644 --- a/src/engine/scripting/dg_olc.h +++ b/src/engine/scripting/dg_olc.h @@ -29,6 +29,7 @@ void dg_olc_script_copy(DescriptorData *d); void dg_script_menu(DescriptorData *d); int dg_script_edit_parse(DescriptorData *d, char *arg); void indent_trigger(std::string &cmd, int *level); +void ProcessLuaFormatterResults(); // define the largest set of commands for as trigger #define MAX_CMD_LENGTH 32768 // 16k should be plenty and then some diff --git a/src/engine/scripting/lua/lua_formatter.cpp b/src/engine/scripting/lua/lua_formatter.cpp index c63499804..d098c17b5 100644 --- a/src/engine/scripting/lua/lua_formatter.cpp +++ b/src/engine/scripting/lua/lua_formatter.cpp @@ -4,8 +4,12 @@ #include "CodeFormatCLib.h" #include "utils/utils_encoding.h" +#include +#include #include #include +#include +#include #include #endif @@ -19,11 +23,13 @@ bool LuaFormatterAvailable() { #endif } +namespace { + +#if defined(WITH_LUA_FORMATTER) bool FormatLuaSource(const std::string& source, std::string& formatted, std::string& error) { formatted.clear(); error.clear(); -#if defined(WITH_LUA_FORMATTER) try { if (source.find('\0') != std::string::npos) { error = "Исходный Lua-код содержит нулевой байт"; @@ -74,13 +80,174 @@ bool FormatLuaSource(const std::string& source, std::string& formatted, std::str error = "внутренняя ошибка форматтера"; return false; } +} + +template +class SpscQueue { +public: + static_assert(Capacity > 1); + static_assert(std::atomic_size_t::is_always_lock_free); + + bool Push(T&& value) { + const auto tail = m_tail.load(std::memory_order_relaxed); + const auto next = (tail + 1) % Capacity; + if (next == m_head.load(std::memory_order_acquire)) { + return false; + } + m_slots[tail] = std::move(value); + m_tail.store(next, std::memory_order_release); + return true; + } + + bool Pop(T& value) { + const auto head = m_head.load(std::memory_order_relaxed); + if (head == m_tail.load(std::memory_order_acquire)) { + return false; + } + value = std::move(m_slots[head]); + m_head.store((head + 1) % Capacity, std::memory_order_release); + return true; + } + + bool Empty() const { + return m_head.load(std::memory_order_relaxed) == m_tail.load(std::memory_order_acquire); + } + + bool Full() const { + const auto tail = m_tail.load(std::memory_order_relaxed); + return (tail + 1) % Capacity == m_head.load(std::memory_order_acquire); + } + +private: + std::array m_slots; + alignas(64) std::atomic_size_t m_head{0}; + alignas(64) std::atomic_size_t m_tail{0}; +}; + +struct LuaFormatJob { + std::uint64_t request_id{}; + std::string source; +}; + +class LuaFormatterWorker { +public: + LuaFormatterWorker() : m_thread(&LuaFormatterWorker::Run, this) {} + + ~LuaFormatterWorker() { + Shutdown(); + } + + void Shutdown() { + if (m_stopping.exchange(true, std::memory_order_acq_rel)) { + return; + } + m_jobs_changed.fetch_add(1, std::memory_order_release); + m_jobs_changed.notify_one(); + m_results_changed.fetch_add(1, std::memory_order_release); + m_results_changed.notify_one(); + if (m_thread.joinable()) { + m_thread.join(); + } + } + + std::uint64_t Submit(std::string source) { + if (m_stopping.load(std::memory_order_acquire)) { + return 0; + } + LuaFormatJob job; + const auto request_id = m_next_request_id++; + job.request_id = request_id; + job.source = std::move(source); + if (!m_jobs.Push(std::move(job))) { + return 0; + } + m_jobs_changed.fetch_add(1, std::memory_order_release); + m_jobs_changed.notify_one(); + return request_id; + } + + bool TryPop(LuaFormatResult& result) { + if (!m_results.Pop(result)) { + return false; + } + m_results_changed.fetch_add(1, std::memory_order_release); + m_results_changed.notify_one(); + return true; + } + +private: + static_assert(std::atomic_bool::is_always_lock_free); + static_assert(std::atomic_uint32_t::is_always_lock_free); + static constexpr std::size_t kQueueCapacity = 64; + + void Run() { + while (!m_stopping.load(std::memory_order_acquire)) { + LuaFormatJob job; + while (!m_jobs.Pop(job)) { + const auto observed = m_jobs_changed.load(std::memory_order_acquire); + if (m_stopping.load(std::memory_order_acquire)) { + return; + } + if (m_jobs.Empty()) { + m_jobs_changed.wait(observed, std::memory_order_acquire); + } + } + LuaFormatResult result; + result.request_id = job.request_id; + result.success = FormatLuaSource(job.source, result.formatted, result.error); + while (!m_results.Push(std::move(result))) { + const auto observed = m_results_changed.load(std::memory_order_acquire); + if (m_stopping.load(std::memory_order_acquire)) { + return; + } + if (m_results.Full()) { + m_results_changed.wait(observed, std::memory_order_acquire); + } + } + } + } + + SpscQueue m_jobs; + SpscQueue m_results; + std::atomic_bool m_stopping{false}; + std::atomic_uint32_t m_jobs_changed{0}; + std::atomic_uint32_t m_results_changed{0}; + std::thread m_thread; + std::uint64_t m_next_request_id{1}; +}; + +LuaFormatterWorker& FormatterWorker() { + static LuaFormatterWorker worker; + return worker; +} +#endif + +} // namespace + +std::uint64_t QueueLuaFormat(std::string source) { +#if defined(WITH_LUA_FORMATTER) + return FormatterWorker().Submit(std::move(source)); #else (void)source; - error = "Форматтер Lua недоступен в этой сборке"; + return 0; +#endif +} + +bool TryPopLuaFormatResult(LuaFormatResult& result) { +#if defined(WITH_LUA_FORMATTER) + return FormatterWorker().TryPop(result); +#else + (void)result; return false; #endif } +void ShutdownLuaFormatter() { +#if defined(WITH_LUA_FORMATTER) + FormatterWorker().Shutdown(); +#endif +} + } // namespace lua_scripting // vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/scripting/lua/lua_formatter.h b/src/engine/scripting/lua/lua_formatter.h index 9fedd0fc4..4daf430f4 100644 --- a/src/engine/scripting/lua/lua_formatter.h +++ b/src/engine/scripting/lua/lua_formatter.h @@ -1,12 +1,23 @@ #ifndef BYLINS_SRC_ENGINE_SCRIPTING_LUA_LUA_FORMATTER_H_ #define BYLINS_SRC_ENGINE_SCRIPTING_LUA_LUA_FORMATTER_H_ +#include #include namespace lua_scripting { +struct LuaFormatResult { + std::uint64_t request_id{}; + bool success{}; + std::string formatted; + std::string error; +}; + bool LuaFormatterAvailable(); -bool FormatLuaSource(const std::string& source, std::string& formatted, std::string& error); +// Вызываются только из основного игрового потока. +std::uint64_t QueueLuaFormat(std::string source); +bool TryPopLuaFormatResult(LuaFormatResult& result); +void ShutdownLuaFormatter(); } // namespace lua_scripting From b61caa61d83f53589c63e076d7d6dbda4ee9c528 Mon Sep 17 00:00:00 2001 From: Vladimir Khanayev Date: Sat, 18 Jul 2026 16:01:02 +0300 Subject: [PATCH 03/10] Shutdown guard for lua formatter Issue #3570 --- src/engine/core/comm.cpp | 11 ++++++++--- src/engine/scripting/lua/lua_formatter.cpp | 21 +++++++++++++++------ src/engine/scripting/lua/lua_formatter.h | 11 ++++++++++- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/engine/core/comm.cpp b/src/engine/core/comm.cpp index de8d3d3a5..377ffb71f 100644 --- a/src/engine/core/comm.cpp +++ b/src/engine/core/comm.cpp @@ -911,10 +911,16 @@ void stop_game(ush_int port) { } } #endif - game_loop(epoll, mother_desc); + { + lua_scripting::LuaFormatterShutdownGuard lua_formatter_shutdown_guard; + game_loop(epoll, mother_desc); + } #else log("Polling using select()."); - game_loop(mother_desc); + { + lua_scripting::LuaFormatterShutdownGuard lua_formatter_shutdown_guard; + game_loop(mother_desc); + } #endif // Shutdown OTEL providers to flush remaining telemetry @@ -968,7 +974,6 @@ void stop_game(ush_int port) { while (descriptor_list) close_socket(descriptor_list, true); #endif - lua_scripting::ShutdownLuaFormatter(); // должно идти после дисконекта плееров FileCRC::save(true); diff --git a/src/engine/scripting/lua/lua_formatter.cpp b/src/engine/scripting/lua/lua_formatter.cpp index d098c17b5..33387dae2 100644 --- a/src/engine/scripting/lua/lua_formatter.cpp +++ b/src/engine/scripting/lua/lua_formatter.cpp @@ -216,17 +216,25 @@ class LuaFormatterWorker { std::uint64_t m_next_request_id{1}; }; -LuaFormatterWorker& FormatterWorker() { - static LuaFormatterWorker worker; +std::unique_ptr& FormatterWorkerStorage() { + static std::unique_ptr worker; return worker; } + +LuaFormatterWorker& EnsureFormatterWorker() { + auto& worker = FormatterWorkerStorage(); + if (!worker) { + worker = std::make_unique(); + } + return *worker; +} #endif } // namespace std::uint64_t QueueLuaFormat(std::string source) { #if defined(WITH_LUA_FORMATTER) - return FormatterWorker().Submit(std::move(source)); + return EnsureFormatterWorker().Submit(std::move(source)); #else (void)source; return 0; @@ -235,16 +243,17 @@ std::uint64_t QueueLuaFormat(std::string source) { bool TryPopLuaFormatResult(LuaFormatResult& result) { #if defined(WITH_LUA_FORMATTER) - return FormatterWorker().TryPop(result); + const auto& worker = FormatterWorkerStorage(); + return worker && worker->TryPop(result); #else (void)result; return false; #endif } -void ShutdownLuaFormatter() { +LuaFormatterShutdownGuard::~LuaFormatterShutdownGuard() { #if defined(WITH_LUA_FORMATTER) - FormatterWorker().Shutdown(); + FormatterWorkerStorage().reset(); #endif } diff --git a/src/engine/scripting/lua/lua_formatter.h b/src/engine/scripting/lua/lua_formatter.h index 4daf430f4..eb3c37dd6 100644 --- a/src/engine/scripting/lua/lua_formatter.h +++ b/src/engine/scripting/lua/lua_formatter.h @@ -13,11 +13,20 @@ struct LuaFormatResult { std::string error; }; +// Единственный guard на время работы сервера. Деструктор останавливает и +// уничтожает лениво созданный worker форматтера. +class LuaFormatterShutdownGuard { +public: + LuaFormatterShutdownGuard() = default; + ~LuaFormatterShutdownGuard(); + LuaFormatterShutdownGuard(const LuaFormatterShutdownGuard&) = delete; + LuaFormatterShutdownGuard& operator=(const LuaFormatterShutdownGuard&) = delete; +}; + bool LuaFormatterAvailable(); // Вызываются только из основного игрового потока. std::uint64_t QueueLuaFormat(std::string source); bool TryPopLuaFormatResult(LuaFormatResult& result); -void ShutdownLuaFormatter(); } // namespace lua_scripting From 6d15b633a7e48b589f29d2bcd8bef46344f8e837 Mon Sep 17 00:00:00 2001 From: Vladimir Khanayev Date: Sat, 18 Jul 2026 17:22:24 +0300 Subject: [PATCH 04/10] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=B3=D0=B0=D1=80=D0=B4=20=D0=B4=D0=BB=D1=8F=20=D1=83?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BB=D1=83=D0=B0?= =?UTF-8?q?=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=D1=82=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #3570 --- src/engine/scripting/lua/lua_formatter.cpp | 80 ++++++++++++++++++---- src/engine/scripting/lua/lua_formatter.h | 15 +++- 2 files changed, 79 insertions(+), 16 deletions(-) diff --git a/src/engine/scripting/lua/lua_formatter.cpp b/src/engine/scripting/lua/lua_formatter.cpp index 33387dae2..9fdf1a9a8 100644 --- a/src/engine/scripting/lua/lua_formatter.cpp +++ b/src/engine/scripting/lua/lua_formatter.cpp @@ -1,5 +1,7 @@ #include "engine/scripting/lua/lua_formatter.h" +#include + #if defined(WITH_LUA_FORMATTER) #include "CodeFormatCLib.h" #include "utils/utils_encoding.h" @@ -216,25 +218,74 @@ class LuaFormatterWorker { std::uint64_t m_next_request_id{1}; }; -std::unique_ptr& FormatterWorkerStorage() { - static std::unique_ptr worker; - return worker; -} +#endif + +} // namespace -LuaFormatterWorker& EnsureFormatterWorker() { - auto& worker = FormatterWorkerStorage(); - if (!worker) { - worker = std::make_unique(); +namespace { + +struct FormatterRuntime { +#if defined(WITH_LUA_FORMATTER) + LuaFormatterWorker& EnsureWorker() { + if (!worker) { + worker = std::make_unique(); + } + return *worker; } - return *worker; + + std::unique_ptr worker; +#endif +}; + +void DeleteFormatterRuntime(void* runtime) { + delete static_cast(runtime); +} + +#if defined(WITH_LUA_FORMATTER) +LuaFormatterShutdownGuard*& ActiveFormatterGuard() { + static LuaFormatterShutdownGuard* guard = nullptr; + return guard; } #endif } // namespace +LuaFormatterShutdownGuard::LuaFormatterShutdownGuard() +#if defined(WITH_LUA_FORMATTER) + : m_runtime(new FormatterRuntime, DeleteFormatterRuntime) { + if (ActiveFormatterGuard() != nullptr) { + std::abort(); + } + ActiveFormatterGuard() = this; +#else + : m_runtime(nullptr, DeleteFormatterRuntime) { +#endif +} + +std::uint64_t LuaFormatterShutdownGuard::Submit(std::string source) { +#if defined(WITH_LUA_FORMATTER) + return static_cast(m_runtime.get())->EnsureWorker().Submit(std::move(source)); +#else + (void)source; + return 0; +#endif +} + +bool LuaFormatterShutdownGuard::TryPop(LuaFormatResult& result) { +#if defined(WITH_LUA_FORMATTER) + auto* runtime = static_cast(m_runtime.get()); + auto* worker = runtime ? runtime->worker.get() : nullptr; + return worker && worker->TryPop(result); +#else + (void)result; + return false; +#endif +} + std::uint64_t QueueLuaFormat(std::string source) { #if defined(WITH_LUA_FORMATTER) - return EnsureFormatterWorker().Submit(std::move(source)); + auto* guard = ActiveFormatterGuard(); + return guard ? guard->Submit(std::move(source)) : 0; #else (void)source; return 0; @@ -243,8 +294,8 @@ std::uint64_t QueueLuaFormat(std::string source) { bool TryPopLuaFormatResult(LuaFormatResult& result) { #if defined(WITH_LUA_FORMATTER) - const auto& worker = FormatterWorkerStorage(); - return worker && worker->TryPop(result); + auto* guard = ActiveFormatterGuard(); + return guard && guard->TryPop(result); #else (void)result; return false; @@ -253,7 +304,10 @@ bool TryPopLuaFormatResult(LuaFormatResult& result) { LuaFormatterShutdownGuard::~LuaFormatterShutdownGuard() { #if defined(WITH_LUA_FORMATTER) - FormatterWorkerStorage().reset(); + if (ActiveFormatterGuard() != this) { + std::abort(); + } + ActiveFormatterGuard() = nullptr; #endif } diff --git a/src/engine/scripting/lua/lua_formatter.h b/src/engine/scripting/lua/lua_formatter.h index eb3c37dd6..9aca41365 100644 --- a/src/engine/scripting/lua/lua_formatter.h +++ b/src/engine/scripting/lua/lua_formatter.h @@ -2,6 +2,7 @@ #define BYLINS_SRC_ENGINE_SCRIPTING_LUA_LUA_FORMATTER_H_ #include +#include #include namespace lua_scripting { @@ -13,14 +14,22 @@ struct LuaFormatResult { std::string error; }; -// Единственный guard на время работы сервера. Деструктор останавливает и -// уничтожает лениво созданный worker форматтера. +// Одновременно может существовать только один guard, охватывающий game_loop. +// Деструктор останавливает и уничтожает лениво созданный worker форматтера. class LuaFormatterShutdownGuard { public: - LuaFormatterShutdownGuard() = default; + LuaFormatterShutdownGuard(); ~LuaFormatterShutdownGuard(); LuaFormatterShutdownGuard(const LuaFormatterShutdownGuard&) = delete; LuaFormatterShutdownGuard& operator=(const LuaFormatterShutdownGuard&) = delete; + +private: + using RuntimeDeleter = void (*)(void*); + std::unique_ptr m_runtime; + std::uint64_t Submit(std::string source); + bool TryPop(LuaFormatResult& result); + friend std::uint64_t QueueLuaFormat(std::string source); + friend bool TryPopLuaFormatResult(LuaFormatResult& result); }; bool LuaFormatterAvailable(); From 13782fee7bf80973812bcb7a6bad56aa2c5a8f2a Mon Sep 17 00:00:00 2001 From: Vladimir Khanayev Date: Mon, 20 Jul 2026 18:52:51 +0300 Subject: [PATCH 05/10] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=81=D0=B8=D0=BD=D1=82=D0=B0=D0=BA=D1=81=D0=B8=D1=81?= =?UTF-8?q?=D0=B0=20=D0=B2=20=D0=9E=D0=9B=D0=A6=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=9B=D0=A3=D0=90=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/engine/scripting/dg_olc.cpp | 2 +- src/engine/scripting/lua/lua_formatter.cpp | 53 +++++++++++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/engine/scripting/dg_olc.cpp b/src/engine/scripting/dg_olc.cpp index 22c2f08f4..0aa357fc4 100644 --- a/src/engine/scripting/dg_olc.cpp +++ b/src/engine/scripting/dg_olc.cpp @@ -320,7 +320,7 @@ void trigedit_disp_menu(DescriptorData *d) { out << "&g8)&n Язык скрипта : &y" << (TrigeditIsLuaTrigger(trig) ? "Lua" : "DG") << "&n\r\n"; #endif if (TrigeditIsLuaTrigger(trig) && lua_scripting::LuaFormatterAvailable()) { - out << "&g9)&n Автоформат Lua\r\n"; + out << "&g9)&n Проверить синтаксис и отформатировать Lua\r\n"; } out << "&gQ)&n Завершить редактирование\r\n" "Введите Выбранное :"; SendMsgToChar(out.str(), d->character.get()); diff --git a/src/engine/scripting/lua/lua_formatter.cpp b/src/engine/scripting/lua/lua_formatter.cpp index 9fdf1a9a8..f225ff5c6 100644 --- a/src/engine/scripting/lua/lua_formatter.cpp +++ b/src/engine/scripting/lua/lua_formatter.cpp @@ -4,8 +4,14 @@ #if defined(WITH_LUA_FORMATTER) #include "CodeFormatCLib.h" +#include "utils/logger.h" #include "utils/utils_encoding.h" +extern "C" { +#include +#include +} + #include #include #include @@ -28,7 +34,31 @@ bool LuaFormatterAvailable() { namespace { #if defined(WITH_LUA_FORMATTER) -bool FormatLuaSource(const std::string& source, std::string& formatted, std::string& error) { +bool ValidateLuaSource(lua_State* lua, const std::string& source, std::string& error) { + if (!lua) { + error = "Проверка синтаксиса LuaJIT недоступна"; + return false; + } + + lua_settop(lua, 0); + const auto status = luaL_loadbuffer(lua, source.data(), source.size(), "=OLC Lua trigger"); + if (status != LUA_OK) { + size_t error_size = 0; + const char* lua_error = lua_tolstring(lua, -1, &error_size); + if (lua_error) { + error.assign(lua_error, error_size); + } else { + error = "LuaJIT отклонил исходный код"; + } + lua_settop(lua, 0); + return false; + } + + lua_settop(lua, 0); + return true; +} + +bool FormatLuaSource(lua_State* lua, const std::string& source, std::string& formatted, std::string& error) { formatted.clear(); error.clear(); @@ -37,6 +67,9 @@ bool FormatLuaSource(const std::string& source, std::string& formatted, std::str error = "Исходный Lua-код содержит нулевой байт"; return false; } + if (!ValidateLuaSource(lua, source, error)) { + return false; + } std::vector koi_input(source.begin(), source.end()); koi_input.push_back('\0'); @@ -72,6 +105,12 @@ bool FormatLuaSource(const std::string& source, std::string& formatted, std::str error = "Результат форматирования Lua нельзя представить в KOI8-R"; return false; } + std::string syntax_error; + if (!ValidateLuaSource(lua, formatted, syntax_error)) { + formatted.clear(); + error = "Форматтер создал некорректный Lua-код: " + syntax_error; + return false; + } return true; } catch (const std::exception&) { formatted.clear(); @@ -183,6 +222,9 @@ class LuaFormatterWorker { static constexpr std::size_t kQueueCapacity = 64; void Run() { + using LuaStatePtr = std::unique_ptr; + LuaStatePtr lua(nullptr, lua_close); + bool lua_state_failure_logged = false; while (!m_stopping.load(std::memory_order_acquire)) { LuaFormatJob job; while (!m_jobs.Pop(job)) { @@ -194,9 +236,16 @@ class LuaFormatterWorker { m_jobs_changed.wait(observed, std::memory_order_acquire); } } + if (!lua) { + lua.reset(luaL_newstate()); + if (!lua && !lua_state_failure_logged) { + log("SYSERR: lua_formatter: luaL_newstate failed"); + lua_state_failure_logged = true; + } + } LuaFormatResult result; result.request_id = job.request_id; - result.success = FormatLuaSource(job.source, result.formatted, result.error); + result.success = FormatLuaSource(lua.get(), job.source, result.formatted, result.error); while (!m_results.Push(std::move(result))) { const auto observed = m_results_changed.load(std::memory_order_acquire); if (m_stopping.load(std::memory_order_acquire)) { From 7de64a319a89c6ad5952b24b9262a52646f94839 Mon Sep 17 00:00:00 2001 From: Daniil-Gusev Date: Tue, 21 Jul 2026 12:53:04 +0300 Subject: [PATCH 06/10] disable unity build for lua formatter --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 310311ae7..a5a4e0a8d 100644 --- a/meson.build +++ b/meson.build @@ -243,7 +243,7 @@ if luajit_opt != 'disabled' emmy_dep = dependency( 'emmyluacodestyle', fallback: ['emmyluacodestyle', 'emmyluacodestyle_dep'], - default_options: ['warning_level=0'], + default_options: ['warning_level=0', 'unity=off'], ) dependencies += emmy_dep project_args += '-DWITH_LUA_FORMATTER' From c5832bf4bcdafd4bfd985282c5d9e9caf8bb0a00 Mon Sep 17 00:00:00 2001 From: Vladimir Khanayev Date: Tue, 21 Jul 2026 17:18:53 +0300 Subject: [PATCH 07/10] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=B0=D0=B2=D1=82=D0=BE=D1=84=D0=BE=D1=80=D0=BC?= =?UTF-8?q?=D0=B0=D1=82=20=D0=9B=D0=A3=D0=90=20=D1=81=D0=BA=D1=80=D0=B8?= =?UTF-8?q?=D0=BF=D1=82=D0=BE=D0=B2=20=D0=BF=D1=80=D0=B8=20=D0=B2=D1=8B?= =?UTF-8?q?=D1=85=D0=BE=D0=B4=D0=B5=20=D0=B8=D0=B7=20=D0=9E=D0=9B=D0=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: #3570 --- docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md | 2 +- src/engine/olc/olc.cpp | 2 + src/engine/olc/olc.h | 1 + src/engine/scripting/dg_olc.cpp | 103 ++++++++++++++++++++++++---- src/engine/scripting/dg_olc.h | 1 + 5 files changed, 94 insertions(+), 15 deletions(-) diff --git a/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md b/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md index 074fcb764..efbe53dd2 100644 --- a/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md +++ b/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md @@ -40,7 +40,7 @@ script: | Lua-триггер можно писать как обычное тело функции: движок выполняет такой chunk с доступным `ctx` - контекстом владельца триггера, актера, объекта и других данных события. Старый явный формат `return function(ctx) ... end` продолжает работать. -В OLC-меню Lua-триггера доступен пункт `Автоформат Lua`. Он форматирует текущий текст встроенной библиотекой EmmyLuaCodeStyle с отступом в 2 пробела. Форматирование запускается только явно: просмотр и сохранение сами текст не меняют. Форматтер работает в отдельном рабочем потоке, поэтому игровой цикл не останавливается; запросы от разных редакторов выполняются последовательно в порядке очереди. До получения результата текущее меню редактора ожидает завершения операции, но из него можно выйти командой `Q`. Если исходник содержит синтаксическую ошибку или результат нельзя сохранить в KOI8-R, исходный OLC-буфер остается без изменений. В сборке без LuaJIT пункт не показывается; встроенный форматтер можно отключить опцией Meson `-Dlua_formatter=false`. +В OLC-меню Lua-триггера доступен пункт `Проверить синтаксис и отформатировать Lua`. Он форматирует текущий текст встроенной библиотекой EmmyLuaCodeStyle с отступом в 2 пробела. При выходе из OLC с сохранением Lua-код проверяется и форматируется автоматически. Если проверка или форматирование завершается ошибкой, OLC показывает сообщение об ошибке, сохраняет исходную версию кода без форматирования и закрывает редактор. Форматтер работает в отдельном рабочем потоке, поэтому игровой цикл не останавливается; запросы от разных редакторов выполняются последовательно в порядке очереди. Во время автоматического форматирования команда `Q` отменяет форматирование, сохраняет исходный код и закрывает редактор. При разрыве соединения после подтверждения сохранения исходный код также сохраняется. При ручном запуске форматирования из меню ожидание можно отменить командой `Q` без сохранения. В сборке без LuaJIT пункт не показывается; встроенный форматтер можно отключить опцией Meson `-Dlua_formatter=false`. Номер строки в ошибке Lua соответствует номеру в `tstat -n ` и в нумерованном выводе `/n` текстового редактора OLC. Пустые строки учитываются. Пока изменения в OLC не сохранены, runtime-сообщения относятся к предыдущей сохраненной версии триггера. diff --git a/src/engine/olc/olc.cpp b/src/engine/olc/olc.cpp index 3b89b3f55..c7bbfc076 100644 --- a/src/engine/olc/olc.cpp +++ b/src/engine/olc/olc.cpp @@ -104,6 +104,7 @@ olc_data::olc_data() trigger_position(0), item_type(0), lua_format_request_id(0), + lua_format_save_on_completion(false), script(0), storage(0) { @@ -474,6 +475,7 @@ void strip_string(char *buffer) { */ void cleanup_olc(DescriptorData *d, byte cleanup_type) { if (d->olc) { + TrigeditSavePendingLuaOnCleanup(d); // Освободить редактируемый триггер if (OLC_TRIG(d)) { diff --git a/src/engine/olc/olc.h b/src/engine/olc/olc.h index 195c3a41d..78f3dd29e 100644 --- a/src/engine/olc/olc.h +++ b/src/engine/olc/olc.h @@ -112,6 +112,7 @@ struct olc_data { int trigger_position; int item_type; std::uint64_t lua_format_request_id; + bool lua_format_save_on_completion; ObjData::triggers_list_t script; char *storage; // for holding commands etc.. }; diff --git a/src/engine/scripting/dg_olc.cpp b/src/engine/scripting/dg_olc.cpp index 0aa357fc4..0c041cd78 100644 --- a/src/engine/scripting/dg_olc.cpp +++ b/src/engine/scripting/dg_olc.cpp @@ -74,7 +74,7 @@ bool TrigeditIsLuaTrigger(const Trigger *trig) return trig && trig->get_script_language() == TriggerScriptLanguage::Lua; } -bool TrigeditFormatLua(DescriptorData *d) +bool TrigeditQueueLuaFormat(DescriptorData *d, bool save_on_completion) { const std::string source = OLC_STORAGE(d) ? OLC_STORAGE(d) : ""; const auto request_id = lua_scripting::QueueLuaFormat(source); @@ -83,24 +83,39 @@ bool TrigeditFormatLua(DescriptorData *d) return false; } d->olc->lua_format_request_id = request_id; - SendMsgToChar("Форматирование Lua-скрипта запущено. Дождитесь результата.\r\n", d->character.get()); + d->olc->lua_format_save_on_completion = save_on_completion; + SendMsgToChar(save_on_completion + ? "Проверка и форматирование Lua-скрипта перед сохранением запущены. Дождитесь результата.\r\n" + : "Форматирование Lua-скрипта запущено. Дождитесь результата.\r\n", + d->character.get()); return true; } -void TrigeditApplyFormattedLua(DescriptorData *d, const lua_scripting::LuaFormatResult& result) +bool TrigeditFormatLua(DescriptorData *d) +{ + return TrigeditQueueLuaFormat(d, false); +} + +bool TrigeditFormatLuaBeforeSave(DescriptorData *d) +{ + return TrigeditQueueLuaFormat(d, true); +} + +bool TrigeditApplyFormattedLua(DescriptorData *d, const lua_scripting::LuaFormatResult& result) { if (!result.success) { - SendMsgToChar(d->character.get(), "Ошибка автоформата Lua: %s\r\n", result.error.c_str()); - return; + SendMsgToChar(d->character.get(), "Ошибка проверки/форматирования Lua: %s\r\n", result.error.c_str()); + return false; } if (result.formatted.size() >= MAX_CMD_LENGTH) { - SendMsgToChar(d->character.get(), "Ошибка автоформата Lua: результат слишком большой.\r\n"); - return; + SendMsgToChar(d->character.get(), "Ошибка проверки/форматирования Lua: результат слишком большой.\r\n"); + return false; } RECREATE(OLC_STORAGE(d), MAX_CMD_LENGTH); memcpy(OLC_STORAGE(d), result.formatted.c_str(), result.formatted.size() + 1); OLC_VAL(d)++; SendMsgToChar(d->character.get(), "Lua-скрипт отформатирован.\r\n"); + return true; } bool TrigeditLuaFormatPending(const DescriptorData *d) @@ -209,8 +224,32 @@ void TrigeditFprintEscapedBody(FILE *fp, const std::string &body) fprintf(fp, "~\n"); } +void TrigeditCommitSave(DescriptorData *d) +{ + trigedit_save(d); + snprintf(buf, sizeof(buf), "OLC: %s edits trigger %d", GET_NAME(d->character), OLC_NUM(d)); + olc_log("%s end trig %d", GET_NAME(d->character), OLC_NUM(d)); + mudlog(buf, NRM, MAX(kLvlBuilder, GET_INVIS_LEV(d->character)), SYSLOG, true); +} + +void TrigeditSaveAndExit(DescriptorData *d) +{ + TrigeditCommitSave(d); + cleanup_olc(d, CLEANUP_ALL); +} + } // namespace +void TrigeditSavePendingLuaOnCleanup(DescriptorData *d) +{ + if (!d || !d->olc || !d->olc->lua_format_save_on_completion || !OLC_TRIG(d)) { + return; + } + d->olc->lua_format_request_id = 0; + d->olc->lua_format_save_on_completion = false; + TrigeditCommitSave(d); +} + void ProcessLuaFormatterResults() { lua_scripting::LuaFormatResult result; @@ -225,10 +264,26 @@ void ProcessLuaFormatterResults() continue; } d->olc->lua_format_request_id = 0; - if (d->state != EConState::kTrigedit || !TrigeditIsLuaTrigger(OLC_TRIG(d))) { + const bool save_on_completion = d->olc->lua_format_save_on_completion; + d->olc->lua_format_save_on_completion = false; + if (!TrigeditIsLuaTrigger(OLC_TRIG(d))) { + if (save_on_completion) { + SendMsgToChar("Форматирование не применено. Исходный код триггера сохраняется.\r\n", + d->character.get()); + TrigeditSaveAndExit(d); + } else { + trigedit_disp_menu(d); + } + continue; + } + const bool formatted = TrigeditApplyFormattedLua(d, result); + if (save_on_completion) { + if (!formatted) { + SendMsgToChar("Исходный Lua-код сохраняется без форматирования.\r\n", d->character.get()); + } + TrigeditSaveAndExit(d); continue; } - TrigeditApplyFormattedLua(d, result); trigedit_disp_menu(d); } } @@ -358,11 +413,27 @@ void trigedit_disp_types(DescriptorData *d) { void trigedit_parse(DescriptorData *d, char *arg) { int i = 0; + if (TrigeditLuaFormatPending(d) && d->olc->lua_format_save_on_completion) { + if (tolower(*arg) == 'q') { + d->olc->lua_format_request_id = 0; + d->olc->lua_format_save_on_completion = false; + SendMsgToChar("Форматирование отменено. Исходный Lua-код сохраняется без форматирования.\r\n", + d->character.get()); + TrigeditSaveAndExit(d); + } else { + SendMsgToChar("Проверка и форматирование Lua-скрипта перед сохранением еще выполняются. " + "Для отмены форматирования и сохранения исходного кода используйте Q.\r\n", + d->character.get()); + } + return; + } + switch (OLC_MODE(d)) { case TRIGEDIT_MAIN_MENU: if (TrigeditLuaFormatPending(d)) { if (tolower(*arg) == 'q') { d->olc->lua_format_request_id = 0; + d->olc->lua_format_save_on_completion = false; } else { SendMsgToChar("Форматирование Lua-скрипта еще выполняется. Для выхода используйте Q.\r\n", d->character.get()); @@ -459,11 +530,15 @@ void trigedit_parse(DescriptorData *d, char *arg) { case TRIGEDIT_CONFIRM_SAVESTRING: switch (tolower(*arg)) { - case 'y': trigedit_save(d); - snprintf(buf, sizeof(buf), "OLC: %s edits trigger %d", GET_NAME(d->character), OLC_NUM(d)); - olc_log("%s end trig %d", GET_NAME(d->character), OLC_NUM(d)); - mudlog(buf, NRM, MAX(kLvlBuilder, GET_INVIS_LEV(d->character)), SYSLOG, true); - // fall through + case 'y': + if (TrigeditIsLuaTrigger(OLC_TRIG(d)) && lua_scripting::LuaFormatterAvailable()) { + if (TrigeditFormatLuaBeforeSave(d)) { + return; + } + SendMsgToChar("Исходный Lua-код сохраняется без форматирования.\r\n", d->character.get()); + } + TrigeditSaveAndExit(d); + return; case 'n': cleanup_olc(d, CLEANUP_ALL); return; diff --git a/src/engine/scripting/dg_olc.h b/src/engine/scripting/dg_olc.h index db6ba5515..dd1ae53ea 100644 --- a/src/engine/scripting/dg_olc.h +++ b/src/engine/scripting/dg_olc.h @@ -30,6 +30,7 @@ void dg_script_menu(DescriptorData *d); int dg_script_edit_parse(DescriptorData *d, char *arg); void indent_trigger(std::string &cmd, int *level); void ProcessLuaFormatterResults(); +void TrigeditSavePendingLuaOnCleanup(DescriptorData *d); // define the largest set of commands for as trigger #define MAX_CMD_LENGTH 32768 // 16k should be plenty and then some From dffe8046d326e22edd613d8f1996a2d1ae326648 Mon Sep 17 00:00:00 2001 From: Vladimir Khanayev Date: Tue, 21 Jul 2026 17:25:38 +0300 Subject: [PATCH 08/10] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=86=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #3570 --- README.md | 38 +++++++++++++++++++++++++++++ docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c5ee5af40..56dec6e46 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ * [Windows (MSVC / Clang)](#windows-msvc--clang) * [Windows (MinGW / MSYS2)](#windows-mingw--msys2) * [Cross-компиляция под Windows с Linux (MinGW)](#cross-компиляция-под-windows-с-linux-mingw) + * [LuaJIT и форматирование Lua](#luajit-и-форматирование-lua) * [Unity-сборка](#unity-сборка) * [Пересборка](#пересборка) * [Тесты](#тесты) @@ -140,6 +141,40 @@ meson setup build --cross-file toolchains/windows-mingw64-cross.txt -Dbuild_prof meson compile -C build ``` +### LuaJIT и форматирование Lua + +Для сборки движка с Lua-триггерами и встроенным форматтером EmmyLuaCodeStyle используйте встроенные Meson-зависимости: + +```bash +meson setup build_lua \ + -Dbuild_profile=release \ + -Dbuild_tests=false \ + -Dluajit=builtin \ + -Dlua_formatter=true +meson compile -C build_lua +``` + +`-Dluajit=builtin` включает LuaJIT и интеграцию через sol2. При включенном LuaJIT опция `lua_formatter` по умолчанию равна `true`; она указана в примере явно, чтобы конфигурация была очевидна. Meson использует subprojects из репозитория или загружает недостающие исходники через wrap-файлы. + +Если LuaJIT установлен в системе и доступен через `pkg-config`, вместо встроенной версии можно использовать: + +```bash +meson setup build_lua \ + -Dbuild_profile=release \ + -Dbuild_tests=false \ + -Dluajit=system \ + -Dlua_formatter=true +``` + +Чтобы оставить Lua-триггеры, но отключить форматтер: + +```bash +meson configure build_lua -Dlua_formatter=false +meson compile -C build_lua +``` + +В итоговой сводке конфигурации Meson строки `LuaJIT` и `Lua formatter` показывают фактически выбранные режимы. Форматтер доступен в OLC только в сборке с LuaJIT. + --- ## Unity-сборка @@ -257,8 +292,10 @@ docker stop mud | `nocrypt` | boolean | `false` | Отключить использование `crypt()` | | `with_asan` | boolean | `false` | Address Sanitizer | | `use_pch` | boolean | `true` | Предкомпилированные заголовки | +| `lua_formatter` | boolean | `true` | Встроенный EmmyLuaCodeStyle; используется только при включенном LuaJIT | | `linker` | string | `` (системный) | Линковщик: `gold`, `mold`, `lld`, `bfd` | | `full_world_path` | string | `` | Абсолютный путь к данным мира для создания симлинков | +| `sol2_root` | string | `` | Необязательный путь к заголовкам sol2; пустое значение использует Meson dependency/fallback | ### Зависимости @@ -276,6 +313,7 @@ meson setup build -Ddependency1=builtin -Ddependency2=system -Ddependency3=disab | `zlib` | `auto` | Поддержка MCCP через ZLib | | `iconv` | `disabled` | Поддержка iconv | | `telegram` | `disabled` | Telegram-интеграция (требует CURL + OpenSSL) | +| `luajit` | `disabled` | Lua-триггеры через LuaJIT и sol2; `builtin` собирает встроенные зависимости | | `boost` | `disabled` | Boost (нужен для scripting) | | `sqlite` | `disabled` | SQLite как источник данных мира | | `yaml` | `disabled` | YAML как источник данных мира | diff --git a/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md b/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md index efbe53dd2..84ddfad3f 100644 --- a/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md +++ b/docs/LUA_SCRIPTS_FOR_BUILDERS_RU.md @@ -2,7 +2,7 @@ Этот документ описывает Lua-триггеры, которые можно использовать вместо DG Script в world-файлах триггеров. -Lua-поддержка сейчас является прототипом и работает только в сборке с `-Dluajit_prototype=true`. Обычная сборка без LuaJIT должна продолжать читать DG-триггеры как раньше. +Lua-поддержка сейчас является прототипом и работает только в сборке с включенной опцией `luajit`, например `-Dluajit=builtin` или `-Dluajit=system`. Обычная сборка без LuaJIT должна продолжать читать DG-триггеры как раньше. **Безопасность:** глобалы `os`, `io`, `debug`, `package` и `require` намеренно отключены. Для реального календарного времени используйте `mud.date("%j")`, `mud.date("*t")`, `mud.date("exact")` и т.п. (не os.date). From 1cbc4e16e53598cd4ec243e22133705f130a6b09 Mon Sep 17 00:00:00 2001 From: Vladimir Khanayev Date: Tue, 21 Jul 2026 17:36:17 +0300 Subject: [PATCH 09/10] Disable Meson unity for LuaJIT/EmmyLua wraps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #3570 Отключена юнити сборка для emmyluacodestyle и luajit. Это сторонние библиотеки не поддерживающие юнити режим. --- subprojects/packagefiles/emmyluacodestyle/meson.build | 2 ++ subprojects/packagefiles/luajit/src/host/meson.build | 2 +- subprojects/packagefiles/luajit/src/meson.build | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/subprojects/packagefiles/emmyluacodestyle/meson.build b/subprojects/packagefiles/emmyluacodestyle/meson.build index 41be757cf..8559ebe2d 100644 --- a/subprojects/packagefiles/emmyluacodestyle/meson.build +++ b/subprojects/packagefiles/emmyluacodestyle/meson.build @@ -23,6 +23,7 @@ uri_lib = static_library( uri_sources, include_directories: uri_inc, c_args: ['-DURI_LIBRARY_BUILD', '-DURI_STATIC_BUILD'], + override_options: ['unity=off'], pic: true, ) @@ -97,6 +98,7 @@ emmy_lib = static_library( include_directories: emmy_inc, link_with: uri_lib, cpp_args: ['-DURI_STATIC_BUILD'], + override_options: ['unity=off'], pic: true, ) diff --git a/subprojects/packagefiles/luajit/src/host/meson.build b/subprojects/packagefiles/luajit/src/host/meson.build index 023aa8950..246a109e5 100644 --- a/subprojects/packagefiles/luajit/src/host/meson.build +++ b/subprojects/packagefiles/luajit/src/host/meson.build @@ -1,5 +1,5 @@ # No need for sanitizing and no point in wasting time with LTO on native helpers. -native_overrides = ['b_sanitize=none', 'b_lto=false'] +native_overrides = ['b_sanitize=none', 'b_lto=false', 'unity=off'] minilua_exe = executable( 'minilua', diff --git a/subprojects/packagefiles/luajit/src/meson.build b/subprojects/packagefiles/luajit/src/meson.build index fe2c59120..289d2f3ae 100644 --- a/subprojects/packagefiles/luajit/src/meson.build +++ b/subprojects/packagefiles/luajit/src/meson.build @@ -177,7 +177,7 @@ luajit_lib_name = 'luajit-' + abiver luajit_lib_kwargs = { 'dependencies' : [m_dep, dl_dep], 'install' : true, - 'override_options': ['b_sanitize=none'], + 'override_options': ['b_sanitize=none', 'unity=off'], } # Shared luajit library. {{{ @@ -265,6 +265,7 @@ if get_option('luajit') export_dynamic: luajit_exe_export_dynamic, install: get_option('luajit'), link_with: luajit_exe_lib, + override_options: ['unity=off'], ) meson.override_find_program('luajit', luajit_exe) From 4f92663a2e7c7c6c00665956c6aad4ad52425f14 Mon Sep 17 00:00:00 2001 From: Vladimir Khanayev Date: Tue, 21 Jul 2026 18:45:22 +0300 Subject: [PATCH 10/10] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=BE=D1=82=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20unity-=D1=81=D0=B1=D0=BE=D1=80=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BB=D1=8F=20LuaJIT=20=D0=B8=20EmmyLuaCodeStyl?= =?UTF-8?q?e.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Удалено неработающее значение unity=off из default_options, поскольку оно переопределялось глобальным параметром -Dunity=on. Для целей Lua-подпроектов используется override_options, который корректно отключает unity-сборку. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index a5a4e0a8d..310311ae7 100644 --- a/meson.build +++ b/meson.build @@ -243,7 +243,7 @@ if luajit_opt != 'disabled' emmy_dep = dependency( 'emmyluacodestyle', fallback: ['emmyluacodestyle', 'emmyluacodestyle_dep'], - default_options: ['warning_level=0', 'unity=off'], + default_options: ['warning_level=0'], ) dependencies += emmy_dep project_args += '-DWITH_LUA_FORMATTER'