Handling OXP Dependencies with JavaScript

From Elite Wiki
Revision as of 02:26, 6 November 2010 by PG1 (talk | contribs) (Created page with '== The Issue == One of the common issues with OXP development is that many OXPs are dependent on the existence of other OXPs. If they only need to exist side by side this isn't …')
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

The Issue

One of the common issues with OXP development is that many OXPs are dependent on the existence of other OXPs. If they only need to exist side by side this isn't a big problem and the player only has to make sure all necessary OXPs are installed. If you need another OXP to be initialised first (have it's startUp function called first) you will run into a problem. An example of this would be if say your OXP randomly added a station above some moons in some systems. If your OXP ran before the OXP(s) that created the moons then it may place the stations differently or not at all compared to if it ran later or last.

This problem arises because Oolite does not guarantee the order in which the OXPs are load. Some version do load in alphabetical order but that isn't true across the board. There have been solution to this problem by running the initialisation code from a timer instead of the this.startUp function but this method is not good for multiple dependency issues. Since version 1.74 of Oolite there has been a much simpler solution.


Solution

Oolite 1.74 removed the automatic calling of OXPs' reset functions (making this.reset redundant) and reloads all functions from the cache when the player dies before calling all the OXPs' startUp functions. This allows for the the OXP developer to call another OXP's this.startUp function (before it does its own initialising) if it hasn't been run yet and have that startUp function deleted so it can't be called again until the player dies and all the functions are reloaded. (Please note a JavaScript function deleted whilst it is in the middle of being called (that includes subfunctions) will still be fully executed.) By this method if OXP_A depends on OXP_B which depends on OXP_C it doesn't matter which gets called first:

Order: OXP_A, OXP_C, OXP_B.

  • OXP_A's startUp is called first.
  • It deletes itself and then looks for the existence of OXP_B's startUp function and as it exists calls it.
  • OXP_B's startUp now it has been called first deletes itself and then looks for the existence of OXP_C's startUp. As it exists and it in turn is called.
  • OXP_C first deletes itself and then executes its initialisation code and returns execution back to OXP_B's startUp.
  • OXP_B now executes its initialisation code and returns execution back to OXP_A's startUp where it gets to execute its initialisation code. This was all within the call to OXP_A.
  • Now Oolite will want to call OXP_C's startUp as it is next in its list. As it has already been called and deleted it won't find it and will skip it.
  • Finally Oolite will try to call OXP_B's startUp function which also has been deleted and will also therefore be skipped.

The result is that the three OXP's have been initialised in the right order. If you tried this with different OXP order the results will be the same.

If the dependency OXP is not using this method you must delete that's startUp function yourself after you have called its startUp function. This may have a subtle knock on effect if that dependency OXP has it own dependencies that are handled in some other way. If that is the case may find that you need to include those OXPs as dependencies of your OXP and call them first.

Well that's the theory, so here is what a OXP template using this idea should looks like in practice:

this.name           = "Oxp_A";
this.author         = "Author";
this.copyright      = "license";
this.description    = "Some Description";
this.version        = "1.0 alpha 1";

this.startUp = function()
{

      delete this.startUp; // Must be done straight away to prevent loops. It doesn't stop this function from working.

      // For handling oxps that are written using this template:
      if (worldScripts.Oxp_B.startUp) worldScripts.Oxp_B.startUp(); // Calls Oxp_B.startUp as it is required to load before your Oxp_A.
      // Repeat above for each OXP that this OXP depends on.

      // For handling old oxps that are NOT written using this template:
      if (worldScripts.Oxp_Old.startUp)
      {
         worldScripts.Oxp_Old.startUp(); // Calls Oxp_Old.startUp as it is required to load before your Oxp_A.
         delete worldScripts.Oxp_Old.startUp; // You do this as Oxp_Old can't.
      }
      // Repeat above for each old OXP that this OXP depends on.

      // Test for existence of an OXP that isn't required but effects the way this OXP works.
      if (worldScripts["Oxp_X"])
      {
         this.Oxp_X_Exists = true;
      }
      else
      {
         this.Oxp_X_Exists = false;
      }
      // Repeat above for other similar OXP checks.

      // Do initialisation stuff here as required

      log(this.name + " " + this.version +" loaded."); // This goes last so the load messages appear the sensible order.

}

// The rest of OXP goes here

Replace the OXP names, repeat the sections and fill the rest of the code as needed.