Fingercomp
-
Публикации
1 629 -
Зарегистрирован
-
Посещение
-
Победитель дней
283
Сообщения, опубликованные пользователем Fingercomp
-
-
Внутри песочницы coroutine.yield переопределён:
yield = function(...) -- custom yield part for bubbling sysyields return coroutine.yield(nil, ...) end
Также переопределён и coroutine.resume.
resume = function(co, ...) -- custom resume part for bubbling sysyields checkArg(1, co, "thread") local args = table.pack(...) while true do -- for consecutive sysyields debug.sethook(co, checkDeadline, "", hookInterval) local result = table.pack( coroutine.resume(co, table.unpack(args, 1, args.n))) debug.sethook(co) -- avoid gc issues checkDeadline() if result[1] then -- success: (true, sysval?, ...?) if coroutine.status(co) == "dead" then -- return: (true, ...) return true, table.unpack(result, 2, result.n) elseif result[2] ~= nil then -- yield: (true, sysval) args = table.pack(coroutine.yield(result[2])) else -- yield: (true, nil, ...) return true, table.unpack(result, 3, result.n) end else -- error: result = (false, string) return false, result[2] end end end
Работает это так:
- Если корутина вернула true, nil, ... — отдать true, ... (поэтому внутри песочницы разница между обычными и переопределёнными функциями не видна).
- Если корутина йелднулась без нила в начале, то это "системный вызов". Потому что доступ к непереопределённой функции coroutine.yield есть только внутри machine.lua. В таком случае йелдится и текущая корутина вверх по цепочке.
-
EEPROM запускается в корутине. Если корутина эта йелдится, случается следующее:
- yield(false) — это выключить компьютер;
- yield(true) — ребутнуть его;
- yield(n), где type(n) == "number", — спать n секунд;
- yield(f), где type(f) == "function", — это вызвать непрямую функцию.
- yield с любым другим аргументом — спать до скончания века.
- Дополнительно обрабатывается умирание корутины: return посреди еепрома — это error("computer halted", 0); другие ошибки прокидываются.
А теперь смотрим на наш вызов.
- В EEPROM вызвана coroutine.yield(0).
- Эта функция переопределена, чтобы вызвать нативный coroutine.yield(nil, 0).
- Корутина тогда йелдится и отдаёт nil, 0.
- В списке выше это пункт 3.5. Компьютер уйдёт в вечный сон.
Ну, конечно, я преувеличиваю. Компьютер спит до момента получения сигнала и, получив, продолжит работу.
Но вообще, незачем звать coroutine.yield напрямую. Есть computer.pullSignal, computer.shutdown, component.invoke(addr, nonDirectMethod, ...).
-
5
-
1
-
1
-
11 минуту назад, ov3rwrite сказал:Ой-ой-ой,вы не видели сам код майноси на гитхабе)Я в году так 2018 смотрел,там были переменные по типу pizda, pizdaLength
Как раз об этом я и пишу:
23 часа назад, Fingercomp сказал:переименования всех переменных с непристойными выражениями
Код видел, приходилось.
-
registerListeners() repeat until event.pull("interrupted") disableListeners()
Этот кусок кода кладём в конец программки. Вуаля.
Если этот кусок кода ровно от начала и прямо до конца обернуть в поток, а затем сделать ему :join(), то disableListeners даже не потребуется. Всё само выключится при завершении потока. Жутко удобно, словами не передать.
Программа закрываться будет по ^C. Если не нравится, репит заменить на это:
while true do os.sleep(math.huge) end
-
1
-
-
Наконец-то мы смогли одолеть эту проказу демоническую! Сто лет не мог поставить: чуть что — ось меня посылала. Хоть теперь покойнее будет.
Предлагаю не останавливаться на этом. Мир ждёт:
- переписанной без матов истории коммитов;
- переименования всех переменных с непристойными выражениями;
- замены значков об ошибке рисунком бодрого розового единорога;
- редакции вики в дореволюционной орфографии для истинных джентльменов;
- перевода MineOS с Lua на китайский вариант Scratch;
- кастомизации количества пробелов в отступах (и заменой на них табов);
- патча OpenComputers, чтобы мониторы работали в кодировке KOI-8.
-
1
-
4
-
-
Чтобы решить проблему, надо оформить код.
entity = require("component").os_entdetector alarm = require("component").os_alarm door = require("component").os_rolldoorcontroller local e = entity.scanPlayers(3) local whitelist = {{}} alarm.setAlarm("klaxon2") alarm.setRange(15) whitelist[1] = "Arsean" whitelist[2] = "sherlock2202" function open() door.open() os.sleep(5) door.close() detect() end function detect() if type(q[1]) == type(nil) then print("Нету") os.sleep(2) q = e detect() else print("Есть") q = e check() end end function check() x = e[1].name if x == "Arsean" then open() else alarm.activate() os.sleep(5) alarm.deactivate() end detect() end q = e detect()
Тяк, сначала починим обозначенную неполадку. Функция detect, согласно названию, должна при каждом вызове пересканировать игроков. Однако сканирование игроков это происходит в самом начале программы (4 строка) и один раз. Гм. Ещё раз, при каждом вызове функции надо выполнять определённый код. При каждом вызове... а почему бы не поместить сканирование внутрь функции? Разве это не тем и занимается?
entity = require("component").os_entdetector alarm = require("component").os_alarm door = require("component").os_rolldoorcontroller local e = entity.scanPlayers(3) local whitelist = {{}} alarm.setAlarm("klaxon2") alarm.setRange(15) whitelist[1] = "Arsean" whitelist[2] = "sherlock2202" function open() door.open() os.sleep(5) door.close() detect() end function detect() e = entity.scanPlayers(3) q = e if type(q[1]) == type(nil) then print("Нету") os.sleep(2) q = e detect() else print("Есть") q = e check() end end function check() x = e[1].name if x == "Arsean" then open() else alarm.activate() os.sleep(5) alarm.deactivate() end detect() end q = e detect()
Ну да, почти. Пришлось только ещё q = e перетащить туда же. Но теперь должно работать.
...
Должно ли?
Исправляем другие проблемы, которые здесь заютились.
Проблема 1. Есть переменные q и e, которые во всех местах устанавливаются одинаковыми, при этом непонятно, чем они отличаются.
Объединим их в одну переменную. Заодно назовём её по-человечески, а не для машины, то есть понятно:
entity = require("component").os_entdetector alarm = require("component").os_alarm door = require("component").os_rolldoorcontroller local scan = entity.scanPlayers(3) local whitelist = {{}} alarm.setAlarm("klaxon2") alarm.setRange(15) whitelist[1] = "Arsean" whitelist[2] = "sherlock2202" function open() door.open() os.sleep(5) door.close() detect() end function detect() scan = entity.scanPlayers(3) if type(scan[1]) == type(nil) then print("Нету") os.sleep(2) detect() else print("Есть") check() end end function check() x = scan[1].name if x == "Arsean" then open() else alarm.activate() os.sleep(5) alarm.deactivate() end detect() end detect()
Проблема 2. Куча переменных не локальны.
Допустим такую ситуацию: в коде переименовали переменную, а в одном месте имя сменить забыли. Если эта переменная была локальной, то программа пропишет ошибку, стопнется, и проблему легко локализовать и починить. Если глобальной, то она никуда не денется до рестарта компьютера. Каково этого — поменял переменную, а в одном месте как будто одно и то же значение застряло? Чинить такие вещи — боль.
Как правило, все переменные делать надо локальными. У глобальных есть несколько применений, но без них обходиться можно всегда. И лучше это делать.
Чиним, короче:
local entity = require("component").os_entdetector local alarm = require("component").os_alarm local door = require("component").os_rolldoorcontroller local scan = entity.scanPlayers(3) local whitelist = {{}} alarm.setAlarm("klaxon2") alarm.setRange(15) whitelist[1] = "Arsean" whitelist[2] = "sherlock2202" local function open() door.open() os.sleep(5) door.close() detect() end local function detect() scan = entity.scanPlayers(3) if type(scan[1]) == type(nil) then print("Нету") os.sleep(2) detect() else print("Есть") check() end end local function check() local x = scan[1].name if x == "Arsean" then open() else alarm.activate() os.sleep(5) alarm.deactivate() end detect() end detect()
Проблема 3. Переполнение стэка из-за рекурсии.
Во всех функциях в коде вызывается detect. В том числе внутри самой detect — когда функция саму себя зовёт, это зовут рекурсией.
У меня есть подозрение, то функциональное программирование непрограммирующему обывателю концептуально проще, чем императивное. Вот взять этот код.
Откуда тут рекурсия? Думаю, автор рассуждал так: после проверки на пробежчиков мы хотим снова отсканировать игроков. То есть перейти в detect. А как перейти в функцию? Вызовом же.
Проблема в том, что Lua — язык императивный по большей части. А ещё оптимизаций делает мало. Поэтому не получится в луа сколь угодно много раз вызывать функции рекурсивно. Упрёмся в лимит и словим ошибку.
Один способ починить — использовать хвостовую рекурсию, которую Lua оптимизировать умеет. Мы вместо этого воспользуемся циклами.
local entity = require("component").os_entdetector local alarm = require("component").os_alarm local door = require("component").os_rolldoorcontroller local scan = entity.scanPlayers(3) local whitelist = {{}} alarm.setAlarm("klaxon2") alarm.setRange(15) whitelist[1] = "Arsean" whitelist[2] = "sherlock2202" local function open() door.open() os.sleep(5) door.close() end local function detect() while true do scan = entity.scanPlayers(3) if type(scan[1]) == type(nil) then print("Нету") os.sleep(2) else print("Есть") check() end end end local function check() local x = scan[1].name if x == "Arsean" then open() else alarm.activate() os.sleep(5) alarm.deactivate() end end detect()
Если в первой функции вызвать вторую, то Луа будет выполнять код второй функции. Когды мы дойдём до конца кода её, Луа вернётся в первую функцию. То есть Lua не забывает, кто вызвал любую функцию. Таким образом, open вернётся в check, а check — в detect.
В detect появился while true do ... end. Эта конструкция называется бесконечным циклом. Цикл — повторение одного и того же кода. Бесконечный — нет условия, при котором программа покинет цикл. (Формально есть ^[C, то есть цикл покинуть можно, но оставим это в стороне.)
Проблема 4. Нелокальность переменных.
Это не повторение проблемы #2, хотя на определённом уровне абстракции
всё начинает казаться молотками и гвоздямиони очень похожи.Переменная scan общая для всех функций. По сути, это тоже "глобальная" переменная, только живёт она не дольше, чем программа (статическая она, наверное, лучше сказать). Если нет веских причин, лучше всё-таки такие переменные передавать явно. То есть аргументом функций.
local entity = require("component").os_entdetector local alarm = require("component").os_alarm local door = require("component").os_rolldoorcontroller local whitelist = {{}} alarm.setAlarm("klaxon2") alarm.setRange(15) whitelist[1] = "Arsean" whitelist[2] = "sherlock2202" local function open() door.open() os.sleep(5) door.close() end local function detect() while true do local scan = entity.scanPlayers(3) if type(scan[1]) == type(nil) then print("Нету") os.sleep(2) else print("Есть") check(scan) end end end local function check(scan) local x = scan[1].name if x == "Arsean" then open() else alarm.activate() os.sleep(5) alarm.deactivate() end end detect()
Теперь scan определяется в detect и передаётся в check аргументом.
Проблема 5. Программа страшно боится одиночества.
Предлагаю взглянуть на эту строку пристально, можно без микроскопа:
local x = scan[1].name
Я утверждаю, что тут ошибка. Разберём строку.
- scan — это таблица с игроками. Она хранит внутри себя ещё таблицы. В последних инфа о конкретном игроке.
- scan[1] — это тогда таблица с инфой о первом игроке. А если вокруг радара пустыня, случайных прохожих нет, а последний местный подох под палящим пустынным солнцем где-то в лаве? scan будет пустой таблицей, и scan[1] будет nil.
- scan[1].name — эта штука безусловно полагает, что scan[1] можно индексировать, то есть это таблица (или что-то вроде неё). Нет, не scan — то, что это таблица, мы уже знаем безусловно. scan[1] — первый элемент таблицы scan. Выше мы определили, что этот элемент — или ещё одна таблица, или nil.
Таблицу индексировать можно. Если проиндексировать nil, программа завершится с ошибкой. Напомню, nil у нас бывает только тогда, когда игроков вокруг нет. Следовательно, на необитаемом острове программа упадёт.
Чтобы починить, подумаем, что надо делать на этом необитаемом острове. Визжать сиреной? Думаю, вряд ли. Лучше просто промолчать и ничего не делать. Добавим проверку.
local entity = require("component").os_entdetector local alarm = require("component").os_alarm local door = require("component").os_rolldoorcontroller local whitelist = {{}} alarm.setAlarm("klaxon2") alarm.setRange(15) whitelist[1] = "Arsean" whitelist[2] = "sherlock2202" local function open() door.open() os.sleep(5) door.close() end local function detect() while true do local scan = entity.scanPlayers(3) if type(scan[1]) == type(nil) then print("Нету") os.sleep(2) else print("Есть") check(scan) end end end local function check(scan) if scan[1] then local name = scan[1].name if name == "Arsean" then open() else alarm.activate() os.sleep(5) alarm.deactivate() end end end detect()
Не удержался, переменовал x и name, чтобы не потеряться.
Проблема 6...
Пожалуй, всё. Нет, код ещё можно улучшать и улучшать. Например, белый список не используется и задаётся странно. Но так и я не автор программы.
В этом посте я попытался описать только "сложные" проблемы, то есть для определениях которых нужен какой-нибудь опыт программирования.
Проблема 3, например, означает, что программа крашилась бы через час после включения. Стабильно. Со странной ошибкой.
Проблему 5 вообще сложно было бы дебажить, так как игрока рядом с компьютером быть не должно, иначе и проблема не проявится.
-
5
-
1
-
1
-
5 минут назад, Totoro сказал:Вроде существует.
Ну-ну. Твой sbt не пакует пустые директории. /home на дискете с опеносью нет. При установке она тоже не появлятся, соответственно.
Только что, IS2511 сказал:У меня при создании компа почему-то без нее система и до, и после установки
После установки пропиши поэтому mkdir /home.
-
1
-
-
Так как есть планы в самом биосе с интернетов грузиться, одной команды с wget мало. Я три дня назад как раз написал небольшой пост про то, как правильно использовать интернет-плату.
Рекомендую ознакомиться с секцией номер 3 в записи. Скачивать с интернетов хоть что-либо — задача нетривиальная, занимает куда больше, чем 1 строчку.
-
1
-
-
Да, верно. Я не знаю ни одной программы, которая бы брала адрес GPU из аргументов. Лучше подменить component.getPrimary.
-
1
-
-
Да, конечно, почему бы и нет. Сложность в том, что component.componentName возвращает прокси только одного компонента, а нам надо и других тоже получить. Здесь есть 2 варианта действий.
1. component.invoke
Эта функция первым аргументом принимает адрес компонента, а вторым — имя метода. Остальные параметры — это аргументы к этому методу. Например, если компонент по адресу "12345678-1234-1234-1234-123456789012" — видеокарточка, поменять разрешение у неё можно вот так:
local com = require("component") local address = "12345678-1234-1234-1234-123456789012" com.invoke(address, "setResolution", 80, 25)
В одиночестве функция выглядит страшно, если сравнивать с проксями. Обычно её используют, итерируя компоненты с помощью component.list, потому что итератор этот выдаёт адрес очередного компонента:
local com = require("component") for addr in com.list("gpu", true) do local w, h = com.invoke(addr, "getResolution") com.invoke(addr, "fill", 1, 1, w, h, " ") end
Я предпочитаю использовать этот способ, когда надо в цикле проходиться по всем компонентам и вызывать у них пару-тройку методов.
local com = require("component") local event = require("event") local function hsv2rgb(h, s, v) local function f(n) local k = (n + h / 60) % 6 return v - v * s * math.max(0, math.min(k, 4 - k, 1)) end local r = math.floor(f(5) * 0x1f + 0.5) local g = math.floor(f(3) * 0x1f + 0.5) local b = math.floor(f(1) * 0x1f + 0.5) return (r << 10) | (g << 5) | b end repeat for addr in com.list("colorful_lamp", true) do local color = hsv2rgb(math.random(0, 360), math.random(.85, 1), math.random(.85, 1)) com.invoke(addr, "setLampColor", color) end until event.pull(0.1, "interrupted")
Здесь у компонента метод вызывается лишь один раз, поэтому проще использовать component.invoke. В противном случае лучше делать прокси.
2. component.proxy
Если список компонентов, с которыми работает программа, более-менее статичен, удобнее использовать component.proxy. Это функция, которая возвращает прокси компонента по данному адресу. С проксями мы уже знакомы: когда делаем в коде component.componentName, на самом деле вызывается component.proxy(component.getPrimary("componentName")).
Когда компонентов несколько, обычный шаблон — это один раз напихать проксей в таблицу и использовать уже её.
local com = require("component") local event = require("event") local gpus = {} for addr in com.list("gpu", true) do table.insert(gpus, com.proxy(addr)) end assert(#gpus >= 4, "4 gpus required") gpu[1].set(1, 1, "first gpu") gpu[2].set(2, 2, "second gpu") gpu[3].set(3, 3, "third gpu") gpu[4].set(4, 4, "fourth gpu")
Важно, что после заполнения таблицы компоненты эти отключаться не должны. В противном случае нужно ставить листнеры на component_added, component_removed.
Прокси также можно использовать в цикле component.list, как в первом способе, чтобы упростить жизнь, если внутри цикла приходится трогать методы компонента по нескольку раз. Вот программка, которая чистит экран и принтит число почищенных символов.
local com = require("component") local event = require("event") for addr in com.list("gpu", true) do local gpu = com.proxy(addr) local litChars = 0 local w, h = gpu.getResolution() local oldBg = gpu.getBackground() gpu.setBackground(0x000000) for x = 1, w, 1 do for y = 1, h, 1 do local char, _fg, bg = gpu.get(x, y) if char ~= " " or bg ~= 0x000000 then litChars = litChars + 1 end gpu.set(x, y, " ") end end gpu.set(1, 1, ("%d lit characters"):format(litChars)) gpu.setBackground(oldBg) end
Как видно, я активно использую кучу методов гпу. Вместо того, чтобы каждый раз печатать component.invoke, я один раз взял прокси, а дальше работаю с ним.
-
9
-
-
Не надо костылять. io.read — это первая станция для ввода данных в прогу. Вторая станция — term.read, там есть пара параметров. Если надо что-то кастомное, как здесь, — пили свой ввод и накладывай ограничения сам.
-
1 час назад, ProgramCrafter сказал:@BrightYC новый эмулятор почему-то медленнее: примерно в 2 раза меньше вычислений в секунду (600К вместо миллиона), дольше рендер случайных символов. Это ограничение сервера или какой-то баг?
Проблема-то не эмуляторе, а в том, что запущен он на железке почти без памяти и с процессором, о котором лучше не говорить.
-
4
-
-
@uraabk можно переключить процессор на Lua 5.2 (взять его в руку и шифт-пкм). Можно прописать в шелле lua и дальше это: _G.bit32 = bit32
-
Оцелот хорош тем, что его мозги — это реорганизованный код из OpenComputers, поэтому вся логика работы компонентов сохранена и может быть легко обновлена. GPU также работает с теми же характеристиками, что и в моде. Задержки в отображении — это проблема исключительно онлайн-версии, которую Тотора год никак не может доработать. В незаконченной десктоп-версии проблем с GPU не наблюдалось.
@Totoro так что давай доделывай оцелота. Нужен новый рендер и воркспайсы.
-
3
-
-
12 часа назад, MrAbad сказал:потому что, это не ООП, а в чистом виде замыкание и функциональное программирование
ООП — это парадигма. Программа манипулирует объектами, которые хранят состояние (какие-либо данные) и могут обрабатывать сообщения. Это ещё называется вызовом методов. В приведённом коде у нас есть объект, хранящий состояние (a = 3, b = 14) и обрабатывающий сообщения printAandB, GET. По всем признакам это чистое ООП.
ООП — это не наследование, полиморфизм и инкапсуляция, и на этих трёх вещах ООП не покоится. Это просто удобные фичи, которые часто встречаются.
-
1
-
2
-
-
Я недавно выложил IRC-либу, которую я делал, чтобы собирать IRC-мост. Теперь я собрал и мост.
Установка
- Соберите компьютер с интернет-платой, кучей памяти (на всякий случай), админ-чатбоксом из OpenTechnology и дебаг-картой (через неё онлайн получает прога).
- Поставьте на него OpenOS.
-
Пропишите следующие команды:
mkdir -p /home/bin wget https://gist.githubusercontent.com/Fingercomp/df483bc2cefa13e0422d656ae82495ac/raw/c8617e01b7baa0e47936300fd9e783afa36601cb/irc-bridge.lua /home/bin/irc-bridge.lua
-
Скачайте и установите IRC-либу. Например, так:
mkdir -p /home/lib/irc /home/lib/irc/client /home/lib/irc/event /home/lib/irc/protocol cd /home/lib/irc set ADDR=https://gitlab.com/cc-ru/irc-oc/-/raw/v1.1.0/irc wget $ADDR/init.lua init.lua wget $ADDR/enum.lua enum.lua wget $ADDR/state.lua state.lua wget $ADDR/throttlingScheduler.lua throttlingScheduler.lua wget $ADDR/client/init.lua client/init.lua wget $ADDR/client/handlers.lua client/handlers.lua wget $ADDR/event/init.lua event/init.lua wget $ADDR/event/bus.lua event/bus.lua wget $ADDR/protocol/init.lua protocol/init.lua wget $ADDR/protocol/isupport.lua protocol/isupport.lua wget $ADDR/protocol/capabilities.lua protocol/capabilities.lua wget $ADDR/protocol/splitter.lua protocol/splitter.lua
-
Запустите мост, чтобы он создал конфиг-файл:
irc-bridge
-
Откройте файл /etc/conversationalist.cfg. Там будет сериализованная Lua-таблица (не самый лучший формат конфига, согласен). Найдите и поменяйте следующие настройки:
- channel — канал, к которому подключаться
- nickname — ник бота в ирке
- account — имя аккаунта бота в ирке (можно в nil поставить, если нет)
- accountPassword — пароль от акка (также в nil поставить, если нет)
- gameAdmins или ircAdmins — в таблицу впишите себя, чтобы можно было конфигать
Всё. Мост поставлен.
Команды
Мост воспринимает команды. Чтобы выполнить команду, например pm on:
- Пропишите в игре: #IRC: pm on
- Пропишите в ирке: /notice @<имя канала> pm on (например, /notice @#cc.ru-server1 pm on).
Список команд:
- online — показать онлайн на другом конце моста.
- pm on — разрешить с другого конца моста слать вам ЛС.
- pm off — запретить слать вам ЛС.
- pm — показать, могут ли вам послать ЛС (в ирке включено по умолчанию, а в игре выключено и надо включать самому).
- msg <имя> <сообщение>: отправить ЛС юзеру на другом конце моста.
- pm ignore list — показать список игнорируемых юзеров.
- pm ignore add <имя> — добавить кого-то в этот список.
- pm ignore del <имя> — вытащить кого-то из него.
Админы могут выполнять ещё такие команды:
- irc admin list — показать список админов в ирке.
- irc admin add <имя> — добавить кого-то в этот список.
- irc admin del <имя> — убрать кого-то из него.
- mc admin list, mc admin add <имя> и mc admin del <имя> — аналогично, но работает со списком админов в игре.
- irc whitelist list — показать список юзеров, которые могут слать сообщения в игру.
- irc whiltelist add <имя> — добавить кого-то в список.
- irc whiltelist del <имя> — убрать кого-то из него.
- mc blacklist list, mc blacklist add <имя>, mc blacklist del <имя> — аналогично, но работает со списоком тех, чьи сообщения не будут слаться в ирку.
- irc alias set <имя> <алиас> — установить алиас юзеру. Когда он будет писать сообщения в игру, его имя будет заменено на алиас.
- irc alias get <имя> — показать алиас для юзера.
- debug on — включить режим дебага. Мост будет писать весь трафик с IRC на экран. Полезно, чтобы узнать, почему тупит мост. Пароли будут показаны плейнтекстом, поэтому лучше оставить выключенным, хотя бы во время подключения.
- debug off — выключить этот режим.
- debug — показать, включён ли дебаг.
Как это выглядит
На мониторе будет рисоваться вот такое:
Как можно догадаться, чтобы мост остановить, нужно нажать Ctrl-C.
Ссылки
Код на гисте: https://gist.github.com/Fingercomp/df483bc2cefa13e0422d656ae82495ac/
-
6
-
Допилил версию 1.1.0. Теперь либа умеет трекать юзеров на канале, их префикс, аккаунты, ники, иные данные о них, режимы каналов. Досье целое собирает.
Скачать можно через hpm. Только он не работает. Поэтому альтернативный вариант: тык.tar. Распаковывать можно программой tar (тырить из oppm).
Документация для 1.1.0 лежит здесь.
P. S. Совсем забыл. Ещё поддержку capabilities сделал. Если кто-то вообще понимает, что это такое.
-
5
-
1
-
-
Предисловие
Я думал на новый сервер запилить прогу — мост между чатом сервера и IRC. У меня уже были такие программки: я насчитал минимум 6 различных версий мостов — каждая была немного переделанным клиентом IRC, который на дискете встроенной есть. Понять, в чём разница, даже с вимдиффом было сложно. Потому я плюнул и решил запилить полноценную ирколибу с красивой апишкой.
Как это выглядит
Вот полный код бота — моста.
Скрытый текстlocal event = require("event") local thread = require("thread") local irc = require("irc") -- ① local events = irc.events local priority = events.priority local com = require("component") local chatbox = com.admin_chatbox local gpu = com.gpu local channel = assert(os.getenv("CONVERSATIONALIST_CHANNEL"), "channel not set") local MC_FORMAT = "§3[§lIRC§3] §7%s§8: §i%s" local IRC_FORMAT = "\x0315%s\x0f: %s" local IRC_JOIN_FORMAT = "\x0315* \x0309%s \x0315has joined the server." local IRC_QUIT_FORMAT = "\x0315* \x0305%s \x0315has left the server." local fg local function log(color, text) if fg ~= color then gpu.setForeground(color) fg = color end print(text) end local function sanitizeMessage(message) return message:gsub("§.?", ""):gsub("%c", "") end local client = irc.builder() -- ② :connection { -- ③ host = "irc.esper.net:6667", throttling = { -- ④ maxDelay = 2, maxThroughput = 5, }, } :auth { -- ⑤ username = "conversationalist", nickname = "s1-conversationalist", realname = "IRC-MC bridge relay", } :account { -- ⑥ nickname = "s1-conversationalist", password = assert(os.getenv("CONVERSATIONALIST_PASSWORD"), "no pass set"), } :bot { -- ⑦ channels = {channel}, } :execution { -- ⑧ threaded = true, reconnect = true, catchErrors = true, } :subscribe(events.irc.message, priority.normal, function(self, client, evt) -- ⑨ if evt.target ~= channel then return end local message = sanitizeMessage(evt.message) log(0xffdb80, ("IRC → %s: %s"):format(evt.source.nickname, message)) event.push("chat::message", evt.source.nickname, message) end) :subscribe(events.client.connected, priority.normal, -- ⑩ function(self, client, evt) log(0x00ff80, "* Connected") end) :subscribe(events.client.disconnected, priority.normal, function(self, client, evt) log(0xff0000, "* Disconnected") end) :subscribe(events.client.registered, priority.normal, function(self, client, evt) log(0x99ff80, "* Registered as " .. client.currentNickname) end) :subscribe(events.client.authenticated, priority.normal, function(self, client, evt) log(0x99ff80, "* Authenticated") end) :subscribe(events.client.error, priority.top, function(self, client, evt) log(0xff0000, ("Caught error: %s"):format(evt.traceback)) end) :build() -- ⑪ local chatThread = thread.create(function() -- ⑫ event.listen("chat::message", function(evt, nickname, message) chatbox.say(MC_FORMAT:format(nickname, message)) end) event.listen("chat_message", function(evt, addr, dim, x, y, z, dist, nickname, msg) log(0x66dbff, (" MC → %s: %s"):format(nickname, msg)) client:msg(channel, IRC_FORMAT:format(nickname, msg)) end) event.listen("player_join", function(evt, addr, dim, x, y, z, dist, nickname) log(0x66dbff, (" MC → %s has joined the server"):format(nickname)) client:msg(channel, IRC_JOIN_FORMAT:format(nickname)) end) event.listen("player_quit", function(evt, addr, dim, x, y, z, dist, nickname) log(0x66dbff, (" MC → %s has left the server"):format(nickname)) client:msg(channel, IRC_QUIT_FORMAT:format(nickname)) end) log(0x0092ff, "* Press ^C to quit...") repeat local evt = event.pull("interrupted") until evt end) client:run() -- ⑬ thread.waitForAny({client.thread, chatThread}) log(0xff4000, "* Quitting") client:stop("Quitting.") -- ⑭ gpu.setForeground(0xffffff) os.exit() -- ⑮
Сто двадцать шесть строчек. Прокомментирую некоторые из них.
- ① Подключаем либу и для укорачивания имён ещё вытаскиваем events, в которых хранятся все ивенты и priority.
- ② Создаем клиент с помощью билдера.
- ③ Через :connection задаём настройки соединения. Самое важное — адрес иркосервера. Порт обязателен.
- ④ Ирколиба знает меру в флуде. Опасаться, что бота выкосит флуд-фильтром, можно гораздо меньше. Это опционально, конечно.
- ⑤ Задаём ник бота, юзернейм и реалнейм. Юзернейм виден в хосте (nickname!username@domain.name), а реалнейм пишется в /whois.
- ⑥ Ирколиба умеет авторизовываться на сервере. Тоже опционально.
- ⑦ Эта группа выделена для ботоводческих настроек. Но пока там единственная опция — в какие каналы автоматически заходить.
-
⑧ Здесь задаются настройки исполнения.
- Опция threaded, по дефолту включённая, запустит бота в отдельном треде.
- Опция reconnect, также включённая по умолчанию, заставит бота переподключиться к серверу, если отвалится от него.
- Опция catchErrors перехватит ошибки в пользовательких листнерах; она отключена по умолчанию, чтобы не смущать.
- ⑨ Бот генерит ивенты для каждого сообщения. Так мы задаём обработчик для ивента. К слову, вместо функции здесь может быть корутина.
- ⑩ Есть и другие события. Например, irc.events.client.connected означает, что клиент соединился с сервером. А irc.events.client.authenticated говорит, что теперь можно слать сообщения.
- ⑪ Когда мы закончили конфигурировать бота, собираем через :build(). Если вместо него вызвать :buildAndRun(), бот тут же ещё и запустится.
- ⑫ Для удобства создадим ещё один тред, где будем работать с чатбоксом и ждать ^C.
- ⑬ Запускаем бота. Затем ждём завершения любого из двух потоков.
- ⑭ Когда это произошло, мы выключаем клиент, если он ещё подключен: тот выйдет с сообщением "Quitting."
- ⑮ Наконец, принудительно останавливаем потоки. На всякий случай.
Красота ведь.
Репозиторий
Репа либы — на нашем гитлабе. Там же есть примеры использования и документация с описанием всего.
Наконец, версия 1.0.0 лежит на хеле. Из-за баги в OC хпм крашиться может (фиксить лень), но можно попробовать скачать:
$ hpm install libirc
-
9
-
1
-
1
-
8 часов назад, whiskas сказал:Хмм я даже не думал что так можна сделать в ОС. Допилил чутку код фингера что б заранить можна было.
Прога выводит в консольку все сообщения з логов в том числе и новые в риал тайме.
[...]
Понятно, что прожка писалась на скорую руку и должна быть доделана. Подскажу, как именно.
- Нет смысла качать мегабайт логов, которые всё равно не влезут в консоль. Нужно вытащить последние строки, например алгоритмом, который я описал в прошлом посте.
- Не нужно досить сервер запросами. Добавить хотя бы os.sleep(5).
- Проверять, изменились ли логи, можно через ETag. Его сервер тоже посылает.
- Проверять, что #chunk > 0, смысла не имеет. Там есть 3 случая: nil, когда оборвано соединение, "", если просто пока нет ответа, или же строка с данными.
- Прога такая работать будет не более суток. Затем она перестанет обновляться. Думаю, ясно почему.
-
3
-
Не-е, нельзя быть таким пессимистичным. Всё можно, и вопрос решается очень легко. В HTTP/1.1 есть хедер Range, который позволяет скачивать файлы кусками. Кроме того, OC умеет посылать и получать хедеры.
local socket = component.internet.request("https://logs.s7.mcskill.ru/Hitechcraft_Public_Logs/public_logs/Hitechcraft_Public_Logs/14-02-2020.txt", nil, { Range = ("bytes=%d-"):format(start) }) local data = "" while true do local chunk = socket.read() if not chunk then break end data = data .. chunk end local _, _, headers = socket.response() print("Got: " .. #data) print("Content-Length: " .. headers["Content-Length"][1]) print("Content-Range: " .. headers["Content-Range"][1])
Вместо start подставить количество байт, уже прочитанных. Там указывается начало диапазона номером байта (начиная с 0), от которого нужно выдать ответ. См. доки.
Поэтому тактика такая:
- Посылаем запрос с методом HEAD (4 параметр к component.internet.request), чтобы получить только хедеры.
- Читаем в хедерах значение Content-Length.
- Начинаем запрашивать куски файла с конца, пока не наберём нужно кол-во строк.
- После получения начальных строк запомним позицию последнего байта и дальше запрашиваем инфу после него.
-
9
-
При достаточном желании можно отправлять сообщения через echo и ncat.
-
1
-
2
-
-
На тайпскрипте я, конечно, не писал, но пробовал MoonScript. Это такой язык, который транспилируется в Lua. У него тоже есть классы, сахара всякие. Но я на нём больше писать не хочу.
Выхлопной код получается страшный. Что не сильно способствует дебагу. А ещё он неоптимален. В том числе по размеру получающегося скрипта, и минификатор не сильно помогает.
Здесь, видимо, всё то же. Так что для опенкомпов будет проще всё же писать на Lua.
-
1
-
2
-
-
О, а когда @LeshaInc успел приделать сайдбар в оцелота? Неожиданным было его обнаружить на скриншоте.
-
Мы в бытность на сервере здешнем подарки получали, сплавляя железо в наггеты опенкомпьютерсные и назад.

Ocelot - продвинутый эмулятор OpenComputers
в Эмуляторы
Опубликовано:
А зачем нужен собранный ocelot-brain? Это же тупо либа. Её надо подключать в другую прогу, которую так и так придётся компилировать. Но смысла тогда готовый брейн собирать поразительно мало, особенно с учётом малого объёма кода.