Handling OXP Dependencies with JavaScript
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 added a station above some moons in a range of 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 loaded. Some version do load in alphabetical order but that isn't true across the board. There have been solutions to this problem by either running the initialisation code from a timer or from a screen change event instead of the startUp function but these methods are not good for multiple dependency issues and in the case of screen change events do not always trigger. 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 an OXP to call a dependency OXP's startUp function before it does its own initialising, provided it hasn't been run yet. Then have that startUp function deleted so it can't be called again until the player dies. (Please note that a JavaScript function deleted whilst it is in the middle of being called, that includes subfunctions, will still be fully executed.) This method then works through the dependency chain until all dependencies are initialised.
As an example if OXP_A depends on OXP_B which depends on OXP_C with this method it doesn't matter what order the OXPs get called in:
Oolite's Load 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 first deletes itself and then looks for the existence of OXP_C's startUp. As it exists it is called.
- OXP_C startUp first deletes itself and as it doesn't have any dependencies then executes its initialisation code and returns execution back to OXP_B's startUp.
- OXP_B startUp now executes its initialisation code and returns execution back to OXP_A's startUp where it gets to execute its initialisation code.
All the above was within the initial call to OXP_A's startUp.
- 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 a different OXP load 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 you may find that you need to include those additional 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 look like in practice:
this.name = "Oxp_A"; this.author = "Author"; this.copyright = "license"; this.description = "Some Description"; this.version = "1.0"; 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 Oxp_A's initialisation stuff here as required. log(this.name + " " + this.version +" loaded."); // This goes last so the load messages appear in a sensible order. } // The rest of OXP goes here
Replace the OXP names, repeat the sections and fill the rest of the code as needed.