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:
scawful
2025-10-05 23:59:38 -04:00
parent 42217a388f
commit 6982d63173
4 changed files with 240 additions and 24 deletions

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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