I'll explain how to implement and use the Horizon Plugin API in a project. The plugin API you've shared is a framework for building modular functionality for the Horizon game server.
## Understanding the Plugin API
The Horizon Plugin API provides a way to extend the Horizon game server with custom functionality through plugins. Here's how it works:
1. Each plugin is a separate Rust crate that implements specific traits
2. The main server loads plugins dynamically at runtime
3. Plugins can interact with each other and the core server
## Implementing a Plugin
Let's create a simple plugin that adds chat functionality to the Horizon server.
### Step 1: Create the plugin crate structure
First, create a new directory in the `plugins` folder:
```bash
mkdir -p plugins/chat_plugin/src
cd plugins/chat_plugin
```
### Step 2: Create the Cargo.toml file
```toml
[package]
name = "chat_plugin"
version = "0.1.0"
edition = "2021"
[dependencies]
horizon-plugin-api = "0.2.0"
horizon_data_types = "0.4.0"
socketioxide = "0.15.1"
parking_lot = "0.12.3"
serde = "1.0.216"
serde_json = "1.0.134"
```
### Step 3: Implement the plugin
Create `src/lib.rs` with the following code:
```rust
use horizon_data_types::Player;
use socketioxide::extract::SocketRef;
pub use horizon_plugin_api::{Plugin, Pluginstate, LoadedPlugin};
use parking_lot::RwLock;
use std::sync::Arc;
use std::collections::HashMap;
use serde_json::{json, Value};
// Define the plugin API trait
pub trait PluginAPI {
fn player_joined(&self, socket: SocketRef, player: Arc<RwLock<horizon_data_types::Player>>);
}
// Define the plugin constructor trait
pub trait PluginConstruct {
fn new(plugins: HashMap<String, (Pluginstate, Plugin)>) -> Plugin;
fn get_structs(&self) -> Vec<&str>;
}
// Implement the constructor for the Plugin type
impl PluginConstruct for Plugin {
fn new(_plugins: HashMap<String, (Pluginstate, Plugin)>) -> Plugin {
println!("Chat plugin initialized!");
Plugin {}
}
fn get_structs(&self) -> Vec<&str> {
vec!["ChatMessage"]
}
}
// Implement the API for the Plugin type
impl PluginAPI for Plugin {
fn player_joined(&self, socket: SocketRef, player: Arc<RwLock<horizon_data_types::Player>>) {
println!("Player joined chat system");
setup_chat_listeners(socket, player);
}
}
// Setup chat event listeners
fn setup_chat_listeners(socket: SocketRef, player: Arc<RwLock<Player>>) {
// Handle incoming chat messages
socket.on("chat_message", move |data: socketioxide::extract::Data<Value>, socket: SocketRef| {
let message = data.0;
// Get player name
let player_name = player.read().get_name().unwrap_or("Unknown".to_string());
// Create the message payload with sender info
let payload = json!({
"sender": player_name,
"message": message["text"],
"timestamp": chrono::Utc::now().timestamp()
});
// Broadcast the message to all clients
socket.broadcast().emit("chat_broadcast", payload).ok();
// Echo back to sender with confirmation
socket.emit("chat_sent", json!({"success": true})).ok();
println!("Chat message processed from {}", player_name);
});
}
```
## Using the Plugin in the Horizon Server
The plugin system automatically loads plugins from the `plugins` directory. Here's how the server interacts with the plugins:
1. The server's `PluginManager` scans the plugins directory during startup
2. It loads each plugin based on the Cargo.toml information
3. Plugins are initialized with the `new()` method
4. The server calls plugin methods like `player_joined` when appropriate
## Testing the Plugin
To test our chat plugin:
1. Build the plugin:
```bash
cd plugins/chat_plugin
cargo build
```
2. Start the Horizon server, which will automatically load our plugin:
```bash
cd ../..
cargo run
```
3. Connect clients to the server and test the chat functionality.
## Advanced Plugin Interactions
Plugins can interact with each other through dependencies. For example, our chat plugin could depend on a permission plugin to check if users are allowed to send messages.
### Implementing Inter-Plugin Communication:
```rust
// In chat_plugin/src/lib.rs:
impl PluginConstruct for Plugin {
fn new(plugins: HashMap<String, (Pluginstate, Plugin)>) -> Plugin {
// Get reference to permission plugin
if let Some((_, permission_plugin)) = plugins.get("permission_plugin") {
// Cast to the permission plugin's API type
let permission_api = permission_plugin as &dyn permission_plugin::PluginAPI;
// Use the permission plugin's functionality
println!("Permission plugin is available: {}",
permission_api.get_version());
}
Plugin {}
}
// Rest of implementation...
}
```
## Best Practices for Plugin Development
1. **Keep plugins focused**: Each plugin should do one thing well
2. **Minimize dependencies**: Only depend on other plugins when necessary
3. **Handle errors gracefully**: Don't crash the server if something goes wrong
4. **Document your API**: Make it clear how other plugins can interact with yours
5. **Follow the event-driven model**: Use the event system for most interactions
6. **Keep state minimal**: Don't store large amounts of data in the plugin itself
## Complete Project Structure
A complete Horizon project with plugins would look like:
```
horizon-project/
├── backend_api/ # Backend API implementation
├── plugin_api/ # Plugin API implementation
├── plugins/
│ ├── chat_plugin/ # Our chat plugin
│ ├── permission_plugin/
│ └── other_plugins/
├── server/ # Main server implementation
└── Cargo.toml # Workspace configuration
```
The plugin system provides a clean, modular architecture that makes it easy to extend the Horizon server with new functionality without modifying the core server code.