Перейти к контенту

Язык Lua. Общие вопросы программирования


Malandrinus

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

Вопрос такой. Есть метод, перебирающий таблицу объектов и вызывающий у каждого объекта метод inject. Сам метод, в зависимости от ситуации может быть как собственно методом, так и переопределенной внешней функцией. Как, кроме объявления этих объектов в глобальной переменной, можно получить доступ к self внутри метода(непереопределенного)? Возможно мне нужно что-то про замыкания, но я в них толком не разобрался

Этот метод также вызывает эту функцию. Т.е. мне нужно либо вызывать функцию внутри метода, либо вместо метода.

 






for k, v in pairs(self.elements) do

oActive_elem = v

  -- v:inject(...) -- если это метод, то вызываем его как метод

  v.inject(...) -- если это переопределенная функция, то передаем параметры как есть

end

 

 

 

Нужно, чтобы если поле объекта - метод, то можно было получить внутри метода доступ к self. А если поле объекта - функция, то избежать получения первым аргументом ссылки self на этот объект.

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


Ссылка на сообщение

Хочу реализовать в сталкере классы, в которых методы будут менять свое поведение в зависимости от параметров объекта(замещаться аналогичными методами).

Пока это все в виде набросков, но схема предложенного выше такова:

Есть класс (скажем 1), задачей которого являет обновление состояния массива объектов другого класса (2). В этом втором классе обновление может быть реализовано как в виде метода (при инициализации в метод inject подставляется нужный в данной ситуации метод), так и в виде функции (этот метод предназначен, в свою очередь для вызова сторонней функции).

Можно конечно не заморачиваться и просто использовать обертку вида

function element_class:inject(...)
  self.fun(...)
end

Когда не требуется производить дополнительных действий, но меня интересует, можно ли обойтись без обертки, подставляя в метод стороннюю функцию и обрабатывая доступ к self в методах inject_xxx() как-то иначе.

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

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


Ссылка на сообщение
Xdlic, используй метатаблицы, в зависимости от условий переопределяешь метод.

 

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

 

Определение метода и его вызов, это просто сахар в Луа.

 

    Если функция определена как метод, то self при ее вызове  будет автоматически скрываться. Простая функция в этом случае будет вести себя несколько иначе - если вызвать ее как метод, параметры будут смещены вставкой self первым аргументом. Речь шла о замещении метода функцией, не расчитанной на получение self. И в первом посте было упомянуто - как вызвать метод (без двоеточия видимо?) и получить внутри него self не делая в заголовке каждой вариации метода лишних телодвижений с получением объекта, в котором метод был переопределен.

 Следует отметить, что вызов inject-метода через точку не подразумевает передачу туда self в явном виде, во время вызова, иначе это скатилось бы к тем же громоздким ветвлениям.

 

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

 

    В чем-то вы правы, но.. Работа вариантов метода полностью определяется внутренним контекстом объекта и не зависит от передаваемых значений. В данном случае было задачей убрать затратные проверки при каждом обновлении.

 

    Заглянул по интересующему вопросу в мануал из шапки и подчерпнул кое-что интересное для себя. Изложенное мной выше достаточно легко реализовать с помощью замыканий - переносим объявления подобных функций в тело функции __init() и они автоматически в своем окружения получают переменную self (замыкаются на окружении объекта). Для этого нужно задавать такие функции через точку(не как методы класса), а внутри спокойно работать, как если бы эта функция была объявлена методом.

















class "element_class"
function element_class:__init(name)
    ...
    function element_class.inject_std(...) -- функция через точку
        -- выполняем стандартные действия, self - ссылка на этот объект
    end
    function element_class.inject_greatdata_debug(...)
        log("DL_CALL, ELEMENT_CLASS:120 call greatdata_debug")
        -- выполняем нужные действия
        self:set_param("inject_std") -- меняем поведение интерфейса inject

    end
    if <стандартное состояние> then
        self.inject = self.inject_std
    elseif <куча проверок> then
        self:set_param("inject_greatdata_debug")
    end
end
function element_class:inject(...)
    -- выполняем действия для неинициализованного состояния объекта
end
-- функция для смены состояния объекта и текущего обработчика, ее по идее бы
-- вообще реализовать в "приватной" форме, но пока не нашел элементарного варианта

