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

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

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

Тем не менее ты прав в плане сравнения с нулем. Проверку с nехt делать всё равно прийдется. Например если есть и индексы от 1 до ... и не натуральные индексы.

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

Gun12, насчет проверки да :)

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

Vita sine libertate, nihil

Vita sine litteris - mors est

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

Gun12,

По поводу определения типа таблиц.

Набросок без итераторов. Проверку на пустую таблицу и скорость не делал.

function GetTabStatus(tab)
    if #tab ~= 0 and not  next(tab, #tab) then
        return 'index' -- индексирудмый массив
    else
        return 'hash' -- всё подряд
    end
end

Увы, здесь не учитывается тот случай, когда в таблице произвольного вида имеются несколько ключей, отвечающих правилам "массивов". Т.е. если есть хотя бы ключ [1], то так таблица будет распознана как массив, хотя там может быть что угодно ещё.

 

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

 

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

 

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

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

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

 

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

После if-а #t определит наличие/отсутствие ключей, начинающихся с 1 и до последнего целочисленного поступательно нарастающего.

Если есть такие, то проверяем, есть ли ещё какие-либо записи (nехt). Если есть - значит хэш, если нет, то индексный массив.

Прогоните код в SсiТЕ.

Не пойму, чего вы хотите от iраirs?

Всё происходит так как и положено.

Какому-то индексу присвоили значение nil. Сборщик мусора удалил это поле. Осталось дырка в индексах. Вот он и тормознул на этом поле. Совершенно правильно работает.

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

Не стал, чтобы не навешивать шоры, ранее давать свой двух-недельной давности набросок:

  local iIdx = next(tTbl)
  if iIdx then --/ проверка: таблица не пуста?
    --/#+# проверка: таблица типа 'список'?
    local bList = iIdx == 1 and #tTbl > 0 --/ флаг списка
    if bList then --/ предпроверка: может быть списком?
      --/ основная проверка: это 'список'?
      for i=1,#tTbl-1 do
        bList = next(tTbl,i) ~= nil
        if not bList then break end --/ прерываем - НЕ список!
      end
    end
    --/ тело основного кода
  end

- набросок пока имеет изъяны ...

Перебор как видно присутствует, но прерываемый. Остается внутри перебора максимально добиться 100% идентификации списков.

 

malandrinus

Тут два момента.

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

2. Делаю все же универсальный вариант и для тех кто порой не понимает разницы в типах таблиц (т.е. не готов определять директивный аргумент) и ... порой таблицы могут иметь различный тип ... упаковка 't должна быть всегда и по возможности максимально экономная.

 

 

Примечание:

Один раз написанная, достаточно универсальная функция упаковки/распаковки (AMK-мод), используется до сих пор многими модами по сути 'в слепую'.

Есть намерение и заменить ее на более актуальный и экономичный вариант и заодно избавить многие моды от ее ошибки (не дающей упаковывать некоторые типы таблиц):

function pack_new(tbl)
  --/ ...
  elseif type(v)=="boolean" then
    ret=ret..string.char(pack_type_bool)..v --/<!?! конкатенация строки и булева значения!?!
  --/ ...
end

Исправленный вариант:

    ret=ret..string.char(pack_type_bool)..((v and "1") or "0")

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

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

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

Может я не так понял. Может вы имеете в виду таблицы с целочисленными индексами, но произвольными? Но ведь в условии были даны варианты таблиц именно индексируемые с 1 и по порядку.Все другие варианты рассматриваются как хэш-ы. С ними нельзя работать ни оператором #, ни table.concat. Также в условии стояла задача узнать тип таблицы.

Я предложил вариант не только без пайрсов, но и без for i=1,#t.

Условия выполнены. Что не так? Не пойму.

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

Gun12

В условии были приведены два возможных варианта таблиц-списков (а не или-или).

Да, именно с индекса 1 они должны иль могут начинаться и по порядку.

Да, именно с целочисленными, но НЕ произвольными.

Порядок индексов может быть нарушен (иметь 'дырки'), если элемет(ы) списка были занилен(ы) (=nil).

Такие 'дырки' просто исключаются из списка (таблица-список чистится).

В твоем варианте непонятно, ведь 'next(tab, #tab)' для списка всегда должен возвращать 'nil', т.е. после последнего нет следующего элемента списка просто по определению.

Может имелось ввиду 'next(tab, #tab-1) > 0', т.е. имеется последний элемент в списке => длина всей таблицы не ноль

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

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

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

Artos, это я тебе возражу (и докажу) - т.к. в своё время всё то, о чём я говорил, неоднократно проверял, и всё это у меня и сейчас вполне так ничего себе работает :). Начнём сначала:

