Перейти к контенту
Malandrinus

Язык Lua. Общие вопросы программирования

Рекомендуемые сообщения

С чего начинать и где взять.

 

Установка Lua:
http://www.amk-team.ru/forum/index.php?showtopic=11584&p=629106

 

Руководство «Программирование на языке Lua», третье издание:
http://www.amk-team.ru/forum/index.php?showtopic=11584&p=905308

Изменено пользователем Kirgudu
Ссылка на комментарий

(по следам ранее опудликованного)

"Упаковка таблицы в строку (стринг) и обратная распаковка"

 

Доработанный вариант с возможностью проверки в SciTE:

--[[

1. При упаковке таблиц типа 'список': {v1,v2,v3,v4} в пак-строку для каждого элемента записывается

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

Экономия: Общее кол-во байт всех индексов таблицы, каждый разряд которых 1 байт.

Т.е. для списка в 100 элементов экономия: 1х9 + 2х90 + 3х1 - 1 = 191 байт.

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

число упаковывается не как десятичная строка, а как хекс-строка.

Пример: 12345 == 0х3039 => "12345" ~= "3039" и экономится как минимум 1 байт на каждом числе.

При сохранении чисел большой разрадности (до 14) - экономия может составлять 3 байта на каждое число.

3. Таблицы должны содержать только элементы типов: строка, число, булево значение, субтаблицы и nil.

Элементы типов: юзердата, функция, поток - запрещены для упаковки.

4. Исправлена ошибка AMK-варианта при упаковке таблиц с булевыми значениями в полях.

5. Совместимость со скриптами и сэйвами, использовавшими упаковку/распаковку из amk.script.

Обратная совместимость отсутствует.

 

Пример практичеккого применения:

В игре в таблицу запоминаются игровые идентификаторы (ID) объектов.

Для 198 объектов:

Паковка таблицы амк-вариантом: 1925 байт

Паковка таблицы 'экономным': 1056 байт

--]]

