Dennis_Chikin 3 658 Опубликовано 4 Января 2015 Поделиться Опубликовано 4 Января 2015 (изменено) С чего начинать и где взять. Установка 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 Изменено 2 Марта 2015 пользователем Kirgudu Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
Artos 99 Опубликовано 24 Сентября 2011 Поделиться Опубликовано 24 Сентября 2011 (изменено) (по следам ранее опудликованного) "Упаковка таблицы в строку (стринг) и обратная распаковка" Доработанный вариант с возможностью проверки в 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 мин.: Поправил пару ошибок в комплекте ... (тяжко все же коды вставлять в парсер форума) Изменено 24 Сентября 2011 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Ссылка на комментарий
Nazgool 250 Опубликовано 24 Сентября 2011 Поделиться Опубликовано 24 Сентября 2011 (изменено) ...т.е. для запуска надо нажать 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 Изменено 24 Сентября 2011 пользователем Gun12 Ссылка на комментарий
Zander_driver 10 333 Опубликовано 27 Сентября 2011 Поделиться Опубликовано 27 Сентября 2011 Выскажу свой совет по оптимизации скриптов именно применительно к игре. Может быть, это будет из разряда "Капитан очевидность рекомендует", но я своих методов в других модах пока не встретил, так что думаю начинающим скриптерам будет полезно. Для начала интересный факт, не все знают. Частота вызова апдейта актора в игре абсолютно точно совпадает с частотой 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 ядер, 32 потока, 5.75 ГГц); RTX 3080; 128 ГБ DDR5; Arctic Liquid Freezer II-420; 3 ТБ SSD PCIe 4.0; 4ТБ HDD. Ссылка на комментарий
Nazgool 250 Опубликовано 27 Сентября 2011 Поделиться Опубликовано 27 Сентября 2011 ...пока большая часть темы представляет практический интерес не для всех, кто считает себя скриптерами... Дружище, чтобы знать что именно интересует людей, от них требуется как минимум озвучить свой интерес. Например мне в ЛС задают вопросы, и я прошу задавать их в этом топике. Но то ли стесняются, то ли ...? Не знаю. Если бы была способность предугадывать желания, то я сменил бы профессию :-) Ссылка на комментарий
Artos 99 Опубликовано 27 Сентября 2011 Поделиться Опубликовано 27 Сентября 2011 (изменено) 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, аналогично. :-) Почему-то многие боятся задавать вопросы, просить помощи в топиках форума ... И это скорее не стеснение, а боязнь выглядеть в глазах многих иль совсем уж 'чайником', или не таким уж матерым кодером ... Все мы в чем-то чайники и далеко не во всем 'матерые'! На то и раздел - "Школа моддинга" Изменено 27 Сентября 2011 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Ссылка на комментарий
Zander_driver 10 333 Опубликовано 27 Сентября 2011 Поделиться Опубликовано 27 Сентября 2011 (изменено) Artos Ну насчет того, что мой пост был бы уместнее "Скриптовании", поспорю. По некоторым причинам это может и так, но там ведется общение по системе вопрос-ответ, И вряд ли кто-то бы задал вопрос именно об этом, т.к. начинающие модеры обычно делают по принципу "у меня работает - значит нормально". Мой же пост скорее является рекомендацией по организации кода, чтобы работало не только у себя любимого, но и у других. А за поправки и уточнения спасибо Вариант "елочка" довольно интересен, но, с другой стороны, когда допустим будут выполнены действия 1200 мс, то и 300мс, и 100мс будут выполнены в тот же такт. А это уже может привести к изрядному подвисанию, если там что-то тяжелое. Несколько независимых таймеров можно подстроить так, что их частоты не будут кратны друг другу. Например, 100мс, 260, 1170, и т.д.. нагрузка на цп будет распределяться равномернее. А в варианте "елочка" пики нагрузки неизбежны. Раньше не писал - потому что меня не было на форуме. Работа. Позвольте, разве я для себя пишу? Ни в коей мере. Ну и наконец. Да, к Lua это отношения не имеет никакого. Я применял точно такой же прием, когда писал код в Delphi. Но думаю, от этого он не становится менее полезным. Изменено 27 Сентября 2011 пользователем Zander_driver Мод, где не бывает одинаковых путей - Судьба Зоны. (Лучшее, что у меня получилось на X-Ray) На базе модифицированного движка OGSR Engine. Бывший мододел на X-Ray / Начинающий игродел на Unreal Engine. Программист. AMD Ryzen 9 7950X (16 ядер, 32 потока, 5.75 ГГц); RTX 3080; 128 ГБ DDR5; Arctic Liquid Freezer II-420; 3 ТБ SSD PCIe 4.0; 4ТБ HDD. Ссылка на комментарий
Artos 99 Опубликовано 27 Сентября 2011 Поделиться Опубликовано 27 Сентября 2011 (изменено) 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сек) и независимы (Приношу извинения куратору темы за этот оффтопик, но ... так и не понял до сих пор, о чем же тут стОит гутарить, а чего следует избегать.) Изменено 27 Сентября 2011 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Ссылка на комментарий
Malandrinus 615 Опубликовано 27 Сентября 2011 Автор Поделиться Опубликовано 27 Сентября 2011 Не все действия требуется делать с максимальной частотой, некоторые действия надо просто сделать "когда-то". Ставим низкоприоритетные действия в очередь. При очередном обновлении выполняем очередное действие из очереди, они естественным образом "размазываются" по времени. На каждый апдейт вешаем только то, что реально требует быстрой реакции: имитацию критического хита, к примеру (требует мгновенной реакции на падение здоровья). Остальные стоят в очереди =) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Ссылка на комментарий
Artos 99 Опубликовано 27 Сентября 2011 Поделиться Опубликовано 27 Сентября 2011 (изменено) Все дейстия можно разделить на группы: а) - постоянно требующие проверок условий/вызовов на всем протяжении игры (например, проверка здоровья/хита/...); б) - постоянно требующие проверок условий/вызовов на протяжении игры, но на конкретной локации (группе локаций); в) - требующие непрерывных проверок условий/вызовов до или после некоего события (например инфопоршня); г) - требующие разовой проверки условий/вызовов после некоего события и до другого события; ... Если на апдейты вешать вызовы только тогда, когда это требуется и отключать их, когда в них отпала нужда - бОльшая часть очередей рассосется и 'не активное' не будет вообще как-либо нагружать игру и съедать FPS'ы. :-) Изменено 27 Сентября 2011 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Ссылка на комментарий
Artos 99 Опубликовано 29 Сентября 2011 Поделиться Опубликовано 29 Сентября 2011 (изменено) Предлагаю обсудить вариант безопасного, информативного и производительного вывода информации в лог, с учетом особенностей игры. Известно, что разработчики по сути кастрировали вывод информационных сообщений из скриптов. Разработка, отладка, поиск ошибок без подобной информации затруднителен. Начинающие модмейкеры порой и не догадываются о полезности вывода в лог удобных сообщений при модификациях кодов. Простейшие варианты грешат тем, что не учитываются возможные ошибки при выводе в лог и подобные функции просто 'глушат', дабы из-за них не вылетала игра. Итак критерии: 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 --/------------------------------------------------------------------ Замечания, предложения, критика и оптимизиция - приветствуются. :-) Изменено 29 Сентября 2011 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Ссылка на комментарий
Zander_driver 10 333 Опубликовано 29 Сентября 2011 Поделиться Опубликовано 29 Сентября 2011 Заранее прошу прощения за запоздалый ответ - раньше не было возможности, и за, по сути, оффтоп. Я думаю, этот мой пост стоит удалить после прочтения заинтересованными лицами. 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 ядер, 32 потока, 5.75 ГГц); RTX 3080; 128 ГБ DDR5; Arctic Liquid Freezer II-420; 3 ТБ SSD PCIe 4.0; 4ТБ HDD. Ссылка на комментарий
Artos 99 Опубликовано 30 Сентября 2011 Поделиться Опубликовано 30 Сентября 2011 (изменено) 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 Изменено 30 Сентября 2011 пользователем kokkai "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Ссылка на комментарий
Monnoroch 6 Опубликовано 10 Октября 2011 Поделиться Опубликовано 10 Октября 2011 (изменено) Теоретически, поскольку Lua в составе сталкера сделан в виде динамической библиотеки, то можно пересобрать эту библиотеку из исходников и добавить туда то, чего не хватает. Пытался, не прокатило, но только потому, что я не включил в него luabind. В принципе, есть вероятность что разработчики движка дописать какое-то API сами, но если нет, то в результате, после подключения luabind, должно работать. Изменено 10 Октября 2011 пользователем Monnoroch Ссылка на комментарий
Nazgool 250 Опубликовано 10 Октября 2011 Поделиться Опубликовано 10 Октября 2011 Один хороший товарищ уже всё подключил к ТЧ. Luа в полном составе и работает. Сейчас тестируем. Только вот не стоит забывать, что функции dеbug,* изначально работают значительно медленнее стандартных. Даже JIТ особо не поможет. Ссылка на комментарий
Malandrinus 615 Опубликовано 10 Октября 2011 Автор Поделиться Опубликовано 10 Октября 2011 Gun12, Один хороший товарищ уже всё подключил к ТЧ. Luа в полном составе и работает. Поделились бы что-ли. Всех проблем это не решает, но во многих вопросах развязывает руки: отладка, работа с файлами, или подключить те же сторонние либы с полноценными регулярными выражениями или парсингом XML, пользы много. Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Ссылка на комментарий
Nazgool 250 Опубликовано 10 Октября 2011 Поделиться Опубликовано 10 Октября 2011 (изменено) Не я автор. Поделиться не позволяют морально-этические. Как только "хозяин" решит, то сам и выложит. Сорри. Ну так а какой глубокий смысл тогда был об этом сообщать? Добавлено через 34 мин.: Какой смысл? Были сомнения по поводу нужности этой работы. Видим что нужно. Изменено 10 Октября 2011 пользователем malandrinus Ссылка на комментарий
RvP 1 Опубликовано 10 Октября 2011 Поделиться Опубликовано 10 Октября 2011 а читать сообщение до конца никто не пробовал? Сейчас тестируем если фатальных глюков нигде не будет, то в ближайшее время народ все это получит Vita sine libertate, nihil Vita sine litteris - mors est Ссылка на комментарий
Artos 99 Опубликовано 10 Октября 2011 Поделиться Опубликовано 10 Октября 2011 (изменено) RvP, а не проще тестировать не в 'две руки'? Как грится ... одна голова хорошо, две - лучше, но и другие не помешают. ;-) Ведь это же не мод, в котором поигральщики от глюков сопли распускают, а инструментарий для ... (сорри за оффтопик) Изменено 10 Октября 2011 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Ссылка на комментарий
RvP 1 Опубликовано 10 Октября 2011 Поделиться Опубликовано 10 Октября 2011 Artos, а откуда инфа что руки 2? О_о Vita sine libertate, nihil Vita sine litteris - mors est Ссылка на комментарий
Artos 99 Опубликовано 10 Октября 2011 Поделиться Опубликовано 10 Октября 2011 (изменено) RvP, а ты не заметил что 'две руки' - в кавычках, да еще и прописью? Все же это не информация и не констатация некоего факта, а образное выражение. Как, в прочем, и про головы ... Суть же в том, что конечно же сырец возморжно и не стОит публиковать ... , но если уже на стадии 'тестирования' - то лишние головы (а порой и руки) врядли помешают, а вот помочь (хотя бы в мелочах) - вполне могут. Ну да ... ежели нет желания/потребности, то и не настаиваем и не навязываем(ся). :-) [x] Изменено 10 Октября 2011 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Ссылка на комментарий
Рекомендуемые сообщения
Создайте аккаунт или авторизуйтесь, чтобы оставить комментарий
Комментарии могут оставлять только зарегистрированные пользователи
Создать аккаунт
Зарегистрировать новый аккаунт в нашем сообществе. Это несложно!
Зарегистрировать новый аккаунтВойти
Есть аккаунт? Войти.
Войти