yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
canvas_automation_service.cc
Go to the documentation of this file.
2
3#ifdef YAZE_WITH_GRPC
4
6
7#include <grpcpp/grpcpp.h>
8
12#include "protos/canvas_automation.grpc.pb.h"
13#include "protos/canvas_automation.pb.h"
15
16namespace yaze {
17
18namespace {
19
20// Helper to convert absl::Status to grpc::Status
21grpc::Status ConvertStatus(const absl::Status& status) {
22 if (status.ok()) {
23 return grpc::Status::OK;
24 }
25
26 grpc::StatusCode code;
27 switch (status.code()) {
28 case absl::StatusCode::kNotFound:
29 code = grpc::StatusCode::NOT_FOUND;
30 break;
31 case absl::StatusCode::kInvalidArgument:
32 code = grpc::StatusCode::INVALID_ARGUMENT;
33 break;
34 case absl::StatusCode::kFailedPrecondition:
35 code = grpc::StatusCode::FAILED_PRECONDITION;
36 break;
37 case absl::StatusCode::kOutOfRange:
38 code = grpc::StatusCode::OUT_OF_RANGE;
39 break;
40 case absl::StatusCode::kUnimplemented:
41 code = grpc::StatusCode::UNIMPLEMENTED;
42 break;
43 case absl::StatusCode::kInternal:
44 code = grpc::StatusCode::INTERNAL;
45 break;
46 case absl::StatusCode::kUnavailable:
47 code = grpc::StatusCode::UNAVAILABLE;
48 break;
49 // Returned by UiThreadQueue when the UI thread is too slow or exiting.
50 case absl::StatusCode::kDeadlineExceeded:
51 code = grpc::StatusCode::DEADLINE_EXCEEDED;
52 break;
53 case absl::StatusCode::kCancelled:
54 code = grpc::StatusCode::CANCELLED;
55 break;
56 default:
57 code = grpc::StatusCode::UNKNOWN;
58 break;
59 }
60
61 return grpc::Status(
62 code, std::string(status.message().data(), status.message().size()));
63}
64
65} // namespace
66
67void CanvasAutomationServiceImpl::RegisterCanvas(const std::string& canvas_id,
68 gui::Canvas* canvas) {
69 canvases_[canvas_id] = canvas;
70}
71
72void CanvasAutomationServiceImpl::RegisterOverworldEditor(
73 const std::string& canvas_id, editor::OverworldEditor* editor) {
74 overworld_editors_[canvas_id] = editor;
75}
76
77gui::Canvas* CanvasAutomationServiceImpl::GetCanvas(
78 const std::string& canvas_id) {
79 auto it = canvases_.find(canvas_id);
80 if (it != canvases_.end()) {
81 return it->second;
82 }
83 return nullptr;
84}
85
86editor::OverworldEditor* CanvasAutomationServiceImpl::GetOverworldEditor(
87 const std::string& canvas_id) {
88 auto it = overworld_editors_.find(canvas_id);
89 if (it != overworld_editors_.end()) {
90 return it->second;
91 }
92 return nullptr;
93}
94
95// ============================================================================
96// Tile Operations
97// ============================================================================
98
99absl::Status CanvasAutomationServiceImpl::SetTile(
100 const proto::SetTileRequest* request, proto::SetTileResponse* response) {
101 auto* canvas = GetCanvas(request->canvas_id());
102 if (!canvas) {
103 response->set_success(false);
104 response->set_error("Canvas not found: " + request->canvas_id());
105 return absl::NotFoundError(response->error());
106 }
107
108 auto* api = canvas->GetAutomationAPI();
109 bool success = api->SetTileAt(request->x(), request->y(), request->tile_id());
110
111 response->set_success(success);
112 if (!success) {
113 response->set_error(
114 "Failed to set tile - out of bounds or callback failed");
115 }
116
117 return absl::OkStatus();
118}
119
120absl::Status CanvasAutomationServiceImpl::GetTile(
121 const proto::GetTileRequest* request, proto::GetTileResponse* response) {
122 auto* canvas = GetCanvas(request->canvas_id());
123 if (!canvas) {
124 response->set_success(false);
125 response->set_error("Canvas not found: " + request->canvas_id());
126 return absl::NotFoundError(response->error());
127 }
128
129 auto* api = canvas->GetAutomationAPI();
130 int tile_id = api->GetTileAt(request->x(), request->y());
131
132 if (tile_id >= 0) {
133 response->set_tile_id(tile_id);
134 response->set_success(true);
135 } else {
136 response->set_success(false);
137 response->set_error("Tile not found - out of bounds or no callback set");
138 }
139
140 return absl::OkStatus();
141}
142
143absl::Status CanvasAutomationServiceImpl::SetTiles(
144 const proto::SetTilesRequest* request, proto::SetTilesResponse* response) {
145 auto* canvas = GetCanvas(request->canvas_id());
146 if (!canvas) {
147 response->set_success(false);
148 response->set_error("Canvas not found: " + request->canvas_id());
149 return absl::NotFoundError(response->error());
150 }
151
152 auto* api = canvas->GetAutomationAPI();
153
154 std::vector<std::tuple<int, int, int>> tiles;
155 for (const auto& tile : request->tiles()) {
156 tiles.push_back({tile.x(), tile.y(), tile.tile_id()});
157 }
158
159 bool success = api->SetTiles(tiles);
160 response->set_success(success);
161 response->set_tiles_painted(tiles.size());
162
163 return absl::OkStatus();
164}
165
166// ============================================================================
167// Selection Operations
168// ============================================================================
169
170absl::Status CanvasAutomationServiceImpl::SelectTile(
171 const proto::SelectTileRequest* request,
172 proto::SelectTileResponse* response) {
173 auto* canvas = GetCanvas(request->canvas_id());
174 if (!canvas) {
175 response->set_success(false);
176 response->set_error("Canvas not found: " + request->canvas_id());
177 return absl::NotFoundError(response->error());
178 }
179
180 auto* api = canvas->GetAutomationAPI();
181 api->SelectTile(request->x(), request->y());
182 response->set_success(true);
183
184 return absl::OkStatus();
185}
186
187absl::Status CanvasAutomationServiceImpl::SelectTileRect(
188 const proto::SelectTileRectRequest* request,
189 proto::SelectTileRectResponse* response) {
190 auto* canvas = GetCanvas(request->canvas_id());
191 if (!canvas) {
192 response->set_success(false);
193 response->set_error("Canvas not found: " + request->canvas_id());
194 return absl::NotFoundError(response->error());
195 }
196
197 auto* api = canvas->GetAutomationAPI();
198 const auto& rect = request->rect();
199 api->SelectTileRect(rect.x1(), rect.y1(), rect.x2(), rect.y2());
200
201 auto selection = api->GetSelection();
202 response->set_success(true);
203 response->set_tiles_selected(selection.selected_tiles.size());
204
205 return absl::OkStatus();
206}
207
208absl::Status CanvasAutomationServiceImpl::GetSelection(
209 const proto::GetSelectionRequest* request,
210 proto::GetSelectionResponse* response) {
211 auto* canvas = GetCanvas(request->canvas_id());
212 if (!canvas) {
213 return absl::NotFoundError("Canvas not found: " + request->canvas_id());
214 }
215
216 auto* api = canvas->GetAutomationAPI();
217 auto selection = api->GetSelection();
218
219 response->set_has_selection(selection.has_selection);
220
221 for (const auto& tile : selection.selected_tiles) {
222 auto* coord = response->add_selected_tiles();
223 coord->set_x(static_cast<int>(tile.x));
224 coord->set_y(static_cast<int>(tile.y));
225 }
226
227 auto* start = response->mutable_selection_start();
228 start->set_x(static_cast<int>(selection.selection_start.x));
229 start->set_y(static_cast<int>(selection.selection_start.y));
230
231 auto* end = response->mutable_selection_end();
232 end->set_x(static_cast<int>(selection.selection_end.x));
233 end->set_y(static_cast<int>(selection.selection_end.y));
234
235 return absl::OkStatus();
236}
237
238absl::Status CanvasAutomationServiceImpl::ClearSelection(
239 const proto::ClearSelectionRequest* request,
240 proto::ClearSelectionResponse* response) {
241 auto* canvas = GetCanvas(request->canvas_id());
242 if (!canvas) {
243 response->set_success(false);
244 return absl::NotFoundError("Canvas not found: " + request->canvas_id());
245 }
246
247 auto* api = canvas->GetAutomationAPI();
248 api->ClearSelection();
249 response->set_success(true);
250
251 return absl::OkStatus();
252}
253
254// ============================================================================
255// View Operations
256// ============================================================================
257
258absl::Status CanvasAutomationServiceImpl::ScrollToTile(
259 const proto::ScrollToTileRequest* request,
260 proto::ScrollToTileResponse* response) {
261 auto* canvas = GetCanvas(request->canvas_id());
262 if (!canvas) {
263 response->set_success(false);
264 response->set_error("Canvas not found: " + request->canvas_id());
265 return absl::NotFoundError(response->error());
266 }
267
268 auto* api = canvas->GetAutomationAPI();
269 api->ScrollToTile(request->x(), request->y(), request->center());
270 response->set_success(true);
271
272 return absl::OkStatus();
273}
274
275absl::Status CanvasAutomationServiceImpl::CenterOn(
276 const proto::CenterOnRequest* request, proto::CenterOnResponse* response) {
277 auto* canvas = GetCanvas(request->canvas_id());
278 if (!canvas) {
279 response->set_success(false);
280 response->set_error("Canvas not found: " + request->canvas_id());
281 return absl::NotFoundError(response->error());
282 }
283
284 auto* api = canvas->GetAutomationAPI();
285 api->CenterOn(request->x(), request->y());
286 response->set_success(true);
287
288 return absl::OkStatus();
289}
290
291absl::Status CanvasAutomationServiceImpl::SetZoom(
292 const proto::SetZoomRequest* request, proto::SetZoomResponse* response) {
293 auto* canvas = GetCanvas(request->canvas_id());
294 if (!canvas) {
295 response->set_success(false);
296 response->set_error("Canvas not found: " + request->canvas_id());
297 return absl::NotFoundError(response->error());
298 }
299
300 auto* api = canvas->GetAutomationAPI();
301 api->SetZoom(request->zoom());
302
303 float actual_zoom = api->GetZoom();
304 response->set_success(true);
305 response->set_actual_zoom(actual_zoom);
306
307 return absl::OkStatus();
308}
309
310absl::Status CanvasAutomationServiceImpl::GetZoom(
311 const proto::GetZoomRequest* request, proto::GetZoomResponse* response) {
312 auto* canvas = GetCanvas(request->canvas_id());
313 if (!canvas) {
314 return absl::NotFoundError("Canvas not found: " + request->canvas_id());
315 }
316
317 auto* api = canvas->GetAutomationAPI();
318 response->set_zoom(api->GetZoom());
319
320 return absl::OkStatus();
321}
322
323// ============================================================================
324// Query Operations
325// ============================================================================
326
327absl::Status CanvasAutomationServiceImpl::GetDimensions(
328 const proto::GetDimensionsRequest* request,
329 proto::GetDimensionsResponse* response) {
330 auto* canvas = GetCanvas(request->canvas_id());
331 if (!canvas) {
332 return absl::NotFoundError("Canvas not found: " + request->canvas_id());
333 }
334
335 auto* api = canvas->GetAutomationAPI();
336 auto dims = api->GetDimensions();
337
338 auto* proto_dims = response->mutable_dimensions();
339 proto_dims->set_width_tiles(dims.width_tiles);
340 proto_dims->set_height_tiles(dims.height_tiles);
341 proto_dims->set_tile_size(dims.tile_size);
342
343 return absl::OkStatus();
344}
345
346absl::Status CanvasAutomationServiceImpl::GetVisibleRegion(
347 const proto::GetVisibleRegionRequest* request,
348 proto::GetVisibleRegionResponse* response) {
349 auto* canvas = GetCanvas(request->canvas_id());
350 if (!canvas) {
351 return absl::NotFoundError("Canvas not found: " + request->canvas_id());
352 }
353
354 auto* api = canvas->GetAutomationAPI();
355 auto region = api->GetVisibleRegion();
356
357 auto* proto_region = response->mutable_region();
358 proto_region->set_min_x(region.min_x);
359 proto_region->set_min_y(region.min_y);
360 proto_region->set_max_x(region.max_x);
361 proto_region->set_max_y(region.max_y);
362
363 return absl::OkStatus();
364}
365
366absl::Status CanvasAutomationServiceImpl::IsTileVisible(
367 const proto::IsTileVisibleRequest* request,
368 proto::IsTileVisibleResponse* response) {
369 auto* canvas = GetCanvas(request->canvas_id());
370 if (!canvas) {
371 return absl::NotFoundError("Canvas not found: " + request->canvas_id());
372 }
373
374 auto* api = canvas->GetAutomationAPI();
375 response->set_is_visible(api->IsTileVisible(request->x(), request->y()));
376
377 return absl::OkStatus();
378}
379
380// ============================================================================
381// Coordinate Mapping Operations
382// ============================================================================
383
384absl::Status CanvasAutomationServiceImpl::ScreenToTile(
385 const proto::ScreenToTileRequest* request,
386 proto::ScreenToTileResponse* response) {
387 auto* canvas = GetCanvas(request->canvas_id());
388 if (!canvas) {
389 response->set_success(false);
390 response->set_error("Canvas not found: " + request->canvas_id());
391 return absl::NotFoundError(response->error());
392 }
393
394 // Create a CoordinateMapper and configure it from canvas state
395 gui::CoordinateMapper mapper;
396 mapper.SetCanvasId(request->canvas_id());
397
398 // Get canvas geometry from current state
399 gui::CanvasGeometry geometry;
400 geometry.canvas_p0 = canvas->zero_point();
401 geometry.canvas_sz = canvas->canvas_size();
402 geometry.canvas_p1 = ImVec2(geometry.canvas_p0.x + geometry.canvas_sz.x,
403 geometry.canvas_p0.y + geometry.canvas_sz.y);
404 geometry.scrolling = canvas->scrolling();
405 mapper.SetGeometry(geometry);
406 mapper.SetScale(canvas->global_scale());
407
408 // Configure tile settings from canvas
409 gui::CoordinateMapperConfig config;
410 config.tile_size = canvas->GetGridStep();
411 config.tiles_per_row =
412 static_cast<int>(canvas->canvas_size().x / config.tile_size);
413 config.tiles_per_col =
414 static_cast<int>(canvas->canvas_size().y / config.tile_size);
415 config.use_tile16 = (config.tile_size == 16.0f);
416 mapper.SetConfig(config);
417
418 // Perform the coordinate mapping
419 auto result = mapper.ScreenToTile(request->screen_x(), request->screen_y());
420
421 // Populate response
422 response->set_success(true);
423 response->set_screen_x(result.screen_x);
424 response->set_screen_y(result.screen_y);
425 response->set_canvas_x(result.canvas_x);
426 response->set_canvas_y(result.canvas_y);
427 response->set_content_x(result.content_x);
428 response->set_content_y(result.content_y);
429 response->set_in_canvas_bounds(result.in_canvas_bounds);
430
431 // Populate tile hit info
432 auto* tile_info = response->mutable_tile_info();
433 tile_info->set_tile_id(result.tile_info.tile_id);
434 tile_info->set_tile16_index(result.tile_info.tile16_index);
435 tile_info->set_rom_offset(result.tile_info.rom_offset);
436 tile_info->set_palette_group(result.tile_info.palette_group);
437 tile_info->set_palette_index(result.tile_info.palette_index);
438 tile_info->set_tile_origin_x(result.tile_info.tile_origin_px.x);
439 tile_info->set_tile_origin_y(result.tile_info.tile_origin_px.y);
440 tile_info->set_tile_width(result.tile_info.tile_size_px.x);
441 tile_info->set_tile_height(result.tile_info.tile_size_px.y);
442 tile_info->set_is_valid(result.tile_info.is_valid);
443 tile_info->set_map_x(result.tile_info.map_x);
444 tile_info->set_map_y(result.tile_info.map_y);
445 tile_info->set_local_map_id(result.tile_info.local_map_id);
446
447 return absl::OkStatus();
448}
449
450// ============================================================================
451// gRPC Service Wrapper
452// ============================================================================
453
461class CanvasAutomationServiceGrpc final
462 : public proto::CanvasAutomation::Service {
463 public:
464 explicit CanvasAutomationServiceGrpc(CanvasAutomationServiceImpl* impl)
465 : impl_(impl) {}
466
467 // Tile Operations
468 grpc::Status SetTile(grpc::ServerContext* context,
469 const proto::SetTileRequest* request,
470 proto::SetTileResponse* response) override {
471 return OnUiThread([&]() { return impl_->SetTile(request, response); });
472 }
473
474 grpc::Status GetTile(grpc::ServerContext* context,
475 const proto::GetTileRequest* request,
476 proto::GetTileResponse* response) override {
477 return OnUiThread([&]() { return impl_->GetTile(request, response); });
478 }
479
480 grpc::Status SetTiles(grpc::ServerContext* context,
481 const proto::SetTilesRequest* request,
482 proto::SetTilesResponse* response) override {
483 return OnUiThread([&]() { return impl_->SetTiles(request, response); });
484 }
485
486 // Selection Operations
487 grpc::Status SelectTile(grpc::ServerContext* context,
488 const proto::SelectTileRequest* request,
489 proto::SelectTileResponse* response) override {
490 return OnUiThread([&]() { return impl_->SelectTile(request, response); });
491 }
492
493 grpc::Status SelectTileRect(
494 grpc::ServerContext* context, const proto::SelectTileRectRequest* request,
495 proto::SelectTileRectResponse* response) override {
496 return OnUiThread(
497 [&]() { return impl_->SelectTileRect(request, response); });
498 }
499
500 grpc::Status GetSelection(grpc::ServerContext* context,
501 const proto::GetSelectionRequest* request,
502 proto::GetSelectionResponse* response) override {
503 return OnUiThread([&]() { return impl_->GetSelection(request, response); });
504 }
505
506 grpc::Status ClearSelection(
507 grpc::ServerContext* context, const proto::ClearSelectionRequest* request,
508 proto::ClearSelectionResponse* response) override {
509 return OnUiThread(
510 [&]() { return impl_->ClearSelection(request, response); });
511 }
512
513 // View Operations
514 grpc::Status ScrollToTile(grpc::ServerContext* context,
515 const proto::ScrollToTileRequest* request,
516 proto::ScrollToTileResponse* response) override {
517 return OnUiThread([&]() { return impl_->ScrollToTile(request, response); });
518 }
519
520 grpc::Status CenterOn(grpc::ServerContext* context,
521 const proto::CenterOnRequest* request,
522 proto::CenterOnResponse* response) override {
523 return OnUiThread([&]() { return impl_->CenterOn(request, response); });
524 }
525
526 grpc::Status SetZoom(grpc::ServerContext* context,
527 const proto::SetZoomRequest* request,
528 proto::SetZoomResponse* response) override {
529 return OnUiThread([&]() { return impl_->SetZoom(request, response); });
530 }
531
532 grpc::Status GetZoom(grpc::ServerContext* context,
533 const proto::GetZoomRequest* request,
534 proto::GetZoomResponse* response) override {
535 return OnUiThread([&]() { return impl_->GetZoom(request, response); });
536 }
537
538 // Query Operations
539 grpc::Status GetDimensions(grpc::ServerContext* context,
540 const proto::GetDimensionsRequest* request,
541 proto::GetDimensionsResponse* response) override {
542 return OnUiThread(
543 [&]() { return impl_->GetDimensions(request, response); });
544 }
545
546 grpc::Status GetVisibleRegion(
547 grpc::ServerContext* context,
548 const proto::GetVisibleRegionRequest* request,
549 proto::GetVisibleRegionResponse* response) override {
550 return OnUiThread(
551 [&]() { return impl_->GetVisibleRegion(request, response); });
552 }
553
554 grpc::Status IsTileVisible(grpc::ServerContext* context,
555 const proto::IsTileVisibleRequest* request,
556 proto::IsTileVisibleResponse* response) override {
557 return OnUiThread(
558 [&]() { return impl_->IsTileVisible(request, response); });
559 }
560
561 // Coordinate Mapping Operations
562 grpc::Status ScreenToTile(grpc::ServerContext* context,
563 const proto::ScreenToTileRequest* request,
564 proto::ScreenToTileResponse* response) override {
565 return OnUiThread([&]() { return impl_->ScreenToTile(request, response); });
566 }
567
568 private:
569 // RPCs arrive on gRPC threads, but the canvas registry and the canvases
570 // belong to the UI thread, which registers canvases and draws them every
571 // frame. Every call runs there, reads included, because a read can see a
572 // canvas in the middle of an update. Capturing by reference is safe:
573 // RunOnUiThread never runs a task after it has returned DeadlineExceeded.
574 template <typename Fn>
575 static grpc::Status OnUiThread(Fn fn) {
576 return ConvertStatus(
577 util::UiThreadQueue::Get().RunOnUiThread(std::move(fn)));
578 }
579
580 CanvasAutomationServiceImpl* impl_;
581};
582
583// Factory function to create the gRPC wrapper
584// Returns as base grpc::Service* to avoid incomplete type issues in headers
585std::unique_ptr<grpc::Service> CreateCanvasAutomationServiceGrpc(
586 CanvasAutomationServiceImpl* impl) {
587 return std::make_unique<CanvasAutomationServiceGrpc>(impl);
588}
589
590} // namespace yaze
591
592#endif // YAZE_WITH_GRPC