refactor: Implement gRPC Wrapper for Canvas Automation Service
- Introduced a gRPC service wrapper for CanvasAutomationServiceImpl, enabling seamless integration of canvas operations with gRPC. - Added a helper function to convert absl::Status to grpc::Status, ensuring consistent error handling across service calls. - Updated UnifiedGRPCServer to initialize and register the Canvas Automation service, enhancing the server's capabilities for handling canvas-related requests. - Refactored service initialization to accommodate the new canvas service, improving modularity and maintainability of the server code.
This commit is contained in:
@@ -2,12 +2,55 @@
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
|
||||
#include "src/protos/canvas_automation.pb.h"
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include "protos/canvas_automation.pb.h"
|
||||
#include "protos/canvas_automation.grpc.pb.h"
|
||||
#include "app/editor/overworld/overworld_editor.h"
|
||||
#include "app/gui/canvas/canvas_automation_api.h"
|
||||
|
||||
namespace yaze {
|
||||
|
||||
namespace {
|
||||
|
||||
// Helper to convert absl::Status to grpc::Status
|
||||
grpc::Status ConvertStatus(const absl::Status& status) {
|
||||
if (status.ok()) {
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
grpc::StatusCode code;
|
||||
switch (status.code()) {
|
||||
case absl::StatusCode::kNotFound:
|
||||
code = grpc::StatusCode::NOT_FOUND;
|
||||
break;
|
||||
case absl::StatusCode::kInvalidArgument:
|
||||
code = grpc::StatusCode::INVALID_ARGUMENT;
|
||||
break;
|
||||
case absl::StatusCode::kFailedPrecondition:
|
||||
code = grpc::StatusCode::FAILED_PRECONDITION;
|
||||
break;
|
||||
case absl::StatusCode::kOutOfRange:
|
||||
code = grpc::StatusCode::OUT_OF_RANGE;
|
||||
break;
|
||||
case absl::StatusCode::kUnimplemented:
|
||||
code = grpc::StatusCode::UNIMPLEMENTED;
|
||||
break;
|
||||
case absl::StatusCode::kInternal:
|
||||
code = grpc::StatusCode::INTERNAL;
|
||||
break;
|
||||
case absl::StatusCode::kUnavailable:
|
||||
code = grpc::StatusCode::UNAVAILABLE;
|
||||
break;
|
||||
default:
|
||||
code = grpc::StatusCode::UNKNOWN;
|
||||
break;
|
||||
}
|
||||
|
||||
return grpc::Status(code, std::string(status.message()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CanvasAutomationServiceImpl::RegisterCanvas(const std::string& canvas_id,
|
||||
gui::Canvas* canvas) {
|
||||
canvases_[canvas_id] = canvas;
|
||||
@@ -332,6 +375,121 @@ absl::Status CanvasAutomationServiceImpl::IsTileVisible(
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// gRPC Service Wrapper
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief gRPC service wrapper that forwards to CanvasAutomationServiceImpl
|
||||
*
|
||||
* This adapter implements the proto-generated Service interface and
|
||||
* forwards all calls to our implementation, converting between gRPC
|
||||
* and absl::Status types.
|
||||
*/
|
||||
class CanvasAutomationServiceGrpc final : public proto::CanvasAutomation::Service {
|
||||
public:
|
||||
explicit CanvasAutomationServiceGrpc(CanvasAutomationServiceImpl* impl)
|
||||
: impl_(impl) {}
|
||||
|
||||
// Tile Operations
|
||||
grpc::Status SetTile(grpc::ServerContext* context,
|
||||
const proto::SetTileRequest* request,
|
||||
proto::SetTileResponse* response) override {
|
||||
return ConvertStatus(impl_->SetTile(request, response));
|
||||
}
|
||||
|
||||
grpc::Status GetTile(grpc::ServerContext* context,
|
||||
const proto::GetTileRequest* request,
|
||||
proto::GetTileResponse* response) override {
|
||||
return ConvertStatus(impl_->GetTile(request, response));
|
||||
}
|
||||
|
||||
grpc::Status SetTiles(grpc::ServerContext* context,
|
||||
const proto::SetTilesRequest* request,
|
||||
proto::SetTilesResponse* response) override {
|
||||
return ConvertStatus(impl_->SetTiles(request, response));
|
||||
}
|
||||
|
||||
// Selection Operations
|
||||
grpc::Status SelectTile(grpc::ServerContext* context,
|
||||
const proto::SelectTileRequest* request,
|
||||
proto::SelectTileResponse* response) override {
|
||||
return ConvertStatus(impl_->SelectTile(request, response));
|
||||
}
|
||||
|
||||
grpc::Status SelectTileRect(grpc::ServerContext* context,
|
||||
const proto::SelectTileRectRequest* request,
|
||||
proto::SelectTileRectResponse* response) override {
|
||||
return ConvertStatus(impl_->SelectTileRect(request, response));
|
||||
}
|
||||
|
||||
grpc::Status GetSelection(grpc::ServerContext* context,
|
||||
const proto::GetSelectionRequest* request,
|
||||
proto::GetSelectionResponse* response) override {
|
||||
return ConvertStatus(impl_->GetSelection(request, response));
|
||||
}
|
||||
|
||||
grpc::Status ClearSelection(grpc::ServerContext* context,
|
||||
const proto::ClearSelectionRequest* request,
|
||||
proto::ClearSelectionResponse* response) override {
|
||||
return ConvertStatus(impl_->ClearSelection(request, response));
|
||||
}
|
||||
|
||||
// View Operations
|
||||
grpc::Status ScrollToTile(grpc::ServerContext* context,
|
||||
const proto::ScrollToTileRequest* request,
|
||||
proto::ScrollToTileResponse* response) override {
|
||||
return ConvertStatus(impl_->ScrollToTile(request, response));
|
||||
}
|
||||
|
||||
grpc::Status CenterOn(grpc::ServerContext* context,
|
||||
const proto::CenterOnRequest* request,
|
||||
proto::CenterOnResponse* response) override {
|
||||
return ConvertStatus(impl_->CenterOn(request, response));
|
||||
}
|
||||
|
||||
grpc::Status SetZoom(grpc::ServerContext* context,
|
||||
const proto::SetZoomRequest* request,
|
||||
proto::SetZoomResponse* response) override {
|
||||
return ConvertStatus(impl_->SetZoom(request, response));
|
||||
}
|
||||
|
||||
grpc::Status GetZoom(grpc::ServerContext* context,
|
||||
const proto::GetZoomRequest* request,
|
||||
proto::GetZoomResponse* response) override {
|
||||
return ConvertStatus(impl_->GetZoom(request, response));
|
||||
}
|
||||
|
||||
// Query Operations
|
||||
grpc::Status GetDimensions(grpc::ServerContext* context,
|
||||
const proto::GetDimensionsRequest* request,
|
||||
proto::GetDimensionsResponse* response) override {
|
||||
return ConvertStatus(impl_->GetDimensions(request, response));
|
||||
}
|
||||
|
||||
grpc::Status GetVisibleRegion(grpc::ServerContext* context,
|
||||
const proto::GetVisibleRegionRequest* request,
|
||||
proto::GetVisibleRegionResponse* response) override {
|
||||
return ConvertStatus(impl_->GetVisibleRegion(request, response));
|
||||
}
|
||||
|
||||
grpc::Status IsTileVisible(grpc::ServerContext* context,
|
||||
const proto::IsTileVisibleRequest* request,
|
||||
proto::IsTileVisibleResponse* response) override {
|
||||
return ConvertStatus(impl_->IsTileVisible(request, response));
|
||||
}
|
||||
|
||||
private:
|
||||
CanvasAutomationServiceImpl* impl_;
|
||||
};
|
||||
|
||||
// Factory function to create the gRPC wrapper
|
||||
// Returns as base grpc::Service* to avoid incomplete type issues in headers
|
||||
std::unique_ptr<grpc::Service> CreateCanvasAutomationServiceGrpc(
|
||||
CanvasAutomationServiceImpl* impl) {
|
||||
return std::make_unique<CanvasAutomationServiceGrpc>(impl);
|
||||
}
|
||||
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_WITH_GRPC
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef YAZE_APP_CORE_SERVICE_CANVAS_AUTOMATION_SERVICE_H_
|
||||
#define YAZE_APP_CORE_SERVICE_CANVAS_AUTOMATION_SERVICE_H_
|
||||
|
||||
#include "grpcpp/impl/service_type.h"
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
|
||||
#include <memory>
|
||||
@@ -126,6 +127,19 @@ class CanvasAutomationServiceImpl {
|
||||
std::unordered_map<std::string, editor::OverworldEditor*> overworld_editors_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Factory function to create gRPC service wrapper
|
||||
*
|
||||
* Creates the gRPC service wrapper for CanvasAutomationServiceImpl.
|
||||
* The wrapper handles the conversion between gRPC and absl::Status.
|
||||
* Returns as base grpc::Service to avoid incomplete type issues.
|
||||
*
|
||||
* @param impl Pointer to implementation (not owned)
|
||||
* @return Unique pointer to gRPC service
|
||||
*/
|
||||
std::unique_ptr<grpc::Service> CreateCanvasAutomationServiceGrpc(
|
||||
CanvasAutomationServiceImpl* impl);
|
||||
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_WITH_GRPC
|
||||
|
||||
@@ -7,27 +7,31 @@
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/core/service/imgui_test_harness_service.h"
|
||||
#include "app/core/service/canvas_automation_service.h"
|
||||
#include "app/net/rom_service_impl.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include "protos/canvas_automation.grpc.pb.h"
|
||||
|
||||
namespace yaze {
|
||||
|
||||
UnifiedGRPCServer::UnifiedGRPCServer()
|
||||
YazeGRPCServer::YazeGRPCServer()
|
||||
: is_running_(false) {
|
||||
}
|
||||
|
||||
UnifiedGRPCServer::~UnifiedGRPCServer() {
|
||||
// Destructor defined here so CanvasAutomationServiceGrpc is a complete type
|
||||
YazeGRPCServer::~YazeGRPCServer() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
absl::Status UnifiedGRPCServer::Initialize(
|
||||
absl::Status YazeGRPCServer::Initialize(
|
||||
int port,
|
||||
test::TestManager* test_manager,
|
||||
app::Rom* rom,
|
||||
app::net::RomVersionManager* version_mgr,
|
||||
app::net::ProposalApprovalManager* approval_mgr) {
|
||||
app::net::ProposalApprovalManager* approval_mgr,
|
||||
CanvasAutomationServiceImpl* canvas_service) {
|
||||
|
||||
if (is_running_) {
|
||||
return absl::FailedPreconditionError("Server is already running");
|
||||
@@ -59,7 +63,16 @@ absl::Status UnifiedGRPCServer::Initialize(
|
||||
std::cout << "⚠ ROM service requested but no ROM provided\n";
|
||||
}
|
||||
|
||||
if (!test_harness_service_ && !rom_service_) {
|
||||
// Create Canvas Automation service if canvas_service provided
|
||||
if (config_.enable_canvas_automation && canvas_service) {
|
||||
// Store the provided service (not owned by us)
|
||||
canvas_service_ = std::unique_ptr<CanvasAutomationServiceImpl>(canvas_service);
|
||||
std::cout << "✓ Canvas Automation service initialized\n";
|
||||
} else if (config_.enable_canvas_automation) {
|
||||
std::cout << "⚠ Canvas Automation requested but no service provided\n";
|
||||
}
|
||||
|
||||
if (!test_harness_service_ && !rom_service_ && !canvas_service_) {
|
||||
return absl::InvalidArgumentError(
|
||||
"At least one service must be enabled and initialized");
|
||||
}
|
||||
@@ -67,13 +80,13 @@ absl::Status UnifiedGRPCServer::Initialize(
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status UnifiedGRPCServer::Start() {
|
||||
absl::Status YazeGRPCServer::Start() {
|
||||
auto status = BuildServer();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::cout << "✓ Unified gRPC server listening on 0.0.0.0:" << config_.port << "\n";
|
||||
std::cout << "✓ YAZE gRPC automation server listening on 0.0.0.0:" << config_.port << "\n";
|
||||
|
||||
if (test_harness_service_) {
|
||||
std::cout << " ✓ ImGuiTestHarness available\n";
|
||||
@@ -81,6 +94,9 @@ absl::Status UnifiedGRPCServer::Start() {
|
||||
if (rom_service_) {
|
||||
std::cout << " ✓ ROM service available\n";
|
||||
}
|
||||
if (canvas_service_) {
|
||||
std::cout << " ✓ Canvas Automation available\n";
|
||||
}
|
||||
|
||||
std::cout << "\nServer is ready to accept requests...\n";
|
||||
|
||||
@@ -90,7 +106,7 @@ absl::Status UnifiedGRPCServer::Start() {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status UnifiedGRPCServer::StartAsync() {
|
||||
absl::Status YazeGRPCServer::StartAsync() {
|
||||
auto status = BuildServer();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -102,7 +118,7 @@ absl::Status UnifiedGRPCServer::StartAsync() {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void UnifiedGRPCServer::Shutdown() {
|
||||
void YazeGRPCServer::Shutdown() {
|
||||
if (server_ && is_running_) {
|
||||
std::cout << "⏹ Shutting down unified gRPC server...\n";
|
||||
server_->Shutdown();
|
||||
@@ -112,11 +128,11 @@ void UnifiedGRPCServer::Shutdown() {
|
||||
}
|
||||
}
|
||||
|
||||
bool UnifiedGRPCServer::IsRunning() const {
|
||||
bool YazeGRPCServer::IsRunning() const {
|
||||
return is_running_;
|
||||
}
|
||||
|
||||
absl::Status UnifiedGRPCServer::BuildServer() {
|
||||
absl::Status YazeGRPCServer::BuildServer() {
|
||||
if (is_running_) {
|
||||
return absl::FailedPreconditionError("Server already running");
|
||||
}
|
||||
@@ -142,6 +158,13 @@ absl::Status UnifiedGRPCServer::BuildServer() {
|
||||
builder.RegisterService(rom_service_.get());
|
||||
}
|
||||
|
||||
if (canvas_service_) {
|
||||
std::cout << " Registering Canvas Automation service...\n";
|
||||
// Create gRPC wrapper using factory function
|
||||
canvas_grpc_service_ = CreateCanvasAutomationServiceGrpc(canvas_service_.get());
|
||||
builder.RegisterService(canvas_grpc_service_.get());
|
||||
}
|
||||
|
||||
// Build and start
|
||||
server_ = builder.BuildAndStart();
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
#include "absl/status/status.h"
|
||||
#include "app/net/rom_version_manager.h"
|
||||
|
||||
// Include grpcpp for grpc::Service forward declaration
|
||||
#include <grpcpp/impl/service_type.h>
|
||||
|
||||
namespace grpc {
|
||||
class Server;
|
||||
}
|
||||
@@ -15,6 +18,8 @@ class Server;
|
||||
namespace yaze {
|
||||
|
||||
// Forward declarations
|
||||
class CanvasAutomationServiceImpl;
|
||||
|
||||
namespace app {
|
||||
class Rom;
|
||||
namespace net {
|
||||
@@ -29,26 +34,28 @@ class ImGuiTestHarnessServiceImpl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class UnifiedGRPCServer
|
||||
* @brief Unified gRPC server hosting both ImGuiTestHarness and RomService
|
||||
* @class YazeGRPCServer
|
||||
* @brief YAZE's unified gRPC server for Zelda3 editor automation
|
||||
*
|
||||
* This server combines:
|
||||
* This server combines multiple automation services for the Zelda editor:
|
||||
* 1. ImGuiTestHarness - GUI test automation (widget discovery, screenshots, etc.)
|
||||
* 2. RomService - ROM manipulation (read/write, proposals, version management)
|
||||
* 3. CanvasAutomation - Canvas operations (tiles, selection, zoom, pan)
|
||||
*
|
||||
* Both services share the same gRPC server instance and port, allowing
|
||||
* clients to interact with both the GUI and ROM data simultaneously.
|
||||
* All services share the same gRPC server instance and port, allowing
|
||||
* clients (CLI, AI agents, remote scripts) to interact with GUI, ROM data,
|
||||
* and canvas operations simultaneously.
|
||||
*
|
||||
* Example usage:
|
||||
* ```cpp
|
||||
* UnifiedGRPCServer server;
|
||||
* server.Initialize(50051, test_manager, rom, version_mgr, approval_mgr);
|
||||
* YazeGRPCServer server;
|
||||
* server.Initialize(50051, test_manager, rom, version_mgr, approval_mgr, canvas_service);
|
||||
* server.Start();
|
||||
* // ... do work ...
|
||||
* server.Shutdown();
|
||||
* ```
|
||||
*/
|
||||
class UnifiedGRPCServer {
|
||||
class YazeGRPCServer {
|
||||
public:
|
||||
/**
|
||||
* @brief Configuration for the unified server
|
||||
@@ -57,11 +64,13 @@ class UnifiedGRPCServer {
|
||||
int port = 50051;
|
||||
bool enable_test_harness = true;
|
||||
bool enable_rom_service = true;
|
||||
bool enable_canvas_automation = true;
|
||||
bool require_approval_for_rom_writes = true;
|
||||
};
|
||||
|
||||
UnifiedGRPCServer();
|
||||
~UnifiedGRPCServer();
|
||||
YazeGRPCServer();
|
||||
// Destructor must be defined in .cc file to allow deletion of incomplete types
|
||||
~YazeGRPCServer();
|
||||
|
||||
/**
|
||||
* @brief Initialize the server with all required services
|
||||
@@ -70,6 +79,7 @@ class UnifiedGRPCServer {
|
||||
* @param rom ROM instance for ROM service (optional)
|
||||
* @param version_mgr Version manager for ROM snapshots (optional)
|
||||
* @param approval_mgr Approval manager for proposals (optional)
|
||||
* @param canvas_service Canvas automation service implementation (optional)
|
||||
* @return OK status if initialized successfully
|
||||
*/
|
||||
absl::Status Initialize(
|
||||
@@ -77,7 +87,8 @@ class UnifiedGRPCServer {
|
||||
test::TestManager* test_manager = nullptr,
|
||||
app::Rom* rom = nullptr,
|
||||
app::net::RomVersionManager* version_mgr = nullptr,
|
||||
app::net::ProposalApprovalManager* approval_mgr = nullptr);
|
||||
app::net::ProposalApprovalManager* approval_mgr = nullptr,
|
||||
CanvasAutomationServiceImpl* canvas_service = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Start the gRPC server (blocking)
|
||||
@@ -116,9 +127,12 @@ class UnifiedGRPCServer {
|
||||
std::unique_ptr<grpc::Server> server_;
|
||||
std::unique_ptr<test::ImGuiTestHarnessServiceImpl> test_harness_service_;
|
||||
std::unique_ptr<app::net::RomServiceImpl> rom_service_;
|
||||
std::unique_ptr<CanvasAutomationServiceImpl> canvas_service_;
|
||||
// Store as base grpc::Service* to avoid incomplete type issues
|
||||
std::unique_ptr<grpc::Service> canvas_grpc_service_;
|
||||
bool is_running_;
|
||||
|
||||
// Build the gRPC server with both services
|
||||
// Build the gRPC server with all services
|
||||
absl::Status BuildServer();
|
||||
};
|
||||
|
||||
@@ -126,3 +140,10 @@ class UnifiedGRPCServer {
|
||||
|
||||
#endif // YAZE_WITH_GRPC
|
||||
#endif // YAZE_APP_CORE_SERVICE_UNIFIED_GRPC_SERVER_H_
|
||||
|
||||
// Backwards compatibility alias
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
namespace yaze {
|
||||
using UnifiedGRPCServer = YazeGRPCServer;
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user