Перейти к содержимому
ZO125

Программа-сортировщик для транспозера

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

Программа умеет управлять одним транспозером, для перемещения определенных предметов между инвентарями.
Предметы можно перемещать/не перемещать проверяя их unlocalized name и meta.

 

При запуске программы откроется графический интерфейс, в котором отобразится ID компонента транспозера, доступные инвентари, их названия, стороны, количество слотов ...

 

Управление в программе:

Скрытый текст

BACKSPACE - Выйти из программы

INSERT - Добавить новый фильтр

DELETE - Удалить фильтр

SPACE - Сохранить настройки

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

 

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

Виды полей и их описание:

 

Скрытый текст

1) ID - Это поле в правиле-его номер. Его нельзя редактировать. Но если селектор стоит на нем, то можно перемещаться между фильтрами с помощью клавиш со стрелками влево и вправо. На остальных полях это приведет к изменению значения поля.

2) Активен - Поле позволяет включить или отключить правило. Отключение правила полезно например если вы его редактируете и не хотите чтобы оно сработало некорректно пока вы меняете настройки.

3) Вход - Это номер стороны света с которой (с инвентаря, стоящего на которой) мы хотим что-то забрать. Она соответствует нумерации сторон света в minecraft (0 - Нижняя (bottom), 1 - Верхняя (top), 2 - северная (north), 3 - южная (south), 4 - западная (west), 5 - восточная (east))

5) Слот (Входа)- Номер слота из которого мы хотим что-то забрать. Если это поле оставить нулем, то программа будет последовательно сканировать слоты пока не найдет предмет (т.е заберет предмет из любого слота). Вместе с Input side описывают инвентарь-источник.
4) Выход - то, же самое что и Input side, но это сторона, в которую мы хотим попытаться поместить, то, что забрали с Input side.

6) Слот (Выхода) - номер слота, в который мы хотим поместить предмет, взятый из Input slot. Также если оставить нулем программа найдет первый по номеру слот, в который можно поместить данный предмет (т.е любой подходящий слот). Вместе с Output side описывают инвентарь-приемник.

7) Имя предмета - это поле отвечает за название (unlocalized name) предмета который мы хотим перемещать. Если этого имени не задать (оставить пустую строку), то будет перемещен предмет вне зависимости от имени. Чтобы отредактировать название нажмите ENTER с селектором на поле и введите текст. Поддерживаются только латинские буквы и спецсимволы. Нажмите ENTER чтобы завершить ввод.

8) Мета (-1) - в это поле можно ввести значение метаданных предмета, к значению метаданных необходимо прибавить число 1, так-как ввод в это поле нуля значит что метаданные будут игнорироваться (будет перемещен предмет вне зависимости от метаданных)

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

 

 

Алгоритм сортировки:

Программа последовательно проходится по всем фильтрам в порядке их ID и пытается вытащить предмет из инвентаря источника (input side, input slot) и положить его в инвентарь приемник (output side, output slot), попутно проверяя совпадает-ли unlocalized name предмета с фильтром, meta с фильтром, если совпадает, то необходимое количество предмета перекладывается в инвентарь-приемник. Программа переходит к следующему фильтру.

Ссылка на pastebin: https://pastebin.com/YQdYSa77

При корректном выходе из программы (С помощью BACKSPACE) программа сохранит конфигурацию в файл в корне файловой системы /filter.txt. Удаление этого файла сотрет конфигурацию.

Буду благодарен за конструктивную критику по поводу качества кода.

 

Скрины:

Скрытый текст

 

Главное меню:

2020-11-21_19_26_26.png.11795fdde8e93224fea6f1d8ce080e5b.png

Пример настройки фильтра. Этот фильтр будет поддерживать с любого слота сундука сверху в девятом слоте сундука сбоку редстоун в количестве 64 штук.

2020-11-21_19_26_29.png.9400c6291a3cf392310b97447767ff0a.png

 

Содержимое сундука сверху:

2020-10-27_19_48_21.png.a0c9894c2c49d7b659a4d8c952a85bb4.png

Сундук сбоку во время работы программы:

2020-10-27_19_48_34.png.25bccec1d612db5632cb5f2f769515df.png

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

А можно добавить скрины сундука до сортировки и после?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
23 часа назад, hohserg сказал:

А можно добавить скрины сундука до сортировки и после?

Готово.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

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

 

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

В 25.10.2020 в 19:21, ZO125 сказал:

Буду благодарен за конструктивную критику по поводу качества кода.

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

 

Предлагаю сразу прояснить следующие моменты:

 

В 25.10.2020 в 19:21, ZO125 сказал:

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

Что значит "сортировка"? Как она осуществляется? Эта сортировка позволяет красиво разложить предметы внутри инвентарей для удобства визуального восприятия игроком, или же позволяет определённым предметам оказаться в нужном инвентаре? Или и то и другое одновременно? В описании об этом не сказано ни слова.

 

В 25.10.2020 в 19:21, ZO125 сказал:

Если поля slot в фильтре оставить нулем, то будет выбираться первый подходящий слот, иначе выбирает слот по номеру.

