capnproto-rust icon indicating copy to clipboard operation
capnproto-rust copied to clipboard

Accessing the super interface using interface inheritance in capnp schema

Open john-hern opened this issue 6 years ago • 14 comments

Not sure if this is the best place to ask this question, however how exactly are we supposed to access the base interface when using interface inheritance? I've looked through the examples and the generated rust code for my interfaces but I dont see an obvious solution. Is this actually supported in the rust implementation?

john-hern avatar Sep 17 '19 12:09 john-hern

To upcast a Client, you need to manually construct a new Client, like this:

https://github.com/capnproto/capnproto-rust/blob/96f2f7894fbbf55e741c39b183a824e27ddbcc3e/capnp-rpc/test/test.rs#L486

Notice there are no restrictions on what interface type you cast to. I.e. this doesn't only support upcasting. If you cast to something that the underlying object does not actually implement, you'll get "unimplemented" errors on method calls.

There are probably a lot of ways that we could make this better, but working on this has not been high on my priority list.


Another time you might want access to the base class is when you're implementing a Server -- you might want to call a method on a parent interface of Self. That's currently not supported. The plan for it is https://github.com/capnproto/capnproto-rust/issues/87.

dwrensha avatar Sep 17 '19 12:09 dwrensha

Thanks for such a quick reply! With this information I was able to unblock myself. Appreciate it.

john-hern avatar Sep 17 '19 14:09 john-hern

I'm running into this problem as well.

I'm trying to use https://github.com/NyanCAD/SimServer/blob/main/Simulator.capnp with this library. It has a Simulator(Cmd) interface that is generic with the interface it returns.

When using this from Python, lack of proper generics made me define concrete simulator interfaces that inherit from the generic ones. Concrete simulators use multiple inheritance to express the supported commands, like so

interface Xyce extends(Simulator(Run)) { }
interface NgspiceCommands extends(Run, Tran, Op, Ac) {}
interface Ngspice extends(Simulator(NgspiceCommands)) { }

In Rust I have the opposite problem, where it seems my Xyce and Ngspice client interfaces don't have any methods at all. So I think I can use the generic Simulator(Cmd) in Rust, but it's not clear how I can support multiple commands.

let sim: simulator::Client<tran::Owned> = rpc_system.bootstrap(rpc_twoparty_capnp::Side::Server)

This seems to be the way to instantiate a simulator supporting a single command. (I tried tran::Client first which gave confusing errors)

Would I have to do something like this every time I want to call a different command?

 let sim_run = simulator::Client { client: sim.clone().client };

pepijndevos avatar Apr 30 '21 09:04 pepijndevos

The workaround suggested here currently doesn't work if the super interface has generic parameters, as capnproto-rust will add a private PhantomData member that prevents the Client from being constructed.

As a workaround for the workaround, you can go via FromClientHook::new:

use capnp::capability::FromClientHook as _;
let super_interface_client = schema_capnp::super_interface::Client::new(sub_interface_client.client.hook);

Twey avatar Nov 24 '21 02:11 Twey

Perhaps it would make this slightly more ergonomic to provide impls of From<sub_interface::Client> for super_interface::Client? That should at least let you just do sim.clone() instead of having to wrap & unwrap the client.

zenhack avatar Nov 24 '21 19:11 zenhack

I think that I am running into a similar issue. I want to have one common implementation for the communication with the server that is shared across multiple sub clients.

The schema looks like this: engine.capnp:

interface BasePlugin {
....
}
interface EngineBasePluginRegistration {}

pluginone.capnp:

using Engine = import "engine.capnp";

interface PluginOne extends(Engine.BasePlugin) {

    registerPluginOne @0 (engine_plugin_one :EnginePluginOne) -> (registration :Engine.EngineBasePluginRegistration);

    interface EnginePluginOne extends(Engine.EngineBasePlugin) {
        bla @0 () -> (bla :Text);
    }

}

Now, the problem is that I don't know how to start the RPC server in such a way that I can handle incoming connections from different clients (e.g., plugin-one, plugin-two). If I use base_plugin::Client: let capnp_client: base_plugin::Client = capnp_rpc::new_client(CapnpMyImpl::new()); when creating the RPC system on the server-side, I get a "method not implemented" error when calling the registerPluginOne method (note that CapnpMyImpl implements base_plugin::Server and plugin_one::Server etc.). Of course, changing the type of capnp_client to plugin_one::Client will make it work for the register_plugin_one method, but not for other plugins. Is such a modular approach using interface inheritance actually supported? What could be an alternative (e.g., creating one RPC system for each plugin)?

MatthiasEckhart avatar Dec 22 '21 13:12 MatthiasEckhart

It's difficult to provide advice about that plugin system without seeing more about how it's intended to be used.

Depending on what you're trying to accomplish, one thing that could help is having your main server interface be separated from plugins, like this:

interface PluginServer {
   loadPlugin @0 (pluginId : UInt64) -> (plugin : Engine.BasePlugin);
}

Then the called could downcast the returned plugin.

I don't know how to start the RPC server in such a way that I can handle incoming connections from different clients (e.g., plugin-one, plugin-two).

Do you statically know the list of all plugins that you want to support? Or does the server need to dynamically support new plugins that somehow get registered at run-time?

dwrensha avatar Dec 27 '21 18:12 dwrensha

Thank you @dwrensha, I really appreciate your help.

It's difficult to provide advice about that plugin system without seeing more about how it's intended to be used.

Depending on what you're trying to accomplish, one thing that could help is having your main server interface be separated from plugins, like this:

interface PluginServer {
   loadPlugin @0 (pluginId : UInt64) -> (plugin : Engine.BasePlugin);
}

Then the called could downcast the returned plugin.

The idea is that I have one server and multiple plugins that are statically known by the server. Each of these plugins use a common implementation for managing the connection to the server, but call a different 'registration' function. So, when starting a plugin, the function for handling the communication with the server from the common dependency is called with a pointer to a function of the plugin that executes the concrete 'register plugin' remote method (i.e., the plugins initiate the connection to the server to register themselves).

I don't know how to start the RPC server in such a way that I can handle incoming connections from different clients (e.g., plugin-one, plugin-two).

Do you statically know the list of all plugins that you want to support? Or does the server need to dynamically support new plugins that somehow get registered at run-time?

Yes, the server statically knows the list of supported plugins (no need to dynamically support new plugins). As the plugins implement the Server traits of both engine_base_plugin and plugin_<one>::engine_plugin_<one> (e.g., for the first plugin) I think that the plugins should be able to handle invocations of remote methods correctly.

However, it seems that the problem is on the server side: Here, I only create a base_plugin::Client and not a client for a concrete plugin (e.g., plugin_one::Client), leading to the issue that I described above. I expected that accepting connections from these 'generic' clients (interfaces of concrete plugins extend this base plugin interface), would also allow me to handle invocations of remote methods for registering concrete plugins (i.e., the server implements the plugin_<one>::Server traits).

MatthiasEckhart avatar Dec 28 '21 12:12 MatthiasEckhart

So it sounds like your server implements all plugin interfaces? One thing that might work is to define a new interface that extends all of the plugin interfaces:

interface AllPlugins extends (PluginOne, PluginTwo) {}

dwrensha avatar Dec 28 '21 13:12 dwrensha

So it sounds like your server implements all plugin interfaces? One thing that might work is to define a new interface that extends all of the plugin interfaces:

interface AllPlugins extends (PluginOne, PluginTwo) {}

Thank you so much! That solved my problem.

MatthiasEckhart avatar Dec 28 '21 17:12 MatthiasEckhart