Howto talk:Troubleshooting Nasal Callbacks

From FlightGear wiki
Jump to navigation Jump to search

proof-of-concept UI

WIP.png Work in progress
This article or section will be worked on in the upcoming hours or days.
See history for the latest developments.
canvas.MessageBox.warning(
    "Developer Feature",
    "This dialog is mainly intended for people familiar with FlightGear/core internals to help troubleshoot issues related to Nasal callback invocation (timers/listeners).",
    func(sel){
        if(sel != canvas.MessageBox.Ok) return;
###################################################################
var (width, height) = (700, 480);
var title = 'Callback Monitor';
 
var window = canvas.Window.new([width, height], "dialog")
    .set("title", title);
 
var myCanvas = window.createCanvas().set("background", canvas.style.getColor("bg_color"));
 
var root = myCanvas.createGroup();
 
var myLayout = canvas.VBoxLayout.new();
 
myCanvas.setLayout(myLayout);
 
var controls = canvas.HBoxLayout.new();
myLayout.addItem(controls);
 
var ControlButtons = [
        {type:canvas.gui.widgets.Label, name: "Property reads/frame: 650", callback: func nil },
        {type:canvas.gui.widgets.Label, name: "Property writes/frame: 350", callback: func nil },
 

        {type:canvas.gui.widgets.Label, name: "Event Queue: 555", callback: func nil },
 
        {type:canvas.gui.widgets.Label, name: "Total Timers: 123", callback: func nil },
        {type:canvas.gui.widgets.Label, name: "Total Listeners: 444", callback: func nil },
 
        {type:canvas.gui.widgets.Button, name: "Global reset", callback:  func nil },
        {type:canvas.gui.widgets.Button, name: "Pause/unpause", callback: func nil },
];
 
foreach(var c; ControlButtons) {
controls.addItem(
        c.type.new(root, canvas.style, {})
        .setText(c.name)
        .setFixedSize(120, 25)
        .listen("clicked", c.callback)
);
} # foreach control
 
var APISelectionHBox = canvas.HBoxLayout.new();
myLayout.addItem(APISelectionHBox);
 
# create a scrollbar  
var scroll = canvas.gui.widgets.ScrollArea.new(root, canvas.style, {size: [96, 128]}).move(20, 100);
myLayout.addItem(scroll, 1);
 
var scrollContent = scroll.getContent()
    .set("font", "LiberationFonts/LiberationSans-Bold.ttf")
    .set("character-size", 16)
    .set("alignment", "left-center");
 
var list = canvas.VBoxLayout.new();
scroll.setLayout(list);
 
APISelectionHBox.addItem(
        canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0})
        .setText("APIs to be tracked:")
);
 
var APIList = ["settimer()", "maketimer()", "setlistener()", ];
foreach(var d; APIList) {    
    APISelectionHBox.addItem(
        canvas.gui.widgets.CheckBox.new(root, canvas.style, {})
            .setText(d)
            #.setChecked(getprop(prop))
            .listen("toggled", func(e) {
                # foo
            })
    );
} # foreach APIList
 
##
## WIP
##
 
##
# call this to add callbacks to the table
#
var addCallback = func(root, layout, callback){
    # create a new layout
    var row = canvas.HBoxLayout.new();
    layout.addItem(row);

 foreach(var key; ["filename", "lineNumber","frequency_hz","duration_ms"])
{
    var label = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0});
    label.setText(callback[key]);
    row.addItem(label);
} # foreach

}; # addCallback
 
##
## some data for testing purposes, so that we don't need to patch any C++ code for now
##
## (should represent a vector of hashes with active callbacks (listeners/timers)

var myFictionalAPI = func() { 
var DummyData = [
{
 id: "0x323231",
 filename: "globals.nas",
 lineNumber: "433",
 frequency_hz: "20",
 duration_ms: "7",  
},
{
 id: "0x32223231",
 filename: "aircraft.nas",
 lineNumber: "43",
 frequency_hz: "10",
 duration_ms: "15",  
},
{
 id: "0x32321131",
 filename: "geo.nas",
 lineNumber: "33",
 frequency_hz: "10",
 duration_ms: "5",  
},
 
]; # end of vector with  samples
 return DummyData;
};
 
var updateTable = func() {
##
# add row for each callback to the scrollbar layout
foreach(var callback; myFictionalAPI() ){
    # will add label fields to each test, so that labels can be dynamically updated
    # using a different callback
    addCallback(root: scrollContent, layout: list, callback: callback);
 } # foreach
} # updateTable
 
# update the table twice per second
var myTimer = maketimer(0.5, updateTable);
#myTimer.start();
 
 updateTable();
 
# override destructor
window.del = func(){
    print("Cleaning up window: ", title);
    myTimer.stop();
    call(canvas.Window.del, [], me);
};
 
###################################################################
    }, # event handler for messageBox
    canvas.MessageBox.Ok |canvas.MessageBox.Cancel | canvas.MessageBox.DontShowAgain
);

features

  • property accesses (reads/writes) per frame, see Howto:Debugging Properties
  • callback origin via naCall/context lookup, as per die/naRuntimeError and printlog() [1]
  • identify FDM/AP coupled callbacks
  • identify redundant callback registrations
  • filter by API (settimer, maketimer, setlistener)
  • filter by namespace
  • track GC invocation frequency
  • track number of total callbacks: listeners/timers (active)
  • track GC stats (pre/post GC invocation)
  • differentiate between global vs. Nasal callbacks (timers/listeners)
  • listeners vs. timers: Nasal vs. others - invocations per frame/second/minute

SGEventMgr

Introduce separate event manager instances for:

  • aircraft-nasal
  • scenery-nasal
  • gui-nasal

overload settimer/maketimer to use the proper SGEventMgr instance

listener count

Cquote1.png For those not following all the cvs logs: I've added a new function to Nasal a few days ago: removelistener(). It takes one argument -- the unique id number of a listener as returned by setlistener(): var foo = setlistener("/sim/foo", die); ... removelistener(foo); This can be used to remove all listeners in an <unload> part that were set by the <load> part of a scenery object: <load> listener = []; append(listener, setlistener("/sim/foo", die)); append(listener, setlistener("/sim/bar", func {}); ... </load> <unload> foreach (l; listener) { removelistener(l) } </unload> screen.nas stores all relevant listener ids in a hash, so that other parts can, for example, remove the mapping of pilot messages to screen and voice): removelistener(screen.listener["pilot"]); The id is 0 for the first listener, 1 for the second etc. removelistener() returns the total number of remaining listeners, or nil on error (i.e. if there was no listener known with this id). This can be used for statistics: id = setlistener("/sim/signals/quit", func {}); # let's not count this one num = removelistener(id); print("there were ", id, " Nasal listeners attached since fgfs was started"); print("of which ", num, " are still active"); m.
— Melchior FRANZ (Mar 2nd, 2006). [Flightgear-devel] Nasal: new command "removelistener()".
(powered by Instant-Cquotes)
Cquote2.png

information

  • file name: naGetSourceFile [2]
  • line number naGetLine [3]
  • call stack (stacktrace, obtained via using die() and/or logError )