Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessing the super interface using interface inheritance in capnp schema #146

Open
john-hern opened this issue Sep 17, 2019 · 14 comments
Open

Comments

@john-hern
Copy link

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?

@dwrensha
Copy link
Member

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

let client1 = ::test_capnp::test_call_order::Client { client: client.clone().client };

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 #87.

@john-hern
Copy link
Author

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

@pepijndevos
Copy link

pepijndevos commented Apr 30, 2021

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 };

@Twey
Copy link

Twey commented Nov 24, 2021

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);

@zenhack
Copy link

zenhack commented Nov 24, 2021

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.

@MatthiasEckhart
Copy link

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)?

@dwrensha
Copy link
Member

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?

@MatthiasEckhart
Copy link

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).

@dwrensha
Copy link
Member

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) {}

@MatthiasEckhart
Copy link

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.

@tamird
Copy link
Contributor

tamird commented Aug 24, 2023

Is it still recommended practice to manually cast clients to the base interface?

interface Foo {
  getInt @0 () -> (i :UInt64);
}

interface Bar extends(Foo) {}

a Client<Bar> seems to not have any of Foo's methods.

@dwrensha
Copy link
Member

Is it still recommended practice to manually cast clients to the base interface?

Nothing has changed since my previous comment #146 (comment)

@dwrensha
Copy link
Member

#300 made it more convenient to cast between interface client types.

@tamird
Copy link
Contributor

tamird commented Sep 16, 2024

#300 made it more convenient to cast between interface client types.

    /// Casts `self` to another instance of `FromClientHook`. This always succeeds,
    /// but if the underlying capability does not actually implement `T`'s interface,
    /// then method calls will fail with "unimplemented" errors.

it would still be good to have a separate conversion path that is available only when the underlying client is statically known to implement T's interface.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants