Multithreading Simulator

Hi, in my fork I’ve used std::map<uint32, Train *> to store Train state because I needed persistent key to store for example current active train in UI.
Previouse std::vector suffered from index changes when a Train was added/removed.

The problem now is that since I store pointers to Train, I cannot deep copy it and so I need to lock mutex when drawing. So now it does not use multithreading efficently…

Hi,

Is it required to have a pointer to Train, else std::map<uint32, Train> would work, then you can easily deep copy it.

That could be an option. Then I would need to store vehicle activeTrain as a train ID and trains’s vehicles as a list of vehicle IDs.

My goal is to have 3 threads:

  • UI thread: deep copy state so does not lock during painting
  • Logic: core, does position calculation and all stuff
  • Network: UDP discovery, handshake timer not delayed by logic thread, manages connected clients list

Now how do I separate logic and network?
Do I need another boost io_context? And when I need to loop objects to send state refresh to clients should it be network thread or logic thread.

Ideally we want to lift burden of looping from the logic thread and put it in network thread.
But then we need to not wait on lock otherwise we might delay handshake timer.
So maybe we need to do some deep copy of state periodically also on network thread? How often?

Yes indeed, then it is easy deep copyable :slight_smile:

Yes you need an io_context per thread, it is possible to have multiple threads on a single io_context but they will act as workers then, so the will both do IO work which adds another level of complexity, you don’t want that here :wink:

All network IO must take place in the network thread, you could either do a periodic deep copy, or use e.g. boost::asio::post in the logic thread to post work to the network thread. (Traintastic server does that for: interface kernel thread ↔ event loop thread.)

If you want to send the full state periodically, you could e.g. make a deep copy of the state at the end of every N logic loops, when a copy is made post a call to the network thread so it knows the state data is updated then it can send it.

Hope I understand it correctly, if not please let me know.

Greetings,
Reinder

Good point. So there are 2 options:

  • lock from UI thread and deep copy state
  • deep copy state in logic thread and post to UI thread, this does not lock by maybe deep copies again to store in UI thread

I don’t know which is better.

Also now I store train’s vehicles as IDs which means many access to lookup ID and get vehicle object…

Then we could join Vehicle to VehicleState and Train to TrainState since it’s all dynamic now.

We should really discuss a refactoring plan!

@reinder I’ve managed to avoid pointers in StateData so now it’s deep copiable.

Also I use QCoreApplication::postEvent(simulatorView, ...) to send a copy to UI thread without locking state mutex.

Currently some UI action use boost::asio::post() but some have not been ported yet so they still lock the mutex.
Now I’m studying how to move network to another thread.
receive() and receiveVariableSize() are both interacting with state so it’s complex to move them to separate thread.

Yes, that is an option too, then Qt will take care of sending it to the correct thread. (Don’t know about its performance, probably good enough.)

You basically have two options in the network thread:

  1. Lock the state and update it.
  2. Use boost::asio::post to the state thread so it can do the update.

The 2nd option is used by Traintastic server ↔ protocol kernel communication.