A fix for the arbitrary OXP loading order problem
I have been working on my own oxp (Visas.oxp) for a while now and as it is an engine oxp (injection system only with no content of it's own) I hit this rather well known issue of requiring an oxp to load after a particular one or set of the other oxps. But I think I have found a nifty way to do this. I have tested it with my visas oxp and the oxp I am using to test it with, and it works.
There is a limitation to this system and that is that it only works between oxps that are written in a particular way. It will not work with oxps that require their startUp function to be called after the startUp function of old oxps, but but I believe I have also found a way around this that will require a modification to the Oolite script loading and event system.
First here is a template that shows how to get this to work for new oxps:
Code: Select all
this.name = "Oxp_A";
this.author = "Author";
this.copyright = "license";
this.description = "Some Description";
this.version = "1.0 alpha 1";
this.loaded = false;
this.startUp = function()
{
if (this.loaded != true)
{
this.loaded = true; // Must be done straight away to prevent loops and circular references.
// For handling oxps that are written using this template:
if (worldScripts.Oxp_B.loaded != true) 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.
// 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 other stuff here as required
log(this.name + " " + this.version +" loaded."); // This goes last so the load messages appear in a sensible order.
}
}
this.shipDied = function(whom, why)
{
if (whom == Player)
{
// Do other stuff here as required
this.loaded = false; // As this.startUp is called afterward (this.reset is deprecated).
}
// Do other stuff here as required
}
// Do other stuff here as required
Assuming that both Oxp_A and Oxp_B are written using the above template this then works by having Oxp_A checking if Oxp_B's startUp function has been called by checking Oxp_B's "loaded" flag and if has been called it then skips it, but if it hasn't been called it calls Oxp_B's startUp function early, before Oxp_A's code gets called, and then sets Oxp_B's "loaded" flag to "true". Now when Oolite comes to call Oxp_B's startUp function itself the "loaded" flag is now "true" so the body of the code is skipped and acts as if the startUp function wasn't called in the first place.
This now just a standard depency tree. The problem of circular references is averted because the currently executing startUp function sets the "loaded" flag to true before calling any startUp functions.
Resetting the loaded flags to "false" during shipDied functions allows the startUp functions to be called again when you restart the game.
You should also put the dependent oxps in your oxp's requires.plist. The above code may also need minor modification for when the dependent oxp is not installed.
Hopefully if everyone agrees this method should become part of Oxp development best practice.
Getting this to work for Old Oxps
Now to make this also work with old oxps we would need to change the way Oolite loads and calls functions:
Currently, as far as I can tell:
- 1) Oolite loads (if the shift key is pressed during starting) all the javascript scripts into memory. (if you don't press shift this is just loaded from cache instead.)
2) Then each startUp function is called in turn in some unspecified order.
3) Later when the player dies all existing shipDied functions are called, again in some unspecified order.
Now if the following changes are made:
- a) If during 1) Oolite checks for which oxps do not have "loaded" defined and makes a list of these oxps called say "old_Oxps" and then defines the loaded variable for these oxp with the value "true".
b) At 2) for an oxp that is in that list, just before calling it's startUp function, Oolite checks the value of oxp.loaded and skips it if it is set to "true". If it is set to "false" then Oolite sets oxp.loaded to true and calls oxp.startUp.
c) When the player dies set all the "loaded"s for the oxps in the list "old_Oxps" to "false".
This is exquivalent to the code I wrote in the template above except for one extra thing that needs changing in the template.
Instead of:
Code: Select all
// For handling oxps that are written using this template:
if (worldScripts.Oxp_B.loaded != true) 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.
you do this:
Code: Select all
// For handling old oxps that are NOT written using this template:
if (worldScripts.Oxp_Old.loaded != true)
{
worldScripts.Oxp_Old.loaded = true; // You do this as Oxp_Old can't.
worldScripts.Oxp_Old.startUp(); // Calls Oxp_Old.startUp as it is required to load before your Oxp_A.
}
// Repeat above for each old OXP that this OXP depends on.
You can mix as many of these two code snippets as required. Which you use would depend on the nature of the dependent oxp. (i.e. is it built use this new template or not)
As I don't full understand the way Oolite loads the oxps and how it would work with concept of the cache, I can not be sure that the old oxps method would work. I will leave that to someone with better knowledge than I.
Even is it doesn't work the template for new oxps should make things easier for oxp developers.