Plan for plotscripting plugins
A plotscripting plugin is defined as an external module that adds new plotscripting commands. The module would be implemented as a DLL or SO, depending on the target platform. Ideally, one would create both versions, so that it will work on both Windows and Linux.
The extension itself[edit]
The plugin itself would be comprised of three or four core files, and whatever other files it needs:
- extension.hse - this would be a manifest describing the commands implemented by the extension, as well as any constants, and some metadata. More details below.
- extension.dll - the Windows compiled binary.
- extension.so - the Linux compiled binary.
- whatever.pex - A (Zip? Lump? Tar+GZ?) of the above files, plus any the extension needs to run.
Extension.hse[edit]
The manifest would describe to HSpeak what the extension can do. It would take a format like this:
extension data, begin foobar #id of extension Foo Bar extension #friendly name Enables the use of Foo and Bar #description line end define extension, begin foobar #id of the extension 1, do foo, 2, 0, 0 #command definition, identical to define function 2, etc, 0 #and such end define constant, begin 0, foobar: whatever end
The meta data is used only in Custom, when importing extensions (as usual, see below for details).
Custom's changes[edit]
I think it would be best if we handled the extensions as such:
In the Plotscripting menu of Custom, we add a new menu that allows us to import and browse extensions. Then, the hse of each active extension is tacked on to the end of the hsi that is generated. Since we need to keep a copy of the extension in the RPG anyway, we might as well have Custom manage the extensions for us too.
Oh, also, we should probably add a new lump to the RPG format as a simple lookup table to correlate extension IDs with the file names. We could call it "extensions.lst" or something.
Plotscripting changes[edit]
To support extensions, and the fact that their command numbers will clash, I suggest a new lump in the script file. It would simply be a list of extension IDs that are used by the plotscript. The offset in the table is then used as the numerical representation of this extension, when compiled.
We also need to add a new Plotscripting Kind, say Kind 8. It would work in much the same way as Kind 6, but the first argument would be the extension number. Or, alternately, we could add a new Kind of Kind, with an ID and an extension number which would reduce overhead, at the expense of complexity.
Game's changes[edit]
When we load an RPG, we would need to parse extension.lst, and then do the following steps:
- unlump each extension into their own folder
- use Dylibload to load the extension into memory
- use Dylibsymbol to extract the special functions, probably into a UDT with easy to use function pointers
- perform initialization on the extension (see below)
- load the new lump from the Plotscript (so we know which extension is which)
And, when unloading the game:
- perform Termination on the extension (see below)
- use Dylibfree to unload the extension
- get rid of the UDTs with the function pointers
It seems pretty easy, right?
When running the script, to execute Kind 8, we simply need to check which extension we need, and then pass the command ID and all the arguments to the extension, returning whatever it returns. It's just that easy.
Extension API[edit]
Obviously, there is a function that is responsible for running the command. But, there are others, too. Here's a description:
(These are written in C, but if we implement this, I'll provide an SDK with several languages)
bool initialize(char* game, callbacks* cb);
Anything that the extension needs to do at start up needs to go in here. If everything goes OK, then it returns true, and we proceed. If it returns false, we need to throw an error. I'll talk about the call backs below.
void terminate();
And, if the extension needs to clean up, that goes in here. Since failing at termination doesn't mean much, we don't allow for the possibility.
int command(int id, int argc, int argv[]);
This is where all the magic happens. We pass the command ID and arguments to the script, and expect a return value.
Extension callbacks[edit]
Further, an extension will often need data from Game. So, we need to provide callbacks back into Game. These function pointers will be stored in a UDT, and passed as a pointer to loaded extensions. That way, if we need to change it at run time for whatever reason, it happens automatically.
char* getString(int id);
Returns a plot string.
void setString(int id, char* str);
Sets a plot string.
int plotFunction(int id, int argc, int argv[]);
Allows the execution of an arbitrary plotscripting function. These three functions alone allow all the access to the engine that a plotscript has.
void* screen();
Returns a pointer to the screen, for drawing.
char* tempDir();
Returns the location of the temporary folder, eg for reading in lump data.
any others?