Module-Writing-Guide
Nathan Schneider edited this page 1 year ago

Modules are the heart of Modpol. They enable users to radically extend the system's functionality. Even some of the most basic functions around orgs are written as modules, so as to make Modpol as expressive as possible.

First, begin by exploring modpol/modules/template.lua as an initial template. It walks you through a lot of the things you need to include. In addition, explore how existing modules were created by looking at their source files in the same directory.

Module file locations

Modules are located in two places: modpol_core/modules/ and (for platform-specific modules) modpol_minetest/modules/. In both cases, the modules should also be listed in the respective api.lua file just below the modules/ directory.

Modules in modpol_minetest can use modules in modpol_core, but core modules should not depend on any platform-specific ones.

Library

The Modpol software provides a library of functions that can be used in modules for manipulating orgs and interacting with users. In particular, module-creators will find the functions in the following files useful:

  • modpol_core/orgs/base.lua
  • modpol_core/orgs/interactions.lua

Many of the interactions functions are overridden for Minetest in modpol_minetest/overrides/interactions.lua, but their specifications should be identical to those in modpol_core.

We soon plan to have more accessible documentation of the available functions.

Interactions

As much as possible, use existing interactions functions defined in modpol_core/interactions/interactions.lua. This will help ensure that the module works across platforms.

Because different platforms handle interaction temporality differently, all interactions should be defined to work asynchronously. That is why interactive functions (e.g., text_query, dropdown_query) take a result function as an argument; the result function defines what should happen after the input is received from the interaction. Whatever comes after the interaction should be nested in or called from that result function. There are plenty of examples of this process in the existing modules.

At the end of a given interaction, generally aim to re-open the dashboard that the module process began with.

Policies

Policies are configurations for modules that can be edited at the org level. They live in [org].policies[module_slug]. If a module's policy table is set to nil, that policy is inactive in that org. It is an empty set by default.

Policies are designed to override values in the [module].config table. When creating a module, be sure to add to the config table any variables that a user (or a module calling your module) might want to configure.

The org:call_module function also enables another module calling the module to override those policies with its config parameter.

Some standard nomenclature for policies include:

  • prompt - config string for message to be sent to users for a decision-making process or the like
  • approval_module - config function slug string for the function used to approve the module's activity (e.g., "consent")

On all configurations, use false as the default, not nil.

Utility modules

Some modules are "utility modules" that are not intended to be called directly by users, only by other modules. For a utility module, be sure to set hide to true in the main data table. Other modules can then use it, but it will not be presented to users.

Persistent memory

Modpol saves org data to persistent storage (what gets restored after a system shutdown) at the following times:

  • When a module process begins
  • When a user interacts with a pending process
  • When a module process is deleted

Outside of these events, if your module needs to make changes to persistent memory (especially when changing the state of an org), use the [org]:record() function.

Testing

In addition to restarting the program, reset orgs (modpol.orgs.reset() or, in Minetest /mptest) after changing a mod. Mods are preserved as a snapshot when they are added to an org, so they will not be changed unless the org is refreshed.

When debugging, you can use modpol.msg([string]) to produce in-game messages. This is a testing-oriented shortcut for modpol.interactions.message. Be sure to delete all testing msg functions when you're done!

Minetest-specific

Minetest can be tricky to develop for. If your mod does not involve Minetest-focused functionality (such as privs, blocks, etc.), rely on the existing cross-platform Modpol library function and develop a module for core Modpol that can be used in other platforms as well.

One critical thing to keep in mind is that all Minetest interactions must be handled asynchronously. As you will see in our example modules, interactions are linked by passing "result functions" through each other.

If you do want to develop a mod for Minetest specifically, be sure to consult rubenwardy's excellent _Minetest Modding Book_ and the Minetest developer wiki. Oh, and VSCode has a Minetest plugin.