1 (2019-11-30 13:29:20 отредактировано toxa)

Тема: библиотека lua_share (обмен данными между скриптами lua)

данная либа - альтернатива StaticVar. не то, чтобы она мне не нравилась, просто возникла идея использовать отдельный стейт луа в качестве хранилища, что позволяет делать с ним разные прикольные штуки. в том числе, обмениваться данными между несколькими терминалами QUIK.

собранная либа и исходники тут: https://www.dropbox.com/s/b72hrelmcclcg … e.zip?dl=0
публичный репозиторий на гитхабе: https://github.com/untoxa/lua_share

пожелания? предложения? welcome, в каменты.


ОПИСАНИЕ:
========

комплект состоит из библиотеки lua_share.dll и файла lua_share_boot.lua, который должен находиться в том же каталоге. если файл lua_share_boot.lua отсутствует, то библиотека ведет себя несколько иначе, но тоже работает, о чем ниже.

инициализация:
-------------------

package.cpath = getScriptPath() .. "\\lua_share.dll"
sh = require "share"

запись и чтение:
-------------------

sh["hello"] = "world" -- запись
val = sh["hello"]     -- чтение

пространства имен:
-----------------------

local ns = sh.GetNameSpace("test_name_space")  -- создать пространство имен test_name_space
ns["hello"] = "world" -- запись 
val = ns["hello"]     -- чтение

получение снапшота:
-------------------------

local ns = sh.GetNameSpace("test_name_space")  -- создать пространство имен test_name_space
ns["hello"] = "hello" -- 1 значение
ns["world"] = "world" -- 2 значение
val = ns:DeepCopy() -- получение снапшота

"bootstrap":
-------------
файл lua_share_boot.lua содержит код, который кастомизирует поведение хранилища. в текущей реализации реализовано сравнение таблиц-ключей по содержимому. например:

local ns = sh.GetNameSpace("test_name_space")
ns[{1, 2, {3, 4}}] = "{1, 2, {3, 4}}"
ns[{1, 2, {3, 4}}] = "{1, 2, {3, 4}} overwrite"
tmp = ns[{1, 2, {3, 4}}]

если файл lua_share_boot.lua существует, то в результате значение tmp будет содержать только строку "{1, 2, {3, 4}} overwrite", иначе обе строки: "{1, 2, {3, 4}}" и "{1, 2, {3, 4}} overwrite", так как ключ {1, 2, {3, 4}} - это всегда копия, а по-умолчанию сравниваются ссылки.
в lua_share_boot.lua можно запрограммировать свое поведение, а так же добавить свои метаметоды, например __gc. см комментарии в коде lua.

IPC:
-----
есть возможность создавать "удаленное" пространство имен, общие для нескольких запущенных приложений (терминалов QUIK). для этого необходимо запустить сервер lua_share_server.exe, который хранит общие данные. сервер запускает lua-скрипт, который хранится в файле lua_share_server.lua и который можно, при желании, кастомизировать. общее хранилище существует, пока запущен сервер.
удаленное пространство имен создается следующим образом:

local ns = sh.GetIPCNameSpace("test_name_space")

способ работы с ним не отличается.

2 (2019-11-24 21:45:31 отредактировано toxa)

Re: библиотека lua_share (обмен данными между скриптами lua)

вот еще пример, почему такая реализация может быть полезной: "очереди".

чтобы добавить функционал очередей в lua_share, создаем специальный pre-defined неймспейс queues (добавим этот код в конец lua_share_boot.lua):

-- predefined "queues" namespace implementation
-----------------------------------------------
queues = {
    __data = {},
    __new = function()
        return {first = 0, last = -1}
    end,
    __push = function(list, value)
        if list.last == nil then
            list.last = -1
            list.first = 0
        end
        local last = list.last + 1
        list.last = last
        list[last] = value
    end,
    __pop = function(list)
        if list.first == nil then
            list.last = -1
            list.first = 0
        end
        local first = list.first
        if first > list.last then return nil end
        local value = list[first]
        list[first] = nil
        list.first = first + 1
        return value
    end
}

