Noiredd / PEGAS

Powered Explicit Guidance Ascent System - a KSP & RO autopilot using the Space Shuttle guidance algorithm, UPFG
http://forum.kerbalspaceprogram.com/index.php?/topic/142213-pegas-powered-explicit-guidance-ascent-system-devlog
MIT License
114 stars 31 forks source link

Add-ons? #33

Closed Noiredd closed 3 years ago

Noiredd commented 4 years ago

Discussion on #31 raised an interesting idea of creating a system for custom add-ons, i.e. modules that could be added to PEGAS to enhance its capabilities without modifying its own code. Let's discuss whether such a system could be useful, how practical could be its implementation without a thorough rewrite of the core, and how could it be implemented.

I'm imagining something akin to how Python's modules work: you can put .py files in a directory and then load them by name (import my_module) or use importlib to do all kinds of magic and e.g. automatically import specific objects/classes/functions from all modules in a given directory (example). If this could even be done in kOS, the system could work like this on the initialization side:

This way adding a new add-on would be as simple as putting its .ks file into the addons directory - no modification of any core module neccessary. What I definitely wouldn't want is to need to add a line to pegas.ks every time I want to integrate a new add-on.

Then during PEGAS' runtime, the subsystem would periodically call all of the registered addon delegates, which are neccessarily lightweight so that each loop over addons doesn't take forever. These functions could check conditions and call heavier handling functions if needed (interrupt-style), or even schedule their execution (event-like). Such a handler could basically hack anything - all of PEGAS is built on global variables and kOS does not limit the scope of such vars (as far as I remember) so just knowing their names should be enough to monkeypatch anything the user would want.

However, my kerboscript is quite rusty by now so I don't know how practical this is. Maybe it cannot be done this way? Let's talk about this. And let's talk about what potential uses this system might have - it's important to keep in mind the practical problem that we want this to solve.

Noiredd commented 3 years ago

Sketch of the solution.
Addons are separate .ks files placed in a separate folder within the repository (e.g. kOS\addons). Each addon implements one or more functions as delegates, requesting PEGAS to register them internally, associating them with a given point when they should be called. PEGAS, in its main module, calls each of the registered delegates at their respective moments. No parameters are exchanged, instead the delegates can access all of the global variables and functions.

pegas_addons.ks:

SET addonHookRegistry TO LEXICON(
    "init", LIST(), // call these when the system is ready, just before the GUI is first drawn
    "passivePre", LIST(), // call these at the beginning of the passive guidance loop
    "passivePost", LIST(), // similarly, at the end of the passive loop
    "activeInit", LIST(), // call these on the transition from passive to active guidance
    "activePre", LIST(), // at the beginning of the active guidance loop
    "activePost", LIST(), // at the end of the loop
    "final", LIST() // right before exiting the script
).

FUNCTION register_hook {
    // store a given delegate in the registry
    DECLARE PARAMETER hook. // delegate to be called, supplied by addon
    DECLARE PARAMETER mode. // string identyfying when to call the hook (registry key)
    addonHookRegistry[mode]:ADD(hook).
}

FUNCTION call_hooks {
    DECLARE PARAMETER mode.
    FOR fun IN addonHookRegistry[mode] {
        fun:CALL().
    }
}

FUNCTION scan_addons {
    // look at all the script files in the addons directory and execute them
    LOCAL addonDir IS CORE:CURRENTVOLUME:OPEN("addons"). // is this right?
    LOCAL addonItems IS addonDir:LEXICON().

    FOR itemName IN addonItems:KEYS() {
        // check if a ".ks" file, ignore if not
        // otherwise execute the addon script:
        RUNPATH("addons/" + itemName).
    }
}

In pegas.ks add lines like call_hooks("init"). in the designated points, with respective arguments.

Now, each addon would have a following structure:

FUNCTION addon_function_that_does_something {
    // user code goes here
    // they should have the ability to access all PEGAS globals, modify them etc
    // as well as call all PEGAS functions
    // the only restriction is that this should not expect any argument
    // neither should it return anything, as the output will be discarded anyway
    // also, large functions should probably be avoided as in-loop hooks
}
// however, nothing happens until this function is registered:
register_hook(addon_function_that_does_something@, "init").
// this tells PEGAS to run the above function at initialization
Noiredd commented 3 years ago

Added in c6874bc