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

Лидеры


Популярный контент

Показан контент с высокой репутацией за 04.06.2020 во всех областях

  1. 13 баллов
    Решил написать свой мультизагрузчик. Возможно, он похож на тот, что поставляется с комплектом MineOS, так что в целом является неплохой заменой стандартного биоса. Установка: Для OpenOS, просто запустите эту команду: wget -fq https://raw.githubusercontent.com/BrightYC/Cyan/master/installer.lua && installer.lua Для MineOS же есть приложение в местном AppMarket, под названием Cyan BIOS. Там тоже довольно элементарно. Исходники лежат тут: https://github.com/BrightYC/Cyan Что он может? Возможность загрузки/доступа к загрузчику по "белому" списку Lua-интерпретатор Возможность подключения компонентов "на лету" Возможность отформатировать/переименовать файловую систему Загрузка по интернету Доступ по белому списку: Доступ по белому списку работает так: при установке биоса указываются те игроки, которые будут иметь доступ к биосу. Если их нет - сигналы с их ником просто будут игнорироваться. Т.е, они в принципе не смогут использовать биос. Так же, есть способ загрузки с подтверждением проверенного пользователя. До тех пор пока проверенный пользователь не нажмет любую клавишу - биос будет ожидать нажатия, и не даст злоумышленникам доступ к компьютеру. Lua-интерпретатор: Lua-интерпретатор почти похож тот, что есть в OpenOS. Отличается он тем, что там нет переноса строк и максимальная длина команды составляет длину монитора (Это можно обойти, если использовать буфер обмена). Так же, интерпретатор имеет следующие функции: os.sleep([timeout: number]) proxy(componentName: string): component proxy or nil -- получает прокси какого-либо компонента по его имени read(lastInput: string or nil): string or nil -- базовый io.read() print(...) Картинки/видео: P.S Огромное спасибо @Fingercomp за лекцию по экранированию символов.
  2. 11 баллов
    Среди всех компонентов в OC у интернет-платы самый ужасный API. Неудивительно, что правильно использовать его умеют немногие. Даже за Vexatos мне приходилось чинить tape.lua — программку для записи кассет. Плюс в ирке нередко спрашивают, как отправить HTTP-запрос на сервер. Значит, пришло время написать, как же всё-таки использовать интернет-плату. Гайд строится на следующих предположениях (сорри за педантизм): Вы умеете прогать на Lua, в том числе знаете о двух основных способах возвращать ошибку. Вы писали уже программы для OpenComputers, которые использовали API этого мода или OpenOS, особенно либу event. Вы как-то использовали (или пытались использовать) интернет-карточку в программах. Секции 1, 3: вы понимаете основные принципы HTTP. Секции 2, 4: вы понимаете, как пользоваться TCP-сокетами и зачем (не обязательно в Lua). Секция 4: вас не смущает setmetatable и вы понимаете, как делать ООП на прототипах. Секции 2, 4: у вас OC 1.6.0 или выше. Секции 1, 3, 5: у вас OC 1.7.5 или выше. Текущая версия мода — 1.7.5, а в новой ничего не изменилось. У инет-карты есть две разных фичи — HTTP-запросы и TCP-сокеты. Кратко пробежимся по API и затем разберём детальнее применение. Рассматривать я буду API компонента: часто используют require("internet") — это не компонент, а обёртка. 1. Отправка HTTP-запросов: component.internet.request У этого метода 4 параметра: URL, на который надо послать запрос. На всякий случай, URL начинается со схемы (http: или https:), после которого идёт адрес хоста (например: //localhost, //127.0.0.1, //[::1], //google.com:443), за которым следует путь (/my-file.html). Пример: https://computercraft.ru/blogs/entry/666-profiliruem-programmy-pod-oc/. Данные запроса. Оно же тело запроса. Если мы отправляем GET/HEAD-запрос, то этот аргумент надо установить в nil. Хедеры, которыми запрос сопровождать. Можно поставить nil, тогда там по минимуму дефолтные подтянутся. Иначе передавать надо таблицу. Её ключи — это названия хедеров. Например, {["Content-Type"] = "application/json"}. Метод запроса. Если же этот аргумент не передавать, то возьмётся по дефолту GET или POST: это зависит от того, пуст ли аргумент 2 или нет. Если возникла ошибка, метод вернёт nil и сообщение об ошибке. Если же всё нормально, то метод вернёт handle — табличку с функциями. Вот что это за функции: handle.finishConnect() — проверяет, подключены ли мы к серверу. Если да, то вернёт true. Если к серверу ещё не подключены, то вернёт false. Если же возникла ошибка (например, 404 вернул сервер или закрыл соединение), то вернёт nil и сообщение об ошибке. Например, nil, "connection lost". В доках написано, что функция ошибку пробрасывает. На самом деле нет: она вообще не бросает исключения. handle.response() — возвращает мета-данные ответа с сервера. Если соединение ещё не установлено, вернёт nil. Если возникла ошибка, вернёт nil и сообщение об ошибке. Например, nil, "connection lost". В противном случае возвращает 3 значения: Код ответа (например, 200). Статус (например, "OK"). Таблицу с хедерами, которые отправил сервер. Выглядит примерно так: {["Content-Type"] = {"application/json", n = 1}, ["X-My-Header"] = {"value 1", "value 2", n = 2}}. Выпишу отдельно, что значения таблицы — это не строки, а ещё одни таблицы. handle.read([n: number]) — читает n байт (если n не задано, то сколько сможет). Если компьютер ещё не успел получить данные, то отдаст "". Если возникла ошибка, то выдаст nil и сообщение об ошибке. Например, nil, "connection lost". Если сервер закрыл соединение, то вернёт nil. В противном случае отдаст строку с частью ответа. handle.close() — закрывает соединение. 2. TCP-сокеты: component.internet.connect У метода есть 2 параметра: Адрес хоста. Например, 127.0.0.1. Здесь также можно указать порт: google.com:80. Порт. Если в первом аргументе порта нет, то второй параметр обязателен. Если возникла ошибка, он также вернёт nil и сообщение. Иначе возвращает handle — табличку с функциями. Вот такими: handle.finishConnect() — то же, что и выше. handle.read([n: number]) — то же, что и выше. handle.write(data: string) — отправляет data по сокету на сервер. Возвращает число переданных байт. Если соединение не установлено, это число равно 0. handle.close() — то же, что и выше. handle.id() — возвращает id сокета. 3. Как правильно отправить HTTP-запрос на сервер и получить ответ Чтобы было интереснее, реальная задача: написать аналог pastebin, только вместо пастбина использовать https://clbin.com/. Особенности: Для взаимодействия с сайтом нужно отправлять HTTP-запросы: GET и POST. Это всё OC умеет. Чтобы скачать, достаточно простого GET по ссылке. Это можно сделать даже через wget. А вот чтобы отправить файл, надо использовать MIME-тип multipart/form-data. OC не умеет из коробки такие формы отправлять. Мы напишем минимальную реализацию, которая бы нас устроила. Не забываем, что этот MIME-тип нужно установить в хедер. При этом мы хотим красиво обработать все ошибки и не допустить ошибок сами. Таким образом, использовать будем практически все фичи. 3.1. multipart/form-data Порядок особенностей нам не важен, поэтому начинаем с самого скучного. Сделаем функцию, которая принимает данные и обрамляет их согласно формату multipart/form-data. local function generateBorder(str) local longestOccurence = nil for match in str:gmatch("%-*cldata") do if not longestOccurence or #match > #longestOccurence then longestOccurence = match end end return longestOccurence and ("-" .. longestOccurence) or "cldata" end local function asFormData(str, fieldName) local border = generateBorder(str) local contentType = "multipart/form-data; boundary=" .. border return ([[ --%s Content-Disposition: form-data; name="%s" %s --%s--]]):format( border, fieldName, str, border ), contentType end Так как это не туториал по интернет-стандартам, вдаваться в детали реализации не буду. С помощью asFormData можно содержимое файла превратить в тело HTTP-запроса. Мы будем вызывать asFormData(str, "clbin"), ибо этого требует сайт. Кроме того, эта функция нам передаст значение хедера Content-Type. Он нам понадобится. 3.2. Взаимодействие с сайтом Напишем теперь функцию — обёртку над component.internet.request. local function request(url, body, headers, timeout) local handle, err = inet.request(url, body, headers) -- ① if not handle then return nil, ("request failed: %s"):format(err or "unknown error") end local start = comp.uptime() -- ② while true do local status, err = handle.finishConnect() -- ③ if status then -- ④ break end if status == nil then -- ⑤ return nil, ("request failed: %s"):format(err or "unknown error") end if comp.uptime() >= start + timeout then -- ⑥ handle.close() return nil, "request failed: connection timed out" end os.sleep(0.05) -- ⑦ end return handle -- ⑧ end Эту функцию можно прямо брать и копипастить в свои программы. Что она делает: ① — отправляем запрос. Сразу обрабатываем ошибку. ② — запрос доходит до сервера не мгновенно. Нужно подождать. Чтобы не зависнуть слишком долго, мы засекаем время начала. ③ — вызываем finishConnect, чтобы узнать статус подключения. ④ — finishConnect вернул true. Значит, соединение установлено. Уходим из цикла. ⑤ — finishConnect вернул nil. Мы специально проверяем через status == nil, потому что не нужно путать его с false. nil — это ошибка. Поэтому оформляем его как ошибку. ⑥ — проверяем, висим ли в цикле мы слишком долго. Если да, то тоже возвращаем ошибку. Не забываем закрыть за собой соединение. ⑦ — нам не нужен бизи-луп. Спим. ⑧ — мы не читаем сразу всё в память, чтобы экономить память. Вместо этого отдаём наружу handle. Частая ошибка — отсутствие элементов ②–⑦. Они нужны. Если до установки соединения мы вызовем handle.read(), то получим nil. Многие программы в этом случае сразу отчаются получить ответ и вернут ошибку. А надо было просто подождать. 3.3. Отправка файла Функция для отправки файла должна сначала прочесть его содержимое, затем сделать запрос и прочесть ответ. В ответе будет находиться URL файла. local function sendFile(path) local f, err = io.open(path, "r") -- ① if not f then return nil, ("could not open file for reading: %s"):format(err or "unknown error") end local contents = f:read("*a") -- ② f:close() local data, contentType = asFormData(contents, "clbin") -- ③ local headers = {["Content-Type"] = contentType} local handle, err = request("https://clbin.com", data, headers, 10) -- ④ if not handle then return nil, err end local url = {} -- ⑤ local read = 0 while true do local chunk, err = handle.read() if not chunk then -- ⑥ local _, _, responseHeaders = handle.response() -- ⑦ local length for k, v in pairs(responseHeaders) do -- ⑧ if k:lower() == "content-length" then length = tonumber(v) end end if not length or read >= length then -- ⑨ break end return nil, ("error occured while reading response: %s"):format(err or "unknown error") -- ⑩ end read = read + #chunk -- ⑪ table.insert(url, chunk) end return table.concat(url) -- ⑫ end ① — открываем файл для чтения. Обрабатываем ошибки. ② — считываем всё из файла. Не забываем закрыть его за собой. ③ — вызываем заранее написанную функцию asFormData. Мы получаем тело запроса и значение хедера Content-Type. Создаём таблицу хедеров. ④ — отправляем наш запрос. Обрабатываем ошибки. ⑤ — handle.read может не сразу вернуть весь ответ, а кусочками. Чтобы не забивать память кучей строк, кусочки мы будем класть в таблицу (получится что-то вроде {"htt", "p://", "clbi", "n.co", "m/ab", "cdef"}). Также мы храним число прочитанных байт. ⑥ — handle.read может ещё вернуть ошибку. В том числе если мы прочли весь ответ, и сервер закрыл соединение. Поэтому обработка ошибок будет немного сложной. ⑦ — мы хотим сверить число прочитанных байт с размером ответа. Для этого нам потребуется получить хедеры, отправленными сервером. Вызываем handle.response. ⑧ — размер ответа обычно пишется в заголовок Content-Length. Однако сервер может поиграться с регистром. Например, писать content-length или CONTENT-LENGTH. OpenComputers не трогает эти хедеры. Поэтому придётся пройтись по всем ключам таблицы и найти хедер без учёта регистра. ⑨ — если length не nil, то это число. Тогда проверяем, что столько байт мы прочли. Если же Content-Length не задан, то будем считать, что серверу не важно, сколько надо прочесть. В любом случае выходим из цикла и завершаем чтение. ⑩ — если мы прочли меньше, чем требуется, то явно ошибка какая-то. Обрабатываем. ⑪ — не забываем обновлять read. ⑫ — наконец, склеиваем таблицу в одну строку. Из цикла можно выйти только в случае ошибки. А при ошибке соединение уже закрыто. Следовательно, самим вызывать handle.close() не нужно. 3.4. Скачивание файлов Код для скачивания похож на предыдущий. Только вот в память мы записывать ответ с сервера уже не будем. Вместо этого напрямую пишем в файл. local function getFile(url, path) local f, err = io.open(path, "w") -- ① if not f then return nil, ("could not open file for writing: %s"):format(err or "unknown error") end local handle, err = request(url, nil, nil, 10) -- ② if not handle then return nil, err end local read = 0 while true do local chunk, err = handle.read() if not chunk then f:close() -- ③ local _, _, responseHeaders = handle.response() local length for k, v in pairs(responseHeaders) do if k:lower() == "content-length" then length = tonumber(v) end end if not length or read >= length then break end return nil, ("error occured while reading response: %s"):format(err or "unknown error") end read = read + #chunk f:write(chunk) end return true end ① — открываем файл, в этот раз для записи. Обрабатываем ошибки. ② — отправляем запрос без данных и с дефолтными хедерами. Обрабатываем ошибки. ③ — если мы сюда попали, то дальше каким-либо образом (ретурном или брейком) выпрыгнем из цикла. Поэтому не забываем закрывать за собой файл. Чтобы было удобнее копипастить, я оставил повторяющийся код в двух функциях. В своей программке можно sendFIle и getFile отрефакторить, выделить дублирующуюся часть в отдельную функцию. 3.5. UI Пришло время красивой каденции. Аккордом финальным в ней будет пользовательский интерфейс. Он к интернет-карте отношения уже не имеет, но для полноты приведу и его. local args, opts = shell.parse(...) local function printHelp() io.stderr:write([[ Usage: clbin { get [-f] <code> <path> | put <path> } clbin get [-f] <code> <path> Download a file from clbin to <path>. If the target file exists, -f overwrites it. clbin put <path> Upload a file to clbin. ]]) os.exit(1) end if args[1] == "get" then if #args < 3 then printHelp() end local code = args[2] local path = args[3] local url = ("https://clbin.com/%s"):format(code) path = fs.concat(shell.getWorkingDirectory(), path) if not (opts.f or opts.force) and fs.exists(path) then io.stderr:write("file already exists, pass -f to overwrite\n") os.exit(2) end local status, err = getFile(url, path) if status then print("Success! The file is written to " .. path) os.exit(0) else io.stderr:write(err .. "\n") os.exit(3) end elseif args[1] == "put" then if #args < 2 then printHelp() end local path = args[2] local url, err = sendFile(path) if url then url = url:gsub("[\r\n]", "") print("Success! The file is posted to " .. url) os.exit(0) else io.stderr:write(err .. "\n") os.exit(4) end else printHelp() end 3.6. Вуаля Осталось добавить реквайры, и мы получим полноценный клиент clbin. Результат — на гисте. 4. Как правильно установить соединение через TCP-сокет Прошлая секция была вроде интересной, поэтому здесь тоже запилим какую-нибудь программку. @Totoro вот сделал интернет-мост Stem. Напишем для него клиент. Правильно. Опять же, особенности: Работает через TCP-сокет. Протокол бинарный. И асинхронный. А ещё сессионный: у каждого TCP-соединения есть собственный стейт. Доки хранятся на вики. При разрыве соединения клиент должен переподключиться и восстановить стейт. Здесь снова придётся использовать все фичи интернет-карты. 4.1. Архитектура Мы разделим программу на 2 части — фронтенд и бэкенд. Фронт будет заниматься рисованием и приёмом данных от пользователя, и им займёмся в конце и без комментариев. Бэк — поддержанием соединения и коммуникации с сервером. Это куда больше имеет отношения к гайду, рассмотрим подробнее. Бэкенд реализуем через ООП. Создадим конструктор, напихаем методов, которые затем будет дёргать фронт. 4.2. Конструктор Привычно вбиваем ООП-шаблон в Lua. local newClient do local meta = { __index = {}, } function newClient(address, channels, connectionTimeout, readTimeout, maxReconnects) local obj = { __address = address, __channels = channels, __connectionTimeout = connectionTimeout, __readTimeout = readTimeout, __maxReconnects = maxReconnects; __socket = nil, __buffer = nil, __running = false, __reconnectCount = 0, } return setmetatable(obj, meta) end end Ну, тут всё мирно пока. Начнём боевые действия с протокола. 4.3. Протокол Для него наклепаем кучу методов, которые будут крафтить пакеты и писать их через write. Write сделаем позже. Также сразу сделаем персеры. local meta = { __index = { __opcodes = { message = 0, subscribe = 1, unsubscribe = 2, ping = 3, pong = 4, }, __craftPacket = function(self, opcode, data) return (">s2"):pack(string.char(opcode) .. data) end, __parsePacket = function(self, packet) local opcode, data = (">I1"):unpack(packet), packet:sub(2) return self.__parsers[opcode](data) end, send = function(self, channel, message) return self:write(self:__craftPacket(self.__opcodes.message, (">s1"):pack(channel) .. message)) end, subscribe = function(self, channel) return self:write(self:__craftPacket(self.__opcodes.subscribe, (">s1"):pack(channel))) end, unsubscribe = function(self, channel) return self:write(self:__craftPacket(self.__opcodes.unsubscribe, (">s1"):pack(channel))) end, ping = function(self, message) return self:write(self:__craftPacket(self.__opcodes.ping, message)) end, pong = function(self, message) return self:write(self:__craftPacket(self.__opcodes.pong, message)) end, }, } meta.__index.__parsers = { [meta.__index.__opcodes.message] = function(data) local channel, idx = (">s1"):unpack(data) return { type = "message", channel = channel, message = data:sub(idx), } end, [meta.__index.__opcodes.subscribe] = function(data) return { type = "subscribe", channel = (">s1"):unpack(data), } end, [meta.__index.__opcodes.unsubscribe] = function(data) return { type = "unsubscribe", channel = (">s1"):unpack(data), } end, [meta.__index.__opcodes.ping] = function(data) return { type = "ping", message = data, } end, [meta.__index.__opcodes.pong] = function(data) return { type = "pong", message = data, } end, } В коде я активно использую string.pack и string.unpack. Эти функции доступны только на Lua 5.3 и выше, но позволяют очень удобно работать с бинарными форматами. 4.4. Подключение к серверу Прежде чем реализуем write, нужно разобраться с подключением. Оно нетривиально. local meta = { __index = { ..., connect = function(self) local socketStream = assert(inet.socket(self.__address)) -- ① local socket = socketStream.socket -- ② local start = comp.uptime() -- ③ while true do local status, err = socket.finishConnect() if status then break end if status == nil then error(("connection failed: %s"):format(err or "unknown error")) -- ④ end if comp.uptime() >= start + self.__connectionTimeout then socket.close() error("connection failed: timed out") -- ④ end os.sleep(0.05) end self.__socket = socket -- ⑤ self.__buffer = buffer.new("rwb", socketStream) -- ⑥ self.__buffer:setTimeout(self.__readTimeout) -- ⑦ self.__buffer:setvbuf("no", 512) -- ⑧ for _, channel in ipairs(self.__channels) do -- ⑨ self:subscribe(channel) end end, }, } ① — я использую обёртку над component.internet. Она потом будет нужна, чтобы мы могли поместить сокет в буфер. Обращаю внимание, что вызов обёрнут в assert. Работает она так: если первое значение не nil и не false, то возвращает его, а иначе кидает ошибку, используя второе значение в качестве сообщения. Проще говоря, она превращает nil, "error message" в исключение. ② — а пока я вытягиваю из обёртки сокет... ③ — чтобы можно было проверить, установлено ли соединение. Код здесь аналогичен тому, что мы делали в прошлой секции. Не выдумываем. ④ — одно различие: вместо return nil, "error message" я сразу прокидываю исключение. Прежде всего потому, что ошибки мы прокидывать должны единообразно. Раз в ① кидаем исключение, и здесь делаем то же. Почему исключение, а не return nil, "error message"? Мы вызывать connect будем из всяких мест. Так как в случае ошибок бэкенд беспомощен, то лучше прокинуть ошибку до фронтенда и не усложнять код бэка проверками на nil. Кроме того, это громкая ошибка: если забыть где-то её обработать, она запринтится на экран, случайно пропустить её или подменить какой-нибудь непонятной "attempt to index a nil value" не получится. В конце концов, мне так проще. ⑤ — сокет я сохраняю в поле. socket.finishConnect нам ещё понадобится. ⑥ — пришло время обернуть сокет в буфер. Может показаться излишним, особенно учитывая ⑧. Причины станут ясны, когда будем делать чтение. rw — это буфер для чтения и записи. b — бинарный режим: buffer:read(2) вернёт 2 байта, а не 2 символа. Так как символы кодируются в UTF-8 и занимают 1 (латиница), 2 (кириллица, диакритика), 3 (BMP: куча письменностей, всякие графические символы, большая часть китайско-японско-корейских иероглифов) или 4 байта (всё, что не влезло в BMP, например emoji), то отсутствие этого режима может дать ощутимую разницу. В нашем случае протокол бинарный — ставим b. ⑦ — устанавливаем таймаут для чтения. Объясню подробнее, когда будем это чтение делать. ⑧ — отключаем буфер для записи. Он нам не нужен. ⑨ — здесь же подключаемся ко всем каналам. Итого мы получаем свойства __socket и __buffer. Сокет использовать будем, чтобы вызывать .finishConnect() и .id(). Буфер — для записи и чтения. 4.5. Запись Теперь, разобравшись с сокетами и буферами, мы можем запросто писать в сокет. Пилим write: local meta = { __index = { ..., write = function(self, data) return assert(self.__buffer:write(data)) end, }, } Здесь тоже оборачиваем write в assert, чтобы кидать исключения. Причины уже пояснял. 4.6. Чтение и обработка пакета Сначала делаем функцию readOne. Она будет пытаться читать ровно один пакет. Здесь требуется нестандартная обработка ошибок, поэтому код сложноват. local meta = { __index = { ..., readOne = function(self, callback) -- ⑥ self.__buffer:setTimeout(0) -- ① local status, head, err = pcall(self.__buffer.read, self.__buffer, 2) self.__buffer:setTimeout(self.__readTimeout) if not status and head:match("timeout$") then return end assert(status, head) -- ② local length = (">I2"):unpack(assert(head, err)) -- ③ local packet = self:__parsePacket(assert(self.__buffer:read(length))) -- ④ if packet.type == "ping" then -- ⑤ self:pong(packet.message) end callback(self, packet) -- ⑥ return true end, } } ① — рассмотрим эту мишуру по порядку: Любой пакет stem начинается с 2 байт, которыми кодируется длина остатка. Отсюда всплывает двойка. Автор buffer, к сожалению, не осилил реализовать адекватную обработку ошибок. Он использует и исключения, и тихие ошибки (nil, "error message"). В случае таймаута будет прокинуто исключение. Однако мы перед чтением поставили таймаут в 0. Если буфер не найдёт сразу 2 байта в сокете, то он сразу кинет ошибку. Мы хотим проверить, есть ли в сокете пакет, который бы можно было прочесть. Используем pcall. Сначала раскроем self.__buffer:read(2) как self.__buffer.read(self.__buffer, 2), а затем поместим функцию и её аргументы в pcall. pcall возвращать будет сразу 3 значения по следующему принципу: Если на сокете есть 2 непрочитанных байта, read вернёт их без ошибок. Тогда status будет равен true, в head сохранятся эти 2 байта, а в err запишется nil. Если на сокете этих байтов нет, то read прокинет исключение "timeout". status установится в false, head приравняется "/lib/buffer.lua:74: timeout", а err также будет nil. Если же при чтении с сокета возникла другая ошибка, то read вернёт её по-тихому: status будет true, head — nil, а сообщение об ошибке уйдёт в err. Не думаю, что этот случай возможен, однако read может кинуть исключение и не из-за таймаута. status установится в false, а ошибка сохранится в head. В if мы проверяем, был ли таймаут (ситуация 1.2). В таком случае мы не кидаем исключения, а тихо выходим. Наконец, не забываем вернуть прежнее значение таймаута. ② — обрабатываем случай 1.4. ③ — обрабатываем случай 1.3 с помощью assert. Последний оставшийся и единственный успешный случай (1.1) также покрывается: распаковываем 2 байта в целое беззнаковое число (uint16_t). ④ — в ③ мы получили длину оставшегося пакета. Очевидно, надо остаток дочитать, что и делаем. Здесь уже не надо отдельно обрабатывать таймаут, достаточно assert. Считанный пакет отдаём в __parsePacket. ⑤ — если сервер докопался до нас своим пингом, отправим ему понгу. ⑥ — функция readOne принимает коллбэк. Это функция, которая будет обрабатывать все пакеты. Коллбэк будет передавать фронтенд, а бэкенд займётся минимальной обработкой, чтобы в принципе работало. Как, например, ③. Отлично. Мы приготовили все примитивы, которые были нужны. Осталось собрать их воедино — в event loop. 4.7. Event loop и события Ивент луп — это цикл, который ждёт событий и что-то с ними делает. Пришло время разобраться, что за события есть в OC. Когда мы вызываем socket.read или socket.finishConnect, устанавливается "ловушка" (селектор). Она срабатывает, когда на сокет пришли новые байты. При этом компьютер получает событие internet_ready. После чего "ловушка" деактивируется до следующего вызова. internet_ready, таким образом, — это событие, извещающее нас о том, что на сокете валяются непрочитанные данные и пора вызвать socket.read, чтобы их собрать. У события два параметра. Первый — это адрес интернет-карты. Второй — id сокета. Тот id, который возвращает socket.id(). Поэтому мы сохранили сокет в поле __socket: сейчас будем использовать его. local meta = { __index = { ..., __run = function(self, callback) while self.__running do local e, _, id = event.pullMultiple(self.__readTimeout, "internet_ready", "stem%-client::stop") -- ① if e == "internet_ready" and id == self.__socket.id() then -- ② while self:readOne(callback) do self.__reconnectCount = 0 -- ③ end elseif e ~= "stem-client::stop" then self:ensureConnected() -- ④ end end end, stop = function(self) self.__running = false event.push("stem-client::stop") -- ⑤ end, } } ① — ждём события internet_ready или stem-client::stop. Так как в event.pullMultiple названия ивентов сверяются через string.match, дефис экранируем. Второй ивент нужен, чтобы принудительно прервать цикл из stop. ② — обрабатываем мы только internet_ready и только для нашего сокета. Проверяем. ③ — если поймался пакет или пакеты, то пытаемся обработать каждый в порядке прибытия. Когда мы закончили обрабатывать все пакеты, self:readOne вернёт nil, и цикл прервётся. Кстати говоря, если мы внутри цикла оказались, то соединение установилось. Не забываем отметить это. ④ — если же улов пуст, перепроверяем, подключены ли мы вообще. ⑤ — не забываем добавить метод, чтобы остановить наш цикл. Отсюда же отсылаем событие stem-client::stop. Отлично. Теперь пришло время ловить все наши прокидываемые исключения. 4.8. Обработка ошибок Последними 2 функциями, которые мы добавим, будут ensureConnected и run. С их помощью бэкенд будет автоматически переподключаться к серверу в случае проблем. local meta = { __index = { ..., ensureConnected = function(self) local status, err = self.__socket.finishConnect() -- ① if status == false then error("not yet connected") end return assert(status, err or "unknown error") end, run = function(self, callback) if self.__running then -- ② return end self:connect() -- ③ self.__running = true while self.__running do -- ④ local status, err = pcall(self.__run, self, callback) -- ⑤ if not status then if self.__reconnectCount == self.__maxReconnects then -- ⑥ return nil, ("connection lost: %s; reconnect limit is reached"):format(err or "unknown error") end self.__reconnectCount = self.__reconnectCount + 1 self.__buffer:close() -- ⑦ if not pcall(self.connect, self) then -- ⑧ if self.__socket then self.__socket:close() end if self.__buffer then self.__buffer:close() end os.sleep(1) end end end self.__buffer:close() end, }, } ① — ensureConnected просто прокинет ошибку, которую вернёт finishConnect(). ② — принимаем защитную позицию против дураков. Рекурсивно запускать циклы смысла нет. ③ — сначала подключаемся к серверу. Если всё отлично, то можно начинать. ④ — как и в __run, здесь мы оборачиваем код в цикл. Если вызван stop(), то сначала остановится self.__run, а затем и этот цикл. ⑤ — обработка исключений требует pcall. Потому что их надо словить. ⑥ — если мы старались-старались, но так и не смогли уложиться в self.__maxReconnects по реконнектам, кидаемся белым флагом. ⑦ — не забудем закрыть буфер. ⑧ — вспомним, что self.connect кидает исключение. Перехватываем. На всякий случае позакрываем то, что породил connect. 4.9. Фронтенд На этом наш бэкенд готов. Поздравляю. Остаётся лишь прицепить ввод-вывод. Опять же, даю готовый код без комментариев, ибо не об этом пост. local gpu = com.gpu local w, h = gpu.getResolution() local function writeLine(color, line) local oldFg if gpu.getForeground() ~= color then oldFg = gpu.setForeground(color) end local lines = 0 for line in text.wrappedLines(line, w + 1, w + 1) do lines = lines + 1 end gpu.copy(1, 1, w, h - 1, 0, -lines) local i = 0 for line in text.wrappedLines(line, w + 1, w + 1) do gpu.set(1, h - lines + i, (" "):rep(w)) gpu.set(1, h - lines + i, line) i = i + 1 end if oldFg then gpu.setForeground(oldFg) end end local channel = ... if not channel then io.stderr:write("Usage: stem <channel>\n") os.exit(1) end if #channel == 0 or #channel >= 256 then io.stderr:write("Invalid channel name\n") os.exit(2) end local client = newClient( "stem.fomalhaut.me:5733", {channel}, 10, 10, 5 ) require("thread").create(function() while true do term.setCursor(1, h) io.write("← ") local line = io.read() if not line then break end local status, err = pcall(client.send, client, channel, line) if not status then writeLine(0xff0000, ("Got error while sending: %s"):format(err or "unknown error")) break end end client:stop() end) client:run(function(client, evt) if evt.type == "message" then writeLine(0x66ff00, "→ " .. evt.message) elseif evt.type == "ping" or evt.type == "pong" then writeLine(0xa5a5a5, "Ping: " .. evt.message:gsub(".", function(c) return ("%02x"):format(c:byte()) end)) end end) os.exit(0) Здесь я упускаю одну вещь: обработку ошибок в client.send. Если мы попытаемся отправить сообщение, когда у нас потеряно соединение (или до того, как оно установлено), мы или словим ошибку, или потеряем сообщение. Починить это можно, добавив очередь отправляемых пакетов, но это в разы усложнит программу, поэтому оставим так. 4.10. Готово! Добавим реквайров... И у нас получился вполне рабочий клиент для Stem! Код программы — на гисте. 5. В чём различие между component.internet и require("internet") Первое — исходный компонент. Второе — обёртка над ним. У обёртки есть 3 функции: internet.request(url, data, headers) — обёртка над component.internet.request. Удобна тем, что все ошибки превращает в исключения за программиста. Кроме того, возвращаемое значение — итератор, и его можно поместить в цикл for. Тем не менее, код, который ждёт установки соединения, нужно писать самому. К тому же, нельзя задать свой HTTP-метод. internet.socket(address, port) — промежуточная обёртка над component.internet.connect. Она используется для того, чтобы потом превратить её в буфер, как сделали мы. Сама по себе достаточно бесполезна. internet.open(address, port) — тоже обёртка над component.internet.connect. Она вызывает internet.socket(address, port) и сразу превращает результат в буфер. Проблема в том, что сам объект сокета использовать можно только через приватные свойства, которые могут ломаться между обновлениями OpenOS. Из-за этого функция исключительно ущербна. Для отправки HTTP-запросов я предпочитаю использовать API компонента. TCP-сокеты же проще создавать через обёртку (internet.socket), вручную проверять подключение и так же вручную укладывать обёртку в буфер, как показано выше. 6. Конец Самое сложное в использовании интернет-карты — это правильно обработать все ошибки. Они могут возникнуть на каждом шагу, при этом быть полноценными исключениями или тихими ошибками. Необработанные исключения крашат программу, из-за чего возникает желание весь код программы поместить в один большой pcall. Например, IRC-клиент, который на дискете поставляется, делает так. Тихие ошибки гораздо подлее. Необработанные, они тоже крашат программу, только вот сама ошибка теряется, подменяется другой (обычно "attempt to index a nil value"). В Lua обработать все ошибки — задача сложная, потому что механизм ошибок ужасен. В нормальных языках стэктрейс отделён от сообщения об ошибке, плюс каждая ошибка имеет свой тип, по которому можно безопасно определять вид ошибки. Lua этим не заморачивается: сообщение об ошибке включает позицию в коде, откуда ошибка прокинута. Есть или нет стэктрейс, зависит от выбора между pcall и xpcall. Если они находятся где-то в другой библиотеке, программист на выбор повлиять не может. В коде Stem-клиента единственный способ узнать, от таймаута ли ошибка прокинута, — матчить последние 7 символов на слово "timeout". Это эталонный костыль. Даже в JavaScript механизм лучше. Поэтому этот пост получился не столько про интернет-карту, сколько про обработку ошибок.
  3. 9 баллов
    Да, конечно, почему бы и нет. Сложность в том, что 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, я один раз взял прокси, а дальше работаю с ним.
  4. 6 баллов
    Чтобы решить проблему, надо оформить код. 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. 6 баллов
    Итак, это оочень простое меню загрузки, к примеру для нескольких ОС, которые хранятся на разных дисках. Функционал: Загрузка с разных дисков Переименование дисков прямо в биосе Загрузка с интернета (добавляйте свои пункты в гитхабе, может потом добавлю по URL) [WiP] Добавление своих пунктов загрузки с разными настройками Скриншот: Установка: (спасибо @BrightYC) wget -f https://raw.githubusercontent.com/timas130/oc-simple-bios/master/code.min.lua && echo "Flashing..." && flash -q code.min.lua "Simple BIOS" && echo "Done!" Исходники: https://github.com/timas130/oc-simple-bios Спасибо за внимание, жду критики. ————————————————————— Changelog от 11.06.2020 (1.1): Добавил загрузку по сети Поправил кириллицу/юникод Поменял инсталлер, спасибо @BrightYC Сделал открытие меню на Ctrl, а не загрузку через 5 сек
  6. 5 баллов
    Лайк за ответ, который в 42 раза длиннее вопроса.
  7. 5 баллов
    Обновил. Теперь, доступ осуществляется не по паролю, а по белому списку. Под белый список есть ровно 220 байт(Со стандартным конфигом), что равно около 18 ников со средней длиной. Установка осуществляется всё так же.
  8. 5 баллов
    Немного норкомании и хаоса:
  9. 4 балла
    Пока компьютер спит, из него можно спокойно вытащить все планки памяти. Крашнется компьютер только при получении какого-либо события. Обнаружил это @BrightYC. Презабавно. #какие-то #теги #самсебетвиттер
  10. 4 балла
    Deshli, боюсь, с таким подходом тебе вряд ли кто-то поможет. И дело вовсе не в том, что мы тут все злые. Дело в том, что программирование это не магия, где достаточно махнуть палочкой и сказать "Хочу, чтоб тыква превратилась в карету". В программировании, что бы получить карету, ты должен отчетливо представлять каких размеров должна быть карета, какой формы и цвета, сколько иметь колес, сколько спиц на каждом колесе и еще очень-очень много деталей. Вот ты уже не первый раз создаешь тему про торгового робота, а я до сих пор не пойму, какие функции должен выполнять этот робот, под какой операционкой работать, имеет ли он отношение к Майнкрафту или какой другой игре. А может это вовсе не игра, а реальный робот, который чем то там торгует? Вместо куска кода, выдранного непонятно откуда, лучше выложи что-то на подобии технического задания, в котором попытайся изложить всё, что ты знаешь о своем роботе и всё что желаешь от него добиться, что бы тот, кто захочет тебе помочь, не задавал кучи вопросов.
  11. 4 балла
    Первый пост. Ну все же хоть что то. Хочу показать свою небольшую поделку по теме инфраструктуры. Посмотреть и скачать ее можно тут: pastebin run mn7W46KJ Задача программы проста. Вывод значений энергетических ячеек из thermal expansion и заполненность резервуаров из immersive engineering. Автоматическое включение двигатель если средний объем ячеек стал ниже заданного значение и заряд до 100%. Среднее значение по кол во ячейкам подключенным к адаптеру. Делал все через огромные table с id которые потом становятся объеками (эффективно ли это по памяти?) Задача оказалась не так сложна как я думал и больше я завис на графике... Потыкав несколько готовых либ понял что ничего не понял (как это обычно бывает) и решил нарисовать свою графику. Единственное что не понял как запускать event отдельно и реагировать на него поэтому сделал пока вот так: Надеюсь вы подскажите как это лучше решить. Буду благодарен. Ну и скриншотики
  12. 4 балла
    Первый опыт компиляции и правок OpenComputers Задача: Собрать мод OpenComputers, проверить его работоспособность в игре, внести небольшие правки в мод и также проверить их работоспособность в игре. Мой путь к решению: Первая страница поисковой выдачи по фразе «opencomputers build mod» не показала ничего интересного для меня. Зато фраза «opencomputers build from source» быстро привела меня на страницу https://ocdoc.cil.li/tutorial:debug_1.7.10 Команды инструкции несколько отличаются от тех, что я применял раньше. Поэтому я задал себе два вопроса: Чем отличается вызов gradlew от gradle? Чем отличается setupDecompWorkspace от setupCIWorkspace? На первый вопрос я ответил неправильно. Из найденной информации я понял, что обёртка gradlew используется для того, чтобы не морочить себе голову отдельной установкой Gradle и всё необходимое устанавливать через скрипт. Но у меня же уже установлен Gradle! Поэтому проще использовать именно его. Ещё не понимая, в чём грабли, я в хаотическом порядке побежал по коммитам, дойдя чуть ли не до начала репозитория. Но gradle упорно выдавал ошибку даже при запуске без параметров: $ gradle ... A problem occurred evaluating root project 'OpenComputers'. > Failed to apply plugin [id 'forge'] > Could not create task of type 'ReobfTask'. Поиск по фразе «gradle Could not create task of type ReobfTask» не дал ничего вразумительного кроме того, что может быть неправильной версия не то Gradle, не то Forge, не то Minecraft, не то JDK. Так я ходил по граблям около двух часов, пытаясь что-то изменить в конфигах Gradle и переходя от коммита к коммиту. Почувствовав усталость, я решил, что зашёл в тупик, и чтобы выйти из него, мне следует взять перерыв, и отдохнув, найти новую точку для приложения усилий. Так я и сделал. Отдохнув, я ещё раз почитал об отличии gradlew от gradle, вспомнил, что встреченная мной ошибка может быть вызвана неправильной версией Gradle, и сразу осознал упущенный мной нюанс: gradlew – не просто обёртка, и позволяет не просто обойтись без установки gradle, а без установки требуемой версии gradle. Проверяю предположение: $ gradle -version Gradle 2.10 $ ./gradlew -version Gradle 5.6.4 Так и есть! Вывод: Для ускорения продвижения в изучении в первый раз следует максимально чётко следовать инструкциям. А уже имея эталонный рабочий вариант, можно смело экспериментировать. Зная, в чём именно я совершил отклонение, можно быстрее находить и причину неудачи тоже. Я быстро отработал ту часть инструкции, которая не касалась использования IDE, нашёл файл свежесобранного мода, переместил его в каталог с остальными модами и запустил игру: $ git clone https://github.com/MightyPirates/OpenComputers.git $ cd OpenComputers $ ./gradlew setupDecompWorkspace $ ./gradlew build $ find . -name OpenComputers*.jar ./libs/OpenComputers-LuaJ.jar ./libs/OpenComputers-JNLua.jar ./build/libs/OpenComputers-MC1.7.10-1.7.5+f73dd9e-dev.jar ./build/libs/OpenComputers-MC1.7.10-1.7.5+f73dd9e-javadoc.jar ./build/libs/OpenComputers-MC1.7.10-1.7.5+f73dd9e-api.jar ./build/libs/OpenComputers-MC1.7.10-1.7.5+f73dd9e-sources.jar $ mv build/libs/OpenComputers-MC1.7.10-1.7.5+???????-universal.jar ~/.minecraft/mods/OpenComputers-MC1.7.10-1.7.5+test-universal.jar Работает! Отвечая на второй вопрос и вникая в нюансы Gradle, я узнал, что его задачи зависят друг от друга. И если я верно понял, то для сборки мода достаточно лишь скачать репозиторий и запустить сборку мода. Необходимые для этого этапа подзадачи будут выполнены автоматически. Проверяю: Для чистоты эксперимента удаляю папку с модом и пользовательскую папку Gradle: $ rm -rf OpenComputers $ rm -r ~/.gradle И получаю собранный мод минимумом команд: $ git clone https://github.com/MightyPirates/OpenComputers.git $ cd OpenComputers $ ./gradlew build Остаётся лишь перенести мод в каталог с другими модами: $ mv build/libs/OpenComputers-MC1.7.10-1.7.5+???????-universal.jar ~/.minecraft/mods/OpenComputers-MC1.7.10-1.7.5+test-universal.jar С компиляцией и сборкой я разобрался. Теперь пора что-нибудь изменить в моде. Чтобы не выдумывать задачу, я вспоминаю исходную цель. В OpenComputers мне не нравится механика управления нагрузкой от пользовательских скриптов. Что я об этом знаю? Во время длительных вычислений я могу получить ошибку «too long without yielding». Попробую найти эту строку в исходниках: $ grep -ir 'too long without yielding' src/main/resources/assets/opencomputers/lua/machine.lua:local tooLongWithoutYielding = setmetatable({}, { __tostring = function() return "too long without yielding" end}) Удача! Это файл на Lua, и мне сейчас, возможно, не потребуется вникать в Scala. Открываю этот файл первым подвернувшимся под руку редактором: $ nano src/main/resources/assets/opencomputers/lua/machine.lua Ищу, как используется переменная tooLongWithoutYielding. Ошибка с таким исключением генерируется лишь в одном месте, в функции checkDeadline() по результатам проверки computer.realTime() > deadline. Ищу, где и как используется переменная deadline. Стараясь не вникать в детали кода, я нахожу участок, который с наибольшей вероятностью задаёт время, в течение которого пользовательский скрипт может работать без уступки времени: deadline = computer.realTime() + system.timeout(). Лучших вариантов я не вижу, поэтому правлю эту строку. Проверяю выполненные изменения: $ git diff --- a/src/main/resources/assets/opencomputers/lua/machine.lua +++ b/src/main/resources/assets/opencomputers/lua/machine.lua @@ -1486,7 +1486,7 @@ local function main() ... - deadline = computer.realTime() + system.timeout() + deadline = computer.realTime() + 10 --system.timeout() По уже отработанной схеме компилирую мод, переношу его в папку с модами и запускаю игру. Для проверки внесённых в мод изменений я запускаю тестовый скрипт: # lua lua > clock=os.clock t_=clock() pcall(function() while true do end end) t=clock() print(t-t_) 5.000662049 Вроде бы ничего не изменилось. Но я перезагружаю тестовый компик и снова запускаю скрипт. Получаю результат: 9.999750501 Сработало! Подобного поведения можно добиться и банальной правкой конфига, но моя цель заключалась в достижении того же эффекта правкой исходников мода. Результат: Я смог скомпилировать мод OpenComputers, осознал пользу обёртки gradlew, нашёл минимальный набор команд для компиляции, а также внёс работоспособное изменение в мод. Ближайшие планы: Во время решения этой задачи я снова уклонился от использования IDE. Но сейчас я начал серьёзно колебаться, выбирая между двумя направлениями: приступить к поиску оптимального алгоритма управления нагрузкой, или же всё таки освоить работу с IDE хотя бы на базовом уровне.
  13. 4 балла
    Совсем забыл в видео показать. Демонстрация подключения компонентов "на лету":
  14. 4 балла
    На всякий случай, если кто-то захочет скачать библиотеку, выкладываю свой, исправленный и работающий на новых версиях вариант Помимо исправления некоторых ошибок, добавил ссылку на зеркало Телеграма - теперь проблем с блокировками быть не должно Ссылка Команда для установки (с зависимостями): pastebin get 4XB9FdRH -f /lib/Telegram.lua; pastebin get 4Lh9ALEY /lib/Promise.lua; pastebin get ji28sbxU /lib/JSON.lua;
  15. 4 балла
    В прошлый раз я патчил OpenComputers, чтобы пробрасывать нативную либу debug. Пойдём дальше. Добавим нативных либ package и os. Прокинем дефолтное окружение внутрь песочницы. Пропатчим мод, чтобы можно было загружать си-модули. Загрузим профилятор и посмотрим, что из этого вышло. На винде ничего не заработает. Гарантирую. Если надо профилировать, ставьте нормальные оси или мучайтесь. 0. Сырцы мода Так как мы будем патчить мод, надо сначала подготовить исходники. $ git clone https://github.com/MightyPirates/OpenComputers.git $ cd OpenComputers $ git checkout master-MC1.12 $ ./gradlew setupDecompWorkspace На третьей строке версию выбираем по вкусу и выпекаем всё необходимое для компиляции. 1. Нативные либы Здесь всё просто. Открываем файл src/main/scala/li/cil/oc/server/machine/luac/LuaStateFactory.scala. Творим следующее: Вуаля. Теперь в machine.lua будут глобальные переменные package и _os. Отмечу отдельно, что меняем мы только архитектуру Lua 5.3. Уже на этом этапе у нас может сломаться персистентность. Это не страшно: она и должна сломаться. 2. Прокидываем окружение Поступаем аналогично тому, что делали в прошлой записи: меняем src/main/resources/assets/opencomputers/lua/machine.lua: Внутри песочницы в глобальной переменной env запечатлено будет всё окружение machine.lua. 3. C-модули Уже сейчас можно загрузить OpenOS и прописать env.require("libname"). Проблема в том, что C-модули так подключить не получится. Связано это с особенностью Lua. Абстрактно задача заключается в том, чтобы загрузить библиотку Lua с dlopen(..., RTLD_GLOBAL). System.loadLibrary в жаве флаг этот упускает по очевидным причинам, а нам он нужен. Значит, пришло время костылей. 3.1. Подключаем JNA: build.gradle Первый ханк нужен, чтобы можно было потом компилировать мод. Почему-то у курсов мавен не работает, а разбираться мне лень. 3.2. Патчим ещё раз src/main/scala/li/cil/oc/server/machine/luac/LuaStateFactory.scala Во-первых, подключаем хэшмапу. Потребуется. Во-вторых, импортируем JNA. Вернее, его часть. В-третьих, патчим код, чтобы он загружал Lua 5.3 через JNA. Магическая константа 0x101 — это значение RTLD_LAZY | RTLD_GLOBAL на моей системе. На фряхе, маке оно может отличаться. На этом этапе Lua 5.2 не будет работать. Включаться будет только Lua 5.3 из-за конфликта имён. Кроме того, JNA — это, вообще, огромная либа. Ради одной функции её подключать — это оверкилл. Но я в тонкостях JVM и JNI не силён. Как уже сказал, разбираться мне лень. 3.3. Компилируем $ ./gradlew assemble Выхлоп в build/libs. Берём жарник без суффиксов вроде -javadoc, -api, -sources. 4. Настраиваем профилятор Профилятор я написал сам на Rust. Вот ссылка: https://github.com/Fingercomp/lprofile-rs Очевидно, нам надо его скомпилировать. 4.1. Компилируем профилятор Ставим cargo (мультитул раста такой) любым удобным способом. Собираем: $ cd .. $ git clone --recurse-submodules https://github.com/Fingercomp/lprofile-rs.git $ cd lprofile-rs $ cargo build --release В target/release будет лежать liblprofile.so. Тырим его. 4.2. Определяем pwd Кидаем пропатченный OC в моды и запускаем игру. Пишем в опенкомпе env._os.getenv("PWD"), чтобы определить текущую директорию. Кидаем либу-профилятор в неё. 4.3. Профилируем Наконец, можно заняться мясом. local profiler = env.require("lprofile").Profiler() local result = profiler(function() local v = 0 for i = 1, 10e6, 1 do v = v + i end end) table.sort(result, function(lhs, rhs) return lhs.totalTime < rhs.totalTime end) print("Name", "# of calls", "Total time", "Total time, excluding inner calls") for _, v in ipairs(result) do print(("%s\t%d\t%.6f s\t%.6f s"):format(v.name, v.calls, v.totalTime, v.totalSelfTime)) end print("total time:", result.totalTime) 5. Зачем Мы получили наполовину сломанную версию OpenComputers: без Lua 5.3, без персистентности. Зато можем профилировать программы. Этот пост я написал, чтобы не забыть самому. Сомневаюсь, что кому-то интересно заниматься такой норкомагией.
  16. 3 балла
    Добрый день, надеюсь не забыли обо мне! А я пришёл вам новую программу показать Давайте разбираться... Пример работы: симуляция магнитного поля между двумя полюсами Описание: Программа написана на языке программирования lua и работает на движке love2d Визуализация использует GLSL шейдеры версии 3 Для хранения работ используются отдельные файлы с шейдерами, чтобы не трогать весь код программы Кнопки внизу экрана скрываются до наведения на них мышкой Кнопки хранятся как объект со своими функциями и свойствами внутри. И вкладываются в массив инструментария Пример работы: волны искажений Возможности: Навигация и/или зуммирование на колесо мыши Несколько типов анимации. (на кнопку включается анимация движения волн, остальные типы пишутся в шейдерах) Сохранение кадра Сохранение анимации Настройка "шага волны" и скорости течения времени кнопками (обязательно необходимо для построения кадра) Видеообзор: Готовые gif анимации: Рисунок электромагнитных полей: Волновые искажения: Цветные иллюзии: Иллюзия в том, что кружков (кроме двойного) нет. На этих участках случайно совпадают цвета Прочее: Обычные рисунки: Эксперименты над волнами: История версий: Кто-то тут может сказать: "а где ссылка на программу?" А её и не будет пока не реализую нормальную систему сохранений. Почему тогда я выкладываю недоработку? Потому что мне нужно рассказать о проекте для резюме. Это моя лучшая программа и я её люблю. Оттягивать нет смысла, первая версия разрабатывалась ещё полтора года назад. А значит до релиза четвёртой ждать ещё неизвестно сколько. Так что спасибо за внимание! Если вдруг кому-то тема визуализации так же интересна как и мне - пишите в ЛС. Было бы интересно обсудить.
  17. 3 балла
    Вероятно, многие хотели запустить несколько программ на одном компьютере (на OpenOS), чтобы они работали одновременно. Моя программа даёт возможность это сделать (программы будут выполняться не совсем одновременно, таково ограничение OpenComputers). Важное условие: в каждой выполняемой программе должен быть os.sleep, event.pull или computer.pullSignal, чтобы она передавала управление другой. Инструкция: 1. Скачайте программу: pastebin get wsJqngC5 parallel.lua 2. Запустите её. 3. Введите пути к программам, которые необходимо выполнить параллельно, через пробел (если программа одна, то введите её один раз). 4. Введите число, сколько раз должна быть запущена каждая программа из списка. 5. Задайте аргументы для каждой запускаемой программы. Если несколько программ должны работать параллельно, то почему бы не запустить их на разных мониторах? Это возможно: просто напишите в аргументы программы %GPU%. Эта строка автоматически заменится на адрес видеокарты. В программе могут встречаться баги и несовместимости с другими приложениями. Просьба сообщать, если таковые найдутся.
  18. 3 балла
    вместо redstone_changet должно быть redstone_changed
  19. 3 балла
    Отлично пашет, за полчаса ковыряния нашёл всего лишь два явных бага: Нет реакции на скроллинг мышью. В списке ивентов через dmesg не высвечивается Некорректно работает computer.maxEnergy(), возвращая math.huge вместо фикисированного числа. В нативных опенкомпах при отключенном поглощении энергии в конфиге (или при отсутствии энерго-модов) функция всегда возвращала 500 Еще было бы крайне приятно, если бы окошечки экранов можно было ресайзить, т.к. более 2х виртуальных экранов даже на WQHD-монитор не влезет. В идеале - вообще группировать их с изменением масштаба в автоматическом режиме плитками а-ля Snap Assist. Но это жир
  20. 3 балла
  21. 2 балла
    Вдруг решил я написать программу для управления компом через дискорд, но поскольку на Lua это не реализовано(по крайней мере здесь ничего подобного не было),я решил использоовать Python, да и тем более мне не взлюбился Lua,да и знаю я его слабо.Обращаю внимание на то,что код на стороне Lua это дичайший франкенштейн из разных гайдов и функций в интернете!Да и сторона питона то вообщем не славится отсутствием костылей) Код на Lua: Код на Python: Внимание! В client.run находится токен бота. global использовать было нежелательно,но это самое быстрое решение которое я нашел Перед использованием необходимо открыть порт(в данном случае 1337) В internet.open первым аргументом указываете свой айпи(там не мой айпи,пробить не пытайтесь) Инструкция по использованию: 1.Запустить код на Python(сервер) 2.Запустить код на Lua(клиент) В консоли где вы запускали питоновский код,должно появится сообщение "<адрес> connected" 3.В дискорд сервере куда вы пригласили бота написать /execute_lua <команда> и он выполнит команду на OpenOS и вернет вам то,что вывела эта команда (эта команда выполнится на компе OpenComputers) Скоро появятся новые фичи по типу выполнения кода на луа и видео с примером использования.
  22. 2 балла
    Интересное дополнение, хотя и спорное. Во-первых, проще было бы не втискиваться в рамки стандартной утилиты, а написать свою, специально предназначенную для записи на ленту и чтения с неё файлов с произвольными данными. И ориентироваться не по нулевому байту, а по размеру файла, заданному в заголовке. Во-вторых, непонятна применимость подобного подхода. Обычные жёсткие диски по причине их малого объёма не позволяют задействовать весь потенциал ленты. Думаю, полезной была бы архивная файловая система с ограниченными функциями: произвольное чтение любого из уже записанных на ленту файлов и дописывание в конец ленты новых файлов.
  23. 2 балла
    Надо скриншотов добавить и описание билда компа/робота, которые юзаются
  24. 2 балла
    на нашем сервере много пк, работать с дискетами работать очень не удобно, (я использую карту гринфилд), мы решили сделать "nas" системму, но дело в том что в "lua" мы полный ноль. работать должно так: пк по беспроводной сети подключается у серверу, (к серверу подключено 2 raid один с системмой а второй пустой), сервер дает доступ ко второму raid и на пк который запросил доступ открывается папка 2 рейд масива. права удаленных пк можно только записывать, скачивать. права сервера: удалять, записывать, скачивать.
  25. 2 балла
    @IS2511 я подозреваю что ява жрет все что в нее влезет, попробуй запускать с -Xmx256m. На машине с бесконечной памятью, ява в теории вообще никогда не будет мусор вывозить...
  26. 2 балла
    Ага. Начинает проясняться. Есть некая биржа, на которой можно продавать реальные либо игрушечные валюты. Операции с валютами можно поручить ботам (роботам), которые программируются на Lua. Наверное, где-то есть и руководство по написанию таких Lua-скриптов. Похоже, топикстартер решил поднять бабла. Вот только таланту не хватает не только на программирование, но и на толковое изложение собственных хотелок.
  27. 2 балла
    Иконку для десктопа не планируете? Смотрится неплохо) (favicon с сайта) Про вопросы: Зачем такая структура папок сохранения (save/opencomputers)? Для удобства перемещения в мир? Если да, то очень странно. Разве /home не существует изначально после установки? Мне казалось да, переменная окружения же есть $HOME Когда template'ы?) Очень хочется) Про хотелки: Честно говоря счетчик FPS в названии окна немного раздражает F3 debug screen?) (см. пункт 1) Настройки внутри приложения? Очень не хватает Создание новых дисков каждый раз раздражает (выбор в настройках?) Выбор сохранения майна (при указании .minecraft) или хотя бы выбор куда папки дисков класть Я так понимаю Ctrl + W прямо в движке заменили на Ctrl + E? Хотелось бы вынести это только на сайт, а в десктопе съедать Ctrl + W (чтобы система не закрывала программу) Крестик на окно компа? Немного неудобно, что приходится тянуться за ESC все время при редактировании сетапа мышкой Сеть только через реле? Добавить настройку "Вкл wifi" для адресной связи всех со всеми (может даже по блокам сетки расстояние брать)? [EDIT-1] Как и у ECS в посте сверху, ресайз окон со скэйлингом контента было бы неплохо, но лично я пока справляюсь на втором tier'е экранов и их влезает 4 штуки удобно, так что для меня это не критично (в отличии от ECS с полноценной графикой) [EDIT-1] Осторожно, имхо. Я пока не привык к управлению, но мне кажется странным иметь действие создания блоков на ЛКМ, может таки перенести создание на ПКМ? Просто кажется нелогичным [EDIT-1] Осторожно, имхо. Принудительный фокус хотя-бы на 1 окно позволяет быстро закрывать окна, но может добавить фокус на поле? Чтобы все экраны становились полупрозрачными и тд. Как раз для таких целей переназначить ЛКМ [EDIT-1] Осторожно, имхо. Затухание пунктов в выпадающем на ПКМ меня кажется очень долгим, возможно немного ускорить? Про баги: Всякие соединения при удалении не полностью чистятся, иногда остаются навечно провода ни к чему UUID дисков в программе не соответствует названиям папок (не очень удобно) Фокус/расфокус на окнах дисплеев дергает их рамку и небольшие визуальные глюки появляются в тексте дисплея Легкие проблемы блюра? [EDIT-1] Сомневаюсь что баг, но при соединении компов (в том числе через те же цвета реле) возникают крайне веселые визуальные глюки когда оба компа пытаются рисовать на 1 дисплей. В оригинале так же? [EDIT-1] Маленький, но все равно. Кабели можно поставить за компы, кабель пытается свалить, но не до конца. Писать алгоритмы поиска пути для кабеля звучит тупо, unless?) WIP Может еще что-то добавлю когда вспомню/найду Edit count: 1
  28. 2 балла
    Ну можно и так Увы, но заказчик зашёл на форум пару раз и больше не появляется, видимо оно ему не надо (программирование в том числе)
  29. 2 балла
    Теперь компы работают В РАЗЫ быстрее!!! Вам лишь нужно смазать процессор девятью граммами... Всем привет! Я так долго не заходил на форум, что забыл вас оповестить о новом законном способе ускорить работу компов в игре о котом я узнал. Так делать не нужно!!! Мой комп страдал, чтобы ваш не горевал. Суть вот в чём. В моде ProjectE есть такой замечательный инструмент как часы времени. Ничего особенного они не делают кроме ускорения цикла дня/ночи. НО!!! Если их поставить на пьедестал и активизировать ПКМ, то они судя по описанию немного ускорять животных поблизости и дадут дополнительные 20 тиков блокам в радиусе 3 блоков рядом. (образует куб 7x7x7 с пьедесталом в центре) И тут мы переходим к самому интересному!!! Часы работают на любых блоках: на растениях, блоках из мода и даже на блоках из самого же ProjectE, которые позволяют собирать и накапливать ECM (местную валюту) {из-за чего кстати я сломал аддон к этому моду и быстро прокачался до максимума ECM - просто посмотрите на скрин и всё поймёте} Очень имбовая вещь, ей можно генерировать много пассивной энергии и тратить её в супер быстрый карьер. А эффекты от часов поблизости складываются!!! Так вот, ради чего мы здесь собрались... судя по логике совместимости модов: робот из OpenComputers должен быть блоком, но для часов времени - он не блок и на него ничего не действует. Зато часы действуют на статичные блоки вроде системного блока, монитора и прочего. Что позволяет использовать компы на очень быстрых скоростях. Правда есть и минус. Внутренние часы компа ничего не подозревают и задержки через os.sleep будут так же ускорены как и ВСЯ работа компа. Зато это открывает возможности делать и запускать реальные игры в майнкрафте. Не заботясь о том, что что-то не успеет прогрузится. Вот такие чудеса творятся в мире ProjectE. И его явно никто добавлять на сервера не будет. Зато попробовать программы позапускать с ним можно и в одиночной игре. Может кто-то что-нибудь с этим придумает.
  30. 2 балла
    Поздравьте с тысячной публикацией!!!
  31. 2 балла
    Гайд по самой плате более чем достойный, однако начиная с пункта 4 он будет полностью понятен лишь человеку, уже знакомому с луа/опенкомпами и работавшему с платой. Новичков отпугнёт. Поясню претензию: Во-первых, какое, блин, ООП? В чтении/записи в сокет функционала кот наплакал, и реализовать его процедурным подходом можно в разы проще и доступнее для понимания. Ты гайд пишешь или состязаешься с читателем? Со стороны это выглядит так: а давайте-ка, детишки, напишем объектную систему для мульти-соединений с сервером по всем канонам. А потом... будем использовать лишь ОДНО во фронтенде! *ba dum tss* Во-вторых, "привычно" для кого, для Fingercomp'а? В луа не имеется общепринятых конвенций по реализации объектов. Метатаблицы и приватные члены - это, безусловно, интересный подход, однако тут я бы задал себе несколько вопросов в порядке убывания приоритетности: Будет ли он быстрее работать и расходовать меньше ресурсов? Нужен ли он для клиента, использующего только 1 соединение с сервером? Будет ли он проще в реализации? Будет ли он понятнее в виде гайда для людей, впервые работающих с интернет-платой и луа в целом? Имхо, ответом на каждый из них будет "нет". Вытекающий вопрос "зачем, господи?" висит мёртвым грузом...
  32. 2 балла
    Да, проверка "content-length" - обязательная штука с плохим интернетом. Я везде ее пихаю, иначе как в опеноси, приходится запускать скачивание несколько раз, а если загрузка происходит прямо из биоса, тогда может произойти котострофа.
  33. 2 балла
    Установщик не работает. Посмотрел код инсталлера, и увидел там вот этот код: local code = internet.request(url).read() Это ненадежно, поэтому у меня твой биос не установился. Установщик простой, и его можно сделать в одну команду: wget -f https://raw.githubusercontent.com/timas130/oc-simple-bios/master/code.min.lua && echo "Flashing..." && flash -q code.min.lua "Simple BIOS" && echo "Done!" Так же, нельзя вводить кириллицу. Биос падает, если ввести что-то не ASCII. Рекомендую использовать unicode.char, вместо string.char. И лучше, конечно, сделать вход в загрузчик по какой-то клавише, потому что загрузка спустя 5 секунд будет только раздражать. А так - загрузчик выглядит симпатично.
  34. 2 балла
    Установка pastebin get c1v8T9CK /lib/Telegram.lua; pastebin get 4Lh9ALEY /lib/Promise.lua;pastebin get ji28sbxU /lib/JSON.lua Создание бота Здесь все подробно обьясняют: https://tlgrm.ru/docs/bots#botfather. Документация Эта библиотека возвращает одну-единственную функцию - Telegram (в либе названа Bot, но будет телеграмом) Telegram(token: string) - создает обьект бота. Использует псевдо-ООП. Bot.API - таблица с единственным полем rawRequest(method: string, options: table): table - сырой запрос без оболочек. Имеет метатаблицу. Bot.API[method](options) - возвращает промис. Promise:next(onFulfilled:function) - установить функцию, принимающая значение result при успешном выполнении промиса. Promise:catch(onRejected:function) - установить функцию, принимающая значение error при ошибке промиса. Promise:await() - ждет завершения промиса и возвращает результат с учетом всех его обработчиков. Bot.longpoll on(event: string,callback: function) - установить обработчик ивента. Весь список - message, edited_message, channel_post, edited_channel_post, inline_query, chosen_inline_result, callback_query, shipping_query, pre_checkout_query start() - запустить лонгполл. Программа блокируется. stop() - остановить. Возможно, библиотека будет не очень хорошо работать, но я тупо не могу ее адекватно протестировать - вебпрокси (закомментированная 7я строка) перестал работать (только для меня xD) P.s. если у вас тоже выбивает ошибку на 30й строке - напишите. component.internet.request тупо льет nil вместо ошибки, я пока полагаю, что это связано с блокировками.
  35. 2 балла
  36. 1 балл
    Для тех, кто спешит: https://ocelot.fomalhaut.me/ На форуме давно мелькают упоминания Ocelot. Это эмулятор OpenComputers, который находится в разработке примерно с 2015 года, был несколько раз переписан и наконец увидел свет в закрытом альфа-тесте зимой 2018. Я немного отвлекся на другие проекты (привет Stem), но теперь возвращаюсь к разработке Ocelot, и с гордостью предствляю вам тизер-анонс и, по совместительству, открытый альфа-тест Ocelot. Ещё один эмулятор? Да. Будем честны. Нормального эмулятора OpenComputers не существует. Те что есть - полны костылей, не совсем соответствуют реальному моду, сложны в установке, заброшены... и так далее. Ocelot - это решение всех этих проблем. Основная идея Ocelot - взять уже существующий код мода OpenComputers, тщательно отделить всё не нужное (Майнкрафт), затем осторожно переписать то что получилось с поправкой на реалии эмулятора. Благодаря этому, Ocelot эмулирует OpenComputers с ранее невиданной точностью. Вплоть до того, что в эмуляторе могут встречаться те же самые баги, что и в моде. Что он умеет? Практически всё. В перспективе. Ocelot позволяет воссоздать схему любой сложности из любого количества блоков - мониторов, компьютеров (любой конфигурации), проводов, модемов и прочих компонентов. Он позволяет управлять скоростью работы компьютеров, позволяет изменять "игровое" время, ставить его на паузу, сохранять состояние работы компьютеров и потом возобновлять работу с любого сохранения. Сейчас доступен базовый набор компонентов и блоков. Это кабель, корпус компьютера, APU/CPU, плашки памяти, видеокарты, дата-карты, EEPROM, дискеты, жесткие диски (managed и unmanaged режимов), интернет-карта, линкед-карта, сетевая карта (проводная и безпроводная), редстоун-карта / блок и монитор. Список будет расширяться. В перспективе будет эмуляция всех блоков и компонентов стандартного OC, роботов, дронов, микроконтроллеров, серверных стоек, плюс эмуляция адаптера и интеграции с ванильными блоками и блоками других модов. Что можно потрогать? Ocelot задуман как модульный проект. А именно: Ocelot Brain Основа эмулятора - это библиотека Ocelot Brain. Она написана на Scala и может быть подключена к любому другому проекта на Scala (и, может быть, Java). Ocelot Brain - это как раз переработанный код OpenComputers в компактной и удобной форме. Отвечает за всю эмуляцию кода и компонентов, а также сохранение / загрузку проектов. Вы можете использовать его для своих проектов, можете помочь с разработкой и патчами. Проект открыт и доступен по адресу: https://gitlab.com/cc-ru/ocelot/ocelot-brain На данный момент Ocelot Brain актуален версии OpenComputers 1.7.4. Ocelot Online На основе проекта Ocelot Brain, в качестве демонстрации его возможностей, создается проект Ocelot Online. Ocelot Online это эмулятор OpenComputers в виде сайта. Да. Всё что вам нужно для его запуска - это открыть сайт. Ссылка: https://ocelot.fomalhaut.me/ Исходный код тоже доступен: https://gitlab.com/cc-ru/ocelot/ocelot-online Поскольку проект пока находится в альфа-релизе, большая часть возможностей закрыта. Доступен только один монитор на всех, который позволяет взаимодействовать с уже настроенным демо-проектом. Конфигурация проекта: Креативный корпус, CPU T3, видеокарта T3, две планки памяти T3.5, managed жесткий диск T3, unmanaged жёсткий T3, интернет карта, редстоун карта T2, дисковод с дискетой Open OS, монитор T2, клавиатура и EEPROM с Advanced Loader от товарища Luca_S. Отличия от стандартного OpenComputers: * В OpenOS уже установлен HPM. Благодаря этому можно быстро ставить разные программы через hpm install. * Вставка текста заменена с Insert на Ctrl + V. Браузер не дает изменить этот хоткей. * В редакторе edit кнопка выхода заменена на Ctrl + E. Стандартная комбинация юзается браузером для закрытия вкладок - и переопределить её нельзя по соображениям безопасности. * Иногда не печатаются стек-трейсы. Это последствия одного фикса против одного вредного эксплойта. Проблема уже сообщена разработчикам OC. Как только нормальный патч появится в OpenComputers - я пропатчу и Ocelot. * Вместо OpenOS EEPROM используется Advanced Loader. Это сделано для удобства и наглядности. * Не работает лок на пользователя - по понятным причинам. Ocelot Online должен так же работать на смартфонах. Однако возможно придется отключить T9 - он портит эвенты клавиатуры. В разработке находится более сложная версия, где все получат возможность зарегистрировать аккаунт и создавать личные проекты любой конфигурации. Но это дело будущего. Ocelot Desktop Это классический вариант эмулятора Ocelot в виде программы, которую можно скачать и запустить на любой операционной системе, где есть Java. Построен на Ocelot Brain и библиотеке LWJGL (как и сам майнкрафт). Разработкой занимается товарищ @LeshaInc. Я не буду спойлерить и рассказывать про его проект - если он захочет, сам расскажет. Альфа-тест Итак, дорогие пользователи, пишите ваши хотелки, сообщайте о багах, обо всем что работает не так как должно, и как в оригинальном OC. Я, со своей стороны, постараюсь проект не забрасывать, развивать и своевременно (или не очень) обновлять. Благодарности Над проектом также работали: @LeshaInc, @Laine_prikol, @Fingercomp и @MeXaN1cK. За что им огромное спасибо и респект. Не забудем также всех, кто помогал с альфа-тестированием, Сангара - за чудесный мод, и мейнтейнеров OpenComputers за то что его не забросили (всё ещё ждем от них фикс). Enjoy!
  37. 1 балл
    ок код выполняется построчно, а так как функции теперь локальные а не глобальные, у верхней функции нет доступа к нижней также можно завернуть весь код в одну функцию, так будет проще ориентироваться в коде, функции обычно выносят при частом обращении из разных мест в коде, чтоб не повторять её по 100 раз
  38. 1 балл
    В Java нет утечек памяти (исключение составляют реально банальные случаи), есть только упоротый бомж сборщик мусора, который когда надо - не собирает, а когда не надо - начинает собирать и жутко лагать
  39. 1 балл
    Я подозреваю, что где-то утечка памяти @LeshaInc Открыто уже несколько суток наверное Если надо конфиг:
  40. 1 балл
    GradleWrapper предназначен для того, чтобы сделать воркспейсы более переносимыми. Не факт, что твоя глобальная версия Gradle правильно все сделает по билд-скрипту. С другой стороны враппер позволяет всем разработчикам проекта иметь идентичный воркспейс, если заработало(или не заработало) у одного, то аналогично будет у всех. Т.к. скорее всего такой алгоритм будет зависеть от аспектов игры(типо, тпс, отгрузка чанков), то скорее всего понадобится вносить изменения в код самого мома, смотреть исходники игры. Поэтому будет хорошей идей попробовать работать в идее(), она довольно хорошо заточена под скалу
  41. 1 балл
    Много существует этих способов. Например, для экспериментов в креативе я использую TickrateChanger. Очень помогает ускорить отладку программ, работающих с объектами мира. Но есть нюансы. Для достижения высокого TPS требуется иметь быстрый процессор. Если реальный комп не справляется с нагрузкой, то часть тиков пропускается, причём, в неуправляемом порядке. Недавно разработчики OpenComputers слегка модифицировали алгоритм работы GPU. Это тоже позволяет сильно увеличить FPS на экранах без необходимости поднимать TPS. Можешь посмотреть в экспериментальных сборках OpenComputers.
  42. 1 балл
    Эх, вот бы сюда сочный protected для подклассов... И костыляешь что хочешь, и либа целёхонькая
  43. 1 балл
    О, тогда уважение. Терпению хардвейщиков поём мы песню
  44. 1 балл
    Разработка десктопной версии продолжается Демо
  45. 1 балл
    Сделал простенькую читалку для файлов GIF, поддерживает весь функционал со спецификации: слои/кадры, прозрачность, текст, комментарии, расширение NETSCAPE2.0 и прочее. Но не все доп. расширения были найдены, потому реализовал только описанные в интернете. Небольшая предыстория: В общем говоря, захотел я себе как-то анимированный "телек" для дома, в качестве украшения. С отображением были конечно проблемы - голо-проектор не вмещал больше 3х цветов в палитре, но это решалось количеством. Второй проблемой встала сама картинка - пнгшки кадрами собирать было лень, да ещё и в индексы надо переводить все цвета... Подождите-ка индексы, анимация, встроенная палитра... Это-же GIF! Эврика подумал я... Пока не увидел реализацию, а потом меня поглотила пучина гифа... При первом знакомстве GIF показался простым, как пробка, форматом (хотя, так и есть). Но сжатие изображения в LZW немного остудило мой пыл. Мне довольно трудно понимать работу хитроскрученных алгоритмов сжатия, но всё оказалось не так плачевно, и вооружившись методом проб и ошибок, я начал познавать это.. что-то. В конце концов, подняв несколько хабро-статеек, спецификацию, форумы покрывшиеся пылью, я начал открывать другую сторону GIF - она идеально подходит под реалии OpenComputers: 256 цветов, неплохое сжатие, встроенный текст. Всё это приправлено расширениями в виде анимаций, прозрачности слоёв, и разными дополнительными блоками. В конце-концов, этот формат может стать аналогом флеша в истории опенкомпов! Да здравствует новая эра, эра флеш-игр, когда-то покорившая интернет! В итоге всех скитаний по интернетам, мне удалось сделать более-менее полную читалку, хотя некоторые расширения потерялись в могилах истории, и я не решился пока их выкапывать. Саму читалку можно скачать для двух версий Lua: 5.2: wget -f https://github.com/Xytabich/GIF-Lua/raw/master/5.2/gif.lua /lib/gif.lua 5.3: wget -f https://github.com/Xytabich/GIF-Lua/raw/master/5.3/gif.lua /lib/gif.lua Различия между ними несущественны - Lua 5.3 использует встроенные битовые операции, а в Lua 5.2 используется библиотека bit32, соответственно. Функционал читалки: -- Сруктуры -- Заголовок GIF - базовая информация гифки. - width:number - ширина гифки - height:number - высота гифки - colorBits:number - кол-во бит, используемое для цветовой палитры. Устарело, но добавил на всякий. - bgIndex:number - индекс фонового цвета - aspectRatio:number - соотношение сторон, считается как: ширина/высота. Тоже устарело, но мало-ли. - colorsCount:number? - количество цветов в глобальной палитре - colors:number[]? - глобальная палитра цветов 0xRRGGBB, индексация начинается с 0 - extensions:table - список расширений гифки Блок изображения (image) - информация о слое/кадре гифки. Содерижит полное изображение, или только его часть. - x:number - отступ от левого края - y:number - отступ от верхнего края - width:number - ширина изображения - height:number - высота изображения - interlaced:bool - построчная или чересстрочная развертка - colorsCount:number? - количество цветов в локальной палитре - colors:number[]? - локальная палитра цветов 0xRRGGBB, индексация начинается с 0 - pixels:string - пиксели изображения, массив байт 0-255. Пиксель является индексом в локальной/глобальной палитре, и записаны в виде линий, друг за другом сверху-вниз. Порядок линий зависит от interlaced. - extension:table? - расширение изображения, параметры анимации и прозрачности - dispMethod:number - метод очистки экрана перед отображением: 1 - не очищается, 2 - закрасить фоновым цветом, 3 - восстановить состояние перед предыдущим кадром - delay:number - время до следующего кадра, в секундах - inputFlag:bool - ожидать ли ввода пользователя для продолжения отображения - transparentIndex:number - индекс цвета в палитре, использующегося в качестве прозрачного Блок текста (text) - отображает текст над изображением. По спецификации символы 7-bit ASCII, но ничто не мешает засунуть туда utf-8. - x:number - отступ от левого края - y:number - отступ от верхнего края - width:number - ширина текстовой области - height:number - высота текстовой области - charWidth:number - ширина символа - charHeight:number - высота символа - fgIndex:number - цвет символа - bgIndex:number - цвет фона символа - text:string - собственно, сам текст Блок комментария (comment) - произвольная текстовая запись, не влияющая на отображение. По спецификации так-же символы 7-bit ASCII, но и юникод спокойно помещается. Расширение гифки: NETSCAPE2.0 - информация о количестве циклов анимации. - iterations:number - количество итераций цикла (0 - бесконечное) - loop:bool - бесконечный ли цикл -- Функции -- gif.read(stream[, pos=0]):table -- читает весь файл. -- stream - поток данных, например - полученный через filesystem.open(...) -- возвращает заголовок с дополнительным полем - blocks:table {type:string, block:object}. В этом поле содержится список всех блоков в порядке чтения. gif.images(stream[, pos=0]):head, image -- итератор, последовательно считывает изображения. -- stream - поток данных, например - полученный через filesystem.open(...) gif.blocks(stream[, pos=0]):head, type, block -- итератор, последовательно считывает все блоки. -- stream - поток данных, например - полученный через filesystem.open(...) Особо сильно оптимизировать не стал, да и не знаю как, так что если есть предложения - выслушаю и внесу поправки. Один из вариантов ускорения чтения - сделать буферизированный поток данных. Код и тестовые изображения доступны в репозитории на github. В чересстрочной развёртке линии следуют не по прямому порядку, подробнее в выписке из спецификации: Пример приведу на тестовом коде, для проектора на картинке выше. Этот код просто рисует файл, так что настроить пересечение областей проекторов нужно вручную, или использовать один. Для OpenComputers этот формат имеет хорошие возможности, как для простого хранения сжатого изображения, или даже составного изображения из различных слоёв, так и для продвинутых действий: презентаций, анимаций, картинок с подписями. А если совсем заморочиться, то можно сделать свой Anone Gif Player, используя комментарии для записи кода, или собственные расширения приложения. По крайней мере, кинетические новелки возможно сделать и в стоковом варианте. История изменений:
  46. 1 балл
    Отличный гайд, премного благодарен!
  47. 1 балл
    Последняя версия : 2.1 Команда для установки : pastebin run ngQT9YF8 Системные требования: Корпус компьютера, екран - 1 Tier Процесор - 1 Tier Видеокарта - 1 Tier Память - 1.5 Tier (x1) Жёрсткий диск - 1 Tier Дисковод, клавиатура, Lua BIOS - должны присутствовать Краткое описание: Inerpat - примитивная система для запуска на любом ведре, и инструмент для создание и отладки EEPROM кода. Также она поможет в ситуации, когда ваш компьютер превратился в "обожённый строительный материал" Специальные клавиши: ALT - Открыть главное меню CTRL - Открыть меню файла/папки. В редакторе - меню сохранения. TAB - Навигация по доступным файловым системам Скриншоты:
  48. 1 балл
    Читал я в очередной раз форум, и нашёл тему "Цитадель". Я давно хотел посмотреть, что да как в ОС, поэтому я создал новый мир на своей сборке (в конце кину), подстроил правила под себя, построил бункер... И ушёл писать копалку. По правилам: Даны компоненты, а не целые компы/дроны/роботы; Внутри бункера можно делать что угодно (даже расширять, но только вниз); Под компом - креативная батарейка из thermal expansion; Энергию от креативной батарейки нельзя никуда подводить! Рядом с единственным зарядником - waypoint для нахождения пути к базе. Цель - сделать систем автоматического создания роботов для добычи большинства ресурсов. Я не знаю, на сколько меня хватит, но я надеюсь на хотя-бы 7 записей. Следующая запись будет, когда я допишу копалку, а пока всё!
  49. 1 балл
    Пусть роботы сами начнут играть в майнкрафт, без контроля человека, как у Крутого.=d Пусть сами познают майн, а какой-нибудь один из роботов сделает такой же форум, где роботы будут ныть, что строить или копать больше нечего
  50. 1 балл
    Накодил для себя монитор реактора из Extreme Reactors с поддержкой МЭ системы и хранилища из EnderIO Прога с простым интерфейсом и крупным текстом, без всяких графических штучек, текст легко читается с экрана с большого расстояния Что умеет: Вывод имени реактора (Если несколько реакторов то делаете несколько таких мониторов, и по имени реакторы будет проще найти) Расчет времени работы на остатке топлива (Если включена поддержка МЭ то расчет ведется с учетом йеллоурита в МЭ системе) Режим хранения (В этом режиме программа будет поддерживать ВЫКЛЮЧЕННОЕ состояние реактора) Время последнего включения реактора Вывод мощности реактора, вывод остатка топлива в МЭ и в самом реакторе, вывод запаса энергии в батарее EnderIO Поддержание нужного уровня заряда батареи реактора Что не умеет (Но возможно будет добавлено): Не поддерживается несколько экранов Не поддерживаются несколько реакторов Не поддерживаются турбины Нет управления по модему Проблемы: На 1.12.2 накопитель из EnderIO не считывается адаптером как мультиблочное хранилище, поэтому выводится информация только об одном блоке накопителя (На 1.7.10 все работает) Конфиг: reactor_Control = true, -- Разрешить управление реактором reactor_Storage_Mode = false, -- Режим хранения реактора, программа всегда будет поддерживать ВЫКЛЮЧЕННОЕ состояние ME_Storage_Support = false, -- Поддержка внешнего хранилища (Расчет остатка времени работы с учетом топлива в хранилище, и вывод топлива в хранилище на экран) EIO_Capacitor_Support = false, -- Поддержка EnderIO (Вывод запаса энергии на экран) reactor_Percent_Off = 100, -- Процент заполнения батареи реактора при котором он автоматически выключится reactor_Percent_Hysteresis = 5, -- Гистерезис заполнения батареи реактора reactor_Name = 'Nexus-6', -- Имя реактора Установка (Версия для Extreme Reactors): wget https://raw.githubusercontent.com/Koteyk0o/Lua/master/reactor_Display.lua /home/reactor_Display.lua Установка (Версия для Big Reactors, обновлений нет и не будет): wget https://raw.githubusercontent.com/Koteyk0o/Lua/master/reactor_Display.lua /home/reactor_Display.lua
Эта таблица лидеров рассчитана в Москва/GMT+03:00
×
×
  • Создать...