Файл 'smart_terrain_presets.ltx' является конфигом, который предназначен только для универсальных гулагов типа 'general_lager'

Для универсальных гулагов предназначен файл-пресет general_lager.ltx - а для "именных", как ты выразился :), предназначен тот, который я указал.

Далее:

респонеры NPC никаким местом не привязаны к каким-либо "гулагам". Поясняю ещё раз: любой респонер может плодить неписей для любых смарттеррейнов (или, если угодно, для "гулагов" :)), если он подходит по праметрам (повторюсь: в общем случае - группировка, ранг). Чтобы убедиться в этом, достаточно через некоторое время после начала игры (после срабатывания респонеров, это дело в разных модах реализовано совершенно по-разному) просмотреть глобальную карту (следует включить отображение всех NPC (или только сталкеров) на глобальной карте - как это сделать, пояснять, надеюсь, не потребуется?). В ярлычках неписей видно, что смарты наполнены совершенно разными респоновыми сталкерами, я имею в виду сталкерами, рождёнными совершенно разными респонерами. И не надо валить всё на алайф, "перегоняющий" сталкеров из одних смартов в другие, повторюсь: отслеживая путь любого NPC от респонера до смарта, становится очевидно, что никакие респонеры не привязаны ни к каким смартам. Единственные респонеры, срабатывающие в момент взятия NPC под гулаг (при условии, что условие для них прописано в смарте) - это респонеры, наполняющие ящики с барахлом в смартах. То есть, поменяв в конкретной секции "опытных" на "мастеров", ничего глобального ты не совершишь, т.к. в оллспоне есть куча других респонеров, которые наплодят и "опытных", и "новичков" в предостаточном количестве.

Далее:

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

Далее: ты, видимо, не знаком с тем фактом, что для любой работы в скриптовых файлах гулагов (тот же gulag_escape.script) можно создать условия "приёма на работу" (я частично уже отписывал, по каким параметрам это можно сделать - там ещё много чего можно "наусловить" :)). Эти условия называются predicate, и их, повторяю, можно применить к любой работе гулага. Если нужен конкретный пример - не поленись, загляни в этот файл (gulag_escape.script), и посмотри, как это реализовано для работы Волка или Фаната в смарте "лагерь новичков" (esc_lager). Таким образом, на работу "пахана" в смарте бандитов на АТП можно поставить условие, человеческим языком описываемое как "на работу принимается (назначается) только NPC с группировкой "бандит", с именем профиля "sim_bandit_master", и с рангом, равным 900 и более". Интересный факт: таким образом, кстати, можно обойти ограничение, выставленное для смартов Кордона в ltx-файле (конкретно smart_terrain_presets.ltx). Можно ещё кучу условий добавить :).

Заключение: в указанный смарт (esc_fabrika_bandit) в оригинальной игре принимаются NPC с группировкой "бандит", с рангами "новичок" (ранг менее 300), и "опытный" (ранг более 300 и менее 600). Никакие "мастера", заспоненные каким-то "назначенным именно для этого гулага" респонером под гулаг в смарте браться не будут. Чтобы изменить статус-кво, см. мой предыдущий пост - там всё для этого есть.

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

=VENOM=

Для чего такой запал? :D Нередко в пылу запала пропускается суть иль видятся фантомы ...

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

Мне, например, не ясно, что ты подразумеваешь под 'респонеры NPC'?

Если под ним подразумеваешь объект класса 'respawn', то почему именно NPC?

Респавнеры могут и монстров и оружие и пр. объекты респавнть!

2. Где-то мною упомянута что респавнеры плодят объекты привязанные к гулагам? Само собою, отреспавненные объекты подчиняются тем конфигам, которые определены их секцией. Это кому-то нужно доказывать?

Слова о связи этого конкретного респавнера 'esc2_respawn_bandits_fabrika' упомянута именно из-за его закомментированной логики связанной с конкретным гулагом. И его близким расположением - ведь работа выбирается от ближайшей ...

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

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

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

Если в вопросе автор только о правке конфига секции пояснил 'намерение заспавнить' и упомянуто 'заспавнить бандюков' - можно с большой долей вероятности сказать о неопытности спрашивающего в скриптовом спавне с правкой пакетов и тем более НЕ об одном бандюке ...

Не находишь, между твоими разъяснениями и навязываниями - нечто схожее на объяснения 'как взять интеграл' на вопрос: "а сколько будет 2х2"? :crazy:

4. Я то как раз не советовал 'создавать дополнительные секции' - а использовать уже готовые.

Простая правка строки респавнера, добавляющая иль заменяющая секции - и после перекомпиляции алл.спавна - АТП заматереит (после смерти существующих). :grin2:

