Информация: Уважаемые посетители! В течение нескольких месяцев на форуме существовала проблема с регистрацией новых пользователей, о которой администрации стало известно недавно. Если вы ранее пытались зарегистрироваться на форуме, но не получили на ваш e-mail письмо с ссылкой для подтверждения регистрации, просим вас зарегистрироваться повторно. Приносим извинения за доставленные неудобства. Если вы все еще испытываете проблемы с регистрацией на форуме, обратитесь за помощью на e-mail: mr.angelo@railroadsim.net

LuaScript - для RS

Другие вопросы и проблемы разработки дополнений

Re: LuaScript - для RS

Сообщение Света » 16.08.2016, 17:05

Всем доброго дня.

Представляю вниманию разработчиков функцию Timer, предназначенную для вставки в скрипт.

Назначение
Функция предназначена упростить контроль временных интервалов в процессе выполнения кода. Каждый, кто писал скрипт, знает, что для того, чтобы сделать задержку, нужно создать переменную, поместить в неё значение задержки, потом периодически вычитать из этой переменной какое-то число, отслеживая, когда значение задержки станет равным или меньше нуля.
Дополнительное неудобство - если до окончания отсчета эту переменную заново перезапустить, отсчет начнется сначала. Чтобы этого избежать, приходится выдумывать новые защитные переменные.
Когда такой "таймер" использован, он продолжает опрашиваться ЦП, хотя в этом нет необходимости.
Каждый такой таймер (а их в скрипте локомотива предостаточно) - это дополнительный множитель всех неудобств, описанных выше.
Мне это надоело. Предлагаемая функция предназначена несколько упростить эту процедуру.

Адаптация в скрипт
Функция вставляется в конец скрипта. Никакие правки не нужны.

Для инициализации рабочей таблицы, вписываем её в функцию Initialise():
Код: Выделить всё
TabTimerData = {}

Для обеспечения счёта периодически делаем вызов из функции Update ( time ):
Код: Выделить всё
 Timer (time)

Использование/вызов
Для того, чтобы запустить таймер, надо отправить в функцию имя таймера и значение задержки в секундах:
Код: Выделить всё
Timer ("timer_1", 15)

Обратите внимание: пока таймер отсчитывает выдержку, его рестарт такой командой невозможен. Это сделано умышленно. Если нужно перезапустить отсчет сначала, применяется другая команда:
Код: Выделить всё
Timer ("timer_1", 15, "restart")

Контролировать окончание отсчета можно 2 способами. Первый способ - это контроль фактического окончания отсчета. Вариант использования:
Код: Выделить всё
   if Timer ( "timer_1" ) then
      -- блок операторов, если время вышло
   else
      -- блок операторов, если отсчет не завершен
   end

Обратите внимание - если опрашивается несуществующий канал таймера, функция также возвратит "false", как и при незавершенном отсчете.
Второй способ - контроль времени, которое осталось до окончания отсчета. Вариант использования:
Код: Выделить всё
   Pause = Timer ("timer_1", "ctrl")

В переменную Pause будет загружено оставшееся время (сек) в числовом формате. Если отсчет закончился, будет загружена строка "timeroff". В случае опроса несуществующего канала будет возвращено значение "nil".

Если необходимости в каком-либо канале таймера нет, его можно разрушить:
Код: Выделить всё
   Timer ("timer_1", "clear")

Такая команда уничтожит канал "timer_1". Это немного сэкономит время, затрачиваемое на обработку функции.
Автоматического удаления каналов с завершенным отсчетом нет.

Количество каналов ограничено объемом оперативной памяти.
Разрешающая способность (сек, грубо) - 1/fps.

Сам код
Код: Выделить всё
function Timer (data_1, data_2, data_3)
   if type(data_1) == "number" then
      for k,v in pairs(TabTimerData) do
         if type(v) ~= "string" then
            v = v - data_1
            if v <= 0 then
               TabTimerData[k] = "timeroff"
            else
               TabTimerData[k] = v
            end
         end
      end
      return
   end

   if type(data_1) == "string" then
      if not data_2 then
         if TabTimerData[data_1] == "timeroff" then
            return true
         else
            return false
         end
      elseif data_2 == "ctrl" then
         return TabTimerData[data_1]
      elseif data_2 == "clear" then
         TabTimerData[data_1] = nil
      elseif not TabTimerData[data_1] or TabTimerData[data_1] == "timeroff" or data_3 == "restart" then
         TabTimerData[data_1] = data_2
      end
   end
end

Приветствуются пожелания по усовершенствованию. Также, несмотря на успешное тестирование, приветствуется любая информация по практическому использованию функции.
Забанена по собственному желанию. По важным вопросам связаться со мной можно через почту divovigna@gmail.com
Аватара пользователя
Света
 
Сообщения: 1338
Зарегистрирован: 18.06.2016, 19:38
Благодарил (а): 983 раз.
Поблагодарили: 1805 раз.
Играю в: Train Simulator Classic
Имя: Пандора

Re: LuaScript - для RS

Сообщение Skif » 16.08.2016, 18:19

У меня есть такая, которая в качестве аргумента принимает имя переменной/контрола, и выполняет задержку исходя из помещенных в настроечный массив констант. Т.е. работает не в виде "Сделайте мне задержку на N тактов" или "Сделайте мне задержку на N секунд" , а "Сделайте мне задержку на прокачку масла при пуске дизеля". Мне так удобнее - все настройки временных задержек переходных процессов у локомотива снесены в один массив в заголовке скрипта.


Хорошая кстати идея - выкладывать фрагменты кода.


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

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