setmetatable(queues, {
    __newindex = function(self, key, value)
        local idx = nil
        if type(key)~="table" then
            idx = key
        else
            idx = __findkey(self.__data, key)
            if not idx then idx = key end
        end
        local queue = self.__data[idx]
        if queue == nil then
            queue = self.__new()
            self.__data[idx] = queue
        end;
        self.__push(queue, value)
    end,
    __index = function(self, key)
        local idx = nil
        if type(key)~="table" then
            idx = key
        else
            idx = __findkey(self.__data, key)
        end
        if idx then
            local queue = self.__data[idx]
            if queue ~= nil then
                return self.__pop(queue)
            end
        end
        return nil
    end
})

теперь в одном из скриптов/потоков пишем:

package.cpath = getScriptPath() .. "/?.dll"
sh = require "lua_share"

function main()
    local ns = sh.GetNameSpace("queues") -- get predefined "queues" namespace
    local i = 0
    while not exitflag do
        i = i + 1
        ns["queue1"] = i                   -- queue some payload
        sleep(100 + math.random(900))      -- 10 times per second or less
    end
end
function OnStop()
    exitflag = true
end

а в другом - вычитываем, что нам послали:

package.cpath = getScriptPath() .. "/?.dll"
sh = require "lua_share"

function main()
    local ns = sh.GetNameSpace("queues") -- get predefined "queues" namespace
    while not exitflag do
        local data = "{"
        local i = ns["queue1"]
        while i ~= nil do
            data = data .. tostring(i) .. ","
            i = ns["queue1"]
        end
        data = data .. "}"
        message("Received: " .. data, 1)
        sleep(1000)
    end
end
function OnStop()
    exitflag = true
end

можно взаимодействовать через сколько угодно очередей:

...
ns[100500] = "push something"
ns[100500] = "push something else"
...
data1 = ns[100500]
data2 = ns[100500]

все это взаимодействие, естественно, thread safe, ничего не потеряется, и не будет никаких race conditions. с точки зрения квика все операции с lua_share атомарны.

3 (2019-11-25 00:06:11 отредактировано toxa)

Re: библиотека lua_share (обмен данными между скриптами lua)

склеивание событий в очередях по значению: неймспейс "eventlists".

предположим, читатель успевает обработать стакан за секунду, а за эту секунду ему писатели наваливают еще 10 таких же событий. в eventlists эти 10 событий склеятся в одно. внимание: порядок - не fifo, то есть это не совсем очередь, а, скорее, список! так сделано для скорости и простоты, но можно легко переделать на нормальное fifo.

еще идеи?

4

Re: библиотека lua_share (обмен данными между скриптами lua)

исправлена досадная ошибка, если кто-то успел скачать - скачайте заново.

5 (2019-11-27 19:04:31 отредактировано toxa)

Re: библиотека lua_share (обмен данными между скриптами lua)

сделал неймспейс "permanent". он загружается при старте и сохраняется, когда последний скрипт отпустит lua_share:

в конец lua_share_boot.lua добавляем:

-- predefined "permanent" namespace implementation
-- namespace with load/save
--------------------------------------------------
function table.val_to_str(v)
    if "string" == type(v) then
        v = string.gsub(v, "\n", "\\n")
        if string.match(string.gsub(v,"[^'\"]",""), '^"+$') then
            return "'" .. v .. "'"
        end
        return '"' .. string.gsub(v,'"', '\\"') .. '"'
    end
    return "table" == type(v) and table.tostring(v) or tostring(v)
end
function table.key_to_str(k)
    if "string" == type(k) and string.match(k, "^[_%a][_%a%d]*$") then
        return k
    end
    return "[" .. table.val_to_str(k) .. "]"
end
function table.tostring(tbl)
    if type(tbl)~='table' then return table.val_to_str(tbl) end
    local result, done = {}, {}
    for k, v in ipairs(tbl) do
        table.insert(result, table.val_to_str(v))
        done[k] = true
    end
    for k, v in pairs(tbl) do
        if not done[k] then
            table.insert(result, table.key_to_str(k) .. "=" .. table.val_to_str(v))
        end
    end
    return "{" .. table.concat(result, ",") .. "}"
