Старатель написал: Volume[alltrade.price] = (Volume[alltrade.price] or 0) + alltrade.qty
1. Все операторы в QLua (включая функции, запускаемые в колбеках), в том числе с участием атомарных C-функций (выдающих однозначный результат в контексте их запуска), являются атомарными. 2. Взаимодействие колбеков с main подробно обсуждалось в ветке «Грядущие изменения на срочном рынке МБ: поддержка работы с 19-значными номе-рами заявок и сделок» и Антон там достаточно детально это описал.
Старатель написал: if N >= 0 then N = N + 1Это атомарная операция?
Фрагмент if N >= 0 then N = N + 1 это "чистый" Lua, а при текущей реализации QLua все такие участки (и в мейне и в колбеках) выполняются под общей блокировкой. Поэтому это атомарная операция.
Итак, сегодня суббота. "Уронить" QUIK 8.13.0.106 после 13.04.21 (с последними его исправлениями) мне не удалось. Ни с помощью моего трэшевого теста, ни добавлением к его работе частого запуска "тяжелого" скрипта с разнообразными операциями. Высказанные ранее мною предположения пока подтверждаются.
Владимир написал: TGB , Но не исчезнут они никогда!
Нужно исходить и того, что в более-менее объемной программе, всегда есть ошибка. И все же ошибки, на которые часто"наступают" пользователи, обычно, со временем вычищаются.
Цитата
Владимир написал: Не, я пока погожу. В конце концов, эта версия работает на компе моего друга. Вот и посмотрим, как она будет себя вести.
Если друг не обновит свою версию последними исправлениями, то ее стабильность не будет отличаться от вашей. Надо иметь ввиду, что проявление ошибок синхронизации существенно зависит от что реализуется в скрипте и возможно у вашего друга, вообще со стабильностью нет проблем.
Владимир написал: Снова Квик гавкнулся. Кажется, причина та же, что была и в прошлых глюках: конфликт утилит прорисовки таблиц и подачи транзакций.
Цитата
TGB написал: И все-таки, я бы на вашем месте перешел на QUIK 8.13.0.106 и еще бы сделал его обновление последними исправлениями.
Все версии до выше указанной (с обязательным обновлением после 13.04.21), по моим представлениям, с ошибкой синхронизации в QLua, проявляющей себя в виде похожем на то, что вы описываете. Для разных пользователей эти ошибки могут проявляться по разному.
Владимир написал: И у меня стойкое ощущение, что чем больше номер версии, тем глючнее содержимое.
Ну вы консерватор Регрес действительно временами (часто) наблюдается, но в основном, все-таки по принципу один шаг назад, а два вперед. Я понимаю, что вы сами с усами И все-таки, я бы на вашем месте перешел на QUIK 8.13.0.106 и еще бы сделал его обновление последними исправлениями.
Поддержка в ветке так и не проявилась. Но есть и хорошая новость. Тест (это OS_Quesha, запущенная в особо тяжелом режиме тестирования синхронизации в 16 потоках) в обновленном QUIK нормально работает до сих пор (16.04.21 15.28). Никаких аномалий типа утечки памяти, зацикливания и т.д. я в QUIK не обнаружил. Мои попытки «обрушить» QUIK многократными дополнительными запусками из OS_Quesha скрипта в котором есть файловые операции, отладочные операции, операции сереализации и восстановления таблиц, в том числе и _G, не удались. Для меня это означает, что в обновленном 13.04.21 QUIK 8.13.0.106 синхронизация в QLua, наконец, реализована корректно и QUIK, я думаю, стал более стабильным. Похоже, эта эпопея , начавшаяся для меня в мае 2020г. с посылки поддержке первого письма с дампом и с текстом о том, что в QLua 8.5 есть ошибка синхронизации, завершилась.
Anton написал: У вас, например, я не увидел DeleteCriticalSection, потому ли, что вы не нашли, куда бы ее приткнуть? Там есть куда, см. luai_userstateopen, luai_userstateclose, luai_userstatefree. Аналогичные макросы есть для потоков. Хайли лайкли косяк (был?) где-то в этих местах, что-то удаляется слишком рано. Ваш хост даже не пытается эту сторону промоделировать, чего же ему падать-то.
Чем меньше дергаешься, тем реже падаешь Я не ставлю задачу что-то моделировать. Первая задача при разработке любой программы это обеспечение правильности ее работы. В своих разработках программ придерживаюсь некоторых принципов. Вот два из этого списка: 1) Не делай лишней работы. 2) Все, что можно реализуй статически. ----- Если по существу обсуждаемого, то я жду субботы. Если мой тест, запущенный в обновленном QUIK 8.13.0.106, не уронит его до субботы, то для меня это будет признак того, что в обновленном QUIK обсуждаемая синхронизация, скорее всего, наконец, реализована корректно.
Александр М написал: Не совсем понял Ваш ответ. После обновления на 8.3.0.106 на родной (НЕ измененной) библиотеке lua проблемы исчезли?
После упомянутого мною обновления QUIK 8.13.0.106, скорости на «смешанном» коде Lua (с обращениями к С-коду) в QUIK и на моем стенде сравнялись. Для меня это косвенный признак того, что, в обновленном QUIK 8.13.0.106 синхронизация могла быть реализована, как на моем стенде, то есть, по моему мнению, правильно. Для подтверждения этой гипотезы я в новой версии QUIK запустил 13.04.21 вечером свой тест, который не обновленную версию QUIK "ронял" гарантированно в пределах чуть более одних суток. Сейчас 15.04.21 17.56 тест идет нормально и установлен новый продолжительности его работы. При этом, в обновленном QUIK 8.13.0.106 я не наблюдаю никаких проблем. Если тест не "уронит" QUIK до субботы, то, наверное, можно будет, с большим основанием, предположить, что в обновленном QUIK 8.13.0.106 наблюдаемые мною ранее ситуации устранены, то есть синхронизация реализована корректно.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Владимир написал: Что такое "нагружает управление памятью" я не понял.
Попробую объяснить. Дело в том, что при программировании на Lua вам совсем не надо управлять памятью (ее запросом/возвратом, контролем за всем этим - большой геморрой при разработке программ). В Lua это делается автоматически ("под капотом"). Тем не менее это все делается за счет вычислительных ресурсов пользователя (время ЦП, память и т.д.). Существуют ситуации, в которых вроде бы на безобидных операциях в циклах, например: s = s .. s1 расход вычислительных ресурсов может резко увеличивается. На каждом выполнении s = s .. s1 в невидимом для вас управлении памятью выполняется по меньшей мере запрос памяти под новый размер s и отказ от памяти s до начала операции. Запрос памяти это достаточно затратная операция, как-то зависящая от размера памяти. Для отказа от памяти в Lua, не считая некоторых случаев, ничего специально не выполняется. Если на некоторую область памяти нет ссылок из существующих в текущий момент объектов скрипта, то она считается неиспользуемой (мусорной). Но чтобы мусор не накапливался (это никем не используемая память ПК), в Lua время от времени запускается сборка мусора (одна из наиболее сложных операций, выполняемых "под капотом" Lua). Сборка мусора тоже использует вычислительные ресурсы скрипта.
Вы дали ответ только на два мои вопроса (а их всего четыре). Было бы интересно почитать ваши версии ответов на остальные мои два вопроса. Кстати, по поводу возможной оптимизации синхронизации в QLua, вы можете посмотреть мой комментарий в ветке «Средства разработки многопоточных скриптов в QUIK».
Anton написал: Квик перехватывает создание стейта для скрипта (для этого в луа есть соответствующие макросы под переопределение), прицепляет к стейту дополнительную структуру и создает критическую секцию в этой структуре, свою для каждого скрипта (именно поэтому разные скрипты таки могут выполняться параллельно друг другу, у вас - нет). ......
Вы это пишите как разработчик QUIK или как любитель?
Цитата
Anton написал: поэтому разные скрипты таки могут выполняться параллельно друг другу, у вас - нет
Вы прочитали это?:
Цитата
TGB написал: На этом стенде, точно та же моя тестовая программа (16 интенсивно нагруженных потоков в одном Lua-скрипте), работает непрерывно неделями, и я не могу дождаться, когда же она, наконец, упадет.
----- Могу так же поздравить себя с тем, что после упомянутого мною обновления QUIK 8.13.0.106 скорости на «смешанном» коде Lua (с обращениями к С-коду) в QUIK и на моем стенде сравнялись. И куда же делась "затейливость", обеспечивающая высокую скорость QUIK? Причем, в обновленном QUIK мой тест, запущенный вечером 13.04.21 пока работает без проблем. Если он не "обрушит" обновленную версию QUIK до субботы, то скорее всего он дотянет и до следующей субботы. А если это произойдет, то кому что-то будет непонятно?
Александр Волфовиц написал: TGB ,а как квик может обновиться "неожиданно для вас"? Появляется оповещение "На сервере появилась новая версия, обновить?" Если нажмёте кнопочку "нет" , то будете работать на старой версии.
"Неожиданно для меня" это фигура речи. Конечно же, мною были нажаты все необходимые кнопки.
Евгений написал: Придется оправдаться за неисправленные ошибки, лучше не читать
)
Читать не читают, но что характерно : 1. Мой комментарий был написан 10.04.21. 2. А вчера вечером (13.04.21) мой QUIK 8.13.0.106 совершенно неожиданно для меня автоматически обновился. Возможно это простое совпадение, но на всякий случай я запустил в нем свой тест (с подключением к серверу. Если он не "обрушит" обновленную версию QUIK в течении недели (до 20.04.21) то, наверное, разработчиков QUIK можно будет поздравить. При этом я не исключаю, что действительно поддержка ветку не читает (и особенно, длинные тексты )
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Об оптимизации синхронизации в QLua. Не затрагивая существующей архитектуры обработки колбеков в QUIK, можно оптимизировать синхронизацию многопоточности QLua. Дело в том, что при синхронизации по умолчанию, как это, похоже, реализовано в существующей QLua, все вызовы C-функций имеют следующий общий вид: unlock ….; <Вызов C-функции>; lock ……; В операциях unlock и lock возможны преключения потоков. То есть, это ресурсоемкие операции. Если вызываемая C-функция выполняется быстро, как напри-мер, многие математические функции, то при существующей синхронизации, коэффициент полезного использования времени ЦП может быть очень низким : <Время выполнения C-функция > / (< Время выполнения unlock > + < Время вы-полнения lock >). Понятно, что в циклах QLua с обращением к коротким C-функциям QLua занимается в основном синхронизацией. Идея оптимизации состоит в том, чтобы у пользователя была динамическая возможность задания/отмены C-функций, которые вызываются без выше описанной синхронизации (ввести особый список). Сделать эффективную реализацию выше описанного несложно. Чтобы гарантировать обязательную «щель» для запуска колбеков, из списка допустимых C-функций, описанных в предыдущем абзаце операций, имеет смысл исключить sleep, а возможно, и еще какие-то функции обеспечения задержек по времени и файловые операции. Существенным положительным моментом описанной оптимизации является то, что она никак не затрагивает тех пользователей, которые не будут ее использовать. При ее применении, коэффициент полезного использования времени ЦП: 1) для функций из особого списка: <Время выполнения C-функция > / < Время анализа особого списка> 2) для обычных функций: <Время выполнения C-функция > / (< Время анализа особого списка> + < Время выполнения unlock > + < Время выполнения lock> ). При качественной реализации оптимизации можно добиться того, чтобы < Время анализа особого списка> было существенно меньше, чем (< Время выполнения unlock > + < Время выполнения lock>).
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Незнайка написал: Правда, не знаю, для каких практических задач может потребоваться склейка огромного количества строк.
Когда вы занимаетесь своей конкретной задачей, вам действительно, решать задачу склейки огромного количества строк может быть никогда не придется. Но если вы разрабатываете программный инструмент для достаточно широкого круга пользователей, вам придется учесть, что у кого то такая задача может возникнуть. Поэтому у меня (где то это уже отмечалось) в системе есть специальная C-функция сбора строки в один проход практически без нагрузки на управление автоматической памятью Lua. Чтобы использовать эту функцию непосредственно в выложенном мною модуле, пришлось бы подключать мой C-пакет.
Цитата
Незнайка написал: На выходе вместо Lua-таблицы получается какая-то нечитаемая "каша". Странный выбор... или у нас разное понимание понятия "сереализация". Чем обусловлен выбор такого формата? Он ведь труден для восприятия.
При сереализации не стоит задача получить строку в удобно читаемом формате. Для этого есть функция dump_tbl, где выделяются все отступы и т.д. Для задачи сереализации достаточно того, чтобы результат str_to_tbl (<строка-результат из функции tbl_to_str (<Таблица>....) был <Таблица>. В принципе, не очень сложно, так модифицировать, выложенный мною модуль, чтобы формат сереализованной таблицы был в виде { [] = { ........ } } но это уже не принципиально и я не вижу смысла в том, чтобы этим заморачивться. Строка-результат сереализации предназначена не для изучения ее пользователем, а для подачи в функцию восстановления таблицы.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Новые версии QUIK стали заметно устойчивее, чем это было в мае 2020г. Однако, нечасто, но в течении двух-трех суток версия QUIK 8.13, в которой запущен мой тест синхронизации в QLua, без подключения к серверу, зависает с выдачей сообщения: Critical error ACCESS_VIOLATION (Критическая ошибка НАРУШЕНИЕ ДОСТУПА). Дампы при этом не сбрасываются. Многое указывает на то, что в QUIK 8.13 есть ошибка синхронизации (и все-таки земля вертится ). Попробую это обосновать. 1. Ошибка «error ACCESS_VIOLATION» в QUIK 8.13 возникает в разных местах моей тестовой программы, и в разные моменты времени. 2. Я собрал из исходников Lua 5.3.5 вариант исполняемого кода Lua (свой стенд), в котором допустимы обращения к VM-Lua из разных потоков. На этом стенде, точно та же моя тестовая программа (16 интенсивно нагруженных потоков в одном Lua-скрипте), работает непрерывно неделями, и я не могу дождаться, когда же она, наконец, упадет. 3. Я провел следующий эксперимент: 1) Запустил на выполнение программу на «чистом» Lua (без обращения к С-кодам, где отсутствует синхронизация) в QUIK 8.13 и на моем стенде. Время выполнения оказалось одинаковым. 2) Запустил на выполнении программу на «смешанном» Lua (с обращениями к С-кодам, где должна работать синхронизация) в QUIK 8.13 и на моем стенде. Время выполнения оказалось разным. Причем на моем стенде ~ 1, 4 раза больше. Вопрос: в QUIK 8.13 сумели, как то сильно оптимизировать синхронизацию доступа к VM-Lua из разных потоков? Я думаю, это вряд ли. Скорее всего, есть ошибка в реализации синхронизации в QLua 8.13.
В своем стенде, для синхронизации доступа к VM-Lua, я использую критическую секцию, и далее я приведу все места в исходном тексте Lua 5.3.5, где мною были внесены изменения для обеспечения синхронизации. Обращаю ваше внимание на то, что при синхронизации должен использоваться общий объект синхронизации. Возможно?, место синхронизации в QLua было выбрано неудачно. ----- Мои изменения (для обеспечения синхронизации) в исходном коде Lua 5.3.5 приведены в следующем виде: 1) № изменения 2) <Имя файла исходника> 3) <Текст исходника, позволяющий определить место после которого внесено изменение> 4) <Текст изменения> -------------------------------------------------------------------------------- 1. Изменение № 1 Файл: ldo.c Место: #include "ldo.h" Добавление: #include "DbgHelp.h" #pragma comment(lib, "Dbghelp.lib") void lua_lock_MT(lua_State *L) { global_State *g = G(L); EnterCriticalSection(&g->Глобальная_критическая_секция); }
4. Изменение № 4 Файл: limits.h Место: /* ** macros that are executed whenever program enters the Lua core ** ('lua_lock') and leaves the core ('lua_unlock') */ Добавление: #if !defined(lua_lock) #define lua_lock(L) lua_lock_MT(L) #define lua_unlock(L) lua_unlock_MT(L) #endif ----- 5. Изменение № 5 Файл: luaconf.h Место: ** the libraries, you may want to use the following definition (define ** LUA_BUILD_AS_DLL to get it). */ Добавление: #define LUA_BUILD_AS_DLL ----- 6. Изменение № 6 Файл: lstate.c Место: g->gcstepmul = LUAI_GCMUL; Добавление: InitializeCriticalSection(&g->Глобальная_критическая_секция); ----- 6. Изменение № 7 Файл: lstate.h Место: TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */ Добавление: CRITICAL_SECTION Глобальная_критическая_секция;
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Конструктивной частью нашей (во многом, содержательной) дискуссии с Артемом я считаю, для себя, реализацию модифицированного модуля сереализации Lua-таблиц. Этот модуль создан в соответствии с вариантом решения, который был кратко описан мною в комментарии № 164. Замечу, что мои комментарии в ходе дискуссии с Артемом, особенно в части размеров кода, относились к функциональности, выложенного им модуля. Функциональность модифицированного модуля, а также эффективность выполнения в нем сереализации и восстановления таблиц, иные. --- Код модуля выложен в конце данного комментария. ----- В простом варианте восстановления сереализованной таблицы (в том числе и после перезапуска скрипта), когда контекст (определение приведено в описании модуля) ее восстановления не отличается от контекста сереализации, все просто: -- Сереализация таблицы -- <Строка образа (! в случае отсутствия 3-го параметра)> = tbl_to_str (<Таблица> [, <Путь файла сохранения образа или вид результата (0 | 1 байт-код или строка-скрипт)> [ ,<Контекст сереализации>]])
………… !! Здесь может быть и перезапуск скрипта --
-- Полное восстановление таблицы (со всеми ее значениями без исключения ) -- <Таблица)> = str_to_tbl (<Сохраненный где образ - результат функции tbl_to_str > [,<Контекст сереализации>]) ------ Более сложный вариант использования модуля (при различии контекста восстановления таблицы от контекста сереализации) приведен далее, в его описа-нии. -------------------------------------------- В описании модуля значения типов function, userdata и thread называются ссылочными значениями. Сереализуемая таблица, в описании, обозначена именем <T>. Внутренним именем ссылочного значения называется результат функции internal_name (<Cсылочное значение>), определенной в модуле. Составное имя имеет вид адреса поля в таблице: <T1>.<T2>…..<Tn>.<Поле таблицы Tn> Под контекстом выполнения (сереализации, восстановления) далее описываемых функций понимается список доступных им, в момент их выполнения, ссылочных значений, на которые есть ссылки из <T>. -- Описание модуля. 1. Существенных отличий данного модуля, от выложенного Артемом, три: 1) Формирование таблицы ссылочных значений <T> в формате, обеспечивающем, полное восстановление <T> (в том числе, с учетом ее локаль-ного окружения) в контексте ее сереализации из образа, сохраненного в файле (в том числе и после перезапуска скрипта). Кроме того, обеспечен удобный контроль и коррекция результата восстановления <T> в контексте, отличном от контекста ее сереализации. 2) Результатом сереализации таблицы <T> является Lua-скрипт восстановления этой таблицы (в виде байт-кода или исходного кода Lua), со-держащий в себе две таблицы ссылочных значений (контекста и <T>), 3) На таблице _G (в моем скрипте) время выполнения сереализации в дан-ном модуле в ~1,5 раза меньше, чем в сравниваемом модуле, а время восстановления _G меньше в ~ в восемьнадцать раз (мои конкретные числа данного модуля для _G ~ 8000 элементов: сереализация ~ 55 млсек., восстановление ~ 2 млсек.). С уменьшением сереализуемой таб-лицы, описанные выше времена, уменьшаются почти линейно.
2. В модуле доступны пять функций: 1) Сереализация таблицы: tbl_to_str (<T> [, <Признак выдачи результата в виде байт-кода или строки-скрипта 0|1> | <Путь файла записи строки-скрипта > [ ,<Контекст сереализации: таблица или функция >] ]) Результат в зависимости от входных параметров: <байт-код восстановления <T> | <Строка-скрипт восстановления <T>> | <Запись строки-скрипта в файл с заданным путем>.
2) Восстановление таблицы: str_to_tbl (< Байт-код скрипта для восстановления <T>, либо строка-скрипт>[ , <Контекст восстановления: таблица или функция >]) Результаты. Их может быть один или три (если есть висячие ссылочные значения): 1-й - восстановленная таблица с пометкой висячих ссылок, если такие существуют в виде составных имен в контексте сереализации. Второй и третий – nil, если висячих ссылок нет или две таблицы: - таблица висячих ссылок с указанием составных имен-адресов в кон-тексте сереализации; - таблица висячих ссылок с указанием составных имен –адресов в <T>. Эти данные могут быть основой для восстановление таких ссылок.
3) Формирование служебных таблиц, используемых в функциях tbl_to_str и str_to_tbl: creating_link_tables (<Контекст выполнения функций tbl_to_str и str_to_tbl > [, <Признак обновления существующего контекста = 1>]) Первый параметр либо таблица (например, _G), либо пользователь-ская функция создания таблиц контекста (по образцу аналогично тому, как это делается в creating_link_tables). В случае функции, при создании | изменении контекста, вместо creating_link_tables запускается пользовательская функция. По умолчанию контекстом является _G. Результаты функции две таблицы формируемые в ней.
5) Печать любой таблицы Lua в виде удобном для анализа, с указанием уровней вложенности всех ее элементов: dump_tbl (<Таблица>, <Символ отступов для выделения вложенности эле-ментов таблицы>, <Уровень раскрываемых вложенностей таблицы (если 0, то показывается все > [, <Вид выдачи результата: false – таблица (по умол-чанию); true - строка> [, < Таблица имен элементов (задаваемых в ключах) , не разворачиваемых далее >]]). ---- В квадратных скобках при описании всех функций указаны необязательные параметры.
3. При сереализации <T> ее ссылочные значения не сохраняются, но при восстановлении <T>, ссылки на ее ссылочные значения, разрешаются (устанавливают-ся на значения) в контексте ее восстановления. Если контекст, в момент восстановления <T>, такой же (в части всех ее ссылочных значений), как и в момент ее сереализации, то ничего кроме запуска функции str_to_tbl с единственным параметром-результатом, полученным из функции tbl_to_str не требуется; в этом случае таблица <T> будет восстановлена полностью (со всеми ее значениями, включая ссылочные). При отличии контекста восстановления от контекста сереализации таблицы в момент ее восстановления, некоторые ссылки могут оказаться «висячими». Для возможности восстановления висячих ссылок функция str_to_tbl выдает дополнительно к восстановленной <T> c указанием в ней висячих ссылок, две таблицы описания таких ссылок в удобном для анализа виде.
4. В функции str_to_tbl выполняется автоматический контроль разрешимости ссылок <T> на ее ссылочные значения. Выполняемый контроль частичный, в том смысле, что если он не проходит, то это значит, что контекст восстановления <T> точно отличен от контекста ее сереализации. Если же этот контроль проходит, то это означает, что имеется всего лишь полное совпадение сформированных имен ссылочных значений <T>, с именами контекста ее восстановления. При этом может оказаться (! только при отличии контекста восстановления <T> от контекста сереализации), что какая то часть восстановленных ссылочных значений не валидна (восстановленное значение отличается от сереализованного). Вероятность появления невалидности значений, скорее всего, будет очень малой, но не 0. Это надо учитывать и, для случая отличия контекста восстановления от контекста сереализации, желательно выполнять дополнительный контроль а, при необходимости и исправление. ---------- Модуль можно использовать свободно, а при распространении, желательна ссылка на меня. Сообщения об обнаруженных ошибках, замечания и вопросы будут мне интересны. --- Одним из вариантов использования данного модуля может быть реализация контрольной точки, обеспечивающей продолжение выполнения любой цикличе-ской функции (аналога функции main в QLua) после ее перезапуска (по любой причине). При этом, для эффективности контрольной точки (уменьшения сохра-няемых данных) а также защищенности данных функции, можно реализовывать (по крайней мере, начиная с версии 5.3), с помощью _ENV, дополнительно к ее локальным переменным, собственное окружение функции, хранящее только ис-пользуемые в ней, ее сереализуемые значения. Такой вариант реализован мною в OS_Quesha для некоторых ее системных циклических функций.
Код модуля:
Код
-- Модуль сериализации таблиц. Автор TGB ---
---------------------------
--- Быстрая сборка строки из массива строк ----
-- Не обязательный параметр pr = false - строка собирается в прямом порядке (от начала массива к концу); true - строка собирается в обратном порядке --
local function m_str_to_str_fast ( m_str , pr)
if type (m_str) ~= 'table' then return nil, ' ! Ошибка. Первый параметр m_str_to_str_fast не таблица ***' end
local const_mod = 10, m_str_tm
local mod, N , NN
local N_mod, i_mod
----
if pr then
m_str_tm = {}
NN = #m_str
for i = NN, 1, -1 do m_str_tm [NN - i +1] = m_str [i] end
m_str = m_str_tm; m_str_tm = {}
end
while 1 do
m_str_tm = {}
mod, N, NN = #m_str % const_mod, math.floor (#m_str / const_mod), #m_str
N_mod = N * const_mod
for i = 1, N do
i_mod = (i -1)*const_mod
m_str_tm [i] = m_str [ i_mod + 1] .. m_str[ i_mod + 2] .. m_str [ i_mod + 3] .. m_str [ i_mod + 4] .. m_str [ i_mod + 5]
.. m_str [ i_mod + 6] .. m_str [ i_mod + 7] .. m_str [ i_mod + 8] .. m_str [ i_mod + 9] .. m_str [ i_mod + const_mod]
end
if mod > 0 then
N = N + 1; m_str_tm [N] = ''
for i = 1, mod do m_str_tm [N] = m_str_tm [N] .. m_str [N_mod + i] end
end
----
if #m_str_tm > 1 then
m_str = m_str_tm
else return m_str_tm [1]
end
end
end
----------------------------------
----------------------------------
--- Вывод произвольной таблицы в виде таблицы-массива строк (либо в виде строки) ------
------ Параметры:
-- 1) t - таблица (выводятся все вложения до limit);
-- 2) i - строка формирования отступа при выводе вложений (например, ' ')
---- 3) limit - уровень вложенности до которого просматривается таблица (если = 0, то все уровни)-----
---- 4) (не обязательный) <Вид выдачи резкльтата: false – таблица (по умол-чанию); true - строка>
---- 5) (не обязательный) <Таблица имен элементов (задаваемых в ключах) , не разворачиваемых далее >
--- ! Результат: таблица строк: структура со значениями таблицы t (текстовая сортировка по возрастанию индексов таблиц) либо строка,
--- в зависимости от параметра pr-----
--- !! Элемент таблицы [1] - заголовок таблицы. Остальные элементы - строковые представления структуры таблицы t
--- с дополнительными служебными описаниями вложенных в t таблиц.
local function dump_tbl (t, i, limit, pr, list)
if type(t) ~= 'table' then
if pr then return ' Первый параметр t не таблица. Результат: ' .. tostring(t) end
return nil, '! Ошибка. Первый параметр t не таблица'
end
list = list or {}
if type(list) ~= 'table' then
return nil, '! Ошибка. Параметр list не таблица'
end
limit = limit or 0
local tbl = {}; --- для результата ----
tbl[#tbl +1] = '===== Таблица (текстовая сортировка по возрастанию индексов таблиц): ' .. tostring(t)
.. ' \n ! Количество выводимых уровней вложенности (если = 0, то выводятся все) = ' .. limit .. '\n'
local seen={} -- просмотренные --
local Level = 0
------------------------------------------
local function dump(t,i, nm) -----
nm = nm or 'T'
if seen [t] then
return seen [t]
end
------
seen [t] = nm --- !
local t_v, t_k, nm_tm
Level = Level +1 --- Уровень вложенности таблицы ----
--- Обработка метатаблицы ----
local mt = debug.getmetatable ( t )
if mt then
nm_tm = seen[mt]
if nm_tm then
tbl[#tbl +1] = i .. 'Для таблицы ' .. nm .. ' cсылка на существующую метатаблицу -> ' .. nm_tm .. '\n'
else
nm_tm = tostring(mt)
tbl[#tbl +1] = i .. 'Вложенность: ' .. Level .. ' === Содержимое метатаблицы ### : ' .. nm_tm .. '\n'
dump(mt, i..'\t', nm_tm)
tbl[#tbl +1] = i .. 'Вложенность: ' .. Level .. ' === Конец метатаблицы ## ' .. nm_tm .. '\n'
end
end
if next (t) == nil then ---- Таблица пустая ---
tbl[#tbl +1] = i .. 'Вложенность: ' .. Level .. ' Таблица: ' .. nm .. ' пустая ### '.. '\n'
Level = Level - 1
return ''
end
local s={} --- массив хранение имен ключей (строк) для сортировки ---
local ss={} -- массив для хранения самих ключей --
local n=0
local ks=0
for k, v in next, t do
ks = tostring(k)
n=n+1
s[n] = ks
ss[ks] = k
end
table.sort(s)
for k,v in ipairs(s) do ---
t_k = ss [v] -- ключ записи в t
t_v=t [t_k] -- значение записи в t
if Level < limit or limit == 0 then
if type(t_k) == 'table' then --- Обработка ключа-таблицы ---
nm_tm = seen[t_k]
if nm_tm then
tbl[#tbl +1] = i .. '[ ' .. tostring(t_k) .. ' - cсылка на существующую таблицу-ключ -> ' .. nm_tm .. ']' .. '\n'
else
tbl[#tbl +1] = i .. 'Вложенность: ' .. Level .. ' === Содержимое ключа-таблица $$ : ' .. v .. '\n'
dump(t_k, i..'\t', tostring(t_k))
tbl[#tbl +1] = i .. 'Вложенность: ' .. Level .. ' === Конец ключа-таблицы $$ ' .. v .. '\n'
end
end
if list [v] then
if type(t_v) == 'table' then
tbl[#tbl +1] = i .. '[' .. v .. '] (' .. type(t_v) .. ') = ' .. tostring(t_v) .. ' | Терминальная таблица (в печати задано далее не разворачивать) ### \n'
else
tbl[#tbl +1] = i .. '[' .. v .. '] (' .. type(t_v) .. ') = ' .. tostring(t_v) .. '\n'
end
else
tbl[#tbl +1] = i .. '[' .. v .. '] (' .. type(t_v) .. ') = ' .. tostring(t_v) .. '\n'
if type(t_v) == 'table' then --- Обработка таблицы ----
nm_tm = seen [ t_v]
if nm_tm then
tbl[#tbl +1] = i .. '[' .. v .. '] ссылка на существующую таблицу -> ' .. nm_tm .. '\n'
else
tbl[#tbl +1] = i .. 'Вложенность: ' .. Level .. ' === Содержимое таблицы : ' .. v .. '\n'
dump(t_v, i..'\t', tostring(t_k))
tbl[#tbl +1] = i .. 'Вложенность: ' .. Level .. ' === Конец таблицы ' .. v .. '\n'
end
end
end
end
end
Level = Level - 1
return ''
end
--------------
dump(t,i, tostring(t))
if pr then --- Выдача результата в виде строки
return m_str_to_str_fast ( tbl)
else return tbl --- Выдача результата в виде массива строк
end
end
-------------------------------- Общие данные модуля -----------------------------------------
--- 1. Для выделения строк используется скобки [[ ]] и поэтому внутри них не надо никаких преобразований (типа замены \ на \\ и т.д).
-- 2. В выдаваемых стоках для удобства анализа, места, на которые надо обращать внимание в первую очередь выделены символом ###.
----
local aliases_cont = { } --- [ <Внутреннее имя ссылочного значения> ] = <Внешнее имя ссылочного значения в контексте>
local aliases_v_cont = { } --- [ <Внешнее имя ссылочного значения в контексте> ] = <Ссылочное значение в контексте>
local wo_creator -- Призак где сформирована таблицы
local tbl_cont = _G --- Контекст по умолчанию ---
local context_tbl = 'CONT' -- Имя, присвоенное контексту
-----
----- Формирование внутренних имен --
local function internal_name ( s )
return tostring ( s ):gsub ( ': ', '' )
end
----------------------------------------------
-- Параметры
-- 1) 1-й параметр либо таблица, либо пользовательская функция соз-дания таблиц контекста (по образцу аналогично тому,
-- как это делается в creating_link_tables). В случае функции, при создании, изменении контек-стэтом вместо creating_link_tables
-- запускается пользовательская функ-ция.
-- 2) необязательный, если 1, то принудительное обновление таблиц формируемых в ней
-- Результаты : две таблицы формируемые в ней.
---
local function creating_link_tables (context, pr)
if type(context) == 'function' then
wo_creator = 'no creating_link_tables'
local cont, v_cont = context (pr) --- context должен выдавать два результата ---
if cont then aliases_cont, aliases_v_cont = cont, v_cont end
return aliases_cont, aliases_v_cont
end
------
if pr then aliases_cont, aliases_v_cont = {}, {} end -- Обновление общих данных ---
if next (aliases_cont) ~= nil and not pr then return end ----
if type(context) ~= 'table' then context = tbl_cont end
----
wo_creator = 'creating_link_tables'
local path = '###: ' .. context_tbl
----------------------------
local function cr_link_tables (item, path, seen)
seen = seen or { }
path = path or context_tbl
local mt
if item == nil or seen [ item ] ~= nil then
return
elseif type ( item ) == 'table' then
seen [ item ] = true
-- Обработка метатаблицы (если есть в item)---
mt = debug.getmetatable ( item )
if type ( mt ) == 'table' then cr_link_tables (mt, path .. '.' .. '.metatable###', seen ) end
---
local path_tm, kl_tm, k_tm
for k, v in pairs ( item ) do
if type ( v ) == 'table' then ---- таблица-значение --
cr_link_tables (v, path .. '.' .. tostring ( k ), seen )
else
if not (type(v) == 'number' or type(v) == 'boolean' or type(v) == 'string') then
if type(k) == 'string' then kl_tm = '.' .. tostring ( k ) else kl_tm = '[' .. tostring ( k ) .. '] ' end
path_tm = path .. kl_tm
k_tm = internal_name (v)
if not aliases_cont [k_tm] then aliases_cont [k_tm] = path_tm end --- ### ?? возможна перезапись ---
aliases_v_cont [ path_tm ] = v
end
end
----
if type ( k) == 'table' then ---- таблица-ключ --
cr_link_tables (k, path ..'.' .. tostring ( k ), seen )
else
if not (type(k) == 'number' or type(k) == 'boolean' or type(k) == 'string') then
if type(k) == 'string' then kl_tm = '.' .. tostring ( k ) else kl_tm = '[' .. tostring ( k ) .. '] ' end
path_tm = '### ключ: ' .. path .. kl_tm
k_tm = internal_name (k)
if not aliases_cont [k_tm] then aliases_cont [k_tm] = path_tm end --- ### ?? возможна перезапись ---
aliases_v_cont [ path_tm] = k
end
end
end
end
end
cr_link_tables (context, path)
-- MessageDb ( 1, 1, 'Скрипт', ' Финальные проверки. creating_link_tables - aliases_v_cont : \n' .. dump_str ( aliases_v_cont , ' ', 0))
---[[
-- Обработка внешних локальных ссылочных данных контекста ---
local level = 2, ii, name, value
local k_tm, path_tm
while 1 do
if debug.getinfo (level, 'n') then
ii = 1
while 1 do
name, value = debug.getlocal(level, ii)
if name then
if not ( type(value) == 'table' or type(value) == 'number' or type(value) == 'boolean' or type(value) == 'string') then
k_tm = internal_name ( value)
path_tm = '###: LOC.' .. tostring (level) .. '.' .. tostring (ii) .. '.' .. name
if not aliases_cont [k_tm] then aliases_cont [k_tm] = path_tm end --- ### ?? возможна перезапись ---
-- aliases_cont [k_tm] = path_tm
aliases_v_cont [ path_tm ] = value
end
else
break
end
ii = ii + 1
end
else
break
end
level = level + 1
end
----------------------------
---]]
return aliases_cont, aliases_v_cont
end
------------------------------------------------------------
-- Функция создает байт-код либо строку-скрипт Lua, при запуске которого восстанавливается таблица t.
-- В эту же строку в конец добавляется две таблицы ссылочных значений
---- Параметры:
-- 1. t - сереализуемая таблица
-- 2. ph_f - (не обязательный) путь файл сброса строки-скрипта либо признак (0 | 1) выдачи образа в виде (байт-кода | строки - скрипта)
-- 3. context - - (не обязательный) контекст
-- Результат в зависимости от входных параметров: <Функция Lua > (один тараметр) | <Строка-скрипт> (второй параметр = 1)
-- | <Запись строки-скрипта в файл> (второй параметр строка путь к файлу)
local function tbl_to_str ( t, ph_f, context)
----Контроль параметров ---
if type ( t ) ~= 'table' then
return nil, '!! Ошибка. Второй параметр не таблица.'
end
ph_f = ph_f or 0
local f_t
if type(ph_f) == 'string' then
f_t = io.open( ph_f, 'w')
if type(f_t ) ~= 'userdata' then return nil, '!! Ошибка при открытии файла' end
end
creating_link_tables (context)
------
local name_alias = 'alias' --- для формирования команд с учетом alias ----
local env_t = context_tbl
local env = env_t .. '.'
local path = '###: TBL'
local t_str = {} --- для сбора строк
---
local name_t, context_t = {}, {}
-----------------------------------------
local function tbl_str ( t, path, seen )
seen = seen or { }
if seen [ t ] then
return false, env .. internal_name ( t )
else seen [ t ] = 0 end
-------
local n_t, mt = env .. internal_name ( t ), debug.getmetatable ( t )
t_str [ #t_str + 1 ] = n_t .. ' = {} --- создание таблицы: ' .. n_t .. ' -- \n'
local n_tt, n_kt, n_vt , st, nm_t, path_t
for k, v in pairs ( t ) do
if (type(v) == 'number' or type(v) == 'boolean' or type(v) == 'string') then -- Простое значение --
if type(v) == 'string' then
n_vt = '[[' .. v .. ']]'
else n_vt = tostring (v)
end
elseif type ( v ) == 'table' then --- Таблица-значение --
n_vt = internal_name (k)
t_str[ #t_str + 1 ] = ' --- ' .. n_vt .. ' - значение-таблица (начало) @ \n'
st, n_vt = tbl_str ( v, path .. '.' .. n_vt, seen )
if not st then t_str [ #t_str + 1 ] = ' -- Таблица ' .. n_vt .. ' была создана ранее ----\n ' end
t_str[ #t_str + 1 ] = ' --- ' .. n_vt .. ' - [' .. internal_name (v) .. '] значение-таблица (конец) @ \n'
else --- Ссылочное значение ---
nm_t = internal_name (v)
path_t = path .. '.' .. internal_name (k)
context_t [nm_t] = aliases_cont [nm_t]
if name_t [nm_t] then
name_t [nm_t] = name_t [nm_t] .. ' | ' .. path_t
else name_t [nm_t] = path_t
end
n_vt = name_alias .. '[ [[' .. nm_t .. ']] ]' or '### err'
end
if (type(k) == 'number' or type(k) == 'boolean' or type(k) == 'string') then -- Простое значение --
if type(k) == 'string' then
n_kt = '[[' .. k .. ']]'
else n_kt = tostring (k)
end
elseif type ( k ) == 'table' then --- Таблица-ключ ---
n_kt = internal_name (k)
t_str[ #t_str + 1 ] = ' --- ' .. n_kt .. ' - ключ-таблица (начало) @ \n'
st, n_kt = tbl_str ( k, path .. '.' .. n_kt, seen ) --- Создание таблицы ключа и ее 'скобок' ----
if not st then t_str[ #t_str + 1 ] = ' -- Таблица ' .. internal_name ( k ) .. ' была создана ранее ----\n ' end
t_str[ #t_str + 1 ] = ' --- ' .. n_kt .. ' - [' .. internal_name (k) .. '] ключ таблица (конец) @ \n' ---
else --- Ссылочное значение ключа ---
nm_t = internal_name (k)
path_t = path .. '.' .. nm_t
context_t [nm_t] = aliases_cont [nm_t]
name_t [nm_t] = path .. '.' .. nm_t .. '.ключ###'
if name_t [nm_t] then
name_t [nm_t] = name_t [nm_t] .. ' | ' .. path_t
else name_t [nm_t] = path_t
end
n_kt = name_alias .. '[ [[' .. nm_t .. ']] ]' or '### err'
end
n_tt = env .. internal_name ( t )
t_str[ #t_str + 1 ] = n_tt .. ' [ ' .. n_kt .. ' ] = ' .. n_vt .. ' \n'
end
if mt then -- создание и подключение метатаблицы в конце прохода по таблице с тем, чтобы она не мешала заполнять таблицу. Иначе пришлось бы использовать rawset ---
t_str[ #t_str + 1 ] = ' --- Создание метатаблицы --- \n'
st, mt = tbl_str ( mt, path .. '.' .. internal_name (mt), seen )
if not st then t_str[ #t_str + 1 ] = '-- Метатаблица ' .. mt .. ' была создана ранее ----\n ' end
t_str[ #t_str + 1 ] = 'debug.setmetatable ( ' .. n_t .. ', ' .. mt .. ' ) --- подключение метатаблицы ' .. mt .. ' --- \n'
end
return true, n_t --- 1 - й результат - признак создания таблицы
end
---------------------
tbl_str (t, path)
--- Формирование фрагментов скрипта -----
local scr1 = 'local '.. name_alias .. ' = {...}; ' .. name_alias .. ' = ' .. name_alias .. ' [1] ; local ' .. env_t .. ' = {} \n'
.. '\n if type(' .. name_alias .. ') == "table" then \n'
local scr2 = '\n return ' .. t_str[1]: match ( '(%S*)') .. '\n else \n--- Две таблицы внутренних имен ссылочных значений : \n '
.. ' -- 1) name_cont [<Внутреннее имя ссылочного значения >] = ###: <Составное имя значения в контексте сереализации таблицы> \n'
.. ' -- 2) name_t [<Внутреннее имя ссылочного значения >] = ###: <Список составных имен таблицы, ссылающиеся на значение > \n'
.. '--- ! Составные имена таблицы (если их неколько) разделены символом | . \n'
local scr3 = '\n local name_cont = { '
local scr4= '\n local name_t= { '
local scr5 = '\n} '
local scr6 = '\n return name_cont, name_t end \n '
----------------------------
if type(f_t ) == 'userdata' then --- Запись строки-скрипта в файл ----
f_t: write (scr1)
for i = 1, #t_str do f_t:write (t_str[i]) end
f_t: write (scr2)
f_t:write (scr3)
for k, v in pairs ( context_t ) do f_t:write ( '\n [ [[' .. k .. ']] ] = [[' .. v ..']],' ) end
f_t:write (scr5)
f_t:write (scr4)
for k, v in pairs ( name_t ) do f_t:write ( '\n [ [[' .. k .. ']] ] = [[' .. v ..']],' ) end
f_t:write (scr5)
f_t:write (scr6)
f_t:close()
return
else ----------------------
local s_a = {} --- для сбора строк ----
----
local s = scr1 .. m_str_to_str_fast ( t_str)
s_a[ #s_a + 1 ] = scr2
s_a[ #s_a + 1 ] = scr3
for k, v in pairs ( context_t ) do s_a[ #s_a + 1 ] = '\n [ [[' .. k .. ']] ] = [[' .. v ..']],' end
s_a[ #s_a + 1 ] = scr5
s_a[ #s_a + 1 ] = scr4
for k, v in pairs ( name_t ) do s_a[ #s_a + 1 ] = '\n [ [[' .. k .. ']] ] = [[' .. v ..']],' end
s_a[ #s_a + 1 ] = scr5
s_a[ #s_a + 1 ] = scr6
s_a = m_str_to_str_fast ( s_a) -- массив строк -> строка
-----
s = s .. s_a
if ph_f == 1 then
return s --- Строка-скрипт ---
else
return load ( s ) -- !!! байт-код скрипта (два результата) ---
end
end
------
end
----------------------------------------------------------------------
--- str_to_tbl (<Байт-код скрипта для восстановления <T>, либо строка-скрипт)>[, <Контекст>] )
--- Результаты. Их может быть один или три (если есть висячие ссылочные значения):
-- 1-й - восстановленная таблица с gjvtnrjq висячих ссылок, если такие существуют в виде составных имен в контексте сереализации.
-- Второй и третий – nil, если висячих ссылок нет или две таблицы:
-- - таблица висячих ссылок с указанием составных имен-адресов в кон-тексте сереализации;
-- - таблица висячих ссылок с указанием составных имен –адресов в <T>.
-- В таблицах висячих ссылок есть данные, облегчающие восстановление этих ссылок.-
------
local function str_to_tbl (str_fun, context)
--MessageDb ( 2, 0, 'Скрипт', ' str_fun str_fun: \n' .. dump_str ( str_fun , ' ', 0))
if type (str_fun) ~= 'string' and type (str_fun) ~= 'function' then
return nil, '! Ошибка во 2-м параметре ф-ции str_to_tb (он должен быть типа string или function )'
end
local err
if type (str_fun) == 'string' then
str_fun , err = load ( str_fun ); if err ~= nil then return nil, err end
end
---------
local cont_t, tbl_t = str_fun ()
creating_link_tables ( context )
--- Проверка существования ссылочных значений таблицы -----
local cont_err, t_err = {}, {}
local N_err = 0
local v_tm
--- Первый этап разрешения ссылок ---
for k, v in pairs ( cont_t ) do
v_tm = aliases_v_cont [ v]
if v_tm then --- есть ссылочное значение ---
cont_t [k] = v_tm
else
N_err = N_err + 1
cont_err [ k] = v
t_err [ k] = tbl_t [k]
end
end
--- Второй этап разрешения ссылок ---
if N_err > 0 then
local cont_err_tm = cont_err
for k, v in pairs ( cont_err_tm) do
local i_mt = # v
local v_tm, yes
for i = i_mt , 1 do if v: sub(i, i) == ',' then i_mt = i; break end end
if i_mt <= # v then
for i = i_mt - 1 , 1 do if v: sub(i, i) == ',' then i_mt = i; break end end
i_mt = i_mt + 1
v_tm = v: sub(i_mt)
yes = nil
for k_k, v_v in pairs ( aliases_v_cont ) do ---
if k_k: find( v_tm, i_mt) then
cont_t [k] = v_v; yes = 1 break
end
end
if yes then
N_err = N_err -1
cont_err [ k] = nil
t_err [ k] = nil
end
end
end
end
-----
if N_err > 0 then
return str_fun (cont_t), cont_err, t_err
else
return str_fun (cont_t)
end
end
-----
return { tbl_to_str = tbl_to_str, str_to_tbl = str_to_tbl, creating_link_tables = creating_link_tables, dump_tbl = dump_tbl , m_str_to_str_fast = m_str_to_str_fast } ---
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Владимир написал: сам Квик, сервер брокера, к которому обращения идут через провайдера и (в теории) биржа. Ошибки и рассогласования возможны при движении по всей этой цепочке, причём в обе стороны.
Все это надо учитывать. А какие претензии к Lua?
Цитата
Владимир написал: Сам факт, что "двухуровневость была заложена в проект языка и реализована изначально" уже доказывает, что Lua не есть хороший язык, а эта "интегрированность с C/C++ имеет целый ряд весьма неприятных следствий,из-за которых я и писал с самого начала скрипт на чистейшем Lua,
Вы сами себя опровергаете. Вы ни с чем не желаете иметь дело кроме Lua и вполне этим обходитесь (при этом не мало покувыркашись с реализацией диалога на таблицах QUIK). Я после того как увидел то, что собой представляет собой архитектура QUIK в части обработки колбеков, а также в части реализации таблиц QUIK (не путать с таблицами Lua) имел возможность, используя второй уровень Lua, потроить то, что считал нужным для себя. Все почти счастливы. А вы вы утверждаете, что двухуровневость это недостаток. После того, как у вас в портфеле появится 1 000 000 акций, вам не надо выбрасывать разработанный вами код, Достаточно будет дополнить его некоторой частью на C, который вам знаком
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Артем написал: Язык отличный и документация исчерпывающая - всё верно.
Добавлю свои «пять копеек». Чтобы было понятно, что у меня есть с чем сравнивать, отмечу: Lua не первый язык, с которым я знаком, и даже не пятый. Далее коротко. 1. Lua, по сути, двухуровневый язык: Lua/C. Эта двухуровневость была заложена в проект языка и реализована изначально (Lua тесно интегрирован с C/C++). Таким образом никаких ограничений ни на функиональность, разрабатываемых программ, ни на их эффективность, языком не накладывается. 2. Сам Lua очень компактный, как по исполняемому коду, так и по его описанию, достаточного для его начального использования. Особенно это заметно на фоне многочисленных монструозных средств разработки программ. Пожалуй, отношение <функциональности Lua> к <его компактности> одно из самых высоких среди известных мне языков программирования. Для хорошего понимания Lua, в основном, достаточно четкого представления об устройстве ассоциативной таблице, которая используется, в том числе, и для хранения всех переменных самой программы Lua. Переменные всех типов Lua это всего лишь типы полей в таких таблицах. В переводе на другие известные языки: обычные таблицы Lua это динамичный список свойств, а метатаблицы - это методы, связанные с этими свойствами. И это, почти все, что достаточно знать, чтобы хорошо разбираться в остальных деталях Lua (в областях видимости переменных и т.д.). Профессионал, наверное, сможет изучить этот язык, почти в полном объеме, дня за два-три. Для начинающих программировать это язык тоже, наверное, один из самых простых. Если делать в программах на Lua проверку типов параметров (а может быть и областей значений) более-менее содержательных функций, то можно повысить и надежность разрабатываемых на Lua программ.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Владимир написал: НАДО! Причём не только автору, но и нам! Если автор нас не устраивает, а тема интересная, заводим свою ветку по той же тематике и ведём дискуссию уже там.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Владимир написал: Автор ветки как никто заинтересован в её качестве, а также в притоке туда других участников! Ему и вожжи в руки! Бразды правления, в смысле. Он и будет убирать флуд и поощрять предметные дискуссии.
Автор сможет убрать и комментарии по существу, но не приятные для него. А нам это надо?
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Владимир написал: TGB , А я ещё тогда предлагал наделять хозяина ветки правами её модератора - это снимает почти все вопросы. Реализовано это было много позже, на Фейсбуке, но не как замену модераторам, а как дополнение к ним. Результат - налицо: ФБ тоже фактически угроблен!
А вот это совершенно лишнее. Называется это перегибы на местах .
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Владимир написал: TGB , Это путь в тупик. Я в Инете с 1992 года, и я ещё помню времена, когда никаких модераторов вообще не было. Качество дискуссий при этом было на порядок выше и обеспечивалось самими участниками. А угроблено это качество было в начале "нулевых", и угроблено именно модераторами.
Ну вы анархист и романтик . Жизнь меняется и уже у собак появились аккаунты. Конечно, какой-то простор для творчества необходим, и если вы заметили, то для пользователей форума мною в моих указаниях это оставляется . Но когда, для того чтобы прочесть на форуме что-то не тривиальное, приходится пробиваться через сугробы напечатанного это не комильфо.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Что то на форуме много взаимных оскорблений. Зачем? Кому это нужно? Это, в том числе, наносит и имиджевый ущерб разработчику QUIK. Градус общения на любом форуме определяются не пользователями (они бывают очень разные) а хозяином форума. Если бы я был директором , то выдал бы следующие указания: добавить пользователям форума в их профиль закрытые по записи два раздела: предупреждения и благодарности. Кроме того, ввести лимит количества символов, вводимых пользователем в течении месяца, исключив из него коды программ. В правила форума добавить пункт, определяющий основания для блокировки (на какой то срок), а также автоматическое удаления учетной записи пользователя на основании данных раздела предупреждения. Установить для предупреждений годовой срок давности, после которого они автоматически снимаются. --------- Удалять учетную запись пользователя без некоторой формализации, наверное, было бы вольюнтаризмом .
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Владимир написал: если нулевой байт считается терминатором, то "a" и "a\0b" И В САМОМ ДЕЛЕ "эквивалентные строки" (но разные массивы данных).
В в строке Lua нулевой байт не считается терминатором. Если мы что то разрабатываем в Lua, то приходится учитывать среду разработки, даже если она нам не нравится.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
TGB написал: Так как символ \0 в Lua это признак завершения строки, то для Lua "a" и "a\0b" - эквивалентные строки.
В этом я ошибся.
Цитата
Артем написал: Lua использует бинарные строки с указанной длиной. Указанные строки - разные, и Lua будет их считать разными; при выгрузке в файл значения разные (и совпадающие со входящими). То что они отобразились одинаково это вероятно из-за того, что в используемом терминале используется С стандарт строк с нуль-терминатором.
Замечание верное, действительно, в своем выводе в файлы и на экран я придерживался стандарта С и с учетом того, что стандарт Lua другой это моя ошибка.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Артем написал: TGB , я ассерт на копии значений поставил в функцию инвертирования таблицы, но не упомянул. Так как нужно это только для дебага ручных операций, то не стал заморачиваться с такими вещами как вторичная инвертированная таблица для проверки уникальности по месту.
Есть хорошо известный принцип любой разработки (и не только программ): контроль должен выполняться в том месте, в котором используются данные, а не известно где. Это делается хотя бы потому, никто не знает требований к данным. лучше чем потребитель. -----
Цитата
Артем написал: Генерировать сериализированные файлы в формате исполняемого кода это что называется все фломастеры на вкус разные. Вывод всё равно получается кашеобразный и нечитаемый.
Зря вы это написали, вынудив меня ответить. Я нигде не утверждал, что предложенный мною вариант реализации обсуждаемого модуля нацелен на виализацию таблиц. Хотя, мне кажется, что вы понимаете, что нет проблем добавить в этот вариант отступы вложенности и комментарии и это сильно повысит качество виализации. Но опять же, я не про это. Я про другое:
Цитата
TGB написал: 2) str_to_tbl - короче аналогичной loadstr в ~10 раз. .
На самом деле, как я вас понимаю, но возможно ошибочно, ваша квалификация позволяет вам быстро достаточно реально оценивать программы (напмсанные, во всяком случае, на Lua). Здесь же. похоже, имеет фактор, отмеченный в моем филосовском отступлении:
Цитата
TGB написал: P.S. Все мы, время от времени, в чем-то ошибаемся. Существенная разница состоит только в том, что одни больше усилий предпринимают для устранения своих ошибок, а другие на их оправдание и надувание щёк.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Артем написал: С алиасингом булевых значений в глубоких таблицах действительно была ошибка которую надо исправить, хотя в целом некритичная - булы они и в африке булы, под каким именно алиасом они сидели это неважно.
Я еще раз посмотрел addalias ( item, path, deep, seen ) и не увидел контроля уникальности path (с выдачей сообщения об ошибке в каком-то виде). Делать это надо отдельно от уже реализованного контроля уникальности item.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Артем написал: TGB , в Lua имеется встроенный компилятор который производит бинарный байткод, который может выполняться в виртуальной машине Lua. и т.д ..........
Это мне известно.
Похоже, мы обсуждаем:
Цитата
TGB написал: То, что делается в исходном, обсуждаемом модуле, можно сделать, как мне кажется, элегантнее, формируя в качестве строки образа таблицы, текст исполняемого скрипта. Результат такого подхода: 1) tbl_to_str на ~20% - длиннее, чем dumpstr; 2) str_to_tbl - короче аналогичной loadstr в ~10 раз. .
Здесь рассмотрен вариант когда результатом выполнения функции tbl_to_str (аналога dumpstr) является строка-скрипт вида: local TBL = {} TBL.table00000263878C31B0 = {} --- создание таблицы -- TBL.table00000263878C31B0 [ [[expansion]] ] = [[txt]] TBL.table00000263878C31B0 [ [[INTERVAL_W1]] ] = 10080 TBL.table00000263878C31B0 [ [[QTABLE_DOUBLE_TYPE]] ] = 2 TBL.table00000263878C31B0 [ [[INTERVAL_M5]] ] = 5 ---- и так далее. return TBL.table00000263878C31B0 ------- Функцию str_to_tbl я приведу полностью: --- Рузультат функции: таблица полученная из строки s или текст описания ошибки --- local function str_to_tbl (s ) do local tt= { } for k, v in pairs ( alias ) do tt[ v ] = k end alias = tt end --- alias: переворот: alias = { [ <Имя> ] = <Значение (> } local fun , err = load ( s ) if err ~= nil then return err else return fun() end end --------------------
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Артем написал: elseif kt == 'boolean' then kcurr = alias[ k ] or k == 'true' elseif kv == 'boolean' then v = alias[ v ] or v == 'true'
С этим согласен.
Цитата
Артем написал: Я понял в чём проблема с метатаблицами: возможны ситуации, когда метатаблица загружается до того, как исползующий ей стол, тогда метаметоды будут активны сразу. [QUOTE] Именно о такой ситуации я написал в комментарии кода. [QUOTE]Артем написал: Метаметоды можно обойти через rawset.
Тоже нормальный (и более простой) вариант.
Цитата
Артем написал: При желании можно с байткодом конечно шаманить но с функциями на С это всё равно не работает, так что ради единообразности и Lua функции тоже не поддерживаются.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Артем написал: TGB ,про бинарные строки вам уже рассказали. Собственно эта информация прямым текстом написана в третьем параграфе первой главы, т.е. в самом начале инструкции - которую вы очевидно не читали. Так что пожалуйста прочитайте, целиком и полностью. Перед тем как делать библиотеки для других людей, сначала убедитесь что ваши познания и умения находятся на уровне повыше плинтуса.
После этого комментария, когда у меня появилось время, мне (сразу, без переобувания в тапочки ) захотелось поучиться у вас, и посмотреть тот код, который вы тут выложили. Выложенный вами модуль, в целом, написан в хорошем стиле. Если вы это сделали сами, то, похоже на то, что Lua вы знаете. Однако, несколько настораживает то, что в модуле нет ни одного комментария и много операторов одной строкой. Такое ощущение, что это написано сразу начисто (и без отладки). Я бы так не смог . При более детальном просмотре я увидел, что в представленном вами модуле есть ошибки (я сделал правки в 8-ми местах). Чего я не ожидал так это то, что в нем не учтено существование в Lua типа boolean (смотрите правки в коде модуля). Этот тип хорошо описан в любезно предложенной вами инструкции на сайте https://www.lua.org/manual/5.3/ в разделе 2.1 (в 1.3 этого описания не ищите). Я поправил в модуле те ошибки, которые, как мне кажется, увидел (но возможно и добавил свои ). Понятно, что какие-то ошибки могли и остаться. Свои правки я как-то прокомментировал, выделив существенные изменения символами ###. Модифицированный код модуля выложен в конце данного комментария. Кстати, я проверил быстродействие dumpstr модуля на своем ПК и моем _G (понятно, что у нас с вами в этом могут быть большие различия). Мой dump_str (написанный два года назад, в тренировочных целях, когда мне пришлось перейти на QUIK) выполняется (на таблице _G) в 3 раза быстрее, чем dumpstr модуля, но это мелочи. У представленного вами модуля назначение другое (в том виде как он есть, это точно не визуализация) и его функциональность богаче. ---- То, что делается в исходном, обсуждаемом модуле, можно сделать, как мне кажется, элегантнее, формируя в качестве строки образа таблицы, текст исполняемого скрипта. Результат такого подхода: 1) tbl_to_str на ~20% - длиннее, чем dumpstr; 2) str_to_tbl - короче аналогичной loadstr в ~10 раз. . -------------------------------- P.S. Все мы, время от времени, в чем-то ошибаемся. Существенная разница состоит только в том, что одни больше усилий предпринимают для устранения своих ошибок, а другие на их оправдание и надувание щёк. ------- Измененный код обсуждаемого модуля:
Код
local aliases = { }
local function escape ( s )
s = tostring ( s ):gsub ( "\n", "\\n" )
return s
end
local function dumpstr ( t, alias, seen )
alias, seen = alias or aliases, seen or { }
if seen[ t ] then return "" else seen[ t ] = true end
local s, mt = string.format ( "newtable %s\n", alias[ t ] or escape ( t ) ), getmetatable ( t )
if mt then s = string.format ( "%smetatable %s\n", s, alias [ mt ] or escape ( mt ) ) end
local k_t, v_t
for k, v in pairs ( t ) do
k_t, v_t = alias[ k ] or escape ( k ), alias[ v ] or escape ( v )
if k_t == 'charpattern' then v_t = '%[' end ---- ??? --- и без этого все работает, но если этот символ оставить в покое, обрубается виализация результата dumpstr ?
s = string.format ( "%sk %s %s\nv %s %s\n", s, type ( k ), k_t, type ( v ), v_t )
end
if mt then s = string.format ( "%s%s", s, dumpstr ( mt, alias, seen ) ) end
for k, v in pairs ( t ) do
if type ( k ) == 'table' then s = string.format ( "%s%s", s, dumpstr ( k, alias, seen ) ) end
if type ( v ) == 'table' then s = string.format ( "%s%s", s, dumpstr ( v, alias, seen ) ) end
end
return s
end
local function unescape ( s )
s = s:gsub ( "\\n", "\n" )
return s
end
--------------- ! Символами ### выделены измененные места, для исправления ошибок -------
local function loadstr ( s, alias )
alias = alias or aliases
do local t = { } for k, v in pairs ( alias ) do t[ v ] = k end alias = t end
local tlist, tcurr, kcurr, troot = { }, nil, nil, nil
local tmt, ttb
for l in s:gmatch ( "([^\n]+)\n" ) do
local nt = l:match ( "newtable (.+)" )
local mt = l:match ( "metatable (.+)" )
local kt, k = l:match ( "k ([^ ]+) (.+)" )
local vt, v = l:match ( "v ([^ ]+) (.+)" )
if nt then if tmt then setmetatable ( ttb, tmt ); tmt, ttb = nil, nil end --- ### 1. setmetatable выполняется после завершения формирования ttb (смотрите п.2)
tlist[ nt ] = tlist [ nt ] or alias[ nt ] or { }; tcurr = tlist[ nt ]; troot = troot or tcurr; d_nt = nt
elseif mt then tlist[ mt ] = tlist[ mt ] or alias [ mt ] or { }; ttb = tcurr; tmt = tlist [ mt ] -- ### 2. setmetatable нужно выполнять после того как будет полностью сформирована tcurr.
elseif k then ---- Иначе могут срабатывать метаметоды, если метатаблица была сформирована раньше.
if kt == 'table' then tlist[ k ] = tlist[ k ] or alias[ k ] or { }; kcurr = tlist[ k ]
elseif kt == 'number' then kcurr = alias[ k ] or tonumber ( k ) or 0/0
elseif kt == 'string' then kcurr = alias[ k ] or unescape ( k )
elseif kt == 'boolean' then kcurr = alias[ k ] or k ----- ### 3. добавлено (учет типа boolean): elseif kt == 'boolean' then kcurr = alias[ k ] or k ---
else kcurr = alias[ k ] or k ----- ### 4. добавлено (учет:элеметов не входящих в alias) or k ---
end
elseif v and tcurr and kcurr ~= nil then
if vt == 'table' then tlist[ v ] = tlist[ v ] or alias[ v ] or { }; tcurr[ kcurr ] = tlist[ v ]
elseif vt == 'number' then tcurr[ kcurr ] = alias[ v ] or tonumber ( v ) or 0/0
elseif vt == 'string' then tcurr[ kcurr ] = alias[ v ] or unescape ( v )
elseif vt == 'boolean' then tcurr [ kcurr ] = alias[ v ] or v ---- ### 5. добавлено: elseif vt == 'boolean' then tcurr [ kcurr ] = alias[ v ] or v ---
else tcurr [ kcurr ] = alias[ v ] or v ----- ### 6. добавлено: or v ---
end
end
end
if tmt then setmetatable ( ttb, tmt ) end --- ### 7. Выполняется в поледней обработанной таблице (! если у нее есть метатаблица)
return troot
end
local function dumpfile ( file, t, alias, seen )
alias, seen = alias or aliases, seen or { }
if seen[ t ] then return else seen[ t ] = true end
local mt = getmetatable ( t )
file:write ( string.format ( "newtable %s\n", alias[ t ] or escape ( t ) ) )
if mt then file:write ( string.format ( "metatable %s\n", alias [ mt ] or escape ( mt ) ) ) end
for k, v in pairs ( t ) do
file:write ( string.format ( "k %s %s\nv %s %s\n", type ( k ), alias[ k ] or escape ( k ), type ( v ), alias[ v ] or escape ( v ) ) )
end
if mt then dumpfile ( file, mt, alias, seen ) end
for k, v in pairs ( t ) do
if type ( k ) == 'table' then dumpfile ( file, k, alias, seen ) end
if type ( v ) == 'table' then dumpfile ( file, v, alias, seen) end
end
end
local function addalias ( item, path, deep, seen )
if item == nil and path == nil and deep == nil and seen == nil then item, path, deep = _G, "_G", true end
seen = seen or { }
if item == nil or path == nil or seen[ item ] ~= nil then
return
elseif type ( item ) == 'table' and deep then
seen[ item ] = true
for k, v in pairs ( item ) do
if type ( v ) == 'table' then
addalias ( v, string.format ( "%s.%s", path , escape( k ) ), deep, seen )
elseif type ( v ) ~= 'string' and type ( v ) ~= 'number' and type ( v ) ~= 'boolean' then ----- ### 8. добавлено (учет boolean): and type ( v ) ~= 'boolean'
aliases[ v ] = string.format ( "%s.%s", path, escape ( k ) )
end
end
else
aliases[ item ] = path
end
end
addalias ( math.huge, "math.huge" )
addalias ( -math.huge, "-math.huge" )
return { dumpstr = dumpstr, dumpfile = dumpfile, loadstr = loadstr, addalias = addalias }
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
s_mike@rambler.ru написал: в первом примере не раскрыто содержимое таблицы, являющеся ключом. Сериализация это подразумевает. Таблица ключа может содержать вложенные таблицы, которые также должны быть раскрыты.
Я при описании dump_tbl ни разу не упоминал про сериализацию и в этой функции ключи типа table, userdata и function не раскрываются. Для них выдается их служебный код (ссылка).
Цитата
s_mike@rambler.ru написал: во втором случае результат явно неверен. "a" и "a\0b" - совершенно разные строки.
Так как символ \0 в Lua это признак завершения строки, то для Lua "a" и "a\0b" - эквивалентные строки.
Цитата
s_mike@rambler.ru написал: в третьем случае 1.#inf не является допустимым числом lua. Это всего лишь нерегламентированное внутреннее представление, которое не может попадать в результат.
Результат 1.#inf выдает Lua и я не собираюсь переделывать его.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Средства разработки многопоточных скриптов в QUIK., OS_Quesha, свидетельство регистрации в Роспатенте № RU 2020612905. Бесплатная для некоммерческого использования.
Незнайка написал: И там и там конкатенация строк. В чём профит?
В том как я предложил вам собирать строку профита нет. Профит есть при использовании специальной быстрой C-функции сборки строки из массива строк (тоже из API, но С-пакета, который пришлось бы устанавливать). Если собирать без профита, то при выполнении операции <строка1> = <строка1> .. <добавляемая строка> в Lua всякий раз выполняется отказ от предыдущей памяти <строка1> и запрашивается новая память под новое значение <строка1>. В быстрой C-функции сборки строки на C++ реализована эффективная динамическая строка, в которую последовательно добавляются <добавляемые строки> вообще без описанной мною в предыдущем предложении нагрузки на автоматическое управление памятью.