Итак, за симуляцию физики поезда в RRS отвечает процесс simulator. Его точка входа, функция main() содержит следующий код (здесь и далее пути буду указывать относительно корня дерева исходников)
simulator/simulator/main.cpp
Код: Выделить всё
int main(int argc, char *argv[])
{
init_journal();
register_handlers();
AppCore app(argc, argv);
if (!app.init())
return -1;
else
return app.exec();
}
Потом создается экземпляр класса AppCore, который наследует от QCoreApplication. Объекту app передаются аргументы командной строки, а потом вызывается метод AppCore::init() выполняющий стартовую реализацию. При успешной инициализации запускается основной цикл обработки сигналов Qt вызовом AppCore::exec().
Что происходит при инициализации. А вот что
simulator/simulator/app.cpp
Код: Выделить всё
bool AppCore::init()
{
// Common application data settings
this->setApplicationName(APPLICATION_NAME);
this->setApplicationVersion(APPLICATION_VERSION);
QString errorMessage = "";
QString cmd_line = "";
for (QString s : this->arguments())
cmd_line += " " + s;
Journal::instance()->info("Process " + APPLICATION_NAME + " started with command line: " + cmd_line);
Journal::instance()->info("Started " + APPLICATION_NAME + " initialization");
switch (parseCommandLine(parser, command_line, errorMessage))
{
case CommandLineOk:
// Creation and initialization of train model
model = new Model();
Journal::instance()->info(QString("Created Model object at address: 0x%1").arg(reinterpret_cast<quint64>(model), 0, 16));
return model->init(command_line);
case CommandLineError:
fputs(qPrintable(errorMessage), stderr);
fputs("\n", stderr);
return false;
case CommandLineVersionRequired:
printf("%s %s\n",
qPrintable(this->applicationName()),
qPrintable(this->applicationVersion()));
return false;
case CommandLineHelpRequired:
parser.showHelp();
return false;
}
return false;
}
Класс Model отвечает за реализацию физической модели поезда, организует её счет в реальном времени, а так же обеспечивает обмен данными между другими процессами, в частности отправляют данные для viewer-ра и принимают от него коды клавиш. При инициализации этого класса происходит:
- Применение аргументов командной строки.
- Создание и настройка решателя дифференциальных уравнений движения поезда
- Загружается профиль пути из выбранного маршрута
- Создается и инициализируется экземпляр класса Train - физическая модель поезда. При инициализации этого класса происходит загрузка и настройка модулей DLL всего подвижного состава, входящего в поезд.
- Создается и инициализируется общая память для обмена с вьювером.
- Инициализируется внешний пульт управления
simulator/simulator/app.cpp
Код: Выделить всё
int AppCore::exec()
{
if (model != Q_NULLPTR)
model->start();
return QCoreApplication::exec();
}
Таймер, обрабатывающий симуляцию создается в классе Model и работает с интервалом, который определяется параметром IntegrationTimeInterval, задаваемым в конфиге cfg/init-data.xml. По-умолчанию его значение равно 20 миллисекундам. Таймер работает в отдельном потоке, дабы как можно четче выдерживать этот самый временной интервал.
Итак, один раз в 20 мс таймер вызывает метод (слот!) Model::process(), реализованный в классе Model так
simulator/model/model.cpp
Код: Выделить всё
void Model::process()
{
double tau = 0;
double integration_time = static_cast<double>(integration_time_interval) / 1000.0;
// Integrate all ODE in train motion model
while ( (tau <= integration_time) &&
is_step_correct)
{
preStep(t);
// Feedback to viewer
sharedMemoryFeedback();
controlStep(control_time, control_delay);
is_step_correct = step(t, dt);
tau += dt;
t += dt;
postStep(t);
}
train->inputProcess();
// Debug print, is allowed
if (is_debug_print)
debugPrint();
}
- preStep() - действия, которые требуется выполнить перед шагом интегрирования дифуров.
- sharedMemoryFeedback() - обмен данными с вьювером (визуализацией).
- controlStepControl() - чтение из общей памяти состояния клавиатуры и передача его в модель поезда
- step() - шаг решения дифуров.
- Наращивание счетчиков модельного времени.
- postStep() - действия, которые надо выполнить после шага интегрирования.
Код: Выделить всё
train->inputProcess();
simulator/train/train.cpp
Код: Выделить всё
void Train::inputProcess()
{
auto end = vehicles.end();
auto begin = vehicles.begin();
for (auto i = begin; i != end; ++i)
{
Vehicle *vehicle = *i;
vehicle->keyProcess();
vehicle->hardwareProcess();
}
}
Отсюда становится очевиден ответ - keyProcess() вызывается раз в IntegrationTimeInterval, то есть по-умолчанию раз в 20 миллисекунд. Поэтому да, наблюдается звонковая работа клавиш, в коде обработки необходимы специальные "противозвонковые" меры, применяемые на всех реализованных для сима локомотивах.
Так работает основной цикл симуляции. Этой его реализации присущ ряд проблем, поскольку в алгоритме есть достаточно тяжелая математика, например продольная динамика поезда. Многие процессы в тормозном оборудовании требуют малого шага интегрирования, исчисляемого 1-2 мс, поэтому, а общем, цикл симуляции и организован так хитро.
Что же, начало бесед о коде RRS положено, надеюсь это будет интересно, рассчитываю на полезные замечания и советы.