samcrow / rust-xplm

Rust interfaces to the X-Plane plugin SDK
Apache License 2.0
39 stars 15 forks source link

OwnedCommand: command already exists on reload #15

Closed JDeeth closed 8 months ago

JDeeth commented 8 months ago

This is a bit perplexing!

If you register an OwnedCommand, when the plugin loads first time, it creates the command. On reloading, it fails (and doesn't register the command handler) because the command already exists. Seems XP12 doesn't delete command IDs when all the handlers are deregistered anymore (if it ever did)?

Win11, XP12.0.8-rc3, rust-xplm 0.3.1, aircraft plugin.

First run:

Fetching plugins for C:\X-Plane/12/Aircraft/Freeware/C152/plugins

RUST: Starting com.jdeeth.rust-xplm.issues.command-demo (OwnedCommand demo)
RUST: Successfully constructed OwnedCommand for jdeeth/xplm-rust/demo
Loaded: C:\X-Plane/12/Aircraft/Freeware/C152/plugins/Test/64/win.xpl (com.jdeeth.rust-xplm.issues.command-demo).

Second run, after loading a different aircraft in between:

Fetching plugins for C:\X-Plane/12/Aircraft/Freeware/C152/plugins

RUST: Starting com.jdeeth.rust-xplm.issues.command-demo (OwnedCommand demo)
RUST: Failed to construct OwnedCommand for jdeeth/xplm-rust/demo: Command exists already
Loaded: C:\X-Plane/12/Aircraft/Freeware/C152/plugins/Test/64/win.xpl (com.jdeeth.rust-xplm.issues.command-demo).

Cargo.toml:

[package]
name = "rust-xplm-test"
version = "0.1.0"
edition = "2021"

[dependencies]
xplm = { git = "https://github.com/samcrow/rust-xplm", version = "0.3.1" }

[lib]
name = "win"
crate-type = ["cdylib"]
bench = false

lib.rs:

extern crate xplm;

use xplm::command::{CommandHandler, OwnedCommand};
use xplm::plugin::{Plugin, PluginInfo};
use xplm::{debugln, xplane_plugin};

mod info {
    pub const NAME: &str = "OwnedCommand demo";
    pub const SIGNATURE: &str = "com.jdeeth.rust-xplm.issues.command-demo";
    pub const DESCRIPTION: &str = "";
}

const COMMAND_NAME: &str = "jdeeth/xplm-rust/demo";

struct NullHandler;
impl CommandHandler for NullHandler {
    fn command_begin(&mut self) {}
    fn command_continue(&mut self) {}
    fn command_end(&mut self) {
        debugln!("RUST: Command end");
    }
}

struct CommandDemoPlugin;

impl Plugin for CommandDemoPlugin {
    type Error = std::convert::Infallible;

    fn start() -> Result<Self, Self::Error> {
        debugln!("RUST: Starting {} ({})", info::SIGNATURE, info::NAME);
        match OwnedCommand::new(COMMAND_NAME, "Compiled 2024-01-04T19:52", NullHandler) {
            Ok(_) => debugln!(
                "RUST: Successfully constructed OwnedCommand for {}",
                COMMAND_NAME
            ),
            Err(error) => debugln!(
                "RUST: Failed to construct OwnedCommand for {}: {}",
                COMMAND_NAME,
                error
            ),
        }
        Ok(CommandDemoPlugin)
    }

    fn info(&self) -> PluginInfo {
        use info::*;
        PluginInfo {
            name: NAME.to_owned(),
            signature: SIGNATURE.to_owned(),
            description: DESCRIPTION.to_owned(),
        }
    }
}

xplane_plugin!(CommandDemoPlugin);

My workaround is to clone rust-xplm locally and depend on this instead, and alter command::OwnedCommandData::new to use the existing command if it exists:

        let command_id = match unsafe { XPLMFindCommand(name_c.as_ptr()) } {
            id if id.is_null() => unsafe {
                XPLMCreateCommand(name_c.as_ptr(), description_c.as_ptr())
            },
            id => id,
        };

Perhaps it's worth separating the creation of commands from the registration/deregistration of command handlers?

Just as a point of comparison, my C++ wrapper for commands, where registering handlers for existing commands is accommodated alongside creating new commands: https://github.com/JDeeth/PPL/blob/xplm210/src/command.h

Registering your own handler for an X-Plane stock command is fine - you can return 1 or 0 to let the default handler run or not. I don't know what happens if a plugin registers a new handler for a command created by another plugin - which handler takes precedence or if one replaces the other…!

JDeeth commented 8 months ago

OK, the command is dropped when X-Plane shuts down, and XPLMCreateCommand returns the existing XPLMCommandRef if there's already a command with the same name. So the workaround simplifies a little:

impl OwnedCommandData {
    pub fn new<H: CommandHandler>(
        name: &str,
        description: &str,
        handler: H,
    ) -> Result<Self, CommandCreateError> {
        let name_c = CString::new(name)?;
        let description_c = CString::new(description)?;

        Ok(OwnedCommandData {
            id: unsafe { XPLMCreateCommand(name_c.as_ptr(), description_c.as_ptr()) },
            handler: Box::new(handler),
        })
    }
}