--/------------------------------------------------------------------
--/#!# Для отладки в SciTE (в кодах игры УДАЛИТЬ!)
--/------------------------------------------------------------------
--/ пакуемая таблица
local t0 = { ["a"]=true, ["b"]=false, ["c"]=3, ["d"]=-111 }
--local t0 = {"a","b","c","d","e"}
print("Count0=", #t0, ", First=", next(t0))
--/ создаем синонимы функций
local printf = function(...) print(...) end
local abort = function(...) print("ERROR:", ...) end
--/------------------------------------------------------------------
--/ Упаковка таблицы в строку (стринг) и обратная распаковка
--/------------------------------------------------------------------
--/ Внимание! Строки в структуре таблицы не должны содержать символов с кодами 0-31.
--[[--------------------------------------------------------
  Формат упаковки:
    table     ::= ( listtable | hashtable )
    subtable  ::= ( listtable | hashtable )
    listtable ::= 0x6 valuetype ( value | subtable 0x5 )
    hashtable ::= keytype key valuetype ( value | subtable 0x5 )
    keytype   ::= ( 0x1 | 0x2 | 0x7 )
    valuetype ::= ( 0x1 | 0x2 | 0x3 | 0x4 | 0x7 )
--]]--------------------------------------------------------
local tPackChar = { --/ служебная таблица маркеров упаковки
  dec = {string.char(1), 1}, --/ 0x1 (SOH) - 'number' (dec)
  str = {string.char(2), 2}, --/ 0x2 (STX) - 'string'
  bln = {string.char(3), 3}, --/ 0x3 (ETX) - 'boolean'
  tbl = {string.char(4), 4}, --/ 0x4 (EOT) - 'table'
  tbe = {string.char(5), 5}, --/ 0x5 (ENQ) - table-end
  tbi = {string.char(6), 6}, --/ 0x6 (ACK) - table-list
  hex = {string.char(7), 7}  --/ 0x7 (BEL) - number-hex
}
--/--------------------------------------------------------
--/ функции для совместимости с сэйвами на базе amk.script
--/--------------------------------------------------------
--/ упаковка таблицы в строку (стринг)
function pack_array_to_string(tTbl,bList)
   printf("pack_array_to_string:>")
  return Pack_Tbl(tTbl,bList) --/> String
end
--/ распаковка 'упакованной' строки (стринга) в таблицу
function unpack_array_from_string(sStr)
  if sStr and sStr ~= '' then
    --/ amk-format упаковки был тэгирован символом c кодом 1 (0x1) --
    if sStr:sub(1,1) == string.char(1) then --/ amk-format?
      if (sStr:sub(2,2) == tPackChar.dec[1] or sStr:sub(2,2) == tPackChar.str[1]) then
        --/ со 2-го символа начинается упакованная строка
        sStr = sStr:sub(2,-1) --/ отрезаем 1-й символ
      end
    end
    --/< ------------------------------------------------------------
    return Parse_Str(sStr) or {} --/> Table
  end
  return {} --/> Zero-Table
end
--/--------------------------------------------------------
--/ упаковка таблицы (списка) 'tTbl' в строку 'sStr'
--/--------------------------------------------------------
function Pack_Tbl(tTbl,bList) --/< table [,(nil|true|false)]
  if type(tTbl) ~= 'table' then
    printf("Pack_Tbl:Not_Table=["..type(tTbl).."]:<Warning!>")
    return "" --/> zero-string (не таблица!)
  elseif not next(tTbl) then --/ --/>? (отсутствует 1-й элемент таблицы)
    return "" --/> zero-string (таблица пуста)
  end
  --/ локальная функция: проверка типа таблицы:'список' или нет?
  function is_List(tTbl)
    local iCntIdx = #tTbl --/ длина индексированной части таблицы
    if iCntIdx > 0 then --/ есть элементы списка?
      --/ индексы начинаются с 1 и за 'списком' отсутствует хеш-элемент
      if next(tTbl) == 1 and not next(tTbl,iCntIdx) then
        for i=2,iCntIdx-1 do --/ цикл перепроверки: 'отсутствуют пустые элементы?'
          if tTbl[i] == nil then --/ пустышка?
            return false --/> не список
          end
        end
        bList = true --/ флаг: это список!
      end
    end
    return bList --/> флаг: список или ... (true|nil)
  end
  --/ локальная функция: упаковка (+конвертер) числа (number) в 'dec' или 'hex' строку (string)
  function Pack_Num2Str(iNum)
    if iNum == math.modf(iNum) then --/ целочисленное?
      local iAbs = math.abs(iNum) --/ модуль числа
      if iAbs > 9 then --/ число 2-x и более разрядное?
        local iLv = math.floor(math.log10(iAbs+1)) --/ степень ближайшего к модулю бОльшего числа кратного 10-ти
        if iAbs >= 10^iLv and iAbs < 2^(4*iLv) then --/ разрядность 'dec' > 'hex'?
          if iNum >= 0 then --/ не отрицательное?
            return tPackChar.hex[1] .. string.format('%X', iAbs) --/> 'hex'-строка
          end
          --/ отрицательное - упаковываем со знаком
          return tPackChar.hex[1] .. "-" .. string.format('%X', iAbs) --/> 'hex'-строка со знаком
        end
      end
    end
    return tPackChar.dec[1] .. iNum --/> 'dec'-строка
  end
  --/ локальная функция: упаковка ключа (индекса) элемента таблицы
  function Pack_Key(key)
    local sType = type(key)
    if sType == 'number' then
      return Pack_Num2Str(key) --/> 'dec|hex'-строка
    elseif sType == 'string' then
      return tPackChar.str[1] .. key --/> строка
    end
    abort("Pack_Tbl:UnSupported_KeyType=" .. sType)
  end
  --/ локальная функция: упаковка значения элемента таблицы
  function Pack_Value(value)
    local sType = type(value)
    if sType == 'number' then
      return Pack_Num2Str(value) --/> 'dec|hex'-строка
    elseif sType == 'string' then
      return tPackChar.str[1] .. value --/> строка
    elseif sType == 'boolean' then
      return tPackChar.bln[1] .. ((value and "1") or "0") --/> 'bool'-строка ('0'|'1')
    elseif sType == 'table' then
      return tPackChar.tbl[1] .. this.Pack_Tbl(value) .. tPackChar.tbe[1] --/> рекурсивный вызов
    end
    abort("Pack_Tbl:UnSupported_ValueType=" .. sType)
  end
  --/ -------------------------------------------
  --/ основное тело функции: all pack
  local tPackStr = {} --/ временная таблица для частей общей строки
  if bList or is_List(tTbl) then --/ упаковывается список?
    table.insert(tPackStr, tPackChar.tbi[1]) --/< маркер 'list' (список)
    for i=1,#tTbl do
      table.insert(tPackStr, Pack_Value(tTbl[i]))
    end
  else --/ полная упаковка (ключ и значение)
    for k,v in pairs(tTbl) do
      table.insert(tPackStr, Pack_Key(k))
      table.insert(tPackStr, Pack_Value(v))
    end
  end
  return table.concat(tPackStr),bList --/> строка [и флаг 'список']
end
--/--------------------------------------------------------
--/ распаковка строки 'sStr' (или части строки от 'at') в таблицу 'tTbl'
--/--------------------------------------------------------
function Parse_Str(sStr,at)
  local tTbl,iLen = {},sStr:len() --/ заготовка таблицы и длина строки
  if not at then at = 1 end --/ по умолчанию: парсинг с начала строки
  local key,value,iByte,bList = nil,nil,nil,nil
  --/ -------------------------------------------
  iByte,at = Get_Byte(sStr,at) --/ код at-го символа в строке
  --/ проверка: упакована таблица типа 'список' (table-list)?
  if iByte == tPackChar.tbi[2] then --/ 'список'?
    bList = true --/ флаг: 'начало списка' (table-list)
    iByte,at = Get_Byte(sStr,at)--/ пропускаем маркер списка и переходим к следующему символу
  end
  --/ цикл парсинга строки с at-го символа
  while at <= iLen do --/ (суб)строка не закончилась?
    if iByte == tPackChar.tbe[2] then --/ проверка: маркер конца субтаблицы?
      return tTbl,at --/> субстрока субтаблицы закончилась - выход из функции
    end
    if not bList then --/ режим 'общей' таблицы (не 'список')?
      --/ парсинг 'key'
      if     iByte == tPackChar.str[2] then --/ 'string'?
        key,at = Get_Str(sStr,at)
      elseif iByte == tPackChar.dec[2] then --/ 'number' (dec)?
        key,at = Get_Num(sStr,at)
      elseif iByte == tPackChar.hex[2] then --/ 'number-hex'?
        key,at = Get_Num(sStr,at,true) --/< 'true' - флаг распаковка 'hex'-строки
      else --/ ошибка формата упаковки
        abort("Parse_Str:(%s):UnSupported_TypeKey=" .. tostring(iByte))
      end
      iByte,at = Get_Byte(sStr,at) --/ код следующего символа строки
    end
    --/ парсинг 'value'
    if     iByte == tPackChar.dec[2] then --/ 'number' (dec)?
      value,at = Get_Num(sStr,at)
    elseif iByte == tPackChar.hex[2] then --/ 'number-hex'?
      value,at = Get_Num(sStr,at,true) --/< 'true' - флаг распаковка 'hex'-строки
    elseif iByte == tPackChar.str[2] then --/ 'string'?
      value,at = Get_Str(sStr,at)
    elseif iByte == tPackChar.bln[2] then --/ 'boolean'?
      value,at = Get_Bool(sStr,at)
    elseif iByte == tPackChar.tbl[2] then --/ 'table'?
      value,at = Parse_Str(sStr,at) --/> рекурсивный вызов для 'табличных субстрок'
    else --/ ошибка формата упаковки
      abort("Parse_Str:(%s):UnSupported_TypeValue=" .. tostring(iByte))
    end
    --/ запоминаем элемент в таблицу
    if bList then --/ элемент списка?
      table.insert(tTbl, value) --/ добавляем в таблицу типа 'список' (table-list)
    else --/ элемент 'общей' таблицы
      tTbl[key] = value
    end
    iByte,at = Get_Byte(sStr,at) --/ код следующего символа строки
  end
  return tTbl,at --/>
end

--/ получение кода at-го символа строки и индекса следующего за ним символа
function Get_Byte(sStr,at)
  return string.byte(sStr:sub(at,at)), at+1 --/>
end
--/ по-символьный парсер строки 'sStr' от at до 1-го 'управляющего' символа
function Get_Str(sStr,at) --/< стринг(строка) и начальный индекс в нем
  local iSym,iLen = at,sStr:len() --/ индекс конца стринга(строки)
  for i=at,iLen do
    local iByte = string.byte(sStr:sub(i,i)) --/ код i-го символа строки
    if iByte < 32 then --/ 'управляющий' символ?
      if iByte > 7 or i == at then --/ 'управляющий' символ запрещенный или 1-й?
        abort("Get_Str:Sym(" .. i .. ")=" .. iByte)
      end
      --/ начальная часть (суб)строки и индекс 1-го упр.символа
      return sStr:sub(at,i-1), i --/> sSubStr,iNext
    end
  end
  --/ вся (суб)строка до конца и индекс 'после конца'
  return sStr:sub(at,iLen), iLen+1 --/> sSubStr,iNext
end
--/ перевод части (от 'at') строки 'sStr' в число (десятичное)
function Get_Num(sStr,at,bHex)
  local sSubStr,iNext = Get_Str(sStr,at)
  local iNum = nil
  if bHex then --/ распаковка хекс-строки?
    if sSubStr:sub(1,1) == "-" then --/ отрицательное число? - отрезаем 1-й символ
      iNum = tonumber(sSubStr:sub(2,-1),16) *(-1) --/ перевод 'hex'-строки в отрицаткельное десятичное число
    else
      iNum = tonumber(sSubStr,16) --/ перевод 'hex'-строки в десятичное число
    end
  else
    iNum = tonumber(sSubStr) --/ перевод 'dec'-строки в десятичное число
  end
  if iNum then --/ есть число?
    return iNum, iNext --/>
  elseif sSubStr == "" then --/ пустая субстрока?
    printf("Get_Num:Zero:Str=" .. tostring(sStr))
    return 0, iNext --/> #?# пока не будем прерывать
  else --/ ошибка формата упаковки
    abort("Get_Num:SubStr=" .. tostring(sSubStr))
  end
end
--/ перевод части (от 'at') строки 'sStr' в булево значение (true|false)
function Get_Bool(sStr,at)
  local sSubStr,iNext = Get_Str(sStr,at)
  return (sSubStr == "1" or string.lower(sSubStr:match('^%s*(%S*)') or '') == 'true'), iNext --/>
end

--/ перевод (части) строки 'sStr' в булево значение (true|false)
function ToBoolean(sStr)
  return (sStr == "1" or string.lower(sStr:match('^%s*(%S*)') or '') == 'true') --/> true|false
end
--/------------------------------------------------------------------
--/#!# Для отладка в SciTE (в кодах игры УДАЛИТЬ!)
--/------------------------------------------------------------------
local s = pack_array_to_string(t0)
print("PackString:", s)
local t1 = unpack_array_from_string(s)
print("Count1=", #t1, ", First=", next(t1))
for k,v in pairs(t1) do
  print(k, "=", v)
end
--/------------------------------------------------------------------

Примечание: Для отладки в SciTE - вставить код в новый открытый документ, задать имя и расширение (например, my_pack.script) и ... , задав в начале кодов требуемые/желаемые значения для таблицы 't0' - проверять результаты (F5).

 

Добавлено через 19 мин.:

malandrinus

Исходный вариант (АМК) паковал все числа в строку используя tostring.

Смотрим: если число равно 10 -> в строку пишется два символа (байта), для 16 -> 2 байта, для 100 -> 3 байта.

При использовании конвертера в хекс-строку имеем:

10 -> 0xA -> 1 байт, 16 -> 0x10 -> 2 байта, 100 -> 0x64 -> 2 байта.

Т.о. в указанных ранее диапазонах (10...15,100...255,1000...4095, и далее), т.е. в тех, когда количество разрядов числа в десятичной форме превышает кол-во разрядов в 16-ти ричной форме - конвертация позволяет экономить на каждом таком числе по байту или более. Т.е. кол-фо знаков в суммарной строке упакованной таблицы уменьшается.

Для сохранений в pstor объектов в игре это важно.

 

 

Добавлено через 16 мин.:

Поправил пару ошибок в комплекте ... (тяжко все же коды вставлять в парсер форума)

Изменено пользователем Artos

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

Ссылка на комментарий
...т.е. для запуска надо нажать F5 или такую зелёную кнопочку со стрелочкой вправо на тулбаре, или из меню "Tools" выполнить команду "Go". Я правильно понял? =)

Из меня писатель не лучший чем, скажем, программист. :-(

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

Все описания, что я пишу и хочу написать, основаны на собранных по крохам информации из НЕТ-а и собственных исследованиях.

Поэтому никакой гарантии в плане абсолютной достоверности и правильности "подачи" не даю. Это все-лишь моё понимание.

Я выкладываю эту информацию как макет, в надежде на участие сообщества в приведении её к более полному, достоверному и "читабельному" виду, такому, чтобы наконец собрать всё до кучи и поместить в шапку темы (зачистив при этом посты с процессом поиска и исследования).

Так что не стесняемся высказывать своё мнение, знания и (malandrinus-у) редактировать неверные позиции.:-)

 

Добавлено через 141 мин.:

Хочу залезть глубоко в недра своего виртуального справочника, и показать как хотелось бы сделать описания. К примеру возьму функцию pairs, тем более что уже возникали вопросы по её использованию.

На первый взгляд кажется, что функция проста в использовании. Может быть так оно и есть, но раз уж топик по lua, то углублённое понимание процессов подразумевается само собой. Этим и займёмся

Описание

 

Функция pairs сама по себе возвращает три значения: функцию next, таблицу t, и nil.

tab = {}
f,t,s = pairs(tab)
print(f) --> функция итератор (для итерации pairs использует функцию next)
print(t) --> таблица tab, передаваемая в функцию
print(s) --> nil, как начальное значение индекса

Использование этой функции предусмотрено в расширенной форме записи оператора for.

for k,v in pairs(t) do
    ...
end

Или же, зная какие результаты она возвращает, записать в такой форме :

for k,v in next,t do
    ...
end

Это значит, что на каждом обороте для получения новых значений переменных цикла k и v вызывается итератор.

Применяющийся в функции pairs итератор возвращает два значения. Это ключ (k - key) и значение (v - value) следующего поля таблицы.

Цикл заканчивается, когда итератор вернет nil.

Стоит заметить, что :

  • Функция pairs вычисляется один раз, в отличии от функции итератора возвращаемой ею.
  • Переменные k и v являются локальными только для данного цикла. Вы не сможете использовать их значения после выхода из for.

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

Итерация.

 

Вот что пишет учебник (к этому ещё вернусь):

Порядок следования индексов неопределен, даже для числовых индексов.

Для прохождения по таблице в числовом порядке, используйте числовой for или функцию ipairs.

Посмотрим :

--------------- читаем таблицу ------------------
t = {10,20,40,[3.3]=50,60,[6]=true,k='abc'}
for k,v in pairs(t) do 
    print(k,v)
end
--------------- изменим порядок -----------------
print('--------------')
t = {k='abc',10,20,40,[3.3]=50,60,[6]=true}
for k,v in pairs(t) do 
    print(k,v)
end

Действительно, порядок следования изменился.

Читаем дальше :

См. предупреждения для функции next если Вы хотите в процессе итерации модифицировать таблицу.

.....

Результат работы next неопределен (undefined) если, в процессе сканирования таблицы, Вы присваиваете какое либо значение в несуществующее поле таблицы. Однако Вы можете модифицировать существующие. В частности, Вы можете очистить поля.

Добавим поля :
t = {k='abc',40,10,[3.3]=50,20,[6]=true,60}

n=9
for k,v in pairs(t) do 
    if n<12 then
        t[n]='val_'..n
        n=n+1
    end
end

for k,v in pairs(t) do 
    print(k,v)
end

Теперь очистим (относится к коду выше) :

for k,v in pairs(t) do 
    if k==3 then
        t[k]=nil
    end
end

Но как бы то ни было, первыми читаются поля массива, начинающиеся с индекса 1 в последовательной математической прогрессии до первого поля, значение которого равно nil.

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

В общем тут поле для экспериментов.

 

Notes

 

Тут я попробую представить функцию pairs в развёрнутом виде, т.е. её строение так, как понимаю я :

Для этого изменю эту функции pairs и next на свои, скажем, my_pairs и my_next

function my_next(tab, key)
    key,val = next(tab,key)
    return key, val
end

function my_pairs(tab)
    return my_next, tab, nil
end

for k,v in my_pairs(t) do
    ...
end

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

Например будем выбирать поля, в которых значение не является числом :

t = {10,20,['r']=30,40,[3.3]=50,60,[6]=true,r ='abc'}

function my_next(tab, key)
    key,val = next(tab,key)
    while type(val)=='number' do
        key,val = next(tab,key)
    end
    return key, val
end

function my_pairs(tab)
    return my_next, tab, nil
end

for k,v in my_pairs(t) do
    print(k,v)
end

 

 

 

Изменено пользователем Gun12
Ссылка на комментарий

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

Для начала интересный факт, не все знают. Частота вызова апдейта актора в игре абсолютно точно совпадает с частотой fps. Т.е., на разных машинах эта частота будет ооочень разной.

Очень часто модостроителю нужно что-то свое поставить в апдейт, иногда хочется поставить и что-то довольно ресурсоемкое. Но - тем самым мы снижаем fps. На слабых машинах это приведет к невозможности играть вообще. Как же быть?

Я поступил следующим образом:

в bind_stalker-e в функции actor_binder:update(delta) вызываю свой апдейтер

twoteam.actor_update(self, delta)

А сам апдейтер организую таким образом

local time_100 = 0
local time_300 = 0
local time_1200 = 0
local time_5000 = 0

function actor_update(actor, delta)
--- Действия, которые надо производить с частотой fps

    time_100 = time_100 + delta
    if time_100 > 100 then
    --- Действия, которые надо производить с частотой раз в 100мс

    time_100 = 0
    end
    time_300 = time_300 + delta
    if time_300 > 300 then
    --- Действия, которые надо производить с частотой раз в 300мс

    time_180 = 0
    end
    time_1200 = time_1200 + delta
    if time_1200 > 1200 then
    --- Действия, которые надо производить с частотой раз в 1200мс

    time_1200 = 0
    end
    time_5000 = time_5000 + delta
    if time_5000 > 5000 then
    --- Действия, которые надо производить с частотой раз в 5 секунд

    time_5000 = 0
    end
end

Какие периоды срабатывания ставить, и сколько делать таких под-апдейтеров, каждый сам для себя решит исходя из практических целей. Плюс в том, что какое-то ресурсоемкое действие можно поставить, скажем, в обработку раз в 1200 мс. Допустим оно занимает 100 мс. Оно будет выполнено, несущественно повлияв на производительность (снижение среднего fps менее 10%). А если бы мы поставили это действие прямо в "родной" апдейт, получили бы fps = 10 или менее. А это уже на грани неиграбельности, согласитесь :)

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

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

