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

Лидеры


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

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

  1. 12 баллов
    Предисловие: Недавно, по накурке, мне пришла идея написать браузер для OC. Браузер это конечно хорошо, но для его работы нужна сеть. Первое, что вспомнилось, это OpenNet. Но у него нашлось некоторое некоторое количество серьёзных недостатков(про них позже). Не найдя альтернативы, я пришёл к выводу, что нужно сделать новую сеть, которая будет похожа на OpenNet, но в которой не будет таких недостатков, какие есть в OpenNet. Собственно, о каких недостатках я говорю: 1) Ограниченность в рамках одного сервера. Для того что бы от сети был хоть какой то минимум пользы, ей нужны пользователи. Тяжело найти сервер, где хотя бы пяти людям будет интересна тематика OC. 2) Требования к структуре сети. Три уровня, не больше, не меньше. Первые два - роутеры, третий - клиенты. Роутеры первого уровня соединены с роутерами второго посредством спаренных карт, и с клиентами только через модемы. 3) Низкая надёжность. Стоит из роутера второго уровня вынуть спаренную карту, он сразу превратится в кирпич. 4) Сложность в развёртывании. Два вида роутеров, три вида библиотек. Отсутствие документации. Захотел сеть на даче - нужно участие владельца "коренных" роутеров. Ключевые особенности: Собственно сеть должна обладать такими свойствами: 1) Возможность бесшовно соединять несколько игровых серверов. 2) Иметь максимально простую структуру. 3) При разрыве связи между двумя частями сети, сеть должна "раскалываться" на две, а не ломаться. 4) Сеть должна иметь максимально простую структуру, и иметь хорошую документацию Процесс разработки: Обозначив основные моменты, я потихоньку начал писать сеть. За основу брал OpenNet. С кодом можно ознакомится на складе грязи(тут сама сеть, и ещё несколько программ, которые я использую при разработке). Что сделано: Собственно, сама библиотека, реализующая связь между узлами. Роутер. Библиотека, позволяющая работать в сети программам, заточенным под OpenNet. Браузер и HTTP-сервер, из за которого собственно, всё и началось. Вернее первая его версия. Теоретически(на практике мост, который я хочу использовать, себя странно ведёт) есть возможность устанавливать соединения между игровыми серверами. Что нужно сделать: DNS Интернет-сервер, и клиент под него Чат (клиент, сервер) Нормальный интерфейс. Инсталлер Шифрование пакетов Как это всё безобразие выглядит: И так, допустим, мне очень нужно развернуть сеть. Что мне для этого нужно: Ставим три компьютера. Один будет роутером, и два клиента. Пусть один клиент подключается к роутеру через модем, а второй через спаренную карту. Начинка роутера: И клиентов: Теперь ставим на все компьютеры OpenOS и файлы с репозитория. Можно воспользоваться инсталятором: pastebin run vVrgPcf3 Начнём с настройки роутера. Запускаем "routconf", и первым делом конфигуратор попросит указать, через какую карту роутер будет подключатся к сети. Так как сети у нас пока нет, пропускаем этот момент. Теперь у нас спрашивают, какие карты будут использоваться для подключения клиентов к роутеру. Отвечаем: Теперь можно и запустить роутер. Пишем "router", и видим картину: Роутер ругнулся, что не имеет выхода в сеть, ну оно и понятно. Теперь настраиваем клиентов, вводим "rnconfig" на каждом из них: Сеть настроена, но что теперь с ней делать? Запускаем на одном компьютере "chat_server", на другом "chat <ip сервера>" . И можем поговорить сами с собой. Или можно посмотреть на браузер. Поднимаем сервер, "webserver", браузер "wr" и наслаждаемся. К стати, сеть называться RacoonNet(над названием я не заморачивался). Собственно, зачем эта тема: Т.к. изначально я собирался сделать только браузер, и вообще, это мой первый проект на Lua, я сам долго не выдержу. Понимаю что сеть сейчас практически является копией OpenNet`а. Я ищу людей, которые захотят помочь мне. Как то так.
  2. 7 баллов
    Хах, г-дин @Totoro не упомянул главный аспект браузера, что я показывал Да он может работать либо по специальной локальной сети внутри мира либо по HTTP(S), НО Браузер делается НЕ ПОД СТАНДАРТ HTML, т.е. смотреть на нем обычный интернет не выйдет Еще в начале возни со всем этим я понял, что HTML просто не осилю и будет история как с Арбузером Поэтому он будет просматривать спец страницы формата NFP (Network Formatted Page), которые сильно упрощены в синтаксисе Впринципе, в будущем можно будет сделать мост, на ходу делающий NFP из HTML и передающий его браузеру, но стандарт еще не закончен, как и браузер. Сейчас помимо текста, есть гиперссылки, возможность качать файлы (сыроватая) и поддержка OCIF картинок Отрисовка на DoubleBuffering-е, поэтому в целом браузер не слишком прожорлив (после открытия страницы с тремя немаленькими картинками свободно 1мб из 2), но лучше иметь не меньше 1.5мб На данный момент надо немного переделать код (кое-где стоят грубые костыли) и добавить еще функционала в формат (можете предлагать) Сам браузер пока выглядит так: Код страницы на экране вот так: Не знаю, оценит ли кто такой "DarkNet" для OC, но почему бы и нет)))
  3. 5 баллов
    Для тех, кто просил скриншоты: Ramp #1 (сбоку, спереди и сзади) Kitchen Table Открывающийся ящик (открытый и закрытый) Модельки конечно не огонь, но для новичка, вроде, неплохо.
  4. 3 балла
    Ошибка "no such component" всегда возникает при попытке доступа к proxy компонента, записанному в переменную, если по какой-то причине сам компонент был физически отсоединён. А вот почему он отсоединился - фиг знает: либо сервак пролагал, либо с выгружающимися чанками проблема, либо возникает коллизия UUID'шников компонента (что маловероятно), либо вообще админ/сожитель своими кривыми ручонками пошарился. Исправить ситуацию можно путём периодического обновления proxy на актуальный через computer.uptime В любом случае ставлю последний счетверённый МОХ, что 108 строка ошибки просто "сдвинулась" в трейсе на единицу, и isAvailable тут ни при чём. Пруф:
  5. 3 балла
    component.drone.move(...) возвращает управление в программу, не дожидаясь завершения движения. Приближение к целевым координатам контролируется с помощью component.drone.getOffset().
  6. 3 балла
    рампа { label = "Ramp #1", tooltip = "Simple ramp, half a block.", emitRedstone = false, buttonMode = false, shapes = { { 0, 0, 0, 16, 1, 16, texture = "planks_oak" }, { 1, 1, 0, 16, 2, 16, texture = "planks_oak" }, { 2, 2, 0, 16, 3, 16, texture = "planks_oak" }, { 3, 3, 0, 16, 4, 16, texture = "planks_oak" }, { 4, 4, 0, 16, 5, 16, texture = "planks_oak" }, { 5, 5, 0, 16, 6, 16, texture = "planks_oak" }, { 6, 6, 0, 16, 7, 16, texture = "planks_oak" }, { 7, 7, 0, 16, 8, 16, texture = "planks_oak" }, { 8, 8, 0, 16, 9, 16, texture = "planks_oak" }, { 9, 9, 0, 16, 10, 16, texture = "planks_oak" }, { 10, 10, 0, 16, 11, 16, texture = "planks_oak" }, { 11, 11, 0, 16, 12, 16, texture = "planks_oak" }, { 12, 12, 0, 16, 13, 16, texture = "planks_oak" }, { 13, 13, 0, 16, 14, 16, texture = "planks_oak" }, { 14, 14, 0, 16, 15, 16, texture = "planks_oak" }, { 15, 15, 0, 16, 16, 16, texture = "planks_oak" } } } стол { label = "Kitchen Table", tooltip = "Simple kitchen table with white quartz top.", emitRedstone = false, buttonMode = false, shapes = { { 0, 0, 0, 1, 14, 1, texture = "planks_oak" }, { 0, 0, 15, 1, 14, 16, texture = "planks_oak" }, { 15, 0, 0, 16, 14, 1, texture = "planks_oak" }, { 15, 0, 15, 16, 14, 16, texture = "planks_oak" }, { 0, 14, 0, 16, 16, 16, texture = "quartz_block_side" } } } коробка которая откривается с боку { shapes = { [1] = { [1] = 0, [2] = 0, [3] = 0, [4] = 16, [5] = 16, [6] = 1, texture = "planks_oak" }, [2] = { [1] = 0, [2] = 0, [3] = 0, [4] = 1, [5] = 16, [6] = 16, texture = "planks_oak" }, [3] = { [1] = 1, [2] = 0, [3] = 15, [4] = 15, [5] = 16, [6] = 16, texture = "planks_oak" }, [4] = { [1] = 15, [2] = 0, [3] = 0, [4] = 16, [5] = 16, [6] = 16, texture = "planks_oak" }, [5] = { [1] = 0, [2] = 0, [3] = 0, [4] = 16, [5] = 1, [6] = 16, texture = "planks_oak" }, [6] = { [1] = 0, [2] = 15, [3] = 0, [4] = 16, [5] = 16, [6] = 16, texture = "planks_oak" }, [7] = { [1] = 0, [2] = 0, [3] = 0, [4] = 16, [5] = 16, [6] = 1, state = true, texture = "planks_oak" }, [8] = { [1] = 0, [2] = 0, [3] = 0, [4] = 1, [5] = 16, [6] = 16, state = true, texture = "planks_oak" }, [9] = { [1] = 1, [2] = 15, [3] = 0, [4] = 16, [5] = 16, [6] = 16, state = true, texture = "planks_oak" }, [10] = { [1] = 15, [2] = 0, [3] = 0, [4] = 16, [5] = 15, [6] = 16, state = true, texture = "planks_oak" }, [11] = { [1] = 0, [2] = 0, [3] = 0, [4] = 16, [5] = 1, [6] = 16, state = true, texture = "planks_oak" } }, emitRedstone = false, collidable = { [1] = true, [2] = true } } я начинающий пж пролайкайте)
  7. 3 балла
    Верно, зачем нам хард, и без него консолька грузится Извините за флуд, не удержался...
  8. 2 балла
    Предположим, Вы написали крутую программу. И она размазана, скажем на 10 файлов. Или нет, лучше на 50. И Вам захотелось сделать инсталятор. Первая мысль - вручную заливать все файлы на pastebin, потом писать инсталятор, в нём же вручную прописывать коды файлов на pastebin, и их целевые адреса в системе. Это предложение даже читать больно, не то что совершать описанное в нём действие. Но, выход есть. И так, по пунктам: 1) Заливаем программу на гитхаб. Нам нужно запомнить два значения, это репозиторий(1) и ветка(2) 2) Качаем вот этот файлик. Или копируем код: local shell = require("shell") local comp = require("computer") local ser = require("serialization") local inet = require("component").internet local fs = require("filesystem") local ref = "master" local repo = "" local deploy_path = "/" 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 or 3 )then handle.close() return nil, "request failed: connection timed out" end os.sleep(0.05) end return handle end function tablelength(T) local count = 0 for _ in pairs(T) do count = count + 1 end return count end function get_files(path) local handle, err = request(path) local body = {} 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 end read = read + #chunk table.insert(body, chunk) end body = table.concat(body) local types = {} local paths = {} local urls = {} local durls = {} for str in string.gmatch(body, "\"type\":\"[^\"]*\",") do table.insert(types, str:sub(9,-3)) end for str in string.gmatch(body, "\"path\":\"[^\"]*\",") do table.insert(paths, str:sub(9,-3)) end for str in string.gmatch(body, "\"url\":\"[^\"]*\",") do table.insert(urls, str:sub(8,-3)) end for str in string.gmatch(body, "\"download_url\":\"[^\"]*\",") do table.insert(durls, str:sub(17,-3)) end for i=1,tablelength(types) do if types[i] == "file" then if not fs.exists(deploy_path..paths[i]:match(".*/")) then fs.makeDirectory(deploy_path..paths[i]:match(".*/")) end shell.execute("wget -f "..durls[i].." "..deploy_path..paths[i]) else get_files(urls[i]) end end end get_files("https://api.github.com/repos/"..repo.."/contents/?ref="..ref) 3) Заполняем его. Нужно заполнить три поля. repo - репозиторий, ref - название ветки(по умолчанию master) или тэга, откуда мы будем скачивать. deploy_path - путь, куда будет скачано содержимое репозитория. На этом инсталер готов.
  9. 2 балла
    pastebin get iKzRve2g lib/forms.lua Конечно, браузер на такой библиотеке не построить, но для небольших приложений может сгодится. Библиотека позволяет создавать следующие визуальные компоненты: Form (форма) - является контейнером для остальных компонентов. Служит для быстрой смены экранного интерфейса без необходимости удалять и создавать компоненты. Button (кнопка) - тут всё понятно. Label (метка) - просто строка текста. Edit (окно ввода) - позволяет вводить текст. Frame (рамка) - просто рамка. List (список) - отображается в виде набора строк. Каждой строке ставится в соответствие элемент, способный хранить любое lua-значение (число, строку, таблицу и т.д.). Пользователь может прокручивать список, выбирать один из элементов, производить сортировку списка. И невизуальные компоненты: Event (событие) - обрабатывает системные события. Timer (таймер) - позволяет вызывать функцию-обработчик с заданным периодом. Пример работы с библиотекой: Попробовать библиотеку в действии можно в дата-центре на компьютере с координатами 10150, 14199. Для этого необходимо запустить файл form.lua
  10. 2 балла
    Очередной никому не нужный мост Библиотека для Kotlin/Java/Pythonскоро (я не уверен, что он вообще будет) с клиентом OpenComputers для интеграции в ваше приложение. Мост нестандартный. Потому что может это всё: Выполнение существующих функций на удаленном устройстве OpenComputers (без компиляции, быстрый способ) Выполнение произвольного кода на удаленном устройстве OpenComputers (с компиляцией, медленный способ) Можно комбинировать выполнение функций и кода в одном запросе, передавая в аргументы результаты предыдущих выполнений. Получение ивентов с фильтром (например, по умолчанию игнорируются множество ивентов пользовательского ввода) Возможность отправки любых сообщений через протокол из, например, event.listen (что крайне не рекомендуется, но возможность есть) Соединение с клиентом будет разорвано автоматически максимум через 10 секунд, если компьютер перестал функционировать (например, выгрузился чанк или отключился сервер), а соединение не разорвалось. И все это с условно-неограниченным количеством подключенных клиентов - лишь бы хватило ОЗУ, производительности ЦПУ и ОС хоста поддерживала что-то получше select (у него есть ограничение в 1024 сокета). Так же поддерживается аутентификация, но не поддерживается шифрование. Оно было бы возможно (в простом варианте без кучи кода и криптографии), если бы мод поддерживал вебсокеты, но пока такая возможность отсутствует. Основная цель - хранение кода подальше от опенкомпов, желательно у себя дома. Так же вы всегда знаете, подключен ли компьютер или нет. Минусы: Требуется статический белый айпи (хотя для написания промежуточного сервера нужно переписать минимум кода благодаря использованию селекторов, я пока не стал этого делать) Из-за того, что изначально OpenComputers не имеет кода для выполнения, немного снижена производительность. Присутствует Javadoc. Пример кода на стороне сервера (Kotlin) Более полный пример (тоже котлин) Репозиторий - в README есть описание работы, а в самом репозитории готовый клиент на moonscript. Да-да, я знаю, что в Lua нет компиляции, но судя по скорости это именно она и происходит.
  11. 2 балла
    Добавил аварийное завершение программы с выключением реактора в случае если что-то пошло не так например: реактор перестал отвечать на запросы ОС, не важно по какой причине (лаг сервера, сосед помог, что угодно) сундук с компонентами свинтили или управляющий блок и т.д. спасибо @ZO125 за идею в этому посту основной пост этой проги программа контроля реактора IC2(1.7.10) с конденсаторами: pastebin get kfRBNS0w r программа контроля реактора IC2(1.12.2) с конденсаторами: pastebin get nneD0Mv5 r
  12. 2 балла
    Самый простой способ создать тег - нажать на кнопку "Create a new release" После заполнения описания, последний коммит в текущей ветке будет отмечен меткой Твой код к ней можно в любой момент "перемотать" в специальном окошке: А еще к релизу будет автоматически добавлен архив с исходниками: --- Вообще, очень советую научиться активно использовать Git в разработке, делая коммиты постоянно, а не выкидывая кучу кода прям перед новым релизом - это выведет разработку на качественно новый уровень, сделает ее удобнее, и, что самое главное, позволит людям работать совместно с тобой - никто не будет рисковать делать какой-то пул-реквест, когда ты, условно говоря, в любой момент можешь принести коммит на 10 тысяч строк и все стереть. Делай небольшие коммиты с осмысленными названиями - поможет отслеживать прогресс и откатывать изменения, если что пойдет не так
  13. 2 балла
    У тебя опечатка в команде --- В общем, идея мне нравится - довольно стройная (как минимум в теории) архитектура с тремя уровнями устройств, связь между устройствами посредством различного оборудования благодаря унифицированному интерфейсу (что я делал-делал в своем OCNS, да не доделал), браузер, и так далее. Поэтому я решил даже немного почитать и поревьюить твой код - а он достаточно приличный, тем более для первого проекта Конечно, недочеты есть, и все, что я нашел, я отметил в твоем коде, который специально для этих целей форкнул: https://github.com/AtomicScience/racoon-dev Просто поищи по коду по меткам "REV:" Также не забудь посмотреть файлик "Общие замечания" в корне репозитория. Надеюсь, тебе будет полезно. P.S.: Учти, что это все, разумеется, не квинтэссенция знаний программистского ремесла, снизошедшая на тебя, смертного, откуда-то, кажется, из-за облаков, а просто мнение о твоем коде от чувака из интернета, построенное через призму субъективных предпочтений P.P.S.: Приглашаю других более-менее опытных участников форума к поддержке инициативы по разбору (хотя бы поверхностному) кода форумчан. Даже 10-15 минут беглого осмотра кода и выделения самых цепляющих моментов, мне кажется, окажутся серьезным подспорьем и новичкам, и старичкам. Спасибо!
  14. 2 балла
  15. 2 балла
    Вот блин ) Надо дебажить. Вроде я на веб-клиенте тестировал, и если там толпа веб-клиентов подключена, то особо нет проблем.
  16. 2 балла
    Обновил доку по Image API, спасибо за наводку. Есть подозрение, что она уже года полтора как валялась пустующей
  17. 2 балла
    Я пришёл ровно к тому же, но у меня получился другой формат страниц, более похожий на Python. Интересно, будет ли потом битва браузеров. Представляю себе: половина страниц в одном формате, половина в другом. page: addr: "browser:about" desc: "О TigerFox" styles: style: properties: "@ 5 nil * 0x0 0xFFFF00" id: "styles_test" script: command: "client.test_style = searchID[[styles_test]]" rect: properties: "@ 0 0 | 60 20 * 0x333333 0xAAAAAA" text: properties: "@ 1 1" txt: "TigerFox - удобный браузер для OpenComputers."
  18. 2 балла
    Тут как раз сегодня в IRC товарищ @Bs0Dd делился наработками по браузеру =) Может его замотивировать?
  19. 2 балла
    Багрепорт. Что бы повторить баг нужно: Запустить эмулятор. Добавить ретранслятор. Подключить ретранслятор к компьютеру. Попробовать сохранить конфигурацию. Последствия бага: Конфигурация не сохраняется, файл workspace.nbt пустой. При попытке сохранится ещё раз, эмулятор зависает намертво. ocelot-desktop.log пустой.
  20. 2 балла
    Пока у развивающегося Ocelot Brain ведётся работа над полноценной документацией, я решил что было бы неплохо, если бы демо Оцелота было бы доступно не только на Scala, но и на во многом совместимом со Scala языке программирования Java. Тогда бы возросла доступность Оцелота для тех разработчиков, которым уж совсем не даётся Scala, а попробовать Оцелот хочется. Поэтому я создал гист на ГитХабе, в котором приведён абсолютно тот же (по функционалу) код что и в оригинальном Demo.scala, но уже для Java 8: https://gist.github.com/Vladg24YT/dcbb1ed68658122f21e8edcf32f0db6d Лог PowerShell'а, доказывающий работоспособность демо, прилагается: https://pastebin.com/U6NCg97Y Содержимое Ocelot.log:
  21. 2 балла
    Можно сделать иначе local install io.write('Install program? [Y/n] ') local work = true while work do local ans = io.read():sub(1,1):lower() if ans =='y' then install = true work = false elseif ans =='n' then install = false work = false end end if install then --производим установку else --можем сделать что-то другое end Тогда инсталлер будет ждать ответа, пока не получит четкое y или n
  22. 2 балла
    Для тех, кто спешит: Потестировать онлайн: https://ocelot.fomalhaut.me/ Скачать на комп и потестировать: Ocelot Desktop На форуме давно мелькают упоминания 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. Стандартная комбинация юзается браузером для закрытия вкладок - и переопределить её нельзя по соображениям безопасности. * Вместо OpenOS EEPROM используется Advanced Loader. Это сделано для удобства и наглядности. * Не работает лок на пользователя - по понятным причинам. Ocelot Online должен так же работать на смартфонах. Однако возможно придется отключить T9 - он портит эвенты клавиатуры. В разработке находится более сложная версия, где все получат возможность зарегистрировать аккаунт и создавать личные проекты любой конфигурации. Но это дело будущего. Ocelot Desktop Это классический вариант эмулятора Ocelot в виде программы, которую можно скачать и запустить на любой операционной системе, где есть Java. Построен на Ocelot Brain и библиотеке LWJGL (как и сам майнкрафт). Разработкой занимается товарищ @LeshaInc. Протестировать проект, сообщить о багах и поддержать разработчиков можно в топике Ocelot Desktop: Альфа-тест Итак, дорогие пользователи, пишите ваши хотелки, сообщайте о багах, обо всем что работает не так как должно, и как в оригинальном OC. Я, со своей стороны, постараюсь проект не забрасывать, развивать и своевременно (или не очень) обновлять. Благодарности Над проектом также работали: @LeshaInc, @Laine_prikol, @Fingercomp и @MeXaN1cK. За что им огромное спасибо и респект. Не забудем также всех, кто помогал с альфа-тестированием, Сангара - за чудесный мод, и мейнтейнеров OpenComputers за то что его не забросили. Enjoy!
  23. 2 балла
    Добрый день, любители поиграть в видеоигры в компании друзей. Сегодня вам крупно повезло, вам посчастливилось стать свидетелем мультиплеерной игры "Змейка" на ОС. Правила очень просты: Вы играете за змею Вам нужно собирать синие ягоды В любой момент, любой желающий игрок может подойти к вашему компьютеру и начать игру с вами, он появится на координатах (5;5) (левый верхний угол) Количество игроков неограниченно Размеры карты - ваш монитор Первый две буквы вашего ника будут отображаться на голове змеи. Упёрся в свой или чужой блок - умер Ушёл через границу экрана - оказался на другом её конце. Преимущества: Системные требования ультранизкие. Работает на минимальной комплектации компа, даже с памятью Т1. Никаких сетевых/интернет карт не требуется. Все события обрабатывает один комп. Быстрая графика, за счёт того что экран не очищается каждый кадр, а изменяет лишь нужные пиксели. Легко настраиваемые параметры, вроде скорости игры, размера карты и прочего. Большие перспективы на будущее Недостатки: Пока что в игре лучше не ставить низкую скорость игры из-за бага управления приводящего к смерти (при шаге назад). Я поставил костыль, когда время будет думаю исправлю Ссылка на код: http://pastebin.com/ndCeF7eT Установка: pastebin get -f ndCeF7eT snake.lua Видео обзор: Скрины: На последнем скрине тест самого слабого компа! Но даже на нём графика шустро работала.
  24. 2 балла
    Неплохо бы увидеть модельки в пикчах.
  25. 2 балла
    Заметил, что дефолтный EEPROM (который Lua BIOS) выставлен в режим ReadOnly. В игровых опенкомпах он доступен для перезаписи
  26. 2 балла
    В прошлый раз мы научились создавать объекты в lua и вызывать их методы. Нами был создан класс ClassA (хорошо, не класс, а объект-прототип), имеющий одно поле (Name) и два метода (Metod1, Metod2). Были созданы два экземпляра объектов и вызваны их методы. Сегодня мы попытаемся создать наследника от ClassA, добавим ему еще одно поле, унаследуем один из методов без изменений, а второй переопределим. Так же как и класс-родитель, наследник изначально представляет собой пустую таблицу: ClassB={} ClassB.__index=ClassB setmetatable(ClassB,ClassA)После выполнения setmetatable, ClassB внешне будет представлять собой точную копию ClassA, т. е. унаследует все его методы.Изменения начнем с конструктора function ClassB:new(_Name,_Size) local obj=ClassA:new(_Name) obj.Size=_Size setmetatable(obj,self) print('Constructor ClassB for '..obj.Name) return obj endКаковы отличия в конструкторах родителя и наследника? Как видим, для создания объекта наследник использует конструктор родителя, а не создает объект «с нуля». Это конечно не обязательно, но такова классика. Другое отличие — новое поле, которое будет хранить воображаемый размер нашего объекта.Переходим к методам. Metod1, как и договаривались оставляем нетронутым, а вот Metod2 переопределим. function ClassB:Metod2() ClassA.Metod2(self) --inherited print('Metod2 of ClassB for '..self.Name..'. Size is '..self.Size) endТут я показал как из метода объекта-наследника вызвать метод объекта-родителя. Но это тоже при необходимости.Вот собственно и всё. Помимо ранее созданных объектов ClassA, создаем еще два экземпляра нового объекта objects={} for i = 1,2 do objects[i] = ClassA:new('Object#'..i) end for i = 3,4 do objects[i] = ClassB:new('Object#'..i, i*i) endИ посмотрим что получилось for i=1,4 do objects[i]:Metod1() objects[i]:Metod2() endУ меня получилось вот что:[ATTACH]116[/ATTACH] Обращаю внимание, что для объектов 3 и 4 конструктор и Metod2 выполняются дважды — сначала от ClassA, затем от ClassB. Мы рассмотрели такие особенности ООП как наследование и полиморфизм. А вот как реализовать на lua сокрытие данных объекта от доступа извне (по моему, это называется инкапсуляцией) я пока не знаю. Если знаете — пишите. Для тех кто все таки решил попробовать себя в объектно-ориентированном программировании, напоминаю — следите за пунктуацией. Имя поля отделяем точкой, имя метода — двоеточием. Это важно.
  27. 1 балл
    Кажется понял о чем ты. Нужно дать возможность получать доступ к родительскому объекту. В принципе, этот доступ есть через метатаблицу: getmetatable(self).draw(self) -- вызываем родительский метод Коряво, конечно. Нужно будет подумать над чем-то вроде: self.inherited:draw()
  28. 1 балл
    Если есть опыт ООП программирования, могу помочь в создании новых компонентов. Для этого не обязательно менять либу, можно создать свой собственный компонент, который используешь только ты. Если понравится, добавлю компонент в либу, а тебя в соавторы. Кнопка имеет свойство border. Если его установить в 1 или 2, кнопка получит одинарную или двойную окантовку. Но имей в виду, что размеры кнопки должны быть достаточными что бы вместить надпись и окантовку. Т.е. высота должна быть не менее трех.
  29. 1 балл
    Не хочу. По тому что: Буду строить на библиотеке forms, ибо:
  30. 1 балл
    Кстати, как насчет идеи использования графической библиотеки от MineOS как основы для браузера? https://github.com/IgorTimofeev/MineOS/wiki/GUI-API ECS`ом, де-факто, проделана вся необходимая работа, нужно лишь придумать способ создания файлов для страниц, да и это необязательно - в сети, где все друг другу доверяют, можно просто шарить lua-скрипты, которые будут рендерить страницы.
  31. 1 балл
    Хорошо, пингани когда сделаешь
  32. 1 балл
    Вот пример скрипта, считывающую ссылки на элементы каталога: local url = "https://github.com/AlexCatze/racoon-dev/" local internet = require("internet") local html = "" local result, response = pcall(internet.request, url) if result then local result = pcall(function() for chunk in response do html = html .. chunk end end) end if result then html:gsub( 'data%-pjax="#repo%-content%-pjax%-container" href="(.-)">', function(s)print(s)end ) end Результатом его выполнения вывод будет следующий вывод: /AlexCatze/racoon-dev/tree/master/bin /AlexCatze/racoon-dev/tree/master/boot /AlexCatze/racoon-dev/tree/master/etc /AlexCatze/racoon-dev/tree/master/lib /AlexCatze/racoon-dev/tree/master/www Далее потребуется рекурсивно обойти все каталоги и найти в них файлы.
  33. 1 балл
    @AlexCatze Есть два предложения: Немного упростить гайд, чтобы не заставлять пользователя возвращаться туда-сюда: Пишем проще: заливаем на гитхаб..., копируем адрес..., создаём файл..., вставляем адрес... Дописать установщику возможность динамически считывать структуру репозитория, например, из каталога https://github.com/AlexCatze/racoon-dev/installer Страницы разбираются не сложно, по фразе data-pjax="#repo-content-pjax-container". Это позволит пользователю не повторять процедуру при смене структуры каталогов или добавлении и удалении файлов.
  34. 1 балл
    Планов менять API нет. Хотя всё может быть. Если не нужен продвинутый функционал(например, работа одновременно с несколькими картами) - можно использовать либу opennet.lua. Она эмулирует OpenNet, и её API меняться точно не будет. При этом основной функционал будет работать нормально.
  35. 1 балл
    Нужно добавить в запрос заголовок user-agent, чтобы GitLab скачивался. Для этого надо код wget пропатчить.
  36. 1 балл
    Помните мост Рида? Ну так вот. Я тут изучаю Rust на досуге, и пишу мини проекты. Так и получился у меня... Stem Это интернет мост для OpenComputers. Что такое мост Для тех кто не знает что такое мост, и для чего он нужен: мост дает примерно такие же возможности как и linked карта. Он позволяет связать между собой компьютеры OpenComputers, где бы они не находились. Только мост реализует это через интернет карту. Однако по сравнению с linked картой есть один очень крутой плюс. Вы можете подключиться к своему OpenComputers компу не только с другого OpenComputers компа из Майнкрафта, но и из реального мира. Например с телефона. Или с вашего домашнего компьютера. Отличие от моста Рида Я немного по другому подошел к архитектуре проекта. Вместо попарного соединения, Stem реализует систему каналов. Работает это очень просто. Вы можете: 1) послать сообщение в канал X 2) подписаться на сообщения из канала X Количество подписчиков не ограничено. Количество клиентов которые могут посылать сообщения в канал тоже не ограничено. ID канала (по которому происходит подписка и отправка сообщений) служит заодно и паролем к нему. Поэтому если вы хотите создать публично доступный канал - просто опубликуйте его ID. А если хотите создать свой, приватный, канал - просто возьмите ID подлиннее и никому его не открывайте. ID - это последовательность любых байт длиной до 256. Число комбинаций (256 в степени 256) это огромное число, так что уникальных ключей хватит надолго. Пример local event = require('event') -- подключаем STEM local stem = require('stem') -- присоединяемся к серверу STEM local server = stem.connect('stem.fomalhaut.me') -- просим сервер присылать нам сообщения с канала 'my-channel-id' server:subscribe('my-channel-id') -- слушаем эвент 'stem_message' в цикле while true do local name, channel_id, message = event.pull('stem_message') if name ~= nil then print(channel_id, message) end end -- ...или регистрируем листенер event.listen('stem_message', function(_, channel_id, message) print(channel_id, message) end) -- мы можем посылать сообщение в канал -- (причем не обязательно быть подписанным на этот канал -- достаточно просто его ID) server:send('my-channel-id', 'hello there') -- просим сервер перестать присылать сообщение с канала server:unsubscribe('my-channel-id') -- полностью отключаемся от сервера STEM server:disconnect() Одновременно можно работать с несколькими серверами Stem и с любым количеством каналов. Библиотека stem.lua Библиотечку можно скачать напрямую по этой ссылке: https://gitlab.com/UnicornFreedom/stem/raw/master/stem.lua Либо установить через HPM: hpm install stem Подробная документация по командам библиотеки находится здесь. Сервер STEM Дефолтный сервер STEM запущен у меня на VPS по адресу: https://stem.fomalhaut.me/ Можете смело его использовать. Единственное, что это тестовый сервер пока. Может пропадать или менять протокол. Новости постараюсь писать сюда. Исходный код проекта находится тут: https://gitlab.com/UnicornFreedom/stem Вы можете скомпилировать его под свою систему и запустить где угодно. Настраивается сервер файлом stem.toml в корневой папке. Дефолтный конфиг может выглядеть так: [tcp] host = '127.0.0.1' port = 5733 [web] host = '127.0.0.1' port = 5780 [general] ping_interval = 60 Чтобы получить полностью свой отдельный и независимый сервер STEM, достаточно будет просто запустить бинарник, получившийся после компиляции. Не забудьте также положить в папку с бинарником папки static и templates. Они нужны для веб-интерфейса. Сервер мультипоточный, и очень производительный. Должен тянуть довольно большие объемы трафика. Но точных бенчмарков я не проводил. Если есть желающие - пишите в IRC, скооперируемся и померяем. 😃 Для того чтобы видеть логи сервера, используйте переменную окружения RUST_LOG. Например чтобы включить полное отображение всех логов: $ RUST_LOG=stem ./stem Веб-интерфейс Если перейти по ссылке на сервер STEM то вы увидите... веб-интерфейс. Веб интерфейс показывает счетчик активных каналов и сессий (клиентских подключений). Кроме того, он дает возможность подключиться к любому каналу STEM и поучаствовать в приеме-передаче сообщений прямо через сайт. Единственное ограничение - как ID канала, так и контент сообщений ограничивается тем, что можно закодировать в UTF-8. Ну вот и все Мост в принципе уже полностью работоспособен. Все идеи, пожелания, отчеты о багах пишите сюда, либо на issue трекер в репозитории. Если кто-нибудь хочет помочь с написанием клиента STEM на своём любимом языке программирования - обращайтесь ко мне в ЛС, IRC или пишите в этой теме. Написать клиент несложно - для примера можно глянуть на код библиотеки для OpenComputers. Она состоит всего из 150 строк кода. Enjoy! 😃
  37. 1 балл
    Как бы да, давно уже. Я ссылку кинул на скачивание первую попавшуюся. Так то хоть программы, хоть гитхаб, куда угодно не заходит
  38. 1 балл
    Не соответствует действительности. Правильно: selColor - цвет фона выбранной строки списка (по умолчанию -0x0000ff) sfColor- цвет шрифта выбранной строки (по умолчанию - 0xffff00)
  39. 1 балл
    Представляю вам опять программу для робота, которая позволяет добывать руду, не лазая по пещерам. Робот, используя геолизер, может самостоятельно находить и добывать руду. Реализованы еще не все возможности, поэтому прошу тестировать и сообщать мне о багах. Требования: Корпус компьютера (уровень II или III) Апгрейд инвентарь (больше - лучше) Апгрейд контроллер инвентаря Жесткий диск EEPROM с прошитым Lua BIOS Геосканер Память (уровень I или выше) Процессор (любой) Апгрейд полета (I уровень) Алмазная кирка или аналогичный инструмент. Опционально: Апгрейд верстак Беспроводная сетевая карта Апгрейд батарея Апгрейд опыта Апгрейд чанклоадер Апгрейд генератор Апгрейд солнечная панель Эндерсундук из мода EnderStorage Установка: Скачать и сохранить файл как init.lua wget https://raw.githubusercontent.com/DOOBW/geominer/master/miner.lua init.lua Закинуть этот файл в корень диска. Добавить диск при сборке робота. Установить робота на платформу из твердых блоков. Дать роботу кирку. Поставить возле робота контейнер и зарядник. Нажать кнопку питания и наслаждаться процессом.
  40. 1 балл
    В случае невозможности открытия картинки image.load() выдаст false и причину, по которой это происходит. Возможно, файл был повреждён или же банально не хватает оперативки. Ну а GUI.image - это всего лишь обёртка над заранее загруженным изображением, и если пихать в неё boolean вместо самой картинки, то либа сдохнет. Почему же нет проверки на тип данных, чтобы предотвратить такие ситуации? Потому что тонны assert'ов и вызовов type() с последующим сравнением убивают и без того слишком малую производительность опенкомпов. Либа спроектирована по принципу перекладывания ответственности на программиста: такова цена спасения. Поэтому юзай следующий код и проверяй валидность пикч вручную: local result, reason = image.load("test.pic") if result then local image = GUI.image(1, 1, result) ... else GUI.alert("Хопа! Пiймав на сломанную пикчу: " .. reason) end Я было хотел ткнуть носом в документацию, мол, "читай сначала, блеан", но только сейчас заметил, что она сдохла как раз в разделе Image API. Пардоньте, щас исправим))0
  41. 1 балл
    Если нужен именно красный цвет, можно использовать вывод через io.stderr. io.stderr:write('Hello World\n') Ещё можно использовать такой трюк (он встречается в библиотеке io), там цвета можно менять: print('\27[31mHello World\27[37m')
  42. 1 балл
    Ой, как мило:3 Забирай себе, если хочется, не жалко ж
  43. 1 балл
    Тоже зашёл как-то раз, а там такое:
  44. 1 балл
    Среди всех компонентов в 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, method) — обёртка над component.internet.request. Удобна тем, что все ошибки превращает в исключения за программиста. Кроме того, возвращаемое значение — итератор, и его можно поместить в цикл for. Тем не менее, код, который ждёт установки соединения, нужно писать самому. 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 механизм лучше. Поэтому этот пост получился не столько про интернет-карту, сколько про обработку ошибок.
  45. 1 балл
    Каждый метод компонента в OpenComputers характеризуется его прямотой: есть прямые методы, а есть непрямые. Не раз в ирке разъяснял разницу. Сейчас расскажу и вам. Предположения: Текущая версия мода — 1.7.5. Вы собирали опенкомпьютер. Вы знаете, кто такой компонент, что у него за методы и как их вызвать. С непрямыми вызовами всё просто. Они уводят компьютер в сон на тик. Всегда, при любом условии, даже в случае ошибки, после вызова непрямого метода компьютер до следующего тика работать не будет. Прямые вызовы такого ограничения не имеют. Один такой вызов может занимать произвольное количество времени. Зависит это от бюджета вызовов. У компьютеров OC есть бюджет вызовов. Это скрытая безразмерная величина. Каждый тик она сбрасывается до определённого значения. Определено оно так: Сначала смотрим на процессор в компьютере. Точнее, на уровень: T1 соответствует 0.5, T2 — 1.0, T3 — 1.5. Затем на каждую из планок памяти. T1 или T1.5 — это 0.5, T2 или T2.5 — 1.0, T3 или T3.5 — 1.5. Получаем несколько чисел. Суммируем, делим на количество — находим тем самым среднее арифметическое, которое и будет максимальным бюджетом вызовов. Практикум: T2 процессор → 1.0 Планка T2.5 → 1.0 Планка T3.5 → 1.5 Бюджет вызовов: (1.0 + 1.0 + 1.5) / 3 ≈ 1.167. Пример второй: Т3 процессор → 1.5 Планки T3.5 → 1.5, 1.5, 1.5 Планка T2.5 → 1.0 Бюджет вызовов: (1.5 + 1.5 + 1.5 + 1.5 + 1.0) / 5 = 1.4. Достаточно. Каждый прямой вызов расходует этот бюджет. По умолчанию — ровно одну тысячную его. Самые последние дев-билды мода делают прямые вызовы по умолчанию абсолютно бесплатными. Когда бюджет уходит в минус, компьютер принудительно спит до следующего тика. Определяет прямоту метода разработчик. Для создания метода в коде мода используется аннотация li.cil.oc.api.machine.Callback. Примерно так (метод hologram.get): @Callback(direct = true, doc = """function(x:number, y:number, z:number):number -- Returns the value for the specified voxel.""") def get(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { ... } Если мы видим здесь direct = true, то это абсолютно точно прямой вызов. Если direct = false или отсутствует, то вызов непрямой. У метода может быть кастомная стоимость вызова. Не 0.001. У дата-карточки такое особенно. Пример (data.inflate): @Callback(direct = true, limit = 4, doc = """function(data:string):string -- Applies inflate decompression to the data.""") def inflate(context: Context, args: Arguments): Array[AnyRef] = { ... } limit = 4 читать как "потребляю 1/4 бюджета при вызове". Для сервера выше это 5 вызовов в тик. Недурно. У видеокарты всё сложно. Вообще, она тоже ограничивает ярость использования через бюджет вызовов. Потому на Т3-комплекте работать будет быстрее. Но количество потребляемого бюджета также зависит и от уровня видеокарточки. Для OC 1.7.5 распределение такое: Операция Стоимость T1 GPU T2 GPU T3 GPU setBackground 1/32 1/64 1/128 setForeground 1/32 1/64 1/128 setPaletteColor 1/2 1/8 1/16 set 1/64 1/128 1/256 copy 1/16 1/32 1/64 fill 1/32 1/64 1/128 Поэтому максимально можно вызвать 384 сета в тик. Чтобы программно определить прямоту методов, есть функция component.methods(addr). Отдаём ей полный адрес компонента, получаем таблицу. В ключах имена методов, в значениях их прямота. Или же можно воспользоваться этой таблицей. Она включает все методы всех компонентов, которые есть в OpenComputers. И наконец, размер бюджета можно сменить в конфиге. Опция budgetCosts занимается именно этим.
  46. 1 балл
    Вот именно - вы боитесь поднять голову выше банального пинг-понга пакетами между платками, а ваши низменные интересы ограничены фермами пшенца и картошечки на дронах и прочей ерунде уровня третьего класса церковно-приходской IT-школы. Когда на AtomicWars (дай бог ему здоровья и стабильной работы) вы задумаетесь над чем-то более глобальным, чем пароли на двери и бурение шахт друг-другу... на участках, вы так или иначе столкнетесь с необходимостью соединить ваши компьютеры, дронов и роботов в сеть. И именно тогда вы вспомните старину Атомика, который, словно Прометей, принес вам то, что изменит вашу жизнь навсегда. Запомните этот твит
  47. 1 балл
    Вот какое чудо удалось сделать: Теперь каждый сможет запускать несколько lua-интерпретаторов, редакторов кода, GUI-программ и распределять их по рабочим столам Я запускал эту программу на компе с 1МБ ОЗУ P.S. Наконец-то приручил стандартный OpenOS-терминал
  48. 1 балл
    У Зер0 всегда крутые проги получаются.
  49. 1 балл
    Список. List1=Form1:addList(left,top,onChange) Список - визуальный компонент, способный хранить множество элементов данных различного типа, подобно таблице в Луа. Каждому элементу (item) списка ставится в соответствие строка (line), посредством которой этот элемент отображается на экране компьютера. Параметры вызова метода addList: left,top - (числа) координаты левого верхнего угла относительно объекта которому принадлежит компонент. onChange - (функция) обработчик события, когда пользователь выбирает при помощи ЛКМ какой либо элемент списка. Дополнительные свойства: W - ширина списка (по умолчанию - 20). H - высота (по умолчанию - 10) border - (0, 1 или 2) рамка, по умолчанию - 2 color - цвет фона (по умолчанию - 0x000000), fontColor - цвет шрифта и рамки(по умолчанию - 0xffffff). selColor - цвет фона выбранной строки списка (по умолчанию -0x0000ff) sfColor - цвет шрифта выбранной строки (по умолчанию - 0xffff00) index - номер выбранного элемента списка items - таблица, содержащая элементы списка lines - таблица, содержащая строки, соответствующие элементам списка visible - имеет значение false, если компонент скрыт (по умолчанию - true) X, Y - абсолютные координаты левого верхнего угла В дополнение к методам, присущим остальным компонентам (см. "кнопка"), список имеет следующие методы: clear() - очищает список insert([pos,]line,item) - вставляет в список в позицию pos элемент item с соответствующей ему строкой line. Все последующие элементы сдвигаются на единицу. Если параметр pos отсутствует, вставка осуществляется в конец списка. sort([comp]) - производит сортировку списка по условию comp. Если параметр comp задан, то он должен быть функцией, принимающей три параметра: comp(list, i, j), где list - указатель на список, а i и j - номера элементов списка. Функция comp должна возвращать true, если элементы списка i и j в результате сортировки меняются местами. Если параметр comp не задан, то сортировка производится по строкам lines в алфавитном порядке. В ближайшем будущем ожидайте презентацию системы визуального программирования на основе библиотеки forms
  50. 1 балл
    Кнопка. Создается командой: Button1=Form1:addButton(left,top,caption,onClick) Команда создает на форме Form1 кнопку и возвращает указатель на вновь созданный объект. Еще раз обращаю внимание: метод forms.addForm вызывается через разделитель "точка", а Form1:addButton - через "двоеточие", поскольку forms - это библиотека, а Form1 - объект. Параметры вызова метода addButton: left,top - (числа) координаты левого верхнего угла кнопки относительно объекта, которому принадлежит кнопка. caption - (строка) надпись на кнопке. onClick - (функция) обработчик нажатия на кнопку. Левый щелчок мыши на кнопке вызовет функцию-обработчик. Функция-обработчик принимает один параметр - указатель на кнопку, которая была нажата. Дополнительные свойства кнопки: W - ширина кнопки (по умолчанию - 10) H - высота кнопки (по умолчанию - 1) border - наличие рамки (по умолчанию - 0) color - цвет кнопки (по умолчанию - 0x606060), fontColor - цвет надписи и рамки (по умолчанию - 0xffffff). visible - имеет значение false, если кнопка скрыта (по умолчанию - true) X, Y - абсолютные координаты левого верхнего угла кнопки Методы компонента: hide() - скрывает компонент. show() - отображает ранее скрытый компонент isVisible() - возвращает true, если компонент не скрыт и находится на активной форме. redraw() - принудительно перерисовывает компонент. Компоненты интерфейса (кнопки, надписи и т.д.) можно создавать не только на форме, но и на других компонентах. К примеру, кнопку можно создать на рамке или надпись на кнопке. При этом дочерний объект будет перемещаться, исчезать и появляться вместе с объектом-родителем.
Эта таблица лидеров рассчитана в Москва/GMT+03:00
×
×
  • Создать...