end
function table.load(fname)
    local f, err = io.open(fname, "r")
    if not f then return {} end
    local fn, err = loadstring("return "..f:read("*a"))
    f:close()
    if type(fn) == "function" then
        local succ, res = pcall(fn)
        if succ and type(res) == "table" then return res end
    end
    return {}
end
function table.save(fname, tbl)
    local f, err = io.open(fname, "w")
    if f ~= nil then
        f:write(table.tostring(tbl))
        f:close()
    end
end

permanent = {
    __data = table.load("lua_share.permanent.dat"),
}
__permanent_metatable = {
    __newindex = __default_namespace_metatable.__newindex,
    __index = __default_namespace_metatable.__index,
    __gc = function(self)
        table.save("lua_share.permanent.dat", self.__data)
    end
}
if _VERSION == "Lua 5.1" then
    local t = permanent
    local proxy = newproxy(true)
    getmetatable(proxy).__gc = function(self) __permanent_metatable.__gc(t) end
    permanent[proxy] = true
end
setmetatable(permanent, __permanent_metatable)

тестовый скрипт:

package.cpath = getScriptPath() .. "/?.dll"
sh = require "lua_share"

function default(v, defv)
    if v~= nil then return v end
    return defv 
end

function main()
    local ns = sh.GetNameSpace("permanent")
    message("loaded: " .. tostring(ns[{4, 5, 6}]), 1)
    ns[{4, 5, 6}] = default(ns[{4, 5, 6}], 0) + 1
end

между запусками данные сохраняются в файл lua_share.permanent.dat, который текстовый внутри и выглядит примерно так:

{[{4,5,6}]=18}

6

Re: библиотека lua_share (обмен данными между скриптами lua)

приветствую!
тоха, огромное спасибо! вот накидал "небольшой" тест.
https://www.dropbox.com/s/thjnna08dw1rv … e.zip?dl=0
качаем, все настройки при создании объекта заявки в конструкторе QL в keepvartest.lua
порядок добавления и запуска:
keepvar.lua
keepvartest.lua

преимущества использования lua_share:
-функции обратного вызова объявлены в одном скрипте;
-точечная передача события в объект заявки, не зависимо в каком боте этот объект создан;
-много лучше и не уступает в скорости table.sinsert/sremove;
-возможность загрузки последнего актуального состояния объекта из пространства имен(в примере не используется)

недостатки lua_share: -не замечено

З.Ы.
реализация класса умной заявки упрощенная, только для примера, применять в своих боевых торговых алгоритмах не рекомендую.

тоха, вам респект!

7 (2019-11-29 21:16:39 отредактировано toxa)

Re: библиотека lua_share (обмен данными между скриптами lua)

коллеги, в общем, я все доделал. представляю финальный вариант либы. теперь она поддерживает обмен данными и между квиками тоже.

хранилище держит отдельный процесс lua_share_server.exe, который нужно запустить руками.

сам файл lua_share_server.exe и файл lua_share_server.lua нужно положить в корневую папку quik. сервер подразумевает, что библиотека lua_share.dll находится в папке .\scripts\ относительно корневого каталога квика. если у вас это не так, то нужно поправить или задать свои пути в lua_share_server.lua вот тут:

package.cpath = "/?.dll;./scripts/?.dll"

дальше все работает как и раньше, но если мы хотим открыть "удаленный" неймспейс "на сервере", то вместо

local ns = sh.GetNameSpace("test_name_space")

мы пишем что-то вроде

local ns = sh.GetIPCNameSpace("test_name_space")

все, больше никаких отличий нет.

в lua_share_server.lua можно написать свой собственный код для работы с хранилищем, совершенно не обязательно он должен использовать библиотеку lua_share, мне просто лень было дублировать функционал. по-сути мы получили довольно шуструю in-memory nosql базу данных на основе lua.

ограничения: максимальный передаваемый за один раз объем ограничен 512K, а для простейшего типа этот объем равен 64K. если не устраивает и нужно больше - можете пересобрать либу с любыми нужными значениями, но прежде задумайтесь: если вы передаете такие большие объемы, все ли вы сделали правильно с точки зрения архитектуры своей программы.

в общем, не уверен, что это все кому-то нужно, так как общаюсь я, преимущественно, сам с собой, если будете использовать - отпишитесь чо как.

спасибо за внимание.

8

Re: библиотека lua_share (обмен данными между скриптами lua)

ps: если GetIPCNameSpace() не используется в программе и, вообще, взаимодействие между квиками не требуется, то  lua_share_server.exe можно не запускать.

pps: если кому-то нужна либа, чтобы взаимодействовать с lua_share_server из своих программ без lua вообще, то таковую можно легко написать, код вызова RPC примерно такой:

var   buf_out, buf_in : array[0..1024] of ansichar;
...
with tIPCClient.create(sizeof(buf_out)) do try
  if open then begin
    try
      // make buffer
      codec.startcodec(buf_out, sizeof(buf_out));
      codec.writestring('GetIPC'); // remote procedure name
      codec.writenumber(2);        // remote procedure parameter count
      codec.writestring('hello');  // param1
      codec.writestring('world');  // param2
      // call remote procedure and get result
      send_receive(buf_out, codec.stopcodec, buf_in, received_len, 5000); // timeout = 5s
      // parse results of RPC
      codec.startcodec(buf_in, received_len);
      for i:= 0 to codec.readint(0) - 1 do begin
        t:= codec.read(buf_out, sizeof(buf_out), len);
        case t of
          LUA_TNUMBER: writeln(pdouble(@buf_out)^);
          LUA_TSTRING: writeln(buf_out);
          else         writeln('some type: ', t);
        end;
      end;
    finally close; end;
  end else writeln('unable to open IPC client')
finally free; end;

9

Re: библиотека lua_share (обмен данными между скриптами lua)

я тут провел измерения насколько быстро работает IPCNameSpace.

изначально делалось:
local ns = sh.GetIPCNameSpace("hello")
ns["world"] = "ok"

цикл состоял из:

1. в клиенте отправлялся запрос на получение значения:
tmp = ns["world"]

2. lua_share_server получал этот запрос в lua, вызвал опять же lua_share, откуда брал, заранее туда помещенное, значение "ok", этот ответ отдавался обратно

3. в клиенте парсился ответ

выполнено 100К итераций.

на моем древнем ноуте c i5 без каких-то дополнительных шаманств с приоритетами процессов получается 25мкс (0,025мс) один цикл. если не вызывать библиотеку lua_share второй раз из lua_share_server.lua, а поместить в него код из lua_share_boot.lua, то получается 16мкс. считаю, что это очень неплохой показатель, можно не париться и вообще всегда использовать GetIPCNameSpace().

10

Re: библиотека lua_share (обмен данными между скриптами lua)

toxa пишет:

я тут провел измерения насколько быстро работает IPCNameSpace.

тоха, спасибо за труды!
это очень круто! что можно делать 1 бота для нескольких  терминалов.

11

Re: библиотека lua_share (обмен данными между скриптами lua)

ну и последнее, чтобы закрыть тему: RPC. теперь можно определить свои общие функции в lua_share_server.lua и вызывать их. делается это так:

sh = require "lua_share"
local a, b, c = sh.RPC("testfunc", "a", {1, 2, {3, "b"}})

или так:

local ns = sh.GetIPCNameSpace("test_name_space")
local a, b, c = ns.RPC("testfunc", "a", {1, 2, {3, "b"}})

функция testfunc() просто возвращает переданные в нее параметры. мы передаем два параметра и в результате получаем a == "a", b == {1, 2, {3, "b"}}, c == nil.

вызов этот атомарный для всех квиков.

12

Re: библиотека lua_share (обмен данными между скриптами lua)

kalikazandr пишет:

можно делать 1 бота для нескольких  терминалов.

да, это может быть полезно, например, для доверительных управляющих.

13

Re: библиотека lua_share (обмен данными между скриптами lua)

хочу обратить внимание на телеграм бот на основе этой либы: https://quik2dde.ru/viewtopic.php?id=307

все общение с телеграмом из квика происходит через две lua_share очереди и вообще никак не тормозят квик, в самом квике нет никаких громоздких библиотек что, в общем, повышает его стабильность. и можно через бота рулить любым количеством квиков, запущенных на той же машине.