function element_class:set_param(param) -- передаем имя нового обработчика, к примеру
    -- а здесь переключаем состояние объекта и меняем значение element_class.inject

    self.inject = self[param]
end

    Ну, вобщем виде, применение данной схемы управления методами достаточно "дорогое" и имеет смысл там, где слишком много действий нужно выполнить для определения - какую-же ветку использовать на этот раз? вместо этого мы сильно нагружаем инициатор, но его ведь нужно вызывать только один раз :)

    В целом такой подход к написанию кода имеет много тонкостей и не подходит в реализации типичных алгоритмов. Но с его помощью вполне возможно реализовать нечто вроде "приватных" переменных/функций, которые никак не получить извне объекта.

    Ну и на последок. Меня заинтересовала сама возможность изменять метод объекта так,

чтобы он всегда соответствовал задаче, решаемой этим конкретным объектом на данный момент.

 

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

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


Ссылка на сообщение

   В ходе своих наработок в Lua для сталкера сделал отметил несколько любопытных моментов:

 

1. По поводу функций table.insert, table.remove и оператора #:

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



for i = 1, #tb do tb[i] = nil end

   При этом функции insert и remove продолжают работать со старым значением индекса, а вот оператор получения длины выдает, что таблица пуста. Собственно смысл: если сейчас попытаться вставить в таблицу значение table.insert(tb, value), то оно встанет как раз по месту мифического конца индексированной части. И мы получим массив вида:

{nil, nil, nil, nil, nil, nil, nil, nil, nil, value}. В случае того же SciTe позиция последнего элемента обновляется при заниливании, а в сталкере этого не происходит.

   Собственно вопрос, можно ли как-то повлиять на значение этого внутреннего счетчика или следать метаметод, обновляющий его?

 

   Получается, что в сталкере задание значения по индексу n+1 счетчик попросту не обновляет, а функции вставки/удаления полей игнорируют текущую структуру таблицы.

 

   Upd: Забыл указать, версия ТЧ 1.0006

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

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


Ссылка на сообщение

Внезапно рухнула сеть, поэтому не отписывался.
Описание теста: платформа ТЧ 1.0006(топология), встроено расширение луа от артоса(by RvP), вернул родной xrLua - результаты теста теже. (Заодно проверил на 4 патче(+от солянки) с чистой геймдатой) - также вставляет не в 1ю позицию.)

local tt = {1,2,3,4,5,6,7,8,9,10,11} -- таблица
local s
my_print_table("начинаем тест", tt)
table.insert(tt, "test1") -- первое обращение к таблице, счетчик создан
for i = 1, #tt do tt[i] = nil end -- удалили поля
-- table.remove(tt, 1)
my_print_table("пустая таблица", tt) -- видим пустую таблицу
table.insert(tt, "test2") -- вставили поля, у меня они встают в 11, 12 ячейке соотв.
table.insert(tt, "test22")
-- table.remove(tt, 1)
my_print_table("добавили 2 поля", tt)
table.insert(tt, "test3")
table.remove(tt, 1)
my_print_table("еще одно поле, сместили все поля", tt) -- все поля встают где-то в районе 9-14 индекса
table.remove(tt, 1)
my_print_table("еще сместили все поля", tt)
-- for i = 5, 11 do tt[i] = i end
table.remove(tt, 1)
table.remove(tt, 1)
my_print_table("fase 7", tt)
-- for i = 1, 11 do tt[i] = nil end
-- for i = 1, 11 do tt[i] = i end
table.insert(tt, 4, "index")
-- table.insert(tt, "index2")
my_print_table("добавляем поле в середину таблицы, конец теста", tt)
Функция вывода таблицы
function my_print_table(text, tt)
qqq(text)
s = "parse tt= " for i = 1, 20 do s = s..string.format("[%i]=%s, ", i, tostring(tt[i])) end qqq(s)
end


Урезанный лог, удалил nil-ы до интересующих ячеек и после

начинаем тест
parse tt= [1]=1, [2]=2, [3]=3, [4]=4, [5]=5, [6]=6, [7]=7, [8]=8, [9]=9, [10]=10, [11]=11, [12]=nil
пустая таблица
parse tt= [9]=nil, [10]=nil, [11]=nil, [12]=nil, [13]=nil, [14]=nil,
добавили 2 поля
parse tt= [9]=nil, [10]=nil, [11]=nil, [12]=nil, [13]=test2, [14]=test22,
еще одно поле, сместили все поля
parse tt= [9]=nil, [10]=nil, [11]=nil, [12]=test2, [13]=test22, [14]=test3,
еще сместили все поля
parse tt= [9]=nil, [10]=nil, [11]=test2, [12]=test22, [13]=test3, [14]=nil,
fase 7
parse tt= [9]=test2, [10]=test22, [11]=test3, [12]=nil, [13]=nil, [14]=nil,
добавляем поле в середину таблицы, конец теста
parse tt= [4]=index, [5]=nil, [6]=nil, [7]=nil, [8]=nil, [9]=nil, [10]=test2, [11]=test22, [12]=test3
test_4 started from 11311.940429688


Как видим - или у меня такой особый сталкер, или повторные table.insert/remove напрочь игнорируют изменения таблицы. Напоминаю - таблица была занилена после первого их использования.

 

как это игнорируют? Нормально они работают, в т.ч. и на 6 патче, все обновляется, и оператор, и значения.

Возможно я не так выразился, имеется ввиду, что позиция по умолчанию для table.insert/remove в сталкере не обновляется, если ранее они были вызваны для заполненной индексом таблицы. Т.е. берем массив, что-то делаем с ним через вставку/удаление, разрушаем массив nil-ми.. следующие вставки/удаления проходят по старой позиции, хотя перед ней может быть и 100500 пустых. Фактически, я не вижу в своем тесте, чтобы каждая новая вставка/удаление учитывали текущую длину массива.
Изменено пользователем Xdlic

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


Ссылка на сообщение
А нужно удалять поля

Давайте оставим этот момент в стороне. По отношению к индексным массива в сталкере вообще никак иначе обращаться категорически не рекомендую(как раз из за необновляющегося счетчика).

Собственно я отправил свое сообщение с мыслью, что получается в движке сталкера функции добавки/удаления реагируют на изменения таблицы совсем не так, как функция #t.

Вопрос имел целью узнать, как влиять на выбор текущей позиции в этих самых table.insert/remove.

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

Пример обертки, которая корректирует значение позиции текущего конца индекса



function insert_two(tab, pos_or_val, val)
    if val then
        table.insert(tab, pos_or_val, val)
    else
        table.insert(tab, #tab+1, val)
    end
end

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

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

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


Ссылка на сообщение

А почему они должны реагировать так же, как #t?

Хотя бы затем, что имено если бы они реагировали также как #t, то это позволяло бы работать в сталкере с концом строки по упрощенному варианту t[#t+1] = val и t[#t] = nil вместо вызовов функций. В чистом луа как раз это и работает.

Никто не исследовал вопрос, где хранятся эти созданные счетчики? В недоступной части таблиц или где-то внутри движка Lua/Сталкера?

Upd:

insert работает абсолютно так же.

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

Ну хорошо, раз в сталкере нельзя работать с индексами по упрощенному варианту, значит будем каждый раз вызывать соответстующие функции... а если одновременно нужно этими инсертами забить 100500 значений в таблицу, которые заранее нельзя задать? Мы получим ощутимый нагруз от этих 100500 вызовов функции table.insert(ну ладно, пусть еще можно ссылку хранить локально). Или в этом случае полностью придется отказаться от индексных массивов и получать случайный перебор элементов из pairs().

Upd2:

И да, смысла в предложенной обертке меньше, чем 0. Чтобы она работала, нужно в ней получать доступ к счетчику и вручную его менять.

Upd3:

Внезапно работает. 

Видимо я и вправду не умеею внятно изъяснять свои мысли/идеи. Твой вариант уже не будет работать совместно с table.insert/remove. Стоит их один раз вызвать и счетчик перестает зависеть от структуры таблицы. Про индексный массив с 100500 строк, тогда уж проще сделать обертку не вокруг самих функций, а вокруг метода. Т.е. реализуем свои table.insert_new/remove_new

function table_insert_new(t, pos_or_val, val)
    local end_pos = #t
    if val then
        if pos_or_val < end_pos then
            for i = end_pos, pos_or_val, -1 do
                t[i] = t[i-1]
            end
        else
            t[pos_or_val] = val
        end
    else
        t[end_pos+1] = val
    end
end

И подобное для remove, вроде алгоритм правильно описал

В результате не будет этого статического счетчика позиции для добавления/удаления, который я вижу в сталкере. А самое главное - можно будет спокойно работать как с t[#t+1] = val, так и с новой версией вставки значений в СЕРЕДИНУ индексного массива.

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

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


Ссылка на сообщение

@Xdlic, воистину ты занимаешься фигней.

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

Ладно, оставим в покое этот болезненный вопрос урезанного сталкерского Lua, интересно, какие еще особенности языка  ПЫСы сумели сломать к релизу?

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


Ссылка на сообщение

попробуй для получения "внутреннего счетчика" использовать table.getn(t).

 

    Не обратил ранее на эту функцию внимания, считая ее аналогичной #. Проверил заодно и table.setn - да, эти функции как раз и позволяют получать и задавать "счетчик" с которым работают функции вставки/удаления. Можно ручками забивать строки через t[#t+1] = val, а затем обновлять его table.setn(t, #t) и стандартные insert/remove будут "видеть", что с таблицей что-то случилось :).. и соответственно будут обрабатывать также и новые данные.

Но... зачем-то их ведь удалили из стандартного набора Lua-функций. В SciTe-ru пишет: 'setn' is obsolete.

 

P.S. Споры о том, можно ли так делать или это "моветон" пропущу мимо ушей.

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


Ссылка на сообщение

Столкнулся с необходимостью сравнить две переменные типа userdata (копаю gui классы из луа и там возникла в этом потребность) . Но меня ждал Великий Облом в виде ошибки:

 

LUA error: No such operator defined
В начале вылетел при проверке на неравенство, потом та же ошибка и при проверке на равенство...

Выдержка из руководства: "...и не имеет предопределенных операций в Lua, за исключением присваивания и проверки на равенство."

 

Вот так, в иных сборках это ошибки не вызывает, а в сталкере похоже метаметода сравнения двух объектов нет. По крайней мере rawequal выдает вменяемые данные, придется или пользоваться им или делать сравнение по меткам.

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


Ссылка на сообщение

Отсюда следует, что можно переопределить

метаметод '__eq', даже не сохраняя ссылку на

прежний, т.к. этот метод больше ни для чего не

нужен, и ничего не затрагивает.

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

 

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

Lua 9/10 времени занимается передачей управления внутрь метаметода и 1/10 - подстановка значения в выражение.

 

Скажу одно - создание в сталкере классов "что было объектно-ориентированым" неразумно. К примеру тот же менеджер событий OGSE - получение экземляра обернуто в дополнительную функцию.. лишь для того, чтобы не плодить новые экземпляры. Ну так сделали бы на базе таблицы: интерфейс "как у класса", а что таблица - все равно в обертку завернуто. Классы здесь, как чесать правое ухо левой пяткой. Повторюсь, без мало-мальской реализации автоподстановки кода классы еще хуже создания пользовательских метаметодов - производительность работы с данными падает в десятки раз.

 

Меня окончательно добило, когда. цикл обхода значений внутри класса по производительности оказался в 50-60 раз медленнее, чем из глобальной таблицы.

 

 

Проще взять за практику сравнивать юзердаты raw'вом, чем что-то прикручивать на локальном уровне. Оба поста скорее констатация факта;), но разумная критика приветствуется.

 

И да, сравнение юзердат оказалось несколько излишним. При достаточной синхронизации учасков кода оно совершенно не нужно.

Upd пропустил пост

Не уверен, что именно это тебе нужно, но

попробуй нечто вроде tostring(CUIStatic) ==

tostring(CUIStatic).

И будет внутридвижковое нефатальное исключение, по просту зависнет. У меня tostring nil'ы жрет, а юзердатами давится
Изменено пользователем Xdlic
  • Нравится 1

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


Ссылка на сообщение
  • Недавно просматривали   0 пользователей

    • Ни один зарегистрированный пользователь не просматривает эту страницу.
×
×
  • Создать...