An fuchsia::modular::Agent is a Peridot component that runs without any direct user interaction. The lifetime of a single fuchsia::modular::Agent instance is bounded by its session. It can be shared by mods across many stories. In addition to the capabilities provided to all Peridot components via fuchsia::modular::ComponentContext, an fuchsia::modular::Agent is given additional capabilities via its fuchsia::modular::AgentContext.
See the simple directory for a complete implementation of the fuchsia::modular::Agent described here.
SimpleAgent is an fuchsia::modular::Agent that periodically writes a simple message to a fuchsia::modular::MessageQueue (a common communication channel).
SimpleAgent implements the Simple FIDL protocol which exposes the ability to control which fuchsia::modular::MessageQueue the messages will be sent to.
library simple;
[Discoverable]
protocol Simple {
// Provides the Simple protocol with a message queue to which
// messages will be written periodically.
1: SetMessageQueue(string queue_token);
};
The first step to writing an fuchsia::modular::Agent is implementing the initializer:
#include <lib/app_driver/cpp/agent_driver.h> class SimpleAgent { public: SimpleAgent(AgentHost* const agent_host) { ... } };
The AgentHost parameter provides the fuchsia::modular::Agent with an StartupContext and an fuchsia::modular::AgentContext.
StartupContextStartupContext gives the fuchsia::modular::Agent access to services provided to it by other system components via incoming_services. It also allows the fuchsia::modular::Agent to provide services to other components via outgoing_services.
fuchsia::modular::AgentContextfuchsia::modular::AgentContext is a protocol that is exposed to all Agents. For example, it allows agents to schedule Tasks that will be executed at specific intervals.
fuchsia::modular::AgentContext also gives fuchsia::modular::Agents access to fuchsia::modular::ComponentContext which is a protocol that is exposed to all Peridot components (i.e. fuchsia::modular::Agent and Module). For example, fuchsia::modular::ComponentContext provides access to Ledger, Peridot's cross-device storage solution.
Simple ProtocolIn order for the SimpleAgent to advertise the Simple protocol to the system it needs to provide it in its outgoing_services.
SimpleAgent(AgentHost* const agent_host) { services_.AddService<Simple>( [this](fidl::InterfaceRequest<Simple> request) { simple_impl_->Connect(std::move(request)); }); } void Connect( fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> outgoing_services) { services_.AddBinding(std::move(outgoing_services)); } private: // The services namespace that the `Simple` service is added to. component::ServiceNamespace services_; // The implementation of the Simple service. std::unique_ptr<SimpleImpl> simple_impl_;
In the initializer above, SimpleAgent adds the Simple service to its ServiceNamespace. When SimpleAgent receives a Connect call, it needs to bind the handle held by request to the concrete implementation in services_. It does this by calling AddBinding(std::move(outgoing_services)).
Now, when a component connects to the SimpleAgent, it will be able to connect to the SimpleInterface and call methods on it. Those method calls will be delegated to the simple_impl_ per the callback given to AddService().
class SimpleImpl : Simple { SimpleImpl(); ~SimpleImpl(); void Connect(fidl::InterfaceRequest<Simple> request); std::string message_queue_token() const { return token_; } private: // |Simple| protocol method. void SetMessageQueue(fidl::StringPtr queue_token); // The bindings to the Simple service. fidl::BindingSet<Simple> bindings_; // The current message queue token. std::string token_; };
The SimpleImpl could be part of the SimpleAgent class, but it's good practice to separate out the implementation of an fuchsia::modular::Agent from the implementation(s) of the protocol(s) it provides.
int main(int /*argc*/, const char** /*argv*/) { async::Loop loop(&kAsyncLoopConfigAttachToThread); auto context = component::StartupContext::CreateFromStartupInfo(); modular::AgentDriver<simple_agent::SimpleAgent> driver( context.get(), [&loop] { loop.Quit(); }); loop.Run(); return 0; }
AgentDriver is a helper class that helps manage the fuchsia::modular::Agent lifecycle. Here it is given a newly created StartupContext and a callback that will be executed when the fuchsia::modular::Agent exits. AgentDriver requires SimpleAgent to implement three methods, one of which is Connect which was shown above.
The other two are:
void RunTask(const fidl::StringPtr& task_id, fit::function<void()> done) { done(); } void Terminate(fit::function<void()> done) { done(); }
RunTask is called when a task, scheduled through fuchsia::modular::AgentContext's ScheduleTask, is triggered.
Terminate is called when the framework requests fuchsia::modular::Agent to exit gracefully.
To connect to the SimpleAgent from a different component:
// The agent is guaranteed to stay alive as long as |agent_controller| stays in scope. fuchsia::modular::AgentControllerPtr agent_controller; fuchsia::sys::ServiceProviderPtr agent_services; SimpleServicePtr agent_service; component_context->ConnectToAgent(agent_url, agent_services.NewRequest(), agent_controller.NewRequest()); ConnectToService(agent_services.get(), agent_service.NewRequest()); agent_service->SetMessageQueue(...);
Here the component context is asked to connect to the fuchsia::modular::Agent at agent_url, and is given a request for the services that the SimpleAgent will provide via agent_services, and a controller for the fuchsia::modular::Agent via agent_controller.
Then the client connects to the Simple protocol by invoking ConnectToService with a request for a new SimpleServicePtr. This protocol pointer can be used immediately to provide the agent with the token for the fuchsia::modular::MessageQueue to send messages to.
See the SimpleModule guide for a more in-depth example.