Здесь предлагается обсуждать язык Lua, его синтаксические особенности, производительность, приёмы использования стандартные и не очень. Также здесь предлагается постить примеры алгоритмов для решения разных достаточно общих задач, по возможности не сильно привязанных к игре.
Вместо означенного - видим обсуждение редакторов, как и что в них можно делать и т.д. Не спорю информация полезная, но во-первых, она пока что разбросана по капле по многим постам, а во вторых... боже мой, я до сего дня вообще не знал тех вещей, которые тут обсуждались. Сидел себе на Notepad-e и горя не знал. Наверно, я неправильный скриптер, или не скриптер совсем?

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

 

  • Нравится 1

Мод, где не бывает одинаковых путей - Судьба Зоны. (Лучшее, что у меня получилось на X-Ray) На базе модифицированного движка OGSR Engine.

Бывший мододел на X-Ray / Начинающий игродел на Unreal Engine. Программист.

AMD Ryzen 9 7950X (16 ядер, 5.7ГГц); RTX 3080; 128 ГБ DDR5; Arctic Liquid Freezer II-420; 3 ТБ SSD PCIe 4.0; 4ТБ HDD.

Ссылка на комментарий
...пока большая часть темы представляет практический интерес не для всех, кто считает себя скриптерами...

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

Например мне в ЛС задают вопросы, и я прошу задавать их в этом топике. Но то ли стесняются, то ли ...? Не знаю. Если бы была способность предугадывать желания, то я сменил бы профессию :-)

Ссылка на комментарий

Zander_driver

1. Плз, длинные портянки кодов стОит прятать под спойлеры (ИМХО).

2.

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

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

Именно для того, чтобы и первым и вторым было чему поучиться, да и третьим встряхнуть извилины и порой сбросить шоры - и предназначен (ИМХО) этот топик. Так что полезным может быть любой совет, лишь бы ... он не преподносился как 'истина в последней инстанции'.

Твой вариант конечно может принести пользу многим, кто и этого не знал. Однако от очевидностей он еще довольно далек.

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

function actor_update(actor, delta)
  --- Действия, которые надо производить с частотой fps
  time_100 = time_100 + delta
  if time_100 > 100 then
    --- Действия, которые надо производить с частотой раз в 100мс
    time_300 = time_300 + time_100
    if time_300 > 300 then
      --- Действия, которые надо производить с частотой раз в 300мс
      time_1200 = time_1200 + time_300
      if time_1200 > 1200 then
        --- Действия, которые надо производить с частотой раз в 1200мс
        time_1200 = 0
      end
      time_300 = 0
    end
    time_5000 = time_5000 + time_100
    if time_5000 > 5000 then
      --- Действия, которые надо производить с частотой раз в 5 секунд
      time_5000 = 0
    end
    time_100 = 0
  end
end

Сгруппировав потребные таймеры по вхождению друг в друга, взаимопоглощается еще немало операций по сложению и сравнению. Ведь нет смысла складывать те же 300 ms из дельт по ~20 ms, когда они все одно будут равны 100х3. Ни у и т.д.

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

function any_update()
  --- Действия, которые надо производить с частотой fps
  local time = time_global() --/ текущее время
  if time_100 < time then
    time_100 = time + 100
    --- Действия, которые надо производить с частотой раз в 100мс
    if time_300 < time then
      time_300 = time + 300
      --- Действия, которые надо производить с частотой раз в 300мс
      if time_1200 < time then
        time_1200 = time + 1200
        --- Действия, которые надо производить с частотой раз в 1200мс
      end
    end
  end
  if time_5000 < time then
    time_5000 = time + 5000
    --- Действия, которые надо производить с частотой раз в 5 секунд
  end
end

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

 

По сути 2-ой вариант и применяется во многих модах и развитие того, что есть и в оригинальной игре. Так что, кому-то приглянется твой вариант, кому-то иной, а кто-то давно третьим пользуется.

А один из основных недостатков и твой и 1-й вариант не решают. Все одно все 'тяжелые' скрипты/алгоритмы будут выполняться единомоментно - а значит будут лаги.

Т.о. требуется не только оптимизировать ресурсоемкость/производительность указанной линейки таймеров, а и разносить тяжелые операции по времени, т.е. по нескольким (ассинхронным) таймерам.

По более оптимальным и 'правильным' вариантам можно поговорить когда интерес и потребность к ним возникнет.

 

Ну а на твой 'p.s.' по 1-м страницам топика - могу дать пару ремарок:

1. А что тебе мешало по интересующим тебя вопросам начать разговор на тех же 1-х страничках? Места пока в топике предостаточно. :-)

Замечу, (ИМХО) твой пост к Lua никакого отношения не имеет (и более уместен где-то в "Скриптовании ..."), по сравнеию с тем, что ранее обсуждался редактор заточенный именно под Lua.

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

От используемого редактора зависит как производительность программиста, так и собственно его уровень и навыки.

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

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

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

 

 

Добавлено через 14 мин.:

Gun12, аналогично. :-)

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

Все мы в чем-то чайники и далеко не во всем 'матерые'! На то и раздел - "Школа моддинга"

Изменено пользователем Artos

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

Ссылка на комментарий

Artos

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

А за поправки и уточнения спасибо :)

Вариант "елочка" довольно интересен, но, с другой стороны, когда допустим будут выполнены действия 1200 мс, то и 300мс, и 100мс будут выполнены в тот же такт. А это уже может привести к изрядному подвисанию, если там что-то тяжелое. Несколько независимых таймеров можно подстроить так, что их частоты не будут кратны друг другу. Например, 100мс, 260, 1170, и т.д.. нагрузка на цп будет распределяться равномернее. А в варианте "елочка" пики нагрузки неизбежны.

 

Раньше не писал - потому что меня не было на форуме. Работа.

Позвольте, разве я для себя пишу? :) Ни в коей мере.

Ну и наконец. Да, к Lua это отношения не имеет никакого. Я применял точно такой же прием, когда писал код в Delphi. Но думаю, от этого он не становится менее полезным.

 

Изменено пользователем Zander_driver

Мод, где не бывает одинаковых путей - Судьба Зоны. (Лучшее, что у меня получилось на X-Ray) На базе модифицированного движка OGSR Engine.

Бывший мододел на X-Ray / Начинающий игродел на Unreal Engine. Программист.

AMD Ryzen 9 7950X (16 ядер, 5.7ГГц); RTX 3080; 128 ГБ DDR5; Arctic Liquid Freezer II-420; 3 ТБ SSD PCIe 4.0; 4ТБ HDD.

Ссылка на комментарий

Zander_driver: Ну насчет того, что мой пост был бы уместнее "Скриптовании", поспорю

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

Твои же слова из того же поста: "Да, к Lua это отношения не имеет никакого. Я применял точно такой же прием, когда писал код в Delphi." - сводят на нет твои же "аргументы" и только подтверждают вышесказанное.

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

А вот "Тема для обсуждения скриптов, спавна и логики всего и всех в серии игр STALKER." - как раз и подходит и для обсуждения алгоритмов, т.к. 'скрипт' - это в первую очередь некий алгоритм.

Ну а то, что там общение ведется в основном в режиме 'вопрос-ответ' - так это всего лишь желание тех, кого интересуют ответы, и леность тех, кто мог бы поделиться своими соображениями/наработками и дать советы, но ... не делает этого.

 

По сути твоих рассуждений по таймерам:

Не хочу обидеть, конечно, но ... давая советы 'начинающим скриптерам', ты сам недалеко от них отошел и набрав немного навыков/опыта, так и не снял шоры на многое.

Zander_driver: Вариант "елочка" довольно интересен, но, с другой стороны, когда допустим будут выполнены действия 1200 мс, то и 300мс, и 100мс будут выполнены в тот же такт. А это уже может привести к изрядному подвисанию, если там что-то тяжелое. Несколько независимых таймеров можно подстроить так, что их частоты не будут кратны друг другу. Например, 100мс, 260, 1170, и т.д.. нагрузка на цп будет распределяться равномернее.
Хорошо, что все же заметил этот недостаток, хотя при более внимательном чтении о нем же уже было сказано:
Artos: А один из основных недостатков и твой и 1-й вариант не решают. Все одно все 'тяжелые' скрипты/алгоритмы будут выполняться единомоментно - а значит будут лаги.

Т.о. требуется не только оптимизировать ресурсоемкость/производительность указанной линейки таймеров, а и разносить тяжелые операции по времени, т.е. по нескольким (ассинхронным) таймерам.

Жаль не заметил слов 'и твой', читать нужно внимательнее.

Все твои таймеры имеют одну и ту же стартовую установку (0) Это означает, что ВСЕ они работают СИНХРОННО, и когда сработает таймер 1200, в это же время сработает и таймер 300 и таймер 100.

Сделав вроде как несколько "независимых" таймеров - по сути ты просто продублировал один и тот же, только задав различные периоды. И в любом случае при кратности периодов - таймеры будут запускать ведомые функции единовременно.

Заинтересовавший тебя вариант "елочка" НИЧЕМ не отличается от твоего же, только оптимизирован с целью исключения лишник операция по сложению и сравнению. Твой вариант точно также будет запускать все вызовы так же как и "елочка", т.е. единовременно.

Итого, если хочешь получить независимые (ассинхронные) таймеры следует сделать что-то типа:

local time_1000,time_5000

function any_update()
  local time = time_global() --/ текущее время
  if not time_1000 then --/ предустановка таймеров
   time_1000 = time + 1000
   time_5000 = time + 5000 + time_1000/2
   --time_5000 = time + 5000 + matc.random(-time_1000/4,time_1000/4) --/ или можно иначе
  end
  if time_1000 < time then
    time_100 = time + 1000
    --- Действия, которые надо производить с частотой раз в 1 сек
  end
  if time_5000 < time then
    time_5000 = time + 5000
    --- Действия, которые надо производить с частотой раз в 5 сек
  end
end

Вот тогда таймеры будут и разнесены (в примере как минимум на 0.5сек) и независимы

 

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

Изменено пользователем Artos

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

Ссылка на комментарий

Не все действия требуется делать с максимальной частотой, некоторые действия надо просто сделать "когда-то". Ставим низкоприоритетные действия в очередь. При очередном обновлении выполняем очередное действие из очереди, они естественным образом "размазываются" по времени. На каждый апдейт вешаем только то, что реально требует быстрой реакции: имитацию критического хита, к примеру (требует мгновенной реакции на падение здоровья). Остальные стоят в очереди =)

 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Ссылка на комментарий

Все дейстия можно разделить на группы:

а) - постоянно требующие проверок условий/вызовов на всем протяжении игры (например, проверка здоровья/хита/...);

б) - постоянно требующие проверок условий/вызовов на протяжении игры, но на конкретной локации (группе локаций);

в) - требующие непрерывных проверок условий/вызовов до или после некоего события (например инфопоршня);

г) - требующие разовой проверки условий/вызовов после некоего события и до другого события;

...

Если на апдейты вешать вызовы только тогда, когда это требуется и отключать их, когда в них отпала нужда - бОльшая часть очередей рассосется и 'не активное' не будет вообще как-либо нагружать игру и съедать FPS'ы. :-)

Изменено пользователем Artos

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

Ссылка на комментарий

Предлагаю обсудить вариант безопасного, информативного и производительного вывода информации в лог, с учетом особенностей игры.

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

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

 

Итак критерии: 1. - надежность (безопасность) кода, 2. - информативность и 3. - производительность.

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

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

3. Вывод в лог и получение его в лог-файле должен по-возможности не приводить к значительным лагам в игре.

 

Предлагаю рабочий вариант комплекта функций для вывода в лог (лог-файл):

--/------------------------------------------------------------------
--/ Functions for Print-Log
--/------------------------------------------------------------------
local console --/ переменная, кеширующая функцию консоли
--/----------------------------------------------
--/ вывод в буфер лога
--/ незначительно нагружает игру, но при фатальных ошибках информация из буфера лога может быть утеряна
--/----------------------------------------------
function printf(fmt,...)
  if not console then console = get_console() end
  if next({...}) then
    fmt = string.sub(this.FormatToString(fmt,...),1,250)
    console:execute("load :>"..fmt)
  else
    console:execute("load :>"..tostring(fmt))
  end
end
--/----------------------------------------------
--/ вывод в лог-файл
--/ сохраняет каждую строку в лог-файл в реальном времени, но в циклах и апдейтах может значително тормозить игру.
--/----------------------------------------------
function print_f(...)
  printf(...)
  console:execute("flush") --/ команда записи буфера лога в файл
end
--/----------------------------------------------
--/ конвертер строки для 'printf'
--/----------------------------------------------
function FormatToString(fmt,...)
  --/ локальная функция: перевод аргумента в стринг
  local to_str = function(val,typ)
    if typ == 'boolean' then
      return tostring(a) --/>
    elseif typ == 'table' or typ == 'userdata' then
      if tonumber(val.x) and tonumber(val.y) and tonumber(val.z) then
        if typ == 'table' then
          return string.format('tab{x=%.3f:y=%.3f:z=%.3f}',val.x,val.y,val.z) --/>
        end
        return string.format('vec(x=%.3f:y=%.3f:z=%.3f)',val.x,val.y,val.z) --/>
      end
    elseif typ == 'number' then
      return tostring(val) --/>
    elseif typ == 'string' then
      return val --/>
    end
    return "<"..typ..">" --/>
  end
  --/ основное тело функции: парсинг исходной строки
  if type(fmt) == 'string' then
    if fmt:match('%\%[s|d]') then --/ есть патерн(ы)
      if next({...}) then --/ есть аргумент(ы)?
        local arg,val,typ,i = {...},nil,nil,nil
        for i=1,#arg do
          val = arg[i] --/ значение текущего аргумента
          typ = type(val) --/ тип текущего аргумента
          if typ == 'string' then
            fmt = fmt:gsub('%\%s',val,1)
          elseif typ == 'number' then
            if fmt:match('%\%d') then
              fmt = fmt:gsub('%\%d',val,1)
            else
              fmt = fmt:gsub('%\%s',val,1)
            end
          else
            fmt = fmt:gsub('%\%s',to_str(val,typ),1)
          end
        end
      end
      fmt = fmt:gsub('%\%[s|d]',"<NOT_arg!>") --/ заглушка от отсутствия аргументов
    end
  else
    fmt = to_str(fmt,type(fmt))
  end
  --/ с заменой обычных пробелов (sym_space='\032') на печатные ('\160')
  return fmt:gsub('%s','\160') --/>
end
--/------------------------------------------------------------------
--/ Abort (функция принудительного прерывания игры)
--/------------------------------------------------------------------
function abort(fmt, ...)
  local sReason = this.FormatToString(fmt or "<>",...) or '<Not_reason>'
  assert("ERROR:" .. sReason)
  if not console then console = get_console() end
  console:execute(string.rep("~",96))
  console:execute("load :>ВНИМАНИЕ!_Ошибка!_Ниже_строка_с_информацией_по_ошибке!")
  console:execute("load :>ОШИБКА:"..string.gsub(sReason,"%s",'\160'),1,500)
  console:execute("load :>ВНИМАНИЕ!_Игра_прервана!")
  console:execute("load :>"..string.rep("~",96))
  console:execute("flush")
  exit() --/> прерываем игру при фатальных ошибках
end
--/------------------------------------------------------------------

 

Замечания, предложения, критика и оптимизиция - приветствуются. :-)

Изменено пользователем Artos

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

Ссылка на комментарий

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

 

Artos.

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

Мой пример с асинхронными таймерами был для наглядности. Мои собственные таймеры по 100, 300 и 1200 - асинхронны ТОЖЕ. Как же так? Всмотритесь в код внимательнее и подумайте, как он выполняется.

Подсчет прошедшего времени ведется по delta. это период времени в миллисекундах, прошедший с прошлого апдейта. В каждом апдейте к таймерам прибавляется delta, и если набралось больше заданного предела - таймер срабатывает. И все ваши рассуждения были бы верны, если бы delta была равна 1. всегда. Но это возможно только если fps = 1000. Едва ли много у кого есть компьютеры столь монструозной мощи, чтобы тянуть сталкер ТЧ на такой скорости.

А на практике у среднего игрока средний fps лежит в пределах нескольких десятков, причем это очень переменная величина даже на одной машине.

При fps ~ 60 имеем delta ~ 16.

И что же в таком случае происходит?

1-й такт.

timer_100 = 0, timer_300 = 0, timer_1200 = 0

прибавляем ко всем таймерам по отдельности нашу delta. Будем хоть тут считать, что она постоянна и равна 16.

2-й такт

timer_100 = 16, timer_300 = 16, timer_1200 = 16

3-й такт

timer_100 = 32, timer_300 = 32, timer_1200 = 32

4-й такт

timer_100 = 48, timer_300 = 48, timer_1200 = 48

5-й такт

timer_100 = 64, timer_300 = 64, timer_1200 = 64

6-й такт

timer_100 = 80, timer_300 = 80, timer_1200 = 80

7-й такт

timer_100 = 96, timer_300 = 96, timer_1200 = 96

8-й такт

timer_100 = 112, timer_300 = 112, timer_1200 = 112

в первом таймере выполняется условие. Выполняем его действия и обнуляем таймер, теперь timer_100 = 0

9-й такт

timer_100 = 16, timer_300 = 128, timer_1200 = 128

