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

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


Malandrinus

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

RvP, а не проще тестировать не в 'две руки'? Как грится ... одна голова хорошо, две - лучше, но и другие не помешают. ;-)

Ведь это же не мод, в котором поигральщики от глюков сопли распускают, а инструментарий для ...

(сорри за оффтопик)

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

RvP, а ты не заметил что 'две руки' - в кавычках, да еще и прописью? Все же это не информация и не констатация некоего факта, а образное выражение. Как, в прочем, и про головы ...

 

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

Ну да ... ежели нет желания/потребности, то и не настаиваем и не навязываем(ся). :-)

[x]

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

RvP

Работает с любым патчем ТЧ.

1. Установил пакет dll-ек по инструкции. Пока никаких доп. вызовов из скриптов ( RvP() ) не произвожу.

2. Попытка запуск игры на 1.0004 и 1.0005 заканчивается выбросом окна ошибки от XR_3DA

"Точка входа в процедуру ?allocate@class_rep@detail@luabind@@QBE?AU&$pair@PAXPAX@stlp_std@@PAUlua_State@@@Z не найдена в библиотеке DLL XRLUA.DLL."

3. Запуск на 1.0006 упирается в первую же строку обработки стринга функцией 'string.format'.

Обход ее приводит к следующим ошибкам по всем функциям класса 'string'.

4. Активация вызовом RvP() из '_g.script' позволяет увидеть тестовый вывод в abs(string) - отображается в логе, но не меняет картины ... Игра рушится на обработках строк.

 

что не так (игра пока чистая, без модов/правок)?

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

RvP

С обновленным вариантом на 1.0004/5 игра стартует, но(!) как и ранее описывал для 1.0006 - при первом же вызове методов класса 'string' игра вылетает с фатал-еррором, ругаясь на nil-евые значения методов.

Внеся небольшую правку в _g.script оригинальной игры:

RvP()

function printf(fmt,...)
  --log(string.format(fmt,...)) --/< тут вылетает аналогично 23-ей строке
  abs("Test_Extended_Lua")
  abs("1st:"..fmt)
  abs(string.format(fmt,...)) --/< 23-я строка
end

Имеем вывод в логе первых двух строк и фатальный вылет с '_g.script:23: attempt to call field 'format' (a nil value)'.

Если тут заремить - вылетит далее на первом же string.XXX ...

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

RvP, с последним вариантом игра (и с модами) пошла ... Но похоже есть нюансы при включенном функционале.

Подробнее - уже завтра.

 

Добавлено через 52 мин.:

P.S. Новый вариант math.random([a[, b]]) багует ...

 

math.random(1,100) => 169 и т.п. Т.е. вылетает за диапазон, что кое где критично.

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

RvP, спасибо за подправленную версию.

Гоняю ... на игре с модом, пока замечаний нет, только положительные эмоции (глядя в более чистый/компактный лог).

Очень полезно, что появилось пространство 'debug', в частности трассировка (debug.traceback).

Несколько пожеланий-рекомендаций:

1. Переименовывать исходную библиотеку не с суффиксом '_old', а с чем-то более нейтральным, например '_src'. У многих 'old/bak' ассоциируется с ненужным мусором, да и чистильщики дисков порой имеют в настройках ткакие маски. Т.о. можно ненароком удалить необходимое.

2. Уточнить в реадме, что версия 1.0000-1.0003 и для 1.0006 предназначается.

3. Дать команде вывода в лог наименование более функционально значимое чем 'abs', например 'to_log' иль 'logf'.

 

И вопрос: Каковы дальнейшие планы по работе с этим расширением? Планируется ли дальнейшее расширение функционала со стороны автора или это на откуп самим модмейкерам?

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

RvP

1. Вполне годится и '_gsc', главное уйти от довольно двусмысленного '_old'.

3. В редакторе подсветку каждый сам себе может править, да и у каждого 'свой' редактор ....

А вот то, что именно и подсвечивается 'не своим' цветом и напоминает о математическом методе, а не консольном - несколько сбивает.

Так как ты и говоришь - так и использую сейчас, но сторонник давать имена функциям/методам/переменным по принадлежности к функционалу а не ... с потолка. Проходит время - и начинаешь вспоминать, что ж означает данное обозначение, оторванное от контекста. :-)

