Board index FlightGear Development Nasal

Nasal namespaces and callbacks

Nasal is the scripting language of FlightGear.

Re: Nasal namespaces and callbacks

Postby galvedro » Wed Apr 30, 2014 7:50 pm

Hau,

I had some time to do the experiment I wanted. It's basically a second iteration of the instance() helper, but resolving the constructor mess I did before. The new version allows defining classes as follows:

Code: Select all
var DerivedHash = {

   parents: [BaseHash1, BaseHash2],

   static_member: ...
   non_static_member: ...

   new: func(args) {
      var m = instance(DerivedHash);
      initialization code...
      return m;
   },
};


The main difference is that members have to be defined at the top level, rather than added to the "m" variable in the constructor. Members named static_* are not added to the instance, but kept where they are.

The magic function would look like this:

Code: Select all
var instance = func(class, inst=nil, hrch=nil) {

   var m = inst != nil ? inst : {}; # new instance
   var h = hrch != nil ? hrch : []; # hierarchy vector

   foreach(var k; keys(class)) {
      typeof(class[k]) == "func" or find("static_", k) == 0
      or (m[k] = duplicate(class[k]));
   }

   append(h, class);

   if (contains(class, "parents")) {
      foreach(var parent; class.parents)
         find_class(parent, h) or (m = instance(parent, m, h));
   }

   m.parents = [class];
   return m;
}


Now, some general comments regarding more advanced features like access protection, etc. While I think that some helpers in the lines I am presenting here could be useful, I think it is a mistake to make Nasal do something that is not designed to do.

Access protection for example (private, protected, public) is an advanced concept that comes into play when designing large software systems, and in those, you typically want the help from a compiler to ensure that everything is following your own rules. Nasal is not designed for implementing such systems, but rather, to become an extension point for them.

I used to do code drivers for the Linux kernel, and we used to say "the kernel trusts itself". You don't do defensive programming down there, for example. If the code is wrong, you fix it. Same could apply for Nasal scripts: if you abuse the system and manipulate script internals at your will, you deserve whatever happens to you :D

So, all in all, I think it is better to keep things simple. In the case of access protection, just a convention like "if it starts with underscore, it is intended to be private" can work perfectly well. Python uses this particular convention, and it has become a language that implements very complex systems indeed.

Regarding the pseudo language approach for doing OOP. Well, I tend to not like it, but some people is able to do this kind of stuff very well, so it depends. Personally, I would not go that route though and try to exploit Nasals clean syntax instead.
galvedro
 
Posts: 145
Joined: Mon Oct 07, 2013 12:55 pm
Location: Sweden

Re: Nasal namespaces and callbacks

Postby Philosopher » Fri May 02, 2014 3:51 am

Just realized something looking at your commit, galvedro ;):
Code: Select all
var _wrap_instanceless = func(fn) func {
    var me = caller()[0].me;
    # Theoretically we can somewhat preserve named arguments here... but I'd have to test - the core Nasal has some issues with calling functions into existing namespaces
    call(fn, arg, nil, caller(0)[0]);
}
var BaseClass = func(cls) {
    foreach (var k; keys(cls))
        if (typeof(cls[k]) == 'func') cls[k] = _wrap_instanceless(cls[k]);
    return cls;
}

Basically this would allow your "me.parents[0].reset()" thing to work again in a slightly different form (e.g. with "me.parents[1]" or "me.parents[0].parents[0]", depending) by inheriting the "me" ref from the caller of the method.
Thanks,
Philosopher
(inactive but lurking occasionally...)
Philosopher
 
Posts: 1590
Joined: Sun Aug 12, 2012 6:29 pm
Location: Stuck in my head...
Callsign: AFTI
Version: Git
OS: Mac OS X 10.7.5

Re: Nasal namespaces and callbacks

Postby Hooray » Sat May 10, 2014 6:46 pm

You don't do defensive programming down there, for example. If the code is wrong, you fix it. Same could apply for Nasal scripts: if you abuse the system and manipulate script internals at your will, you deserve whatever happens to you


