Yes, I was just about to mention this data record (structure), which does have an additional OnGround
member: when this value is set to “true” (value 1) then MSFS will properly place the aircraft on ground (also such that its gear is properly aligned with the surface).
Note the remark given in
https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Structures_And_Enumerations/SIMCONNECT_DATA_INITPOSITION.htm
Quote:
The primary use of this structure is to initialize the positioning of the user aircraft, because it also optimizes some of the terrain systems and other Microsoft Flight Simulator systems. Simply setting parameters such as latitude, longitude and altitude does not perform this kind of optimization. This structure should not be used to incrementally move the user aircraft (as this will unnecessarily initiate the reloading of scenery)
In practice this means that MSFS will (re-)spawn other AI objects such as airport vehicles and other AI aircraft (which - unfortunately - might end up on the same parking spot as your “user aircraft” (= the aircraft normally controlled by the player, and being followed by the camera, having object ID = SIMCONNECT_OBJECT_ID_USER
(= 0), in case the user aircraft also happens to be parked on a parking spot.
Also, as mentioned in the remark, only send this (special) data structure once, at the beginning of your “simulation” (in my case: at the beginning of the “replay”, or when completely “rewinding” to “timestamp 00:00:00”).
Have a look at my MSFSSimConnectPlugin#onInitialPositionSetup() method. Just mentally remove all the “C++ gibberish”, e.g. I am using “real C++ enumeration classes”, and the verbose “utility function” Enum::toUnderlyingType simply removes the underlying type, typically an int (integer).
By the way, I am using an “event based recording/replay”, that is instead of an “infinite while loop with some sleep()” (which is the typical example given in various SDK example code) and polling for data (or sending data in a fixed interval) I am in fact “requesting data as it becomes available”, with a frequencey according to SIMCONNECT_PERIOD_SIM_FRAME (“simulated frame”). So during recording MSFS sends us data update for each “simulated frame” (which might be > the displayed FPS, but on average computer systems it is roughly equal to the displayed frames per second).
Likewise, each time we receive the “simulated frame” event we send data to MSFS. Like this we do not “oversaturate” the “SimConnect channel”, and send data whenever MSFS “is ready for it”.
(I also support a timer-based approach for recording, in order to reduce the sampled data).
But how then do we get “notified” about this “simulated frame event” (and any other available data), without “polling” for it ourselves (again, either in some “busy loop” (+ some sleep() in between, in order not to completely burn the CPU ;)), or timer-based)?
The trick is to use the underlying WIN32 event system.
The given SDK example uses WIN32 API and the example (IIRC) is already some kind of “UI class” which a) already has its own “window handle” (of type HWND) and b) (IIRC) already inherits some functionality to filter / receive the WIN32 events.
As my actual “connection plugin” is actually “without user interface” I am simply creating a “hidden QWidget” (yes, my app is a Qt toolkit application), have a look at the EventWidget class. This “hidden widget” re-implements the nativeEvent method which is called whenever a “native WIN32 event” has been rececived by the application (which in case of Qt applications “broadcasts” it to all its child widgets, such as my “hidden event widget” (“hidden” in the sense that this widget is simply never displayed anywhere).
The actual “logic” is very simple: we filter the SimConnnectUserMessage
(which is defined to be 0x0402, but that is just some random “magic number” which happens to be the same as the given SDK example that I one found (somewhere ;)). I don’t think the actual value matters (but “client events” may have to be larger (and/or smaller) than some system / WIN32 API defined value, can’t remember - consult your WIN32 API docu ;)).
bool EventWidget::nativeEvent([[maybe_unused]] const QByteArray &eventType, void *message, [[maybe_unused]] long *result) noexcept
{
bool handled;
const MSG *msg = static_cast<MSG *>(message);
switch(msg->message) {
case SimConnnectUserMessage:
emit simConnectEvent();
handled = true;
break;
default:
handled = false;
break;
}
return handled;
}
Whenever we receive this “SimConnnectUserMessage” event we notify any “listener” (Qt uses “signals and slots” - think of it as some elegant “callback mechanism” (“observer pattern”), if you haven’t heard about Qt signals and slots).
So a final but important question remains: how does MSFS know that we are expecting this SimConnnectUserMessage user-defined WIN32 event (with its “magic ID” 0x0402)?
That’s because we request this when establishing the connection with MSFS, like so:
bool MSFSSimConnectPlugin::connectWithSim() noexcept
{
HWND hWnd = reinterpret_cast<HWND>(d->eventWidget->winId());
DWORD userEvent = EventWidget::SimConnnectUserMessage;
HRESULT result = ::SimConnect_Open(&(d->simConnectHandle), ::ConnectionName, hWnd, userEvent, nullptr, ::SIMCONNECT_OPEN_CONFIGINDEX_LOCAL);
if (result == S_OK) {
...
}
}
Aha! We provide MSFS both our HWND (“window handle”), so MSFS knows who is expecting WIN32 events, and we are also telling which WIN32 event we’d like to receive whenever data is ready to be read (like the “simulated frame event” - and don’t confuse “native WIN32 events” with “SimConnect events”). That is, our defined userEvent
. So you see, also MSFS “knows” about our “magic WIN32 event ID”.
So after the connection was successful MSFS knows that we (HWND) are expecting “native events”, and it also knows which event ID to send us whenever there is something to be read. So all that remains is to listen to those WIN32 events (-> EventWidget) and whenver we have received such a native event we can then call our “simconnect data dispatcher”, by calling SimConnect_CallDispatch()
(which we would otherwise do in a “busy loop”, or timer-based).
Whether such a “native event” based processing or a simple (“fixed update rate”) “busy loop” is more appropriate depends on the frequency and the actual scenario. But I found that for my “replay (recording) scenario” this event-based approach suits me well, as I want to “send the data as often as possible - but not more often than necessary” (= don’t overwhelm MSFS with superfluous data requests it cannot handle anyway).
Again, to summarise:
- Upon “connection” we provide the (optional) HWND “window handle” and our defined “native event ID”
- We then receive and filter our “native event ID”
- Whenever we have received this “native event ID” we simply call
SimConnect_CallDispatch()
- In case of replay we check whether we have received the “Simulated Frame Event” (= a “SimConnect event” to which we have subscribed); if we received this event, we send our request data (“update latitute, longitue, etc.”)
- In case of “recording” we simply check the received data type and process / store it accordingly
This also gracefully handles situations where MSFS may have to lower its FPS due to increased scenery complexity (or “other hickups”), and hence we would also receive less “simulated frame events” and by imlication would send less data requests (during replay), until MSFS has “recovered” its performance (FPS).
And for the record, flights can be programmatically saved and loaded into a file (stored somewhere at a path of your choice, but MSFS needs to have access to this path) with:
https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Flights/SimConnect_FlightLoad.htm
But again, I haven’t tried this myself just yet.