• Which the release of FS2020 we see an explosition of activity on the forun and of course we are very happy to see this. But having all questions about FS2020 in one forum becomes a bit messy. So therefore we would like to ask you all to use the following guidelines when posting your questions:

    • Tag FS2020 specific questions with the MSFS2020 tag.
    • Questions about making 3D assets can be posted in the 3D asset design forum. Either post them in the subforum of the modelling tool you use or in the general forum if they are general.
    • Questions about aircraft design can be posted in the Aircraft design forum
    • Questions about airport design can be posted in the FS2020 airport design forum. Once airport development tools have been updated for FS2020 you can post tool speciifc questions in the subforums of those tools as well of course.
    • Questions about terrain design can be posted in the FS2020 terrain design forum.
    • Questions about SimConnect can be posted in the SimConnect forum.

    Any other question that is not specific to an aspect of development or tool can be posted in the General chat forum.

    By following these guidelines we make sure that the forums remain easy to read for everybody and also that the right people can find your post to answer it.

MSFS20 Memory sharing in MSFS

Messages
531
Country
france
Hi Developers,

I would like to ask a general question about memory sharing in MSFS, I mean the way several instruments can share information. Let me explain...
In FSX/P3D, the gauges used to be DLLs, and you could have all the gauges of an aircraft grouped into a single DLL (what I usually did). When the aircraft was loaded in the sim, the DLL was loaded and executed in the same memory space as the sim. Anyway, it meant that several parts of the DLL could share information because they were in the same memory space. For example, the flight plan that was composed of all the route waypoints could easily be shared between the FMC, which displays the flight plan as a list, and the Navigation Display (or the Multi-Function Display) that was drawing the route on the screen. That was easy.

In MSFS it is different because each instrument is completely independent form the others. This is what I noticed when developing instruments in JavaScript (I didn't try WASM but I guess it is the same). If we take the same example, the flight plan information cannot be shared between the FMC and the ND/MFD. If you use the standard MSFS flight plan, that's fine because MSFS provides a way to read this information, so both the FMC and the ND/MFD can read it, they will both read the same information. But what if you want to create you own custom flight plan with your own data structure? Or what if you want to share any other kind of custom information between instruments?
I know you can use L: vars for this, this is fine for simple information, but it may not be the right solution for complex information such as a flight plan or any other structured information. I am not even sure a L: var can handle a string. If so, how can we share a string between 2 instruments?

If I missed something in the SDK, I would be glad to know :)

Thanks,
Eric
 
It seems this question has no object because I found a way to let all the instruments of a panel see each other, which means they can share information.

I am surprised because I am 99% sure it didn't work before, I already tested this in the past. Maybe something changed in the way the sim manages the panel, anyway I am happy to see it works fine.

I like this kind of post in which I make questions and answers 😂
 
It is simple.
Let's say I have a PFD and a ND, each of them in a class.
I have created a "Shared.js" in which I have functions for each instrument to register, something like:

RegisterPFD(instr) {
pfd = instr;
}
RegisterND(instr) {
nd = instr;
}

and other functions to get these variables:

GetPFD() {
return pfd;
}
GetND() {
return nd;
}

You cannot imagine anything simpler :)
In addition, each instrument registers itself in the class constructor.
For the PFD:
RegisterPFD(this);
For the ND:
RegisterND(this);

Now the good news is that my "Shared.js" is seen by all my instruments, which means that in the ND class, I can use GetFPD() to have access to the PFD and all the information it handles, and vice-versa for the ND. It means that with this, each instrument can see all other instruments as soon as they are registered this way.
I am surprised that it works because I thought each instrument had its own memory space, which would mean that "Shared.js" would be duplicated for each instrument with its own variables, and it wouldn't work. But it works, and this shows the same "Shared.js" is used by all the instruments of the same panel, this is good news.
 
Please let me know if it works as expected for you, because as I said in my first post, I thought I had tested this some months ago and it didn't work.
 
looks pretty neat to me... I guess you have a race between the PFD and the ND for who can reference the other first but can watch out for nulls in the update loops.

So far for my stuff (e.g. with a shared FlightPlan, which is not a gauge) the relationships are asymmetric so I can instantiate the FlightPlan first and the multiple gauges reference that instance.

Specifically for Flight Plan are you using the Coherent.call(GET_FLIGHTPLAN) (sp?) or are you using the Asobo FlightPlanManager / FlightPlan classes? I've found the 'get' works great but the flightplan has a whole load of crap in it (TIMEDCNT waypoints etc) I don't want.
 