Если поле item в фильтре оставить пустым то будут извлечены все предметы.

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

 

В 25.10.2020 в 19:21, ZO125 сказал:

Поле count отвечает за число предметов в целевом инвентаре. Плохо работает, если не указан целевой слот.

Что такое целевой инвентарь? Что такое целевой слот? Каким образом они заданы? Что значит "плохо работает"? Это как? А как работает хорошо, если слот указан? Чем определяется плохость или хорошесть работы?

 

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

 

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

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

@eu_tomat Ознакомился с вашим сообщением, отредактировал тему. Вот пример практического применения подобной программы:
Столик крафта из Tinkers Construct умеет взаимодействовать с ванильным сундуком. Почему-бы не реализовать механизм, который будет подавать частоиспользуемые ресурсы прямо в сундук? Это может упростить процесс крафта. Вообщем идея и схема для примера, но вот.

Скрины:

Скрытый текст

Собственно схема:

2020-11-13_22_28_39.png.6ba8b9f26ab0b81209de4a01d18b5861.png

Содержимое сундука (вид из столика):

2020-11-13_22_28_37.png.0bd8489f1be7c9ab3cb06052b4590fbe.png

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

Собственно файл фильтра:

filter.txt

 

22 часа назад, eu_tomat сказал:

Что значит "сортировка"? Как она осуществляется? Эта сортировка позволяет красиво разложить предметы внутри инвентарей для удобства визуального восприятия игроком, или же позволяет определённым предметам оказаться в нужном инвентаре? Или и то и другое одновременно? В описании об этом не сказано ни слова.

Именно второй вариант. Мы выбираем инвентарь и предметы которые мы хотим в него положить / из него извлечь посредством ввода стороны света и слота, а также unlocalized name самого предмета.

Я также должен сказать что не первый год пишу на lua в майне, но это моя первая "Публичная" программа. Я просто понял что мое умение писать код не будет развиваться, если я его(код) не опубликую.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
20 минут назад, ZO125 сказал:

отредактировал тему

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

25 минут назад, ZO125 сказал:

Именно второй вариант. Мы выбираем инвентарь и предметы которые мы хотим в него положить...

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

 

28 минут назад, ZO125 сказал:

Я просто понял что мое умение писать код не будет развиваться, если я его(код) не опубликую.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Начну, с loadProperities, функции чтения конфигурации. В ней несколько раз встречаются строки вида string.byte(bytes, ...) и string.sub(bytes, ...). Я предлагаю использовать синтаксический сахар, предоставляемый Lua для более компактной записи: bytes:byte(...) и bytes:sub(...). На поведении программы это не отразится, но код станет светлее, что благоприятно скажется на его чтении.

 

Там же встречается такая строчка: (enabled == 1 or true and false). В использованном контексте брать это выражение в скобки не обязательно, а с точки зрения замусоривания кода лишними символами – вредно. А кроме того, результатом выполнения выражения enabled == 1 уже является true или false, поэтому вся эта длинная запись приводит ещё и к бесполезному дублированию вычислений. С тем же успехом для булевой переменной var можно было бы написать длинное

if var then
  return true
else
  return false
end

вместо лаконичного

return var

Такой подход приводит к раздуванию кода на пустом месте. Поэтому, учитывая сказанное выше, строчку сокращаем до enabled == 1.

 

Зато в функции saveProperities эта конструкция облегчит код:

    local enbd = 0
    if filter.enabled then
      enbd = 1
    end

до

    local enbd = filter.enabled and 1 or 0