Также можно использовать для реализации у локомотива "МСТС-ного" способа управления по принципу "Одно нажатие кнопки - одна позиция рукоятки". Рекомендуется в некоторых случаях в качестве
дефолтного метода управления контролами в TS. Руки пока не доходят написать унифицированный хендлер для управления локомотивом, займусь им, когда высвободится немного времени от тренажера. Но контроллер в моей машине уже работает близко к задуманной идее. Краны машиниста ждут своей очереди - не будут, как на ТЭМ2, проскакивать позиции (а у меня и сейчас не проскакивают, но будет еще круче).
Skif
 
Сообщения: 3750
Зарегистрирован: 01.10.2009, 17:42
Благодарил (а): 0 раз.
Поблагодарили: 2 раз.
Имя: Дмитрий

Re: LuaScript - для RS

Сообщение sfateev » 17.08.2016, 17:02

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

Skif, выложи, пожалуйста. Честно говоря, я думал, что дрожание стрелок задаётся анимацией. :book:
Аватара пользователя
sfateev
 
Сообщения: 845
Зарегистрирован: 29.05.2006, 10:07
Откуда: Москва
Благодарил (а): 532 раз.
Поблагодарили: 73 раз.
Имя: Святослав

Re: LuaScript - для RS

Сообщение Skif » 17.08.2016, 18:03

Можно и анимацией - но анимацией то ты все равно управляешь посредством скрипта!

Я для себя сделал вывод - самый лучший способ двигать любой анимированный узел на модели (форточку, дверь, створки жалюзи, вентилятор...) - это привязать к ним контрол и уже контрол скриптом двигать на нужные значения. А не ломать каждый раз голову - на какой кадр (а точнее - временную метку) сдвинуть анимацию и в какой момент ее сбрасывать. Удобнее, знаете ли.

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

Почему удобнее линковать с анимированными деталями контролы, а не переменные?

1. На этапе отладки можно назначить в Input Mapper управляющие клавиши и подвигать стрелку прибора руками (если это не системный контрол вроде BrakeCylinderPressureBAR - их двигать нельзя).

...и посему крайне желательно ВСЕ приборы в кабине привязывать к СОБСТВЕННЫМ контролам, а системные к ним линковать в скрипте - непосредственно или через дополнительную логику. У меня так реализован редукционный клапан питательной магистрали, проверка плотности тормозной магистрали, сверхзарядка ПМ и УР (только визуальная, ни на что не влияет в тормозах. Для простоты.). Тогда на этапе отладки можно с клавиатуры двигать любые стрелки приборов (для сверки значений с тем, что видно на шкале), зажигать лампочки, имитировать рост температуры охлаждающей жидкости и т.п.

2. Потому, что из контролов удобно создавать логические цепочки if-elseif-else , AND, OR и производные от них, собирая схемы локомотивов (точнее - их поведенческие алгоритмические модели). И их значения видно в отладочном окне ControlStateDialog.

3. Потому, что их значения используются как триггеры для звуковой модели локомотива или вагона. Переменную посадить на триггер нельзя. А контрол - пожалуйста. И если у вас вентиль привода жалюзи должен сделать "ПШШШШ", когда створки повернутся на половину хода, то это можно сделать одним контролом. Не создавая дополнительных переменных (кроме тех, что выше по логической цепочке - есть ли питание вентиля от цепей управления, есть ли воздух в магистрали управления, не держит ли створки термостат и т.п. сахарок)


Рассмотрим еще такое понятие, как "виртуальный контрол". Это контрол, не имеющий интерфейса ввода и вывода. Т.е. к нему ни прикреплен Input Mapper, ни прилинкован анимированный узел модели для индикации состояния. Однако! Такой контрол может управляться скриптом - и следовательно, использоваться для управления звуковыми событиями или процессами в схеме локомотива.

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





Сегодня вечером постараюсь выложить функции.

Добавлено спустя 3 часа 8 минут 51 секунду:
Итак, обещанные функции.

UpdateControl (time, name, index, target, speed) и UpdateControlWithTremor (time, name, index, target, speed, gain, freq) - для КОНТРОЛОВ

UpdateVariable (time, name, target,speed) - для ПЕРЕМЕННЫХ . Варианта с дребезгом нет - так как не используется для вывода данных на приборы. А для расчетов в системе. Тремор
добавлять следует только к тому, что этот тремор физически испытывает - в данном случае, к стрелкам приборов.

Использование: применяется для перевода значений контролов и переменных в заданные с заданным темпом единиц/секунду (именно в СЕКУНДУ, а не В ТАКТ, чем так грешат дефолтные скрипты - скорость процессов в которых ой как зависит от ФПС сцены!). Поэтому можно использовать в том числе для расчета переходных процессов в тормозах, если они у вас не дефолтные.

----------------------------------------------------------

UpdateControl (time, name, index, target, speed)

time - системная переменная времени, имеет значение прошедшего времени с прошлого такта скрипта. НЕ ИЗМЕНЯТЬ. При использовании в теле функции подставить имя переменной времени из родительского цикла. Обычно это inteval.
name - имя контрола, который обновляется данным вызовом функции.
index - индекс контрола, который обновляется данным вызовом функции. Добавлено для совместимости формата данных с системными функциями TS вроде GetControlValue.
target - значение, которое нужно присвоить контролу. Направление движения определяется автоматически. Если значение выходит за диапазон контрола, будет достигнуто соответствующее предельное значение.
speed - скорость, с которой нужно обновить значение. Каждую секунду (игровую), к текущему значению контрола будет добавляться либо вычитаться значение speed, промежуточные значения (с частотой тактирования скрипта - читай, ФПС) рассчитываются в теле функции автоматически. Потому движение будет плавным.