that uses a SimVarBatch
Thanks Eric - does your "SimVarBatch" method involve iterating the FS9GPS vars (i.e. where you update an index and get the waypoint data some time later) or have you invented a way that avoids all that? (I looked for "SimVarBatch" on Google and remarkably absolutely nothing turns up, even the usual garbage) - I realise this is dragging the thread sideways so I can create a new thread for "SimVarBatch" if you're ok answering that.
 
I didn't invent anything here :)
The way I wrote my flight plan manager is based on code I found in the existing JavaScript source files provided by Asobo. By the way, I regret this is not explained in the SDK, having to dig into someone else's code to understand this is a real pain. Anyway, you are right, my code uses iterations on the fs9gps vars, this is the way it works. I also had a look at the FlightPlanManager.js and it seems that using the Coherent calls would make my code much simpler. I will probably re-write this part to take advantage of what Asobo created for us. Again, I wish it was fully documented in the SDK but I'm afraid this will remain a dream forever...
I don't think anyone will blame you if this discussion is moving away from the initial subject. It is often the case in this forum and I am sure the information we share here is interesting for many other developers, I think we can keep chatting in this thread.
 
If it's any help at all... I use the coherent call in a "Task" class (code below) which loads the MSFS flightplan and creates a local list of "waypoints". The main advantage is the call is asynchronous (that's a plus, keeping the update loop non-blocking) but it returns all the flightplan in a single returned JS object, so any JS values are possible and no messing with iterating the values across multiple update calls (I've been there in FSX and MSFS before this...).

Here's the relevant methods Task.load() which uses Coherent.call() and also Task.add_waypoint_from_flightplan() which constructs my own Waypoint object given each waypoint found in the flightplan. Of course it's similar to MSFS FlightPlanManager.js which is the Asobo example we both found, but maybe some of my embedded comments will provide clues (e.g. you can see me dropping the TIMECRUIS, TIMEDSCNT, TIMEVERT waypoints added by MSFS which is a bad design IMHO, they should have given access to the flightplan "as loaded" and build their own data separately for the autopilot). I need the flightplan because we use that to hold soaring tasks, similar to a VFR flight A to B to C to A, and we don't need MSFS messing with the data from the file before we get it, e.g. it overwrites the flightplan 'elevation' data for any airport waypoint, replacing it with the MSFS airport ground elevation.

Please note in the code below I use my own decode_wp_name() - this is special for soaring where we encode additional information in the waypoint NAME specifically because MSFS messes will all the other data. E.g. a waypoint called "*Foo Airport+1234" can be 'decoded' so my code knows to use an elevation of 1234 feet in the nav instrument (which may not be the airport elevation) and the "*" means this waypoint is the start of the racing task (although not necessarily the start of the flight plan). I suspect you can sensibly ignore that detail.

Code:
    // Load task from flightplan
    // As \Official\Steam\asobo-vcockpits-instruments\html_ui\Pages\VCockpit\Instruments\Shared\FlightElements\FlightPlanManager.js
    load() {
        //this.instrument.debug_log("Task.load()");
        let parent = this;
        Coherent.call("GET_FLIGHTPLAN")
            .then((flightPlanData) => {
                //this.instrument.debug_log("got flightplan");
                //this.instrument.debug_log("flightPlanIndex "+flightPlanData.flightPlanIndex);
                //this.instrument.debug_log("activeWaypointIndex "+flightPlanData.activeWaypointIndex);
                //this.instrument.debug_log("waypoints length "+flightPlanData.waypoints.length);
                console.log("GET_FLIGHTPLAN", flightPlanData);
                this.start_index_is_set = false; // Has this.start_index and this.finish_index been set ?
                this.finish_index_is_set = false;
                if (flightPlanData.waypoints.length > 1) {
                    for (let i=0; i<flightPlanData.waypoints.length; i++) {
                        let fp_wp = flightPlanData.waypoints[i];
                        this.add_waypoint_from_flightplan(fp_wp);
                    }
                }

                if (this.waypoints.length > 0) {
                    this.active = true;
                    this.index = 0; //(this.waypoints.length > 1) ? 1 : 0;
                    // start_index and finish_index MAY have be set by a WP name starting with '*' so we should not overwrite
                    if (!this.start_index_is_set) {
                        this.start_index = this.waypoints.length < 4 ? 0 : 1;
                    }
                    if (!this.finish_index_is_set) {
                        this.finish_index = this.waypoints.length < 4 ? this.waypoints.length - 1 : this.waypoints.length - 2;
                    }
                    //this.instrument.debug_log("Flightplan Active");
                } else { // No flightplan, so use current position
                    //this.instrument.debug_log("No flightplan, using HOME");
                    this.waypoints = [ new WP(  this,   // task
                                                0,
                                                "HOME",
                                                this.instrument.PLANE_POSITION,
                                                SimVar.GetSimVarValue("GROUND ALTITUDE", "meters")
                    ) ];
                    this.index = 0;
                    this.active = true;
                }
                this.instrument.nav_display_refresh(0);
            })
            .catch((e) => {
                console.log("GET_FLIGHTPLAN exception", e);
                this.instrument.debug_log("GET_FLIGHTPLAN rejected");
            }); // End Coherent.call()
    }

    // For each MSFS flightplan waypoint, add it to this.task.waypoints[ ]
    add_waypoint_from_flightplan(fp_wp) {
        //this.instrument.debug_log("Task add WP "+fp_wp.ident);
        console.log("add_waypoint_from_flightplan", fp_wp.ident, fp_wp);
        if ( fp_wp.ident.startsWith("TIMECRUIS") || fp_wp.ident.startsWith("TIMEDSCNT") || fp_wp.ident.startsWith("TIMEVERT") ) {
            console.log("add_waypoint_from_flightplan skipping",fp_wp.ident);
            return;
        }

        let index = this.waypoints.length;

        // Note MSFS **OVERLOADS** fp_wp.icao with additional (hack) info and AFAIK airports will be "A <icao>" e.g. "A KRVL"
        // this is because MSFS will use .facilityLoader.getFacility(fp_wp.icao) to get the data. We don't need that.
        // So here we generate our WP .name either from the .ident or the .icao
        let name;
        if (fp_wp.ident==null || fp_wp.ident=="") {
            if (fp_wp.icao!=null && fp_wp.icao.startsWith("A ") && fp_wp.icao.length > 7) {
                //console.log("add_waypoint_from_flightplan",fp_wp.icao.replace(/ /g,"x")); // no replaceAll() in Coherent Debugger
                name = fp_wp.icao.slice(7);
                if (name.endsWith(" ")) {
                    name = name.slice(0,-1);
                }
            } else if (fp_wp.icao!=null && fp_wp.icao.replace(/ /g,"")!="") { // Check icao not null or all spaces
                name = fp_wp.icao;
            } else {
                name = "TP"+index;
            }
        } else {
            name = fp_wp.ident;
        }

        // Get properties for each waypoint
        const wp = new WP(  this,   // task
                            index,
                            name,
                            new LatLong(fp_wp.lla.lat, fp_wp.lla.long),
                            fp_wp.lla.alt
        );

        let original_name = wp.name;
        this.decode_wp_name(wp, index);
        console.log("Wp from '"+original_name+"' FP extra params: alt_m="+wp.alt_m.toFixed(0)+
            " radius_m="+(wp.radius_m==null ? "null" : wp.radius_m.toFixed(0))+
            " min/max_alt_m="+(wp.min_alt_m==null ? "null" : wp.min_alt_m.toFixed(0))+"/"+
            (wp.max_alt_m==null ? "null" : wp.max_alt_m.toFixed(0)));

        if (index > 0) {
            const prev_wp = this.waypoints[index-1];
            wp.update_leg_distance(prev_wp);
            wp.update_leg_bearing(prev_wp);
            prev_wp.update_start_line(wp);
            wp.update_finish_line();
        }

        // We got a good waypoint, so add it to local flightplan
        // Add this waypoint to our internal this.task array
        this.waypoints.push(wp);
    }
 
Thanks for sharing that code, I will look at it closely.
What I see now is that it is quite similar to the code I have, even if mine is certainly more complex and difficult to understand/maintain. But like your code, my code works is asynchronous mode, which is good, I agree with you.
I didn't look into details yet, but one thing looks strange to me: in your code it seems that you didn't register any listener and as far as I remember, a listener was used in FlightPlanManager.js (I can't remember its name). Isn't that necessary?
 
MSFS it is different because each instrument is completely independent form the others. This is what I noticed when developing instruments in JavaScript (I didn't try WASM but I guess it is the same).

In WASM it would be very similar to a .dll, the sub gauges can all be within a single gauge file.
 
Thanks for the info, that's good to know. At this time I went with JavaScript but I may think about WASM in the future, even if I must say JavaScript development is VERY convenient, especially with the ability to test the instruments in a web page before deploying them.
 
even if I must say JavaScript development is VERY convenient, especially with the ability to test the instruments in a web page before deploying them.
I agree with you on that. With WASM it takes way too long to see the results of your code changes, and the debugging is pretty much useless. The main reason I don't go to JavaScript is all the code is visible.
 
I understand your point. Debugging is so easy in Javascript, especially with the Coherent debugger provided in the SDK, it is very powerful.
Regarding the visibility of the code, you are right, but there must be some way to encrypt it when you release your product on the marketplace. I didn't look into this closely but I am sure there is a possibility.
In the meantime, I found a code obfuscator that works very well, it makes your code absolutely impossible to read and understand while working exactly the same.
 
a listener was used in FlightPlanManager.js
yes, asobo-vcockpits-instruments .. FlightElements/FlightPlanManager.js has the following code, and I initially did something similar in my flight plan code:

Code:
    registerListener() {
        if (this._isRegistered) {
            return;
        }
        RegisterViewListener("JS_LISTENER_FLIGHTPLAN", () => {
            Coherent.call("LOAD_CURRENT_ATC_FLIGHTPLAN");
            this.instrument.requestCall(() => {
                let nbWp = SimVar.GetSimVarValue("GPS FLIGHT PLAN WP COUNT", "number");
                SimVar.SetSimVarValue("L:Glasscockpits_FPLHaveOrigin", "boolean", (nbWp > 0 ? 1 : 0));
                SimVar.SetSimVarValue("L:Glasscockpits_FPLHaveDestination", "boolean", (nbWp > 1 ? 1 : 0));
                this._isRegistered = true;
                this._isRegisteredAndLoaded = true;
                if (this._onCurrentGameFlightLoaded) {
                    this._onCurrentGameFlightLoaded();
                }
            }, 200);
        });
    }

Essentially this usage seems primarily for some 'loaded' feedback into the gauge code. Ultimately my 'Task' class didn't need that and I decided to do without it. Maybe it's interesting to note the code above is from the latest version of MSFS - in a prior version that I initially copied into my gauge the RegisterViewListener is structured differently:

Code:
    registerListener() {
        if (this._isRegistered) {
            return;
        }
        this._isRegistered = true;
        RegisterViewListener("JS_LISTENER_FLIGHTPLAN");
        setTimeout(() => {
            Coherent.call("LOAD_CURRENT_ATC_FLIGHTPLAN");
            setTimeout(() => {
                this._isRegisteredAndLoaded = true;
                if (this._onCurrentGameFlightLoaded) {
                    this._onCurrentGameFlightLoaded();
                }
            }, 200);
        }, 200);
    }
 
Eric regarding your 'memory sharing', is it possible the technique works between gauges within the same panel.cfg [VCockpit] but not between e.g. [VCockpit01] and [VCockpit02] ?

It seems that way to me but I didn't exactly replicate your technique. What I did was:

At the top of one gauge HTML file I added at the top:
Code:
<script>
    var test_var = "hello world";
</script>

In another gauge JS file I added the following code which runs on start up and on a gauge button press:
Code:
            if (typeof test_var === 'undefined') {
                console.log("test_var is undefined");
            } else {
                console.log("test_var=[" + test_var + "]");
            }

This works between gauges within a VCockpit but not between VCockpits. If I duplicate the global test_var declaration in the HTML for both gauges (still in different VCockpits), then they each seem to see their 'local' copy only. The technique is still super-useful, but we might need to manage the VCockpit structure (and hence textures) if my testing is correct.
 
Last edited:
Eric regarding your 'memory sharing', is it possible the technique works between gauges within the same panel.cfg [VCockpit] but not between e.g. [VCockpit01] and [VCockpit02] ?
Yes, I think this is the case. I came to this same conclusion when I was comparing the A320 Neo with the aircraft I am working on today. Some months ago I developed custom displays for the A320 and noted there is no memory sharing between instruments, which is why I wrote earlier in a post "I was sure this was not working before and now it works." The aircraft I am now working on has all the gauges defined in the same VCockpit section (using the same VC texture) and memory is shared.
Nothing about this is written in the SDK, again we have to guess everything, but I think we guessed right this time :)
 
Back
Top