13auto level_to_string(level::level_enum level) ->
const char*
34auto parse_min_level(
const std::string&
name, level::level_enum& out) ->
bool
51 if(
name ==
"warn" ||
name ==
"warning")
61 if(
name ==
"critical")
63 out = level::critical;
69auto play_state_to_json(
const play_state_info& info) -> std::string
72 R
"({{"phase":{},"is_active":{},"is_paused":{},"is_splash":{},"is_simulation_running":{},"frames_running":{}}})",
74 info.is_active ? "true" :
"false",
75 info.is_paused ?
"true" :
"false",
76 info.is_splash ?
"true" :
"false",
77 info.is_simulation_running ?
"true" :
"false",
86 {.name =
"play_get_state",
87 .description =
"Get editor play mode state: phase, active, paused, splash, frames_running.",
94 .mutates_scene =
false});
97 {.name =
"play_set_active",
99 "Enter or exit play mode. Blocked when inactive and scripts have compile errors. "
100 "Optional allow_splash (default true).",
102 R
"({"type":"object","properties":{"active":{"type":"boolean"},"allow_splash":{"type":"boolean"}},"required":["active"]})",
109 return {.text =
"Missing active", .is_error =
true};
111 bool allow_splash =
true;
112 read_bool(args,
"allow_splash", allow_splash);
116 return {.text =
error, .is_error =
true};
120 .mutates_scene =
false});
123 {.name =
"play_set_paused",
124 .description =
"Pause or resume simulation while play mode is active.",
126 R
"({"type":"object","properties":{"paused":{"type":"boolean"}},"required":["paused"]})",
133 return {.text =
"Missing paused", .is_error =
true};
138 return {.text =
error, .is_error =
true};
142 .mutates_scene =
false});
145 {.name =
"play_skip_frame",
146 .description =
"Advance one simulation frame while play mode is active and paused.",
154 return {.text =
error, .is_error =
true};
158 .mutates_scene =
false});
161 {.name =
"selection_get",
162 .description =
"Get the active entity selection (active_entity_id + entity_ids).",
168 std::string ids =
"[";
169 for(
size_t i = 0; i < sel.entity_ids.size(); ++i)
178 return {.text = fmt::format(R
"({{"active_entity_id":{},"entity_ids":{}}})",
179 sel.active_entity_id.empty() ? "null"
184 .mutates_scene =
false});
187 {.name =
"selection_set_batch",
189 "Set editor entity selection by UUID (or integral) ids. "
190 "mode=normal (default) replaces selection; mode=add appends (ctrl-select).",
192 R
"({"type":"object","properties":{"entity_ids":{"type":"array","items":{"type":"string"}},"mode":{"type":"string","enum":["normal","add"]}},"required":["entity_ids"]})",
196 simdjson::dom::array arr;
197 if(args[
"entity_ids"].get(arr))
199 return {.text =
"Missing entity_ids", .is_error =
true};
201 std::vector<std::string> ids;
204 std::string_view
view;
207 return {.text =
"entity_ids must be strings", .is_error =
true};
209 ids.emplace_back(
view);
213 if(
read_string(args,
"mode", mode) && mode ==
"add")
220 return {.text =
error, .is_error =
true};
223 std::string out_ids =
"[";
224 for(
size_t i = 0; i < sel.entity_ids.size(); ++i)
233 return {.text = fmt::format(R
"({{"active_entity_id":{},"entity_ids":{}}})",
234 sel.active_entity_id.empty() ? "null"
239 .mutates_scene =
false});
242 {.name =
"selection_clear",
243 .description =
"Clear the editor selection.",
249 return {.text = R
"({"cleared":true})", .is_error = false};
251 .mutates_scene =
false});
254 {.name =
"window_request_focus",
256 "Focus and raise the editor OS main window (os::window::request_focus). Call before "
257 "asset create/reimport when the editor is unfocused so filesystem watcher events can "
258 "fire (materials, folders, etc.). Restores the window if minimized.",
266 return {.text =
error, .is_error =
true};
268 return {.text = R
"({"ok":true,"requested":true})", .is_error = false};
270 .mutates_scene =
false});
273 {.name =
"panel_focus_scene",
274 .description =
"Focus the Scene panel (editor viewport tab) so it becomes the active ImGui window.",
282 return {.text =
error, .is_error =
true};
284 return {.text = R
"({"panel":"scene","focused":true})", .is_error = false};
286 .mutates_scene =
false});
289 {.name =
"panel_focus_game",
290 .description =
"Focus the Game panel (play view tab) so it becomes the active ImGui window.",
298 return {.text =
error, .is_error =
true};
300 return {.text = R
"({"panel":"game","focused":true})", .is_error = false};
302 .mutates_scene =
false});
305 {.name =
"logs_get_recent",
307 "Get recent console log entries. Optional min_level (trace|debug|info|warn|error|critical), "
308 "max_count (default 50), after_id (exclusive cursor).",
310 R
"({"type":"object","properties":{"min_level":{"type":"string"},"max_count":{"type":"integer","minimum":1,"maximum":1024},"after_id":{"type":"integer","minimum":0}}})",
314 level::level_enum min_level = level::info;
315 std::string level_name;
316 if(
read_string(args,
"min_level", level_name) && !parse_min_level(level_name, min_level))
318 return {.text =
"Invalid min_level", .is_error =
true};
320 double max_count_d = 50.0;
321 size_t max_count = 50;
324 max_count =
static_cast<size_t>(std::clamp(max_count_d, 1.0, 1024.0));
326 double after_d = 0.0;
327 uint64_t after_id = 0;
328 if(
read_double(args,
"after_id", after_d) && after_d >= 0.0)
330 after_id =
static_cast<uint64_t
>(after_d);
333 std::string json =
"[";
334 for(
size_t i = 0; i < logs.size(); ++i)
340 const auto& e = logs[i];
341 json += fmt::format(R
"({{"id":{},"level":{},"text":{},"filename":{},"funcname":{},"line":{}}})",
350 return {.text = std::move(json), .is_error =
false};
352 .mutates_scene =
false});
355 {.name =
"edit_undo",
356 .description =
"Undo the last undoable editor action (same as Ctrl+Z).",
364 return {.text =
error, .is_error =
true};
369 return {.text = R
"({"ok":false,"undone":false,"reason":"nothing to undo"})", .is_error = false};
371 auto action = em.
undo();
372 const auto name = action ? action->name : std::string{};
373 return {.text = fmt::format(R
"({{"ok":true,"undone":true,"action":{}}})", make_json_string(name)),
376 .mutates_scene =
true});
379 {.name =
"edit_redo",
380 .description =
"Redo the last undone editor action (same as Ctrl+Y).",
388 return {.text =
error, .is_error =
true};
393 return {.text = R
"({"ok":false,"redone":false,"reason":"nothing to redo"})", .is_error = false};
395 auto action = em.
redo();
396 const auto name = action ? action->name : std::string{};
397 return {.text = fmt::format(R
"({{"ok":true,"redone":true,"action":{}}})", make_json_string(name)),
400 .mutates_scene =
true});
auto require_not_play_mode(rtti::context &ctx, std::string &error) -> bool
auto empty_object_schema() -> std::string
auto read_bool(const simdjson::dom::object &args, const char *key, bool &out) -> bool
auto read_double(const simdjson::dom::object &args, const char *key, double &out) -> bool
auto make_json_string(const std::string &value) -> std::string
auto read_string(const simdjson::dom::object &args, const char *key, std::string &out) -> bool
void register_editor_tools(mcp_tool_registry ®istry)
auto undo() -> std::shared_ptr< editing_action_t >
auto redo() -> std::shared_ptr< editing_action_t >
static auto skip_play_frame(rtti::context &ctx, std::string *error=nullptr) -> bool
static auto request_main_window_focus(rtti::context &ctx, std::string *error=nullptr) -> bool
Focus/raise the OS main window so asset watcher and similar focus-gated work can run (e....
static auto set_play_active(rtti::context &ctx, bool active, bool allow_splash=true, std::string *error=nullptr) -> bool
static auto get_play_state(rtti::context &ctx) -> play_state_info
static void clear_selection(rtti::context &ctx)
static auto focus_scene_panel(rtti::context &ctx, std::string *error=nullptr) -> bool
static auto set_selection(rtti::context &ctx, const std::vector< std::string > &entity_ids, bool add=false, std::string *error=nullptr) -> bool
static auto get_recent_logs(rtti::context &ctx, level::level_enum min_level, size_t max_count, uint64_t after_id=0) -> std::vector< log_query_entry >
static auto focus_game_panel(rtti::context &ctx, std::string *error=nullptr) -> bool
static auto set_play_paused(rtti::context &ctx, bool paused, std::string *error=nullptr) -> bool
static auto get_selection(rtti::context &ctx) -> selection_info