Код функции. Вставить в Simulation скрипт, вне всех методов (т.е. в самом начале либо самом конце файла, вне операторных скобок)

Код: Выделить всё
function UpdateControl (time, name, index, target, speed)         

-- функция апдейта контрола на новое значение с задаваемой скоростью единиц/сек.
-- name - имя контрола
-- index - индекс контрола
-- target - целевое значение
-- speed - скорость перевода

n_value = Call ("*:GetControlValue", name, index);
n_step = time*speed;


if ( n_step > math.abs(n_value - target) ) then   -- устранение "дребезга" вокруг конечного значения.
   n_step = math.abs(n_value - target);
end

if (speed > 0) then

if (n_value > target) then
   Call("*:SetControlValue", name, index, n_value - n_step);
end
if (n_value < target) then
   Call("*:SetControlValue", name, index, n_value + n_step);
end

if (n_value == target) then
   Call("*:SetControlValue", name, index, n_value);
   return 1;
end

else return -1;

end
end


Пример использования в теле скрипта:

Код: Выделить всё
if (AirSpeed >= ApplicationRate) then
      UpdateControl (inteval, "TrainBrakeControl", 0, 0.42, 0.1*AirSpeed);
   end


Комментарий: в данном блоке кода производится сравнение скорости воздушного потока со темпом разрядки УР темпом служебного торможения. Если проверка возвращает True, значение СИСТЕМНОГО контрола (прошу это отметить) TrainBrakeControl с индексом 0 (а обычно 1,2 и так далее у локомотива и не бывает) приводится к значению 0.42 темпом (0.1 х AirSpeed) единиц/сек.

Во, заодно раскрыл секрет, как у меня тормоза разряжают фиксированным темпом вне зависимости от ФПС игры - кто-то интересовался, я помню.


-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Продолжим. Вторая функция.

UpdateControlWithTremor (time, name, index, target, speed, gain, freq)

time - системная переменная времени, имеет значение прошедшего времени с прошлого такта скрипта. НЕ ИЗМЕНЯТЬ. При использовании в теле функции подставить имя переменной времени из родительского цикла. Обычно это inteval.
name - имя контрола, который обновляется данным вызовом функции.
index - индекс контрола, который обновляется данным вызовом функции. Добавлено для совместимости формата данных с системными функциями TS вроде GetControlValue.
target - значение, которое нужно присвоить контролу. Направление движения определяется автоматически. Если значение выходит за диапазон контрола, будет достигнуто соответствующее предельное значение.
speed - скорость, с которой нужно обновить значение. Каждую секунду (игровую), к текущему значению контрола будет добавляться либо вычитаться значение speed, промежуточные значения (с частотой тактирования скрипта - читай, ФПС) рассчитываются в теле функции автоматически. Потому движение будет плавным.

gain - амплитуда синусоидальных колебаний, накладываемых на мгновенное значение на данном такте. Можно добавить генератор случайных чисел, но будьте осторожны - чтобы не зашкалил прибор. Это АБСОЛЮТНЫЕ значения, т.е. в тех же единицах, что и target.
freq - частота синусоидальных колебаний, накладываемых на мгновенное значение на данном такте. Герц, то есть колебаний в секунду. Обычно кратно RPM или аналогичному значению. У спидометра - частоте пульсаций в датчике скорости.

Код функции. Вставить в Simulation скрипт, вне всех методов (т.е. в самом начале либо самом конце файла, вне операторных скобок)


Код: Выделить всё

TremorCounter = {      -- счетчики для тремора стрелок приборов

   ["EngineRPM"]       = 0,
   ["GMPPress F"]      = 0,
   ["WaterTemp F"]      = 0,
   ["OilPress F"]      = 0,
   ["GMPPress R"]      = 0,
   ["WaterTemp R"]      = 0,
   ["OilPress R"]      = 0,
   ["Speedometer F"]    = 0,
   ["Speedometer R"]    = 0
}

-- В этом блоке ПЕРЕЧИСЛИТЬ ОБЯЗАТЕЛЬНО имена всех контролов в указанном формате, которые будут обновляться функцией UpdateControlWithTremor . Это счетчики колебаний синусоиды.
-- нужны для предотвращения сбоя фазы колебаний и защиты от переполнения переменной. Если кто-то найдет способ этот блок не использовать (см. код функции), это будет здорово.


function UpdateControlWithTremor (time, name, index, target, speed, gain, freq)

-- функция апдейта контрола на новое среднее значение с задаваемой скоростью единиц/сек и колебаниями.
-- name - имя контрола
-- index - индекс контрола
-- target - целевое среднее значение
-- speed - скорость перевода
-- gain - амплитуда синусоидальных колебаний
-- freq - частота синусоидальных колебаний

n_value = Call ("*:GetControlValue", name, index);
n_step = time*speed;
n_inc = 1.0;
if (TremorCounter [name] > 10) then -- защита от переполнения переменной-счетчика.
   TremorCounter [name] = 0;
end

TremorCounter [name] = TremorCounter [name] + n_inc*time;
tremor = gain* math.sin (freq * TremorCounter [name]);

if ( n_step > math.abs(n_value - target) ) then   -- устранение "дребезга" вокруг конечного значения.
   n_step = math.abs(n_value - target);
end

if (speed > 0) then

if (n_value > target) then
   Call("*:SetControlValue", name, index, n_value - n_step + tremor);
end
if (n_value < target) then
   Call("*:SetControlValue", name, index, n_value + n_step + tremor);
end

if (n_value == target) then

   Call("*:SetControlValue", name, index, n_value + tremor);
   return 1;
end

else return -1;
end
end


Пример использования в теле скрипта:

Код: Выделить всё
if (PKM_Front == 0.25) then

   UpdateControl (inteval, "WaterTemp F", 0, WaterTemp, 200);

   UpdateControlWithTremor (inteval, "OilPress F", 0, OilPress, 20, 0.03, EngineRPM*0.05);
   UpdateControlWithTremor (inteval, "GMPPress F", 0, OilPressGMP, 20, 0.02, EngineRPM*0.05);

-- этот код НЕПОЛНЫЙ, здесь длинная цепочка if-elseif-else-end . Не используйте его "как есть"
end


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

Также обновляем :

- Индикатор давления масла переднего пульта на контроле "OilPress F", индексом 0, до значения переменной OilPress, темпом 20 Атм/сек. На результат накладывать синусоидальные колебания
амплитудой в 0.03 Атм , с частотой в 0.05 от частоты вращения коленчатого вала дизеля (стрелка упругая, она не будет колебаться на 1-ой гармонике в 500-1500 колебаний в секунду)

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





Последняя и достаточно простая функция - так как переменные тоже иногда нужно обновлять постоянным темпом (зависящим от системных часов, а не ФПС), по аналогии с функцией для
контролов написана и функция для переменных. Позволяет строить линии задержки (ставите проверку на значение переменной - и увеличиваете ее медленно функцией), рассчитывать
переходные процессы, а можно использовать в сочетании с SetControlValue аналогично функции UpdateControl - результат будет аналогичный.

UpdateVariable (time, name, target,speed)

time - системная переменная времени, имеет значение прошедшего времени с прошлого такта скрипта. НЕ ИЗМЕНЯТЬ. При использовании в теле функции подставить имя переменной времени из родительского цикла. Обычно это inteval.
name - имя переменной, которая обновляется данным вызовом функции.

target - значение, которое нужно присвоить переменной. Направление движения определяется автоматически. Внимание! У переменных нет диапазонов, кроме предельных значений типа - переменная может переполниться, если вы не
контролируете процесс обновления значения условиями!!! Зазеваетесь - получите температуру масла в -65535 градусов.

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

Тело функции:

Код: Выделить всё
function UpdateVariable (time, name, target,speed)

-- функция апдейта переменной на новое значение с задаваемой скоростью единиц/сек.
-- name - имя контрола
-- target - целевое значение
-- speed - скорость перевода

--Эта функция возвращает значение!

n_step = time*speed;
n_value = 0;

if ( n_step > math.abs(name - target) ) then   -- устранение "дребезга" около конечного значения.
   n_step = math.abs(name - target);
end

if (speed >0) then

if ( name > target) then
   n_value = name - n_step;
end
if ( name < target) then
   n_value = name + n_step;
end
if ( name == target) then
   n_value = name;
end

else return -1;
end
return n_value;
end



Эта функция возвращает значение, в отличие от предыдущих! И ее можно использовать в Call-aх для мгновенной передачи рассчитанных значений в контролы. Пример использования:

Код: Выделить всё
elseif (GetPower < 0.5) then   -- дизель остановлен
   
   -- остывание жидкостей до минимальной температуры сезона
   
   WaterTemp    = UpdateVariable (inteval, WaterTemp, MinTemp, 0.010);
   OilTemp    = UpdateVariable (inteval, OilTemp, MinTemp, 0.012);
   OilTempGMP    = UpdateVariable (inteval, OilTempGMP, MinTemp, 0.012);


В общем, все понятно из комментариев - переменным, используемым в скрипте для рассчета температуры воды, масла и масла гидропередачи, назначаются новые значения, равные температуре
жидкостей у "холодного" тепловоза. Проще говоря - они остывают до уличной температуры, заданным темпом. Вода - помедленнее, масло, как более теплоемкое - быстрее. 0.010 и 0.012 градуса в секунду. Как можно было заметить выше, именно эти значения передаются (тоже с плавным ходом) на приборы в кабине. Т.е. водичка остывает медленно - стрелка прибора тоже реагирует не мгновенно, а со своей скоростью. Если приборы отключить, они тоже упадут на ноль не мгновенно, а мягко и лампово. Как и положено настоящим.



Надеюсь, я сделал вашу жизнь веселее. Пользуйтесь, на здоровье. :diablo:

Добавлено спустя 49 минут 5 секунд:
П.С.

Обратите внимание - функции UpdateControl и UpdateControlWithTremor не возвращают рабочих значений, а обновляют значения контролов, принятых в аргументах. Т.е. в любимых олдфагами терминах называются ПРОЦЕДУРАМИ. Ладно, на самом деле - возвращают (-1) при неправильном вводе параметров. Но это обычно нигде не используется. Примерная аналогия в ООП - методы для записи значений в классе или его конструктор.

Функция же UpdateVariable должна использоваться только как часть математического или логического выражения и возвращает ОБНОВЛЕННОЕ ЗНАЧЕНИЕ той самой переменной, которую вы обновляете, т.е является "истинной" ФУНКЦИЕЙ. Забудете его забрать - так оно у вас и останется необновленным. Почему я сделал так, а не написал эту функцию по полной аналогии? А ХЗ - работает, и работает надежно. А переписывать скрипт мне не хочется. Возможно, в последующих локомотивах полностью перепишу по аналогии с функцией для контролов - чтобы выполнялась как процедура, а не вызывалась в рассчетах. :beby:

Также ВАЖНО: значения параметра Speed , передаваемого в тело функции, должно быть БОЛЬШЕ НУЛЯ. Это вам не консольный код и не приложение, которое может ругнуться "ой, мне прислали какую-то херню". Функция просто не выполнится (код -1), значение останется прежним.
Skif
 
Сообщения: 3750
Зарегистрирован: 01.10.2009, 17:42
Благодарил (а): 0 раз.
Поблагодарили: 2 раз.
Имя: Дмитрий

Re: LuaScript - для RS

Сообщение sfateev » 17.08.2016, 22:48

Skif, cпасибище огромное за целый мануал! :up:
Света, спасибо и за твой бесценный труд! :)
Надеюсь воспользоваться этими уроками.
Было бы здорово, если бы кто-нибудь в общих чертах объяснил принцип создания скрипта для АЛСН.
Последний раз редактировалось sfateev 18.08.2016, 03:07, всего редактировалось 1 раз.
Аватара пользователя
sfateev
 
Сообщения: 845
Зарегистрирован: 29.05.2006, 10:07
Откуда: Москва
Благодарил (а): 532 раз.
Поблагодарили: 73 раз.
Имя: Святослав

Re: LuaScript - для RS

Сообщение i2GR » 18.08.2016, 01:00

в блогах как бэ не принцип, а совсем скрипт дешифратора есть
Аватара пользователя
i2GR
 
Сообщения: 540
Зарегистрирован: 04.09.2008, 16:59
Благодарил (а): 260 раз.
Поблагодарили: 303 раз.
Блог: Просмотр блога (4)
Имя: Игорь

Re: LuaScript - для RS

Сообщение sfateev » 18.08.2016, 03:06

i2GR, спасибо! "Слона то я и не приметил...(с)))))"
Аватара пользователя
sfateev
 
Сообщения: 845
Зарегистрирован: 29.05.2006, 10:07
Откуда: Москва
Благодарил (а): 532 раз.
Поблагодарили: 73 раз.
Имя: Святослав

Re: LuaScript - для RS

Сообщение Света » 08.09.2016, 22:16

i2GR писал(а):в блогах как бэ не принцип, а совсем скрипт дешифратора есть

К сожалению, это не будет работать на маршруте.
Замысел хорош, исполнение - хоть и не в моем стиле, но тоже похвально. А алгоритм нерабочий. ИМХО.
Вот почему.
-- счетчик полученных в течение кадра сообщений (каждый кадр может быть принято до двух сообщений от светофоров (при правильной их работе), поэтому надо учесть все сообщения)
CounterOCSM = CounterOCSM + 1

В нормальной обстановке на маршруте с более чем одной единицей ПС сообщений за кадр может быть от 1 до 4 (вернее, так будет после выпила сообщений протокола версии 0.5, а до того их от 2 до 8). Привожу очень укороченный фрагмент лога для подтверждения (повторяющиеся фрагменты вырезаны мной):
Код: Выделить всё
00:00:00 Запуск.
[...]
00:00:14   Новое сообщение - F0311302826~
00:00:14   Новое сообщение - 03113002826~
00:00:14   Новый кадр
00:00:14   Новое сообщение - B000000044Y<C~
00:00:14   Новое сообщение - 0000000044Y<C~
00:00:14   Новое сообщение - F0311302826~
00:00:14   Новое сообщение - 03113002826~
00:00:14   Новый кадр
[...]
00:00:23   Новый кадр
00:00:23   Включение green. Выполнение
00:00:23   Новое сообщение - B000000044Y<C~
00:00:23   Новое сообщение - 0000000044Y<C~
00:00:23   Новое сообщение - F0311302826~
00:00:23   Новое сообщение - 03113002826~
00:00:23   Новый кадр
[...]
00:00:35   Новый кадр
00:00:35   Новое сообщение - B0211302663~
00:00:35   Новое сообщение - 02113002663~
00:00:35   Новое сообщение - B000000066Y<C~
00:00:35   Новое сообщение - 0000000066Y<C~
00:00:35   Новое сообщение - F0311302606~
00:00:35   Новое сообщение - 03113002606~
00:00:35   Новый кадр
[...]
00:01:04   Новый кадр
00:01:04   Новое сообщение - B000000571Y<C~
00:01:04   Новое сообщение - 0000000571Y<C~
00:01:04   Новое сообщение - B0011300013~
00:01:04   Новое сообщение - 00113000013~
00:01:04   Новое сообщение - F0111301002~
00:01:04   Новое сообщение - 01113001002~
00:01:04   Новое сообщение - F000000869XFK~
00:01:04   Новое сообщение - 0000000869XFK~
00:01:04   Новый кадр

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

Исходя из этого, я утверждаю, что первый прогнозированный сбой случится на 20 строке тела функции. Будут засчитаны оригинальное сообщение и его клон для протокола 0.5. Ладно, допустим, отсылка сообщений по протоколу 0.5 упразднена. Значит, сбой случится на 35 секунде, когда будут пойманы сообщения B0211302663~ и B000000066Y<C~. Направление одно, а информация разная.
Другой спорный момент - строка 45. Сравнение скорости локомотива с константой 0,05. Но ведь чувствительность светофоров, при которой они определяют направление движения - 0,15. Разве нет вероятности получить ложный индекс направления в этом окне?

Поэтому, перед тем, как разрабатывать логику обработки сообщений, надо сделать фильтр, который будет отсеивать ложные сообщения. Например, сообщения, которые передаются идущему впереди составу и не блокируются им. Сообщения, которые "утверждают", что они передаются со светофора впереди, тогда как они идут с другой стороны. Сообщения с другими протоколами.
Также надо разработать концепцию определения отсеивания сообщений, отталкиваясь от конструктивных особенностей локомотива. Если в ТЭМ2 один светофорчик, работающий на катушки с двух сторон, то в ТЭ10м сзади катушек, НЯЗ, нет! Поэтому АЛСН априори не может показать цветной код от светофора сзади, даже если он открыт. Но если взять 2ТЭ10м, то показания на светофорчиках в каждой кабине будут разные (я так думаю). Если так, значит надо отмечать коды сообщений для каждой кабины персонально, но в блоке УКБМ-ЭПК обрабатывать только актуальные для активной кабины. Здесь ещё одна трудность - если тепловоз заторможен, то при смене кабины надо менять и индекс принимаемых кодов. При скорости 0,15 светофоры сменят индексы. Опять логику задействовать...

Добавлено спустя 21 минуту 29 секунд:
Skif, спасибо за примеры.
Один момент только насторожил. Вы, наверное, очень долго писали/пишете программы на ассемблере? Удивила вот эта конструкция из взаимоисключающих условий:
Код: Выделить всё
if (n_value > target) then
   Call("*:SetControlValue", name, index, n_value - n_step + tremor);
end
if (n_value < target) then
   Call("*:SetControlValue", name, index, n_value + n_step + tremor);
end

if (n_value == target) then
   Call("*:SetControlValue", name, index, n_value + tremor);
   return 1;
end
Очень напомнило :cofe: :
Код: Выделить всё
   CPWRN   20, 19, $0203      ; Если это цифры 2-3,
   RJEQ   CALL_AT            ;идем на вызов АТ
   cpi   R19,   $03         ; Если 1-я цифра "3",
   RJEQ   BUU               ;идем на контроллер удаленного доступа
   cpi   R19,   $0A         ; Если 1-я цифра "0",
   RJEQ   BVC               ;идем на контроллер блокировки входящих вызовов
   cpi   R19,   $05         ; Если 1-я цифра не "5",
   RJNE   ERROR_NUMBER      ; Переход на НЕВЕРНЫЙ НОМЕР


Зачем три раза проверять условие, если первое же его выполнение исключает выполнение в остальных ветках? Почему не if-elseif-else-end? Это ассемблерная привычка или может есть какой-то нюанс, о котором я не знаю?
Забанена по собственному желанию. По важным вопросам связаться со мной можно через почту divovigna@gmail.com
Аватара пользователя
Света
 
Сообщения: 1338
Зарегистрирован: 18.06.2016, 19:38
Благодарил (а): 983 раз.
Поблагодарили: 1805 раз.
Играю в: Train Simulator Classic
Имя: Пандора

Re: LuaScript - для RS

Сообщение i2GR » 09.09.2016, 02:24

ИМХО не работающим является не алгоритм, а конкретная реализация. Некоторое время этот скрипт работал идеально.
Счетчик должен был изменяться только при сообщениях на F... или B...
Что там реально все портит я не знаю. Давно проблемой АЛСН не занимался, но особенностью было то, что понятия "спереди" и "сзади" условны. Спереди - это, по крайней мере, был, тот светофор, расстояние до которого уменьшается.
0.05 это костыль. в идеале я бы хотел регистрировать отличие от нуля.
Надо думать, короче.
Аватара пользователя
i2GR
 
Сообщения: 540
Зарегистрирован: 04.09.2008, 16:59
Благодарил (а): 260 раз.
Поблагодарили: 303 раз.
Блог: Просмотр блога (4)
Имя: Игорь

Re: LuaScript - для RS

Сообщение Skif » 09.09.2016, 10:16

Skif, спасибо за примеры.
Один момент только насторожил. Вы, наверное, очень долго писали/пишете программы на ассемблере? Удивила вот эта конструкция из взаимоисключающих условий:

Код: Выделить всё
if (n_value > target) then
Call("*:SetControlValue", name, index, n_value - n_step + tremor);
end
if (n_value < target) then
Call("*:SetControlValue", name, index, n_value + n_step + tremor);
end

if (n_value == target) then
Call("*:SetControlValue", name, index, n_value + tremor);
return 1;
end


Это не ассемблер. Это SystemC - самый лучший подход при написании "тактируемых" скриптов (запускаемых с частотой кадра - что и имеет место в RW). Так удобнее в отладке и быстрее в работе на системах, где критична частота - три отдельных if работают быстрее, чем if-elseif-else . Ну, и самый универсальный ответ - А МНЕ ТАК УДОБНЕЕ. :olen:
Skif
 
Сообщения: 3750
Зарегистрирован: 01.10.2009, 17:42
Благодарил (а): 0 раз.
Поблагодарили: 2 раз.
Имя: Дмитрий

Re: LuaScript - для RS

Сообщение Света » 09.09.2016, 11:45

i2GR
i2GR писал(а):...особенностью было то, что понятия "спереди" и "сзади" условны. Спереди - это, по крайней мере, был, тот светофор, расстояние до которого уменьшается.
0.05 это костыль. в идеале я бы хотел регистрировать отличие от нуля.
Надо думать, короче.
Я Вам подкину идею.
Главная проблема, ИМХО, заключается в том, что неизвестно направление принятых сообщений. Думаю, Вы со мной в этом согласитесь - ведь именно этот недостаток функции OnCustomSignalMessage побудил Вас лепить костыль с индексами направления. Замысел неплохой, только его единственный серьёзный недостаток - присваивание индексов, привязанное к ПС. Вот главная слабость системы. Ведь индекс изменяется достаточно непредсказуемо - легкие рывки локомотива на границе чувствительности светофора создадут такую смесь сообщений, что любой дешифратор перегрузится.
Поэтому я предлагаю несколько иную концепцию присвоения "направленности" сообщений.
Предлагаю систему, при которой "направленность" будет задаваться без привязки к локомотивам. А по взаимному расположению светофоров относительно друг друга.
Для этого надо выбрать ключевой светофор (или создать маркер на путь), который в начале сценария отправит запрос с сообщением, например, "F" вперед и "B" - назад. Каждый светофор, получив этот запрос, устанавливает внутренний триггер направления. Если "F" пришло сзади или "B" спереди, триггер устанавливается в "F", иначе - в "B". Также, получив этот запрос, светофор пересылает его дальше.
В результате такой операции все светофоры на маршруте окажутся ориентированы относительно друг друга. Те, что "смотрят" в одну сторону, будут иметь один индекс, а те, что "смотрят" им навстречу, - получат индекс-антипод. Останется только присоединить индекс триггера к сообщению локомотива. То есть, дальше уже система отработана.
Локомотивный дешифратор в таком случае получится максимально простым - достаточно один раз синхронизировать направление локомотива и индекс, чтобы четко различать нужные индексы.
Единственное слабое место, которое я сейчас вижу - это кольца и замкнутые съезды. Но такие моменты достаточно легко решаются. Достаточно ввести приоритет присваивания триггера направления по индексу линка.
Забанена по собственному желанию. По важным вопросам связаться со мной можно через почту divovigna@gmail.com
Аватара пользователя
Света
 
Сообщения: 1338
Зарегистрирован: 18.06.2016, 19:38
Благодарил (а): 983 раз.
Поблагодарили: 1805 раз.
Играю в: Train Simulator Classic
Имя: Пандора

Re: LuaScript - для RS

Сообщение Skif » 09.09.2016, 14:12

Я за маркеры - именно такая система используется в ТРС. И именно с этой целью. Эти же маркеры раньше использовали для задания направления для движения ботов.

Как вариант - использовать в качестве маркера сам светофор, договорившись,что со стороны линз - "зад", а со стороны лесенки - "перед" для передачи сообщений. Т.е. вперед - это перед локомотивом, а назад - это навстречу локомотиву.
Skif
 
Сообщения: 3750
Зарегистрирован: 01.10.2009, 17:42
Благодарил (а): 0 раз.
Поблагодарили: 2 раз.
Имя: Дмитрий

Re: LuaScript - для RS

Сообщение TRam_ » 09.09.2016, 20:31

Если в ТЭМ2 один светофорчик, работающий на катушки с двух сторон, то в ТЭ10м сзади катушек, НЯЗ, нет! Поэтому АЛСН априори не может показать цветной код от светофора сзади, даже если он открыт.
Пассажирским Ушкам иногда ставят катушки и сзади. Переключатели "с передней на заднюю" у них, как и у большинства двухкабинных локов находятся в машинном (а не в кабинах).
По поводу ТЭМа - при надвиге на горку коды на некоторых станциях тоже подаются, но не спереди а сзади. А чтоб коды принимались и спереди и сзади - это только в метро бывает.
в z7 всё можно, а что нельзя - можно в sU
Аватара пользователя
TRam_
 
Сообщения: 1925
Зарегистрирован: 30.11.2007, 20:14
Благодарил (а): 2 раз.
Поблагодарили: 66 раз.
Играю в: Auran Trainz
Роль: Разработчик
Имя: Владимир

Re: LuaScript - для RS

Сообщение Skif » 10.09.2016, 12:03

Нет такого в метро - не вноси путаницы. Это я тебе как метрополитенщик заявляю. В хвостовой кабине комплект АРС отключается, иначе машина встанет колом из-за приема кода ЛН (ложное направление - оно же используется для контроля стороны открытия дверей в системе МАРС) и замыкания цепей АРС друг на друга через переключатели РЦ АРС. Кроме того, цепи АРС хвостового вагона заведены в контроллер на реверсивную рукоятку, и при извлеченной рукоятке разомкнуты.

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

В системе АРС-Д хвостовой комплект может использоваться в исключительных случаях в качестве резервного при неисправности и отключении головного (тумблер АРС-Р на пульте), в системах МАРС и БАРС в каждой голове стоят два комплекта - основной и дублирующий АРС-Р.
Skif
 
Сообщения: 3750
Зарегистрирован: 01.10.2009, 17:42
Благодарил (а): 0 раз.
Поблагодарили: 2 раз.
Имя: Дмитрий

Re: LuaScript - для RS

Сообщение Света » 10.09.2016, 21:17

i2GR писал(а):...Что там реально все портит я не знаю.., ...но особенностью было то, что понятия "спереди" и "сзади" условны. Спереди - это, по крайней мере, был, тот светофор, расстояние до которого уменьшается...
Всё портят две вещи.
1 - Это сообщения, адресованные другому локомотиву, которые он не перехватывают, и, таким образом, достигающие пользовательский локомотив. То, что эти сообщения вообще существуют - это не баг, это, как раз совершенно верная работа светофора. Вот если бы можно было скриптом светофора отличить пользовательский локомотив от бота, тогда это можно было бы исправить. НЯЗ, светофор не умеет различать принадлежность локомотива. Значит надо отсеивать эти сообщения самому локомотиву. Это просто сделать, здесь нет никакой проблемы.
2 - Путаница, возникающая из-за динамически меняющейся индексации "F" и "B". ИМХО, это самое проблемное место 0.6 версии.

На самом деле, с этим тоже можно бороться. Для того, чтобы отсеять светофоры, которые сзади, надо блокировать светофоры, которые отдаляются при движении (понятия "спереди" и "сзади" условны. Спереди - это, по крайней мере, был, тот светофор, расстояние до которого уменьшается.(с)). Для этого есть достаточно простой алгоритм.
Создаются 3 таблицы. 1 - "черный список", 2 - расстояние и 3 - время жизни данных о светофоре. Во всех таблицах ключом является ID светофора. Благодаря этому таблицы синхронизированы между собой.
Входящее сообщение раскладывается на составляющие. Для работы нужны ID светофора, и расстояние до светофора.
Пункты обработки:
1. Обновляется время жизни светофора с этим ID.
2. Проверка, нет ли светофора с таким ID в черном списке. Если есть - просто продолжается его время пребывания в черном списке. Сам сигнал в таком случае не обрабатывается.
3. Если этого светофора нет в черном списке, проверяется, есть ли он вообще в таблице расстояний. Это делается для того, чтобы если это первое упоминание об этом светофоре, то не пропустить его на фильтр до того, как будет выяснено, приближается к нему локомотив или нет. Поэтому, если этот светофор в таблице не числился, то единственное, что происходит - в таблице создается его ячейка и вносится расстояние до него. А если он уже числится в таблице расстояний, то есть возможность проверить, как изменилось соотношение расстояний - если расстояние увеличилось (т.е. локомотив от него отдаляется), то он будет помещен в черный список, а если нет - то сообщение в полном сборе будет передано на фильтр, так как этот светофор впереди локомотива.
На этом сортировка "спереди" - "сзади" почти закончена. Дальше надо определить, кому предназначено это сообщение. Или локомотиву игрока, или боту (ботам) впереди. Этот фильтр я приводить здесь не буду.
Что касается сортировки по "спереди" - "сзади", то остается только прибрать за собой. В первую очередь надо определится с тем, как удалять светофоры из черного списка. Ведь не исключено, что двигаться придется реверсом, а значит информация от заблокированных светофоров уже будет корректной. Кроме того, информация о тех светофорах, мимо которых проследовал локомотив, уже устарела и только захламляет таблицу расстояний. Она уже никак не пригодится. Поэтому нужно проводить очистку:
4. Создается локальная переменная. В ней впоследствии будет ID светофора, который не упоминается за время поступления более, чем 5 сообщений (число с потолка, его можно запросто увеличить).
5. Прогоняется вся таблица времени жизни данных светофора. Каждое число инкрементируется. Когда какое-то из них становится меньше 1, это свидетельствует о том, что светофор не заявлял о себе достаточно давно. Его ID помечается на удаление. Если дальше в таблице окажется ещё один такой светофор (вероятность, очень близкая к 0), то он примет эстафету на удаление на себя. То есть, за цикл может быть удален только 1 светофор. А другой будет удален в следующем цикле. То есть, буквально через мгновение.
6. Если переменная на удаление не равна 0, это значит, что в ней есть ID светофора, от которого перестали приходить сообщения. Во всех таблицах информация об этом светофоре будет удалена.

Фрагмент скрипта, реализующий этот алгоритм:
Код: Выделить всё
   Sig_BL = {} -- черный список
   Sig_CV = {} -- список расстояний для контроля изменения
   Sig_LF = {0} -- время хранения данных сигнала
   
   ID = string.sub(SignMess,11);
   Dist = tonumber(string.sub(SignMess,7,10))

   
function clearing (ID, Dist)
   -- Обработка принятого кода
   Sig_LF[ID] = 5
   if Sig_BL[ID] then
      Sig_BL[ID] = 3
   elseif Sig_CV[ID] then
      if Sig_CV[ID] < Dist then
         Sig_BL[ID] = 3
      else
      --[...фильтр ложных сообщений...]
      end
   else
      Sig_CV[ID] = Dist
   end
   local maxkey = 0
   for k, val in pairs (Sig_LF) do
      if val <= 0 then
         maxkey = k
      else
         Sig_LF[k] = Sig_LF[k]-1
      end
   end
   if maxkey ~= 0 then
      Sig_BL[maxkey] = nil
      Sig_CV[maxkey] = nil
      Sig_LF[maxkey] = nil
   end
end


В функции, принимающей сообщения от светофоров, сообщения с индексом "В" изначально блокируются. Из-за нестабильности индексации это, ИМХО, меньшее зло. Поэтому, в теперешней реализации передачи сообщений светофорами, если применить такой алгоритм сортировки, двигаясь от сигнала реверсом, видеть его код на локомотивном светофорчике не получится... По крайней мере, не сделав аналогичный канал для приема и сортировки "В"-сообщений. С переключением результатов ручкой реверсора. Вот только стоит ли оно того?

*В таблицу черного списка помещается число 3... Также переменная имеет имя maxkey... Никакой мистики, это хвосты после оптимизации кода - многое удалено. Так как в таблицу черного списка можно поместить что угодно, важно само присутствие там ID неправильного светофора, это число не было убрано. Имя переменной в предыдущей версии несло логический смысл. Сейчас нет, но так как эта переменная существует в очень ограниченном пространстве, это не имеет значения.
Забанена по собственному желанию. По важным вопросам связаться со мной можно через почту divovigna@gmail.com
Аватара пользователя
Света
 
Сообщения: 1338
Зарегистрирован: 18.06.2016, 19:38
Благодарил (а): 983 раз.
Поблагодарили: 1805 раз.
Играю в: Train Simulator Classic
Имя: Пандора

Пред.След.

Вернуться в [RW] Другие вопросы

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 5