Имя стандартного 'log' - тоже не считаю удачным, но по аналогии с 'printf' и аналогичными - 'logf' (ИМХО) вполне и узнаваем и понятен.

 

Ну и ... не знаю, востребованы ли string.trim_r или string.trim_l, а вот string.trim_w (чистка и усечение по первому слову) будет более востребован.

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

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

Что-то типа:

return string.match(str, '^%s*(%S*)') --/ trim_w

в отличии от обычного трима:

return string:match(str, '^%s*(.*%S)') --/ trim

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

Gun12, ты полностью прав, поэтому и написал 'типа этого' ... а я осталял себе как напоминалку, т.к. могла возникнуть потребность фильтровать еще и '\160' ...

Кстати, твой вариант на ~30% быстрее упомянутого мною. :-)

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

7.9

1. Означает именно то, что прежняя библиотека ПРОДОЛЖАЕТ использоваться под новым именем, а не становится рудиментом. Без нее (под новым заданным именем) работа невозможна. По сути новая dll-ка - это надстройка, в которую добавлен дополнительный функционал, через которую вызываются штатные библиотеки Lua5.1 и транслируются запросы в прежнюю библиотеку из исходной игры.

2. Да, именно не попробовать, а использовать. Иного варианта нет.

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

Учитывая, что любому приложению ОС выделяет определенные ресурсы (в том числе и объем ОЗУ), и приложения распределяют полученные ресурсы как задумали программисты, написавшие приложения - размер строк/таблиц/... определяется объемом выделенной приложением памяти для хранения/обработки подобных переменных ...

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

malandrinus, полностью солидарен с высказанным. Та же таблица умножения является универсальной, но только сам человек может исходя из потребностей брать из нее то, что ему нужно.

Выше приведенный пример банально укладывается в простейшее:

local MyFunc = function() return math.random(-4,5)*500 end

Меняя цифирьки - получаем условную универсальность.

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

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

xStream, работа кодера в команде конечно же имеет свои особености и критерии. Хотя ... и времена команд практически прошли (в Сталкере) и в основном работа или одиночек или строго параллельно кодер+моделлер+дизайнер+ ... Т.е. один никак не мешает другому.

Забывать о ясности и даже красивости кода при его публикации (ведь моды - публичный продукт) никогда конечно не стОит, но и приносить оптимизацию/скорость в угоду 'рюшечкам' так же не разумно. Чаще всего, функция/блок/модуль должны быть не изнутри понятны, а ясна/понятна/и проста суть их применения для других, т.е. точки входа/выхода.

