SA:MP lua51.dll

БеzликиЙ

Эксперт в области Аризона РПГ
Автор темы
Проверенный
2,141
1,279
Ебусь тут чутка с copas во имя загрузки картинок в arizona-mimgui. Загрузка картинок работает.

1770396899055.png

Проблема в том, что спустя какое-то время без видимой причины игра берёт и крашится с вот таким трейсом:

1770396985200.png
(нажмите "Да", чтобы посмотреть полный репорт)

По словам Фупа, copas - лучшая из возможных реализаций для асинхронного HTTP/S взаимодействия в Lua. Куда копать?

Аналогичный краш: https://www.blast.hk/threads/79871/

Проблема в том, что у меня нет потоков в хуках. У меня потоки создаются в шести местах:
- строка 65, в функции установки КД для подсказки keyboardHint (аккуратно удаляется при следующем вызове или по завершению задержки)
- строка 83 (такой же)
- строка 99 (аналогично)
- строка 114 (он же)
- строка 206 (main loop библиотеки copas, идёт не более одного раза, потому что напрямую зависит от переменной copas.running)
- строка 984 (поток, в котором загружается изображение - создаётся по одному на картинку в момент её загрузки, очевидно, проблема где-то тут, но она не должна происходить от всего 32 картинок??!?)

Немного поигрался с картинками, во имя реализации инвентаря вынес шестой поток в очередь, ну и добавил main() для собственно обработки этой очереди. Итого у меня на весь скрипт всего семь потоков - main и шесть lua_thread.

Последний такой краш произошёл при загрузке всего ТРЁХ!!! картинок.
1770482846975.png

Нет, я не идиот, я проверяю, запущен ли поток загрузки в очереди, перед тем, как пускать туда новый.
 
Последнее редактирование:
  • Вау
  • Нравится
Реакции: Асука и pastow

БеzликиЙ

Эксперт в области Аризона РПГ
Автор темы
Проверенный
2,141
1,279
Пробуй юзать мьютекс, подозреваю, что у тебя нет синхронизации потока

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

нет, дело определённо в потоках. если вручную скачать все 9790 существующих на данный момент картинок предметов, всё заебись, только фпс проседает до пяти из-за обилия картинок
1770512839217.png


в исходной теме есть комментарий "не запускай потоки так часто":


между потоками загрузки картинок у меня задержка, одна секунда между завершением старого потока и появлением нового 🥴
 
Последнее редактирование:

kernelich

Участник
36
28
Попробуй через библиотеку effil.
Что то на подобии этого.


lua:
function requestRunner()
    return effil.thread(function(httpMethod, url, requestBody)
        local requestLib = require("requests")
        
        local success, response = pcall(requestLib.request, httpMethod, url, {data = requestBody})

        if success then
            response.json, response.xml = nil
            return true, response
        else
            return false, response
        end
    end)
end
function handleAsyncHttpRequestThread(requestThread, successCallback, errorCallback)
    local threadStatus
    local threadError

    repeat
        threadStatus, threadError = requestThread:status()
        wait(0)
    until threadStatus ~= "running"

    if not threadError then
        if threadStatus == "completed" then
            local requestSuccess, response = requestThread:get()

            if requestSuccess then
                successCallback(response)
            else
                errorCallback(response)
            end
            return
        elseif threadStatus == "canceled" then
            return errorCallback(threadStatus)
        end
    else
        return errorCallback(threadError)
    end
end
function asyncHttpRequest(httpMethod, url, requestBody, successCallback, errorCallback)
    local requestThread = requestRunner()(httpMethod, url, requestBody)

    successCallback = successCallback or function() return end
    errorCallback = errorCallback or function() return end

    return {
        effilRequestThread = requestThread,
        luaHttpHandleThread = lua_thread.create(handleAsyncHttpRequestThread, requestThread, successCallback, errorCallback)
    }
end

gui.WebImage = function(url, size)
    local iid = string.gsub(url, ".*arizona%-rp", "")
    local cachepath = cachedir .. iid

    if not imagesbuffer[iid] then
        if doesFileExist(cachepath) then
            imagesbuffer[iid] = gui.CreateTextureFromFile(cachepath)
        elseif not imagesthreads[iid] then
            imagesthreads[iid] = true
            
            asyncHttpRequest("GET", url, nil,
                function(response)
                    if response.status_code == 200 then
                        local dir = cachepath:match("(.*[/\\])")
                        if dir and not doesDirectoryExist(dir) then createDirectory(dir) end

                        local f = io.open(cachepath, 'wb')
                        if f then
                            f:write(response.text)
                            f:close()
                            imagesbuffer[iid] = gui.CreateTextureFromFile(cachepath)
                        end
                    end
                    imagesthreads[iid] = false
                end,
                function(err)
                    print("Error: " .. tostring(err))
                    imagesthreads[iid] = false
                end
            )
        end
    end

    if imagesbuffer[iid] then
        gui.Image(imagesbuffer[iid], size)
    else
        gui.Dummy(size)
    end
end
 

constersuonsis

Известный
265
164
Где то с полгода назад столкнулся с такой проблемой и в самп и в раксамп. Возможно изобрёл велосипед, но оно работает стабильно, учитывая что запросы отправляются каждые 3 сек и бывает пачками.
???:
local http = require("socket.http")
local ltn12 = require("ltn12")
local requests = require("requests")
local cjson = require("cjson")
local socket = require("socket")

