From FlightGear wiki
Jump to navigation Jump to search

FGNasalSys vs FGPythonSys - common baseclass

These are APIs that are sufficiently generic in nature to be moved to a common baseclass that can be shared by FGPythonSys/FGNasalSys:

  • fgvalidatePath()
  • loadResoureFile (fgdata access)
  • printlog(), i.e. wrapping SG_LOG() [1]
  • loadModule()
  • loadPropertyScripts()
  • addcommand()/removecomand()
  • runfgcommand() (with locking, e.g. using SGMutex)
  • it would make sense to introduce separate APIs to differentiate between different simulator scopes: core/gui, aircraft, ai/mp aircraft, scenery - having this separation in place would make it much more straightforward to establish data dependencies (i.e. for threading), but also to selectively reset a context (imagine resetting/switching the aircraft, or repositioning the aircraft, leaving scenery tiles etc)

class FGScriptingSys : public SGSubsystem, public SGPropertyChangeListener {
    virtual ~FGScriptingSys();

   const char* readfile(const char* file, int* lenOut);

    // Subsystem functions.
    virtual void init();
    virtual void reinit();
    virtual void bind();
    virtual void unbind();
    virtual void update(double dt);
    // Load and execute a script.
    bool loadModule(SGPath file, const char *module) = 0;
    virtual void loadPropertyScripts() = 0;
    virtual void loadPropertyScripts(SGPropertyNode *n) = 0;
    virtual void resetInterpreter() = 0;

    virtual void loadCoreScript() = 0; // low-level/core scripts
    virtual void loadSceneryScript() = 0; // scenery related scripts
    virtual void loadAircraftScript() = 0; // aircraft specific scripts


}; // FGScriptingSys

class FGPythonSys : public FGScriptingSys {
}; //FGPythonSys

class FGNasalSys: public FGScriptingSys {

}; //FGNasalSys
I think this is a great idea, though through implementation I am seeing a number of major Nasal vs. Python differences that would require a lot of work on the Nasal side, to abstract that interface.
For example for IO streams, the Nasal logprint() vs. print() functions are very different to the design I am thinking for Python, where I will direct absolutely everything into SG_LOG, using SG_INFO for all STDOUT output, and SG_ALERT for all STDERR output. I am also considering IO stream objects for each of the simgear log priorities to be placed into the 'fg_sys' module (to replace 'sys' module functions), which can be directly written to in a Python script, for example with:
  • fg_sys.stdout.write() (The default STDOUT stream)
  • fg_sys.stderr.write() (The default STDERR stream)
  • fg_sys.stdin.write() (The default STDIN stream, which is totally useless for FlightGear)
  • fg_sys.stdbulk.write()
  • fg_sys.stddebug.write()
  • fg_sys.stdinfo.write()
  • fg_sys.stdwarn.write()
  • fg_sys.stdalert.write()
So it can be seen that the fundamental design is quite different to Nasal's implementation. The Python design is more object oriented. The IO redirection of sys.stdout and sys.stderr, and the IO stream object creation, is however set up via a subsystem ioRedirection() function, to allow for different logging classes to be used in the future. The Nasal IO functions are located in the Nasal library, so it will require a lot of painful work to beat Nasal into shape to use proper IO redirection into SG_LOG. Anyway, the actual implementation of this IO redirection is at a lower level than the subsystem interface.
Bugman (talk) 04:28, 12 February 2016 (EST)

I saw your comments on the forum (and Philosopher's response) - I do agree that there are some subtle differences, but if they are more than just "subtle", my suggestion would be to maintain abstract methods in the base class and implement them in the child class for now. Apart from that, Nasal/CppBind should make it possible to come up with a mechanism that matches Python's semantics, i.e. regardless of logprint/printlog/printerror etc--Hooray (talk) 18:21, 15 February 2016 (EST)
Another issue is that Nasal needs a lot of work, almost open heart surgery, to be beaten into shape to act as a normal subsystem. This issue is unfortunately pretty much a blocker for a scripting subsystem base class at the moment. I hope though that the test suite and other infrastructure I am working on will help decouple Nasal from the rest of FG and allow this to eventually happen.
Bugman (talk) 04:28, 12 February 2016 (EST)
Nasal is not a conventional subsystem in that its code is triggered by other subsystems, mainly via Callbacks through Timers and Listeners, despite running in the main loop, so that actual Nasal overhead does not show up in the underlying Nasal subsystem, but in arbitrary subsystems modifying properties or running embedded Nasal code (think GUI, scenery, models etc). While that is common practice for the time being, there is nothing preventing us from registering a Nasal child class to inherit from SGSubsystem/Mgr to implement a more proper subsystem, this can be accomplished using Nasal/CppBind - and Tom is already doing that for many Canvas base-classes, see $FG_SRC/Scripting and any file there using cppbind (the headers) - it's roughly 20-30 lines of code to expose a C++ baseclass to scripting space so that it can be inherited from and registered (see Canvas widgets).
In summary, you are correct of course, but it's more to do with existing code and features than with the Nasal the language/VM or the underlying subsystem. The main issue here is 3-fold: 1) timers, 2) listeners, 3) tons of spaghetti code in $FG_ROOT/Nasal loaded/executed unconditionally.
Anyway, I am willing to help work out decoupling Nasal accordingly, including overlapping work that covers/touches FGPythonSys (i.e. common APIs).--Hooray (talk) 18:21, 15 February 2016 (EST)
EDIT: Here's a simple cppbind-based Nasal binding [2]

How to deal with Subsystem specific bindings (Python & Nasal)

it would make sense to come up with a helper class (adapter) that abstracts away the registration of APIs/data structures that are to be supposed to scripting space, so that a subsystem does not need to call lower-level APIs that are scripting language specific, but just a single abstraction layer that hides the marshaling details to convert data structures and function/method calls between C++ and scripting code. For the time being, there is a lot of ugly "postInitFOO()" logic all over the place to add subsystem-specific APIs to the Nasal namespace [3]. --Hooray (talk) 18:31, 15 February 2016 (EST)