Ktlo
-
Публикации
96 -
Зарегистрирован
-
Посещение
-
Победитель дней
9
Сообщения, опубликованные пользователем Ktlo
-
-
Всмысле ?
На этом сервере нет.
Смысла данной либы нет, т.к. параллельная отрисовка у тебя всё равно не выйдет. Параллельность сможет сработать только при использовании функций, приостанавливающих действие каждого потока: coroutine.yield(), sleep(), os.pullEvent(), os.pullEventRaw() и т. д.
-
2
-
-
Афигеть, я и не думал, что этот мод на такое способен.
Я всё время использовал похожий ProjectRed. -
Перенос статьи
Эта тема была перенесена из блока "Статьи".
Эта простая программа служит в основном для декора. С её помощью вы сможете с лёгкостью выводить список цистерн, подключённых к компьютеру, с подробной информацией о каждой. Скачать её можно с pastebin CAUxJx2T.
Как запустить
Я подумал, что для такой программы не нужна система, поэтому для работы вам необходимо установить её на EEPROM. Комплектация компьютера - минимальная, необходим процессор первого уровня, видеокарта (желательно второго уровня), монитор (тоже желательно второго уровня), адаптеры с расширением tank controller с подключёнными цистернами.
Как работает
Программа ищет всё подключённые к компьютеру адаптеры с модулем tank controller. Более того вы можете спокойно присоединять цистерны ко всем её сторонам, и они все будут отображаться, главное не делайте это во время работы программы. Когда всё подключено, просто запустите компьютер.
В конечном итоге всё должно выглядеть примерно так:

