First Plugin

Learn how to create your first plugin with the Rust language module, including basic syntax and setup.

Welcome to the Plugify Rust Language Module Plugin Development Guide. This guide will walk you through the process of creating your first plugin using Rust within the Plugify framework. Whether you're a game modder, systems programmer, or plugin developer, this tutorial will help you understand the fundamental concepts and steps necessary to build a fully functional Rust plugin.

What is Plugify?

Plugify is a modular plugin framework that enables developers to extend applications by integrating external plugins dynamically. It provides a structured approach to plugin development with clear guidelines on plugin structure, dependency management, and API exposure.

Why Use Rust?

The Rust Language Module allows developers to create high-performance, memory-safe plugins using Rust. By leveraging dynamic libraries (shared objects) and Rust's ownership system, developers can extend applications while maintaining both efficiency and safety. Plugify ensures that your plugin integrates smoothly with the core framework by following a standardized plugin structure and API.

What You'll Learn

In this guide, you will:

  1. Set up the directory structure for your plugin.
  2. Define a plugin manifest (.pplugin file) to register your plugin in the Plugify ecosystem.
  3. Write the Rust code for your plugin using the provided API.
  4. Manage dependencies to ensure correct plugin initialization.
  5. Compile and package your plugin using Cargo.

By the end of this tutorial, you'll have a working Rust plugin that can be loaded into the Plugify framework. Let's get started!

Directory Structure

To ensure seamless integration with the Plugify framework, your plugin must follow a specific directory structure. Each plugin should be placed inside its own folder within the extensions directory. The folder name must match the plugin's name and follow these rules:

  1. Allowed Characters: Alphanumeric (A-Z, a-z, 0-9), special characters (_, -).
  2. Spaces are NOT allowed in the folder name.
  3. The .pplugin configuration file must have the same name as the plugin folder.

Example Directory Layout

Breakdown of the Structure

  • res/extensions/ – The main directory where all plugins are stored.
  • plugin_name/ – Each plugin has its own dedicated folder. The folder name must match the .pplugin file name.
  • bin/ – This subfolder contains the compiled plugin binaries (.dll for Windows, .so for Linux, etc.).
  • plugin_name.pplugin – The configuration file that defines metadata about the plugin.

By following this structure, Plugify can correctly detect, load, and manage plugins across different platforms.

The Plugin Manifest

Each plugin in the Plugify framework requires a manifest file with the .pplugin extension. This file is a JSON-based configuration that provides essential metadata about the plugin, ensuring that it can be properly identified, loaded, and managed.

Key Responsibilities of the Manifest File:

  • Defines the plugin version and author details.
  • Specifies the entry point for execution.
  • Lists dependencies required by the plugin.
  • Declares exported methods available for external interaction.

Example of a Manifest File

plugin_name.pplugin
{
  "$schema": "https://raw.githubusercontent.com/untrustedmodders/plugify/refs/heads/main/schemas/plugin.schema.json",
  "version": "0.1.0",
  "name": "PluginRust",
  "description": "An example of a Rust plugin. This can be used as a starting point when creating your own plugin.",
  "author": "untrustedmodders",
  "website": "https://github.com/untrustedmodders/",
  "license": "MIT",
  "entry": "bin/example_plugin",
  "platforms": [],
  "language": "rust",
  "dependencies": [],
  "methods": [],
  "classes": []
}

Key Fields Explained

  • entry: Specifies the location of the compiled plugin binary (without extension).
  • language: Should be set to rust for Rust plugins.
  • dependencies: Lists other required plugins, ensuring correct load order.
  • methods: Functions exposed by the plugin for external interaction.
  • classes: Classes exported by the plugin that can be instantiated or used by other plugins.

Why is the Manifest File Important?

  • Ensures Compatibility – Defines supported versions and platforms.
  • Enables Modularity – Lists dependencies for structured plugin loading.
  • Facilitates Integration – Allows other plugins to call exposed methods.

By following this manifest structure, Plugify can efficiently load and manage plugins, ensuring seamless functionality across different projects.

Writing the Plugin Code

Creating a Rust plugin for Plugify is straightforward. You can either use the pre-built Rust plugin template available in our repository or write your plugin from scratch.

Using the Plugin Template

The easiest way to get started is by downloading the Rust plugin template from our repository. It contains all the necessary files, including:

  • A preconfigured Cargo project
  • A sample implementation
  • The required Plugify crate dependencies

Just clone the repository, and your environment will be ready for development.

Writing a Plugin from Scratch

If you prefer to build your plugin manually, follow these steps:

Setting Up Your Plugin Project

  1. Create a new Rust library project:
    cargo new --lib plugin_name
    cd plugin_name
    
  2. Update Cargo.toml to create a dynamic library:
    Cargo.toml
    [package]
    name = "plugin_name"
    version = "0.1.0"
    edition = "2021"
    
    [lib]
    crate-type = ["cdylib"]
    
    [dependencies]
    plugify = { git = "https://github.com/untrustedmodders/rust-plugify" }
    

Plugin Code Structure

Here is a basic example of a Rust plugin implementation:

src/lib.rs
use plugify::register_plugin;

fn on_plugin_start() {
    println!("Rust: on_plugin_start");
}

fn on_plugin_update(_dt: f32) {
    println!("Rust: on_plugin_update");
}

fn on_plugin_end() {
    println!("Rust: on_plugin_end");
}

register_plugin!(
    start: on_plugin_start,
    update: on_plugin_update,
    end: on_plugin_end
);

Understanding Plugin Lifecycle Methods

Each plugin can define the following lifecycle methods, which Plugify will call at specific times:

MethodDescriptionRequired?
on_plugin_startCalled when the plugin is loaded and ready to run.❌ Optional
on_plugin_update(dt: f32)Called every frame, allowing periodic updates.❌ Optional
on_plugin_endCalled when the plugin is unloaded or shuts down.❌ Optional

Using the Register Macro

To register your plugin with Plugify, you must use the register_plugin! macro. This macro:

  • Defines the plugin entry point.
  • Connects your lifecycle functions to the Plugify system.
  • Ensures that lifecycle methods are correctly exposed.

You can register any combination of lifecycle functions:

// All three lifecycle functions
register_plugin!(
    start: on_plugin_start,
    update: on_plugin_update,
    end: on_plugin_end
);

// Only start and end
register_plugin!(
    start: on_plugin_start,
    end: on_plugin_end
);

// Only start
register_plugin!(
    start: on_plugin_start
);

Including the Plugify Crate

The Plugify crate provides essential definitions and utilities required to interact with Plugify. Add it to your Cargo.toml:

[dependencies]
plugify = { git = "https://github.com/untrustedmodders/rust-plugify" }

The crate includes:

  • register_plugin! macro for plugin registration.
  • Type definitions for cross-language compatibility.
  • Helper functions for accessing Plugify features.

Summary

  • Use the Rust plugin template to get started quickly.
  • Define lifecycle functions (on_plugin_start, on_plugin_update, on_plugin_end) as needed.
  • Register your plugin using the register_plugin! macro.
  • Use the Plugify crate to access necessary utilities.

By following these steps, you'll have a fully functional Plugify plugin ready to run!

Dependency Management

Dependency management in Plugify ensures that plugins are loaded in the correct order based on their dependencies. The system uses Topological Sorting to determine the appropriate sequence, preventing initialization issues when plugins rely on other plugins.

How Dependencies Work

Each plugin can declare its dependencies inside the dependencies field of its plugin manifest (.pplugin file). The Plugify core will:

  • Analyze the dependencies listed in each plugin's manifest.
  • Sort the plugins using Topological Sorting, ensuring dependencies are loaded before dependent plugins.
  • Validate platform compatibility and requested versions.

Dependency Representation

Dependencies are declared using the following JSON format inside the dependencies field of the plugin manifest:

plugin_name.pplugin
"dependencies": [
    {
        "name": "core-plugin",
        "optional": false,
        "constraints": ">=1.0.0 <2.0.0"
    }
]

Explanation of Fields

FieldTypeRequired?Description
namestring✅ YesThe unique name of the dependency plugin.
optionalboolean❌ No (default: false)If true, the plugin can still load even if the dependency is missing.
constraintsstring❌ NoSpecifies a required version of the dependency. If omitted, any compatible version is allowed.

Example Plugin with Dependencies

Here's an example plugin manifest (.pplugin) that declares multiple dependencies:

plugin_name.pplugin
{
  "version": "1.0.0",
  "name": "MyRustPlugin",
  "entry": "bin/my_rust_plugin",
  "language": "rust",
  "dependencies": [
    {
      "name": "core-plugin",
      "optional": false,
      "constraints": "2.0.0"
    },
    {
      "name": "utils-plugin",
      "optional": true
    }
  ]
}

In this example:

  • core-plugin is mandatory and must be version 2.0.0.
  • utils-plugin is optional, meaning the plugin will still load even if it's missing.

Key Takeaways

  1. Ensure mandatory dependencies are available before loading your plugin.
  2. Use optional: true for dependencies that enhance functionality but aren't critical.
  3. Specify platforms if a dependency isn't cross-platform.
  4. Define constraints if your plugin requires a specific version of a dependency.

By properly managing dependencies, your plugin will load efficiently and avoid unexpected failures due to missing or incompatible dependencies.

Building the Plugin with Cargo

Rust uses Cargo as its build system and package manager. Building your plugin is straightforward:

Building in Release Mode

Using Cargo (Command Line)

cargo build --release

The compiled library will be in target/release/:

  • On Windows: plugin_name.dll
  • On Linux: libplugin_name.so
  • On macOS: libplugin_name.dylib

Copy to Plugify Extensions

Copy the compiled library to your Plugify extensions/plugin_name/bin/ directory.

Verify Plugin Load

Start Plugify and verify your plugin loads correctly:

plg plugins

Build Configuration

You can customize your build in Cargo.toml:

Cargo.toml
[profile.release]
opt-level = 3          # Maximum optimization
lto = true            # Link-time optimization
codegen-units = 1     # Better optimization
strip = true          # Strip symbols (smaller binary)
panic = "abort"       # Smaller binary, no unwinding

Running and Testing the Plugin

Once you have built your plugin, the next step is to run and test it within the Plugify system.

Placing the Plugin in the Correct Directory

Ensure your plugin is correctly structured inside the extensions folder. Your plugin directory should contain:

  • The compiled binary in bin/ directory
  • The .pplugin manifest file

Verifying Plugin Load Status

You can check if your plugin loaded successfully by using terminal commands provided by Plugify.

  • List all loaded plugins:
plg plugins

This will display all currently loaded plugins.

  • Query specific plugin information:
plg plugin example_plugin

This command retrieves detailed information about a specific plugin.

Handling Plugin Load Failures

If your plugin fails to load, Plugify will provide error messages in the console. You can also explicitly query the plugin status using:

plg list

Debugging Issues

If your plugin isn't working as expected:

  • Check the console logs for detailed error messages.
  • Ensure all dependencies are properly installed and compatible.
  • Verify the entry point in the .pplugin manifest matches the actual plugin binary location.
  • Use Rust's error messages to identify compilation or runtime issues.

Conclusion

This guide covered the essential steps to create a Rust plugin for Plugify, including setting up the project, writing the plugin code, configuring the manifest, and building the plugin using Cargo. Following these guidelines ensures smooth integration into the Plugify ecosystem with the safety and performance benefits of Rust.