Unravel Engine C++ Reference
Loading...
Searching...
No Matches
editor_actions.cpp
Go to the documentation of this file.
1#include "editor_actions.h"
2#include "entity_inspect.h"
4#include "engine/ui/ui_tree.h"
5
8#include <editor/hub/hub.h>
18#include <engine/ecs/ecs.h>
19#include <engine/engine.h>
20#include <engine/events.h>
21#include <engine/play_mode.h>
28#include <engine/ecs/scene.h>
29#include <filedialog/filedialog.h>
31#include <filesystem/watcher.h>
32#include <filesystem>
34
35
37#include <string_utils/utils.h>
38
39namespace unravel
40{
41
42namespace
43{
44
45auto get_vscode_executable() -> fs::path
46{
47 fs::path executablePath;
48
49#if UNRAVEL_PLATFORM_WINDOWS
50 // Windows implementation
51 try
52 {
53 // Common installation paths
54 std::vector<fs::path> possiblePaths = {"C:\\Program Files\\Microsoft VS Code\\Code.exe",
55 "C:\\Program Files (x86)\\Microsoft VS Code\\Code.exe",
56 fs::path(std::getenv("LOCALAPPDATA")) / "Programs" /
57 "Microsoft VS Code" / "Code.exe"};
58
59 for(const auto& path : possiblePaths)
60 {
61 if(fs::exists(path))
62 {
63 executablePath = path;
64 break;
65 }
66 }
67
68 if(executablePath.empty())
69 {
70 // Search for Code.exe in the PATH environment variable
71 const char* pathEnv = std::getenv("PATH");
72 if(pathEnv)
73 {
74 std::string pathEnvStr(pathEnv);
75 std::stringstream ss(pathEnvStr);
76 std::string token;
77 while(std::getline(ss, token, ';'))
78 {
79 fs::path codePath = fs::path(token) / "Code.exe";
80 if(fs::exists(codePath))
81 {
82 executablePath = codePath;
83 break;
84 }
85 }
86 }
87
88 // If still not found, perform a recursive search in Program Files
89 if(executablePath.empty())
90 {
91 std::vector<fs::path> directoriesToSearch = {"C:\\Program Files",
92 "C:\\Program Files (x86)",
93 fs::path(std::getenv("LOCALAPPDATA")) / "Programs"};
94
95 for(const auto& dir : directoriesToSearch)
96 {
97 try
98 {
99 for(const auto& entry : fs::recursive_directory_iterator(dir))
100 {
101 if(entry.is_regular_file() && entry.path().filename() == "Code.exe")
102 {
103 executablePath = entry.path();
104 break;
105 }
106 }
107 if(!executablePath.empty())
108 {
109 break;
110 }
111 }
112 catch(const fs::filesystem_error&)
113 {
114 continue;
115 }
116 }
117 }
118 }
119 }
120 catch(const std::exception& e)
121 {
122 std::cerr << "Error finding VSCode executable path on Windows: " << e.what() << std::endl;
123 }
124
125#elif UNRAVEL_PLATFORM_OSX
126 // macOS implementation
127 try
128 {
129 // Common application bundle paths
130 std::vector<fs::path> possibleAppPaths = {"/Applications/Visual Studio Code.app",
131 "/Applications/Visual Studio Code - Insiders.app",
132 fs::path(std::getenv("HOME")) / "Applications" /
133 "Visual Studio Code.app"};
134
135 for(const auto& appPath : possibleAppPaths)
136 {
137 if(fs::exists(appPath))
138 {
139 // The executable is inside the app bundle
140 fs::path codeExecutable = appPath / "Contents" / "MacOS" / "Electron";
141 if(fs::exists(codeExecutable))
142 {
143 executablePath = codeExecutable;
144 break;
145 }
146 }
147 }
148
149 if(executablePath.empty())
150 {
151 // Search for 'code' in /usr/local/bin or /usr/bin
152 std::vector<fs::path> possibleLinks = {"/usr/local/bin/code", "/usr/bin/code"};
153 for(const auto& linkPath : possibleLinks)
154 {
155 if(fs::exists(linkPath))
156 {
157 // Resolve symlink
158 executablePath = fs::canonical(linkPath);
159 break;
160 }
161 }
162 }
163
164 if(executablePath.empty())
165 {
166 // Search in PATH environment variable
167 const char* pathEnv = std::getenv("PATH");
168 if(pathEnv)
169 {
170 std::string pathEnvStr(pathEnv);
171 std::stringstream ss(pathEnvStr);
172 std::string token;
173 while(std::getline(ss, token, ':'))
174 {
175 fs::path codePath = fs::path(token) / "code";
176 if(fs::exists(codePath))
177 {
178 executablePath = fs::canonical(codePath);
179 break;
180 }
181 }
182 }
183 }
184 }
185 catch(const std::exception& e)
186 {
187 std::cerr << "Error finding VSCode executable path on macOS: " << e.what() << std::endl;
188 }
189
190#elif UNRAVEL_PLATFORM_LINUX
191 // Linux implementation
192 try
193 {
194 // Search for 'code' executable in PATH
195 const char* pathEnv = std::getenv("PATH");
196 if(pathEnv)
197 {
198 std::string pathEnvStr(pathEnv);
199 std::stringstream ss(pathEnvStr);
200 std::string token;
201 while(std::getline(ss, token, ':'))
202 {
203 fs::path codePath = fs::path(token) / "code";
204 if(fs::exists(codePath) && fs::is_regular_file(codePath))
205 {
206 // Resolve symlink if necessary
207 executablePath = fs::canonical(codePath);
208 break;
209 }
210 }
211 }
212
213 if(executablePath.empty())
214 {
215 // Check common installation directories
216 std::vector<fs::path> possiblePaths = {
217 "/usr/bin/code",
218 "/bin/code",
219 "/sbin/code",
220 "/usr/share/code/bin/code",
221 "/usr/share/code-insiders/bin/code",
222 "/usr/local/share/code/bin/code",
223 "/opt/visual-studio-code/bin/code",
224 "/var/lib/flatpak/app/com.visualstudio.code/current/active/files/bin/code",
225 fs::path(std::getenv("HOME")) / ".vscode" / "bin" / "code"};
226
227 for(const auto& path : possiblePaths)
228 {
229 if(fs::exists(path))
230 {
231 executablePath = path;
232 break;
233 }
234 }
235 }
236 }
237 catch(const std::exception& e)
238 {
239 std::cerr << "Error finding VSCode executable path on Linux: " << e.what() << std::endl;
240 }
241
242#else
243#error "Unsupported operating system."
244#endif
245
246 return executablePath;
247}
248
249void remove_extensions(std::vector<std::vector<std::string>>& resourceExtensions,
250 const std::vector<std::string>& extsToRemove)
251{
252 // Convert extsToRemove to a set of lowercase strings
253 std::unordered_set<std::string> extsToRemoveSet;
254 for(const auto& ext : extsToRemove)
255 {
256 extsToRemoveSet.insert(string_utils::to_lower(ext));
257 }
258
259 for(auto outerIt = resourceExtensions.begin(); outerIt != resourceExtensions.end();)
260 {
261 std::vector<std::string>& innerVec = *outerIt;
262
263 innerVec.erase(std::remove_if(innerVec.begin(),
264 innerVec.end(),
265 [&extsToRemoveSet](const std::string& ext)
266 {
267 return extsToRemoveSet.find(string_utils::to_lower(ext)) !=
268 extsToRemoveSet.end();
269 }),
270 innerVec.end());
271
272 if(innerVec.empty())
273 {
274 outerIt = resourceExtensions.erase(outerIt);
275 }
276 else
277 {
278 ++outerIt;
279 }
280 }
281}
282void generate_workspace_file(const std::string& file_path,
283 const std::vector<std::vector<std::string>>& exclude_extensions,
284 const editor_settings& settings)
285{
286 // Start constructing the JSON content
287 std::ostringstream json_stream;
288
289 json_stream << "{\n";
290 json_stream << " \"folders\": [\n";
291 json_stream << " {\n";
292 json_stream << " \"path\": \"..\"\n";
293 json_stream << " }\n";
294 json_stream << " ],\n";
295 json_stream << " \"settings\": {\n";
296 json_stream << " \"dotnet.preferCSharpExtension\": true,\n";
297 json_stream << " \"files.exclude\": {\n";
298 json_stream << " \"**/.git\": true,\n";
299 json_stream << " \"**/.svn\": true";
300
301 // Add the exclude patterns from the provided extensions
302 for(const auto& extensions : exclude_extensions)
303 {
304 for(const auto& ext : extensions)
305 {
306 // Escape any special characters in the extension if necessary
307
308 // Create the pattern to exclude files with the given extension
309 std::string pattern = "**/*" + ext;
310
311 // Add a comma before each new entry
312 json_stream << ",\n";
313 json_stream << " \"" << pattern << "\": true";
314 }
315 }
316
317 // Close the files.exclude object and add files.associations
318 json_stream << "\n";
319 json_stream << " },\n"; // End of "files.exclude"
320 json_stream << " \"files.associations\": {\n";
321 json_stream << " \"*.rcss\": \"css\",\n";
322 json_stream << " \"*.rhtml\": \"html\"\n";
323 json_stream << " }\n";
324 json_stream << " }\n"; // End of "settings"
325
326 // Add the "extensions" section
327 json_stream << ",\n";
328 json_stream << " \"extensions\": {\n";
329 json_stream << " \"recommendations\": [\n";
330#if DOTNETPP_BACKEND_MONO
331 json_stream << " \"ms-vscode.mono-debug\",\n";
332#endif
333 json_stream << " \"ms-dotnettools.csharp\"\n";
334 json_stream << " ]\n";
335 json_stream << " }\n";
336
337 // Add the "launch" section
338 json_stream << ",\n";
339 json_stream << " \"launch\": {\n";
340 json_stream << " \"version\": \"0.2.0\",\n";
341 json_stream << " \"configurations\": [\n";
342#if DOTNETPP_BACKEND_MONO
343 json_stream << " {\n";
344 json_stream << " \"name\": \"Attach to Mono\",\n";
345 json_stream << " \"request\": \"attach\",\n";
346 json_stream << " \"type\": \"mono\",\n";
347 json_stream << " \"address\": \"" << settings.debugger.ip << "\",\n";
348 json_stream << " \"port\": " << settings.debugger.port << "\n";
349 json_stream << " }\n";
350#else
351 (void)settings;
352 json_stream << " {\n";
353 json_stream << " \"name\": \"Attach to " << EDITOR_NAME << "\",\n";
354 json_stream << " \"type\": \"coreclr\",\n";
355 json_stream << " \"request\": \"attach\"\n";
356 json_stream << " \"processName\": \"" << EDITOR_NAME << "\"\n";
357 json_stream << " },\n";
358 json_stream << " {\n";
359 json_stream << " \"name\": \".NET Core Attach\",\n";
360 json_stream << " \"type\": \"coreclr\",\n";
361 json_stream << " \"request\": \"attach\"\n";
362 json_stream << " }\n";
363#endif
364 json_stream << " ]\n";
365 json_stream << " }\n";
366
367 // Close the JSON object
368 json_stream << "}";
369
370 // Write the JSON string to a file
371 std::ofstream file(file_path);
372 if(file.is_open())
373 {
374 file << json_stream.str();
375 }
376
377 APPLOG_TRACE("Workspace {}", file_path);
378}
379
392#if !DOTNETPP_BACKEND_MONO
393void generate_csproj(const fs::path& source_directory,
394 const std::vector<fs::path>& external_dll_paths,
395 const fs::path& output_directory,
396 const std::string& project_name = "MyLibrary",
397 std::string dotnet_sdk_version = {})
398{
399 if(dotnet_sdk_version.empty())
400 {
401 dotnet_sdk_version = dotnet::get_dotnet_version();
402 }
403 // Ensure the output directory exists
404 try
405 {
406 fs::create_directories(output_directory);
407 }
408 catch(const fs::filesystem_error& e)
409 {
410 throw std::runtime_error("Failed to create output directory: " + std::string(e.what()));
411 }
412
413 // Verify that the source directory exists
414 if(!fs::exists(source_directory) || !fs::is_directory(source_directory))
415 {
416 throw std::runtime_error("Source directory does not exist or is not a directory: " + source_directory.string());
417 }
418
419 // Verify that all external DLLs exist and are files
420 for(const auto& dll_path : external_dll_paths)
421 {
422 if(!fs::exists(dll_path) || !fs::is_regular_file(dll_path))
423 {
424 throw std::runtime_error("External DLL does not exist or is not a file: " + dll_path.string());
425 }
426 }
427
428 // Collect all C# source files from the specified source directory
429 std::vector<fs::path> csharp_sources;
430 try
431 {
432 for(const auto& entry : fs::recursive_directory_iterator(source_directory))
433 {
434 if(entry.is_regular_file() && entry.path().extension() == ".cs")
435 {
436 // Compute the relative path from the source directory
437 fs::path relative_path = fs::relative(entry.path(), source_directory);
438 csharp_sources.push_back(relative_path);
439 }
440 }
441 }
442 catch(const fs::filesystem_error& e)
443 {
444 throw std::runtime_error("Error while iterating source directory: " + std::string(e.what()));
445 }
446
447 // Generate the list of source files for the .csproj file with <Link> elements (for virtual folders)
448 std::string csharp_source_items;
449 for(const auto& source_file : csharp_sources)
450 {
451 // Convert path to generic format (forward slashes)
452 std::string source_file_str = source_file.string();
453 fs::path full_physical_path = fs::absolute(source_directory / source_file);
454 std::string full_physical_path_str = full_physical_path.string();
455
456 // Construct the <Compile Include> with <Link>
457 csharp_source_items += " <Compile Include=\"" + full_physical_path_str + "\">\n";
458 csharp_source_items += " <Link>" + source_file_str + "</Link>\n";
459 csharp_source_items += " </Compile>\n";
460 }
461
462 // Generate external DLL references
463 std::string external_dll_references;
464 for(const auto& dll_path : external_dll_paths)
465 {
466 std::string dll_name = dll_path.filename().string();
467 fs::path dll_absolute_path = fs::absolute(dll_path);
468 std::string dll_absolute_path_str = dll_absolute_path.string(); // Forward slashes
469
470 external_dll_references += " <Reference Include=\"" + dll_name + "\">\n";
471 external_dll_references += " <HintPath>" + dll_absolute_path_str + "</HintPath>\n";
472 external_dll_references += " <Private>False</Private>\n";
473 external_dll_references += " </Reference>\n";
474 }
475
476 // Build the .csproj content. This project exists for IDE tooling
477 // (intellisense/analyzers); the engine compiles scripts itself with csc.
478 // Keep build outputs in temp/ so restores don't pollute the project dir.
479 std::string csproj_content;
480 csproj_content += "<Project Sdk=\"Microsoft.NET.Sdk\">\n";
481 csproj_content += " <PropertyGroup>\n";
482 csproj_content += " <TargetFramework>net" + dotnet_sdk_version + "</TargetFramework>\n";
483 csproj_content += " <OutputType>Library</OutputType>\n";
484 csproj_content += " <AssemblyName>" + project_name + "</AssemblyName>\n";
485 csproj_content += " <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n";
486 csproj_content += " <ImplicitUsings>disable</ImplicitUsings>\n";
487 csproj_content += " <Nullable>disable</Nullable>\n";
488 csproj_content += " <GenerateAssemblyInfo>false</GenerateAssemblyInfo>\n";
489 csproj_content += " <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>\n";
490 csproj_content += " <BaseOutputPath>temp/bin</BaseOutputPath>\n";
491 csproj_content += " <BaseIntermediateOutputPath>temp/obj</BaseIntermediateOutputPath>\n";
492 csproj_content +=
493 " <EnableDefaultCompileItems>false</EnableDefaultCompileItems>\n"; // Disable default .cs file inclusion
494 csproj_content += " </PropertyGroup>\n";
495 csproj_content += " <ItemGroup>\n";
496 csproj_content += csharp_source_items;
497 csproj_content += " </ItemGroup>\n";
498 csproj_content += " <ItemGroup>\n";
499 csproj_content += external_dll_references;
500 csproj_content += " </ItemGroup>\n";
501 csproj_content += "</Project>\n";
502
503 // Define the path to the .csproj file
504 fs::path csproj_path = output_directory / (project_name + ".csproj");
505
506 // Write the .csproj file
507 std::ofstream csproj_file(csproj_path);
508 if(!csproj_file.is_open())
509 {
510 APPLOG_ERROR("Failed to create .csproj file at {}", csproj_path.string());
511 return;
512 }
513
514 csproj_file << csproj_content;
515
516 APPLOG_TRACE("Generated {}", csproj_path.string());
517}
518#else
519void generate_csproj_legacy(const fs::path& source_directory,
520 const std::vector<fs::path>& external_dll_paths,
521 const fs::path& output_directory,
522 const std::string& project_name = "MyLibrary",
523 const std::string& dotnet_framework_version = "v4.7.1")
524{
525 auto uid = generate_uuid(project_name);
526 fs::path output_path = fs::path("temp") / "bin" / "Debug";
527 fs::path intermediate_output_path = fs::path("temp") / "obj" / "Debug";
528
529 // Ensure the output directory exists
530 try
531 {
532 fs::create_directories(output_directory);
533 }
534 catch(const fs::filesystem_error& e)
535 {
536 throw std::runtime_error("Failed to create output directory: " + std::string(e.what()));
537 }
538
539 // Verify that the source directory exists
540 if(!fs::exists(source_directory) || !fs::is_directory(source_directory))
541 {
542 throw std::runtime_error("Source directory does not exist or is not a directory: " + source_directory.string());
543 }
544
545 // Verify that all external DLLs exist and are files
546 for(const auto& dll_path : external_dll_paths)
547 {
548 if(!fs::exists(dll_path) || !fs::is_regular_file(dll_path))
549 {
550 throw std::runtime_error("External DLL does not exist or is not a file: " + dll_path.string());
551 }
552 }
553
554 // Collect all C# source files from the specified source directory
555 std::vector<fs::path> csharp_sources;
556 try
557 {
558 for(const auto& entry : fs::recursive_directory_iterator(source_directory))
559 {
560 if(entry.is_regular_file() && entry.path().extension() == ".cs")
561 {
562 // Compute the relative path from the output directory
563 fs::path relative_path = fs::relative(entry.path(), output_directory);
564 csharp_sources.push_back(relative_path);
565 }
566 }
567 }
568 catch(const fs::filesystem_error& e)
569 {
570 throw std::runtime_error("Error while iterating source directory: " + std::string(e.what()));
571 }
572
573 // Generate the list of source files for the .csproj file
574 std::string csharp_source_items;
575 for(const auto& source_file : csharp_sources)
576 {
577 // Convert path to generic format (forward slashes)
578 std::string source_file_str = source_file.string();
579 csharp_source_items += " <Compile Include=\"" + source_file_str + "\" />\n";
580 }
581
582 // Generate external DLL references
583 std::string external_dll_references;
584 for(const auto& dll_path : external_dll_paths)
585 {
586 std::string dll_name = dll_path.filename().string();
587 fs::path dll_absolute_path = fs::absolute(dll_path);
588 std::string dll_absolute_path_str = dll_absolute_path.string(); // Forward slashes
589
590 external_dll_references += " <Reference Include=\"" + dll_name + "\">\n";
591 external_dll_references += " <HintPath>" + dll_absolute_path_str + "</HintPath>\n";
592 external_dll_references += " <Private>False</Private>\n"; // Mimic Unity's references
593 external_dll_references += " </Reference>\n";
594 }
595
596 // Build the .csproj content
597 std::string csproj_content;
598 csproj_content += "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
599 csproj_content += "<Project ToolsVersion=\"4.0\" DefaultTargets=\"Build\" "
600 "xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n";
601 csproj_content += " <PropertyGroup>\n";
602 csproj_content += " <LangVersion>9.0</LangVersion>\n";
603 csproj_content += " </PropertyGroup>\n";
604 csproj_content += " <PropertyGroup>\n";
605 csproj_content += " <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n";
606 csproj_content += " <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n";
607 csproj_content += " <ProductVersion>10.0.20506</ProductVersion>\n";
608 csproj_content += " <SchemaVersion>2.0</SchemaVersion>\n";
609 csproj_content += " <RootNamespace></RootNamespace>\n";
610 csproj_content += " <ProjectGuid>{" + hpp::to_string_upper(uid) + "}</ProjectGuid>\n";
611 csproj_content += " <OutputType>Library</OutputType>\n";
612 csproj_content += " <AppDesignerFolder>Properties</AppDesignerFolder>\n";
613 csproj_content += " <AssemblyName>" + project_name + "</AssemblyName>\n";
614 csproj_content += " <TargetFrameworkVersion>" + dotnet_framework_version + "</TargetFrameworkVersion>\n";
615 csproj_content += " <FileAlignment>512</FileAlignment>\n";
616 csproj_content += " <BaseDirectory>.</BaseDirectory>\n";
617 csproj_content += " <OutputPath>" + output_path.string() + "</OutputPath>\n";
618 csproj_content +=
619 " <IntermediateOutputPath>" + intermediate_output_path.string() + "</IntermediateOutputPath>\n";
620
621 csproj_content += " </PropertyGroup>\n";
622
623 // Add other necessary PropertyGroups as needed (similar to the Unity example)
624 // ...
625
626 // ItemGroup for Compile (C# files)
627 csproj_content += " <ItemGroup>\n";
628 csproj_content += csharp_source_items;
629 csproj_content += " </ItemGroup>\n";
630
631 // ItemGroup for References
632 csproj_content += " <ItemGroup>\n";
633 csproj_content += external_dll_references;
634 csproj_content += " </ItemGroup>\n";
635
636 // Add other ItemGroups as needed (e.g., Analyzers, etc.)
637 // ...
638
639 // Import the C# targets
640 csproj_content += " <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n";
641
642 // Optionally add custom Targets
643 csproj_content += " <Target Name=\"GenerateTargetFrameworkMonikerAttribute\" />\n";
644
645 // Optionally add BeforeBuild and AfterBuild targets
646 csproj_content +=
647 " <!-- To modify your build process, add your task inside one of the targets below and uncomment it.\n";
648 csproj_content += " Other similar extension points exist, see Microsoft.Common.targets.\n";
649 csproj_content += " <Target Name=\"BeforeBuild\">\n";
650 csproj_content += " </Target>\n";
651 csproj_content += " <Target Name=\"AfterBuild\">\n";
652 csproj_content += " </Target>\n";
653 csproj_content += " -->\n";
654
655 csproj_content += "</Project>\n";
656
657 // Define the path to the .csproj file
658 fs::path csproj_path = output_directory / (project_name + ".csproj");
659
660 // Write the .csproj file
661 std::ofstream csproj_file(csproj_path);
662 if(!csproj_file.is_open())
663 {
664 APPLOG_ERROR("Failed to create .csproj file at {}", csproj_path.string());
665 return;
666 }
667
668 csproj_file << csproj_content;
669
670 APPLOG_TRACE("Generated {}", csproj_path.string());
671}
672#endif
673
674auto trim_line = [](std::string& line)
675{
676 // Trim trailing spaces and \r
677 line.erase(std::find_if(line.rbegin(),
678 line.rend(),
679 [](char ch)
680 {
681 return !std::isspace(int(ch));
682 })
683 .base(),
684 line.end());
685};
686
687auto parse_line(std::string& line, const fs::path& fs_parent_path) -> bool
688{
689#if UNRAVEL_PLATFORM_WINDOWS
690 // parse dependencies output
691 if(line.find("[ApplicationDirectory]") != std::string::npos)
692 {
693 std::size_t pos = line.find(':');
694 if(pos != std::string::npos)
695 {
696 line = line.substr(pos + 2); // +2 to skip ": "
697 trim_line(line);
698
699 return true;
700 }
701 }
702#else
703 // parse ldd output
704 size_t pos = line.find("=> ");
705 bool found = pos != std::string::npos;
706
707 bool is_local = false;
708 if(!found)
709 {
710 pos = line.find('\t');
711 found = pos != std::string::npos;
712 is_local = true;
713 }
714
715 if(found)
716 {
717 if(!is_local)
718 {
719 line = line.substr(pos + 3); // +3 to remove '=> '
720 }
721 else
722 {
723 line = line.substr(pos + 1); // +1 to remove '\t'
724 }
725 size_t address_pos = line.find(" (0x");
726 if(address_pos != std::string::npos)
727 {
728 line = line.substr(0, address_pos); // remove the address
729 }
730
731 trim_line(line);
732
733 fs::path fs_path(line);
734
735 if(is_local)
736 {
737 fs_path = fs_parent_path / fs_path;
738 line = fs_path.string();
739 }
740
741 if(fs::exists(fs_path) && fs::exists(fs_parent_path))
742 {
743 if(fs::equivalent(fs_path.parent_path(), fs_parent_path))
744 {
745 return true;
746 }
747 }
748 }
749
750#endif
751 return false;
752}
753
754auto get_subprocess_params(const fs::path& file) -> std::vector<std::string>
755{
756 std::vector<std::string> params;
757
758#if UNRAVEL_PLATFORM_WINDOWS
759 params.emplace_back(fs::resolve_protocol("editor:/tools/dependencies/Dependencies.exe").string());
760 params.emplace_back("-modules");
761 params.emplace_back(file.string());
762
763#else
764
765 params.emplace_back("ldd");
766 params.emplace_back(file.string());
767#endif
768 return params;
769}
770
771auto parse_dependencies(const std::string& input, const fs::path& fs_parent_path) -> std::vector<std::string>
772{
773 std::vector<std::string> dependencies;
774 std::stringstream ss(input);
775 std::string line;
776
777 while(std::getline(ss, line))
778 {
779 if(parse_line(line, fs_parent_path))
780 {
781 dependencies.push_back(line);
782 }
783 }
784 return dependencies;
785}
786
787auto get_dependencies(const fs::path& file) -> std::vector<std::string>
788{
789 auto parent_path = file.parent_path();
790
791 auto params = get_subprocess_params(file);
792 APPLOG_TRACE("Params: \n{}", params);
793
794 auto result = subprocess::call(params);
795 APPLOG_TRACE("Dependencies: \n{}", result.out_output);
796 return parse_dependencies(result.out_output, parent_path);
797}
798
799#if !DOTNETPP_BACKEND_MONO
801auto parse_version_name(const std::string& name) -> std::vector<int>
802{
803 std::vector<int> parts;
804 std::string current;
805 for(char c : name)
806 {
807 if(c >= '0' && c <= '9')
808 {
809 current += c;
810 }
811 else if(c == '.')
812 {
813 parts.push_back(current.empty() ? 0 : std::atoi(current.c_str()));
814 current.clear();
815 }
816 else
817 {
818 break;
819 }
820 }
821 if(!current.empty())
822 {
823 parts.push_back(std::atoi(current.c_str()));
824 }
825 return parts;
826}
827
832auto pick_highest_version_dir(const fs::path& base, int preferred_major = -1) -> fs::path
833{
834 fs::path best;
835 std::vector<int> best_version;
836 bool best_matches_major = false;
837
838 fs::error_code ec;
839 for(const auto& entry : fs::directory_iterator(base, ec))
840 {
841 if(!entry.is_directory(ec))
842 {
843 continue;
844 }
845 auto version = parse_version_name(entry.path().filename().string());
846 if(version.empty())
847 {
848 continue;
849 }
850 bool matches_major = preferred_major >= 0 && version[0] == preferred_major;
851 // A major-matching candidate always beats a non-matching one;
852 // within the same tier, the higher version wins.
853 bool better = best.empty();
854 if(!better && matches_major != best_matches_major)
855 {
856 better = matches_major;
857 }
858 else if(!better)
859 {
860 better = std::lexicographical_compare(best_version.begin(),
861 best_version.end(),
862 version.begin(),
863 version.end());
864 }
865 if(better)
866 {
867 best = entry.path();
868 best_version = version;
869 best_matches_major = matches_major;
870 }
871 }
872 return best;
873}
874
876auto version_major(const std::string& version) -> int
877{
878 int major = std::atoi(version.c_str());
879 return major > 0 ? major : -1;
880}
881#endif
882
883auto save_scene_impl(rtti::context& ctx, const fs::path& path) -> bool
884{
885 return editor_actions::save_scene_to_path(ctx, path, false, true);
886}
887
888auto add_extension_if_missing(const std::string& p) -> fs::path
889{
890 fs::path def_path = p;
891 if(!ex::is_format<scene_prefab>(def_path.extension().generic_string()))
892 {
893 def_path.replace_extension(ex::get_format<scene_prefab>(false));
894 }
895
896 return def_path;
897}
898
899auto save_scene_as_impl(rtti::context& ctx, fs::path& path, const std::string& default_name = {}) -> bool
900{
901 auto& ev = ctx.get_cached<events>();
902 auto& play = ctx.get_cached<play_mode>();
903 if(play.is_active())
904 {
905 return false;
906 }
907
908 auto& em = ctx.get_cached<editing_manager>();
909 if(em.is_prefab_mode())
910 {
911 em.save_prefab_changes(ctx);
912 return true;
913 }
914
915 auto save_path = fs::resolve_protocol("app:/data/").string();
916
917 if(!default_name.empty())
918 {
919 auto def_path = add_extension_if_missing(default_name);
920
921 save_path += def_path.string();
922 }
923
924 std::string picked;
925 if(native::save_file_dialog(picked,
927 "Scene files",
928 "Save scene as",
929 save_path))
930 {
931 auto& em = ctx.get_cached<editing_manager>();
932
933 path = add_extension_if_missing(picked);
934
935 return save_scene_impl(ctx, path);
936 }
937
938 return false;
939}
940
941void try_delete_empty_parents(const fs::path& start, const fs::path& root, fs::error_code& ec)
942{
943 fs::path current = start.parent_path();
944 while(current != root && fs::is_empty(current, ec))
945 {
946 APPLOG_TRACE("Removing Empty Parent Directory {}", current.generic_string());
947 fs::remove(current, ec);
948 current = current.parent_path();
949 }
950}
951
952void remove_unreferenced_files(const fs::path& root)
953{
954 fs::error_code ec;
955 const fs::recursive_directory_iterator end;
956
957 std::vector<fs::path> deleted_dirs;
958
959 // First pass: remove matching script files
960 {
961 fs::recursive_directory_iterator it(root, ec);
962 while(it != end)
963 {
964 const fs::path current_path = it->path();
965 ++it;
966
967 for(const auto& type : ex::get_suported_formats<script>())
968 {
969 auto ext = fs::reduce_trailing_extensions(current_path).extension().generic_string();
970 if(ext == type)
971 {
972 APPLOG_TRACE("Removing Script {}", current_path.generic_string());
973 fs::remove(current_path, ec);
974 deleted_dirs.push_back(current_path.parent_path());
975 break;
976 }
977 }
978 }
979 }
980
981 // Second pass: remove manifest files
982 {
983 fs::recursive_directory_iterator it(root, ec);
984 while(it != end)
985 {
986 const fs::path current_path = it->path();
987 ++it;
988
989 if(current_path.extension().generic_string() == ".manifest")
990 {
991 APPLOG_TRACE("Removing Manifest {}", current_path.generic_string());
992 fs::remove(current_path, ec);
993 }
994
995 if(current_path.extension().generic_string() == ".temp")
996 {
997 APPLOG_TRACE("Removing Temp File {}", current_path.generic_string());
998 fs::remove(current_path, ec);
999 }
1000 }
1001 }
1002
1003 // Third pass: remove now-empty directories
1004 {
1005 fs::recursive_directory_iterator it(root, ec);
1006 while(it != end)
1007 {
1008 const fs::path current_path = it->path();
1009 ++it;
1010
1011 if(fs::is_directory(current_path, ec) && fs::is_empty(current_path, ec))
1012 {
1013 APPLOG_TRACE("Removing Empty Directory {}", current_path.generic_string());
1014 fs::remove(current_path, ec);
1015 deleted_dirs.push_back(current_path.parent_path());
1016 }
1017 }
1018 }
1019
1020 // Deduplicate deleted parent paths and sort deepest first
1021 std::sort(deleted_dirs.begin(), deleted_dirs.end());
1022 deleted_dirs.erase(std::unique(deleted_dirs.begin(), deleted_dirs.end()), deleted_dirs.end());
1023 std::sort(deleted_dirs.begin(),
1024 deleted_dirs.end(),
1025 [](const fs::path& a, const fs::path& b)
1026 {
1027 return a.string().size() > b.string().size();
1028 });
1029
1030 // Final cleanup: walk up and try deleting empty parents
1031 for(const auto& path : deleted_dirs)
1032 {
1033 try_delete_empty_parents(path, root, ec);
1034 }
1035}
1036
1037} // namespace
1038
1040{
1041 auto& play = ctx.get_cached<play_mode>();
1042 if(play.is_active())
1043 {
1044 return false;
1045 }
1046 prompt_save_scene(ctx,
1047 [&ctx]()
1048 {
1051 {
1053 });
1054 });
1055
1056 return true;
1057}
1058
1060 const asset_handle<scene_prefab>& asset,
1061 std::string* error) -> bool
1062{
1063 auto& em = ctx.get_cached<editing_manager>();
1064 em.clear();
1065
1066 auto& ec = ctx.get_cached<ecs>();
1067 ec.unload_scene();
1068
1069 auto& scene = ec.get_scene();
1070 if(!scene.load_from(asset))
1071 {
1072 if(error)
1073 {
1074 *error = "Failed to load scene: " + asset.id();
1075 }
1076 return false;
1077 }
1078
1079 em.sync_prefab_instances(ctx, &scene);
1080 em.clear_unsaved_changes();
1081
1082 if(ctx.has<project_manager>())
1083 {
1084 auto& pm = ctx.get_cached<project_manager>();
1085 pm.get_project_editor_settings().scene.opened_scene = asset;
1086 pm.save_project_editor_settings();
1087 }
1088 return true;
1089}
1090
1092{
1094
1095 auto& em = ctx.get_cached<editing_manager>();
1096 em.clear();
1097
1098 auto& ec = ctx.get_cached<ecs>();
1099 ec.unload_scene();
1100
1101 defaults::create_scene_from_preset(ctx, ec.get_scene(), preset);
1102 em.clear_unsaved_changes();
1103
1104 if(ctx.has<project_manager>())
1105 {
1106 auto& pm = ctx.get_cached<project_manager>();
1107 pm.get_project_editor_settings().scene.opened_scene = {};
1108 pm.save_project_editor_settings();
1109 }
1110 return true;
1111}
1112
1114 const fs::path& path,
1115 bool update_source,
1116 bool show_notification) -> bool
1117{
1118 auto& play = ctx.get_cached<play_mode>();
1119 if(play.is_active())
1120 {
1121 return false;
1122 }
1123
1124 fs::path absolute = path;
1125 if(fs::has_known_protocol(path))
1126 {
1127 absolute = fs::resolve_protocol(path);
1128 }
1129 absolute = fs::absolute(absolute);
1130
1131 auto& ec = ctx.get_cached<ecs>();
1132 auto& scene = ec.get_scene();
1133 if(!asset_writer::atomic_save_to_file(absolute.string(), scene))
1134 {
1135 return false;
1136 }
1137
1138 auto& em = ctx.get_cached<editing_manager>();
1140
1141 if(show_notification)
1142 {
1144 }
1145
1146 if(update_source)
1147 {
1148 auto& am = ctx.get_cached<asset_manager>();
1149 const auto protocol_key = fs::convert_to_protocol(absolute).generic_string();
1150 scene.source = am.get_asset<scene_prefab>(protocol_key);
1151
1152 if(ctx.has<project_manager>())
1153 {
1154 auto& pm = ctx.get_cached<project_manager>();
1155 pm.get_project_editor_settings().scene.opened_scene = scene.source;
1156 pm.save_project_editor_settings();
1157 }
1158 }
1159
1160 return true;
1161}
1163{
1164 auto& ev = ctx.get_cached<events>();
1165 auto& play = ctx.get_cached<play_mode>();
1166 if(play.is_active())
1167 {
1168 play.set_active(ctx, false);
1169 }
1170
1171 std::string picked;
1172 if(native::open_file_dialog(picked,
1174 "Scene files",
1175 "Open scene",
1176 fs::resolve_protocol("app:/data/").string()))
1177 {
1178 auto path = fs::convert_to_protocol(picked);
1179 if(ex::is_format<scene_prefab>(path.extension().generic_string()))
1180 {
1181 auto& am = ctx.get_cached<asset_manager>();
1182 auto asset = am.get_asset<scene_prefab>(path.string());
1183
1184 return open_scene_from_asset(ctx, asset);
1185 }
1186 }
1187 return false;
1188}
1189
1191{
1192 return prompt_save_scene(ctx,
1193 [&ctx, asset]()
1194 {
1195 std::string error;
1197 {
1199 }
1200 });
1201}
1203{
1204 auto& ec = ctx.get_cached<ecs>();
1205 auto& scene = ec.get_scene();
1206 auto& em = ctx.get_cached<editing_manager>();
1207
1208 if(em.is_prefab_mode())
1209 {
1210 em.save_prefab_changes(ctx);
1211 return true;
1212 }
1213
1214 if(!scene.source)
1215 {
1216 fs::path picked;
1217 if(save_scene_as_impl(ctx, picked, "Scene3D"))
1218 {
1219 auto path = fs::convert_to_protocol(picked);
1220
1221 auto& am = ctx.get_cached<asset_manager>();
1222 scene.source = am.get_asset<scene_prefab>(path.string());
1223 return true;
1224 }
1225 }
1226 else
1227 {
1228 auto path = fs::resolve_protocol(scene.source.id());
1229 return save_scene_impl(ctx, path);
1230 }
1231
1232 return false;
1233}
1235{
1236 auto& ec = ctx.get_cached<ecs>();
1237 auto& scene = ec.get_scene();
1238
1239 fs::path p;
1240 return save_scene_as_impl(ctx, p, scene.source.name());
1241}
1242
1243auto editor_actions::prompt_save_scene(rtti::context& ctx, const std::function<void()>& on_continue) -> bool
1244{
1245 auto& ev = ctx.get_cached<events>();
1246 auto& play = ctx.get_cached<play_mode>();
1247 if(play.is_active())
1248 {
1249 on_continue();
1250 return false;
1251 }
1252
1253 auto& em = ctx.get_cached<editing_manager>();
1254 if(!em.has_unsaved_changes())
1255 {
1256 on_continue();
1257 return true;
1258 }
1259
1260 ImBox::ShowSaveConfirmation("Save scene?",
1261 "Do you want to save the changes you made?",
1262 [&ctx, on_continue](ImBox::ModalResult result)
1263 {
1264 if(result == ImBox::ModalResult::Save)
1265 {
1266 save_scene(ctx);
1267 }
1268
1269 if(result != ImBox::ModalResult::Cancel)
1270 {
1271 on_continue();
1272 }
1273 });
1274
1275 return true;
1276}
1277
1279{
1280 auto& ev = ctx.get_cached<events>();
1281 auto& play = ctx.get_cached<play_mode>();
1282 if(play.is_active())
1283 {
1284 return false;
1285 }
1286
1287 prompt_save_scene(ctx, [&ctx]() {
1288 auto& pm = ctx.get_cached<project_manager>();
1289 pm.close_project(ctx);
1290 });
1291
1292 return true;
1293}
1294
1296{
1297 auto& ev = ctx.get_cached<events>();
1298 auto& play = ctx.get_cached<play_mode>();
1299 if(play.is_active())
1300 {
1301 return false;
1302 }
1303 auto& pm = ctx.get_cached<project_manager>();
1304 if(!pm.has_open_project())
1305 {
1306 return false;
1307 }
1308 auto project_path = fs::resolve_protocol("app:/");
1309
1310 pm.close_project(ctx);
1311
1312 auto& em = ctx.get_cached<editing_manager>();
1313 em.queue_action("Reload Project", [&ctx, &pm, project_path]()
1314 {
1315 pm.open_project(ctx, project_path);
1316 });
1317 return true;
1318}
1319
1320void editor_actions::run_project(const fs::path& executable_path)
1321{
1322 subprocess::call(executable_path.string());
1323}
1324
1326{
1327 auto& pm = ctx.get_cached<project_manager>();
1328 auto& settings = pm.get_settings();
1329 bool valid_location = fs::is_directory(params.deploy_location);
1330 bool valid_startup_scene = settings.standalone.startup_scene.is_valid();
1331 return valid_location && valid_startup_scene;
1332}
1333
1334
1336 const deploy_settings& params) -> std::map<std::string, tpp::shared_future<void>>
1337{
1338 auto& th = ctx.get_cached<threader>();
1339
1340 std::map<std::string, tpp::shared_future<void>> jobs;
1341 std::vector<tpp::shared_future<void>> jobs_seq;
1342
1343 fs::error_code ec;
1344
1345 auto& am = ctx.get_cached<asset_manager>();
1346
1347 auto& pm = ctx.get_cached<project_manager>();
1348 auto project_name = pm.get_name();
1349 auto executable_path = params.deploy_location / (project_name + fs::executable_extension());
1350
1351 // am.get_database("engine:/")
1352
1353 if(params.deploy_dependencies)
1354 {
1355 APPLOG_INFO("Clearing {}", params.deploy_location.generic_string());
1356 fs::remove_all(params.deploy_location, ec);
1357 fs::create_directories(params.deploy_location, ec);
1358
1359 auto job =
1360 th.pool
1361 ->schedule("Deploying Dependencies",
1362 [params, executable_path]()
1363 {
1364 APPLOG_INFO("Deploying Dependencies...");
1365
1366 fs::path app_executable =
1367 fs::resolve_protocol("binary:/" + std::string(PLAYER_NAME) + fs::executable_extension());
1368 auto deps = get_dependencies(app_executable);
1369
1370 fs::error_code ec;
1371 for(const auto& dep : deps)
1372 {
1373 APPLOG_TRACE("Copying {} -> {}",
1374 fs::path(dep).generic_string(),
1375 params.deploy_location.generic_string());
1376 fs::copy(dep, params.deploy_location, fs::copy_options::overwrite_existing, ec);
1377 }
1378
1379
1380 APPLOG_TRACE("Copying {} -> {}",
1381 app_executable.generic_string(),
1382 params.deploy_location.generic_string());
1383 fs::copy(app_executable, executable_path, fs::copy_options::overwrite_existing, ec);
1384
1385 APPLOG_INFO("Deploying Dependencies - Done");
1386 })
1387 .share();
1388 jobs["Deploying Dependencies"] = job;
1389 jobs_seq.emplace_back(job);
1390 }
1391
1392 {
1393 auto job = th.pool
1394 ->schedule("Deploying Project Settings",
1395 [params]()
1396 {
1397 APPLOG_INFO("Deploying Project Settings...");
1398
1399 auto data = fs::resolve_protocol("app:/settings");
1400 fs::path dst = params.deploy_location / "data" / "app" / "settings";
1401
1402 fs::error_code ec;
1403
1404 APPLOG_TRACE("Clearing {}", dst.generic_string());
1405 fs::remove_all(dst, ec);
1406 fs::create_directories(dst, ec);
1407
1408 APPLOG_TRACE("Copying {} -> {}", data.generic_string(), dst.generic_string());
1409 fs::copy(data, dst, fs::copy_options::recursive, ec);
1410
1411 APPLOG_INFO("Deploying Project Settings - Done");
1412 })
1413 .share();
1414
1415 jobs["Deploying Project Settings"] = job;
1416 jobs_seq.emplace_back(job);
1417 }
1418
1419 {
1420 auto job =
1421 th.pool
1422 ->schedule(
1423 "Deploying Project Data",
1424 [params, &am]()
1425 {
1426 APPLOG_INFO("Deploying Project Data...");
1427
1428 fs::error_code ec;
1429 {
1431 fs::path cached_data =
1432 params.deploy_location / "data" / "app" / ex::get_compiled_directory_no_slash();
1433
1434 APPLOG_TRACE("Clearing {}", cached_data.generic_string());
1435 fs::remove_all(cached_data, ec);
1436 fs::create_directories(cached_data, ec);
1437
1438 APPLOG_TRACE("Copying {} -> {}", data.generic_string(), cached_data.generic_string());
1439 fs::copy(data, cached_data, fs::copy_options::recursive, ec);
1440
1441 remove_unreferenced_files(cached_data);
1442 }
1443
1444 {
1445 fs::path cached_data = params.deploy_location / "data" / "app" / "assets.pack";
1446 APPLOG_TRACE("Creating Asset Pack -> {}", cached_data.generic_string());
1447 am.save_database("app:/", cached_data);
1448 }
1449
1450 APPLOG_INFO("Deploying Project Data - Done");
1451 })
1452 .share();
1453
1454 jobs["Deploying Project Data"] = job;
1455 jobs_seq.emplace_back(job);
1456 }
1457
1458 {
1459 auto job =
1460 th.pool
1461 ->schedule(
1462 "Deploying Engine Data",
1463 [params, &am]()
1464 {
1465 APPLOG_INFO("Deploying Engine Data...");
1466
1467 fs::error_code ec;
1468 {
1469 fs::path cached_data =
1470 params.deploy_location / "data" / "engine" / ex::get_compiled_directory_no_slash();
1471 auto data = fs::resolve_protocol(ex::get_compiled_directory("engine"));
1472
1473 APPLOG_TRACE("Clearing {}", cached_data.generic_string());
1474 fs::remove_all(cached_data, ec);
1475 fs::create_directories(cached_data, ec);
1476
1477 APPLOG_TRACE("Copying {} -> {}", data.generic_string(), cached_data.generic_string());
1478 fs::copy(data, cached_data, fs::copy_options::recursive, ec);
1479
1480 remove_unreferenced_files(cached_data);
1481 }
1482
1483 {
1484 fs::path cached_data = params.deploy_location / "data" / "engine" / "assets.pack";
1485 APPLOG_TRACE("Creating Asset Pack -> {}", cached_data.generic_string());
1486 am.save_database("engine:/", cached_data);
1487 }
1488
1489 APPLOG_INFO("Deploying Engine Data - Done");
1490 })
1491 .share();
1492 jobs["Deploying Engine Data..."] = job;
1493 jobs_seq.emplace_back(job);
1494 }
1495
1496#if DOTNETPP_BACKEND_MONO
1497 {
1498 auto job =
1499 th.pool
1500 ->schedule(
1501 "Deploying Mono",
1502 [params, &am, &ctx]()
1503 {
1504 APPLOG_INFO("Deploying Mono...");
1505
1506 auto paths = script_system::find_dotnet_paths(ctx);
1507 fs::path assembly_path = dotnet::get_core_assembly_path();
1508 fs::path assembly_dir = assembly_path.parent_path();
1509 fs::path lib_version = assembly_dir.filename();
1510
1511 fs::path assembly_dir_gac = assembly_dir.parent_path() / "gac";
1512
1513 fs::error_code ec;
1514
1515 {
1516 fs::path cached_data = params.deploy_location / "data" / "engine" / "mono" / "lib";
1517
1518 APPLOG_TRACE("Clearing {}", cached_data.generic_string());
1519 // fs::remove_all(cached_data, ec);
1520
1521 APPLOG_TRACE("Creating directories {}", cached_data.generic_string());
1522 fs::create_directories(cached_data, ec);
1523
1524 auto mono_libraries = dotnet::get_common_library_names_for_deploy();
1525
1526 fs::path lib_dir = assembly_dir.parent_path().parent_path();
1527 for(const auto& path : mono_libraries)
1528 {
1529 fs::path so_file = lib_dir / path;
1530 if(fs::exists(so_file))
1531 {
1532 auto dst = cached_data / path;
1533 APPLOG_TRACE("Copying {} -> {}", so_file.generic_string(), dst.generic_string());
1534 fs::copy(so_file, dst, fs::copy_options::overwrite_existing, ec);
1535 }
1536 }
1537
1538
1539 cached_data /= "mono";
1540
1541 APPLOG_TRACE("Clearing {}", cached_data.generic_string());
1542 fs::remove_all(cached_data, ec);
1543
1544 fs::path cached_data_lib_version = cached_data / lib_version;
1545 fs::path cached_data_gac = cached_data / "gac";
1546
1547 fs::create_directories(cached_data, ec);
1548
1549 APPLOG_TRACE("Copying {} -> {}",
1550 assembly_dir.generic_string(),
1551 cached_data.generic_string());
1552 fs::copy(assembly_dir, cached_data_lib_version, fs::copy_options::recursive, ec);
1553
1554 fs::copy(assembly_dir_gac, cached_data_gac, fs::copy_options::recursive, ec);
1555 }
1556
1557 fs::path config_dir = paths.config_dir;
1558 config_dir /= "mono";
1559
1560 {
1561 fs::path cached_data = params.deploy_location / "data" / "engine" / "mono" / "etc";
1562 cached_data /= "mono";
1563
1564 APPLOG_TRACE("Clearing {}", cached_data.generic_string());
1565 fs::remove_all(cached_data, ec);
1566 fs::create_directories(cached_data, ec);
1567
1568 APPLOG_TRACE("Copying {} -> {}", config_dir.generic_string(), cached_data.generic_string());
1569 fs::copy(config_dir, cached_data, fs::copy_options::recursive, ec);
1570 }
1571
1572 APPLOG_INFO("Deploying Mono - Done");
1573 })
1574 .share();
1575 jobs["Deploying Mono..."] = job;
1576 jobs_seq.emplace_back(job);
1577 }
1578#else
1579 {
1580 auto job =
1581 th.pool
1582 ->schedule(
1583 "Deploying .NET",
1584 [params]()
1585 {
1586 APPLOG_INFO("Deploying .NET...");
1587
1588 fs::error_code ec;
1589
1590 // The managed bridge payload (Clrpp.Managed.dll + runtimeconfig +
1591 // optional NuGet deps) is self-contained in one folder. Ship it
1592 // next to the bundled dotnet root; the game passes this location
1593 // to the runtime at init (compiler_paths::assembly_dir).
1594 {
1595 const std::string runtime_dir = dotnet::managed_runtime_dir();
1596 fs::path src = fs::resolve_protocol("binary:/" + runtime_dir);
1597 fs::path dst = params.deploy_location / "data" / "engine" / runtime_dir;
1598
1599 APPLOG_TRACE("Clearing {}", dst.generic_string());
1600 fs::remove_all(dst, ec);
1601 fs::create_directories(dst, ec);
1602
1603 APPLOG_TRACE("Copying {} -> {}", src.generic_string(), dst.generic_string());
1604 fs::copy(src, dst, fs::copy_options::recursive, ec);
1605 }
1606
1607 // Bundle a pruned dotnet root (hostfxr + shared framework) so the
1608 // deployed game runs without a machine-wide .NET install. The game
1609 // passes this folder to the runtime as the dotnet root override.
1610 {
1611 fs::path dotnet_root = dotnet::get_core_assembly_path();
1612
1613 // Prefer the runtime major we target (see
1614 // dotnet::get_dotnet_version); fall back to the
1615 // newest installed one.
1616 int preferred_major = version_major(dotnet::get_dotnet_version());
1617
1618 fs::path fxr_src =
1619 pick_highest_version_dir(dotnet_root / "host" / "fxr", preferred_major);
1620 fs::path shared_src =
1621 pick_highest_version_dir(dotnet_root / "shared" / "Microsoft.NETCore.App",
1622 preferred_major);
1623
1624 if(fxr_src.empty() || shared_src.empty())
1625 {
1626 APPLOG_WARNING("Deploying .NET - could not locate hostfxr/shared framework "
1627 "under {}; the deployed game will require an installed .NET "
1628 "runtime",
1629 dotnet_root.generic_string());
1630 }
1631 else
1632 {
1633 fs::path runtime_dst = params.deploy_location / "data" / "engine" / "dotnet";
1634
1635 APPLOG_TRACE("Clearing {}", runtime_dst.generic_string());
1636 fs::remove_all(runtime_dst, ec);
1637
1638 fs::path fxr_dst = runtime_dst / "host" / "fxr" / fxr_src.filename();
1639 fs::path shared_dst =
1640 runtime_dst / "shared" / "Microsoft.NETCore.App" / shared_src.filename();
1641
1642 fs::create_directories(fxr_dst, ec);
1643 fs::create_directories(shared_dst, ec);
1644
1645 APPLOG_TRACE("Copying {} -> {}",
1646 fxr_src.generic_string(),
1647 fxr_dst.generic_string());
1648 fs::copy(fxr_src, fxr_dst, fs::copy_options::recursive, ec);
1649
1650 APPLOG_TRACE("Copying {} -> {}",
1651 shared_src.generic_string(),
1652 shared_dst.generic_string());
1653 fs::copy(shared_src, shared_dst, fs::copy_options::recursive, ec);
1654 }
1655 }
1656
1657 APPLOG_INFO("Deploying .NET - Done");
1658 })
1659 .share();
1660 jobs["Deploying .NET..."] = job;
1661 jobs_seq.emplace_back(job);
1662 }
1663#endif
1664
1665 tpp::when_all(std::begin(jobs_seq), std::end(jobs_seq))
1666 .then(tpp::this_thread::get_id(),
1667 [params, executable_path](auto f)
1668 {
1669 if(params.deploy_and_run)
1670 {
1671 run_project(executable_path);
1672 }
1673 else
1674 {
1675 fs::show_in_graphical_env(params.deploy_location);
1676 }
1677 });
1678
1679 return jobs;
1680}
1681
1683{
1684 auto& ctx = engine::context();
1685 auto& pm = ctx.get_cached<project_manager>();
1686 auto project_name = pm.get_name();
1687 const auto& editor_settings = pm.get_editor_settings();
1688
1689 fs::error_code err;
1690
1691 auto workspace_folder = fs::resolve_protocol("app:/.vscode");
1692 fs::create_directories(workspace_folder, err);
1693
1694 auto formats = ex::get_all_formats();
1695 formats.emplace_back(std::vector<std::string>{".meta"});
1696 formats.emplace_back(std::vector<std::string>{".asset"});
1697 formats.emplace_back(std::vector<std::string>{".manifest"});
1698 formats.emplace_back(std::vector<std::string>{".temp"});
1699
1700 remove_extensions(formats, ex::get_suported_formats<gfx::shader>());
1701 remove_extensions(formats, ex::get_suported_formats<script>());
1702 remove_extensions(formats, ex::get_suported_formats<ui_tree>());
1703 remove_extensions(formats, ex::get_suported_formats<style_sheet>());
1704
1705 auto workspace_file = workspace_folder / fmt::format("{}-workspace.code-workspace", project_name);
1706 generate_workspace_file(workspace_file.string(), formats, editor_settings);
1707
1708 auto source_path = fs::resolve_protocol("app:/data");
1709
1710 auto engine_dep = fs::resolve_protocol(script_system::get_lib_compiled_key("engine"));
1711
1712 auto output_path = fs::resolve_protocol("app:/");
1713
1714#if DOTNETPP_BACKEND_MONO
1715 generate_csproj_legacy(source_path, {engine_dep}, output_path, project_name);
1716#else
1717 generate_csproj(source_path, {engine_dep}, output_path, project_name);
1718#endif
1719}
1720
1721void editor_actions::open_workspace_on_file(const fs::path& file, int line)
1722{
1723 auto& ctx = engine::context();
1724 auto& pm = ctx.get_cached<project_manager>();
1725 auto project_name = pm.get_name();
1726 auto vscode_exe = pm.get_editor_settings().external_tools.vscode_executable;
1727 tpp::async(
1728 [vscode_exe, project_name, file, line]()
1729 {
1730 auto external_tool = vscode_exe;
1731 if(external_tool.empty())
1732 {
1733 external_tool = get_vscode_executable();
1734 }
1735
1736 static const char* tool = "[Visual Studio Code]";
1737 static const char* setup_hint = "Edit -> Editor Settings -> External Tools";
1738
1739 if(external_tool.empty())
1740 {
1741 APPLOG_ERROR("Cannot locate external tool {}", tool);
1742 APPLOG_ERROR("To configure {} visit : {}", tool, setup_hint);
1743 return;
1744 }
1745 auto workspace_key = fmt::format("app:/.vscode/{}-workspace.code-workspace", project_name);
1746 auto workspace_path = fs::resolve_protocol(workspace_key);
1747
1748 auto result = subprocess::call(external_tool.string(),
1749 {workspace_path.string(), "-g", fmt::format("{}:{}", file.string(), line)});
1750
1751 if(result.retcode != 0)
1752 {
1753 APPLOG_ERROR("Cannot open external tool {} for file {}", tool, external_tool.string(), file.string());
1754 APPLOG_ERROR("To configure {} visit : {}", tool, setup_hint);
1755 }
1756 });
1757}
1758
1759void editor_actions::recompile_shaders(const std::string& group)
1760{
1761 auto& ctx = engine::context();
1762 auto& am = ctx.get_cached<asset_manager>();
1763 auto shaders = am.get_assets<gfx::shader>(group);
1765 for(auto& asset : shaders)
1766 {
1767 fs::error_code ec;
1768 auto path = fs::absolute(fs::resolve_protocol(asset.id()).string(), ec);
1769 fs::watcher::touch(path, false);
1770 }
1772}
1773
1774void editor_actions::recompile_textures(const std::string& group)
1775{
1776 auto& ctx = engine::context();
1777 auto& am = ctx.get_cached<asset_manager>();
1778 auto textures = am.get_assets<gfx::texture>(group);
1780 for(auto& asset : textures)
1781 {
1782 fs::error_code ec;
1783 auto path = fs::absolute(fs::resolve_protocol(asset.id()).string(), ec);
1784 fs::watcher::touch(path, false);
1785 }
1787}
1788
1789void editor_actions::recompile_meshes(const std::string& group)
1790{
1791 auto& ctx = engine::context();
1792 auto& am = ctx.get_cached<asset_manager>();
1793 auto meshes = am.get_assets<mesh>(group);
1795 for(auto& asset : meshes)
1796 {
1797 fs::error_code ec;
1798 auto path = fs::absolute(fs::resolve_protocol(asset.id()).string(), ec);
1799 fs::watcher::touch(path, false);
1800 }
1802}
1803
1804void editor_actions::recompile_ui(const std::string& group)
1805{
1806 auto& ctx = engine::context();
1807 auto& am = ctx.get_cached<asset_manager>();
1809 {
1810 auto assets = am.get_assets<ui_tree>(group);
1811 for(auto& asset : assets)
1812 {
1813 fs::error_code ec;
1814 auto path = fs::absolute(fs::resolve_protocol(asset.id()).string(), ec);
1815 fs::watcher::touch(path, false);
1816 }
1817 }
1818 {
1819 auto assets = am.get_assets<style_sheet>(group);
1820 for(auto& asset : assets)
1821 {
1822 fs::error_code ec;
1823 auto path = fs::absolute(fs::resolve_protocol(asset.id()).string(), ec);
1824 fs::watcher::touch(path, false);
1825 }
1826 }
1827
1829}
1830void editor_actions::recompile_scripts(const std::string& group)
1831{
1832 auto& ctx = engine::context();
1833 auto& am = ctx.get_cached<asset_manager>();
1834 auto scripts = am.get_assets<script>(group);
1836 for(auto& asset : scripts)
1837 {
1838 fs::error_code ec;
1839 auto path = fs::absolute(fs::resolve_protocol(asset.id()).string(), ec);
1840 fs::watcher::touch(path, false);
1841 }
1843}
1844void editor_actions::recompile_all(const std::string& group)
1845{
1846 auto& ctx = engine::context();
1847 auto& am = ctx.get_cached<asset_manager>();
1848 auto assets = am.get_all_assets(group);
1850 for(auto& asset : assets)
1851 {
1852 fs::error_code ec;
1853 auto path = fs::absolute(fs::resolve_protocol(asset).string(), ec);
1854 fs::watcher::touch(path, false);
1855 }
1857}
1858
1859auto editor_actions::rebuild_reflection_probes(rtti::context& /*ctx*/, bool force_full_first_frame) -> size_t
1860{
1861 size_t count = 0;
1862 for(auto* scn : scene::get_all_scenes())
1863 {
1864 if(!scn || !scn->registry)
1865 {
1866 continue;
1867 }
1868
1869 count += reflection_probe_system::mark_all_dirty(*scn, force_full_first_frame);
1870 }
1871 return count;
1872}
1873
1875{
1876 auto& play = ctx.get_cached<play_mode>();
1877 if(play.is_active())
1878 {
1879 return true;
1880 }
1881 auto& scripting = ctx.get_cached<script_system>();
1882 if(scripting.has_compilation_errors())
1883 {
1884 if(error)
1885 {
1886 *error = "All compiler errors must be fixed before you can enter Play Mode!";
1887 }
1888 return false;
1889 }
1890 return true;
1891}
1892
1894{
1895 play_state_info info;
1896 if(!ctx.has<play_mode>())
1897 {
1898 return info;
1899 }
1900 auto& play = ctx.get_cached<play_mode>();
1901 info.is_active = play.is_active();
1902 info.is_paused = play.is_paused();
1903 info.is_splash = play.is_splash();
1904 info.is_simulation_running = play.is_simulation_running();
1905 info.frames_running = play.frames_running();
1906 if(play.is_splash())
1907 {
1908 info.phase = "splash";
1909 }
1910 else if(play.is_simulation_running())
1911 {
1912 info.phase = "running";
1913 }
1914 else if(play.is_active())
1915 {
1916 info.phase = "active";
1917 }
1918 else
1919 {
1920 info.phase = "inactive";
1921 }
1922 return info;
1923}
1924
1925auto editor_actions::set_play_active(rtti::context& ctx, bool active, bool allow_splash, std::string* error) -> bool
1926{
1927 if(active && !can_enter_play(ctx, error))
1928 {
1929 return false;
1930 }
1931 ctx.get_cached<play_mode>().set_active(ctx, active, allow_splash);
1932 return true;
1933}
1934
1935auto editor_actions::toggle_play(rtti::context& ctx, bool allow_splash, std::string* error) -> bool
1936{
1937 auto& play = ctx.get_cached<play_mode>();
1938 if(!play.is_active() && !can_enter_play(ctx, error))
1939 {
1940 return false;
1941 }
1942 play.toggle(ctx, allow_splash);
1943 return true;
1944}
1945
1946auto editor_actions::set_play_paused(rtti::context& ctx, bool paused, std::string* error) -> bool
1947{
1948 auto& play = ctx.get_cached<play_mode>();
1949 if(!play.is_active())
1950 {
1951 if(error)
1952 {
1953 *error = "Play mode is not active";
1954 }
1955 return false;
1956 }
1957 play.set_paused(ctx, paused);
1958 return true;
1959}
1960
1962{
1963 auto& play = ctx.get_cached<play_mode>();
1964 if(!play.is_active())
1965 {
1966 if(error)
1967 {
1968 *error = "Play mode is not active";
1969 }
1970 return false;
1971 }
1972 if(!play.is_paused())
1973 {
1974 if(error)
1975 {
1976 *error = "Play mode must be paused to skip a frame";
1977 }
1978 return false;
1979 }
1980 play.skip_next_frame(ctx);
1981 return true;
1982}
1983
1985{
1986 selection_info info;
1987 auto& em = ctx.get_cached<editing_manager>();
1988 if(auto* active = em.try_get_active_selection_as<entt::handle>())
1989 {
1990 info.active_entity_id = entity_id_string(*active);
1991 }
1992 for(const auto& handle : em.try_get_selections_as_copy<entt::handle>())
1993 {
1994 if(handle)
1995 {
1996 info.entity_ids.push_back(entity_id_string(handle));
1997 }
1998 }
1999 return info;
2000}
2001
2003 const std::vector<std::string>& entity_ids,
2004 bool add,
2005 std::string* error) -> bool
2006{
2007 auto& em = ctx.get_cached<editing_manager>();
2008 auto* scn = em.get_active_scene(ctx);
2009 if(!scn || !scn->registry)
2010 {
2011 if(error)
2012 {
2013 *error = "No active scene";
2014 }
2015 return false;
2016 }
2017 if(!add)
2018 {
2019 em.unselect();
2020 }
2021 bool any = false;
2022 for(const auto& id : entity_ids)
2023 {
2024 auto entity = find_entity_by_id(*scn, id);
2025 if(!entity)
2026 {
2027 if(error)
2028 {
2029 *error = "Entity not found: " + id;
2030 }
2031 return false;
2032 }
2034 em.select(entity, mode);
2035 any = true;
2036 }
2037 if(!any && !add)
2038 {
2039 em.unselect();
2040 }
2041 return true;
2042}
2043
2045{
2046 ctx.get_cached<editing_manager>().unselect();
2047}
2048
2050 level::level_enum min_level,
2051 size_t max_count,
2052 uint64_t after_id) -> std::vector<log_query_entry>
2053{
2054 if(!ctx.has<hub>())
2055 {
2056 return {};
2057 }
2058 auto snapshot = ctx.get_cached<hub>().get_panels().get_console_log_panel().snapshot_logs(min_level,
2059 max_count,
2060 after_id);
2061 std::vector<log_query_entry> out;
2062 out.reserve(snapshot.size());
2063 for(auto& entry : snapshot)
2064 {
2065 log_query_entry item;
2066 item.id = entry.id;
2067 item.level = entry.level;
2068 item.text = std::move(entry.text);
2069 item.filename = std::move(entry.filename);
2070 item.funcname = std::move(entry.funcname);
2071 item.line = entry.line;
2072 out.push_back(std::move(item));
2073 }
2074 return out;
2075}
2076
2078 const std::string& entity_id,
2079 bool include_components,
2080 std::string* error) -> std::string
2081{
2082 auto& em = ctx.get_cached<editing_manager>();
2083 auto* scn = em.get_active_scene(ctx);
2084 if(!scn || !scn->registry)
2085 {
2086 if(error)
2087 {
2088 *error = "No active scene";
2089 }
2090 return {};
2091 }
2092 entt::handle entity;
2093 if(entity_id.empty())
2094 {
2095 if(auto* active = em.try_get_active_selection_as<entt::handle>())
2096 {
2097 entity = *active;
2098 }
2099 if(!entity)
2100 {
2101 if(error)
2102 {
2103 *error = "No entity_id provided and no active entity selection";
2104 }
2105 return {};
2106 }
2107 }
2108 else
2109 {
2110 entity = find_entity_by_id(*scn, entity_id);
2111 if(!entity)
2112 {
2113 if(error)
2114 {
2115 *error = "Entity not found: " + entity_id;
2116 }
2117 return {};
2118 }
2119 }
2120 auto summary = entity_to_summary_json(entity, 0, 0);
2121 if(!include_components)
2122 {
2123 return summary;
2124 }
2125 auto components = entity_components_serialized(entity);
2126 std::string escaped;
2127 escaped.reserve(components.size() + 8);
2128 for(char c : components)
2129 {
2130 switch(c)
2131 {
2132 case '\\':
2133 escaped += "\\\\";
2134 break;
2135 case '"':
2136 escaped += "\\\"";
2137 break;
2138 case '\n':
2139 escaped += "\\n";
2140 break;
2141 case '\r':
2142 escaped += "\\r";
2143 break;
2144 case '\t':
2145 escaped += "\\t";
2146 break;
2147 default:
2148 escaped += c;
2149 break;
2150 }
2151 }
2152 if(!summary.empty() && summary.back() == '}')
2153 {
2154 summary.pop_back();
2155 summary += ",\"components_serialized\":\"" + escaped + "\"}";
2156 }
2157 return summary;
2158}
2159
2161{
2162 if(!ctx.has<hub>())
2163 {
2164 if(error)
2165 {
2166 *error = "Hub is not available";
2167 }
2168 return false;
2169 }
2170 auto& panel = ctx.get_cached<hub>().get_panels().get_scene_panel();
2171 panel.set_visible(true);
2172 panel.focus();
2173 return true;
2174}
2175
2177{
2178 if(!ctx.has<hub>())
2179 {
2180 if(error)
2181 {
2182 *error = "Hub is not available";
2183 }
2184 return false;
2185 }
2186 auto& panel = ctx.get_cached<hub>().get_panels().get_game_panel();
2187 panel.set_visible(true);
2188 panel.focus();
2189 return true;
2190}
2191
2193{
2194 if(!ctx.has<renderer>())
2195 {
2196 if(error)
2197 {
2198 *error = "Renderer is not available";
2199 }
2200 return false;
2201 }
2202 auto* main_window = ctx.get_cached<renderer>().get_main_window();
2203 if(!main_window)
2204 {
2205 if(error)
2206 {
2207 *error = "Main window is not available";
2208 }
2209 return false;
2210 }
2211 auto& window = main_window->get_window();
2212 if(!window.is_open())
2213 {
2214 if(error)
2215 {
2216 *error = "Main window is not open";
2217 }
2218 return false;
2219 }
2220 if(window.is_minimized())
2221 {
2222 window.restore();
2223 }
2224 window.show();
2225 window.raise();
2226 window.request_focus();
2227 return true;
2228}
2229
2231 const std::vector<std::string>& paths,
2232 const fs::path& target_path,
2233 bool async) -> std::vector<import_files_item>
2234{
2235 auto& ts = ctx.get_cached<threader>();
2236 std::vector<import_files_item> items;
2237 items.reserve(paths.size());
2238 fs::error_code ec;
2239 fs::create_directories(target_path, ec);
2240 auto copy_one = [](const fs::path& source, const fs::path& dest, bool is_directory) -> bool
2241 {
2242 fs::error_code err;
2243 if(is_directory)
2244 {
2245 fs::copy(source, dest, fs::copy_options::recursive | fs::copy_options::overwrite_existing, err);
2246 if(err)
2247 {
2248 APPLOG_ERROR("Failed to import directory {}, error: {}", source.string(), err.message());
2249 return false;
2250 }
2251 return true;
2252 }
2253 asset_writer::atomic_copy_file(source, dest, err);
2254 if(err)
2255 {
2256 APPLOG_ERROR("Failed to import file {}, error: {}", source.string(), err.message());
2257 return false;
2258 }
2259 return true;
2260 };
2261 for(const auto& path : paths)
2262 {
2263 import_files_item item{};
2264 fs::path source = fs::path(path).make_preferred();
2265 fs::path filename = source.filename();
2266 fs::path dest = target_path / filename;
2267 item.source_path = source.generic_string();
2268 item.dest_path = dest.generic_string();
2269 item.is_directory = fs::is_directory(source, ec);
2270 const auto protocol = fs::convert_to_protocol(dest);
2271 if(!protocol.empty())
2272 {
2273 item.dest_key = protocol.generic_string();
2274 }
2275 APPLOG_INFO("Importing {0}", filename.string());
2276 if(async)
2277 {
2278 auto job = ts.pool->schedule("Importing " + filename.extension().string(),
2279 copy_one,
2280 source,
2281 dest,
2282 item.is_directory);
2283 item.future = job.share();
2284 }
2285 else
2286 {
2287 const bool ok = copy_one(source, dest, item.is_directory);
2288 item.future = tpp::make_ready_future<bool>(bool(ok)).share();
2289 }
2290 items.push_back(std::move(item));
2291 }
2292 return items;
2293}
2294
2295auto editor_actions::wait_import_jobs(std::vector<import_files_item>& items,
2296 std::chrono::milliseconds timeout) -> bool
2297{
2298 const auto deadline = std::chrono::steady_clock::now() + timeout;
2299 bool all_ok = true;
2300 for(auto& item : items)
2301 {
2302 if(!item.future.valid())
2303 {
2304 all_ok = false;
2305 continue;
2306 }
2307 const auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(
2308 deadline - std::chrono::steady_clock::now());
2309 if(remaining.count() <= 0)
2310 {
2311 all_ok = false;
2312 break;
2313 }
2314 const auto status = item.future.wait_for(remaining);
2315 if(status != std::future_status::ready)
2316 {
2317 all_ok = false;
2318 break;
2319 }
2320 if(!item.future.get())
2321 {
2322 all_ok = false;
2323 }
2324 }
2325 return all_ok;
2326}
2327} // namespace unravel
entt::handle b
entt::handle a
static void resume()
Resumes all watchers.
Definition watcher.cpp:93
static void pause()
Pauses all watchers.
Definition watcher.cpp:87
static void touch(const fs::path &path, bool recursive, fs::file_time_type time=fs::now())
Sets the last modification time of a file or directory. by default sets the time to the current time.
Definition watcher.cpp:57
Manages assets, including loading, unloading, and storage.
auto get_all_assets(const std::string &group) const -> std::vector< std::string >
Gets all assets.
auto get_assets(const std::string &group={}) const -> std::vector< asset_handle< T > >
Gets all assets in a specified group.
auto get_asset(const std::string &key, load_flags flags=load_flags::standard, load_mode mode=load_mode::immediate) -> asset_handle< T >
Gets an asset by its key.
static void show(std::function< void(defaults::scene_preset)> on_preset_selected)
Request the create scene modal to open.
static void cancel_if_pending()
Dismiss a pending create-scene modal without invoking the callback.
Main class representing a 3D mesh with support for different LODs, submeshes, and skinning.
Definition mesh.h:323
auto get_project_editor_settings() -> project_editor_settings &
auto get_name() const -> const std::string &
void close_project(rtti::context &ctx)
static auto mark_all_dirty(scene &scn, bool force_full_first_frame=false) -> size_t
Flags every reflection probe in the scene for rebuild.
defaults::scene_preset preset
const char * id
std::vector< render_pass_node_item > items
std::string name
Definition hub.cpp:33
@ ImGuiToastType_Success
#define APPLOG_WARNING(...)
Definition logging.h:19
#define APPLOG_ERROR(...)
Definition logging.h:20
#define APPLOG_INFO(...)
Definition logging.h:18
#define APPLOG_TRACE(...)
Definition logging.h:17
std::string error
Definition mcp_async.cpp:33
bool is_local
texture_job_type type
const fs::path * filename
ModalResult
Modal result flags for message box buttons.
auto ShowSaveConfirmation(const std::string &title, const std::string &message, std::function< void(ModalResult)> callback) -> std::shared_ptr< MsgBox >
Show a save confirmation dialog with Save/Don't Save/Cancel buttons.
NOTIFY_INLINE void PushNotification(const ImGuiToast &toast)
Insert a new toast in the list.
auto get_suported_formats< gfx::shader >() -> const std::vector< std::string > &
auto get_all_formats() -> const std::vector< std::vector< std::string > > &
auto get_format(bool include_dot=true) -> std::string
auto get_compiled_directory_no_slash(const std::string &prefix={}) -> std::string
auto is_format(const std::string &ex) -> bool
auto get_compiled_directory(const std::string &prefix={}) -> std::string
auto get_suported_formats() -> const std::vector< std::string > &
auto get_suported_formats_with_wildcard() -> std::vector< std::string >
path reduce_trailing_extensions(const path &_path)
another.
path resolve_protocol(const path &_path)
Given the specified path/filename, resolve the final full filename. This will be based on either the ...
bool has_known_protocol(const path &_path)
Checks whether the path has a known protocol.
path convert_to_protocol(const path &_path)
Oposite of the resolve_protocol this function tries to convert to protocol path from an absolute one.
void end(encoder *_encoder)
Definition graphics.cpp:427
auto to_lower(const std::string &str) -> std::string
Definition utils.cpp:42
auto call(const std::vector< std::string > &args_array, const std::vector< std::string > &environment_array={}) -> call_result
auto atomic_save_to_file(const fs::path &key, const asset_handle< T > &obj) -> bool
auto atomic_copy_file(const fs::path &src, const fs::path &dst, fs::error_code &ec) noexcept -> bool
auto generate_uuid() -> hpp::uuid
Definition uuid.cpp:25
auto entity_id_string(entt::handle entity) -> std::string
Stable id string for MCP/editor tooling (UUID preferred).
auto find_entity_by_id(scene &scn, const std::string &id) -> entt::handle
Resolve an entity by UUID string or integral entt id.
auto entity_components_serialized(entt::handle entity) -> std::string
Full associative serialization of serializeable components (scene-file format).
auto entity_to_summary_json(entt::handle entity, int depth, int max_depth) -> std::string
JSON summary: transform, component pretty-names, optional children.
std::vector< math::vec3 > start
entt::handle entity
Thread-safe handle to an asset.
auto get_cached() -> T &
Definition context.hpp:49
auto has() const -> bool
Definition context.hpp:28
static void create_scene_from_preset(rtti::context &ctx, scene &scn, scene_preset preset)
Creates a 3D scene from a quality preset.
scene_preset
Quality presets for new scene creation (low = less expensive, high = more expensive).
Definition defaults.h:215
Manages the entity-component-system (ECS) operations for the ACE framework.
Definition ecs.h:12
void unload_scene()
Unloads the current scene.
Definition ecs.cpp:25
auto get_active_scene(rtti::context &ctx) -> scene *
void clear(bool clear_unsaved=true)
void queue_action(const std::string &name, const std::function< void()> &action)
static auto close_project(rtti::context &ctx) -> bool
static void run_project(const fs::path &executable_path)
static auto can_enter_play(rtti::context &ctx, std::string *error=nullptr) -> bool
static auto load_scene_from_asset(rtti::context &ctx, const asset_handle< scene_prefab > &asset, std::string *error=nullptr) -> bool
Non-modal scene load (shared by File menu + MCP). Clears edit state, loads asset, syncs prefabs,...
static auto skip_play_frame(rtti::context &ctx, std::string *error=nullptr) -> bool
static auto import_files(rtti::context &ctx, const std::vector< std::string > &paths, const fs::path &target_path, bool async=true) -> std::vector< import_files_item >
Copy external files/folders into target_path (content-browser Import parity).
static void open_workspace_on_file(const fs::path &file, int line=0)
static void recompile_shaders(const std::string &group="")
static void recompile_scripts(const std::string &group="")
static auto save_scene_to_path(rtti::context &ctx, const fs::path &path, bool update_source=true, bool show_notification=true) -> bool
Atomic-save active scene to path/key. When update_source is true, sets scene.source and project opene...
static void recompile_ui(const std::string &group="")
static auto open_scene(rtti::context &ctx) -> bool
static auto open_scene_from_asset(rtti::context &ctx, const asset_handle< scene_prefab > &asset) -> 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 void recompile_textures(const std::string &group="")
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 prompt_save_scene(rtti::context &ctx, const std::function< void()> &on_continue) -> 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 inspect_entity(rtti::context &ctx, const std::string &entity_id, bool include_components, std::string *error=nullptr) -> std::string
static void recompile_all(const std::string &group="")
static auto focus_game_panel(rtti::context &ctx, std::string *error=nullptr) -> bool
static auto save_scene(rtti::context &ctx) -> bool
static auto new_scene(rtti::context &ctx) -> bool
static auto set_play_paused(rtti::context &ctx, bool paused, std::string *error=nullptr) -> bool
static auto reload_project(rtti::context &ctx) -> bool
static auto save_scene_as(rtti::context &ctx) -> bool
static auto rebuild_reflection_probes(rtti::context &ctx, bool force_full_first_frame=true) -> size_t
Flags every reflection probe across all loaded scenes for rebuild.
static auto toggle_play(rtti::context &ctx, bool allow_splash=true, std::string *error=nullptr) -> bool
static auto deploy_project(rtti::context &ctx, const deploy_settings &params) -> std::map< std::string, tpp::shared_future< void > >
static auto wait_import_jobs(std::vector< import_files_item > &items, std::chrono::milliseconds timeout) -> bool
Block until all import copy jobs complete or timeout elapses.
static void recompile_meshes(const std::string &group="")
static auto new_scene_from_preset(rtti::context &ctx, defaults::scene_preset preset) -> bool
Non-modal new scene from preset (shared by create-scene modal + MCP). Cancels any pending create-scen...
static auto can_deploy_project(rtti::context &ctx, const deploy_settings &params) -> bool
static void generate_script_workspace()
static auto get_selection(rtti::context &ctx) -> selection_info
static auto context() -> rtti::context &
Definition engine.cpp:111
One async content-browser-style import job (external path -> project folder).
std::string text
level::level_enum level
std::string funcname
std::string filename
uint64_t id
int line
Owns play-mode state and orchestrates the splash -> running lifecycle.
Definition play_mode.h:17
void toggle(rtti::context &ctx, bool allow_splash=true)
Definition play_mode.cpp:33
void set_paused(rtti::context &ctx, bool paused)
Definition play_mode.cpp:59
void skip_next_frame(rtti::context &ctx)
Definition play_mode.cpp:84
Represents a scene-specific prefab. Inherits from the generic prefab structure.
Definition prefab.h:31
Represents a scene in the ACE framework, managing entities and their relationships.
Definition scene.h:70
asset_handle< scene_prefab > source
The source prefab asset handle for the scene.
Definition scene.h:182
auto load_from(const asset_handle< scene_prefab > &pfb, bool call_callbacks=true) -> bool
Loads a scene from a prefab asset.
Definition scene.cpp:219
static auto get_all_scenes() -> const std::vector< scene * > &
Definition scene.cpp:143
static auto get_scene(entt::handle entity) -> scene *
Gets the scene from an entity handle.
Definition scene.cpp:343
static auto find_dotnet_paths(const rtti::context &ctx) -> dotnet::compiler_paths
static auto get_lib_compiled_key(const std::string &protocol) -> std::string
std::vector< std::string > entity_ids
asset_handle< scene_prefab > startup_scene
Definition settings.h:138
struct unravel::settings::standalone_settings standalone
Represents a UI style sheet asset (CSS/RCSS document).
Definition style_sheet.h:26
Represents a UI visual tree asset (HTML/RML document).
Definition ui_tree.h:25
gfx::uniform_handle handle
Definition uniform.cpp:9
std::string token
std::vector< GitHubAsset > assets