Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Итак, после некоторых изменений, меня устраивает тот вид, в котором пневматика функционирует сейчас. А значит есть две вещи: в скорости можно будет оформить это более публично, чем в текущий момент и появляется смысл писать хорошее описание, не боясь того, что придется нафиг все переделывать.
- Итак, далее идут две вещи — описание того, что я изменил по сравнению с предыдущей версией и рассказ о том, как работает и как используется система.
- В основном, все переделки затронули только внутренности (моделирование ТМ и прочего я не трогал, практически).
- То, что не входит в «практически»:
- 1. Убраны, удалены, забыты разбиения на какие-то уровни Abstract, Standard, ..., ибо не нужны. Теперь есть единственный LpePneumaticObject и от него все и идет.
- 2. Стерт с лица файлов загадочный и неведомый HandleApoMessage, ибо не нужен. Его единственный смысл был в уведомлении о начале/конце моделирования систем данного вагона. Теперь для этого другие методы.
- 3. Дефолтный ТЦ теперь обладает только одной нодой. Нафиг и так же удален юз. Он все равно был корявый. Желающие могут реализовать альтернативные классы, все же.
- 4. Убрана нафиг централизованная инициализация через соотв. библиотеку, ибо понятно что. Система инициализации теперь распределенная и гибкая. А так же весьма нетривиальная, в связи с чем здесь будет небольшое отступление.
- Инициализация.
- Требования к скриптам управления запуском (и дальнейшей работой) определялись не сразу, но в конце-концов стали такими:
- 1. Экономия ресурсов. Не хочу тормозов от крутых скриптов ни у себя, ни у других. Из десяти стоящих на карте локов реально полностью обрабатывать нужно лишь один. Или два, если юзер извращается :)
- 2. Из предыдущего следует, что локомотив должен отличать, кто с ним работает — игрок или бот. Если бот — то крутые скрипты не нужно запускать вообще, а запустить маленький, для бота. И ждать, когда же будет черед юзера.
- 3. Локомотив должен знать, в каком состоянии ему инициализироватья. Стоял он месяц под забором или его только что сдала нам другая бригада?
- 4. Игрок не должен потерять состояние локомотива если он перейдет в другой вагон или поезд. И также не должно быть переходов в режимы бота на дефолтных станциях. Т.е. устойчивость.
- Первый вариант, удовлетворяющий пунктам — тупо свойства в браузере. Но это тупо:
- 1. Это сотня лишних кликов мышью.
- 2. Это будет разным у каждого лока.
- 3. Это нужно каждый раз писать самому.
- Другая мысль была, и она была воплощена первой — библиотека, которая будет определять поезд игрока и говорить об этом. Скрипты пневматики/локомотива будут спрашивать её и она будет отвечать — этот поезд управляется игроком, или нет. Устойчивость обеспечивается настраиваемой выдержкой, или сменой только по явному указанию, или вообще блокировкой каких-либо переключений.
- Но централизация получалась громоздкой. Да и по пунктам №3 и 2 были трудности — для тупых сессий, типа того, что мы видим в ЗДСимуляторе, это можно сделать, а далее начинаются проблемы. И мне не хочется ставить ограничения на том, что в ТРС, собственно, можно сделать на порядок более богато, чем во всех альтернативах — сценарии.
- Потому был выбран иной, децентрализованный подход. Он позволяет для каждого вагона:
- 1. Управлять состоянием его скрипта
- 2. Другим системам следить за тем, что происходит с ним.
- Вагонная/локомотивная часть системы заключена в классе LseInit. Внешний интерфейс построен на сообщениях.
- При отправке управляющего сообщения отправителем надо указывать тот объект, к которому необходимо применить данное действие, получателем должны быть все (широковещательная отправка). Сделать это можно с помощью Router.PostMessage(obj.GetId(), Router.MESSAGE_BROADCAST, "Major", "Minor", 0.0);
- Информационные сообщения отсылаются как обычно, широковещательно от источника.
- Для описания состояния скрипта вводятся два понятия — уровень и предсостояние (уж простите за тавтологию :).
- Есть 4 уровня — halt, wait, user, auto. При запуске лок переходит автоматом переходит в wait, из него может перейти в user или auto после приема сообщения. В halt все остановлено, ничего не исполняется, в wait работает один поток-монитор, user предполагает запуск полновесного скрипта, в auto — для ботов.
- Во всех уровнях кроме halt раз в минуту рассылается информационное сообщение Lse.Init.Runlevel, Wait|User|Auto, что требуется для компонентов, которые расположены вне скрипта лока, как например, библиотека пневматики.
- При переходе из одного уровня в другой рассылается сообщение Lse.Init.RunlevelChange, Wait|User|Auto Init|Term, пока просто для наличия такой функциональности.
- После заверщения рассылается сообщение Lse.Init.RunlevelChanged, Wait|User|Auto|Halt.
- Управляющие сообщения:
- Lse.Init.TrainPhysics, Enabled|Disabled должно отсылаться при использовании Train.EnablePhysics, ибо соответствующей функции-геттера нет. При получении Disabled происходит автоматический переход в уровень wait. И добавлен отсутствующий геттер LseInit.GetPhysicsState().
- Данное сообщение стоит особняком — отправителем должен быть указан поезд (внимание, поезд!). Все вагоны со стандартной инициализацией отреагируют на это сообщение. При этом уровень автоматически сбросится на Wait, все переходы заблокируются.
- Lse.Init.PreState, Cold|Warm|Hot|HotRunning <idleMinutes> — для задания предсостояния, с которым локомотив должен инициализироваться. По моему мнению, должно быть достаточным 4 значения:
- Cold — локомотив после ремонта, с завода; степень неготовности максимальная, требуется длительная процедура запуска.
- Warm — локомотив после отстоя в депо после сдачи его предыдущей бригадой.
- Hot — локомотив во время смены бригад на промежуточной станции.
- HotRunning — помимо предыдущего, он еще и запущен, можно ехать сразу же.
- Второй параметр — время в минутах, сколько локомотив находился в этом состоянии. Требуется для продолжительных процессов (воздух в ГР, ЗР, напряжение АБ и т.п.).
- По умолчанию должен подразумеваться Warm с выдержкой в сутки. Не обязательно поддерживать все, при запросе неподдерживаемого должен выбираться ближайший из поддерживаемых.
- Lse.Init.SetRunlevel, Halt|Wait|User|Auto — то самое сообщение, которым локомотив переводится на нужный уровень. Можно перейти из любого в любой (кроме halt, из него принципиально никуда).
- Использовать этот класс просто:
- class MyVehicle isclass Vehicle, LseInit { ... };
- class MyLocomotive isclass Locomotive, LseInit { ... };
- Для перегрузки доступны следующие функции: RcUserInit, RcUserTerm, RcAutoInit, RcAutoTerm, RcWaitInit, RcWaitTerm. Они вызываются из потока (значит, в них можно спать). Обязательно необходимо вызывать функцию предка с помощью inherited(). После запуска/остановки всех нужных потоков и выполнения всех действий функция должна вернуться, при этом будет отослано сообщение о завершении перехода.
- И далее, прилагаются к этой системе помощники для быстрого задания нужных состояний в редакторе — InitVs, они же Initialization Vehicles. Они представляют из себя крошечной длины вагоны, исполняющие скрипты, рассылающие управляющие сообщения. Они сделаны для удобства и как образец применения этих сообщений. Гораздо быстрее прицепить в конец лока игрока такой вагон, который отошлет ему сообщение RunlevelInit, User, а не лезть в свойста/задавать команду/настраивать правило. И, плюс, это сработает и в случае поезда, выпущенного порталом/сгенерированного скриптом.
- Сейчас есть два образца такой технологии — оключателя физики (InitV Disable Physics) и, как раз таки, поезда игрока (InitV User Train).
- Их класс — LseInitVehicle.
- Работа этих штук задается четырьмя методами:
- public string GetInitVehicleCategory();
- void DoPreInit(Train train);
- void DoInit(Train train);
- void DoInit(Vehicle[] vehs);
- Основные действия должны заключаться в методе DoInit(Vehicle[]). Он получает массив вагонов, на которые распространяется его действие.
- Сперва теория.
- Для того, чтобы можно было их группировать, по-разному настраивая разные группы вагонов, определены такие правила:
- 1. InitV — стрелка. Его перед указывает, в каком направлении он будет проводить свои инициализационные действия.
- 2. Подвластные ему вагоны идут по ходу его стрелки, не включая других вагонов, являющихся InitV.
- 3. Подвластные ему вагоны заканчиваются там, где стоит другой InitV с категорией, одинаковой с нашей.
- Теперь практический пример:
- № 8 7 6 5 4 3 2 1
- InitV InitV InitV InitV
- категория: a b c b
- > > > [ ] [ ] [ ] > [ ]
- Стрелочки — InitVs, квадратные скобки — вагоны.
- В момент инициализации InitV номер 6 и 8 получат массив с вагонами 1, 3, 4, 5; InitV №7 — 3, 4, 5; InitV №2 — только 1.
- И да, рекомендую всегда делать также, как здесь — стрелки InitV должны всегда быть сонаправлены в составе. Иначе возможны глюки, да и это бессмысленно.
- Далее, DoPreInit(Train) предназначено для действий, которые влияют на дальнейшую инициализацию. Например, отключение физики. Данное действие логично выполнить перед всеми другими, если вдруг они будут. Эта функция гарантированно выполнится первой, а все другие — после небольшой паузы.
- DoInit(Train) — аналог DoInit(Vehicle[] vehs), но для всего поезда. Для поездов исключение категориями не ведется, потому рекомендую не совмещать две эти функции. Конкретный InitV должен использовать что-то одно.
- Категории не предназначены для продвинутой фильтрации. Они предназначены для разделения состава на области, где будут действовать два InitV одной категории, но с разными настройками. Например, один будет отправлять сообщения PreState, Warm 360, а другой — PreState, Hot 10.
- После завершения инициализации InitV перемещается в конец состава и удаляется.
- Модели пока не в финишном варианте. Будут другие.
- Далее пневматика.
- Пневматикой и, в частности, расчетами заведует отдельная библиотка. Почему?
- 1. Весь код в одном месте.
- 2. Вагоны могут быть обработаны отдельно от локомотивов, в том числе и составы без локомотивов вообще.
- Для экономии ресурсов, полное моделирование (метод Мак-Кормака для расчета одномерного движения газа в ТМ, обновление объектов, обновление задатчика тормозной силы) применяется только к группе «тронутых» вагонов. Ими являются:
- 1. Вагоны/локи, находящиеся в уровне User.
- 2. Все вагоны поездов, в которых есть п.1
- Уровень определяется слушанием Lse.Init.Runlevel, User.
- Далее, вагоны остаются тронутыми три минуты. Если вот уже три минуты ни они, ни кто-то из их поезда сообщение о уровне не отправлял, они помечаются неактивными.
- Неактивные вагоны рассчитываются еще 30 минут по упрощенной схеме:
- 1. Вместо МакКормака раз в секунду рассчитывается среднее давление в ТМ
- 2. Вместо ежекадровых апдейтов объектов они обновляются раз в секунду другой функцией.
- 3. Задатчик силы работает также (ну не получается по-другому, да и к тому ж, не такой уж он и тяжелый).
- Такая зверская оптимизация в п.1 и п.2 вызвана соображением о том, что в отстаивающем составе, плюс к тому не связанным с игроком, маловероятны переходные процессы, есть лишь утечки. Даже если переходные процессы имеются, за ними никто не наблюдает, а значит можно пожертвовать точностью ради кадров в секунду.
- Пункт следующий, архитектура.
- Библиотека работает с объектами, причем весьма абстрактными. Все модели тормозного/пневматического оборудования строятся на них. Бывают они такие:
- LpeNode — объект, описывающий некий резервуар, объем. Применяется для взаимодействия между различными объектами. Например, при работе у ТМ и у ВР какого-то вагона есть общий LpeNode, который представляет собой объем ТМ в данном вагоне.
- Из-за высокой дискретности и, из-за стабильности, нельзя использовать малые объемы, рекомендую не строить из них цепочки. Ограничиться 1-2 максимум (ВР — авторежим — ТЦ, например).
- LpeConnector — описывает межвагонное соединение. Имеет функции, позволяющие проверить наличие соединения с какой-то стороны и, собственно, присоединено ли :)
- Соединение между вагонами считается соединенным, если у обоих соотв. соединения существуют и соединены (тавтология, однако).
- LpePipe — подкласс предыдущего класса. Расширяет его функциональность концевым краном, характерным для пневматических магистралей. Также присутствует свойство поломки, но я не знаю зачем я его сделал :) Сейчас оно никак не используется, только проверяется.
- Да, для проверки состояния соединения есть функции в классе LpeUtil. Использовать надо их, они учитывают всю задуманную логику соединений.
- LpePneumaticObject — базовый класс, который является основой для всех пневматических/тормозных устройств (ТЦ, ВР, ТМ, ...) и схем.
- Имеет важные методы, а именно:
- Vehicle GetOwner() — возвращает вагон, который содержит это устройство.
- void Init(Vehicle vehicle) — инициализация устройства. Не забывайте про inherited(vehicle);
- void Update(float dt) — регулярный апдейт активного устройства. Вызывается раз в кадр, но не реже раза в 0.1 секунды (если кадр длится дольше, вызывается несколько раз, т.е. амортизация). Не забывайте про inherited(dt);
- void UpdateIdle(float dt) — регулярное обновление неактивного устройства. Вызывается раз в секунду, амортизации нет (хотел бы я посмотреть на тех, кто играет с 1 ФПС :)) Не забывайте про, ну понятно :)
- Должен содержать код, стабильный при таких интервалах. Я решил схитрить, не выдумывать велосипед, а поиздеваться над физикой:
- 1. Весь расчет выносится в отдельную функцию, принимающую float dt, float leakdt, последний — интервал времени для расчета утечек, а первый — для всего остального. И далее:
- public void Update(float dt)
- {
- inherited(dt);
- _update(dt, dt);
- }
- public void UpdateIdle(float dt)
- {
- inherited(dt);
- _update(Math.Fmin(0.1, dt), dt);
- }
- Да, все процессы, кроме утечек, идут со скоростью в 10 раз меньше реальной. Но, как я расписал выше, нас волнуют только утечки.
- LpeBrakeForceSetter — объект, задающий тормозную силу на конкретном вагоне. Принимает два параметра:
- BrakeEffort — требуемая тормозная сила.
- ApplyEffort — применять ли её (сделано т.к. первый параметр задается схемой, а второй — библиотекой).
- Возвращает:
- BrakeEffortApplied — реально действующая сейчас тормозная сила.
- Как работает задание тормозной силы.
- В моей реализации, каждый вагон должен иметь специальный спек. Он задает гигантский ЗР и ГР, разъединяет ТМ у соседей, отключает питание ЗР из ТМ.
- Тормозная сила задается с помощью разрядки ТМ на нужное время. При этом дефолтный ВР наполняет ТЦ. После достижения нужного уровня давление в ТМ поднимается до давления ЗР. При необходимости снизить выполняется ступень отпуска. И т.д.
- Давление в ТМ задается с помощью SetBrakePipeEfficiency. Величина питания ТМ из ГР установлена в половину величины разрядки через функцию. Значит, балансируя где-то в середине мы поддерживаем давление в ТМ на постоянном уровне, отклоняясь изменяем.
- Скрипт расчетчика аналитически вычисляет требуемое давление в ТЦ, опираясь на параметры спека. И задает нужное, манипулируя давлением в ТМ.
- Так так такое задание неточное и замедленное, то библиотека суммирует BrakeEffort и BrakeEffortApplied по всему поезду, находит разницу и для компенсации корректирует скорость поезда.
- Если поезд состоит из одного вагона, то SetBrakePipeEfficiency не имеет действия и вся сила задатется коррекцией скорости.
- Такие сложности с дефолтным ТЦ сделаны для реакций в поездах. Да, в ТРС они какие-то хреновенькие, как выяснилось, однако:
- 1. Раз написано, пусть остается.
- 2. Быть может, все изменится к лучшему.
- 3. При трогании они работают.
- LpeScheme — объект, реализующий схему тормозного оборудования вагона/лока.
- Содержит обновляемые схемой поля, хранящие соседей, список соединений, задатчик тормозной силы (а зачем без него вообще использовать эту систему? :))
- Вместо Init в нем есть два метода: SchemeCreate, где должны создаваться объекты схемы и SchemeInit, где они должны инициализироваться, вместе с самой схемой. Это сделано для разделения места, где создается оборудования с тем, где оно используется.
- На этом завершаются объекты, с которыми система работает напрямую. Они являются каркасом.
- На этом каркасе реализуются уже реальные, а не абстрактные объекты:
- LpeBrakePipe — ТМ
- LpeBrakeCylinder — ТЦ
- LpeAirDistributor — ВР и т.д.
- И есть большой файл, содержащий дефолтные реализации работы оборудования — lse.std.pneum.gs.
- Вообще система гибка, гибка в том, что наследуясь от каркасных классов можно создавать различные конфигурации оборудования, не ограничиваясь стандартным набором ТМ/ТЦ/ВР, который встречается везде и всюду. Легко можно реализовать МРТ, дисковые тормоза, скомбинировать задатчик тормозной силы с тяговой для совместного учета ЭДТ, тяги, боксования, юза, торможения. И т.д.
- Есть пара замечаний:
- LpeStdScheme и LpeStdVehicleScheme. Оба класса определяют некую стандартную конфигурацию. Первый определяет ТМ, второй — остальной стандартный набор (ТЦ, ВР). В принципе, это легко можно сделать в своем классе, но это удобно тем, что имеется стандартизация. Стандартизация особенно важна для соединений, чтобы не было двадцать разных коннекторов ТМ у разных вагонов :)
- Да, так случилось, что эта пневматика оказалась подходящей для поддержки межвагонных соедиений, пневматических и электрических. Самой системой моделируется только пневматические, электрику надо рассчитывать самим, но система предоставляет унифицированный интерфейс коннекторов. В том числе и ХАД-панель, подобную панели по Ф9 в МСТС, где можно будет управлять концевыми кранами, рукавами, соединениями и пр. (сейчас я как раз ею и занимаюсь).
- Соединения идентифицируются по именам. Одинаковые имена означают совместимость.
- Определены 4 стандартных имени для пневматических магистралей:
- tm — ТМ
- pm — Питательная
- im — Импульсная
- mvt — Вспомогательного тормоза
- На именование нет ограничений. Рекомендации — коннекторы эл. цепей/СМЕ должны должны содержать тип ПС (серию). Желательно какой-то признак скрипта, чтобы можно было отследить совместимость/несовместимость. Номера быть не должно. У пневматических — соответственно обозначению в схеме тормозного оборудования.
- Желательно, конечно, выносить этот вопрос на обсуждение, чтобы была взаимозаменяемость/унификация.
- Далее, важны также спеки. К системе прилагается референсный спек. Из него полагается взять настройки flowsize, volume, pressure. В motor необходимо настроить параметр brakeratio. Он задает силу в Ньютонах при максимальном давлении в ТЦ. Вы должны поставить в него, с запасом, максимальную требуемую вам силу.
- В разделе lse-pneumatics-298469 контейнера extensions находятся параметры дефолтных устройств. Обычно требуют настройки параметры торможения и ТЦ. Этот контейнер может также быть в конфиге ПСа и конфиге его спека. Не обязательно заполнять в них все, при отсутствии какого-то в конфиге ПС идет поиск в конфиге спека ПС, а если нет и там — в референс-спеке. В нем есть все параметры со значением по умолчанию.
- К моменту выпуска системы я планирую подготовить отдельные спеки для пассажирских вагонов, грузовых 4-осн и 8-осн. Пока, как образец, есть только пассажирский.
- Поддержка ПСом системы может быть двух видов:
- 1. Поддержка спеком. Определяется по наличию раздела lse-pneumatics-298469 контейнера extensions. Такому ПСу создается дефолтная вагонная схема, тип вагона (пасс, груз 4, груз 8) будет определяться автоматически. Подходит для вагонов.
- 2. Поддержка реализацией LpeVehicle. Этот интерфейс определяет функцию Lpe_GetVehicleScheme(), что позволяет указать системе на произвольный объект класса LpeScheme. Подходит для локомотивов.
- Если ПС не соотвествует ни п.1, ни п.2, то такой вагон не поддерживает пневматику и ею не обрабаывается вообще.
- Такой ПС не должен быть в поездах, рассчитываемых системой. Это вызовет ошибки и глюки в задании тормозной силы.
- Впрочем, переделка ПСа по пункту №1, с учетом дефолтных спеков не должна вызывать затруднений. Это одно из преимуществ этой системы, в ранних вариантах подобных реализаций все вагоны должны были иметь специфичный скрипт.
- Локомотивам полагается пункт №2. Это связано с тем, что все равно требуется связывание с органами управления специальным скриптом.
- Я также хотел бы, чтобы поддерживающий данную систему ПС (оба пункта) имели в конце своего имени «(P)», в скобках букву Пи английскую. Это помогло бы в редакторе отличать поддерживающих от неподдерживающих.
- С библиотекой поставляется набор дефолтных объектов, см файл lse.std.pneum.gs. Там есть реализации ТЦ, ТМ, ВР292, дефолтной вагонной и локомотивной схем.
- ВР292 все же неудачен. Пока нет альтернатив и времени их писать и настраивать, он остается. В дальнейшем, должен быть написан аналог, который проще в 20 раз, в столько же раз легче. Вместе с 483.
- В локомотивную схему будет добавлена поддержка магистралей ПМ и МВТ.
- Также еще не сделано:
- 1. ХАД. Делается, но не готово.
- 2. Предсостояние. Для него будет добавлена следующая функция в класс LpePneumaticObject:
- public void InitPreState(int prestate, int idleMinutes);
- Также, возможно, будут добавлены (для удобства, но возможно будут полезны):
- public void OnActiveSimulationBegin();
- public void OnInactiveSimulationBegin();
- public void OnSimulationEnd();
- которые будут сигнализировать о соотв. переменах в состоянии симуляции.
- Это изменение не нарушит совместимость.
- 3. 483. Давно просящийся объект, но на настройку ВРов уходит слишком много времени, пока не могу заняться этим. Если кто-то сможет написать и настроить такой класс, реализующий все требуемые методы, был бы крайне благодарен.
- 4. ЭПТ. Будет, делается вместе с тапком.
- Вроде все. Потом постараюсь оформить в более пристойном варианте :)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement