dfhack icon indicating copy to clipboard operation
dfhack copied to clipboard

Extend documentation in skeleton plugin, add example plugin structures for class 1, 2, and 3 plugins

Open myk002 opened this issue 3 years ago • 2 comments

Creates "canonical" examples of class 1 (non-enableable), class 2 (enableable from dfhack.init), and class 3 (enableable after world loaded, keeps persistent state in world) plugins.

also:

  • moves skeleton.cpp to plugins/examples so it can live with the use case examples
  • adds BUILD_SKELETON=ON to the all plugins build action
  • removes the unneeded structure around the skeleton plugin -- most plugins don't need their own subdirectories or header files
  • adds debug logging to skeleton.cpp and the new use case examples
  • adds more comments and removes unneeded lines from skeleton.cpp
  • adds a few (documented and tested) functions to argparse that I was planning on showcasing in the example lua integration. they're no longer used in this PR, but they are still useful, so I kept them in. I do plan to use them in the autonestbox/autobutcher refactor.

myk002 avatar Aug 03 '22 07:08 myk002

You can see what the Lua integration examples were going to look like in https://github.com/DFHack/dfhack/pull/2257/commits/aecf0be0cb1f367106948e31e204abc15fdf205c

myk002 avatar Aug 03 '22 07:08 myk002

bah. the move of skeleton.cpp makes it difficult to see what's changed. here's the diff:

$ diff plugins/skeleton/skeleton.cpp /tmp/skeleton.cpp 
1c1,14
< // This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D
---
> // This is an example plugin that documents and implements all the plugin
> // callbacks and features. You can include it in the regular build by setting
> // the BUILD_SKELETON option in CMake to ON. Play with loading and unloading
> // the plugin in various game states (e.g. with and without a world loaded),
> // and see the debug messages get printed to the console.
> //
> // See the other example plugins in this directory for plugins that are
> // configured for specific use cases (but don't come with as many comments as
> // this one does).
> 
> #include <string>
> #include <vector>
> 
> #include "df/world.h"
3d15
< // some headers required for a plugin. Nothing special, just the basics.
5,14c17,18
< #include <Console.h>
< #include <Export.h>
< #include <PluginManager.h>
< #include <modules/EventManager.h>
< // If you need to save data per-world:
< //#include "modules/Persistence.h"
< 
< // DF data structure definition headers
< #include "DataDefs.h"
< //#include "df/world.h"
---
> #include "Debug.h"
> #include "PluginManager.h"
16,17c20,24
< // our own, empty header.
< #include "skeleton.h"
---
> #include "modules/Persistence.h"
> #include "modules/World.h"
> 
> using std::string;
> using std::vector;
20d26
< using namespace df::enums;
22,23c28,31
< // Expose the plugin name to the DFHack core, as well as metadata like the DFHack version.
< // The name string provided must correspond to the filename -
---
> // Expose the plugin name to the DFHack core, as well as metadata like the
> // DFHack version that this plugin was compiled with. This macro provides a
> // variable for the plugin name as const char * plugin_name.
> // The name provided must correspond to the filename --
27,30c35,39
< // The identifier declared with this macro (ie. enabled) can be specified by the user
< // and subsequently used to manage the plugin's operations.
< // This will also be tracked by `plug`; when true the plugin will be shown as enabled.
< DFHACK_PLUGIN_IS_ENABLED(enabled);
---
> // The identifier declared with this macro (i.e. is_enabled) is used to track
> // whether the plugin is in an "enabled" state. If you don't need enablement
> // for your plugin, you don't need this line. This variable will also be read
> // by the `plug` builtin command; when true the plugin will be shown as enabled.
> DFHACK_PLUGIN_IS_ENABLED(is_enabled);
34,35c43,44
< // plugin from being loaded if df::global::world is null (i.e. missing from symbols.xml):
< //
---
> // plugin from being loaded if df::global::world is null (i.e. missing from
> // symbols.xml).
38,41c47,57
< // You may want some compile time debugging options
< // one easy system just requires you to cache the color_ostream &out into a global debug variable
< //#define P_DEBUG 1
< //uint16_t maxTickFreq = 1200; //maybe you want to use some events
---
> // logging levels can be dynamically controlled with the `debugfilter` command.
> // Actual plugins will likely want to set the default level to LINFO or LWARNING
> // instead of the LDEBUG used here.
> namespace DFHack {
>     // for configuration-related logging
>     DBG_DECLARE(skeleton, status, DebugCategory::LDEBUG);
>     // run `debugfilter set debug skeleton onupdate` to see logging in plugin_onupdate
>     DBG_DECLARE(skeleton, onupdate, DebugCategory::LINFO);
>     // for command-related logging
>     DBG_DECLARE(skeleton, command, DebugCategory::LDEBUG);
> }
43c59
< command_result command_callback1(color_ostream &out, std::vector<std::string> &parameters);
---
> static command_result command_callback1(color_ostream &out, vector<string> &parameters);
44a61
> // run when the plugin is loaded
46,59c63,71
<     commands.push_back(PluginCommand("skeleton",
<                                      "~54 character description of plugin", //to use one line in the ``[DFHack]# ls`` output
<                                      command_callback1,
<                                      false,
<                                      "example usage"
<                                      " skeleton <option> <args>\n"
<                                      "    explanation of plugin/command\n"
<                                      "\n"
<                                      " skeleton\n"
<                                      "    what happens when using the command\n"
<                                      "\n"
<                                      " skeleton option1\n"
<                                      "    what happens when using the command with option1\n"
<                                      "\n"));
---
>     DEBUG(status,out).print("initializing %s\n", plugin_name);
> 
>     // For in-tree plugins, don't use the "usage" parameter of PluginCommand.
>     // Instead, add an .rst file with the same name as the plugin to the
>     // docs/plugins/ directory.
>     commands.push_back(PluginCommand(
>         "skeleton",
>         "Short (~54 character) description of command.", // to use one line in the ``[DFHack]# ls`` output
>         command_callback1));
62a75
> // run when the plugin is unloaded
63a77,78
>     DEBUG(status,out).print("shutting down %s\n", plugin_name);
> 
70a86,87
> // run when the `enable` or `disable` command is run with this plugin name as
> // an argument
72,82c89,93
<     namespace EM = EventManager;
<     if (enable && !enabled) {
<         //using namespace EM::EventType;
<         //EM::EventHandler eventHandler(onNewEvent, maxTickFreq);
<         //EM::registerListener(EventType::JOB_COMPLETED, eventHandler, plugin_self);
<         //out.print("plugin enabled!\n");
<     } else if (!enable && enabled) {
<         EM::unregisterAll(plugin_self);
<         //out.print("plugin disabled!\n");
<     }
<     enabled = enable;
---
>     DEBUG(status,out).print("%s from the API\n", enable ? "enabled" : "disabled");
> 
>     // you have to maintain the state of the is_enabled variable yourself. it
>     // doesn't happen automatically.
>     is_enabled = enable;
86,87d96
< 
< /* OPTIONAL *
92c101,103
<     if (enabled) {
---
>     DEBUG(status,out).print("game state changed: %d\n", event);
> 
>     if (is_enabled) {
115a127
> 
119,122c131,136
< // Whatever you put here will be done in each game step. Don't abuse it.
< DFhackCExport command_result plugin_onupdate ( color_ostream &out )
< {
<     // whetever. You don't need to suspend DF execution here.
---
> // Whatever you put here will be done in each game frame refresh. Don't abuse it.
> // Note that if the plugin implements the enabled API, this function is only called
> // if the plugin is enabled.
> DFhackCExport command_result plugin_onupdate (color_ostream &out) {
>     DEBUG(onupdate,out).print("onupdate called\n");
> 
131,132c145,147
< DFhackCExport command_result plugin_save_data (color_ostream &out)
< {
---
> DFhackCExport command_result plugin_save_data (color_ostream &out) {
>     DEBUG(status,out).print("save or unload is imminent; time to persist state\n");
> 
137,138c152,154
< DFhackCExport command_result plugin_load_data (color_ostream &out)
< {
---
> DFhackCExport command_result plugin_load_data (color_ostream &out) {
>     DEBUG(status,out).print("world is loading; time to load persisted state\n");
> 
142d157
< * OPTIONAL */
143a159,165
> // This is the callback we registered in plugin_init. Note that while plugin
> // callbacks are called with the core suspended, command callbacks are called
> // from a different thread and need to explicity suspend the core if they
> // interact with Lua or DF game state (most commands do at least one of these).
> static command_result command_callback1(color_ostream &out, vector<string> &parameters) {
>     DEBUG(command,out).print("%s command called with %zu parameters\n",
>         plugin_name, parameters.size());
145,159c167,168
< // A command! It sits around and looks pretty. And it's nice and friendly.
< command_result command_callback1(color_ostream &out, std::vector<std::string> &parameters) {
<     // It's nice to print a help message you get invalid options
<     // from the user instead of just acting strange.
<     // This can be achieved by adding the extended help string to the
<     // PluginCommand registration as show above, and then returning
<     // CR_WRONG_USAGE from the function. The same string will also
<     // be used by 'help your-command'.
<     if (!parameters.empty()) {
<         return CR_WRONG_USAGE; //or maybe you want it to do something else
<     }
<     // Commands are called from threads other than the DF one.
<     // Suspend this thread until DF has time for us.
<     // **If you use CoreSuspender** it'll automatically resume DF when
<     // execution leaves the current scope.
---
>     // I'll say it again: always suspend the core in command callbacks unless
>     // all your data is local.
161d169
<     // Actually do something here. Yay.
163,169c171,180
<     // process parameters
<     if (parameters.size() == 1 && parameters[0] == "option1") {
<         // stuff
<     } else {
<         return CR_FAILURE;
<     }
<     // Give control back to DF.
---
>     // Return CR_WRONG_USAGE to print out your help text. The help text is
>     // sourced from the associated rst file in docs/plugins/. The same help will
>     // also be returned by 'help your-command'.
> 
>     // simple commandline parsing can be done in C++, but there are lua libraries
>     // that can easily handle more complex commandlines. see the blueprint plugin
>     // for an example.
> 
>     // TODO: do something according to the flags set in the options struct
> 

myk002 avatar Aug 03 '22 07:08 myk002