editor_build_profile.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892
  1. /**************************************************************************/
  2. /* editor_build_profile.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "editor_build_profile.h"
  31. #include "core/io/json.h"
  32. #include "editor/editor_file_system.h"
  33. #include "editor/editor_node.h"
  34. #include "editor/editor_paths.h"
  35. #include "editor/editor_settings.h"
  36. #include "editor/editor_string_names.h"
  37. #include "editor/gui/editor_file_dialog.h"
  38. #include "editor/themes/editor_scale.h"
  39. #include "scene/gui/separator.h"
  40. const char *EditorBuildProfile::build_option_identifiers[BUILD_OPTION_MAX] = {
  41. // This maps to SCons build options.
  42. "disable_3d",
  43. "disable_2d_physics",
  44. "disable_3d_physics",
  45. "disable_navigation",
  46. "openxr",
  47. "rendering_device", // FIXME: there's no scons option to disable rendering device
  48. "opengl3",
  49. "vulkan",
  50. "module_text_server_fb_enabled",
  51. "module_text_server_adv_enabled",
  52. "module_freetype_enabled",
  53. "brotli",
  54. "graphite",
  55. "module_msdfgen_enabled"
  56. };
  57. const bool EditorBuildProfile::build_option_disabled_by_default[BUILD_OPTION_MAX] = {
  58. // This maps to SCons build options.
  59. false, // 3D
  60. false, // PHYSICS_2D
  61. false, // PHYSICS_3D
  62. false, // NAVIGATION
  63. false, // XR
  64. false, // RENDERING_DEVICE
  65. false, // OPENGL
  66. false, // VULKAN
  67. true, // TEXT_SERVER_FALLBACK
  68. false, // TEXT_SERVER_COMPLEX
  69. false, // DYNAMIC_FONTS
  70. false, // WOFF2_FONTS
  71. false, // GRAPHITE_FONTS
  72. false, // MSDFGEN
  73. };
  74. const bool EditorBuildProfile::build_option_disable_values[BUILD_OPTION_MAX] = {
  75. // This maps to SCons build options.
  76. true, // 3D
  77. true, // PHYSICS_2D
  78. true, // PHYSICS_3D
  79. true, // NAVIGATION
  80. false, // XR
  81. false, // RENDERING_DEVICE
  82. false, // OPENGL
  83. false, // VULKAN
  84. false, // TEXT_SERVER_FALLBACK
  85. false, // TEXT_SERVER_COMPLEX
  86. false, // DYNAMIC_FONTS
  87. false, // WOFF2_FONTS
  88. false, // GRAPHITE_FONTS
  89. false, // MSDFGEN
  90. };
  91. const EditorBuildProfile::BuildOptionCategory EditorBuildProfile::build_option_category[BUILD_OPTION_MAX] = {
  92. BUILD_OPTION_CATEGORY_GENERAL, // 3D
  93. BUILD_OPTION_CATEGORY_GENERAL, // PHYSICS_2D
  94. BUILD_OPTION_CATEGORY_GENERAL, // PHYSICS_3D
  95. BUILD_OPTION_CATEGORY_GENERAL, // NAVIGATION
  96. BUILD_OPTION_CATEGORY_GENERAL, // XR
  97. BUILD_OPTION_CATEGORY_GENERAL, // RENDERING_DEVICE
  98. BUILD_OPTION_CATEGORY_GENERAL, // OPENGL
  99. BUILD_OPTION_CATEGORY_GENERAL, // VULKAN
  100. BUILD_OPTION_CATEGORY_TEXT_SERVER, // TEXT_SERVER_FALLBACK
  101. BUILD_OPTION_CATEGORY_TEXT_SERVER, // TEXT_SERVER_COMPLEX
  102. BUILD_OPTION_CATEGORY_TEXT_SERVER, // DYNAMIC_FONTS
  103. BUILD_OPTION_CATEGORY_TEXT_SERVER, // WOFF2_FONTS
  104. BUILD_OPTION_CATEGORY_TEXT_SERVER, // GRAPHITE_FONTS
  105. BUILD_OPTION_CATEGORY_TEXT_SERVER, // MSDFGEN
  106. };
  107. void EditorBuildProfile::set_disable_class(const StringName &p_class, bool p_disabled) {
  108. if (p_disabled) {
  109. disabled_classes.insert(p_class);
  110. } else {
  111. disabled_classes.erase(p_class);
  112. }
  113. }
  114. bool EditorBuildProfile::is_class_disabled(const StringName &p_class) const {
  115. if (p_class == StringName()) {
  116. return false;
  117. }
  118. return disabled_classes.has(p_class) || is_class_disabled(ClassDB::get_parent_class_nocheck(p_class));
  119. }
  120. void EditorBuildProfile::set_item_collapsed(const StringName &p_class, bool p_collapsed) {
  121. if (p_collapsed) {
  122. collapsed_classes.insert(p_class);
  123. } else {
  124. collapsed_classes.erase(p_class);
  125. }
  126. }
  127. bool EditorBuildProfile::is_item_collapsed(const StringName &p_class) const {
  128. return collapsed_classes.has(p_class);
  129. }
  130. void EditorBuildProfile::set_disable_build_option(BuildOption p_build_option, bool p_disable) {
  131. ERR_FAIL_INDEX(p_build_option, BUILD_OPTION_MAX);
  132. build_options_disabled[p_build_option] = p_disable;
  133. }
  134. void EditorBuildProfile::clear_disabled_classes() {
  135. disabled_classes.clear();
  136. collapsed_classes.clear();
  137. }
  138. bool EditorBuildProfile::is_build_option_disabled(BuildOption p_build_option) const {
  139. ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, false);
  140. return build_options_disabled[p_build_option];
  141. }
  142. bool EditorBuildProfile::get_build_option_disable_value(BuildOption p_build_option) {
  143. ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, false);
  144. return build_option_disable_values[p_build_option];
  145. }
  146. void EditorBuildProfile::set_force_detect_classes(const String &p_classes) {
  147. force_detect_classes = p_classes;
  148. }
  149. String EditorBuildProfile::get_force_detect_classes() const {
  150. return force_detect_classes;
  151. }
  152. String EditorBuildProfile::get_build_option_name(BuildOption p_build_option) {
  153. ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, String());
  154. const char *build_option_names[BUILD_OPTION_MAX] = {
  155. TTRC("3D Engine"),
  156. TTRC("2D Physics"),
  157. TTRC("3D Physics"),
  158. TTRC("Navigation"),
  159. TTRC("XR"),
  160. TTRC("RenderingDevice"),
  161. TTRC("OpenGL"),
  162. TTRC("Vulkan"),
  163. TTRC("Text Server: Fallback"),
  164. TTRC("Text Server: Advanced"),
  165. TTRC("TTF, OTF, Type 1, WOFF1 Fonts"),
  166. TTRC("WOFF2 Fonts"),
  167. TTRC("SIL Graphite Fonts"),
  168. TTRC("Multi-channel Signed Distance Field Font Rendering"),
  169. };
  170. return TTRGET(build_option_names[p_build_option]);
  171. }
  172. String EditorBuildProfile::get_build_option_description(BuildOption p_build_option) {
  173. ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, String());
  174. const char *build_option_descriptions[BUILD_OPTION_MAX] = {
  175. TTRC("3D Nodes as well as RenderingServer access to 3D features."),
  176. TTRC("2D Physics nodes and PhysicsServer2D."),
  177. TTRC("3D Physics nodes and PhysicsServer3D."),
  178. TTRC("Navigation, both 2D and 3D."),
  179. TTRC("XR (AR and VR)."),
  180. TTRC("RenderingDevice based rendering (if disabled, the OpenGL back-end is required)."),
  181. TTRC("OpenGL back-end (if disabled, the RenderingDevice back-end is required)."),
  182. TTRC("Vulkan back-end of RenderingDevice."),
  183. TTRC("Fallback implementation of Text Server\nSupports basic text layouts."),
  184. TTRC("Text Server implementation powered by ICU and HarfBuzz libraries.\nSupports complex text layouts, BiDi, and contextual OpenType font features."),
  185. TTRC("TrueType, OpenType, Type 1, and WOFF1 font format support using FreeType library (if disabled, WOFF2 support is also disabled)."),
  186. TTRC("WOFF2 font format support using FreeType and Brotli libraries."),
  187. TTRC("SIL Graphite smart font technology support (supported by Advanced Text Server only)."),
  188. TTRC("Multi-channel signed distance field font rendering support using msdfgen library (pre-rendered MSDF fonts can be used even if this option disabled)."),
  189. };
  190. return TTRGET(build_option_descriptions[p_build_option]);
  191. }
  192. EditorBuildProfile::BuildOptionCategory EditorBuildProfile::get_build_option_category(BuildOption p_build_option) {
  193. ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, BUILD_OPTION_CATEGORY_GENERAL);
  194. return build_option_category[p_build_option];
  195. }
  196. String EditorBuildProfile::get_build_option_category_name(BuildOptionCategory p_build_option_category) {
  197. ERR_FAIL_INDEX_V(p_build_option_category, BUILD_OPTION_CATEGORY_MAX, String());
  198. const char *build_option_subcategories[BUILD_OPTION_CATEGORY_MAX]{
  199. TTRC("General Features:"),
  200. TTRC("Text Rendering and Font Options:"),
  201. };
  202. return TTRGET(build_option_subcategories[p_build_option_category]);
  203. }
  204. Error EditorBuildProfile::save_to_file(const String &p_path) {
  205. Dictionary data;
  206. data["type"] = "build_profile";
  207. Array dis_classes;
  208. for (const StringName &E : disabled_classes) {
  209. dis_classes.push_back(String(E));
  210. }
  211. dis_classes.sort();
  212. data["disabled_classes"] = dis_classes;
  213. Dictionary dis_build_options;
  214. for (int i = 0; i < BUILD_OPTION_MAX; i++) {
  215. if (build_options_disabled[i] != build_option_disabled_by_default[i]) {
  216. if (build_options_disabled[i]) {
  217. dis_build_options[build_option_identifiers[i]] = build_option_disable_values[i];
  218. } else {
  219. dis_build_options[build_option_identifiers[i]] = !build_option_disable_values[i];
  220. }
  221. }
  222. }
  223. data["disabled_build_options"] = dis_build_options;
  224. if (!force_detect_classes.is_empty()) {
  225. data["force_detect_classes"] = force_detect_classes;
  226. }
  227. Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
  228. ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
  229. String text = JSON::stringify(data, "\t");
  230. f->store_string(text);
  231. return OK;
  232. }
  233. Error EditorBuildProfile::load_from_file(const String &p_path) {
  234. Error err;
  235. String text = FileAccess::get_file_as_string(p_path, &err);
  236. if (err != OK) {
  237. return err;
  238. }
  239. JSON json;
  240. err = json.parse(text);
  241. if (err != OK) {
  242. ERR_PRINT("Error parsing '" + p_path + "' on line " + itos(json.get_error_line()) + ": " + json.get_error_message());
  243. return ERR_PARSE_ERROR;
  244. }
  245. Dictionary data = json.get_data();
  246. if (!data.has("type") || String(data["type"]) != "build_profile") {
  247. ERR_PRINT("Error parsing '" + p_path + "', it's not a build profile.");
  248. return ERR_PARSE_ERROR;
  249. }
  250. disabled_classes.clear();
  251. if (data.has("disabled_classes")) {
  252. Array disabled_classes_arr = data["disabled_classes"];
  253. for (int i = 0; i < disabled_classes_arr.size(); i++) {
  254. disabled_classes.insert(disabled_classes_arr[i]);
  255. }
  256. }
  257. for (int i = 0; i < BUILD_OPTION_MAX; i++) {
  258. build_options_disabled[i] = build_option_disabled_by_default[i];
  259. }
  260. if (data.has("disabled_build_options")) {
  261. Dictionary disabled_build_options_arr = data["disabled_build_options"];
  262. List<Variant> keys;
  263. disabled_build_options_arr.get_key_list(&keys);
  264. for (const Variant &K : keys) {
  265. String key = K;
  266. for (int i = 0; i < BUILD_OPTION_MAX; i++) {
  267. String f = build_option_identifiers[i];
  268. if (f == key) {
  269. build_options_disabled[i] = true;
  270. break;
  271. }
  272. }
  273. }
  274. }
  275. if (data.has("force_detect_classes")) {
  276. force_detect_classes = data["force_detect_classes"];
  277. }
  278. return OK;
  279. }
  280. void EditorBuildProfile::_bind_methods() {
  281. ClassDB::bind_method(D_METHOD("set_disable_class", "class_name", "disable"), &EditorBuildProfile::set_disable_class);
  282. ClassDB::bind_method(D_METHOD("is_class_disabled", "class_name"), &EditorBuildProfile::is_class_disabled);
  283. ClassDB::bind_method(D_METHOD("set_disable_build_option", "build_option", "disable"), &EditorBuildProfile::set_disable_build_option);
  284. ClassDB::bind_method(D_METHOD("is_build_option_disabled", "build_option"), &EditorBuildProfile::is_build_option_disabled);
  285. ClassDB::bind_method(D_METHOD("get_build_option_name", "build_option"), &EditorBuildProfile::_get_build_option_name);
  286. ClassDB::bind_method(D_METHOD("save_to_file", "path"), &EditorBuildProfile::save_to_file);
  287. ClassDB::bind_method(D_METHOD("load_from_file", "path"), &EditorBuildProfile::load_from_file);
  288. BIND_ENUM_CONSTANT(BUILD_OPTION_3D);
  289. BIND_ENUM_CONSTANT(BUILD_OPTION_PHYSICS_2D);
  290. BIND_ENUM_CONSTANT(BUILD_OPTION_PHYSICS_3D);
  291. BIND_ENUM_CONSTANT(BUILD_OPTION_NAVIGATION);
  292. BIND_ENUM_CONSTANT(BUILD_OPTION_XR);
  293. BIND_ENUM_CONSTANT(BUILD_OPTION_RENDERING_DEVICE);
  294. BIND_ENUM_CONSTANT(BUILD_OPTION_OPENGL);
  295. BIND_ENUM_CONSTANT(BUILD_OPTION_VULKAN);
  296. BIND_ENUM_CONSTANT(BUILD_OPTION_TEXT_SERVER_FALLBACK);
  297. BIND_ENUM_CONSTANT(BUILD_OPTION_TEXT_SERVER_ADVANCED);
  298. BIND_ENUM_CONSTANT(BUILD_OPTION_DYNAMIC_FONTS);
  299. BIND_ENUM_CONSTANT(BUILD_OPTION_WOFF2_FONTS);
  300. BIND_ENUM_CONSTANT(BUILD_OPTION_GRAPHITE_FONTS);
  301. BIND_ENUM_CONSTANT(BUILD_OPTION_MSDFGEN);
  302. BIND_ENUM_CONSTANT(BUILD_OPTION_MAX);
  303. BIND_ENUM_CONSTANT(BUILD_OPTION_CATEGORY_GENERAL);
  304. BIND_ENUM_CONSTANT(BUILD_OPTION_CATEGORY_TEXT_SERVER);
  305. BIND_ENUM_CONSTANT(BUILD_OPTION_CATEGORY_MAX);
  306. }
  307. EditorBuildProfile::EditorBuildProfile() {
  308. for (int i = 0; i < EditorBuildProfile::BUILD_OPTION_MAX; i++) {
  309. build_options_disabled[i] = build_option_disabled_by_default[i];
  310. }
  311. }
  312. //////////////////////////
  313. void EditorBuildProfileManager::_notification(int p_what) {
  314. switch (p_what) {
  315. case NOTIFICATION_READY: {
  316. String last_file = EditorSettings::get_singleton()->get_project_metadata("build_profile", "last_file_path", "");
  317. if (!last_file.is_empty()) {
  318. _import_profile(last_file);
  319. }
  320. if (edited.is_null()) {
  321. edited.instantiate();
  322. _update_edited_profile();
  323. }
  324. } break;
  325. }
  326. }
  327. void EditorBuildProfileManager::_profile_action(int p_action) {
  328. last_action = Action(p_action);
  329. switch (p_action) {
  330. case ACTION_RESET: {
  331. confirm_dialog->set_text(TTR("Reset the edited profile?"));
  332. confirm_dialog->popup_centered();
  333. } break;
  334. case ACTION_LOAD: {
  335. import_profile->popup_file_dialog();
  336. } break;
  337. case ACTION_SAVE: {
  338. if (!profile_path->get_text().is_empty()) {
  339. Error err = edited->save_to_file(profile_path->get_text());
  340. if (err != OK) {
  341. EditorNode::get_singleton()->show_warning(TTR("File saving failed."));
  342. }
  343. break;
  344. }
  345. [[fallthrough]];
  346. }
  347. case ACTION_SAVE_AS: {
  348. export_profile->popup_file_dialog();
  349. export_profile->set_current_file(profile_path->get_text());
  350. } break;
  351. case ACTION_NEW: {
  352. confirm_dialog->set_text(TTR("Create a new profile?"));
  353. confirm_dialog->popup_centered();
  354. } break;
  355. case ACTION_DETECT: {
  356. confirm_dialog->set_text(TTR("This will scan all files in the current project to detect used classes."));
  357. confirm_dialog->popup_centered();
  358. } break;
  359. case ACTION_MAX: {
  360. } break;
  361. }
  362. }
  363. void EditorBuildProfileManager::_find_files(EditorFileSystemDirectory *p_dir, const HashMap<String, DetectedFile> &p_cache, HashMap<String, DetectedFile> &r_detected) {
  364. if (p_dir == nullptr) {
  365. return;
  366. }
  367. for (int i = 0; i < p_dir->get_file_count(); i++) {
  368. String p = p_dir->get_file_path(i);
  369. uint64_t timestamp = 0;
  370. String md5;
  371. if (p_cache.has(p)) {
  372. const DetectedFile &cache = p_cache[p];
  373. // Check if timestamp and MD5 match.
  374. timestamp = FileAccess::get_modified_time(p);
  375. bool cache_valid = true;
  376. if (cache.timestamp != timestamp) {
  377. md5 = FileAccess::get_md5(p);
  378. if (md5 != cache.md5) {
  379. cache_valid = false;
  380. }
  381. }
  382. if (cache_valid) {
  383. r_detected.insert(p, cache);
  384. continue;
  385. }
  386. }
  387. // Not cached, or cache invalid.
  388. DetectedFile cache;
  389. HashSet<StringName> classes;
  390. ResourceLoader::get_classes_used(p, &classes);
  391. for (const StringName &E : classes) {
  392. cache.classes.push_back(E);
  393. }
  394. if (md5.is_empty()) {
  395. cache.timestamp = FileAccess::get_modified_time(p);
  396. cache.md5 = FileAccess::get_md5(p);
  397. } else {
  398. cache.timestamp = timestamp;
  399. cache.md5 = md5;
  400. }
  401. r_detected.insert(p, cache);
  402. }
  403. for (int i = 0; i < p_dir->get_subdir_count(); i++) {
  404. _find_files(p_dir->get_subdir(i), p_cache, r_detected);
  405. }
  406. }
  407. void EditorBuildProfileManager::_detect_classes() {
  408. HashMap<String, DetectedFile> previous_file_cache;
  409. Ref<FileAccess> f = FileAccess::open(EditorPaths::get_singleton()->get_project_settings_dir().path_join("used_class_cache"), FileAccess::READ);
  410. if (f.is_valid()) {
  411. while (!f->eof_reached()) {
  412. String l = f->get_line();
  413. Vector<String> fields = l.split("::");
  414. if (fields.size() == 4) {
  415. const String &path = fields[0];
  416. DetectedFile df;
  417. df.timestamp = fields[1].to_int();
  418. df.md5 = fields[2];
  419. df.classes = fields[3].split(",");
  420. previous_file_cache.insert(path, df);
  421. }
  422. }
  423. f.unref();
  424. }
  425. HashMap<String, DetectedFile> updated_file_cache;
  426. _find_files(EditorFileSystem::get_singleton()->get_filesystem(), previous_file_cache, updated_file_cache);
  427. HashSet<StringName> used_classes;
  428. // Find classes and update the disk cache in the process.
  429. f = FileAccess::open(EditorPaths::get_singleton()->get_project_settings_dir().path_join("used_class_cache"), FileAccess::WRITE);
  430. for (const KeyValue<String, DetectedFile> &E : updated_file_cache) {
  431. String l = E.key + "::" + itos(E.value.timestamp) + "::" + E.value.md5 + "::";
  432. for (int i = 0; i < E.value.classes.size(); i++) {
  433. String c = E.value.classes[i];
  434. if (i > 0) {
  435. l += ",";
  436. }
  437. l += c;
  438. used_classes.insert(c);
  439. }
  440. f->store_line(l);
  441. }
  442. f.unref();
  443. // Add forced ones.
  444. Vector<String> force_detect = edited->get_force_detect_classes().split(",");
  445. for (int i = 0; i < force_detect.size(); i++) {
  446. String c = force_detect[i].strip_edges();
  447. if (c.is_empty()) {
  448. continue;
  449. }
  450. used_classes.insert(c);
  451. }
  452. // Filter all classes to discard inherited ones.
  453. HashSet<StringName> all_used_classes;
  454. for (const StringName &E : used_classes) {
  455. StringName c = E;
  456. if (!ClassDB::class_exists(c)) {
  457. // Maybe this is an old class that got replaced? try getting compat class.
  458. c = ClassDB::get_compatibility_class(c);
  459. if (!c) {
  460. // No luck, skip.
  461. continue;
  462. }
  463. }
  464. while (c) {
  465. all_used_classes.insert(c);
  466. c = ClassDB::get_parent_class(c);
  467. }
  468. }
  469. edited->clear_disabled_classes();
  470. List<StringName> all_classes;
  471. ClassDB::get_class_list(&all_classes);
  472. for (const StringName &E : all_classes) {
  473. if (all_used_classes.has(E)) {
  474. // This class is valid, do nothing.
  475. continue;
  476. }
  477. StringName p = ClassDB::get_parent_class(E);
  478. if (!p || all_used_classes.has(p)) {
  479. // If no parent, or if the parent is enabled, then add to disabled classes.
  480. // This way we avoid disabling redundant classes.
  481. edited->set_disable_class(E, true);
  482. }
  483. }
  484. }
  485. void EditorBuildProfileManager::_action_confirm() {
  486. switch (last_action) {
  487. case ACTION_RESET: {
  488. edited.instantiate();
  489. _update_edited_profile();
  490. } break;
  491. case ACTION_LOAD: {
  492. } break;
  493. case ACTION_SAVE: {
  494. } break;
  495. case ACTION_SAVE_AS: {
  496. } break;
  497. case ACTION_NEW: {
  498. profile_path->set_text("");
  499. edited.instantiate();
  500. _update_edited_profile();
  501. } break;
  502. case ACTION_DETECT: {
  503. _detect_classes();
  504. _update_edited_profile();
  505. } break;
  506. case ACTION_MAX: {
  507. } break;
  508. }
  509. }
  510. void EditorBuildProfileManager::_hide_requested() {
  511. _cancel_pressed(); // From AcceptDialog.
  512. }
  513. void EditorBuildProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected) {
  514. TreeItem *class_item = class_list->create_item(p_parent);
  515. class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
  516. class_item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_class));
  517. const String &text = p_class;
  518. bool disabled = edited->is_class_disabled(p_class);
  519. if (disabled) {
  520. class_item->set_custom_color(0, class_list->get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)));
  521. }
  522. class_item->set_text(0, text);
  523. class_item->set_editable(0, true);
  524. class_item->set_selectable(0, true);
  525. class_item->set_metadata(0, p_class);
  526. bool collapsed = edited->is_item_collapsed(p_class);
  527. class_item->set_collapsed(collapsed);
  528. if (p_class == p_selected) {
  529. class_item->select(0);
  530. }
  531. if (disabled) {
  532. // Class disabled, do nothing else (do not show further).
  533. return;
  534. }
  535. class_item->set_checked(0, true); // If it's not disabled, its checked.
  536. List<StringName> child_classes;
  537. ClassDB::get_direct_inheriters_from_class(p_class, &child_classes);
  538. child_classes.sort_custom<StringName::AlphCompare>();
  539. for (const StringName &name : child_classes) {
  540. if (String(name).begins_with("Editor") || ClassDB::get_api_type(name) != ClassDB::API_CORE) {
  541. continue;
  542. }
  543. _fill_classes_from(class_item, name, p_selected);
  544. }
  545. }
  546. void EditorBuildProfileManager::_class_list_item_selected() {
  547. if (updating_build_options) {
  548. return;
  549. }
  550. TreeItem *item = class_list->get_selected();
  551. if (!item) {
  552. return;
  553. }
  554. Variant md = item->get_metadata(0);
  555. if (md.is_string()) {
  556. description_bit->parse_symbol("class|" + md.operator String() + "|");
  557. } else if (md.get_type() == Variant::INT) {
  558. String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption((int)md));
  559. description_bit->set_custom_text(TTR(item->get_text(0)), String(), TTRGET(build_option_description));
  560. }
  561. }
  562. void EditorBuildProfileManager::_class_list_item_edited() {
  563. if (updating_build_options) {
  564. return;
  565. }
  566. TreeItem *item = class_list->get_edited();
  567. if (!item) {
  568. return;
  569. }
  570. bool checked = item->is_checked(0);
  571. Variant md = item->get_metadata(0);
  572. if (md.is_string()) {
  573. String class_selected = md;
  574. edited->set_disable_class(class_selected, !checked);
  575. _update_edited_profile();
  576. } else if (md.get_type() == Variant::INT) {
  577. int build_option_selected = md;
  578. edited->set_disable_build_option(EditorBuildProfile::BuildOption(build_option_selected), !checked);
  579. }
  580. }
  581. void EditorBuildProfileManager::_class_list_item_collapsed(Object *p_item) {
  582. if (updating_build_options) {
  583. return;
  584. }
  585. TreeItem *item = Object::cast_to<TreeItem>(p_item);
  586. if (!item) {
  587. return;
  588. }
  589. Variant md = item->get_metadata(0);
  590. if (!md.is_string()) {
  591. return;
  592. }
  593. String class_name = md;
  594. bool collapsed = item->is_collapsed();
  595. edited->set_item_collapsed(class_name, collapsed);
  596. }
  597. void EditorBuildProfileManager::_update_edited_profile() {
  598. String class_selected;
  599. int build_option_selected = -1;
  600. if (class_list->get_selected()) {
  601. Variant md = class_list->get_selected()->get_metadata(0);
  602. if (md.is_string()) {
  603. class_selected = md;
  604. } else if (md.get_type() == Variant::INT) {
  605. build_option_selected = md;
  606. }
  607. }
  608. class_list->clear();
  609. updating_build_options = true;
  610. TreeItem *root = class_list->create_item();
  611. HashMap<EditorBuildProfile::BuildOptionCategory, TreeItem *> subcats;
  612. for (int i = 0; i < EditorBuildProfile::BUILD_OPTION_CATEGORY_MAX; i++) {
  613. TreeItem *build_cat;
  614. build_cat = class_list->create_item(root);
  615. build_cat->set_text(0, EditorBuildProfile::get_build_option_category_name(EditorBuildProfile::BuildOptionCategory(i)));
  616. subcats[EditorBuildProfile::BuildOptionCategory(i)] = build_cat;
  617. }
  618. for (int i = 0; i < EditorBuildProfile::BUILD_OPTION_MAX; i++) {
  619. TreeItem *build_option;
  620. build_option = class_list->create_item(subcats[EditorBuildProfile::get_build_option_category(EditorBuildProfile::BuildOption(i))]);
  621. build_option->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
  622. build_option->set_text(0, EditorBuildProfile::get_build_option_name(EditorBuildProfile::BuildOption(i)));
  623. build_option->set_selectable(0, true);
  624. build_option->set_editable(0, true);
  625. build_option->set_metadata(0, i);
  626. if (!edited->is_build_option_disabled(EditorBuildProfile::BuildOption(i))) {
  627. build_option->set_checked(0, true);
  628. }
  629. if (i == build_option_selected) {
  630. build_option->select(0);
  631. }
  632. }
  633. TreeItem *classes = class_list->create_item(root);
  634. classes->set_text(0, TTR("Nodes and Classes:"));
  635. _fill_classes_from(classes, "Node", class_selected);
  636. _fill_classes_from(classes, "Resource", class_selected);
  637. force_detect_classes->set_text(edited->get_force_detect_classes());
  638. updating_build_options = false;
  639. _class_list_item_selected();
  640. }
  641. void EditorBuildProfileManager::_force_detect_classes_changed(const String &p_text) {
  642. if (updating_build_options) {
  643. return;
  644. }
  645. edited->set_force_detect_classes(force_detect_classes->get_text());
  646. }
  647. void EditorBuildProfileManager::_import_profile(const String &p_path) {
  648. Ref<EditorBuildProfile> profile;
  649. profile.instantiate();
  650. Error err = profile->load_from_file(p_path);
  651. String basefile = p_path.get_file();
  652. if (err != OK) {
  653. EditorNode::get_singleton()->show_warning(vformat(TTR("File '%s' format is invalid, import aborted."), basefile));
  654. return;
  655. }
  656. profile_path->set_text(p_path);
  657. EditorSettings::get_singleton()->set_project_metadata("build_profile", "last_file_path", p_path);
  658. edited = profile;
  659. _update_edited_profile();
  660. }
  661. void EditorBuildProfileManager::_export_profile(const String &p_path) {
  662. ERR_FAIL_COND(edited.is_null());
  663. Error err = edited->save_to_file(p_path);
  664. if (err != OK) {
  665. EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving profile to path: '%s'."), p_path));
  666. } else {
  667. profile_path->set_text(p_path);
  668. EditorSettings::get_singleton()->set_project_metadata("build_profile", "last_file_path", p_path);
  669. }
  670. }
  671. Ref<EditorBuildProfile> EditorBuildProfileManager::get_current_profile() {
  672. return edited;
  673. }
  674. EditorBuildProfileManager *EditorBuildProfileManager::singleton = nullptr;
  675. void EditorBuildProfileManager::_bind_methods() {
  676. ClassDB::bind_method("_update_selected_profile", &EditorBuildProfileManager::_update_edited_profile);
  677. }
  678. EditorBuildProfileManager::EditorBuildProfileManager() {
  679. VBoxContainer *main_vbc = memnew(VBoxContainer);
  680. add_child(main_vbc);
  681. HBoxContainer *path_hbc = memnew(HBoxContainer);
  682. profile_path = memnew(LineEdit);
  683. path_hbc->add_child(profile_path);
  684. profile_path->set_editable(true);
  685. profile_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  686. profile_actions[ACTION_NEW] = memnew(Button(TTR("New")));
  687. path_hbc->add_child(profile_actions[ACTION_NEW]);
  688. profile_actions[ACTION_NEW]->connect(SceneStringName(pressed), callable_mp(this, &EditorBuildProfileManager::_profile_action).bind(ACTION_NEW));
  689. profile_actions[ACTION_LOAD] = memnew(Button(TTR("Load")));
  690. path_hbc->add_child(profile_actions[ACTION_LOAD]);
  691. profile_actions[ACTION_LOAD]->connect(SceneStringName(pressed), callable_mp(this, &EditorBuildProfileManager::_profile_action).bind(ACTION_LOAD));
  692. profile_actions[ACTION_SAVE] = memnew(Button(TTR("Save")));
  693. path_hbc->add_child(profile_actions[ACTION_SAVE]);
  694. profile_actions[ACTION_SAVE]->connect(SceneStringName(pressed), callable_mp(this, &EditorBuildProfileManager::_profile_action).bind(ACTION_SAVE));
  695. profile_actions[ACTION_SAVE_AS] = memnew(Button(TTR("Save As")));
  696. path_hbc->add_child(profile_actions[ACTION_SAVE_AS]);
  697. profile_actions[ACTION_SAVE_AS]->connect(SceneStringName(pressed), callable_mp(this, &EditorBuildProfileManager::_profile_action).bind(ACTION_SAVE_AS));
  698. main_vbc->add_margin_child(TTR("Profile:"), path_hbc);
  699. main_vbc->add_child(memnew(HSeparator));
  700. HBoxContainer *profiles_hbc = memnew(HBoxContainer);
  701. profile_actions[ACTION_RESET] = memnew(Button(TTR("Reset to Defaults")));
  702. profiles_hbc->add_child(profile_actions[ACTION_RESET]);
  703. profile_actions[ACTION_RESET]->connect(SceneStringName(pressed), callable_mp(this, &EditorBuildProfileManager::_profile_action).bind(ACTION_RESET));
  704. profile_actions[ACTION_DETECT] = memnew(Button(TTR("Detect from Project")));
  705. profiles_hbc->add_child(profile_actions[ACTION_DETECT]);
  706. profile_actions[ACTION_DETECT]->connect(SceneStringName(pressed), callable_mp(this, &EditorBuildProfileManager::_profile_action).bind(ACTION_DETECT));
  707. main_vbc->add_margin_child(TTR("Actions:"), profiles_hbc);
  708. class_list = memnew(Tree);
  709. class_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
  710. class_list->set_hide_root(true);
  711. class_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);
  712. class_list->connect("cell_selected", callable_mp(this, &EditorBuildProfileManager::_class_list_item_selected));
  713. class_list->connect("item_edited", callable_mp(this, &EditorBuildProfileManager::_class_list_item_edited), CONNECT_DEFERRED);
  714. class_list->connect("item_collapsed", callable_mp(this, &EditorBuildProfileManager::_class_list_item_collapsed));
  715. // It will be displayed once the user creates or chooses a profile.
  716. main_vbc->add_margin_child(TTR("Configure Engine Compilation Profile:"), class_list, true);
  717. description_bit = memnew(EditorHelpBit);
  718. description_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE);
  719. description_bit->connect("request_hide", callable_mp(this, &EditorBuildProfileManager::_hide_requested));
  720. main_vbc->add_margin_child(TTR("Description:"), description_bit, false);
  721. confirm_dialog = memnew(ConfirmationDialog);
  722. add_child(confirm_dialog);
  723. confirm_dialog->set_title(TTR("Please Confirm:"));
  724. confirm_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorBuildProfileManager::_action_confirm));
  725. import_profile = memnew(EditorFileDialog);
  726. add_child(import_profile);
  727. import_profile->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
  728. import_profile->add_filter("*.build", TTR("Engine Compilation Profile"));
  729. import_profile->connect("files_selected", callable_mp(this, &EditorBuildProfileManager::_import_profile));
  730. import_profile->set_title(TTR("Load Profile"));
  731. import_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
  732. export_profile = memnew(EditorFileDialog);
  733. add_child(export_profile);
  734. export_profile->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
  735. export_profile->add_filter("*.build", TTR("Engine Compilation Profile"));
  736. export_profile->connect("file_selected", callable_mp(this, &EditorBuildProfileManager::_export_profile));
  737. export_profile->set_title(TTR("Export Profile"));
  738. export_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
  739. force_detect_classes = memnew(LineEdit);
  740. main_vbc->add_margin_child(TTR("Forced Classes on Detect:"), force_detect_classes);
  741. force_detect_classes->connect(SceneStringName(text_changed), callable_mp(this, &EditorBuildProfileManager::_force_detect_classes_changed));
  742. set_title(TTR("Edit Compilation Configuration Profile"));
  743. singleton = this;
  744. }