Да и к слову, зачем нужна переменная enbd, если её значение используется лишь один раз, и можно было сразу писать такой код:

    local enabled     = string.char(filter.enabled and 1 or 0)
    local input_side  = string.char(filter.input_side)
    ...

 

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

    local enabled     = string.char(enbd)
    local input_side  = string.char(filter.input_side)
    local output_side = string.char(filter.output_side)
    local input_slot  = string.char(filter.input_slot)
    local output_slot = string.char(filter.output_slot)
    local name_length = string.char(#filter.name)
    local name = filter.name
    local metadata =    string.char(filter.metadata)
    local count       = string.char(filter.count)
    bytes = bytes .. enabled .. input_side .. output_side .. input_slot .. output_slot .. name_length .. name .. metadata .. count

на более короткий и эффективный вариант:

    bytes = bytes
      .. string.char( enbd, filter.input_side, filter.output_side, filter.input_slot, filter.output_slot, #filter.name )
      .. filter.name
      .. string.char( filter.metadata, filter.count)

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

 

Есть ещё пара строк в этой функции, за которые цепляется глаз:

  for i = 1, #filters, 1 do
    local filter = filters[i]

Их хочется заменить одной строкой:

for i, filter in ipairs(filters) do

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

 

 

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

 

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

 

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

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

@eu_tomat С отзывом ознакомлен. Отредактировал код функций loadProperities и saveProperities. Заменил ссылку на pastebin.
Исправил все, кроме 

19 часов назад, eu_tomat сказал:

Есть ещё пара строк в этой функции, за которые цепляется глаз:


  for i = 1, #filters, 1 do
    local filter = filters[i]

Их хочется заменить одной строкой:


for i, filter in ipairs(filters) do

Тут я все-таки настою чтобы остался именно такой формат цикла. Мне не очень нравится как в lua реализованы циклы с итератором... Также я считаю быстродействие кода несколько важнее красоты...
Кстати вот тут наверное следует задать следующий вопрос: Хорошо-ли инициализировать новую функцию внутри старой, которая может вызываться несколько раз, например (код):

function generator(text)
  return function()
    	print(text)
    end
end

local f1 = generator("Some text 1...")
local f2 = generator("Some text 2...")

f1()
f2()

Не загадит-ли вложенная функция кучу ОЗУ, если я создам их несколько? Не лучше-ли создать "обьект" с полем text и один метод, вызывая его через ":"? Долго-ли занимает процесс инициализации функции, т.е имеет-ли вообще смысл где-то инициализировать одну функцию внутри другой, или лучше сразу инициализировать все что надо, передавая данные через аргументы или "обьектом"?

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
26 минут назад, ZO125 сказал:

я считаю быстродействие кода несколько важнее красоты...

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

 

23 минуты назад, ZO125 сказал:

Хорошо-ли инициализировать новую функцию внутри старой, которая может вызываться несколько раз

Это зависит от того, что именно для нас хорошо. С точки зрения быстродействия лучше вообще не создавать и не вызывать функции. И локальные переменные внутри часто вызываемых функций тоже лучше не создавать. Но читать и дорабатывать такой код будет не просто. Хороший код всегда является результатом компромисса между быстродействием, потреблением памяти и простотой обслуживания кода. Баланс определяется условиями задачи. Поэтому говорить о качестве кода в отрыве от условий задачи бессмысленно.

 

37 минут назад, ZO125 сказал:

Долго-ли занимает процесс инициализации функции, т.е имеет-ли вообще смысл где-то инициализировать одну функцию внутри другой?

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

 

51 минуту назад, ZO125 сказал:

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Теперь я предлагаю разобрать таблицу inventories и код, работающий с ней. Сейчас эта таблица строится при запуске программы таким образом:

local function initInventories()
  for i = 0, 5, 1 do
    local name = transposer.getInventoryName(i)
    local size = transposer.getInventorySize(i)
    if name then
      table.insert(inventories, newInventory(i, name, size))
    end
  end
end

Здесь я бы предпочёл избавился от вызова функции newInventory. Хотя быстродействие этого участка кода и не критично, но оно немного увеличится. А главное, упростится обслуживание кода спустя месяц-другой: не потребуется искать функцию newInventory, чтобы вспомнить детали её работы. Необходимые детали окажутся в месте непосредственного их использования:

      table.insert(inventories, {
        side = side,
        name = name,
        size = size,
        stacks = {}
      })

 

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

local function initInventories()
  for side = 0, 5 do
    local name = transposer.getInventoryName(side)
    if name then
      inventories[side] = {
        name = name,
        size = transposer.getInventorySize(side),
        stacks = {}
      })
    end
  end
end

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

 

В функции drawInfo текущий код

  for i = 1, #inventories do
    local inventory = inventories[i]
    gpu.set(1, i + 9, inventory.side .. " : " .. inventory.size .. " : " .. inventory.name)
  end

будет немного усложнён и замедлен:

  local i = 0
  for side = 0, 5 do
    if inventories[side] then
      i = i+1
      gpu.set(1, i + 9, side .. " : " .. inventories[side].size .. " : " .. inventories[side].name)
    end
  end

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

 

В функции getStacks текущий код

  for i = 1, #inventories do
    local inventory = inventories[i]
    inventory.stacks = transposer.getAllStacks(inventory.side)
  end

также будет усложнён и замедлен:

  for side = 0, 5 do
    if inventories[side] then
      inventories[i].stacks = transposer.getAllStacks(inventory.side)
    end
  end

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

 

В функции getInventoryBySide текущий код

  for i = 1, #inventories, 1 do
    local inventory = inventories[i]
    if inventory.side == side then
      return inventory
    end
  end

будет сильно упрощён и ускорен:

  return inventory[side]

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

 

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

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Изменил формат таблицы и места взаимодействия программы с ней. Обновил код на pastebin.

[21.11.20]: Добавлена простейшая поддержка Unicode, интерфейс переведен на Русский язык.

В меню в качестве границ используются символы Unicode.

Улучшен механизм ввода текста. Строка теперь выделяется нормально, каретку можно перемещать, добавлен мигающий курсор и строка теперь принимает Unicode-символы (Хотя зачем это нужно в англоязычном unlocalized-name непонятно...)

[05.12.20]: Добавлена возможность выбора диапазона слотов.

Исправлены некоторые (А их еше и осталось много) ошибки.

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Присоединяйтесь к обсуждению

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

Гость
Ответить в тему...

×   Вы вставили отформатированное содержимое.   Удалить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отобразить как ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

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


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