5. Спасибо что просвятил нас сИрых и несмышленых насчет предикатов и возможности вносить свои условия для гулагов. Чоб мы делали ... Вот только одна загвоздка, а зачем растолковывать то, о чем НЕ спрашивается и апприори предполагать у опонентов незнание данной темы? :fool:

Но спасибо и за то, что вдруг кому-то это потребно и прочитает. Лишним не будет.

6. Твое замечание о необходимости править 'smart_terrain_presets.ltx' для включения в гулаги на локациях дефолтных НПС соответствуещих рангов, принимаю и не оспариваю. Запамятовал, давненько не копал этот файл, давно уже доведенный до потребной кондиции.

 

Итого: 1:5 в чью то пользу ;-)

 

Читай написанное, а не выдумывай то, что только в твоей голове возникло и что сам же "доказываешь".

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

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

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

Gun12,

Не пойму, чего вы хотите от iраirs?

Всё происходит так как и положено.

Какому-то индексу присвоили значение nil. Сборщик мусора удалил это поле. Осталось дырка в индексах. Вот он и тормознул на этом поле. Совершенно правильно работает.

Сборщик мусора удаляет не ссылки, а значения. Между тем в таблице собственно хранятся именно ссылки. Будет ли удалена из таблицы ссылка зависит от ряда обстоятельств. Вот пример различного поведения:

t = {1, nil, 2, 3} -- завёл массив с "дыркой посередине"

