前言
近来无事,想来试着做一套铁路仿真软件,通过任务选择的方式得到列车,线路,列车故障事件,评判规则等,数据主要包括列车车型数据,铁路线路数据,用户数据,训练数据和评判数据等。
系统架构上考虑分为计算(服务)和表现(客户)端,客户端采用unity(c#),服务端..,开始考虑的用c++,但确实没有内置的运行时反射,很多序列化不好办,包管理器也没有,那就考虑一个性能差不多的成熟的原生语言。这里考虑用pascal,至少上手比较容易。
由于是本机程序,所以数据库采用sqlite,服务端和客户端通讯采用udp,点对点通信,客户端那边也就由一点的通讯,同时与多个子节点进行通讯和管理,子节点应包括用户表现的部分,例如ui,电路图,车载显示器,三维场景,司机操作面板等。
客户端主要交由某个公司去做吧,这里主要编制服务端。
服务端的仿真计算周期30ms,包括线路计算、电路计算、牵引制动力计算、空气管理计算,车载信号计算等。如果作为单线程,则要求所有时间可控,但由于线路包括上千公里的遍历与查找,可能使计算时间不稳定,所以还是会使用到多线程。
数据
数据首先需要制作和调试,以及各方面数据的对照检查,所以提供一个数据制作工具,然后存储部分由于有大量的动态结构,所以采用字段+json(至少读下来然后反序列化就可以直接用),以及最后一个类为数据的远程读取,存储和修改。
Tsm_data = class
public
function get_train(name: string): string;
function get_task(id: integer): nullable<string>;
function get_road(road_id: integer): string;
function set_train(name: string; json: string): boolean;
function set_task(id: integer, Json: string): boolean;
function set_road(road_id: integer, Json: string): boolean;
end;
与此对应的提供序列化的类,通过以往的经验,似乎mormot2的函数比较高效
Tsm_json<T> = class
public
function to_variant(Json: string): T;
function to_string(v: T): string;
end;
通讯
udp通讯的最大好处就是快,然后不容易异常,这里就用indy吧,然后之间的通讯数据包括三类:命令字(启动,暂停,结束,保存等),操作数据(例如操作了设备,按下按键),状态数据(例如操作台面板的灯亮,列车行驶到的位置等),为了减少通讯量,我们只发送接收变化的值,毕竟很多时候用户什么事都没做,所以通讯的是个动态结构,这里用Json进行序列化和反序列化,然后就可以用一个通用的结构来表达。
comm_Json = record
id_, type_, subtype_: Integer;//命令字 类型 子类型
timestamp: Integer; //时间戳
content_json: string; //实际内容
end;
当然还会涉及一系列的枚举和常量定义,这里不赘述了。
内部结构
如果一个程序内部有多个线程同时按照定时周期进行计算,同时每个线程之间还存在实时的数据交互,那怎么办呢?共享读写变量好像不太合适,那就使用event_bus可能比较合适,只要有一个模块/线程具有30ms的定时器,那么就可以通过发送信号让每个模块/线程都计算一次,这样来推动?大概是可行的,然后一些慢速模块/线程,比如日志记录/存储/评价等,就不用接收定时信号了。
最后弄一个计算类,初始化把事件总线的服务端、通讯端口,目录日志,线路计算、列车计算、数据读写等都放进去,大概就可行了。
不早了,下面就可以编代码了,这就是今天的内容。