dragonfly-plugin

Crates.iodragonfly-plugin
lib.rsdragonfly-plugin
version0.3.0
created_at2025-11-25 05:14:20.347732+00
updated_at2025-11-27 22:38:19.291583+00
descriptionDragonfly gRPC plugin SDK for Rust
homepagehttps://github.com/secmc/dragonfly-plugins
repositoryhttps://github.com/secmc/dragonfly-plugins
max_upload_size
id1949212
size323,559
(Jviguy)

documentation

README

Dragonfly Rust Plugin SDK

The dragonfly-plugin crate is the Rust SDK for Dragonfly gRPC plugins. It gives you:

  • Derive macros to describe your plugin (#[derive(Plugin)]) and commands (#[derive(Command)]).
  • A simple event system based on an EventHandler trait and an #[event_handler] macro.
  • A Server handle with high‑level helpers (like send_chat, teleport, world_set_block, …).
  • A PluginRunner that connects your process to the Dragonfly host and runs the event loop.

Crate and directory layout

The Rust SDK lives under packages/rust as a small workspace:

  • dragonfly-plugin (this crate): Public SDK surface used by plugin authors.
    • src/lib.rs: Re-exports core modules and pulls in this README as crate-level docs.
    • src/command.rs: Command context (Ctx), parsing helpers, and CommandRegistry trait.
    • src/event/: Event system (EventContext, EventHandler, mutation helpers).
    • src/server/: Server handle and generated helpers for sending actions to the host.
    • src/generated/df.plugin.rs: Prost/tonic types generated from proto/types/*.proto (do not edit).
  • macro/ (dragonfly-plugin-macro): Procedural macros for #[derive(Plugin)], #[derive(Command)], and #[event_handler]. This crate is re-exported by dragonfly-plugin and is not used directly by plugins.
  • xtask/: Internal code generation tooling that reads df.plugin.rs and regenerates event/handler.rs, event/mutations.rs, and server/helpers.rs. It is not published.
  • example/: A minimal example plugin crate showing recommended usage patterns for the SDK.
  • tests/: Integration tests covering command derivation, event dispatch, server helpers, and the interaction between the runtime and macros.

All APIs in this README reflect the 0.3.x line. Within 0.3.x we intend to keep:

  • The Plugin, EventHandler, EventSubscriptions, and CommandRegistry trait shapes.
  • The event_handler, Plugin, and Command macros and their attribute syntax.
  • The Server helpers and event::EventContext semantics (including cancel and mutation helpers).

Breaking changes may still happen in a future 0.4.0, but not within 0.3.x.


Quick start

1. Create a new plugin crate

cargo new my_plugin --bin

2. Add dependencies

[package]
name = "my_plugin"
version = "0.1.0"
edition = "2021"

[dependencies]
dragonfly-plugin = "0.3"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] } # optional, for DB-backed examples

Only dragonfly-plugin and tokio are required; other crates (like sqlx) are up to your plugin.

3. Define your plugin

use dragonfly_plugin::{
    event::{EventContext, EventHandler},
    event_handler,
    types,
    Plugin,
    PluginRunner,
    Server,
};

#[derive(Plugin, Default)]
#[plugin(
    id = "example-rust",            // must match plugins.yaml
    name = "Example Rust Plugin",
    version = "0.3.0",
    api = "1.0.0"
)]
struct MyPlugin;

#[event_handler]
impl EventHandler for MyPlugin {
    async fn on_player_join(
        &self,
        server: &Server,
        event: &mut EventContext<'_, types::PlayerJoinEvent>,
    ) {
        let player_name = &event.data.name;
        println!("Player '{}' has joined.", player_name);

        let welcome = format!(
            "Welcome, {}! This server is running a Rust plugin.",
            player_name
        );

        // Ignore send errors; they usually mean the host shut down.
        let _ = server
            .send_chat(event.data.player_uuid.clone(), welcome)
            .await;
    }

    async fn on_chat(
        &self,
        _server: &Server,
        event: &mut EventContext<'_, types::ChatEvent>,
    ) {
        let new_message = format!("[Plugin] {}", event.data.message);
        event.set_message(new_message);
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("Starting example-rust plugin...");
    PluginRunner::run(MyPlugin, "tcp://127.0.0.1:50050").await
}

The #[event_handler] macro:

  • Detects which on_* methods you implement.
  • Generates an EventSubscriptions impl that subscribes to the corresponding types::EventType variants.
  • Wires those events into event::dispatch_event.

Commands

The 0.3.x series introduces a first‑class command system.

Declaring a command

use dragonfly_plugin::{command::Ctx, Command};

#[derive(Command)]
#[command(
    name = "eco",
    description = "Economy commands.",
    aliases("economy", "rustic_eco")
)]
pub enum Eco {
    #[subcommand(aliases("donate"))]
    Pay { amount: f64 },

    #[subcommand(aliases("balance", "money"))]
    Bal,
}

This generates:

  • A static Eco::spec() -> types::CommandSpec.
  • A TryFrom<&types::CommandEvent> impl that parses args into Eco.
  • An EcoHandler trait with async methods (pay, bal) and an __execute helper.

Handling commands in your plugin

Add the command type to your plugin’s #[plugin] attribute, and implement the generated handler trait for your plugin type:

use dragonfly_plugin::{command::Ctx, Command, Plugin};

#[derive(Plugin)]
#[plugin(
    id = "rustic-economy",
    name = "Rustic Economy",
    version = "0.1.0",
    api = "1.0.0",
    commands(Eco)
)]
struct RusticEconomy {
    // your state here, e.g. DB pools
}

impl EcoHandler for RusticEconomy {
    async fn pay(&self, ctx: Ctx<'_>, amount: f64) {
        // ...
        let _ = ctx
            .reply(format!("You paid yourself ${:.2}.", amount))
            .await;
    }

    async fn bal(&self, ctx: Ctx<'_>) {
        // ...
        let _ = ctx.reply("Your balance is $0.00".to_string()).await;
    }
}

The #[derive(Plugin)] macro then:

  • Reports the command specs in the initial hello handshake.
  • Generates a CommandRegistry impl that:
    • Parses CommandEvents into your command types.
    • Cancels the event if a command matches.
    • Dispatches into your EcoHandler implementation.

Within 0.3.x the shape of the command API (Ctx, CommandRegistry, CommandParseError, and the Command derive attributes) is considered stable.


Events, context, and mutations

  • event::EventContext<'_, T> wraps each incoming event:
    • data: &T gives read‑only access.
    • cancel().await marks the event as cancelled and immediately sends a response.
    • Event‑specific methods (like set_message for ChatEvent) live in generated extensions.
  • event::EventHandler is a trait with an async method per event type; you usually never write impl EventHandler by hand except inside an #[event_handler] block.

You generally do not construct EventContext yourself; the runtime does it for you.


Connection and runtime

Use PluginRunner::run(plugin, addr) from your main function:

  • For TCP, pass e.g. "tcp://127.0.0.1:50050" or "127.0.0.1:50050".
  • On Unix hosts you may also pass:
    • "unix:///tmp/dragonfly_plugin.sock" or
    • an absolute path ("/tmp/dragonfly_plugin.sock").

On non‑Unix platforms, Unix socket addresses will return an error.

PluginRunner:

  • Connects to the host.
  • Sends an initial hello (PluginHello) with your plugin ID, name, version, API version and commands.
  • Subscribes to your EventSubscriptions.
  • Drives the main event loop until the host sends a shutdown message or closes the stream.

Stability policy for 0.3.x

Within the 0.3.x series we aim to keep:

  • Trait surfaces for Plugin, EventHandler, EventSubscriptions, CommandRegistry.
  • Macro names and high‑level attribute syntax (#[plugin(...)], #[event_handler], #[derive(Command)], #[subcommand(...)]).
  • Server helper method names and argument shapes.
  • EventContext behavior for cancel, mutation helpers, and double‑send (panic in debug, log in release).

We may still:

  • Add new events and actions.
  • Add new helpers or mutation methods.
  • Improve error messages and diagnostics.

For details on how the code is generated and how to maintain it, see MAINTAINING.md.

Commit count: 0

cargo fmt