23auto read_text_file(
const fs::path& path, std::string& out, std::string&
error) ->
bool
25 std::ifstream
input(path, std::ios::binary);
28 error =
"Failed to open file: " + path.generic_string();
31 std::ostringstream buffer;
32 buffer <<
input.rdbuf();
37auto write_text_file_atomic(
const fs::path& path,
const std::string& contents, std::string&
error) ->
bool
40 const auto absolute = fs::absolute(path);
41 fs::create_directories(absolute.parent_path(), err);
45 [&](
const fs::path& temp)
47 std::ofstream output(temp, std::ios::binary | std::ios::trunc);
50 throw std::runtime_error(
"Failed to open temp file for write: " + temp.generic_string());
52 output.write(contents.data(),
static_cast<std::streamsize
>(contents.size()));
55 throw std::runtime_error(
"Failed writing temp file: " + temp.generic_string());
61 error =
"Atomic write failed: " + absolute.generic_string() +
" (" + err.message() +
")";
67auto resolve_source_path(
const std::string& path_or_key) -> fs::path
69 if(path_or_key.empty())
74 const fs::path as_path(path_or_key);
75 if(as_path.is_absolute())
87auto find_script_object(script_component& script_comp,
const std::string& type_name)
88 -> script_component::script_object
90 for(
const auto& obj : script_comp.get_script_components())
96 if(obj.pinned->get_object().get_type().get_fullname() == type_name)
104auto unique_script_path(
const fs::path& dir,
const std::string& stem) -> fs::path
107 fs::path candidate = dir / (stem + ext);
110 while(fs::exists(candidate, ec))
113 candidate = dir / (fmt::format(
"{}{}", stem, i) + ext);
118auto protocol_for_path(
const fs::path& absolute) -> std::string
129 {.name =
"scripts_list_types",
131 "List C# ScriptComponent type full names available to add via scene_add_scripts_batch "
132 "(project + engine scriptable types from script_system).",
139 return {.text =
"Script system unavailable", .is_error =
true};
142 std::string json =
"[";
143 for(
size_t i = 0; i < types.size(); ++i)
152 return {.text = json, .is_error =
false};
154 .mutates_scene =
false});
157 {.name =
"scripts_get_source",
159 "Read C# source for a script. Resolve by path/key, or by entity_id + type_name "
160 "(uses get_script_source_location).",
162 R
"json({"type":"object","properties":{"path":{"type":"string"},"key":{"type":"string"},"entity_id":{"type":"string"},"type_name":{"type":"string"}}})json",
170 if(path.empty() && !key.empty())
175 std::string type_name;
177 std::string entity_id;
182 if(entity_id.empty() || type_name.empty())
184 return {.text =
"Provide path/key, or entity_id + type_name", .is_error =
true};
188 if(!scn || !scn->registry)
190 return {.text =
"No active scene", .is_error =
true};
196 return {.text =
"Entity has no ScriptComponent", .is_error =
true};
198 auto obj = find_script_object(*script_comp, type_name);
201 return {.text =
"Script type not on entity: " + type_name, .is_error =
true};
203 path = script_comp->get_script_source_location(obj);
206 return {.text =
"No source path for script type: " + type_name, .is_error =
true};
210 const auto absolute = resolve_source_path(path);
213 if(!read_text_file(absolute, code,
error))
215 return {.text =
error, .is_error =
true};
217 return {.text = fmt::format(R
"({{"path":{},"bytes":{},"code":{}}})",
223 .mutates_scene =
false});
226 {.name =
"scripts_set_sources_batch",
228 "Write C# source files (batch) via asset_writer::atomic_write_file. Each item: code, "
229 "plus path/key or entity_id + type_name. Optional recompile (default true, once for batch).",
231 R
"json({"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"path":{"type":"string"},"key":{"type":"string"},"entity_id":{"type":"string"},"type_name":{"type":"string"}},"required":["code"]}},"recompile":{"type":"boolean","default":true}},"required":["items"]})json",
238 return {.text =
error, .is_error =
true};
240 simdjson::dom::array items_arr;
241 if(args[
"items"].get(items_arr))
243 return {.text =
"Missing items array", .is_error =
true};
245 bool recompile =
true;
247 std::string results =
"[";
250 size_t requested = 0;
251 std::string protocol;
252 for(
auto el : items_arr)
255 simdjson::dom::object obj;
258 return {.text =
"Each item must be an object", .is_error =
true};
268 results += R
"({"ok":false,"error":"Missing code"})";
275 if(path.empty() && !key.empty())
279 std::string type_name;
280 std::string entity_id;
285 if(entity_id.empty() || type_name.empty())
287 results += R
"({"ok":false,"error":"Provide path/key, or entity_id + type_name"})";
290 scene* scn =
nullptr;
300 results += R
"({"ok":false,"error":"Entity has no ScriptComponent"})";
303 auto script_obj = find_script_object(*script_comp, type_name);
304 if(!script_obj.pinned)
306 results += fmt::format(R
"({{"ok":false,"error":{}}})",
310 path = script_comp->get_script_source_location(script_obj);
313 results += fmt::format(R
"({{"ok":false,"error":{}}})",
318 const auto absolute = resolve_source_path(path);
319 if(!write_text_file_atomic(absolute, code,
error))
324 if(recompile && protocol.empty())
326 protocol = protocol_for_path(absolute);
333 results += fmt::format(R
"({{"ok":true,"path":{},"bytes":{}}})",
338 if(recompile && ok_count > 0 && !protocol.empty())
342 return {.text = fmt::format(R
"({{"results":{},"count":{},"requested":{},"recompile":{},"protocol":{}}})",
346 recompile ? "true" :
"false",
348 .is_error = ok_count == 0 && requested > 0};
350 .mutates_scene =
false});
353 {.name =
"scripts_create_batch",
355 "Create many C# ScriptComponent .cs files from TemplateComponent.cs.in. Each item: name, "
356 "optional folder (default app:/data/scripts). Optional recompile (default true, once).",
358 R
"json({"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"folder":{"type":"string"}},"required":["name"]}},"recompile":{"type":"boolean","default":true}},"required":["items"]})json",
365 return {.text =
error, .is_error =
true};
367 simdjson::dom::array items_arr;
368 if(args[
"items"].get(items_arr))
370 return {.text =
"Missing items array", .is_error =
true};
372 bool recompile =
true;
374 const auto template_path =
376 std::string results =
"[";
379 size_t requested = 0;
380 std::string protocol;
381 for(
auto el : items_arr)
384 simdjson::dom::object obj;
387 return {.text =
"Each item must be an object", .is_error =
true};
397 results += R
"({"ok":false,"error":"Missing name"})";
400 if(
name.size() > 3 &&
name.substr(
name.size() - 3) ==
".cs")
406 results += R
"({"ok":false,"error":"name must be a valid C# identifier"})";
409 std::string folder =
"app:/data/scripts";
413 folder =
"app:/data/scripts";
415 const auto dir = resolve_source_path(folder);
416 const auto dst = unique_script_path(dir,
name);
422 if(recompile && protocol.empty())
424 protocol = protocol_for_path(dst);
432 results += fmt::format(R
"({{"ok":true,"path":{},"key":{},"type_name":{}}})",
438 if(recompile && ok_count > 0 && !protocol.empty())
442 return {.text = fmt::format(R
"({{"results":{},"count":{},"requested":{},"recompile":{},"protocol":{}}})",
446 recompile ? "true" :
"false",
448 .is_error = ok_count == 0 && requested > 0};
450 .mutates_scene =
false});
Class that contains core data for audio listeners. There can only be one instance of it per scene.
auto get_format(bool include_dot=true) -> std::string
path extract_protocol(const path &_path)
Given the specified path/filename, resolve the final full filename. This will be based on either the ...
path resolve_protocol(const path &_path)
Given the specified path/filename, resolve the final full filename. This will be based on either the ...
path convert_to_protocol(const path &_path)
Oposite of the resolve_protocol this function tries to convert to protocol path from an absolute one.
auto is_valid_csharp_identifier(const std::string &name) -> bool
auto create_script_from_template(const fs::path &template_path, const fs::path &dst, std::string *error) -> bool
Instantiate a ScriptComponent template (.cs.in), substituting #SCRIPTNAME# with the destination file ...
void atomic_write_file(const fs::path &dst, const std::function< void(const fs::path &)> &callback, fs::error_code &ec) noexcept
auto require_not_play_mode(rtti::context &ctx, std::string &error) -> bool
auto empty_object_schema() -> std::string
auto find_entity(scene &scn, const std::string &id) -> entt::handle
auto require_edit_scene(rtti::context &ctx, scene *&out_scene, std::string &error) -> bool
auto read_bool(const simdjson::dom::object &args, const char *key, bool &out) -> bool
auto make_json_string(const std::string &value) -> std::string
void register_script_tools(mcp_tool_registry ®istry)
auto read_string(const simdjson::dom::object &args, const char *key, std::string &out) -> bool
auto require_open_project(rtti::context &ctx, std::string &error) -> bool
auto get_active_scene(rtti::context &ctx) -> scene *
Represents a scene in the ACE framework, managing entities and their relationships.
static void set_needs_recompile(const std::string &protocol, bool now=false)