This would certainly be true if all the people writing Nasal code would be trained programmers - but I guess the majority of aircraft developers have not done much coding at all, and there's a fair amount of "library code" in $FG_ROOT/Nasal that also lacks certain properties. Your point about Nasal being designed with certain requirements -like access specifiers- in mind is valid though. But the problem is that Nasal, despite being a high-level scripting language, may still be too low-level for most people - i.e. because resources like timers and listeners will typically need to be managed explicitly. Likewise, support for reset/re-init needs to be manually implemented.
And even complete "subsystems" are implemented on top of concepts like timers and listeners instead of having a more rigid framework-structure that encourages and requires a more conventional design.

We do have some fairly "large" systems implemented entirely in scripting space, that still only use a subset of the language's features - often in a copy&paste fashion. And this is causing problems in other places. So I am not sure if we really need a proper OOP framework or even a DSL, but some way to encourage and enforce "best practices" in order not to break other simulator features would be pretty crucial sooner or later.

Things that would need to be covered would probably include:
  • logging/debugging (printlog/die)
  • providing a subsystem interface (SGSubsystem)
  • supporting reset/re-init
  • saving/loading/resuming flights
  • resource management (timers, listeners - and possibly threads)
  • split-frame loop management
  • support suspending/reloading code from disk (RAD)
  • formalize multi-instance synchronization, no matter if it's between multiple MP instances or a multi-instance multi-screen setup: we need a way to sync/replicate certain state across Nasal-based subsystems to provide a consistent experience

Currently, many of these things need to be separately implemented by people, and are typically not at all supported by scripting space subsystems. But currently we're seeing an increasing number of subsystems implemented in scripting space, and even most C++ code isn't prepared to handle these requirements properly. But the manpower is currently not in core development, but in base package/middleware development.

MapStructure/Canvas are basically halfway there already, but only because such considerations were kept in mind - i.e. Canvas-based systems would be relatively straightforward to replicate across multiple instances (think multiplayer, multi-pilot or multi-instance setups) - but that still doesn't apply to most C++ instruments, especially the glass stuff. And things like suspending and reloading code from disk is currently implemented manually in a few places, even though it would be a framework responsibility.

Overall, one long-term goal remains to initialize scripting much earlier to get rid of some legacy C++ code that isn't performance critical, and make Nasal/Canvas available earlier, so that things like GUI launchers can be directly implemented on top - and that, too, requires some way to sanely declare dependencies among "run-levels", i.e. to start & shut down systems as required. Any scripted "systems" that do not implement certain interfaces are a pain to support here obviously. Currently, we're loading a huge pile of Nasal code unconditionally and expect things to just work, without formalizing that certain scripts depend on C++ subsystems like the FDM, AP or route manager (terrain/scenery).

This will typically not affect aircraft or scenery script, but most of the code in $FG_ROOT/Nasal/Canvas will need to be reviewed accordingly to pull this off. Having scripted subsystems that do not implement SGSubsystem is unnecessarily difficult and error-prone currently, and all the listener/timer management is a constant source of trouble, because people tend to have multiple instances of their callbacks running after resetting a few times...
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 11354
Joined: Tue Mar 25, 2008 8:40 am

Re: Nasal namespaces and callbacks

Postby galvedro » Sat May 10, 2014 7:36 pm

Well, I was just talking about language features, like access control and such. What you are saying however is that scripts should preferably be run in a sandboxed environment, which is a different problem I think.
galvedro
 
Posts: 145
Joined: Mon Oct 07, 2013 12:55 pm
Location: Sweden

Re: Nasal namespaces and callbacks

Postby Hooray » Sat May 10, 2014 8:00 pm

sandboxing is one thing, but we really need scripts to comply with certain requirements - implementing subsystems on top of timer/listener callbacks doesn't scale too well, and most complex scripts end up replicating similar infrastructure to do certain things like managing resources or handling reset/re-init, or even just script versioning, and supporting different FG versions. So this is more about coming up with a set of interfaces that people are required to implement, not so much about having sandbox'ed environment IMO
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 11354
Joined: Tue Mar 25, 2008 8:40 am

Previous

Return to Nasal

Who is online

Users browsing this forum: No registered users and 0 guests