В общем все должно быть в меру, разумном сочетании и в обязательной привязке к контексту задачи. И упражнения в изощренности кода и в его упрощенности - вредны при их передозировке (эти слова для *Shoker*'а, дабы не замудрился ...). :-)

(сорри за оффтопик)

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

xStream, к сожалению, времена идут но ... новички как ранее начинают, как правило с итераций по 65К таблицам ...

Но это уже вопрос больше из области 'не хочу учиться, а хочу жениться', т.е. не уча азы и наработанное - сразу выдать на гора 'свой мод'.

Насчет отдельных таблиц - конечно же решение верное, но ... и не напасешься порой таблиц на все про все и не все в таблице учтешь.

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

Тут недавно про 'coroutine' GUN12 нам всем напомнил, и вышел довольно неплохой промежуточный вариант распределенного сканирования:

Вот это на апдейте у актора:

local iPrgScan =  25  --/ периодичность сканирования Зоны (25ms => ~15 минут)

function OnUpdate(uo,delta)
  --/ непрерывное циклическое сканирование локаций по всему диапазону ID объектов (1...65534)
  if coroutine.status(CoScan) == 'suspended' then --/ статус сопрограммы: приостановлена?
    local bFlg,Result = coroutine.resume(CoScan, iPrgScan) --/ (пере)запуск сопрограммы
    if not bFlg then --/ ошибка при выполнении сопрограммы
      printf("OnUpdate:Error_CoScan=[%s]:<%s>", Result, "Error!")
    end
  end
end

А это основа сорограммы:

local tOAMain,tTmpMain = {},{} --/ дин.массив основных объектов (используется в m_news.script)
local tOwners,tTmpOwn  = {},{} --/ дин.массив владельцев предметов

local CoScan = coroutine.create( --/ сопрограмма сканирования объектов игры
  function(iTimeLimit) --/ функция сканирования объектов игры с ограничением времени работы
--    log("%s:CoScan:TimeLimit=[%s]:[%s]", sModule, iTimeLimit, ">") --/#~#
    local oPTimer,iTimeStop,soObj = nil,nil,nil
    local iCurID = 65535 --/ начальный ID
    while true do
      if iCurID >= 65534 then --/ проверка: цикл закончен (или предустановка)?
        --coroutine.yield(iCurID) --/ приостановка (опционально)
        if iCurID == 65534 then
          tOAMain,tOwners = tTmpMain,tTmpOwn --/ обновляем основные массивы
          log("%s:CoScan:Owners=[%s]:Time=[%s]%s", sModule, GetSizeTable(tOwners), Get_StrTime(), "") --/#~#
          --Print_Tables_OffLineAlife() --/#~# for Debug
        end
        --/ предустановки для нового цикла:
        tTmpMain,tTmpOwn = {},{} --/ временные массивы
        iCurID = 0 --/ начальный ID (опускаем 0-actor)
        iTimeStop = iTimeLimit --/ начальная установка для таймера
        oPTimer = profile_timer() --/ (пере)создаем объект таймера
      end
      iCurID = iCurID +1 --/ переход к следующему ID
      soObj = sim:object(iCurID) --/ очередной серверный объект игры
      if soObj then --/ объект в игре? ('пустышки' пропускаются и таймером)
        oPTimer:start() --/ старт таймера (на продолжение)
        --/ -- обработка объекта ----------------
        this.Build_TableObj(soObj,iCurID)
        --/ -------------------------------------
        oPTimer:stop() --/ (при)остановка таймера
        if oPTimer:time() > iTimeStop then --/ проверка: не закончен ли лимит времени?
          coroutine.yield(iCurID) --/ приостановка цикла
          iTimeStop = oPTimer:time() + iTimeLimit --/ обновляем для текущего подцикла сканирования
        end
      end
    end
    printf("%s:DoScan:<Error!>")
    return 65535 --/>
  end
)

Т.о. и эксклюзивные таблицы доступны и коллбэки их актуализируют и ... периодически каждые 10...15 минут эти таблицы освежаются просканнированным глобальным диапазоном по всем объектам, котрый не единовременно выполняется (давая лаги в игре), а размазанно по всему временному интервалу. Т.е. вариант включает в себя все достоинства известных вариантов, исключает лаги и почти исключает зависимость от 'пропущенных' недоработок коллбэков.

Будучи запущенным один раз - на протяжении всей игры периодически сканирует всю Зону по всем объектам создавая соответствующие 'тематические' таблицы. И все это практически в фоновом режиме.

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

Имел ввиду конечно де не универсальность при создании 'таматических' таблиц, а то, что и этого порой не достаточно.

И конечно же доп. нить (тред) дает накладные расходы, не упомянуто и то, что создаются двойные таблицы (рабочая и временная), что тоже минус. Но(!) тут или иметь периодические 'быстрые' итерации по всему диапазону ID (65К) и иметь лаги, или ... размазать эти лаги во времени, чтобы их не заметно было.

Т.о. получился не обособленный 'еще один' вариант, а вариант 'симбиоз' с глобальным сканирование 65К + создание тематических (архитектурных) таблиц + распределение издержек во времени == в результате все довольны, игрок не видит лагов, кодер работает со своими таблицами, экономя другие ресурсы ... :-)

А отлаживать то как раз проще, обработка объектов в вынесенном блоке и имеет свою диагностику. Один раз написав - даже не приказался к отладке, т.к. все заработало сразу и работает как часы (да и проще просто врядли бывает, ИМХО, "ломаться" нечему).

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

Ну ... с перехватчиком Lua от alpet (ссылка) и добавленным дебагером от RvP уже и переполнения стека не так страшны и сложны в отладке как в былые времена. ;-)

(тоже и извиняюсь за некоторый оффтопик и прекращаю ... на время)

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


Ссылка на сообщение
*Shoker*: Знаю что это из ряда фантастики, но штатными методами LUA можно ли "выключить" весь скрипт или функцию? Тоесть допустим у меня скрипт с рядом функций, которые из разных мест вызываются переодически. Могу ли я тупо "остановить" их. Тоесть чтобы если функцию вызовет какой то скрипт, игра не вылетела, но и функция не выполнилась?
(выскажу свое ИМХО)

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

1. Функции, в Lua - один из типов информации, с которыми возможны большинство стандартных операций, т.е. присвоения, занИления, сохранения в таблицах и пр.

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

3. Аналогично и со 'скриптами' (если под ними понимаем файл-скрипт). Обращаясь не к нему (файлу) непосредственно, а именно через метатаблицу (_G), ничто не мешает как угодно заменить на нужное _G[script]. Т.е. по сути подменять один скрипт другим. Примеров даже в имеющихся модах предостаточно.

4. Ну а чтобы не вылетело - все зависит от того, что обжидает в ответ вызвавшая функция и как обрабатывает полученный ответ. Ничто не мешает перед вызовом перепроверять и наличие собственно скрипта и/или функции в нем, и конечно же по полученному ответу не форсировать операций с неполученными 'штатно' параметрами.

 

Составлять же функцию 'на лету' - и Gun12 дал пояснение и ... А ЗАЧЕМ? Все одно, составленная таким образом функция должна быть подчинена некоему алгоритму и соответствовать некоему шаблону. Т.о. заложив шаблон или даже готовый набор возможных вариантов и выбирая из него нужное - вполне безопасно можно подстраиваться под нужное. Пример имеется даже для прекондишенов в диалогах (см. AMK-mod), в котоором рандомятся условия и по ним в собственно скрипт добавляются требуемые функции.

 

А вообще взглянул бы ты на известный тебе мод, в котором давным давно и модули/скрипты опциональны и отключаемы 'на лету' и функции 'на лету' добавляются/заменяются хоть в _G, хоть в иных скриптах.

 

Т.о. никакая это не фантастика, а вполне тривиальные вещи, способ реализации которых зависит от конкретного контекста.

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

Andrey07071977, пара помарок:

 

1. Функция exec принимает аргументом sName, по наличию которого вызывается функция assert, но в нее передается неопределенный аргумент sFilePath ...

 

2. В функции sandbox при выводе в лог используется также неопределенная sResult вместо (вероятно) полученной oResult (от pcall).

 

Насчет проблемы с оператором '...' - что-то непонятно ... Укажи места кода в которых у тебя затыкалось.

Заменил все твои таблицы на оператор и проверил в игре - никаких заморочек.

Использовал так:

--[[
  Call functions in protected environment (original: http://www.amk-team.ru/forum/index.php?showtopic=6458#entry258260)
  @param string sType - "file" or "chunk". Required. TODO: "chunk" functionality
  @param string sName - file path. Optional. Path of the file to load relative to bin, will use default (aa_test.script) if nil.
  @param boolean bFunc - execute one function with arguments. Optional. By default will execute function test(...)
  @param table tArgs - arguments for function above. Optional. TODO: find out why ... didn`t work.
  @return object oResult - function execution results (if any)
--]]

function sandbox(sType, sName, bFunc, ...)
  local bStatus, Result = pcall(exec, sType, sName, bFunc, ...)
  if bStatus ~= false then
    return Result --/>
  else --// Ouput to screen or log
    --ODS("~C07[~T] ~C0C[ERROR] ~C07in ~C0Asandbox()~C07 call - ~C0B"..tostring(Result).."~C07")
  end
end

--[[
  Execute file or chunk (original: http://www.amk-team.ru/forum/index.php?showtopic=6458#entry258260)
  @param string sType - "file" or "chunk". Required. TODO: "chunk" functionality
  @param string sName - file path. Optional. Path of the file to load relative to bin, will use default (aa_test.script) if nil.
  @param boolean bFunc - execute one function with arguments. Optional. By default will execute function test(...)
  @param table tArgs - arguments for function above. Optional. TODO: find out why (...) didn`t work.
--]]
function exec(sType, sName, bFunc, ...)
  if sType == "file" then
    if not sName then sName = "..\\gamedata\\scripts\\aa_test1.script" end
    local Return = assert(loadfile(sName)) --/ sName == sFilePath
    if type(Return) == "function" then
      if bFunc then
        Return()
        test(...) --// execute specific function with arguments
      else
        Return()
      end
    else --// Ouput to screen or log
      --ODS("~C07[~T] ~C0C[ERROR] ~C07in ~C0Aexec()~C07 call - ~C0B"..tostring(Return).."~C07")
    end
  elseif sType == "chunk" then
    --// TODO: chunk functionality
  else --// Ouput to screen or log
    --ODS("~C07[~T] ~C0C[ERROR] ~C07in ~C0Aexec() ~C07call - invalid sType: ~C0B"..tostring(sType).."~C07")
  end
end


--/ "Пример использования (generic_object_binder:hit_callback):"

--/ Callback:
function generic_object_binder:hit_callback(oObj, fAmount, vDirection, oWho, iBoneIndex)
  aa_util.sandbox("file", "..\\gamedata\\scripts\\aa_test1.script", true, oObj, fAmount, vDirection, oWho, iBoneIndex)
end

--/ aa_test1.script:
function test(oObj, fAmount, vDirection, oWho, iBoneIndex)
  local sWhoName = oWho and oWho:character_name()
  local sWpn = oWho and oWho.active_item and oWho:active_item() and oWho:active_item():section()
  --local sBodyPart = aa_util.get_body_part(iBoneIndex)
  local fActorHealth = oObj and oObj.health
  if fActorHealth and fActorHealth <= 0.5 then
    oObj.health = 1
  end
  --/ вевести все на экран или в лог (я использую Lua перехватчик, поэтому вызовы закомментировал - думаю разберетесь)
--[[
  ODS("~C07[~T] ~C0E[INFO] ~C06Actor hit details: ----------------------------~C07")
  ODS("~C07[~T] ~C0E[INFO] ~C07Hit by: ~C0B"..sWhoName.."~C07")
  ODS("~C07[~T] ~C0E[INFO] ~C07Amount: ~C0D"..string.format("%2.1f", fAmount*100).."~C07")
  ODS("~C07[~T] ~C0E[INFO] ~C07Body P: ~C0B"..iBoneIndex.."~C07")
    ODS("~C07[~T] ~C0E[INFO] ~C07Health: ~C0B"..string.format("%2.1f", fActorHealth*100).."~C07")
  ODS("~C07[~T] ~C0E[INFO] ~C07Weapon: ~C0B"..sWpn.."~C07")
--]]
end

 

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

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


Ссылка на сообщение
xStream, и не соглашусь с тем что "полная" ;-), т.к. уж лучше такая, чем никакая ... И обычно высказывая мнение приводят хоть какие-то аргументы (кроме "на мой вкус ...") или альтернативы.

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

xStream, отвечу с конца (так и оффтопик в тему ляжет).

 

Отпишусь обязательно, но и только что за комп сел и скачал. На вскидку: что-то на половину знакомое, что-то уже заинтересовало, но (вот!) как раз отсутсутвие привычной нотации и достаточно куцая, а порою отсутствующая, семантика - не дает сразу глубоко "въехать в код" ... Согласись, что читая: e, rev, obj, who - не сразу приходит на ум тип данных/объектов, что замедляет 'осмысленное чтение/восприятие'. Поэтому вначале посмотрю, осмыслю и опробую, дабы не отписываться банальностями (хулить иль восторгаться) иль не критиковать понапрасну или обмишурившись ... :-)

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

По своему опыту, именно "мозолящие глаза" префиксы выручают в поисках ошибок при некорректных применениях/использованиях типов данных. Как пример, только сегодня потерял десяток минут в погадалках ошибки, возникшей после оптимизации кода. Было obj:position() для клиентского объекта, а исправил код под серверный, и ... недоглядел. А вот привычное se_obj или soObj - сразу бы резануло глаза и подсказало бы гнилое место ...

 

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

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


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

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