LINUX.ORG.RU

Луа5.1 и параноидальная оптимизация

 


1

3
local sub, gsub, random = string.sub, string.gsub, math.random
local SendAddonMessage = SendAddonMessage

-- Таблица смещений
local STR_OFFSETS = {}
for i = 1, 100 do
    STR_OFFSETS[i] = (i-1)*3 + 1
end

function time100_Server(channel, text, sender, prefix)
    if prefix ~= "time100" then return end

    local objFull = mFldS:getStaticStr(sender, 1) or ""
    local hpFull = mFldS:getStaticStr(sender, 2) or ""

    local rezT, rezF = {}, {}
    local obj_char, hp_val, formattedHp

    for i = 1, 100 do
        local offset = STR_OFFSETS[i]
        if offset + 2 > #objFull then break end

        obj_char = sub(objFull, offset, offset + 2)
        hp_val = en10(sub(hpFull, offset, offset + 2) or "000")

        if obj_char == "00f" then
            if hp_val < 999 then
                formattedHp = sub("   "..en85(hp_val + 1), -3)
                rezF[#rezF+1] = formattedHp
                mFldS:addStaticStr(sender, 2, i, formattedHp)
            else
                mFldS:addStaticStr(sender, 1, i, "00t")
                mFldS:addStaticStr(sender, 2, i, en85(999))
                rezT[#rezT+1] = "999"
            end
        elseif obj_char == "00t" then
            if hp_val < 999 then
                hp_val = hp_val + 1
            elseif hp_val >= 999 and random(10) == 1 then
                hp_val = hp_val + 1
            end
            formattedHp = sub("   "..en85(hp_val), -3)
            rezT[#rezT+1] = formattedHp
            mFldS:addStaticStr(sender, 2, i, formattedHp)
        end
    end

    if text == "1" then
        local function sendChunked(base, data)
            if #data == 0 then return end
            data = gsub(data, " ", "%0")
            --SendAddonMessage(base.." "..sender, sub(data, 1, 150), "GUILD")
            if #data > 150 then
                --SendAddonMessage(base.."_2 "..sender, sub(data, 151), "GUILD")
            end
        end
        sendChunked("time100_00t", table.concat(rezT))
        sendChunked("time100_00f", table.concat(rezF))
    end
end

Допустим есть такой вот код на луа. Тут мы получаем строку с «объектами». Строка всегда 300 символов, каждый объект всегда 3 символа. Если находим два нужных объекта, итерируем им «здоровье». Записываем изменения в базу и при необходимости отправляем об этом общее уведомление в эфир.

Чтение и изменение строки происходит самописными методами:

-- Добавление или обновление строки
function NsDb:addStaticStr(nik, nStr, nArg, message)
    self.input_table[nik] = self.input_table[nik] or {}

    if not nArg then
        self.input_table[nik][nStr] = message
        return
    end

    -- Обновляем строку
    local currentStr = self.input_table[nik][nStr]
    self.input_table[nik][nStr] = currentStr:sub(1, (nArg - 1) * 3)
                                .. ("   " .. message):sub(-3)
                                .. currentStr:sub(nArg * 3 + 1)
end

function NsDb:getStaticStr(nik, nStr, nArg)
    local str = self.input_table 
              and self.input_table[nik] 
              and self.input_table[nik][nStr]
    
    return str and (nArg and str_sub(str, (nArg-1)*3 + 1, nArg*3) or str)
end

И вот на 10000 проходов задержка где то на секунду и отжор памяти метров на 15.

Избавляться от локальных переменных пробовал - получается криво. Попробовал вон перейти вместо прямого саба и конкатенации на регулярки и матч, всеравно плохо.

А можно ли еще что то тут такое придумать, чтобы одновременно снизить скорость прохода и локальное потребление памяти?

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

Может можно еще что нибудь, что я сейас не вижу?

OBJECT_POSITION_PATTERNS = {}
for i = 1, 100 do  -- Важно: цикл до 100, а не 10!
    OBJECT_POSITION_PATTERNS[i] = "^" .. string.rep("...", i - 1) .. "(...)"
end

Например, я пробовал вот так обращаться к объектам. Но вроде это не лучше.

Короче, решено: самая оптимальная работа на луа со строками через саб. Быстрее и дешевле нет ничего. Прямое обращение саб к подстроке и все. Без локальных переменных и кэширований.

★★★★★

Последнее исправление: LightDiver (всего исправлений: 3)
Ответ на: комментарий от MOPKOBKA

Это то, что видят пользователи. Каждая клетка грядки может быть заменена любым объектом.

https://s.iimg.su/s/24/ncWrlx0ybS5cE0wMN4HbOpsYlr03C1EsPMYkHw7g.jpg

Например, срубил деревья, получил бревна, построил хижину - в хижине квесты, которые нужно выполнить. В данном случае хижина - кнопка для доступа к квестам.

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

А я в итоге могу вводить любой тип объектов пользователям, добавлять любую механику его работы без жестких привязок к кординатам. Объекты могут быть вложены друг в друга, например.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 1)
Ответ на: комментарий от LightDiver

Мощно, но это ты описываешь что происходит %) Но я вообще не могу понять что это такое. Дополнительная игра в игре? Там на фоне какое то 3д фентези судя по всему.

По поводу оптимизации, если нету сложных трансформаций, то можно лишь при открытии этого окна:

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

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

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

Возможно уже предлагали, но я специфику не знаю, не увидел такого по треду.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 3)
Ответ на: комментарий от MOPKOBKA

Есть онлайновая РПГ игра. Где бегают орки, эльфы и прочие гоблины. Это игроки.

Игроки объединяются в гильдии по интересам.

Вот я в рамках такой гильдии даю игрокам задания, они их выполняют, я даю за это награды. Поле - интерфейс работы с этими заданиями.

Тыкнет игрок на хижину, а ему оттуда: «Найди похищенную принцессу, ее похитители утащили в глубины Темнолесья, откуда уже четвертый час доносятся неприличные звуки, о герой!». И идет он ее искать и спасать. Спас, получил опыт и сумку на 20 ячееек условно.

Про таймеры все сложнее. Траваи деревья должны расти только пока игрок в игре. Онлайн. Поэтому нельзя просто так итерировать на сервере. Я придумал, что игрок раз в 100 секунд отправляет серверу запрос. Получил запрос - проитерировал объекты.

Количество запросов в секунду и их размер очень серьезно ограничены… Считать конечный результат тоже не получится из за неравномерного онлайна игроков. Один может зайти на 5 минут, второй на полчаса итд.

Вообще раньше все это было на клиентах игроков. Вся логика была там. Система была распределенной и не требовала сервера. Но они начали читерить. Ибо язык интерпретируемый. Все что находится у игрока - он может менять. Они и начали менять. Пришлось перенести логику на сервер.

Когда у каждого игрока было свое поле, можнобыло просто хэш-таблицами все делать. Там никаких пробелм с этим не было. Мало данных было. А теперь на сервере все данные ВСЕХ игроков. И появились проблемы.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 4)
Ответ на: комментарий от LightDiver

Про таймеры все сложнее. Траваи деревья должны расти только пока игрок в игре. Онлайн.

Ну это не проблема для варианта с обновлением по количеству прошедшего времени, нужно лишь завести переменные «ПрошлоВремени», «ВремяПредыдущегоОткрытия».

При открытии окна, делаем

ВычислитьСостояниеКартеПоПрошедшемуВремени(ПрошлоВремени)

ВремяПредыдущегоОткрытия = ТекущееВремя()
ПрошлоВремени = 0

При отключении делаем

ПрошлоВремени = ПрошлоВремени + (ТекущееВремя() - ВремяПредыдущегоОткрытия)

При подключении

ВремяПредыдущегоОткрытия = ТекущееВремя()

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

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

Ненене. Игрок может рубить деревья. Игрок может собирать траву. А шанс сбора травы напрямую зависит от ее роста. Нельзя просто так взять и пропустить все этапы роста.

Например, если у травы 50хп, шанс сбора 1%. А если 950хп, 90%. Там свои нюансы и нужно все делать п оследовательно.

Так же есть петы. Например бобер, который рубит деревья за игрока. Его тоже придется вносить в этот стосекундный таймер и он будет итерировать хп деревьев в обратную сторону - «рубить их». Там механик много. Эта функция будет усложняться в будущем.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 1)
Ответ на: комментарий от LightDiver

Ну вот он открыл окно, в это время по количеству прошедшего времени вычисляется текущее состояние, и отправляется ему. Он уже видит актуальное количество ХП, и выросшие деревья.

Игрок всегда в этой схеме должен в открытом окне видеть актуальные данные. Но пока он не видит, никаких действий на ней не происходит.

Это не будет работать, если эти деревья есть и в 3d мире. Потому что их можно видеть не открывая окна.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 3)
Ответ на: комментарий от MOPKOBKA

И нужна жесткая синхронизация с сервером. Игрок состояния получает от сервера. Как только чтото завязывается на клиент, они тут же начинают с этим читерить, собаки некрещеные.

У игрока только интерфейс. Открыл - скачались состояния поля. Навел мышь на объект, увидел состояния таких объетков. Все оптимизированно должно быть и не должно быть лишних запросов.

Просто по времени ничего нельзя сделать так просто.

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от LightDiver

И нужна жесткая синхронизация с сервером.

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

Если сервер считает что игрок онлайн, то только в таком случае может набегать время.

У игрока только интерфейс. Открыл - скачались состояния поля. Навел мышь на объект, увидел состояния таких объетков.

Ну я вроде правильно понимаю. Сервер отсылает состояние, клиент присылает действия которые обрабатываются сервером.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

А какая разница? Мне для времени всеравно нужно таймер использовать. Сейчас я испоьзую таймер раз в сто секунд, например. И это будет далеко не единственный таймер, к слову.

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от LightDiver

А какая разница?

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

У тебя получается каждые 100 секунд условно идет работа, у меня только если запрашивается состояние.

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

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

Нужна привязка роста не к действиям игрока, а к онлайну игрока.

Вот представь, игрок зашел в игру и не открыл карту вообще. У него не будет по твоей модели расти трава. А это уже беда..

Да, я могу отслеживать вход игрока в игру, выход. Но это гораздо больше обработки.. Хм.. Мне как раз придется время высчитывать бла бла. Дополнительная возня. Нужно подумать.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 1)
Ответ на: комментарий от LightDiver

Вот представь, игрок зашел в игру и не открыл карту вообще. У него не будет по твоей модели расти трава. А это уже беда..

Такой проблемы в моей модели нету. Ну вот посмотри мой псевдокод сейчас, если он зашел, а потом отключился, то время его онлайна запишется в сумму:

ПрошлоВремени = ПрошлоВремени + (ТекущееВремя() - ВремяПредыдущегоОткрытия)
И в следующий раз когда он зайдет, и все же откроет карту, то в этот момент ты вычислишь по «ПрошлоВремени» актуальное состояние, и он будет думать что пока он не смотрел на карту, все росло.

Но это гораздо больше обработки...

Там нету callback на это действие? По идее тебе всего надо всего два вызова функции за сессию игрока сделать, если он не смотрит карту. А в твоей модели надо каждые 100 секунд (или сколько у тебя там) обновлять состояние.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 3)
Ответ на: комментарий от MOPKOBKA

Нее.. Это ересь какая то. Ты добавляешь кучу лишних сложных вычислений. Неравномерный рост. В моей формуле одно дерево растет примерно за сутки онлайна игрока.

Тут чем проще, тем лучше. Как только мы начинаем усложнять, начинается жопа.

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от LightDiver

Ну вот зашел игрок отыграл пару часов, и отключился, не смотря на карту ни разу. Такую ситуацию разберем.

Какие действия в моей модели? Буквально две строки за всю сессию!

-- Подключение
ВремяПредыдущегоОткрытия = ТекущееВремя()

-- Отключение
ПрошлоВремени = ПрошлоВремени + (ТекущееВремя() - ВремяПредыдущегоОткрытия)
Две установки числовой переменной, одно сложение, одно вычитание. Вроде мало вычислений, согласись.

А теперь посчитаем твою модель, если я ее правильно понимаю:

1 час / 100 секунд = 36 обновлений карты

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

Где промежутки? Вот сидит игрок. Открывает он карту, мне при каждом открытии им карты придется пересчитывать состояния?

Сейчас он открыл карту - я ему просто переслал стркоу объектов все. В твоем варианте мне при каждом открытии придется считать состояния всех объектов.

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от LightDiver

Где промежутки? Вот сидит игрок. Открывает он карту, мне при каждом открытии им карты придется пересчитывать состояния?

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

Это от спама пересчетом идеально защитит.

Сейчас он открыл карту - я ему просто переслал стркоу объектов все. В твоем варианте мне при каждом открытии придется считать состояния всех объектов.

Да, надо будет запустить функцию которая посчитает текущее состояние.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

Ну так ты добавляешь в десятки раз больше нагрузки. Сейчас у меня это все делается, но раз в 100 секунд при онлайне игрока. А у тебя все то же самое, но кучу раз, как игрок открыл карту. Ты просто меняешь триггер.

Кроме того, карта может быть уже открыта у игрока. Вот сидит он с открытой картой. И должен видеть рост.

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от LightDiver

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

А у тебя все то же самое, но кучу раз, как игрок открыл карту.

Ну я же написал про защитную переменную, если время между предыдущим открытием карты менее 100 секунд, то ты показываешь просто предыдущее состояние, не вызываешь функцию актуализации.

Просто отдаешь состояние которое уже есть. Если игрок зажал клавишу в репите открытия карты, то показывается просто текущий стейт, ноль вычислений к этому. И так пока не пройдет 100 секунд между предыдущим обновлением. Заметь, никакого таймера нету в моей модели.

Кроме того, карта может быть уже открыта у игрока. Вот сидит он с открытой картой. И должен видеть рост.

Это худший случай, тут как и у тебя просто будет каждые 100 секунд обновляться карта.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 3)
Ответ на: комментарий от MOPKOBKA

Не, ты не понял. Как ты собрался добавлять переменную куда? На который объект?

У игрока может быть от 1 до 100 объектов. Нужно понять сколько их у него и где они. Нам всеравно нужно парсить строку для каждого изменения. Всю строку.

Пользователь может присылать строку с его объектами - это моя последняя идея. Получаем такую строку, итерируем объекты. Это самое оптимальное.

Но каждый раз этого делать всеравно нельзя. Придется воодить новые формулы и считать сколько добавлять объектам. И каждый раз дергать эту несчастную строку.

Представь, 1000 пользователей. Все начинают дергать поле.

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от LightDiver

Как ты собрался добавлять переменную куда?

Туда же, где ты сохраняешь карту. Нужно дополнительно 3 числовых значения.

Представь, 1000 пользователей. Все начинают дергать поле.

Чем это отличается от таймера который для 1000 пользователей начинает пересчитывать карту? Я не сейчас выдумал эту модель, она проверенна на практике, и далеко не для 1000 пользователей. Но возможно я ее плохо объясняю.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA
["Шеф"] = {
		"00t00t00t00t00t0ka0ka0ka00t00t00t0ka00t0ka0ka00t0ka0ka00t00t00t00t00t0ka0ka0ka0ka00t0ka0ka00t0ka0ka0ka0ka0ka00t0ka0ob0ka0ka0ka00t00t00t0ka0ka0ka0ka0ka00t00t0ka0ka0ka00t0ka0ka0ka0ka00t0ka00t0ka0ka00t0ka0ka0ka0ka0ka0ka0ka0ka0ka0ka00t00t0ka0ka0ka0ka0ka00t0ka0ka0ka0ka0ka0ka0ka0ka00t0ka0ka0ka0ka0ka00t00t", -- [1]
		" )<75'75P76) (pnilnilnil78#7A2782nil7A_nilnil7A=nilnil79C77$76g7Ab79qnilnilnilnil78gnilnil7A=nilnilnilnilnil7CGnilnilnilnilnil78O7Cp78Cnilnilnilnilnil7CM7Btnilnilnil77|nilnilnilnil75Vnil76nnilnil7AWnilnilnilnilnilnilnilnilnilnil78m7BJnilnilnilnilnil79Hnilnilnilnilnilnilnilnil78wnilnilnilnilnil7C879t", -- [2]
	},

Вот объекты. Что тебе дадут еще три переменные?

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от MOPKOBKA

Вон выше строка с объектами. Ты можешь сократить вычисления только если пользователь открывает таблицу реже, чем 100 секунд. Вообще в этом есть некий смысл.

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

Сейчас у меня одна механика роста - 100с. Придется делать две. Одна - при открытии поля. Добавлять вычисления времени и количества роста. Вторую - по таймеру, если поле уже открыто. Нужно синхронизировать их между собой.

Изначальная идея сократить нагрузку, а не увеличить ее.

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от anonymous

«En» переводится как «в». Это перевод объекта в систему счисления. Сложно перепутать, если пишешь: «Перевести число 100 в 85ую систему счисления», «перевести строку ‘)bv’ в 10ую систему счисления».

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

Вообще идея с тиггером по открытию поля интересная в перспективе. Это надо запомнить.

Не для данной ситуации точно, но сама идея богатая. Нужно подумать где можно применить. И так же надо вход выход игрока из игры отслеживать…

Нужно это все объединить в одну механику и хранить в одном объекте.. Ага.. Короче, за идею спасибо.

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от LightDiver

Он не может отставать тк там буквально меньше вызовов mFldS:getStaticStr, en10 и сокращена логика if-ов. Все остальное тоже самое. Похоже, что условия тестирования у вас неодинаковые от теста к тесту.

Obezyan
()
Ответ на: комментарий от Obezyan

Условия тестирования простые: вызов в цикле функции 10, 50, 150, 500, 1000, 5000, 10000 раз.

На больших значениях твой вариант лучше чуть чуть. Но мне большие значения не нужны. А вот на малых значениях до 150 проходов за раз - хуже вдвое.

Тут как раз прикол в двойной выгоде на малых значениях. Я не понимаю откуда оно берется, но это работает. А раз работает - пущай будет.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 1)
Ответ на: комментарий от LightDiver

На больших значениях твой вариант лучше чуть чуть. Но мне большие значения не нужны. А вот на малых значениях до 150 проходов за раз - хуже вдвое.

Значит проблема не в этом коде, а в функциях en10 и en85, чудес не бывает.

Obezyan
()
Ответ на: комментарий от Obezyan
-- Локализация системных функций
local abs, floor = math.abs, math.floor
local byte, sub, char = string.byte, string.sub, string.char
local tbl_insert, tbl_concat, error = table.insert, table.concat, error

local _convertTable3 = {
    [0] = "0", [1] = "1", [2] = "2", [3] = "3", [4] = "4",
    [5] = "5", [6] = "6", [7] = "7", [8] = "8", [9] = "9",
    [10] = "A", [11] = "B", [12] = "C", [13] = "D", [14] = "E",
    [15] = "F", [16] = "G", [17] = "#", [18] = "$", [19] = "%",
    [20] = "(", [21] = ")", [22] = "*", [23] = "+", [24] = "-",
    [25] = "/", [26] = ";", [27] = "<", [28] = "=", [29] = ">",
    [30] = "@", [31] = "H", [32] = "I", [33] = "J", [34] = "K",
    [35] = "L", [36] = "M", [37] = "N", [38] = "O", [39] = "P",
    [40] = "Q", [41] = "R", [42] = "S", [43] = "T", [44] = "U",
    [45] = "V", [46] = "W", [47] = "X", [48] = "Y", [49] = "Z",
    [50] = "^", [51] = "_", [52] = "`", [53] = "a", [54] = "b",
    [55] = "c", [56] = "d", [57] = "e", [58] = "f", [59] = "g",
    [60] = "h", [61] = "i", [62] = "j", [63] = "k", [64] = "l",
    [65] = "m", [66] = "n", [67] = "o", [68] = "p", [69] = "q",
    [70] = "r", [71] = "s", [72] = "t", [73] = "u", [74] = "v",
    [75] = "w", [76] = "x", [77] = "y", [78] = "z", [79] = "{",
    [80] = "|", [81] = "}", [82] = "[", [83] = "]", [84] = "'",
}
-- Обратная таблица конвертации
local _reverseConvertTable3 = {}
for k, v in pairs(_convertTable3) do
    _reverseConvertTable3[v] = k
end
-- Максимальное поддерживаемое число (85^12)
local MAX_NUMBER = 85^12
-- Буфер для кодирования
local encode_buffer = {}
-- Кодирование числа в строку
function en85(dec)
    if type(dec) ~= "number" then error("Input must be a number") end
    if dec == 0 then return "0" end
    -- Проверка диапазона
    if dec < 0 or dec > MAX_NUMBER then
        error("Number out of range: " .. tostring(dec))
    end
    local idx = 0
    repeat
        local remainder = dec % 85
        dec = floor(dec / 85)
        idx = idx + 1
        encode_buffer[idx] = _convertTable3[remainder]
    until dec == 0
    -- Сборка строки в правильном порядке (обратном)
    local result = ""
    for i = idx, 1, -1 do
        result = result .. (encode_buffer[i] or "")
    end
    -- Очистка буфера
    for i = 1, idx do
        encode_buffer[i] = nil
    end
    return result
end
-- Декодирование строки в число
function en10(encoded)
    if type(encoded) ~= "string" then return 0 end
    if encoded == "0" then return 0 end
    local number = 0
    local len = #encoded
    for i = 1, len do
        local symbol = sub(encoded, i, i)
        local digit = _reverseConvertTable3[symbol] or 0
        number = number * 85 + digit
    end
    return number
end

Я эти функции допиливал месяцами раз за разом под конкретные задачи и конкретные количества обработок наконкретном ресурсе. Не удивительно, что теперь они ведут себя лучше всего на нужных мне вариантах работы.

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от LightDiver

Ты можешь сократить вычисления только если пользователь открывает таблицу реже, чем 100 секунд. Вообще в этом есть некий смысл.

Да, я об этом написал выше, наверное стоило лучше подчеркнуть. Это лишь сократит количество вызовов метода обновления карты, но не ускорит сам метод обновления карты.

Изначальная идея сократить нагрузку, а не увеличить ее.

Она не увеличивается. Синхронизация в этой модели не нужна.

Я считаю нужно искать трюки а не микро-оптимизировать саму функцию. Еще можно расширить мою модель, и рисовать хп и прогресс растения только на клиенте учитывая время и там же обновлять для вывода (обновлять только для отрисовки, никакого сохранения на сервере), а на сервере обновлять его только если игрок хочет срубить дерево, то есть при изменении, для проверки реально ли выросло растение. Тут придется иметь один и тот же код на клиенте и сервере, если есть возможность то можно просто вынести в отдельный файл.

Но если интересует только микро-оптимизация, то я заметил что соединение строк всегда быстрее чем соединение через таблицы, и в принципе таблицы не особо быстрые, это касается твоего base и обновления карты. Еще ты можешь ужимать сущность и ее здоровье в два символа, потому что ASCII это 94 знака, если объединить то как то так:

function encode94(n)
    local s = ''
    repeat
        s = string.char(32 + n % 94) .. s
        n = math.floor(n / 94)
    until n == 0.0
    return s
end

function decode94(encoded)
    local n = 0
    for i = 1, #encoded do
        n = n * 94 + (string.byte(encoded, i, i) - 32) 
    end
    return n
end

function encode94_ent(e, n)
    return encode94(e * 1000 + n)
end

function decode94_ent(s)
    local n = decode94(s)
    return math.floor(n / 1000), n % 1000
end

local tree_ent_id = 3
print(decode94_ent(encode94_ent(tree_ent_id, 999)))
print(#encode94_ent(tree_ent_id, 999))
Этот код немного быстрее твоего по моим замерам (decode_ent не измерял), и сокращает размер сущности с 3 до 2, при условии что 999 это максимальное здоровье, 8 типов сущности на одном экране, самих id типов сущностей может быть больше, просто нужно переключать палитры. Можно и альтернативное сжатие если этого мало, память точно уменьшить можно.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 8)
Ответ на: комментарий от MOPKOBKA

Спасибо, потестирую. Но я за полтора года перебирал разные варианты. И выбирал не самые быстрые, а оптимальное между отжиранием памяти и скоростью.

LightDiver ★★★★★
() автор топика

Lua — относительно специфичная штука. Сомневаюсь, что кто-то посвящает много времени оптимизации. Проще переписать на C++ «горячие» участки.

Но если интересно, то есть совершенно крышесносная концепция под жаргонным названием «poor man’s profiler».

В оригинале используется gdb, но лучше использовать elfutils. Полученный результат визуализировать Flamegraph.

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

Только учти что в случае с Lua 5.1 это все пустая трата времени. Ибо давно Lua 5.4 же, в том числе и с оптимизациями исполнения байт-кода.

anonymous
()
Ответ на: комментарий от anonymous

Lua - эта «Pascal»(не бринча Хэнсена)(Oberon тот же Pascal что c «правильными» классами(типами с расширениями точнее) и ...) который «смог»

ибо универсальность «таблиц» - ну эт же красота

qulinxao3 ★☆
()
Ответ на: комментарий от qulinxao3

От этой универсальности таблиц я матерюсь почти кадый день. Это лютый трэш угар и содомия. Это худшее, что есть в луа, хотя я считаю, что он недооценен и прекрасен.

Динамическое изменение этих лядских таблиц. Динамическое изменение содержимого по сути. Неможность однозначно управлять типом таблиц. Отсутствие многих типов, которые нужно эмоулировать вручную. Это кошмар на самом деле.

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

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от MOPKOBKA

@Модераторы Санитары!!! Похоже, что подсветка синтаксиса не работает на некоторых языках. Это явно 1С код, но его синтаксис не подсвечивается. Используемая на сайте последняя версия hljs 1С-синтаксис поддерживает поддерживает, можно проверить в демке.

Есть предложение докачать пропущенные языки.

Obezyan
()
Ответ на: комментарий от LightDiver

Его планировали для небольших скриптов внутри приложения, или файлов настроек, поэтому выбор динамической типизации не удивителен. Зачем в макросе для какого нибудь редактора типы?

Отсутствие многих типов, которые нужно эмулировать вручную.

Например? В твоей версии нету целочисленных, а помимо?

MOPKOBKA ★★★★★
()
Ответ на: комментарий от MOPKOBKA

генезис (первые года уж точно) Lua тождейственен Python

года до 95-96 они вообще взаимозаменяемы в смысле ниши на которой ожидалось их создателями применение их творений

то что ща они настолько разные(особенно в восприятии публики) ну

идеократия и «поймай меня если сможешь» «по сути» одно и тоже

qulinxao3 ★☆
()
Ответ на: комментарий от qulinxao3

то что ща они настолько разные(особенно в восприятии публики) ну

Ну даже если отбросить восприятие, уже так много кода на питоне, что пришлось завозить возможности из настоящих языков, такие как typing, поэтому он объективно меняется. Так же как PHP, из простого языка, в него набираются возможности в Java, и типичный код уже становится не процедурным скриптом, а FizzBuzzEnterprise, хотя и на Java можно писать в простом процедурном стиле без излишеств.

Для Lua в одном из первых документов, кратко обсуждается возможность написание внешнего компилятора для VM, что позволит получить преимущества внешнего компилятора (статическая типизация???). Черпали вдохновение из UCSD, и что то подобное наверное было актуально в Бразилии в то время, учитывая их «импортозамещение». Про Python 1 сейчас трудно что то найти, про него таких идей не нашел.

Еще одно отличие, как и в случае с языками Вирта, Lua предполагалось дорабатывать под область, есть различные диалекты Lua сейчас, про различные диалекты Python я не знаю, кроме имплементаций которые уходят от CPython, но это уже немного другое.

MOPKOBKA ★★★★★
()
Ответ на: комментарий от MOPKOBKA

Кортеж, список, множество, словарь, например. Ты не можешь просто так и сделать в луа сипсок с уникальными элементами. Нужно сначала проверять каждый элемент, сравнить с шаблоном.

Зато вчера я нашел в питоне много интересного. Смотри забавное:

def is_user_confirmed(self, user_id: int) -> bool:
        return user_id in self.whitelist

В питон можно статическую типизацию 3-5 разных видов, например.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 1)
Ответ на: комментарий от LightDiver

Кортеж, список, множество, словарь, например

По моему все это покрывает таблица. Между кортежом и списком я разницы не знаю, множество это таблица где значение всегда true, а словарь ну это таблица и есть.

Кортеж, список, массив, это лишь ограниченный словарь.

Список еще интересен вставкой в середину, в lua из коробки:

local list = {}
table.insert(list, 1, 'left')
table.insert(list, 2, 'right')
table.insert(list, 2, 'mid')

Ты не можешь просто так и сделать в луа сипсок с уникальными элементами.

Список с уникальными элементами это и есть множество, а оно записывается просто:

local set = {}
set[5] = true
set[10] = true
set[5] = true

if set[5] then
  print('5 в множестве')
end

Зато вчера я нашел в питоне много интересного. Смотри забавное:

На Lua можно так переписать так, но whitelist нужно сделать множеством:

function is_user_confirmed(self, user_id)
  return self.whitelist[user_id]
end

В питон можно статическую типизацию 3-5 разных видов, например.

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

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 8)
Ответ на: комментарий от MOPKOBKA

Не, ты не понял. Разница именно в практике применения. В питоне не нужна вот эта обвязка для таблиц. Не нужны проверки. Там УЖЕ есть готовые объекты, где все используется как тебе надо.

В луа же нужно эмулировать это поведение - вводить доп проверки и обработку таблиц. А кое что ты вовсе сделать не можешь гарантирвоанно, например, создать массив. Одна ошибка и ты ошибся - и он становися хэш-таблицей. Сам просто потому что.

И про питон - я показал тебе использование раст-синтаксиса в питоне. Так можно. Это конечно чисто декорации, но всеравно забавно. -> bool - тип возвращаемого значения метода бул. :int - тип переменной инт.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 1)
Ответ на: комментарий от LightDiver

В питоне не нужна вот эта обвязка для таблиц.

А какая обвязка? Мой код это чистый lua. Но я понял, тебя не сама возможность волнует, а типобезопасность и проверка ошибок.

А кое что ты вовсе сделать не можешь гарантирвоанно, например, создать массив.

Это и проблема Python, там нету массива, только список. Ну и в классах такая же проблема:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(10, 20)
p.х = 100
print(p.x)
Выведет 10, в присваивании я написал русскую Хэ. Хотя защит несравнимо больше, это называется строгая типизация.

И про питон - я показал тебе использование раст-синтаксиса в питоне. Так можно.

Мне кажется это не Rust синтаксис %)

function is_user_confirmed(user_id: integer): boolean;

Это конечно чисто декорации, но всеравно забавно.

Их можно проверить без запуска программы, через утилиту mypy: https://mypy-lang.org/

Так же как в компилируемых языках типа C, Pascal проверяются типы.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 5)