-- usage:
-- local temp_headers = {
--        ["Content-Type"] = "application/json"
-- }
-- local r = handleResponse("https://api.???.su/", "POST", cjson.encode({}), temp_headers)

function async_http_request(url, request_data, callback, proxy)
    coroutine.wrap(function()
        local request_params = {
            url = url,
            method = "POST",
            source = ltn12.source.string(request_data),
            headers = {
                ["Content-Type"] = "application/json",
                ["Content-Length"] = tostring(#request_data)
            },
            sink = ltn12.sink.string
        }
        if proxy then
            request_params.proxy = proxy
        end

        local response_body, status_code = http.request(request_params)

        if callback then
            if status_code then
                callback(true, response_body, status_code)
            else
                callback(false, nil, nil)
            end
        end
    end)()
end

function async_request(url, method, request_data, headers, proxy)
    coroutine.wrap(function()
        local options = {
            url = url,
            data = request_data,
            headers = headers
        }
        if proxy then
            options.proxy = proxy
        end

        local success, response = pcall(requests.request, method, options)

        if success and response then
            print(response.status_code, url, success)
            if response.status_code == 200 then
                print("Success: " .. response.text .. " | URL: " .. url)
                return true
            else
                print("Error: Status Code: " .. tostring(response.status_code) .. " | URL: " .. url .. " | Body: " .. tostring(response.text))
                return false
            end
        else
            print("ERROR: Request failed. URL: " .. url .. " | Error: " .. tostring(response))
            return false
        end
    end)()
end

function handleResponse(url, method, request_data, headers, proxy)
    local options = {
        data = request_data,
        headers = headers
    }
    if proxy then
        options.proxy = proxy
    end

    local success, response = pcall(requests.request, method, url, options)

    if not success then
        return { status_code = nil, text = tostring(response), json = nil }
    end

    local json_data = nil
    if response and response.text and #response.text > 0 then
        local json_success, result = pcall(cjson.decode, response.text)
        if json_success then
            json_data = result
        end
    end

    return {
        status_code = response.status_code,
        text = response.text,
        json = json_data
    }
end
 

БеzликиЙ

Эксперт в области Аризона РПГ
Автор темы
Проверенный
2,141
1,279
  • Грустно
Реакции: potrillity

XRLM

Против ветра рождённый
Модератор
1,659
1,340
ты уверен, что крашит из-за запросов? когда я грузил море картинок, я столкнулся с проблемой, что спустя некоторое время игру крашит. я выяснил, что это из-за нехватки памяти в игре, 4гб памяти довольно быстро улетают.
пока что мое временное решение - это сжатие картинок до разрешения 100х100 (я просто больше не юзаю в скрипте), это очень понизило расход памяти моим скриптом.
еще одно решение, которое использует редкод в чате совместно с первым - выгружать картинки, если они не использовались какое то время после последнего рендеринга картинки. я пока это не реализовывал, руки не доходят, но тоже должно хорошо помогать.
если что я использую модифицированный мною сниппет космо https://www.blast.hk/threads/13380/page-43#post-1517490, я просто еще добавил очередь на загрузку картинок и несколько дополнительных проверок, чтобы картинка всегда гарантированно была подгружена, думаю ни для кого не проблема сделать так же (мне просто лень кодом делиться).

насчет запросов, использую самописную библиотеку на плюсах для луа, которая использует boost asio + beast для сокетов и http запросов, но на луа по моему мнению лучший способ этот (автор вроде егдвач):
Lua:
local effil = require('effil')

function requestRunner()
    return effil.thread(function(method, url, args)
        local requests = require 'requests'
        local _args = {}
        local function table_assign(target, def, deep)
            for k, v in pairs(def) do
                if target[k] == nil then
                    if type(v) == 'table' or type(v) == 'userdata' then
                        target[k] = {}
                        table_assign(target[k], v)
                    else
                        target[k] = v
                    end
                elseif deep and (type(v) == 'table' or type(v) == 'userdata') and (type(target[k]) == 'table' or type(target[k]) == 'userdata') then
                    table_assign(target[k], v, deep)
                end
            end
            return target
        end
        table_assign(_args, args, true)
        local result, response = pcall(requests.request, method, url, _args)
        if result then
            response.json, response.xml = nil, nil
            return true, response
        else
            return false, response
        end
    end)
end

function handleAsyncHttpRequestThread(runner, resolve, reject)
    local status, err
    repeat
        status, err = runner:status()
        wait(0)
    until status ~= 'running'
    if not err then
        if status == 'completed' then
            local result, response = runner:get()
            if result then
                resolve(response)
            else
                reject(response)
            end
        return
        elseif status == 'canceled' then
            return reject(status)
        end
    else
        return reject(err)
    end
end

function asyncHttpRequest(method, url, args, resolve, reject)
    if type(method) ~= 'string' then
        return print('"method" expected string')
    elseif type(url) ~= 'string' then
        return print('"url" expected string')
    elseif type(args) ~= 'table' then
        return print('"args" expected table')
    end
    local thread = requestRunner()(method, url, effil.table(args))
    if not resolve then resolve = function() end end
    if not reject then reject = function() end end

    return {
        effilRequestThread = thread;
        luaHttpHandleThread = lua_thread.create(handleAsyncHttpRequestThread, thread, resolve, reject);
    }
end