пропустим 6 тактов, в которые не происхоит ничего особенного, прибавим ко всем таймерам по 6*16=96

15-й такт

timer_100 = 112, timer_300 = 224, timer_1200 = 224

В первом таймере опять выполняется условие. Он опять выполняется и обнуляется, timer_100 = 0

16-й такт

timer_100 = 16, timer_300 = 240, timer_1200 = 240

На этот раз пропустим всего 4 такта. Прибавим везде по 4*16 = 64

20-й такт

timer_100 = 80, timer_300 = 304, timer_1200 = 304

Вот и ваша ошибка. timer_300 выполнил условие и сработал, а timer_100 - еще нет. Где же ваша синхронность? А нет ее. timer_300 обнуляется

21-й такт

timer_100 = 96, timer_300 = 16, timer_1200 = 320

22-й такт

timer_100 = 112, timer_300 = 32, timer_1200 = 336

Вот теперь наконец сработал timer_100. Через два такта после своего соседа.

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

 

 

p.s. Вам бы самому не мешало иногда перечитывать свои слова, и не выдавать свои размышления за истину в последней инстанции. Пусть они основаны на теории, которая вам кажется верной. Может я и ошибся, но

Все твои таймеры имеют одну и ту же стартовую установку (0) Это означает, что ВСЕ они работают СИНХРОННО, и когда сработает таймер 1200, в это же время сработает и таймер 300 и таймер 100.

Сделав вроде как несколько "независимых" таймеров - по сути ты просто продублировал один и тот же, только задав различные периоды. И в любом случае при кратности периодов - таймеры будут запускать ведомые функции единовременно.

Заинтересовавший тебя вариант "елочка" НИЧЕМ не отличается от твоего же, только оптимизирован с целью исключения лишник операция по сложению и сравнению. Твой вариант точно также будет запускать все вызовы так же как и "елочка", т.е. единовременно.

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

p.p.s. И совсем уж переходя на личности... Artos, помоему вы очень любите спорить :) Хотя я и сам такой...

 

Мод, где не бывает одинаковых путей - Судьба Зоны. (Лучшее, что у меня получилось на X-Ray) На базе модифицированного движка OGSR Engine.

Бывший мододел на X-Ray / Начинающий игродел на Unreal Engine. Программист.

AMD Ryzen 9 7950X (16 ядер, 5.7ГГц); RTX 3080; 128 ГБ DDR5; Arctic Liquid Freezer II-420; 3 ТБ SSD PCIe 4.0; 4ТБ HDD.

Ссылка на комментарий

Zander_driver

Начну с конца, хоть это не совсем оффтопик, т.к. касается любой темы:

Zander_driver: Artos, помоему вы очень любите спорить Хотя я и сам такой...

Скажем так: "Я не любитель лишь бы спорить, но по делу - не прочь и шпаги скрестить."

Есть избитое выражение: "В споре рождается истина". И я полностью согласен с этим, если не называть спором - базар/флуд или переливание из пустого в порожнее.

Есть разные формы общения: монологи, диалоги/дискуссии, споры ... Если первые подразумевают информирование, обмен мнениями, то последнее - защиту своего мнения/выбора/решения.

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

 

Вернемся к нашим баранам, то бишь таймерам. Никакого спора с моей стороны не было и нет.

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

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

Конечно же раписывать 'все и вся' в подробностях и нюансах без нужды никто из нас не затрудняет. От того и недопонималки. Ну а посты 'через день на третий' тем более не способствуют диалогу.

Не стану сейчас вдаваться в подсчеты цифр и пр. частности. Признаю, что при определенных условиях таймеры в твоем решении работают ассинхронно, и т.п. И в то же время не собираюсь начинать обсуждать то, что никакого отношения к таймерам они не имеют, точнее если и имеют, то только работая по неким интервалам времени, никак не соответствующим тобою же заявляемым 100мсек, 1сек, 5сек и т.п.

 

Итак, что же потребно тем, у кого есть необходимость в таймерах. Что должо решить предлагаемое? Каковы критерии?

1. Минимизация вызовов внешних функций в единицу времени, дабы не расходовать лишние ресурсы игры;

2. Минимизация единомоментных вызовов внешних функций, дабы исбегать возникновения лагов в игре;

3. По-возможности, независимость от 'субъктивных' причин: слабый комп, маловато памяти, высокие/низкие настройки в игре, ... ;

4. Универсальность реализации/применения в различных скриптах/кодах.

 

Твой вариант "таймеров" частично решает только первую часть задачи. Выполнение 2-го критерия полно услловностей и никак не исключает того, что в какой-то момент времени все функции будут вызваны единомоментно. Ну о 3 и 4 можно и не говорить, т.к. завязка периодов целиком завязана на собственно 'мощности компа' и кроме как в методах апдейта подобное не применить.

 

Дав вариант решения "вариант универсальный:" и чут позже "ассинхронные таймеры" - я, например и не вижу далее обсуждать, и тем более спорить, о прежнем. Или тебе хочется поспорить что хужЕе твой вариант или "елочка"? ;-)

 

Ну и о полезном. Выше malandrinus и я дали тезисно основные ключевые моменты для таймеров, предназначенных для решения сделующих задач (напомню):

1. Минимизация вызовов внешних функций в единицу времени, дабы не расходовать лишние ресурсы игры;

2. Минимизация единомоментных вызовов внешних функций, дабы исбегать возникновения лагов в игре;

3. По-возможности, независимость от 'субъктивных' причин: слабый комп, маловато памяти, высокие/низкие настройки в игре, ... ;

4. Универсальность реализации/применения в различных скриптах/кодах.

 

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

local timer,counter = 0,0 --/ таймер и счетчик периодов

function any_update()
  local time = time_global() --/ текущее время
  if timer < time then
    timer = time + 100 --/ 'тикает' таймер (можно задать и иное)
    if counter >= 10 then
      counter = 1 --/ сброс счетчика периодов
    else
      counter = counter +1 --/ следующий период
    end
    --/ Действия, которые надо производить с 1 раз в 0.1 сек (100ms)
    if     counter == 1 then
      --/ Действие 1, выполняющиеся 1 раз в сек (1s)
    elseif counter == 3 then
      --/ Действие 2, выполняющиеся 1 раз в сек (1s)
    elseif counter == 5 then
      --/ Действие 3, выполняющиеся 1 раз в сек (1s)
    elseif counter == 7 then
      --/ Действие 4, выполняющиеся 1 раз в сек (1s)
    elseif counter == 9 then
      --/ Действие 5, выполняющиеся 1 раз в сек (1s)
    else
      --/ Действия-02, выполняющиеся 5 раз в сек (200ms)
      if counter == 3 or counter == 6 or counter == 9 then
        --/ Действия-03, выполняющиеся 3 раза в сек (333ms)
        if counter ~= 6 then
          --/ Действия-05, выполняющиеся 2 раза в сек (500ms)
        end
      end
    end
  end
end

 

Ну и более соответствующий всем 4-м критериям:

local iCnt,iIdx = 0,0 --/ кол-во активных функций и индекс текущей
local iTimer,iTimePrd = 0,0 --/ таймер и период его обновления
local tExtFunc = { --/ массив внешних функций и флаг(вкл/откл)
  [1] = {func = my_script_1.my_func_1, flg = true},
  [2] = {func = my_script_2.my_func_2, flg = true},
  [3] = {func = my_script_3.my_func_3, flg = true},
  [4] = {func = my_script_4.my_func_4, flg = true},
  [5] = {func = my_script_5.my_func_5, flg = true}
}

function any_update()
  if iTimer < time_global() then  --/ сверка с текущим временем
    if iIdx < iCnt then
      iIdx = iIdx +1 --/ следующий период
    else --/ закончен цикл: обновляем параметры
      iIdx = 1 --/ сброс индекса текущего периода
      iCnt = 0 --/ сброс кол-ва функций
      for k,v in pairs(tExtFunc) do
        if v.flg == true then --/ вызов функции активен?
          iCnt = iCnt +1 --/ подсчет кол-ва активных вызовов
        end
      end
       --/ установка периода вызовов в пропорции от кол-ва активных вызовов
      iTimePrd = 1000/math.max(iCnt,0.1) --/ при нуле - ставим тайм-аут 10 сек
    end
    iTimer = time_global() + iTimePrd --/ 'тикает' таймер
    if tExtFunc[iIdx].flg == true then --/ функция активна?
      tExtFunc[iIdx].func() --/ вызываем внешнюю функцию
    end
  end
end

function Add_MyFunc(my_func) --/ добавление функции
  table.include( tExtFunc, {func = my_func, flg = true} )
end

function Start_MyFunc(iIdx) --/ активизация вызова функции
    tExtFunc[iIdx] = true
end

function Stop_MyFunc(iIdx) --/ деактивизация вызова функции
    tExtFunc[iIdx] = false
end

 

Данный вариант предусматривает только по-секундный таймер, который позволяет 'размазать' вызываемые функции по всему диапазону времени (периоду 1 сек), от(в)ключать вызовы функций извне, добавоять дополнителльные функции. Сделать при необходимости аналогичные таймеры на милисекунды иль минуты/часы - получаем полный набор. Внеся предуставноки, позволяющие разнести и добавленные таймеры, можно добиться исключения единомементности запуска различных функций, что минимизирует возможность лагов в игре.

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

 

P.S. Подправленный код для

local iCnt,iAct,iIdx = 0,0,0 --/ кол-во функций/вызовов и из порядковый индекс
local iTimer,iTimePrd = 0,0 --/ таймер и период его обновления
local tExtFunc = { --/ массив внешних функций и флаг(вкл/откл)
  [1] = {func = my_script_1.my_func_1, flg = true},
  [2] = {func = my_script_2.my_func_2, flg = true},
  [3] = {func = my_script_3.my_func_3, flg = true},
  [4] = {func = my_script_4.my_func_4, flg = true},
  [5] = {func = my_script_5.my_func_5, flg = true}
}

function any_update()
  if iTimer < time_global() then
    if iIdx >= iCnt then --/ цикл закончен: обновляем параметры
      iTimePrd = 1000 --/ общий период цикла таймера (1 сек)
      iCnt,iAct,iIdx = 0,0,1 --/ сброс кол-в и индекса
      for k,v in pairs(tExtFunc) do
        iCnt = iCnt +1 --/ подсчет кол-ва функций
        if v.flg == true then
          iAct = iAct +1 --/ подсчет кол-ва активных вызовов
        end
      end
      if iCnt > 1 then
        --/ 'размазываем' период на кол-во активных вызовов
        iTimePrd = iTimePrd/iAct
      end
    else
      iIdx = iIdx +1 --/ следующий период цикла
    end
    iTimer = time_global() + iTimePrd --/ 'тикает' таймер
    for i=iIdx,iCnt do
      --/ неактивные вызовы пропускаются
      if tExtFunc[i].flg == true then --/ функция активна?
        tExtFunc[i].func() --/ вызываем внешнюю функцию
        return
      end
    end
  end
end

function Add_MyFunc(my_func) --/ добавление функции
  table.include( tExtFunc, {func = my_func, flg = true} )
end

function Start_MyFunc(iIdx) --/ активизация вызова функции
    tExtFunc[iIdx] = true
end

function Stop_MyFunc(iIdx) --/ деактивизация вызова функции
    tExtFunc[iIdx] = false
end

 

Изменено пользователем kokkai

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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

 

Пытался, не прокатило, но только потому, что я не включил в него luabind.

В принципе, есть вероятность что разработчики движка дописать какое-то API сами, но если нет, то в результате, после подключения luabind, должно работать.

Изменено пользователем Monnoroch
Ссылка на комментарий

Один хороший товарищ уже всё подключил к ТЧ. Luа в полном составе и работает. Сейчас тестируем.

Только вот не стоит забывать, что функции dеbug,* изначально работают значительно медленнее стандартных. Даже JIТ особо не поможет.

Ссылка на комментарий

Gun12,

Один хороший товарищ уже всё подключил к ТЧ. Luа в полном составе и работает.

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

 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Ссылка на комментарий

Не я автор.

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

Как только "хозяин" решит, то сам и выложит. Сорри.

 

Ну так а какой глубокий смысл тогда был об этом сообщать?

 

Добавлено через 34 мин.:

Какой смысл?

Были сомнения по поводу нужности этой работы.

Видим что нужно.

Изменено пользователем malandrinus
Ссылка на комментарий

а читать сообщение до конца никто не пробовал?:)

Сейчас тестируем

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

Vita sine libertate, nihil

Vita sine litteris - mors est

Ссылка на комментарий

RvP, а не проще тестировать не в 'две руки'? Как грится ... одна голова хорошо, две - лучше, но и другие не помешают. ;-)

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

(сорри за оффтопик)

Изменено пользователем Artos

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

Ссылка на комментарий

RvP, а ты не заметил что 'две руки' - в кавычках, да еще и прописью? Все же это не информация и не констатация некоего факта, а образное выражение. Как, в прочем, и про головы ...

 

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

Ну да ... ежели нет желания/потребности, то и не настаиваем и не навязываем(ся). :-)

[x]

Изменено пользователем Artos

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

Ссылка на комментарий

Создайте аккаунт или авторизуйтесь, чтобы оставить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

Создать аккаунт

Зарегистрировать новый аккаунт в нашем сообществе. Это несложно!

Зарегистрировать новый аккаунт

Войти

Есть аккаунт? Войти.

Войти
  • Недавно просматривали   0 пользователей

    Ни один зарегистрированный пользователь не просматривает эту страницу.

AMK-Team.ru

×
×
  • Создать...