Вверху каждой ячейки на мониторе название жидкости, по середине её количество в цистерне, снизу же располагается максимальный объём.
Вы также можете прописать каждому типу жидкости свой цвет. У меня это проделано только для воды лавы и молока, что можно увидеть на скриншоте. Для этого откройте файл любым редактором и найдите первую локальную таблицу fluids в ней название каждой жидкости соответствует цвету, вам следует просто продолжить таблицу, также вы можете изменить надпись на верху, для этого придайте переменной name значение любой строки, но старайтесь не использовать 2-байтовые символы.
Программа работает на компьютере не переставая, способ выключения я не придумал, т.к. зачем это надо.
Обновление
Я постарался учесть все ваши пожелания в коментариях и обновил программу. Ниже перечислены все изменения.- Если размер каждой цистерны на экране меньше 15 в ширину, то он фиксируется на 15, другие же цистерны, которые не попали на экран можно увидеть, тыкнув в правую часть экрана, при этом экран будет прокручиваться вправо на одну ячейку цистерны. Если необходимо вернуться назад тыкните в левую часть экрана.
- Plug-and-Play добавлен, но плохо работает или вообще не работает с компонентами tank controller. Подключение новых цистерн и отключение работает без проблем.
- Теперь программа также может работать с OpenOS, но лучше использовать EEPROM.
-
Решил написать немного о логических выражениях в Lua. Логические операции в Lua (в частности end и or) отличаются по возможностям с теми же операциями в других языках программирования. Во многом это зависит от того, что в Lua нет типов переменных (ну там всякие int, byte, short). Зная эти особенности, вы можете избавиться от ненужных логических блоков, но сначала следует разобраться, что они вообще делают и какие значения в lua истинные, а какие ложные.
Ложные значения - это nil и false, а всё остальное (таблицы, числа, сопрограммы, true) - истинные.
OR
Запись простого выражения:
var = znach1 or znach2
Алгоритм:
сначала оператор проверяет первое значение, если оно истинное, возвращает его, если ложное, то второе значение.
Примеры:local znach1 = true local znach2 = false local var var = znach1 or znach2 -- Вернёт znach1, то-есть true var = znach2 or znach1 -- Вернёт znach1, то-есть true var = false or nil -- Вернёт nil
ANDЗапись простого выражения:
var = znach1 or znach2
Алгоритм:
сначала оператор проверяет первое значение, если оно ложное, возвращает его, если истинное, то второе значение.
Примеры:var = true and false -- Вернёт второе, то-есть false var = false and true -- Вернёт znach1, то-есть true var = true and 42 -- Вернёт второе, то-есть 42 var = nil and 42 -- Вернёт первое, то-есть nil var = false and nil -- Вернёт false
NOTЗапись простого выражения:
var = not znach
Алгоритм:
оператор проверяет значение, если истинное, возвращает false, иначе true.
Примеры:var = true and false -- Вернёт второе, то-есть false var = not false -- Вернёт true var = not true -- Вернёт false var = not nil -- Вернёт true var = not 42 -- Вернёт false var = not not 42 -- Вернёт true
Теперь можно показать самые используемые выражения.
Наверняка у вас был случай, когда нужно, чтобы переменная приравнялась одному значению, а если его нет, то другому. Многие записали бы так:
if var == nil then var = 23 endЭто конечно работает, но не лучше ли записать так:
var = var or 23
Кратко и красиво, да к тому же нет лишнего логического блока. Вы также можете продолжить эту цепочку:
var = var or var2 or var3 or 23
В такой цепочке выражение вернёт первое истинное значение, при этом не трогая все те что за ним. Это значит, что вы спокойно можете сделать так:
var = var or error("Переменная var содержит ложное значение!")И радовались бы дальше, но ведь иногда нам нужно, придать значение переменной в зависимости от истинности выражения. Не зная удобного способа, вы бы сделали так:
if var then var2 = 23 else var2 = 42 endНо это можно записать без логического блока:
var2 = var and 23 or 42
Вы можете заменить переменную var любым выражением, но если вы плохо разбираетесь в приоритетах операторов рекомендую выделять скобками:
var2 = (var > 0) and 23 or 42 -- В данном примере скобки не обязательны
Теперь давайте основываясь на узнанном, напишем функцию с проверкой аргумента:
function func(var) var = type(var) == "number" and var == math.floor(var) and var or error("bad argument: celoe number expected, got "..var, 2) endИ заметьте ни одного логического блока. В результате наша функция проверит является ли аргумент целым числом, иначе вернёт ошибку.
Бывает случай, когда необходимо проверить существует ли поле в таблице и выполнять действия в зависимости от этого, но ведь значение может быть не таблицей, из-за этого ошибка будет не пользователя а ваша. Я не буду приводить всякие отдельные случаи просто напишу само выражение:
if not (type(table) == "table" and table.field and ...) then -- ... - ваши логические выражения -- Какие-то действия endЕщё один важный момент, когда аргумент вашей функции может быть не указан, но потом внутри самой функции обретает значение типа boolean (true либо false) через оператор or это не выполнить, т.к. false - ложное значение, но можно воспользоваться следующими выражениями, в зависимости от того, что вам нужно:
var = var ~= false -- Если var = nil, то вернёт true, если переменная типа boolean не изменит её значения var = not not var -- То же, что и первое, но при значении nil вернёт false
Думаю, что наиболее используемые моменты осветил, но если что-то не понятно, спрашивайте.
[ok=Ни один смайлик не использован][/ok]
-
11
-
-
Object API
(Работаю над названием)
Представляю вам третью версию Object API, которое я начал писать ещё давно, первых двух версий вы здесь не найдёте. Это библиотека работает только на Computer Craft, так что возможно зря я её писал...
В любом случае, я надеюсь, что вы оцените. Сейчас в этой библиотеке есть следующие элементы: Button, CheckBox, Graph, Image, Label, Process, Registry, TextBox (не доделано). Планирую написать ещё очень много.
Скачать можно с pastebin:pastebin get 3Bq2Sgvi object
Устанавливается стандартной функцией os.loadAPI("object") или с помощью require. Под мою функцию она изначально была лучше адаптированна. Но не подумайте, со стандартной функцией работает.
Изображение
Получение объекта изображения:Image = Object.Image{xPos=number, yPos=number, Image=table or string[, Term=table, Width=number, Height=number, xOffset=number, yOffset=number, BackgroundColor=number, BorderStyle=string, BorderColor=number, LinesColor=number, Visible=boolean]}Обязательные аргументы:
- xPos и yPos — положение верхнего, левого угла изображения,
- Image — таблица изображения или путь до него, можно загружать изображение стандартной программы paint с помощью paintutils.loadImage или изображение формата nft с помощью функции Object.Image.LoadNFT.
Необязательные аргументы:
- Term — объект окна или монитора, где будет отображаться изображение,
- Width, Height — ширина и высота изображения,
- xOffset, yOffset — смещение изображения по x и y соответственно,
- BackgroundColor — цвет за изображением, если равен нулю, то не используется,
- BorderStyle — стиль рамки вокруг изображения, может быть "none", "box" или "lines",
- BorderColor — цвет рамки,
- LinesColor — цвет линий на рамке,
- Visible — ели false, не рисует изображение автоматически,
- Key — номер кнопки.
Таблица Object.Image также содержит функцию для загрузки nft изображений:
tImage = Object.Image.LoadNFT( "path" )
Загрузка изображений отдельно от объекта может понадобиться для хранения всех изображений в одном файле и последующего смещения в каждом объекте, чтобы не создавать несколько одинаковых таблиц.
Все параметры объекта можно поменять, например:
Image.xOffset = Image.xOffset+4
Также объект содержит только читаемый параметр Image.Path, который устанавливается при указании пути до файла.
Методы:
- Image.Draw() — рисует объект,
- Image.Detect(event=string, tEvent=table) — детектирует следующие действия, связанные с изображением: "mouse_click", "mouse_up", "mouse_drag", "mouse_scroll", "monitor_touch" и "key", где event — ожидаемое действие, а tEvent — таблица с информацией действия полученной от os.pullEvent (tEvent = { os.pullEvent() }), если tEvent не указано, будет прослушивать действие сам.
Потом напишу про остальные объекты.
-
И можно ли будет от такого класса сделать наследника если невозможно добавлять новые поля?
Можно:
function newClass() local object, private = class() setmetatable(object, {}) --Удаляем метатаблицу object.foo(...) --Добавляем метод --<Что-нибудь> end setmetatable(object, private) --Возвращаем нашу метатаблицу назад return object, private end -
Можно)
переопределить оператор присваивания
Метатаблицы Карл...
Я почитал про метатаблицы поподробнее и всё исправил. Оказывается запретить изменение ключей можно.
-
Можно)
переопределить оператор присваивания
Как?
-
Это как? Так что ли?
Obj1:Metod1() --Вызов метода объекта 1 (нормальный) Obj2:Metod2() --Вызов метода объекта 2 (нормальный) Obj1.Metod1(Obj2) --Вызов метода объекта 1 для объекта 2 (странный)
Да, так.
Был несколько разочарован реализацией readonly-полей. Я надеялся на:
print(Obj.ReadOnlyVar) -- читаем поле --> 123 Obj.ReadOnlyVar=456 -- пытаемся изменить print(Obj.ReadOnlyVar) -- повторно читаем поле --> 123
Тоже бы хотелось так, но это не возможно, так как нельзя запретить добавление значения в таблицу объекта.
-
Начнет с программы в шесть строк, закончит офигенной системой с графическим интерфейсом... Наверное.
-
Всё я закончил с ООП, если есть вопросы, спрашивайте.
-
local function toLines(str, w) --Переносит текст на следующую строку, если не хватает места
А "/n" отменили?Тогда будет писать с начала строки, а нужно, чтобы писал на кнопке.
local function rusType(var) --Руссифицированная функция type
Зачем?Я хотел, чтобы ошибки выводились на русском языке вместе с неправильным типом заданного параметра.
-
Данное API будет добавлять в вашу систему более удобные средства программирования разных элементов типа кнопки, картинки, процесса в виде разных объектов. Это API ещё находится в разработке, пока я добавил один элемент, кнопку. Я постарался сделать это API максимально гибким, и пока я не начал делать следующий элемент, хотелось бы услышать ваше мнение. Устанавливается всё очень просто, как любая другая библиотека. Скачать можно с pastebin.
Кнопка
Для того, чтобы получить кнопку, как объект, нужно использовать следующую функцию:local Object = require("Имя_файла") local Button = Object.New.Button{xPos=number, yPos=number, Width=number, Height=number}Разберём необходимые параметры:
- xPos, yPos - числа, обозначающее положение верхнего левого угла кнопки;
- Width, Height - длина и высота соответственно.
Также кнопки есть дополнительные параметры, которые можно не указывать:
- Screen - адрес монитора с кнопкой (переключает видеокарту на другой монитор, если параметр указывает на не подключённый монитор), выдаёт ошибку, если такого монитора нет;
- Text - текст на кнопке, переносится на следующую строку по центру кнопки автоматически;
- TextColor - цвет текста, если не указан, ставит белый;
- BackgroundColor - цвет кнопки, если не указан ставит синий;
- ActiveColor - цвет нажатой кнопки, по стандарту зелёный;
- InactiveColor - цвет неиспользуемой кнопки, по стандарту серый;
- Time - время нажатия, по стандарту 0.3 секунды;
- Style - стиль кнопки. Их всего два, с округлёнными углами (по стандарту) и ровный прямоугольник;
- Visible - параметр видимости, не рисует кнопку автоматически, если false, и рисует, если true.
Все параметры - ключи таблицы, и по-этому могут быть указаны в любом порядке.
Также вы спокойно можете сделать так:
local Object = require"object" local params = {xPos=2, yPos=2, Width=10 ,Height=4, Text=54679183} local Button1 = Object.New.Button(params) local Button2 = Object.New.Button(params)И это будет работать.
Для получения значения одного из параметров, можно сделать следующее:
local Text = Button.Text
Но вы не можете сделать это в другую сторону, а именно:
Button.Text = "klopsterinator" --Не работает
Все параметры, которые вы задали при создании кнопки (включая свои, которых нет в списке) - только читаемые.
Кнопка имеет 3 встроенных функции (а можно ведь и свои сделать
). Первая из них это:Button.Draw( state ) --Рисует кнопку
Если параметр state не указан, то будет нарисована обычная кнопка, если "active", то активная, если "inactive", то неактивная.
Далее:
Button.Set{}Меняет любые параметры кнопки и добавляет ваши.
Button.Detect{Action=string}Детектирует действия на экране, связанные с кнопкой, возвращает true, если действие с кнопкой произошло, false, если нет, и значение CurrentButton. Имеет несколько параметров.
Необходимые:
- Action - действие, может быть "touch", "drag", "scroll" и "drop".
Дополнительные:
- Button - целевая кнопка мыши или целевое направление действия "scroll".
Следующие несколько параметров нужны, если вы используете несколько кнопок одновременно.
- Screen - экран, полученный из функции event.pull(),
- x, y - координаты точки, полученные из функции event.pull(),
- CurrentButton - то же, что и Button, только полученное из event.pull().
Пример кода
local Object = require"object" local event = require"event" local Button = Object.New.Button{xPos=2, yPos=2, Width=11, Height=4, Text="Я - кнопка,и я работаю!!!"} local detect = { } detect.Action = "touch" _, detect.Screen, detect.x, detect.y, detect.CurrentButton = event.pull(detect.Action) if Button.Detect(detect) then Button.Set{Text="Нажата"} Button.Draw("inactive") endЕсли есть какие-то предложения по улучшению, пишите.
-
1
-
Я видел статью про ООП (Объектно-Ориентированного Программирования), написанную на этом форуме, и вот, что могу сказать, он может быть немного непонятен новичкам в Lua. Я всегда пользовался другим способом создания "классов".
Сначала следует разобраться со значениями. Класс - это набор методов (в нашем случае функций), процедур и переменных которые дальше будут наследоваться объектом или другим классом. Объектами называют сущности, обладающие набором свойств и операций над ними. Объект - это производное класса.
В Lua классов как таковых нет, но если пофантазировать, то можно представить функцию как класс, а объект как таблицу.
Создание простого псевдо-класса
1. И так создаем новую функцию и называем её к примеру class.
function class() --Объявление класса --Тело класса end --Конец класса
Пока это просто пустая функция.
2.Теперь пропишем в теле класса следующее:
function class() --Объявление класса local Me = { } --Создание объекта return Me --Возвращение объекта (производного класса) end --Конец классаТеперь мы создали пустую таблицу и заставили функцию вернуть её. Для создания полноценного объекта нам не хватает заполнить его свойствами и методами. Таблица будущего объекта должна быть локальной.
3.Создадим свойства и методы к нему:
function class() --Объявление класса local Me = { --Таблица для методов и переменных ["Var"] = "HelloWorld"; --Публичная переменная } local Secret = "Cake = Lie" --Скрытая переменная function Me.PrintVar() print(Me.Var) end function Me.PrintSecret() print(Secret) end return Me --Возвращение объекта (производного класса) end --Конец классаМы создали два свойства одно скрытое (Secret), а второе публичное (Me.Var) и два метода один для вывода на экран свойства Me.Var, а второй для вывода свойства Secret. Скрытые свойства обязательно должны быть локальными, а публичные находиться в таблице объекта. Также вы можете создать локальную функцию, которую смогут использовать только методы этого объекта.
5.Вы можете хранить этот класс в отдельном файле и вызывать его с помощью функции require(), таким образом, чувствовать себя программистом на C, где обычно каждый класс находится в отдельном файле
. Выглядит это примерно так:local class = require("имя_библиотеки").class -- загрузили класс из другого файла (если его нет в этом) local Object = class() -- Создание объекта на основе класса Object.PrintVar() --Вызов первого метода --> HelloWorld Object.PrintSecret() --Вызов второго метода --> Cake = Lie Object.Var = "BuyWorld" --Изменение значения свойства Var Object.PrintVar() --Вызов первого метода --> BuyWorldЗдесь же я вызвал все методы объекта, поменял значение свойства Var и снова вызвал. Попробуйте сами, всё должно работать и с несколькими объектами тоже.
Наследование
Сначала необходимо познакомиться с самим термином. Наследование - это перенимание всех свойств и методов одного класса другим.
Давайте напишем новый класс и назовём его parent.
function parent() --Класс предок для будущего класса наследника local Me = { } local private = { ["Secret"] = 42 } Me.Var = "HelloWorld" function Me.PrintVar() print(Me.Var) end return Me endВ этом классе есть свойство Var и метод PrintVar(), а также скрытое свойство в таблице private.
Теперь пишем класс наследник:
function class() local Me = parent() --Перенимаем таблицу объекта из класса parent(), то есть наследуем. return Me end
Всё! Поздравляю, вы наследовали класс!
Но на самом деле не полностью, т. к. остаётся наше скрытое свойство. Я не просто так засунул его в таблицу. Мы можем передать эту таблицу как и сам объект, но отдельно.function parent()--Класс предок local Me = { ["Var"] = "HelloWorld" --Публичное свойство } local private = { ["Secret"] = 42 --Скрытое свойство } function Me.PrintVar() --Метод print(Me.Var) end return Me, private --Возвращение объекта, а затем таблицы со скрытыми переменными end function class() --Класс наследник local Me, private = parent() --Наследование function Me.getVar() --Функция, возвращающая свойство Var из класса parent return Me.Var end function Me.getSecret() return private.Secret --Функция, возвращающая скрытое свойство Secret класса parent end return Me, private --Этот класс можно наследовать тоже, таким образом, получится двойное наследование endТеперь тестим:
local class = require("filename").class --Нужно, если вы разместили класс в другом файле --[[ Если вы разместили классы в разных файлах, то следует загружать каждый отдельно, но так как они у меня в одном файле, я вызову только наследника, и это всё равно будет работать. ]] local object = class() --Создаём объект object.PrintVar() --Тестим метод из класса предка --> HelloWorld print(object.getVar()) --Тестим метод класса class, работающий с Var --> HelloWorld print(Me.getSecret()) --Получаем значение скрытого свойства --> 42Как видите, всё должно работать. Вы можете сами попробовать построить этот класс и вызывать его методы.
В принципе тут больше нечего объяснять, я думаю. Если что-то не понятно, спрашивайте, отвечу.
Экономия оперативной памяти
Представим класс:
function class() return { ["foo"] = function() --Здесь очень много кода, вычислений, инструкций и т.д. end; ["var"] = "какая-то переменная, для примера" } --Оформил тело объекта так, для разнообразия endГлавный минус таких "классов" в том, что они тратят оперативную память, бессмысленно создавая идентичные функции, которые сохраняются отдельно. Но этого можно избежать почти полностью. Почему почти, вы сейчас уведите.
Представим класс в несколько ином виде, как блок do <тело класса> end.
do --Начало класса local function class_foo(self) --Наша "тяжёлая" функция --Здесь очень много кода, вычислений, инструкций и т.д. end function class() --Функция для вызова классаreturn {
["foo"] = function()
return class_foo(self) --Ссылаемся на локальную функцию
end;
["var"] = "какая-то переменная, для примера"
}end end --Конец класса
Вот и всё! Сложная функция записывается только один раз, а методы ссылаются на неё. Также вы можете не использовать блок do, но тогда функции будут доступны вне класса.
Только читаемые свойства
Это последнее, о чём стоит написать в этой теме. Только читаемые свойства могут понадобится для возвращения результатов объекта не используя функции, и при этом, не предоставляя возможность редактировать эти самые свойства. Это можно сделать используя магию метатаблиц. Не знаете, что это? Тогда бегом читать! Хотя сейчас можно обойтись и без этого
. Объяснить на словах, как это сделать, трудно, так что просто смотрите код:function class() local private = { } --Таблица со скрытыми свойствами private.__index = { --Таблица внутри таблицы скрытых свойств с читаемыми переменными. !!!Важно! Таблица должна сохраняться под ключом __index!!! ReadOnly = true; --Читаемое свойство Set = function( var ) --Читаемый метод, который изменяет значение читаемого свойства private.__index.ReadOnly = var end; } private.secret = 42823 --Скрытое свойство private.__newindex = function() end --Метаметод, непозволяющий менять значение таблицы объекта, если приравняете не к функции, а к таблице будет выводить в неё все попытки замены свойств. local Me = { } --Сам объект, да он пуст setmetatable( Me, private ) --Магия метатаблиц: устанавливает метатаблицу private для объекта return Me, private --Зачем я возвращаю таблицу со скрытыми свойствами, смотрите в наследовании endНадеюсь комментарии к коду выше вполне понятны и всё объясняют. Так что мы можем перейти к тестированию.
Тестируем:
local class = require 'class'.class --Загрузка класса, если он в другом файле. local object = class() --Получаем объект на основе нашего класса object.Set( "test" ) --Устанавливаем значение для читаемого свойства читаемой функцией. Напрямую мы это сделать не сможем. print(object.ReadOnly) --Выводим значение ReadOnly --> test object.ReadOnly = "He he!" --Изменяем значение, надеясь, что это сработает print(object.ReadOnly) --Но он выводит старое значение --> test
На этом всё. Теперь вы можете использовать читаемые свойства для того, чтобы не создавать специальные для этого методы.
Вывод. Этот способ реализации ООП в Lua будет работать и в OC и в СС. "Классы" очень похожи на классы из других языков программирования. В моём варианте нет двоеточия, которое приносит странную возможность обработки одного объекта методом другого. Использовать ООП в игре можно для удобной разметки интерфейса в вашей программе (например для создания кнопок), и для экономии системных ресурсов в вашем компьютере внутри компьютера.
-
6
-
-
Код хороший, но подожди ка, а где же таблица и функции package? Это ведь специальный инструмент, что бы не забивать оперативную память, и ускорить работу программы при нескольких одинаковых вызовах require().
Больше об этом написано здесь http://ilovelua.narod.ru/about_lua.html#Module_loaders
Можешь посмотреть как require и package реализовано в ОС https://github.com/MightyPirates/OpenComputers/blob/master-MC1.7.10/src/main/resources/assets/opencomputers/loot/OpenOS/lib/package.lua
Я изменил код теперь всё должно работать, как было написано в той статье. Спасибо за информацию. Моя программа вышла меньше чем OC. Хотя я её особо не смотрел
-
2
-
-
os.loadAPI чем не устраивал?Но за прошу лайк, часто когда с ок компа сажусь за кк комп, пишу половину функций неверно. Вот только проблемам есть. Кк не поддерживает полную вставку кода, только 1 строка, а смысл этого велосипеда, если придётся писать os.loadAPI? Хотя, можно её как глобальную функцию в стартап вставить
Просто, используя эту функцию, можно загружать библиотеки в локальную переменную, не трогая глобальную, а также использовать shell API. Кроме способа, который ты описал, можно использовать функцию dofile("fileName"), если хочешь хранить её в отдельном от программы файле.
-
Пока я писал свою систему для ComputerCraft, мне пришла мысль изобрести велосипед в виде функции require(), которая была удалена из мода по неизвестным мне причинам. Собственно вот код:
function require(...) local i local libs = { } for i=1, #arg do arg[i] = fs.combine("", arg[i]) --В кавычки можно написать путь к директории с вашими библиотеками. if not fs.exists(arg[i]) or fs.isDir(arg[i]) then error("Couldn't find a file by the path #"..i, 2) end local g = getfenv() local t = { } setmetatable(t, {__index = _G}) t.shell = shell t.multishell = multishell setfenv(1, t) local f=function() dofile(arg[i]) end local ok, err = pcall(f) setfenv(1, g) if not ok then error(err..", in #"..i, 2) end libs[i] = t end return unpack(libs) end,,Что-же эта функция делает?"- может кто-то спросить, а тоже что и раньше. Она загружает библиотеку по выбранному вами пути в таблицу (или правильнее будет сказать, в окружение). Самое главное - это то, что вы сможете загружать любое API в любую переменную, в том числе и локальную. Выглядит это примерно так:
local firstVariable, secondVariable = require("APIs/HelloWorld.api", "APIs/Object.lua")Как вы наверное уже поняли, можно загружать не ограниченное количество библиотек за раз.
Особенности:
- загрузка нескольких переменных одновременно;
- возможность не захламлять глобальное окружение;
- использование библиотек в любой другой таблице;
- пропуск shell API и multishell API в окружение вашего API*;
- запуск вашего файла с библиотекой во время загрузки (ну это я из пальца выжимаю);
- в отличии от стандартной функции, вы не можете использовать require()для поиска библиотеки среди файлов, но по-моему это лишнее в CC, но вы можете указать одну папку (заметка на счёт этого в коде);
- вы можете убедится в том, что окружение, куда вы запихнули свою библиотеку, не содержит копию всех функций и переменных из глобального окружения (что хорошо сказывается на производительности).
Для того, чтобы использовать эту функцию, просто вставьте её код в вашу программу. Надеюсь, что это кому-нибудь пригодится.
Дополнение.
Я переделал функцию и добавил взаимодействие с глобальной таблицей package, этот вариант намного лучше предыдущего. Чтобы использовать его скачайте файл с помощью команды:
pastebin get naxXwUey package
Далее загрузите в глобальное окружение с помощью функции dofile("package"). Теперь рассмотрим, что же я изменил.

