I think we must face this problem and try to solve it as soon as possible. As Georg and others have pointed out, this is becoming an obstacle for PJSR developers since not being able to integrate scripts within the PI platform is frustrating. I continue being amazed by how far PI scripting is going.
I have been thinking on a way to integrate JavaScript scripts into PixInsight's object-oriented architecture. Presently scripts can be executed but they can't be properly encapsulated. This departs from PI's OO design, and here is the problem. So let's provide some efficient encapsulation for these rebels
The natural and obvious way to encapsulate scripts is by means of a Script process. This process already exists. Its purpose is to allow integration of executed scripts in processing histories. A processing history is a read-only ProcessContainer, which is a sorted list of process instances. A Script instance includes a script's source code (as a block of plain UTF-8 text) and provides just the basic functionality to interact with ProcessContainer. The problem is that we need Script to interact with the script it encapsulates, just as a process class knows how to play with its own instances. In particular, we need a way of communication between a script and a Script instance to allow for persistent storage of script parameters.
Persistence of instance parameters is an automatic process in PixInsight. The core application provides all the required resources. This is why a newly developed process (a C++/PCL module) integrates so smoothly without the developer's intervention (other than defining process parameters --a task that can also be automated, and it will be, with an upcoming code generation script).
Now we must define exactly the same interface for the Script process, both internally (between Script and its encapsulated script) and externally (between Script and Core):
* The external interface is relatively simple. It reduces to adding a unique String parameter to Script. A formal description for this parameter would be:
Script::parameters:
<script-parameter-list>
opt<script-parameter-list>:
<parameter-id>=<parameter-value>(<separator><script-parameter-list>)
opt<separator>:
one of: \n ;
Note that the above description fits strictly within JavaScript syntax.
Why a unique string parameter, instead of a set of parameters, each with its own type, as happens with regular process instances? Because we have no way to know in advance what a script wants as its parameters. Each script will define and use a completely different (perhaps empty) set of parameters. The only way to store this is as a string with an internal syntax. Fortunately, JavaScript provides us with all the syntax we need
We could implement something more sophisticated, as a second parameter indicating the type of each parameter. IMO, this wouldn't add more functionality, given the dynamic nature of javaScript, and it could rather be restricting or limiting in the sense that such a fixed system wouldn't be able to support script-defined objects. Let's allow the JavaScript engine do all the nitty-gritty.
* The internal interface is somewhat more complicated. We need a bidirectional communication:
- A script must broadcast its own working parameters during execution, and Core must be hearing to take note of them. Upon script termination, Core will generate a new Script instance with the corresponding value for Script::parameters.
- During execution, a script has access to several read-only properties of a special JavaScript object. As you probably figure out at this point, that properties are just the result of interpreting and executing Script::parameters as JavaScript code.
Let's put a practical example. A script could do the following:
function MyParameters
{
// Set default parameter values.
this.setDefault = function()
{
this.radius = 100;
this.lineWidth = 3;
this.helloText = "Hello, PI!";
};
// Retrieve working parameters.
this.retrieve = function()
{
if ( Parameters.has( "radius" ) )
this.radius = Parameters.get( "radius" );
if ( Parameters.has( "lineWidth" ) )
this.lineWidth = Parameters.get( "lineWidth" );
if ( Parameters.has( "helloText" ) )
this.helloText = Parameters.get( "helloText" );
};
// Send parameters and values to Core.
this.broadcast = function()
{
Parameters.set( "radius", this.radius );
Parameters.set( "lineWidth", this.lineWidth );
Parameters.set( "helloText", this.helloText );
};
// Create and initialize properties.
this.setDefault();
}
function main()
{
var P = new MyParameters;
P.retrieve(); // acquire Script instance parameters
// ...
}
The above snippet exemplifies our internal communication system. The Parameters global object provides a bidirectional interface between our script and the Script process. Parameters has three main methods:
Boolean Parameters.has( String id )
Returns true if the specified parameter
id has been defined. In general, that will be true if the script is being executed from an existing instance of Script, false otherwise.
void Parameters.set( String id, Object value )
Broadcasts existence of a parameter whose name is
id and has the specified
value. If the specified parameter already exists, its current value is replaced with the new one. If the parameter does not exist, it is newly created.
Object Parameters.get( String id )
Acquires the specified parameter
id and returns its value, if it exists, or
undefined otherwise.
I can try to implement this for the upcoming version 1.5.8, or postpone it for 1.6, depending on the difficulties that I find, but this definitely has to be done. What do you think?