print(#t) -- его длина 4

t[3] = nil -- занулил элемент со значением 2

print(#t) -- длина по прежнему 4

 

t = {} -- исходно пустая таблица

t[3] = 1 -- заполняем с конца, здесь оператор длины даёт 0

t[2] = 2 -- здесь тоже 0

t[1] = 3

print(#t) -- имеем 3

-- типа получили массив? Как бы не так!

t[2] = nil -- зануляем элемент посередине

print(#t) -- получаем 1

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

 

Это бы всё не стоило обсуждения (работает так и работает, надо только знать), но в документации Lua декларируется, что таблицы - это всегда ассоциативные массивы, а разница массив/не массив - это только детали внутренней оптимизации, которые как-бы не должны сказываться на поведении. К примеру, то, что там внутри используется для реализации ассоциативного массива, в принципе не должно быть важно. Хеш, ну и хорошо. Могло быть и что-то другое, бинарные деревья к примеру. А в реальности выходит, что детали реализации и подробности внутренней оптимизации очень даже важны и заслуживают обсуждения.

 

Возьмём iраirs. В соответствии с мануалом перебор с её помощью и оператор взятия длины работают по одному алгоритму. Если почитать, то в документации на язык таки упоминается, что оператор длины должен измерять длину не только до первой дырки в целочисленных ключах, но и до первого значения nil. Так что выходит, что пример выше показывает фактически ошибочное или нестандартное поведение.

 

Но опять же если думать над этим дальше, то такое нестандартное поведение - единственная возможность перебирать по порядку аргументы функции, используя конструкцию args = {...}. Если всё будет работать в точном соответствии со спецификацией языка, то дальше первого пустого аргумента мы не уйдём.

 

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

Artos,

2. Делаю все же универсальный вариант и для тех кто порой не понимает разницы в типах таблиц (т.е. не готов определять директивный аргумент) и ... порой таблицы могут иметь различный тип ... упаковка 't должна быть всегда и по возможности максимально экономная.

Рискну заметить, что это не самая лучшая идея сама по себе, а в соединении с теми, кто "порой не понимает разницы в типах таблиц" идея приобретает совершенно разрушительную мощь. Если функция заранее знает, какого типа аргумент будет на входе, то можно будет сделать проверки и остановить работу с вразумительным сообщением. Если на входе возможные варианты, то функция уже никак не сможет сказать, а что на самом деле хотел сделать программист. Здесь ошибка в данных, или это неправильно использованный вызов или что-то ещё? Вариантов становится слишком много, проверить их все уже невозможно. Остаётся только довериться программисту, но ведь программист подразумевается такой, что "порой не понимает разницы в типах таблиц". Такому только доверься... Это готовый бардак в коде, испорченные сейвы, практическая невозможность отлаживать и вообще сопровождать код. IMHO, не стоит этого делать.

 

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

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

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

 

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

Выкладываю предварительный (рабочий!) вариант упаковки и распаковки таблиц в строку и обратно.

Предназначен для модернизации/замены варианта из АМК-мода v.1.4.x (pack_array_to_string/unpack_array_from_string) при сохранении в сэйвы.

Совместим с прежними сэйвами из модов, использовавшими прежний формат упаковки.

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

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

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

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

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

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

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

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

4. Совместим в прежними вариантами упаковки в модах и сэйвами. Обратной совместивости нет.

--/------------------------------------------------------------------
--/ Внимание! Строки в структуре не должны содержать символов с кодами 0-31.
--[[-----------------------------------------------------------------
  Формат упаковки:
    table     ::= ( list | subtable )
    list      ::= keytype nil valuetype ( value | subtable 0x5 )
    subtable  ::= keytype key valuetype ( value | subtable 0x5 )
    keytype   ::= ( 0x1 | 0x2 | 0x3 | 0x4 | 0x6 )
    valuetype ::= ( 0x1 | 0x2 | 0x3 | 0x4 )
--]]-----------------------------------------------------------------
local tPackChar = {
  n = {1, string.char(1)}, --/ 'number'
  s = {2, string.char(2)}, --/ 'string'
  b = {3, string.char(3)}, --/ 'boolean'
  t = {4, string.char(4)}, --/ 'table'
  e = {5, string.char(5)}, --/ table-end
  l = {6, string.char(6)}, --/#+# table-list
  h = {7, string.char(7)}  --/#+# number-hex
}
--/------------------------------------------------------------------
--/ Паковка таблицы в строку (и обратная распаковка)
--/------------------------------------------------------------------
--/ упаковка таблицы в строку - функция синоним прежней
function pack_array_to_string(tTbl,bList) --/< таблица [, (true|false) -флаг: список или нет]
  return Pack_Tbl(tTbl,bList) --/> String
end
--/ распаковка 'упакованной' строки в таблицу - функция синоним прежней
function unpack_array_from_string(sStr) --/< строка упакованной таблицы
  if sStr and sStr ~= '' then --/ защита от отсутствия или пустой строки
    if sStr:sub(1,1) == string.char(1) then
      --/ старый формат упаковки был тэгирован символом c кодом '1'
      if sStr:sub(2,2) == tPackChar.n[2] or sStr:sub(2,2) == tPackChar.s[2]  then
        --/ 2-м символом начинается упакованная строка
        sStr = sStr:sub(2,-1) --/ отрезаем 1-ый символ
      end
    end
    return Parse_Str(sStr) or {} --/> Table
  end
  return {} --/> ZeroTable
end
--/------------------------------------------------------------------
--/ упаковка таблицы 'tTbl' в строку 'sStr'
function Pack_Tbl(tTbl,bList) --/< таблица [, флаг: список или нет]
  if type(tTbl) ~= 'table' then
    return "" --/> не таблица
  end
  local sStr = ""
  local iIdx = next(tTbl)
  if iIdx then --/ проверка: таблица не пуста?
    if bList == nil then --/ если не задан тип таблицы - определяем 'список' или нет
      --/#+# проверка: таблица типа 'список'?
      bList = iIdx == 1 and #tTbl > 0
      if bList then --/ предпроверка: может быть списком?
        --/ основная проверка: этот 'список'?
        for i=1,#tTbl-1 do
          bList = next(tTbl,i) ~= nil
          if not bList then break end --/ Не список?!
        end
      end
    end
    --/< ----------------------------------
    local key,value,sType = nil,nil,nil
    for key,value in pairs(tTbl) do
      if bList then --/ упаковывается список?
        sStr = sStr .. tPackChar.l[2] --/ маркер списка (list)
      else
        sType = type(key)
        if sType == 'number' then
          if key > 9 and key <= 65535 and key == math.floor(key) then --/ 2-х байтовое или более и целочисленное?
            sStr = sStr .. tPackChar.h[2] .. string.format('%X', key) --/ метка хекс-строки
          else
            sStr = sStr .. tPackChar.n[2] .. key
          end
        elseif sType == 'string' then
          sStr = sStr .. tPackChar.s[2] .. key
        else
          abort("Pack_Tbl:UnSupported_KeyType=" .. tostring(sType)
        end
      end
      sType = type(value)
      if sType == 'number' then
        if value > 9 and value <= 65535 and value == math.floor(value) then --/ 2-х байтовое или более и целочисленное?
          sStr = sStr .. tPackChar.h[2] .. string.format('%X', value) --/ метка хекс-строки
        else
          sStr = sStr .. tPackChar.n[2] .. value
        end
      elseif sType == 'string' then
        sStr = sStr .. tPackChar.s[2] .. value
      elseif sType == 'boolean' then
        sStr = sStr .. tPackChar.b[2] .. ((value and "1") or "0")
      elseif sType == 'table' then
        sStr = sStr .. tPackChar.t[2] .. this.Pack_Tbl(value) .. tPackChar.e[2] --/> рекурсивный вызов
      else
        abort("%s:Pack_Tbl:UnSupported_ValueType=" .. tostring(sType)
      end
    end
  end
  return sStr --/>
end
--/ распаковка в таблицу 'tTbl' строки 'sStr' (или части строки от 'at')
function Parse_Str(sStr,at)
  local tTbl,iLen = {},sStr:len()
  local key,value,iByte
  if not at then at = 1 end --/ парсинг с начала строки
  while at < iLen do --/ (суб)строка не закончилась?
    --/ парсинг 1-го символа (суб)строки
    iByte = string.byte(sStr:sub(at,at))--/ численное значение at-го символа в строке
    at = at +1 --/ переход к следующему символу строки
    if     iByte == tPackChar.l[1] then --/ таблица типа 'список'? (table-list)
      key = nil --/ для элементов списка индексы формируются автоматически
    --/ парсинг 'key'
    elseif iByte == tPackChar.n[1] then --/ 'number'?
      key,at = Get_Num(sStr,at)
    elseif iByte == tPackChar.h[1] then --/#+# 'number-hex'?
      key,at = Get_Dec(sStr,at)
    elseif iByte == tPackChar.s[1] then --/ 'string'?
      key,at = Get_Str(sStr,at)
    elseif iByte == tPackChar.e[1] then --/ конец (суб)таблицы? (table-end)
      return tTbl,at --/> субстрока субтаблицы закончилась - выход из функции
    else
      abort("Parse_Str:(%s):UnSupported_TypeKey=" .. tostring(iByte)
    end
    --/ парсинг 'value'
    iByte = string.byte(sStr:sub(at,at))--/ численное значение at-го символа в строке
    at = at +1 --/ переход к следующему символу строки
    if     iByte == tPackChar.b[1] then --/ 'boolean'?
      value,at = Get_Bool(sStr,at)
    elseif iByte == tPackChar.n[1] then --/ 'number'?
      value,at = Get_Num(sStr,at)
    elseif iByte == tPackChar.h[1] then --/#+# 'number-hex'?
      value,at = Get_Dec(sStr,at)
    elseif iByte == tPackChar.s[1] then --/ 'string'?
      value,at = Get_Str(sStr,at)
    elseif iByte == tPackChar.t[1] then --/ 'table'?
      value,at = this.Parse_Str(sStr,at) --/ рекурсивный вызов для 'табличных субстрок'
    else
      abort("Parse_Str:(%s):UnSupported_TypeValue=" .. tostring(iByte)
    end
    if key == nil then --/ элемент списка?
      table.insert(tTbl, value) --/ добавляем в таблицу типа 'список' (table-list)
    else
      tTbl[key] = value
    end
  end
  return tTbl, at --/>
end
--/------------------------------------------------------------------
--/ по-символьный парсер строки 'sStr' от at до первого 'управляющего' символа
function Get_Str(sStr,at) --/< стринг(строка) и начальный индекс в нем
  local iLen = sStr:len() --/ индекс конца стринга(строки)
  for i=at,iLen do
    if string.byte(sStr:sub(i,i)) < 32 then --/ 'управляющий' символ?
      if i == at then --/ 1-ым символом идет управляющий? (пустая строка)
        return "", i --/> zero-string,iNext
      end
      --/ начальная часть (суб)строки и индекс первого упр.символа
      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)
  local sSubStr,iNext = Get_Str(sStr,at)
  local iNum = tonumber(sSubStr)
  if iNum then
    return iNum, iNext --/>
  elseif sSubStr == "" then --/ пустая субстрока?
    return 0, iNext --/> #?# не будем пока прерывать
  else
    abort("Get_Num:SubStr=" .. tostring(sSubStr)
  end
end
--/ перевод части (от 'at') строки 'sStr' в десятичное число
function Get_Dec(sStr,at)
  local sSubStr,iNext = Get_Str(sStr,at)
  local iDec = tonumber(sSubStr, 16) --/ переводим хекс-строку в десятичное число
  if iDec then
    return iDec, iNext --/>
  elseif sSubStr == "" then --/ пустая субстрока?
    return 0, iNext --/> #?# не будем пока прерывать
  else
    abort("Get_Dec: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*)')) == 'true'), iNext --/>
end
--/------------------------------------------------------------------

 

 

Критика, предложения, возражения и вопросы - приветствуются! :)

 

=====================================================

Еще один вариант оптимизации для 'штатных' функций игры:

 

Файл 'xr_logic.script', функции чтения/записи в 'pstor' объектов и функции чтения/записи в нет-пакеты объектов (для сэйвов).

 

Особенности:

1. Дополнительная стабильность при возможных некорректных вызовах функций.

2. Защита от некоторых 'битых' сэйвов. Кол-во записей для не актора ограничено 48-мью переменными (опционально).

3. Оптимизация скорости отдельных операций.

4. Экономичность расходования байтов: Целые числа из диапазона -32768...65535 записываются в 2 байта, вместо прежних 4-х.

5. Совместимость с прежними сэйвами. Обратная совместимость отсутствует.

local tPstorFunc_r = {
  [0] = "r_float",
  [1] = "r_stringZ",
  [2] = "r_bool",
  [3] = "r_u16", --/#+#
  [4] = "r_s16"  --/#+#
}
local tPstorFunc_w = {
  ["number"]  = {0, "w_float"},
  ["string"]  = {1, "w_stringZ"},
  ["boolean"] = {2, "w_bool"},
  ["u16"]     = {3, "w_u16"}, --/#+#
  ["s16"]     = {4, "w_s16"}  --/#+#
}

function pstor_is_registered_type(sType)
  if sType == 'number' or sType == 'string' or sType == 'boolean' then
    return true --/>
  end
  return false --/>
end

--/ запись в storage объекта
function pstor_store(oObj, sVarName, Value)
  local idObj = oObj and oObj:id()
  if idObj and sVarName then --/#fix#
    local tStor = db.storage[idObj]
    if Value == nil then --/ стираем
      if tStor.pstor then
        tStor.pstor[sVarName] = nil
      end
      return --/>
    elseif tPstorFunc_w[type(Value)] then
      if not tStor.pstor then tStor.pstor = {} end
      tStor.pstor[sVarName] = Value
      return --/>
    end
    printf("pstor_store:VarName=[%s],value=[%s]<~Not_Registered_Type=[%s]:<%s>", sVarName, Value, type(Value), "Warning!")
  end
  printf("pstor_store:Obj=[%s],varname(%s)=[%s]:<%s>", oObj and oObj:name(), sVarName, Value, "Warning!")
  abort("pstor_store:Obj=[%s],varname=[%s]:<%s>", oObj and oObj:name(), sVarName, "Error!")
end
--/ чтение из storage объекта
function pstor_retrieve(oObj, sVarName, DefValue)
  local idObj = oObj and oObj:id() --/#fix#
  local tStor = idObj and db.storage[idObj]
  if tStor and tStor.pstor then
    local Value = tStor.pstor[sVarName]
    if Value ~= nil then
      return Value --/>
    end
  end
  return DefValue --/>
end


function save_logic(oObj, packet) --/ не изменяем
  local tStor = db.storage[oObj:id()]
  packet:w_s32( (tStor.activation_time or 0) - time_global() )
  utils.w_CTime(packet, tStor.activation_game_time) --/ GAMETIME added by Stohe.
end

function load_logic(oObj, reader) --/ не изменяем
  local tStor = db.storage[oObj:id()]
  tStor.activation_time = reader:r_s32() + time_global()
  tStor.activation_game_time = utils.r_CTime(reader) --/ GAMETIME added by Stohe.
end

function pstor_save_all(oObj, packet)
  local idObj = oObj:id()
  local tPstor = db.storage[idObj].pstor
  if not tPstor then
    db.storage[idObj].pstor = {}
    packet:w_u32(0) --/< count variables
    return --/>
  elseif next(tPstor) == nil then --/ пустой?
    packet:w_u32(0) --/< count variables
    return --/>
  end
  
  local iCnt = 0
  for k,v in pairs(tPstor) do
    iCnt = iCnt +1
  end
  packet:w_u32(iCnt) --/< count variables
  
  local tFunc,oFunc --/#+#
  for k,v in pairs(tPstor) do
    tFunc = tPstorFunc_w[type(v)]
    if tFunc then
      --/#+# если число ("number") - проверим: достаточно ли 2-х байт?
      if tFunc[1] == 0 and v < 65535 and v > -32768 and v == math.floor(v) then
        --/ проверяем целочисленное число на знак
        if v >= 0 then
          tFunc = tPstorFunc_w["u16"] --/ положительное
        else
          tFunc = tPstorFunc_w["s16"] --/ отрицательное (требуется сохранить и знак)
        end
      end
      packet:w_stringZ(k) --/< name variable
      packet:w_u8(tFunc[1]) --/< type variable
      oFunc = packet[tFunc[2]] --/ write-function
      oFunc(packet,v) --/< value variable
    else
      printf("pstor_save_all:obj=[%s],id=[%s],type=[%s]<~?:k=[%s],v=[%s],:(%s):<%s>", oObj:name(), idObj, type(v), k, v, iCnt, "Error!")
      abort("pstor_save_all:not_registered_type=["..type(v).."]<~encountered")
      return --/>
    end
  end
end

function pstor_load_all(oObj, reader)
  local idObj = oObj:id()
  local tPstor = db.storage[idObj].pstor
  if not tPstor then
    tPstor = {}
    db.storage[idObj].pstor = tPstor
  end
  
  local iCnt = reader:r_u32() --/< count variables
  if iCnt == 0 then
    return --/>
  elseif iCnt > 48 and idObj ~= db.actor:id() then --/#fix# для не актора допустимо не более 48 переменных
    printf("pstor_load_all:Obj=[%s],Cnt=[%s]>32:<%s>", oObj:name(), iCnt, "Warning!")
    iCnt = 32
  end
  
  local sVarName,iType,sFunc,oFunc
  for i=1, iCnt do
    sVarName = reader:r_stringZ() --/< name variable
    iType    = reader:r_u8() --/< type variable
    sFunc = tPstorFunc_r[iType] --/ name read-function
    if sFunc then
      oFunc = reader[sFunc] --/ read-function
      tPstor[sVarName] = oFunc(reader) --/< value variable
    else
      printf("pstor_load_all:Obj=[%s],id=[%s],var(%s)=[%s],type=[%s]<-?:(%s/%s):<%s>", oObj:name(), idObj, sVarName, tPstor[sVarName], iType, i, iCnt, "Error!")
      tPstor[sVarName] = nil
    end
  end
end

=====================================================

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

malandrinus

И согласен с твоими доводами и нет.

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

 

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

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

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

Если же и не уверен в себе и не доверяет стороннему программеру - флагом 'false' запрещает работу по экономии для списков.

 

Согласись, набор правил применения довольно прост и достаточен.

Если можно так выразиться: "Если ты в скриптах 'нуб' - используй по дефолту, не трогая руками." ;-)

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

 

И последний аргумент(ы): Проверено и протестировано на себе во всех режимах - все же чего-то стОит.

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

 

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

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

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

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

Artos

 

Что-то мы говорим на разных языках.

Попробую ещё раз.

Вопрос-1: Как оптимально определить тип таблицы 'список' (list), т.е. типа: {"a","b","c","d"} или {1,5,2,4,3}?

 

По типу построения между этими таблицами нет никакой разницы. На самом деле они же выглядят так :

{[1]='a', [2]='b', [3]='c', [4]='d'}
и
{[1]=1, [2]=5, [3]=2, [4]=4, [5]=3}

Т.е. оператор # определит ВСЕ! поля этих таблиц.

...определение именно списка, а не иного типа массива.

Если добавить в любой из списков поле, не соответствующее продолжению "списка", например {[1]='a', [2]='b', [3]='c', [4]='d', [7]='e'}, либо 'занилить' значение, после чего поле удалиться из списка {[1]='a', [2]='b', [3]=nil, [4]='d'}, то список перестанет быть таковым и станет хэшем на общих основаниях.

Теперь оператор # определит в первом случае из 6-ти фактических полей только 5 (от индекса 1 до индекса 5). Во втором случае только 2 (т.к. поле с индексом 3 исчезло, и следующий индекс после 2 будет индекс 4), вместо фактических 3-х.

!!! Для того, чтобы не нарушать строение таблицы как списка (который работает быстрее чем любой другой тип таблиц) нужно пользоваться специально созданными для этого функциями table.insert и table.remove, а не заниливанием и непонятно каким добавлением.

...и не полный перебор списка на поиск 'дыр' в индексах

Теперь о 'дырах' и собственно моем варианте без перебора.

Имеется 3 типа таблиц :

t1 = {a='p', k=6, [12]='s'} 
t2 = {'a', 'b', k=6}
t3 = {'a', 8, 'c'}

Хотя в таблице t2 и есть элеметы списка, но и она, и таблица t1 не являются списками. Это комбинированые хэш-таблицы.

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

Вот я и пошел таким путем.

if #tаb~=0

Определяю, есть ли в таблице элементы списка (индексов от 1 и до упора).

Если нет, то это уже не список. Ответ - хэш.

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

И теперь проверяя nехt(tаb,#tаB) смотрю, есть ли за этим последним элементом списка ещё поля, характерные для хэша?

Если нет (nil), то фактическая длина таблицы равна количеству полей, что означает одно - таблица является списком.

Ну а если есть ещё поля, то это смешанная таблица, а по сути НЕ список.

 

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

В варианте с заниливанием внутри таблицы ([3]=nil) допустил неточность. Читать 'заниливанием извне', например t[3] = nil

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

Как сделать так, чтоб другие Сталкеры курили?

Там по моему, нужен такой код:

kurit sidya_ 0

?

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

Кивач

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

Если имеешь опыт редактирования конфигов и скриптов - не проще ли поиском отыскать готовые наработки по этой теме и взглянуть как и что там сделано?

 

Gun12

Или я никак не могу пояснить что хотелось бы или ты читаешь мои попытки прояснить по диагонали ...

Попробуем иначе:

1. Обе таблицы: {"a","b","c","d"} И {1,5,2,4,3} - это в контексте вопроса именно списки, но с разным наполнением одним типом значений, и даны как примеры списков.

2. Эти два варианта списков могут иметь и вариации на тему: смешанные типы элементов: строки+числа , числа многоразрядные и не по порядку и т.д. Т.е. вариаций может быть много.

3. Выше твоего поста приведены коды упаковки и распаковки таблиц в строку и обратно. Именно в контексте этих кодов и требуется решение для определения 'список иль нет'.

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

Т.о. требуется определить именно те типы таблиц (выставить флаг 'bList'), для которых подобная 'экономная' упаковка не приведет к потере данных после распаковки.

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

 

P.S. Про 'nехt( tаb, #tаb )' - понял, но т.к. это все же точечная проверка - и не допонял ее цели. 'Точечная' - если в таблице будет после 'списочного' фрагмента идти 'nil' и продолжение с хеш-элементами - то такая проверка, вернет 'nil'? наткнувшись на него после списочного фрагмента и НЕ увидит все остальное. Т.е. это все же частный случай.

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

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

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

Artos, я добавил сигареты и попытался на основе колбасы сделать. Но они упорно отказываются курить (Может не хотят начинать курить? :D:huh: ). Но вылетов нет. Там по моему нужны (для добавления события "курит") фалы скриптов: state_lib, state_mgr_animation, state_mgr_animation_list и xr_kamp. Модель и в стандартном игре есть.

Топик все же не для погадалок ... Так можно гадать до бесконечности.

Если что-то сделал и не получается - поясняешь и/или выкладываешь сделанное - и можно будет искать ошибки или подсказать недоделки.

Если же ничего не сделано, а только похотелка - читаем шапк темы, читаем статьи на вика сайтах по теме, ... и тогда задаем конкретные вопросы о недопонятом и/или ненайденном.

--/ Artos

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

Gun12,

Теперь о 'дырах' и собственно моем варианте без перебора...

...

Вот я и пошел таким путем.

if #tаb~=0

Определяю, есть ли в таблице элементы списка (индексов от 1 и до упора).

Если нет, то это уже не список. Ответ - хэш.

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

И теперь проверяя nехt(tаb,#tаB)

Вообще говоря, перебор там таки есть, хотя и внутри операции размера. Эта операция для получения размера как раз и перебирает все элементы. Где-то я об этом в мануале читал. Хотя это разумеется не скриптовый перебор и не так уж затратно.

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

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

 

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

 

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

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

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

 

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

malandrinus

(хотя и оффтопик, но все же касательно темы)

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

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

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

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

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

 

О хакерском складе мышления:

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

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

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

Плох ли такой склад мышления, а точнее подход при создании модификаций? Хм, если бы все руководствовались чисто программистским подходом - ИМХО, врядли так далеко бы зашло модифицирование игры!

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

 

ИМХО, упрощенно: Программист - большей частью теоретик (практик в написании алгоритмов/кодов), Хакер - общий практик, с программистскими навыками, которые в чем-то более глубоки чем у первого, но в чем-то поверхностны. Для создания модификаций более адаптирован именно второй.

 

 

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

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

Gun12

Что же в итоге?

Такой вариант достаточен для селекции в игре таблиц, для которых не требуется сохранять индексы элементов?:

 local iCnt = #tTbl --/ длина индексированной части таблицы
local bList = iCnt > 0 and iIdx == 1 and not next(tTbl,iCnt) --/ флаг: tTbl является списком (true)

- т.е. проверка: имеются элементы 'списка', 1-й индекс начинается с 1 и за 'списком' отсутствует хеш-элемент

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

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

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

Gun12,

Наверное ты всё-таки прав. Не нужно высовываться

Где я такое сказал?

 

Artos,

мне интересно, что ты будешь делать с этой функцией дальше. Вот ты проверил, что это массив. Но ведь это только говорит об упорядоченности ключей. А что насчёт значений? Ведь в качестве значений может быть что угодно: числа, строки, таблицы, пользовательские объекты. До какой степени ты будешь пытаться автоматизировать обработку?

 

 

 

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

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

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

 

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

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

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

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

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

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

Войти

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

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

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