Difference between revisions of "Initializing Nasal early"

From FlightGear wiki
Jump to: navigation, search
(Switch to {{gitorious url}} to fix the broken Gitorious links.)
(removed outdated content)
Line 1: Line 1:
{{Non-stable|version=4.x|progress=60}}
+
[[Nasal Initialization]]
 
+
[[File:Nasal and Canvas initialized early.png|thumb|'''Towards [[FGCanvas]]''': Screen shot showing the [[Nasal]] and [[Canvas]] subsystems initialized early, so that both subsystems can be used to implement GUI launchers or more dynamic splash screens etc.]]
+
 
+
{{infobox subsystem
+
|image =
+
|name =  [[File:Nasallogo5.png]] Bootstrapping
+
|started= 07/2014
+
|description = scripted startup, as per [http://forum.flightgear.org/viewtopic.php?f=30&t=21083] and [http://forum.flightgear.org/viewtopic.php?f=71&t=23499]
+
|merge-requests = {{Merge-request|flightgear|1586}} {{Pending}}
+
|status = Under active development as of 07/2014 [http://forum.flightgear.org/viewtopic.php?f=71&t=23499&p=214576#p214576]
+
|developers = Hooray, Philosopher (since 07/2014),
+
|topic-fg= {{gitorious url|fg|philosophers-flightgear|branch=topics/fgcanvas|view=shortlog}}
+
|topic-fgdata= {{gitorious url|fg|canvas-hackers-fgdata|branch=topics/fgcanvas|view=shortlog}}
+
}}
+
 
+
{{Portability Navbar}}
+
{{Nasal Internals}}
+
 
+
{{FGCquote|1= I am delighted to have this cleaned up, and a way to incrementally init bits of Nasal|2= {{cite web  | url    = http://forum.flightgear.org/viewtopic.php?p=192734#p192734  | title  = <nowiki>Re: Modular Nasal bootstrapping (again)</nowiki>  | author = <nowiki>zakalawe</nowiki>  | date  = Oct 27th, 2013  }}}}
+
 
+
{{FGCquote
+
  |I would prefer to init Nasal earlier in startup, because I would like use [[Aircraft Center|Canvas-based UI for pieces such as aircraft-selection]] - but right now we have to load Nasal in postInit, which is seconds and seconds into startup. Most Nasal things that need FDM state already wait on fdm-initialized, so in ''theory'' the only fix required is to skip &lt;nasal&gt; evaluation in 'animation' XML files (apparently there is a long-standing assumption that such elements are skipped when loading an aircraft as the main one, i.e they are only run for AI / MP aircraft - this is a bad situation but changing it now could be a serious compatibility problem)
+
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=192734#p192734
+
    |title=<nowiki>Re: Modular Nasal </nowiki>
+
    |author=<nowiki>zakalawe</nowiki>
+
    |date=<nowiki>Sun Oct 27</nowiki>
+
  }}
+
}}
+
 
+
{{FGCquote
+
  |The old ‘reset’, which does *not* restart Nasal, is now stripped down to the bare minimum of systems needed for repositioning (tele-porting). Since many aircraft Nasal scripts rely on /sim/signals/reinit to do work on being teleported, that behaviour was also retained.
+
In an ideal world, there would be a better API to inform aircraft and Nasal modules about a teleport, but for the meantime, the property you mentioned is the one. Eg, really we should have an API to distinguish on-ground vs in-air starts, and adjust the initial state of properties accordingly.
+
BTW, for the new reset process, the subsystem, including Nasal, is actually destroyed and re-created. This is intentionally to make it almost certain the behaviour after a reset is the same as after the first launch of the sim.
+
  |{{cite web |url=http://sourceforge.net/p/flightgear/mailman/message/33135215/
+
    |title=<nowiki>Re: [Flightgear-devel] Expected behavior on "reinit"</nowiki>
+
    |author=<nowiki>James Turner</nowiki>
+
    |date=<nowiki>2014-12-10</nowiki>
+
  }}
+
}}
+
 
+
{{cquote|The primary goals are:
+
* make startup more predictable and less hard-coded.
+
* allow running flightgear in a server/test mode with only some subsystems, and no rendering
+
 
+
Obviously supporting a standalone 'fgcanvas' would be quite a small extension from those. I'm not worrying about dynamic dependencies or automatic subsystem creation for the moment - I expect the user / defaults to have defined a set of subsystems that work without crashing  You're correct of course that Nasal has many assumptions about subsystems, but I think that can be improved incrementally on the Nasal side.
+
 
+
For the test mode, I really want to start Nasal-the-langauge very early, without loading all the modules in Nasal/ immediately. (Or maybe load a 'safe' subset). That's going to take some thought and I didn't get that far yet!<ref>{{cite web |url=http://forum.flightgear.org |title=incremental initialization of SGSubsystems|author=James Turner |date=Oct 01, 2012}}</ref>|James Turner}}
+
 
+
{{FGCquote|1= This is exactly what I added support for implementing new commands in Nasal, so that going forward we can correctly put custom dynamic things in commands. |2= {{cite web  | url    = http://forum.flightgear.org/viewtopic.php?p=191781#p191781  | title  = <nowiki>Re: Modular Nasal bootstrapping (again)</nowiki>  | author = <nowiki>zakalawe</nowiki>  | date  = Oct 14th, 2013  }}}}
+
 
+
{{FGCquote
+
|1= on the complexity of dependencies for Nasal scripts, I think again the answer is to have the running status of each subsystem stored somewhere, and exposed via the property tree. The trick will be picking good groups, so most things have one or two dependencies, and having some documentation somewhere stating what's in each group.
+
|2= {{cite web
+
  | url    = http://sourceforge.net/p/flightgear/mailman/message/13175741/
+
  | title  = <nowiki>Re: [Flightgear-devel] Subsystem run-levels</nowiki>
+
  | author = <nowiki>James Turner</nowiki>
+
  | date  = Apr 18th, 2006
+
  | added  = Apr 18th, 2006
+
  | script_version = 0.25
+
  }}
+
}}
+
 
+
{{FGCquote
+
|1= the run-level concept is just one property, and nicely encapsulates the hierarchy of subsystems (if you're switching to level 8, all lower levels must already be initialied). But having names and separate flags makes the actual dependency clearer: 'this script depends on the aircraft subsystems' or 'this script depends on the environmental subsystems'. I guess it partly depends how complex the dependencies for any given script are in practice - hopefully they're actually quite simple.
+
|2= {{cite web
+
  | url    = http://sourceforge.net/p/flightgear/mailman/message/13175705/
+
  | title  = <nowiki>Re: [Flightgear-devel] Re: Subsystem run-levels</nowiki>
+
  | author = <nowiki>James Turner</nowiki>
+
  | date  = Apr 18th, 2006
+
  | added  = Apr 18th, 2006
+
  | script_version = 0.25
+
  }}
+
}}
+
 
+
This is a summary of all discussions relating to:
+
* initializing the Nasal scripting interpreter earlier {{Progressbar|70}}
+
* moving $FG_ROOT/Nasal initialization out of C++ into scripting space (aka "bootstrapping") while introducing proper dependency resolution (inter- and intra-modular) {{Progressbar|20}}
+
* auditing $FG_ROOT for any other cases of embedded Nasal code that may have implicit assumptions regarding other subsystems (e.g. dialog files) {{Not done}}
+
* moving subsystem-specific initialization of Nasal APIs into each subsystem's bind methods, as per [http://forum.flightgear.org/viewtopic.php?f=71&p=214436#p214436] {{Progressbar|70}}
+
* extending [[Nasal/CppBind|cppbind]] to keep track of added symbols, to easily remove them on demand {{Pending}}
+
* moving subsystem-specific argument processing and fgcommand initialization into each subsystem  {{Not done}}
+
* providing an integrated GUI front-end/launcher using [[Canvas]] and [[Nasal]]: [[Aircraft Center]]
+
* decoupling FlightGear initialization such that certain subsystems can be optionally disabled right at the beginning (e.g. FDM, sound, replay, scenery) {{Progressbar|10}}
+
* simplifying the fg_init.cxx/main.cxx initialization logic (fgCreateSubsystems) and exposing it to scripting space {{Progressbar|60}}
+
* allowing argument processing to be delegated to scripting space (e.g. options, startup position handling, presets etc.) {{Not done}}
+
* grouping related subsystems into '''SGSubsystemGroup''' instances to manage them holistically {{Progressbar|20}}  - analogous to [[FlightGear Run Levels|run-levels]], e.g.:
+
** AUDIO: sound, voice (TTS), fgcom
+
** AIRCRAFT: aircraft-model, controls, flight (FDM), xml-autopilot, route-manager
+
** VIDEO: gui, canvas, canvasGUI
+
* making the initialization process/splash screen more responsive by using Nasal/Canvas instead of hard-coded GL code [http://forum.flightgear.org/viewtopic.php?f=71&t=23542] [http://forum.flightgear.org/viewtopic.php?f=71&t=22021] ({{Issue|1456}}) {{Pending}}
+
* allowing custom startup profiles/modes to be implemented via Nasal helper scripts (which also touches the area of '[[Howto:Create_a_custom_version_of_FlightGear|modding]]'), for example: {{Not done}}
+
** [[FGCanvas]]
+
** running stand-alone Canvas applications, e.g. the [[Interactive Nasal REPL]] or a [[MapStructure]] map
+
** [[FlightGear Benchmark]]
+
** [[FlightGear Headless]]
+
** [[FGViewer]]
+
** supporting stand-alone [[Nasal]]/[[Canvas]]-baased applications like a MapStructure-based moving map or a virtual [[Stand Alone ATC Control Development|ATC client]]
+
** supporting a safe subset of subsystems for [[Howto:Optimizing FlightGear for mobile devices|mobile devices]]
+
 
+
[[File:Towards-canvas-driven-splash-screens.png|500px|thumb|Making Nasal/Canvas available as early as possible, also means that we can show Canvas-based splash screens, or even run Canvas dialogs until the scenery has finished loading. This screen shot shows two fully-responsive Canvas dialogs while FlightGear is still scenery loading.]]
+
 
+
 
+
{{Note|The FlightGear initialization sequence is generally handled by $FG_SRC/Main/main.cxx, which is where subsystem initialization is delegated to fgCreateSubsystems() in $FG_SRC/Main/fg_init.cxx}}
+
 
+
== Objective ==
+
{{cquote|
+
The position init code is a little complex, to allow for starting on a carrier and some other cases, and ideally we would do it from script, but unfortunately there's some technical limitations on doing that. (Not insurmountable, but not quick either). Adding more cases to the position-init code is certainly doable - one suggestion I made some time ago, is when MP is active, to default to starting at a free parking position instead of on a runway. (When no startup position is provided at all). This would avoid people accidentally starting on the runway in MP events, which would be a big usability win.
+
 
+
When no parking positions are defined, AI traffic defaults to the 'airport centre' location. Selection a taxiway parallel to the active runway is possible, but for complex airport layouts I can imagine many ways this logic could fail. Personally I think it would be more robust to stick to picking parking positions, and otherwise using the airport centre, because it would encourage people to add the parking position data :)
+
 
+
I anyone wants to work on this, it would be a self-contained function in position-init code, just ask here for any questions. All the pieces certainly exist - the taxiways, parking locations and runways for the airport are all accessible.<ref>{{cite web |url=http://sourceforge.net/p/flightgear/mailman/attachment/F69E014E-F15A-4EAB-B512-36B87CA3B2C5%40mac.com/1/|title= Aircraft positioning on the runway|author=James Turner |date=2013-11-12 10:32:27}}</ref>|James Turner}}
+
 
+
{{FGCquote
+
  | I am delighted to have this cleaned up, and a way to incrementally init bits of Nasal, but I would be much happier to see a patch of the C++ difs, the sooner the better, just in case you're doing something that is not going to work safely.
+
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=192734#p192734
+
    |title=<nowiki>Re: Modular Nasal </nowiki>
+
    |author=<nowiki>zakalawe</nowiki>
+
    |date=<nowiki>Sun Oct 27</nowiki>
+
  }}
+
}}
+
 
+
{{FGCquote
+
  |I really don't believe there should be an option to disable bootstrapping, it should just be an issue of matching FGData. Basically, if we have new-style scripts (i.e. using require() and deprecating "nasal-dir-initialized"), then we ''need'' a bootstrap script (because otherwise... we would have to reimplement the corresponding code in C++ space); else, we have old FGData (as I sometimes like to switch back to, to pursue other projects like the Nasal Browser) and we use the old C++ loading code.
+
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=193231#p193231
+
    |title=<nowiki>Re: Modular Nasal bootstrapping (again)</nowiki>
+
    |author=<nowiki>Philosopher</nowiki>
+
    |date=<nowiki>Mon Nov 04</nowiki>
+
  }}
+
}}
+
 
+
{{FGCquote
+
  | it's mostly Nasal code in $FG_ROOT/Nasal where dependencies need to be better established and formalized to get rid of implicit assumptions regarding availability of certain subsystems (e.g. view.nas)
+
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=214206#p214206
+
    |title=<nowiki>FGCanvas Experiments & Updates</nowiki>
+
    |author=<nowiki>Hooray</nowiki>
+
    |date=<nowiki>Sun Jul 06</nowiki>
+
  }}
+
}}
+
 
+
{{FGCquote
+
  |revisit bootstrapping soon, because it's the only sane way to resolve dependencies (intra-module or even subsystem support).
+
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=214207#p214207
+
    |title=<nowiki>Re: FGCanvas Experiments & Updates</nowiki>
+
    |author=<nowiki>Philosopher</nowiki>
+
    |date=<nowiki>Sun Jul 06</nowiki>
+
  }}
+
}}
+
 
+
{{FGCquote
+
  |Given my recent "FGCanvas" experiments (making ~60% of the subsystems optional), and successes there, I'd probably consider handing off control to a hard-coded Nasal script in FGNasalSys, even though we could make the file-name property-configurable, whereas it would default to something like "default.boot". <br/>
+
<br/>
+
That would allow us to easily use --prop{{=}} (or a dedicated '''--mode{{=}}''') parameter to override the bootstrapping script. <br/>
+
Once that is place, we could also move certain fg_init.cxx logic to Nasal space by using Zakalawe's add-subsystem/remove-subsystem fgcommands, which I already started extending to allow subsystems to be registered as SGSubsystemGroup instead of "just" SGSubsystem - which allows us to use groups as "containers" for specific subsystems, like "aircraft" (fdm, autopilot, route  manager etc) or "audio" (fgcom, sound, voice). Making these entirely optional and runtime-configurable is becoming much more straightforward that way - also, because all the reset/re-init logic can be moved to each groups postinit/reinit methods.<br/>
+
<br/>
+
Conceptually, that's straightforward to do, and apart from supporting different startup modes, we could even support "applications", i.e. stuff  like running just the REPL, or just a canvas-map, or even just the Aircraft Center eventually.<br/>
+
<br/>
+
So, I'd prefer to load a single property-defined Nasal script and delegate control to it in FGNasalSys:init(), so that we can handle both there, nasal bootstrapping, but also subsystems at some point.
+
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=214423#p214423
+
    |title=<nowiki>Re: Modular Nasal bootstrapping (again)</nowiki>
+
    |author=<nowiki>Hooray</nowiki>
+
    |date=<nowiki>Wed Jul 09</nowiki>
+
  }}
+
}}
+
 
+
{{FGCquote
+
  |for FGCanvas-testing, I just used a slightly-extended version of this:
+
<syntaxhighlight lang="diff">
+
diff --git a/src/Main/options.cxx b/src/Main/options.cxx
+
index 0389faa..26eac9c 100644
+
--- a/src/Main/options.cxx
+
+++ b/src/Main/options.cxx
+
@@ -1434,8 +1434,9 @@ struct OptionDesc {
+
    int (*func)( const char * );
+
    } fgOptionArray[] = {
+
+
+    {"mode",         true,  OPTION_STRING, "/sim/startup/boot-script", false, "default", 0 },
+
    {"language",                    true,  OPTION_IGNORE, "", false, "", 0 },
+
- {"console",                      false, OPTION_IGNORE,  "", false, "", 0 },
+
+    {"console",                      false, OPTION_IGNORE,  "", false, "", 0 },
+
    {"disable-rembrandt",            false, OPTION_BOOL,  "/sim/rendering/rembrandt/enabled", false, "", 0 },
+
    {"enable-rembrandt",            false, OPTION_BOOL,  "/sim/rendering/rembrandt/enabled", true, "", 0 },
+
    {"renderer",                    true,  OPTION_STRING, "/sim/rendering/rembrandt/renderer", false, "", 0 },
+
diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx
+
index fde6319..b8c2114 100644
+
--- a/src/Scripting/NasalSys.cxx
+
+++ b/src/Scripting/NasalSys.cxx
+
@@ -833,6 +833,7 @@ void FGNasalSys::init()
+
      .member("singleShot", &TimerObj::isSingleShot, &TimerObj::setSingleShot)
+
      .member("isRunning", &TimerObj::isRunning);
+
+
+#if 0
+
    // Now load the various source files in the Nasal directory
+
    simgear::Dir nasalDir(SGPath(globals->get_fg_root(), "Nasal"));
+
    loadScriptDirectory(nasalDir);
+
@@ -845,6 +846,20 @@ void FGNasalSys::init()
+
        simgear::PathList scripts = dir.children(simgear::Dir::TYPE_FILE, ".nas");
+
        addModule(directories[i].file(), scripts);
+
    }
+
+#endif
+
+
+
+    const char* boot_script = fgGetString("/sim/startup/boot-script", "default");
+
+    SGPath fullpath(globals->get_fg_root(), "Boot");
+
+    fullpath.append(boot_script);
+
+    fullpath.concat(".boot");
+
+
+
+
+
+    SG_LOG(SG_NASAL, SG_INFO, "Using boot script:" << fullpath);
+
+    SGPath file = fullpath.file();
+
+    if(!loadModule(fullpath, "boot")) {
+
+    SG_LOG(SG_NASAL, SG_ALERT, "Error could not load boot script:" << fullpath);
+
+    exit(-1);
+
+    }
+
+
    // set signal and remove node to avoid restoring at reinit
+
    const char *s = "nasal-dir-initialized";
+
 
+
</syntaxhighlight>
+
As you can see, the file name of the "boot script" defaults to $FG_ROOT/Boot/default.boot but can be easily overridden to add custom modes.<br/>
+
Which is kinda where we could now add your bootstrap.nas logic and review the necessary C++ changes to make it work.<br/>
+
For testing purposes, I used io.nas as the template for my own boot script, i.e. to load globals.nas, props.nas etc<br/>
+
<br/>
+
Once the hard-coded functionality is re-implemented, we can explore making things better configurable, i.e. more optional, over time.
+
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=214428#p214428
+
    |title=<nowiki>Re: Modular Nasal bootstrapping (again)</nowiki>
+
    |author=<nowiki>Hooray</nowiki>
+
    |date=<nowiki>Wed Jul 09</nowiki>
+
  }}
+
}}
+
<references/>
+
 
+
 
+
For the sake of simplicity, we could refactor the ::init() method such that the common init code could be shared, between the initial startup Nasal interpreter, and the final runtime interpreter - so that we could load init code from a separate $FG_ROOT directory to do such things, without parsing all the stuff in $FG_ROOT/Nasal.
+
 
+
Referring to the GLSL segfault fix and my earlier comment about defaulting to a NON-SHADER environment by adjusting shader quality level to 0 if shaders are unsupported: For now, this is obviously easy to simply hard-code by showing an ALERT message and overriding the shader settings from there on.
+
 
+
However, in the long run, we'd probably want to simply delegate such stuff to Nasal space. While Nasal is currently not available that early, we could use a bare (or just temporary) Nasal instance during initialization to hand-off such stuff to scripting space. This seems to be an old idea actually (see the comments below from 2008) - and peopel were actually working on having Nasal + GUI code up really early back in 2008.
+
 
+
Given the state of the current init code in fg_init.cxx, the idea of factoring stuff out into Nasal space is very appealing - which means having less unmaintained C++ spaghetti code, and things like processing startup arguments can be really easily done from Nasal space, what's needed is "only" the props code - or rather just setprop/getprop, which would allow simple logic to be moved to scripting space - and it would not even be performance-critical.
+
 
+
In other words, it would be sensible to pick up the old idea and make a minimal Nasal interpreter available early on, and pass control to it - so that things like thís can be easily run, without hard-coding any logic:
+
 
+
<syntaxhighlight lang="nasal">
+
if (getprop("/sim/rendering/gl-shading-language-version")=="UNSUPPORTED") {
+
print("Warning: Shaders unavailable, defaulting to quality level 0!\n")
+
setprop("/sim/rendering/shaders/quality-level", 0);
+
}
+
</syntaxhighlight>
+
 
+
{{cquote|The only catch is, subsystems are initialised late, but I need a handful to be up before I can use the GUI dialogs; obviously the GUI subsystem itself, but also Nasal and a few others. (There are some issues with initialising nasal early, it is currently deliberately being done very late, but more on that later...)<ref>{{cite web |url=http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg03373.html|title=<nowiki>[Flightgear-devel]</nowiki> Subsystem run-levels|author=James Turner|date=Mon, 17 Apr 2006 13:51:07 -0700}}</ref>|James Turner}}
+
 
+
{{cquote|I would appreciate if Nasal were around the first subsystems to be initialized. But this would require some Nasal dependency resolution. Almost all Nasal code needs globals.nas, and most need props.nas, too (which needs globals.nas). gui.nas needs globals.nas and props.nas, and screen.nas
+
needs all three etc. This needs to be taken care of if Nasal is to be initialized early.<ref>{{cite web |url=http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg03390.html|title=<nowiki>[Flightgear-devel]</nowiki> Re: Subsystem run-levels|author=Melchior FRANZ|date=Tue, 18 Apr 2006 03:37:04 -0700}}</ref>|Melchior FRANZ}}
+
 
+
{{cquote|starting up the interpreter (the first part of  FGNasalSys::init) can be done very early (and quickly), and the subsytem would then wait for a relatively high-valued 'init' call before running scripts (the part that needs all other properties to be defined).<ref>{{cite web |url=http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg03373.html|title=<nowiki>[Flightgear-devel]</nowiki> Subsystem run-levels|author=James Turner|date=Mon, 17 Apr 2006 13:51:07 -0700}}</ref>|James Turner}}
+
 
+
{{cquote|I'm sure other people can propose potential groups, but I'd imagine:
+
 
+
early (nasal itself, GUI)
+
time based subsystems (ephemeris, datetime, warp, properties, interpolator)
+
environmental (weather fetch)
+
aircraft systems (FDM, electrical, cockpit?, instruments, failures, GPS)
+
scenery (tile manager, views, panels)
+
input 
+
IO subsystems - ATC, traffic, multiplayer
+
sound<ref>{{cite web |url=http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg03401.html|title=Re: <nowiki>[Flightgear-devel]</nowiki> Subsystem run-levels|author=James Turner|date=Tue, 18 Apr 2006 08:23:01 -0700}}</ref>|James Turner}}
+
 
+
{{cquote|I will start the systems I need (Nasal, GUI and input) early. This might also help make the startup progress a bit more fine-grained.<ref>{{cite web |url=http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg03418.html|title=<nowiki>[Flightgear-devel]</nowiki> Run-levels redux.|author=James Turner|date=Wed, 19 Apr 2006 06:43:14 -0700}}</ref>|James Turner}}
+
 
+
{{cquote|In the even longer run, we'd actually want to associate the Nasal scripts with run-levels (/etc/rc.d, anyone?), since the frontend GUI might want a few scripts loaded, while I assume most are only relevant when actually flying. Such a change also makes postinit() unnecessary, I think - since the effect can always be achieved by having init() watch for a higher run-level.<ref>{{cite web |url=http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg03373.html|title=<nowiki>[Flightgear-devel]</nowiki> Subsystem run-levels|author=James Turner|date=Mon, 17 Apr 2006 13:51:07 -0700}}</ref>|James Turner}}
+
 
+
 
+
<references/>
+
[[Category:Developer Plans]]
+

Revision as of 09:00, 8 January 2020

Nasal Initialization