Во-первых, при повторном вызове функции с одним и тем же API, вы получаете ту же самую таблицу, то-есть:
local var1 = require("lib1") local var2 = require("lib1") print(var1 == var2) -->Вывод: trueТак получается, потому что каждая новая загруженная библиотека сохраняется в таблице package.loaded, а при повторном вызове, вам выдаётся значение из этой таблицы. Я также внес все стандартные API ComputerCraft'а в эту таблицу.
Во-вторых, я добавил значение package.path, оно работает точно также, как и в оригинале. Просто перечисляете директории с библиотеками в током виде:
package.path = "/APIs/?.lua;/Lib/?;/?"
Вы можете изменить это значение в самом файле, я там оставил комментарий.
Также я добавил таблицу package.preload, которая содержит специальные пользовательские функции для загрузки конкретного API, по стандарту я её оставил пустой (что логично). Ключи таких функций являются названиями API, а возвращать они должны функции, которые возвращают само API (правда не знаю, кому это может понадобиться).
И наконец, package.loaders, в этом массиве, расположены функции для загрузки всех API. Их пока две. Аргументом этой функции должно быть имя модуля, а возвращать она должна функцию, загружающую API.
Для тех, кто ничего не понял, просто знайте второй вариант этой функции с дополнением лучше.)))
*Да-да, теперь вам не придётся мучиться и придумывать изощрённые способы получения допуска к функциям этого API в вашей библиотеке!
-
5

Метатаблицы, Lua
в Уроки
Опубликовано: · Изменено пользователем Ktlo
Метатаблицы — это самые обыкновенные таблицы, содержащие функции, которые изменяют стандартное поведение операторов в Lua. Метатаблица в Lua может быть у любого значения, но только у каждой таблицы может быть своя метатаблица, у всех остальных же одна на всех. Так например у типа значения строки есть своя встроенная метатаблица, которая позволяет нам делать так: ("строка"):rep(10) В данной таблице присутствует поле __index. А у потоков метатаблицы нет, а надо бы!!! Но обо всём по порядку.
Для установки и получения метатаблиц в Lua по стандарту существует 4 функции:
Теперь, я думаю, можно приступить к самим метаметодам. Для своего, а может быть и вашего удобства я их разделю на четыре категории:
Разное
Начну сразу с этой категории, т.к. здесь всё самое вкусное.
1) __index(self: value, key: value) — метаметод, который вызывается при попытке индексирования значения с этим метаметодом. В качестве аргументов функция получает само значение, как первый аргумент, и индекс как второй, возвращать должна любое значение под заданным индексом. Если значение-таблица, то вызывается только тогда, когда нет поля с искомым индексом в самой таблице. Если вам необходимо проигнорировать этот метаметод, используйте rawget(table: table, key: value):value, эта простая функция вернёт значение в самой таблице и не притронется к метаметоду. Пример:
local t = { } setmetatable(t, { __index = _G }) t:print() --то же самое с помощью функции t = setmetatable(t, { __index = function(self, key) return _G[key] end }) t:print()2) __newindex(self: value, key: value, value: value) — делает то же что и __index, только наоборот. Этот метаметод вызывается, когда значению, присваивают что-нибудь под каким-нибудь индексом. Также можно установить вместо функции таблицу, куда будут уходить все новые значения под новыми индексами. Если ваше значение с метатаблицей — таблица, то вы можете вставить поле прямо в неё, избегая метометод, с помощью rawset(table: table, key: value, value: value):nil. Пример:
--Давайте сделаем таблицу, которую нельзя обойти стандартными итераторами local t = { } local private = { } setmetatable(t, { __index = private; __newindex = private; }) t.var = 23 t.var2 = 42 for key, value in pairs(t) do --По сути ничего неделает print(key, value) end print(t.var) --> 23 print(t.var2) --> 423) __call(self: value, args: ... ) — превращает любое значение с метатаблицей в типа функцию. С ним вы можете вызвать ваше значение, как функцию. Первым аргументом всегда будет само значение, остальные — это те которые были указаны при вызове функции. Пример:
local t = { } setmetatable(t, { __call = function(self, String) table.insert(self, String) return true end }) print(t("Hello!")) --> true print(t[1]) --> Hello!4) __metatable: value — это не метаметод, это просто поле, которое содержит информацию, которая будет возвращаться функцией getmetatable(value: value):value. Также при наличии этого поля не удастся сменить метатаблицу с помощью setmetatable(value: value, metatable: table):table на другую: выскочит ошибка. Чтобы проигнорировать этот параметр можно использовать функции из библиотеки debug.
5) __tostring(self: value) — при попытке перевести значение в строку будет вызван этот метаметод, использован же будет его результат. Пример:
local t = { } setmetatable(t, { __tostring = function(self) return "Я - таблица!" end }) print(tostring(t)) --> Я - таблица! print(t) --Да, да. Это также работает. --> Я - таблица!6) __len(self: value) — взаимодействует с оператором #. Если вы хотите проигнорировать этот метаметод в значении таблица, просто используйте table.getn(table: table):number. Не знаю, что добавить, смотрите пример. Пример:
local t = { } setmetatable(t, { __len = function(self) return 3 --Размер этого "массива" теперь всегда 3 end }) for i = 1, 8 do table.insert(t, i) end -- В массиве теперь 8 элементов, print(#t) -- но результат всё равно 3 --> 37) __pairs(self: value) — заменяет собой функцию pairs(table: table), должен возвращать итератор, как минимум. Проигнорировать можно, подставив стандартный итератор next(level: number, table: table):value, value.
8) __ipairs(self: value) — то же самое, но для ipairs(table: table). Стандартный итератор — __inext(level: number, table: table):value, value (не удивляйтесь двойному подчёркиванию, это не метаметод, а стандартная функция глобального окружения).
9) __name: string — метаполе, которое используется при возникновении ошибки. Это поле в метатаблице наверняка существует для типа userdata, но также успешно работает с таблицами. Когда срабатывает ошибка несоответствия типов данных, то Lua заменяет слово, обозначающее этот тип данных, на то, что хранится в __name (при условии, что __name — строка). Для понимания того, что вы сейчас прочитали, пример:
t = setmetatable({}, { __name = "qwerty" }) -- Переименовываем table в qwerty print(type(t)) -- Никак не влияет на type --> table math.sqrt(t) -- t - не число, а из таблицы корень не получишь --[[ Этот код выдаст примерно следующую ошибку: stdin:2: bad argument #1 to 'sqrt' (number expected, got qwerty) ]]Следующие два метаметода взаимодействуют со сборщиком мусора в Lua. На разных версиях lua они немного отличаются по возможностям. Я опишу, как они работают на lua 5.3.
10) __gc(self: value) — вызывается, когда сборщик мусора хочет удалить объект из памяти. По некоторым данным не работает с таблицами, но я протестировал в Lua интерпритаторе, и всё вроде как функционирует
setmetatable({ }, { __gc = function(self) print(self, " был удалён с ОЗУ") end }) --[[ Через какое-то время ленивый сборщик мусора удалит вашу таблицу, и вы увидите этот знаменательный момент]]11) __mode: string — параметр, указывающий на то, что таблица не может защитить ключи или значения от безжалостного сборщика мусора. Если __mode == "k", то при не нахождении переменной содержащей, значение равное этому ключу, сборщик мусора спокойно удалит это поле и из таблицы тоже, если же __mode == "v", то тоже самое он будет делать для значений в таблице. Полностью правильно это будет работать только если удаляемые ключи и значения не являются: строкой, boolean или числом (это связано с особенностью работы сборщика). Пример:
local t = setmetatable({ }, { __mode = "v" }) do local var = {} t.var = var end print(t.var) --> table: XXXXXXXXXXX -- После очередного сбора мусора print(t.var) --> nilМатематические
1) __unm(self: value) — унарный минус, это, когда var = -value. В качестве аргумента получает само значение. Думаю, примеры здесь не нужны.
Следующие метаметоды однотипны, поэтому я опишу принцип действия для всех, а потом с какими операторами они взаимодействуют.
Принцип действия: сначала Lua смотрит на первое значение в действии, если оно содержит метатаблицу с метаметодом этого действия, то он вызывается, и решением этого действия становится результат метаметода (то, что он вернёт). Если не находит смотрит во втором значении и проделывает тоже самое, если ненаходит ни там ни там, то действует по стандартному алгоритму. При постановке аргументов в метаметоде использует их изначальный порядок (то есть 2 + 3 будет вызывать __add(2, 3)) Надеюсь объяснил понятно.
2) __add(op1: value, op2: value) — операция сложения. Оператор +.
3) __sub(op1: value, op2: value) — операция вычитания. Оператор -.
4) __mul(op1: value, op2: value) — операция умножения. Оператор *.
5) __div(op1: value, op2: value) — операция деления. Оператор /.
6) __mod(op1: value, op2: value) — операция получения остатка от деления. Оператор %.
local _42 = { } setmetatable(_42, { __add = function(op1, op2) op1 = type(op1) == "table" and 42 or op1 --Если первый аргумент - таблица, то присваиваем ему значение 42, иначе его изначальное значение op2 = type(op2) == "table" and 42 or op2 --Проделываем то же самое return op1 + op2 --Складываем и возвращаем результат end }) print(_42 + 8) --> 50 print(8 + _42) --> 50 print(_42 + _42) --> 84 print({} + _42) --> 84Сравнение
Следующие метаметоды нотличаются от всех тех, что выше. Во-первых они могут возвращать только логическое значение (true или false), если возвращаемое значение не является таковым, то будет переделано в него (например "return {}" будет восприниматься, как "return true"). Во-вторых, они будут вызываться только, если одинаковый метаметод присутствует в метатаблицах обоих значений, причём сами метатаблицы могут быть разными.
1) __eq(op1: value, op2: value) — операция равенства, операторы: == и ~=. При использовании оператора ~= возвращаемое значение инвертируется. Порядок сравниваемых значений в аргументах соответствует их изначальной постановке. Не вызывается если значения одинаковы (например, когда мы сравниваем одну и ту же таблицу). Пример:
local t1 = {} local t2 = {} local __eq = function(op1, op2) print "Работает" return op1 end setmetatable(t1, { __eq = __eq }) setmetatable(t2, { __eq = function(op1, op2) print "Работает" return op1 end }) print(t1 == t2) --__eq не вызвался, т.к. методы в метатаблицах разные. --> false setmetatable(t2, { __eq = __eq }) print(t1 == t2) --Метод успешно вызван --> Работает --> true print(t1 == t1) --Не вызван, т.к. одинаковы --> true2) __lt(op1: value, op2: value) — операция меньше чем, операторы: < и >. Сравниваемые значения расставляются в аргументах в том порядке, в каком их сравнивают, при использовании оператора <. Оператор > просто меняет их местами.
3) __le(op1: value, op2: value) — операция меньше или равно, операторы: <= и >=. Работает также, что и метаметод выше, но с другими операторами.
local meta = getmetatable(io.stdout) function meta.__shl(ostream, data) return ostream:write(tostring(data)) end local cout = io.stdout local endl = "\n" cout = cout << "Я люблю C++!" << endl << "Число: " << math.pi << endl << cout;