Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin system #339

Draft
wants to merge 39 commits into
base: master
Choose a base branch
from
Draft

Plugin system #339

wants to merge 39 commits into from

Conversation

vyPal
Copy link
Contributor

@vyPal vyPal commented Nov 24, 2024

Time spent on this PR: wakatime

Description

A plugin system using dynamic library loading. Library metadata is stored directly in the library file (.so, .dylib, or .dll) so all the server admin has to do is put the os-specific file into the plugin directory. Plugin metadata is pulled from the Cargo.toml file at compile time.

This system is still in an early stage of development, I need to implement a lot of things and split up the code to make it easier to work with in the future, as well as add tests, and some other things.

TODO List

  • Dynamic library loading
  • On load/unload
  • Event priorities
  • Custom commands
  • Custom events
  • Command to manage plugins
  • List plugins in query
  • Player events
  • World events
  • Async support (if anyone could help with this I would appreciate it)
  • Extism (WASM) support - might not implement
  • Lua support - will be a separate pr

Usage

A separate crate named pumpkin-api-macros defines macros to simplify plugin development:

#[plugin_method]
fn on_load(&mut self, server: &dyn PluginContext) -> Result<(), String> {
    server.get_logger().info("Plugin loaded!");
    Ok(())
}

#[plugin_event(blocking = true, priority = Highest)]
fn on_player_join(&mut self, server: &dyn PluginContext, player: &Player) -> Result<bool, String> {
    server.get_logger().info(format!("Player {} joined the game", player.gameprofile.name).as_str());
    // Returning true will block any other plugins from receiving this event
    Ok(true)
}

#[plugin_impl]
pub struct MyPlugin {}

There is an example plugin available at plugins/hello-plugin-source/

Testing

Works on my machine ™️
Tested on:

  • Arch linux
  • Windows 11

Checklist

Things need to be done before this Pull Request can be merged.

  • Code is well-formatted and adheres to project style guidelines: cargo fmt
  • Code does not produce any clippy warnings: cargo clippy
  • All unit tests pass: cargo test
  • I added new unit tests, so other people don't accidentally break my code by changing other parts of the codebase. How?

@Commandcracker
Copy link
Contributor

A method for generating plugin IDs is needed. I would like to have something decentralized. Using uuid4 could be a good option ?

To explain the importance of plugin IDs: plugins need a reliable way to declare dependencies on one another, and using names alone is not a robust long-term solution. Additionally, the plugin manager must be able to handle dependencies efficiently. It would also be worth investigating whether we can support scenarios where two plugins have circular dependencies, requiring each other.

We need a way to detect if a plugin is outdated. I propose that plugin_metadata should include the current plugin API version being used by the plugin. Then, the plugin loader can simply check whether the plugin's API version is compatible with the supported API versions (using semantic versioning).

I want to implement side-loading for plugins, specifically one for WASM and another for Lua. For this to work, we would need to ensure that the plugin stack can handle side-loaded plugins in a way that allows a normally loaded plugin to require a side-loaded one. This might involve allowing a plugin to load another "plugin". I don't know how we can make this work, but it would be really nice.

@Commandcracker
Copy link
Contributor

Btw: The system you made looks really good!

Forgot to say this: We also need to implement something like events or hooks.

@vyPal
Copy link
Contributor Author

vyPal commented Nov 25, 2024

plugins need a reliable way to declare dependencies on one another, and using names alone is not a robust long-term solution.

One of my main targets is to make the plugins similair to spigot plugins, they use names as the plugin identifiers and for dependencies, but having some unique id system would probably be better.

It would also be worth investigating whether we can support scenarios where two plugins have circular dependencies, requiring each other.

Depends on what functionality they would require from each other. Another good question is how exactly would the plugins interface with each other. I'd probably recommend a system where plugins can register certain methods or structs with the plugin manager, so that other plugins can access them.

We need a way to detect if a plugin is outdated. I propose that plugin_metadata should include the current plugin API version being used by the plugin. Then, the plugin loader can simply check whether the plugin's API version is compatible with the supported API versions (using semantic versioning).

This is definetly a good idea. It would also be nice if the plugin manager could either periodically or on startup check if any plugins have newer versions available, but this would require implementing a plugin marketplace. The plugin marketplace could also help with providing unique IDs to plugins, but this would no longer be a decentralized system. Maybe if IDs were prefixed with the provisioner of the ID (like 'market:abcd1234' or something similair)?

I want to implement side-loading for plugins, specifically one for WASM and another for Lua. For this to work, we would need to ensure that the plugin stack can handle side-loaded plugins in a way that allows a normally loaded plugin to require a side-loaded one.

If I were to implement the functionality where plugins can register their methods on the manager and expose them to other plugins, this would allow us to use WASM and Lua as full plugins that could interface with other plugins as well

Forgot to say this: We also need to implement something like events or hooks.

I am working on those now, I am primarily thinking up some efficient system for distributing these events between plugins, and a system for plugins to register events they will be listening for, so that all events dont need to be sent to all plugins

@Snowiiii
Copy link
Owner

Snowiiii commented Nov 25, 2024

First of all, Good Job so far this PR looks pretty promising and you put a good amount of effort into it already. For now one thing i would change is, to remove the plugin_metadata and require it in the plugins Cargo.toml.

@Tohjuler
Copy link

Hi, I have a idea for both the id handling and update.
You could make a public plugin database of sorts, not for holding the plugins just info about the plugins, a id, versions, file hash (for verifying the file) and more.
It could also handle some telemetry, of course optional.
It would also help on security as the plugin files are verified, you could just send a warning if the plugin is not in the database, or have a option for a secure mode, where it will not load plugin that is not verified.
I'm sure there a holes in this idea, but I also just come op with it, have a good day :)

@Snowiiii
Copy link
Owner

Snowiiii commented Nov 30, 2024

I saw you put
Extism (WASM) support and Lua support into TODO. Im not a fan of that, and also it would make sense to split this into multiple PRs since this is something which will take a long time. Also See #110

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants