Hello everyone,
I’m working on a project within “MSFS 2020” where the goal is to allow users to create an object (here I try to create a firetruck) at specific locations on the map by clicking on it. The project consists of two main components: a JavaScript front-end that manages map interactions and GPS tracking, and a WASM module that is supposed to handle communication with SimConnect for firetruck creation. No external server like Node.js or any local servers are used; the JavaScript communicates directly with the MSFS WASM module.
Functionality:
- GPS Tracking:
- The map shows the current position of the user’s plane in real-time by polling SimVars like latitude, longitude, and heading. This part is functioning as expected, and the plane icon updates regularly.
- There’s a toggle button that enables or disables auto-follow mode. When enabled, the map centers on the plane as it moves.
- Map Layers:
- There is a layer switcher allowing users to toggle between different map styles (e.g., OpenTopoMap, OpenStreetMap, Thunderforest). This is also working correctly, with the map layers switching and updating without any issues.
- Firetruck Creation:
- When the user clicks on the map, the latitude and longitude of the click are sent to the WASM module, where it should trigger the creation of a firetruck at that location using SimConnect. The coordinates are correctly passed to the WASM module, and the click event seems to be processed fine in the JavaScript.
The Problem:
Despite having no apparent errors in the console and with the WASM debugger showing that the module is functioning, SimConnect is not properly connecting. Even though the WASM module is supposed to send requests to SimConnect to create a firetruck object, nothing appears in the simulator.
SimConnect Inspector does not detect any connection from the WASM module, and there is no evidence that any request is reaching the simulator. The SimConnect functionality is supposed to trigger the creation of a firetruck using SimConnect_AICreateSimulatedObject
, but it seems SimConnect isn’t initialized properly, even though the code runs without immediate errors.
Debugging Notes:
- WASM Ready Status: In the JavaScript code, we check if the WASM module is ready using
Module.ccall('isSimConnectReady')
, and it returnstrue
. However, SimConnect Inspector shows no active connection or events related to the module, indicating SimConnect may not be correctly set up inside the WASM module. - Firetruck Request Fails Silently: Even though the JavaScript is passing the correct coordinates to the WASM module, the firetruck doesn’t appear, and there’s no indication that the
SimConnect_AICreateSimulatedObject
request is reaching MSFS.
Question:
Does anyone have experience dealing with SimConnect and WASM modules in MSFS? Specifically, why might the WASM module not be establishing a connection with SimConnect, despite the code indicating it is ready? Also, how can I further debug or troubleshoot why SimConnect Inspector doesn’t recognize my module?
Below is the code for both the JavaScript and WASM modules. Any guidance would be appreciated!
PS: An explanation or example of how to resolve this issue would be greatly appreciated, as I am open to corrections or suggestions that will help fix this problem.
here my JavaScript & cpp (to be compilated into wasm) codes :
JS
let simvarLoaded = false;
let wasmReady = false;
let map;
let planeMarker;
let followPlane = false;
let tileLayer;
let currentLayer = 1;
function logMessage(message, type = "info") {
const messageArea = document.getElementById('messageArea');
const logEntry = document.createElement('div');
logEntry.className = `log-entry log-${type}`;
logEntry.innerHTML = message;
if (messageArea) {
messageArea.appendChild(logEntry);
messageArea.scrollTop = messageArea.scrollHeight;
}
console.log(message);
}
// Fonction pour enregistrer le callback de log côté WASM
function registerWasmLogCallback() {
if (typeof Module !== "undefined" && typeof Module._registerLogCallback === "function") {
Module._registerLogCallback((message) => {
logMessage(message, "wasm-log"); // Enregistre les logs du WASM
});
}
}
// Chargement de SimVar et initialisation de la carte
function loadSimVar() {
if (typeof SimVar === "undefined") {
logMessage("Loading SimVar...", "info");
Include.addScript("/JS/simvar.js", () => {
setTimeout(() => {
if (typeof SimVar !== "undefined") {
logMessage("[OK] SimVar loaded.", "success");
simvarLoaded = true;
initializeMap();
checkWasmReady();
} else {
logMessage("[ERROR] Failed to load SimVar.", "error");
}
}, 1000);
});
} else {
logMessage("[OK] SimVar already loaded.", "success");
simvarLoaded = true;
initializeMap();
checkWasmReady();
}
}
function checkWasmReady() {
if (typeof Module !== "undefined" && typeof Module.ccall === "function") {
try {
wasmReady = Module.ccall('isSimConnectReady', 'boolean', [], []);
if (wasmReady) {
logMessage("[OK] WASM module and SimConnect are ready.", "success");
registerWasmLogCallback(); // Enregistre le callback WASM pour recevoir les logs
} else {
logMessage("[INFO] SimConnect not ready yet. Retrying in 5 seconds...", "info");
setTimeout(checkWasmReady, 5000);
}
} catch (error) {
logMessage("[ERROR] Error checking WASM readiness: " + error, "error");
setTimeout(checkWasmReady, 5000);
}
} else {
logMessage("[INFO] WASM module not loaded yet. Retrying in 5 seconds...", "info");
setTimeout(checkWasmReady, 5000);
}
}
function updatePlanePosition() {
if (!simvarLoaded) return;
try {
const lat = SimVar.GetSimVarValue("GPS POSITION LAT", "degree latitude");
const lon = SimVar.GetSimVarValue("GPS POSITION LON", "degree longitude");
const heading = SimVar.GetSimVarValue("PLANE HEADING DEGREES TRUE", "degrees");
if (planeMarker) {
planeMarker.setLatLng([lat, lon]);
const iconWrapper = planeMarker.getElement().querySelector('.plane-icon-wrapper');
if (iconWrapper) {
iconWrapper.style.transform = `rotate(${heading}deg)`;
}
if (followPlane) {
map.setView([lat, lon]);
}
}
} catch (error) {
logMessage("[ERROR] Error updating plane position: " + error, "error");
}
}
function initializeMap() {
const initialLat = SimVar.GetSimVarValue("GPS POSITION LAT", "degree latitude");
const initialLon = SimVar.GetSimVarValue("GPS POSITION LON", "degree longitude");
map = L.map('map', {
minZoom: 2,
maxZoom: 19,
zoomControl: true,
dragging: true
}).setView([initialLat, initialLon], 14);
tileLayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
attribution: 'Map data: © OpenStreetMap contributors, OpenTopoMap'
}).addTo(map);
const planeIcon = L.divIcon({
className: 'plane-icon',
html: '<div class="plane-icon-wrapper"><img src="/icons/toolbar/ICON_MAP_PLANE_WILDFIRE.svg" alt="Plane"></div>',
iconSize: [80, 80],
iconAnchor: [40, 40]
});
planeMarker = L.marker([initialLat, initialLon], { icon: planeIcon }).addTo(map);
map.on('click', function (e) {
triggerPulseMarker(e.latlng.lat, e.latlng.lng);
triggerFireTruckCreation(e.latlng.lat, e.latlng.lng);
});
initializeToggleButton();
initializeLayerSwitcher();
setInterval(updatePlanePosition, 1000);
}
function triggerPulseMarker(lat, lon) {
const pulseIcon = L.divIcon({
className: 'vfx-marker',
html: '<div class="pulse"></div>',
iconSize: [30, 30],
iconAnchor: [15, 15]
});
const pulseMarker = L.marker([lat, lon], { icon: pulseIcon }).addTo(map);
setTimeout(() => map.removeLayer(pulseMarker), 2000);
}
function triggerFireTruckCreation(lat, lon) {
if (!wasmReady) {
logMessage("[ERROR] WASM module not ready. Cannot create firetruck.", "error");
return;
}
try {
Module.ccall('updateClickCoordinates', null, ['number', 'number'], [lat, lon]);
const success = Module.ccall('requestFireTruckCreation', 'boolean', [], []);
if (success) {
logMessage("[OK] Firetruck creation requested successfully.", "success");
} else {
logMessage("[ERROR] Failed to create firetruck.", "error");
}
} catch (error) {
logMessage("[ERROR] Error creating firetruck via WASM: " + error, "error");
}
}
function initializeToggleButton() {
const toggleButton = document.getElementById('toggle-follow');
if (toggleButton) {
toggleButton.setAttribute('on', "false");
toggleButton.addEventListener('click', function () {
followPlane = !followPlane;
this.setAttribute('on', followPlane);
this.setAttribute('title', followPlane ? "Désactiver Suivi" : "Activer Suivi");
logMessage(followPlane ? "[OK] Suivi activé." : "[OK] Suivi désactivé.", "success");
});
} else {
logMessage("[ERREUR] Bouton suivi non trouvé.", "error");
}
}
function initializeLayerSwitcher() {
const layerSwitchButton = document.getElementById('layer-switch');
if (layerSwitchButton) {
layerSwitchButton.addEventListener('click', function () {
currentLayer = (currentLayer % 3) + 1;
switch (currentLayer) {
case 1:
map.removeLayer(tileLayer);
tileLayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
attribution: 'Map data: © OpenStreetMap contributors, OpenTopoMap'
}).addTo(map);
break;
case 2:
map.removeLayer(tileLayer);
tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Map data: © OpenStreetMap contributors'
}).addTo(map);
break;
case 3:
map.removeLayer(tileLayer);
tileLayer = L.tileLayer('https://{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', {
attribution: 'Map data: © Thunderforest'
}).addTo(map);
break;
}
logMessage(`Couches de carte : ${currentLayer}`, "info");
});
} else {
logMessage("[ERREUR] Bouton switch non trouvé.", "error");
}
}
window.onload = function () {
logMessage("Loading...", "info");
loadSimVar();
};
wasm
#include <MSFS/MSFS.h>
#include <MSFS/MSFS_WindowsTypes.h>
#include <MSFS/Legacy/gauges.h>
#include <SimConnect.h>
#include <stdio.h>
#include <string.h>
#include <WASMModule.h>
// Déclaration d'un pointeur de fonction pour le callback vers JavaScript
typedef void (*LogCallbackType)(const char* message);
LogCallbackType logCallback = nullptr;
HANDLE hSimConnect = NULL;
bool simConnectConnected = false;
static double clickLat = 0.0;
static double clickLon = 0.0;
// Fonction pour initialiser la connexion à SimConnect
void initSimConnect() {
HRESULT hr = SimConnect_Open(&hSimConnect, "wildfirePanel", NULL, 0, 0, 0);
if (SUCCEEDED(hr)) {
simConnectConnected = true;
if (logCallback) logCallback("[WASM] Connexion à SimConnect réussie.");
}
else {
simConnectConnected = false;
if (logCallback) logCallback("[WASM] Échec de la connexion à SimConnect.");
}
}
// Fonction pour vérifier si SimConnect est prêt
extern "C" bool isSimConnectReady() {
if (logCallback) logCallback("[WASM] Vérification de la connexion à SimConnect.");
return simConnectConnected;
}
// Fonction appelée pour créer un camion de pompier aux coordonnées (lat, lon)
extern "C" bool requestFireTruckCreation() {
if (logCallback) logCallback("[WASM] Demande de création du camion de pompier...");
if (!simConnectConnected) {
if (logCallback) logCallback("[WASM] SimConnect non connecté. Impossible de créer le camion.");
return false;
}
// Initialisation de la position
SIMCONNECT_DATA_INITPOSITION init;
init.Latitude = clickLat;
init.Longitude = clickLon;
init.Altitude = 0; // Par défaut au sol
init.Pitch = 0;
init.Bank = 0;
init.Heading = 0;
init.OnGround = 1; // Assurez-vous que l'objet est sur le sol
init.Airspeed = 0;
// Création du camion de pompier via SimConnect
HRESULT hr = SimConnect_AICreateSimulatedObject(
hSimConnect,
"ASO_Firetruck01", // Nom de l'objet à créer
init, // Données de position initiale
0 // Aucun flag supplémentaire
);
if (SUCCEEDED(hr)) {
if (logCallback) logCallback("[WASM] Camion de pompier créé avec succès.");
return true;
}
else {
if (logCallback) logCallback("[WASM] Échec de la création du camion de pompier.");
return false;
}
}
// Fonction pour mettre à jour les coordonnées du clic depuis JavaScript
extern "C" void updateClickCoordinates(double lat, double lon) {
clickLat = lat;
clickLon = lon;
if (logCallback) logCallback("[WASM] Coordonnées de clic mises à jour.");
}
// Fonction pour enregistrer le callback de log
extern "C" void registerLogCallback(LogCallbackType callback) {
logCallback = callback;
}
// Fonction d'initialisation appelée lorsque WASM est chargé
extern "C" void module_init() {
if (logCallback) logCallback("[WASM] Initialisation du module WASM.");
initSimConnect(); // Initialisation de SimConnect à ce moment-là
}
// Fonction de nettoyage appelée lorsque le module est déchargé
extern "C" void module_deinit() {
if (logCallback) logCallback("[WASM] Déchargement du module WASM.");
if (simConnectConnected) {
SimConnect_Close(hSimConnect);
if (logCallback) logCallback("[WASM] Connexion SimConnect fermée.");
}
}