Implement Feature Build Profiles
This PR is a continuation of #50381 (which was implemented exactly a year ago!) * Add a visual interface to select which classes should not be built into Godot (well, they are built if something else uses them, but if not used the optimizer will remove them out). * Add a detection system to scan the project and figure out the actual classes used. * Added the ability for SCons to load build profiles. Obligatory Screen: A simple test with a couple of nodes in the scene resulted in a 25% reduction for the final binary size TODO: * Script languages need to implement used class detection (left for another PR). * Options to disable servers or server functionalities (like 2D or 3D physics, navigation, etc). Are missing, that should also greatly aid in reducing binary size. * Options to disable some modules would be desired. * More options to disable drivers (OpenGL, Vulkan, etc) would be desired. In general this PR is a starting point for more contributors to improve and enhance this functionality.
This commit is contained in:
parent
5fec0d232a
commit
6236a688b7
24
SConstruct
24
SConstruct
|
@ -195,7 +195,7 @@ opts.Add("extra_suffix", "Custom extra suffix added to the base filename of all
|
|||
opts.Add(BoolVariable("vsproj", "Generate a Visual Studio solution", False))
|
||||
opts.Add(BoolVariable("disable_3d", "Disable 3D nodes for a smaller executable", False))
|
||||
opts.Add(BoolVariable("disable_advanced_gui", "Disable advanced GUI nodes and behaviors", False))
|
||||
opts.Add("disable_classes", "Disable given classes (comma separated)", "")
|
||||
opts.Add("build_feature_profile", "Path to a file containing a feature build profile", "")
|
||||
opts.Add(BoolVariable("modules_enabled_by_default", "If no, disable all modules except ones explicitly enabled", True))
|
||||
opts.Add(BoolVariable("no_editor_splash", "Don't use the custom splash screen for the editor", True))
|
||||
opts.Add("system_certs_path", "Use this path as SSL certificates default for editor (for package maintainers)", "")
|
||||
|
@ -720,7 +720,27 @@ if selected_platform in platform_list:
|
|||
|
||||
if env["tools"]:
|
||||
env.Append(CPPDEFINES=["TOOLS_ENABLED"])
|
||||
methods.write_disabled_classes(env["disable_classes"].split(","))
|
||||
|
||||
disabled_classes = []
|
||||
|
||||
if env["build_feature_profile"] != "":
|
||||
print("Using build feature profile: " + env["build_feature_profile"])
|
||||
import json
|
||||
|
||||
try:
|
||||
ft = json.load(open(env["build_feature_profile"]))
|
||||
if "disabled_classes" in ft:
|
||||
disabled_classes = ft["disabled_classes"]
|
||||
if "disabled_build_options" in ft:
|
||||
dbo = ft["disabled_build_options"]
|
||||
for c in dbo:
|
||||
env[c] = dbo[c]
|
||||
except:
|
||||
print("Error opening feature build profile: " + env["build_feature_profile"])
|
||||
Exit(255)
|
||||
|
||||
methods.write_disabled_classes(disabled_classes)
|
||||
|
||||
if env["disable_3d"]:
|
||||
if env["tools"]:
|
||||
print(
|
||||
|
|
|
@ -865,6 +865,22 @@ String ResourceLoaderBinary::get_unicode_string() {
|
|||
return s;
|
||||
}
|
||||
|
||||
void ResourceLoaderBinary::get_classes_used(Ref<FileAccess> p_f, HashSet<StringName> *p_classes) {
|
||||
open(p_f, false, true);
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < internal_resources.size(); i++) {
|
||||
p_f->seek(internal_resources[i].offset);
|
||||
String t = get_unicode_string();
|
||||
ERR_FAIL_COND(p_f->get_error() != OK);
|
||||
if (t != String()) {
|
||||
p_classes->insert(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceLoaderBinary::get_dependencies(Ref<FileAccess> p_f, List<String> *p_dependencies, bool p_add_types) {
|
||||
open(p_f, false, true);
|
||||
if (error) {
|
||||
|
@ -1337,6 +1353,16 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
|
|||
return OK;
|
||||
}
|
||||
|
||||
void ResourceFormatLoaderBinary::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
|
||||
ERR_FAIL_COND_MSG(f.is_null(), "Cannot open file '" + p_path + "'.");
|
||||
|
||||
ResourceLoaderBinary loader;
|
||||
loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
|
||||
loader.res_path = loader.local_path;
|
||||
loader.get_classes_used(f, r_classes);
|
||||
}
|
||||
|
||||
String ResourceFormatLoaderBinary::get_resource_type(const String &p_path) const {
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
|
||||
if (f.is_null()) {
|
||||
|
|
|
@ -101,6 +101,7 @@ public:
|
|||
void open(Ref<FileAccess> p_f, bool p_no_resources = false, bool p_keep_uuid_paths = false);
|
||||
String recognize(Ref<FileAccess> p_f);
|
||||
void get_dependencies(Ref<FileAccess> p_f, List<String> *p_dependencies, bool p_add_types);
|
||||
void get_classes_used(Ref<FileAccess> p_f, HashSet<StringName> *p_classes);
|
||||
|
||||
ResourceLoaderBinary() {}
|
||||
};
|
||||
|
@ -112,6 +113,7 @@ public:
|
|||
virtual void get_recognized_extensions(List<String> *p_extensions) const;
|
||||
virtual bool handles_type(const String &p_type) const;
|
||||
virtual String get_resource_type(const String &p_path) const;
|
||||
virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes);
|
||||
virtual ResourceUID::ID get_resource_uid(const String &p_path) const;
|
||||
virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false);
|
||||
virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map);
|
||||
|
|
|
@ -352,6 +352,16 @@ Variant ResourceFormatImporter::get_resource_metadata(const String &p_path) cons
|
|||
|
||||
return pat.metadata;
|
||||
}
|
||||
void ResourceFormatImporter::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
|
||||
PathAndType pat;
|
||||
Error err = _get_path_and_type(p_path, pat);
|
||||
|
||||
if (err != OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceLoader::get_classes_used(pat.path, r_classes);
|
||||
}
|
||||
|
||||
void ResourceFormatImporter::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
|
||||
PathAndType pat;
|
||||
|
|
|
@ -65,12 +65,12 @@ public:
|
|||
virtual bool handles_type(const String &p_type) const;
|
||||
virtual String get_resource_type(const String &p_path) const;
|
||||
virtual ResourceUID::ID get_resource_uid(const String &p_path) const;
|
||||
|
||||
virtual Variant get_resource_metadata(const String &p_path) const;
|
||||
virtual bool is_import_valid(const String &p_path) const;
|
||||
virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false);
|
||||
virtual bool is_imported(const String &p_path) const { return recognize_path(p_path); }
|
||||
virtual String get_import_group_file(const String &p_path) const;
|
||||
virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes);
|
||||
virtual bool exists(const String &p_path) const;
|
||||
|
||||
virtual int get_import_order(const String &p_path) const;
|
||||
|
|
|
@ -76,6 +76,21 @@ bool ResourceFormatLoader::handles_type(const String &p_type) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
void ResourceFormatLoader::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
|
||||
Vector<String> ret;
|
||||
if (GDVIRTUAL_CALL(_get_classes_used, p_path, ret)) {
|
||||
for (int i = 0; i < ret.size(); i++) {
|
||||
r_classes->insert(ret[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String res = get_resource_type(p_path);
|
||||
if (!res.is_empty()) {
|
||||
r_classes->insert(res);
|
||||
}
|
||||
}
|
||||
|
||||
String ResourceFormatLoader::get_resource_type(const String &p_path) const {
|
||||
String ret;
|
||||
|
||||
|
@ -180,6 +195,7 @@ void ResourceFormatLoader::_bind_methods() {
|
|||
GDVIRTUAL_BIND(_get_dependencies, "path", "add_types");
|
||||
GDVIRTUAL_BIND(_rename_dependencies, "path", "renames");
|
||||
GDVIRTUAL_BIND(_exists, "path");
|
||||
GDVIRTUAL_BIND(_get_classes_used, "path");
|
||||
GDVIRTUAL_BIND(_load, "path", "original_path", "use_sub_threads", "cache_mode");
|
||||
}
|
||||
|
||||
|
@ -730,6 +746,18 @@ Error ResourceLoader::rename_dependencies(const String &p_path, const HashMap<St
|
|||
return OK; // ??
|
||||
}
|
||||
|
||||
void ResourceLoader::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
|
||||
String local_path = _validate_local_path(p_path);
|
||||
|
||||
for (int i = 0; i < loader_count; i++) {
|
||||
if (!loader[i]->recognize_path(local_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return loader[i]->get_classes_used(p_path, r_classes);
|
||||
}
|
||||
}
|
||||
|
||||
String ResourceLoader::get_resource_type(const String &p_path) {
|
||||
String local_path = _validate_local_path(p_path);
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ protected:
|
|||
GDVIRTUAL1RC(String, _get_resource_type, String)
|
||||
GDVIRTUAL1RC(ResourceUID::ID, _get_resource_uid, String)
|
||||
GDVIRTUAL2RC(Vector<String>, _get_dependencies, String, bool)
|
||||
GDVIRTUAL1RC(Vector<String>, _get_classes_used, String)
|
||||
GDVIRTUAL2RC(int64_t, _rename_dependencies, String, Dictionary)
|
||||
GDVIRTUAL1RC(bool, _exists, String)
|
||||
|
||||
|
@ -67,6 +68,7 @@ public:
|
|||
virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const;
|
||||
virtual bool recognize_path(const String &p_path, const String &p_for_type = String()) const;
|
||||
virtual bool handles_type(const String &p_type) const;
|
||||
virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes);
|
||||
virtual String get_resource_type(const String &p_path) const;
|
||||
virtual ResourceUID::ID get_resource_uid(const String &p_path) const;
|
||||
virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false);
|
||||
|
@ -170,6 +172,7 @@ public:
|
|||
static void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions);
|
||||
static void add_resource_format_loader(Ref<ResourceFormatLoader> p_format_loader, bool p_at_front = false);
|
||||
static void remove_resource_format_loader(Ref<ResourceFormatLoader> p_format_loader);
|
||||
static void get_classes_used(const String &p_path, HashSet<StringName> *r_classes);
|
||||
static String get_resource_type(const String &p_path);
|
||||
static ResourceUID::ID get_resource_uid(const String &p_path);
|
||||
static void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false);
|
||||
|
|
|
@ -305,6 +305,13 @@ void ClassDB::add_compatibility_class(const StringName &p_class, const StringNam
|
|||
compat_classes[p_class] = p_fallback;
|
||||
}
|
||||
|
||||
StringName ClassDB::get_compatibility_class(const StringName &p_class) {
|
||||
if (compat_classes.has(p_class)) {
|
||||
return compat_classes[p_class];
|
||||
}
|
||||
return StringName();
|
||||
}
|
||||
|
||||
Object *ClassDB::instantiate(const StringName &p_class) {
|
||||
ClassInfo *ti;
|
||||
{
|
||||
|
|
|
@ -357,6 +357,7 @@ public:
|
|||
static bool is_resource_extension(const StringName &p_extension);
|
||||
|
||||
static void add_compatibility_class(const StringName &p_class, const StringName &p_fallback);
|
||||
static StringName get_compatibility_class(const StringName &p_class);
|
||||
|
||||
static void set_current_api(APIType p_api);
|
||||
static APIType get_current_api();
|
||||
|
@ -418,16 +419,16 @@ _FORCE_INLINE_ Vector<Error> errarray(P... p_args) {
|
|||
|
||||
#endif
|
||||
|
||||
#define GDREGISTER_CLASS(m_class) \
|
||||
if (!GD_IS_DEFINED(ClassDB_Disable_##m_class)) { \
|
||||
::ClassDB::register_class<m_class>(); \
|
||||
#define GDREGISTER_CLASS(m_class) \
|
||||
if (m_class::_class_is_enabled) { \
|
||||
::ClassDB::register_class<m_class>(); \
|
||||
}
|
||||
#define GDREGISTER_VIRTUAL_CLASS(m_class) \
|
||||
if (!GD_IS_DEFINED(ClassDB_Disable_##m_class)) { \
|
||||
::ClassDB::register_class<m_class>(true); \
|
||||
#define GDREGISTER_VIRTUAL_CLASS(m_class) \
|
||||
if (m_class::_class_is_enabled) { \
|
||||
::ClassDB::register_class<m_class>(true); \
|
||||
}
|
||||
#define GDREGISTER_ABSTRACT_CLASS(m_class) \
|
||||
if (!GD_IS_DEFINED(ClassDB_Disable_##m_class)) { \
|
||||
if (m_class::_class_is_enabled) { \
|
||||
::ClassDB::register_abstract_class<m_class>(); \
|
||||
}
|
||||
|
||||
|
|
|
@ -358,6 +358,7 @@ private:
|
|||
friend class ::ClassDB; \
|
||||
\
|
||||
public: \
|
||||
static constexpr bool _class_is_enabled = !bool(GD_IS_DEFINED(ClassDB_Disable_##m_class)) && m_inherits::_class_is_enabled; \
|
||||
virtual String get_class() const override { \
|
||||
if (_get_extension()) { \
|
||||
return _get_extension()->class_name.operator String(); \
|
||||
|
@ -667,6 +668,8 @@ public: // Should be protected, but bug in clang++.
|
|||
_FORCE_INLINE_ static void register_custom_data_to_otdb() {}
|
||||
|
||||
public:
|
||||
static constexpr bool _class_is_enabled = true;
|
||||
|
||||
void notify_property_list_changed();
|
||||
|
||||
static void *get_class_ptr_static() {
|
||||
|
|
|
@ -17,6 +17,12 @@
|
|||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="_get_classes_used" qualifiers="virtual const">
|
||||
<return type="PackedStringArray" />
|
||||
<argument index="0" name="path" type="String" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="_get_dependencies" qualifiers="virtual const">
|
||||
<return type="PackedStringArray" />
|
||||
<argument index="0" name="path" type="String" />
|
||||
|
|
|
@ -0,0 +1,797 @@
|
|||
/*************************************************************************/
|
||||
/* editor_build_profile.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "editor_build_profile.h"
|
||||
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/json.h"
|
||||
#include "editor/editor_file_dialog.h"
|
||||
#include "editor/editor_file_system.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_property_name_processor.h"
|
||||
#include "editor/editor_scale.h"
|
||||
#include "editor/editor_settings.h"
|
||||
|
||||
const char *EditorBuildProfile::build_option_identifiers[BUILD_OPTION_MAX] = {
|
||||
// This maps to SCons build options.
|
||||
"disable_3d",
|
||||
"disable_2d_physics",
|
||||
"disable_3d_physics",
|
||||
"disable_navigation",
|
||||
"openxr",
|
||||
"opengl3",
|
||||
"vulkan",
|
||||
};
|
||||
|
||||
const bool EditorBuildProfile::build_option_disable_values[BUILD_OPTION_MAX] = {
|
||||
// This maps to SCons build options.
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
};
|
||||
|
||||
void EditorBuildProfile::set_disable_class(const StringName &p_class, bool p_disabled) {
|
||||
if (p_disabled) {
|
||||
disabled_classes.insert(p_class);
|
||||
} else {
|
||||
disabled_classes.erase(p_class);
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorBuildProfile::is_class_disabled(const StringName &p_class) const {
|
||||
if (p_class == StringName()) {
|
||||
return false;
|
||||
}
|
||||
return disabled_classes.has(p_class) || is_class_disabled(ClassDB::get_parent_class_nocheck(p_class));
|
||||
}
|
||||
|
||||
void EditorBuildProfile::set_item_collapsed(const StringName &p_class, bool p_collapsed) {
|
||||
if (p_collapsed) {
|
||||
collapsed_classes.insert(p_class);
|
||||
} else {
|
||||
collapsed_classes.erase(p_class);
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorBuildProfile::is_item_collapsed(const StringName &p_class) const {
|
||||
return collapsed_classes.has(p_class);
|
||||
}
|
||||
|
||||
void EditorBuildProfile::set_disable_build_option(BuildOption p_build_option, bool p_disable) {
|
||||
ERR_FAIL_INDEX(p_build_option, BUILD_OPTION_MAX);
|
||||
build_options_disabled[p_build_option] = p_disable;
|
||||
}
|
||||
|
||||
void EditorBuildProfile::clear_disabled_classes() {
|
||||
disabled_classes.clear();
|
||||
collapsed_classes.clear();
|
||||
}
|
||||
|
||||
bool EditorBuildProfile::is_build_option_disabled(BuildOption p_build_option) const {
|
||||
ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, false);
|
||||
return build_options_disabled[p_build_option];
|
||||
}
|
||||
|
||||
bool EditorBuildProfile::get_build_option_disable_value(BuildOption p_build_option) {
|
||||
ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, false);
|
||||
return build_option_disable_values[p_build_option];
|
||||
}
|
||||
|
||||
void EditorBuildProfile::set_force_detect_classes(const String &p_classes) {
|
||||
force_detect_classes = p_classes;
|
||||
}
|
||||
|
||||
String EditorBuildProfile::get_force_detect_classes() const {
|
||||
return force_detect_classes;
|
||||
}
|
||||
|
||||
String EditorBuildProfile::get_build_option_name(BuildOption p_build_option) {
|
||||
ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, String());
|
||||
const char *build_option_names[BUILD_OPTION_MAX] = {
|
||||
TTRC("3D Engine"),
|
||||
TTRC("2D Physics"),
|
||||
TTRC("3D Physics"),
|
||||
TTRC("Navigation"),
|
||||
TTRC("XR"),
|
||||
TTRC("RenderingDevice"),
|
||||
TTRC("OpenGL"),
|
||||
TTRC("Vulkan"),
|
||||
};
|
||||
return TTRGET(build_option_names[p_build_option]);
|
||||
}
|
||||
|
||||
String EditorBuildProfile::get_build_option_description(BuildOption p_build_option) {
|
||||
ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, String());
|
||||
|
||||
const char *build_option_descriptions[BUILD_OPTION_MAX] = {
|
||||
TTRC("3D Nodes as well as RenderingServer access to 3D features."),
|
||||
TTRC("2D Physics nodes and PhysicsServer2D."),
|
||||
TTRC("3D Physics nodes and PhysicsServer3D."),
|
||||
TTRC("Navigation, both 2D and 3D."),
|
||||
TTRC("XR (AR and VR)."),
|
||||
TTRC("RenderingDevice based rendering (if disabled, the OpenGL back-end is required)."),
|
||||
TTRC("OpenGL back-end (if disabled, the RenderingDevice back-end is required)."),
|
||||
TTRC("Vulkan back-end of RenderingDevice."),
|
||||
};
|
||||
|
||||
return TTRGET(build_option_descriptions[p_build_option]);
|
||||
}
|
||||
|
||||
Error EditorBuildProfile::save_to_file(const String &p_path) {
|
||||
Dictionary data;
|
||||
data["type"] = "build_profile";
|
||||
Array dis_classes;
|
||||
for (const StringName &E : disabled_classes) {
|
||||
dis_classes.push_back(String(E));
|
||||
}
|
||||
dis_classes.sort();
|
||||
data["disabled_classes"] = dis_classes;
|
||||
|
||||
Dictionary dis_build_options;
|
||||
for (int i = 0; i < BUILD_OPTION_MAX; i++) {
|
||||
if (build_options_disabled[i]) {
|
||||
dis_build_options[build_option_identifiers[i]] = build_option_disable_values[i];
|
||||
}
|
||||
}
|
||||
|
||||
data["disabled_build_options"] = dis_build_options;
|
||||
|
||||
if (!force_detect_classes.is_empty()) {
|
||||
data["force_detect_classes"] = force_detect_classes;
|
||||
}
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
|
||||
|
||||
JSON json;
|
||||
String text = json.stringify(data, "\t");
|
||||
f->store_string(text);
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error EditorBuildProfile::load_from_file(const String &p_path) {
|
||||
Error err;
|
||||
String text = FileAccess::get_file_as_string(p_path, &err);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
JSON json;
|
||||
err = json.parse(text);
|
||||
if (err != OK) {
|
||||
ERR_PRINT("Error parsing '" + p_path + "' on line " + itos(json.get_error_line()) + ": " + json.get_error_message());
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
Dictionary data = json.get_data();
|
||||
|
||||
if (!data.has("type") || String(data["type"]) != "build_profile") {
|
||||
ERR_PRINT("Error parsing '" + p_path + "', it's not a build profile.");
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
disabled_classes.clear();
|
||||
|
||||
if (data.has("disabled_classes")) {
|
||||
Array disabled_classes_arr = data["disabled_classes"];
|
||||
for (int i = 0; i < disabled_classes_arr.size(); i++) {
|
||||
disabled_classes.insert(disabled_classes_arr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < BUILD_OPTION_MAX; i++) {
|
||||
build_options_disabled[i] = false;
|
||||
}
|
||||
|
||||
if (data.has("disabled_build_options")) {
|
||||
Dictionary disabled_build_options_arr = data["disabled_build_options"];
|
||||
List<Variant> keys;
|
||||
disabled_build_options_arr.get_key_list(&keys);
|
||||
|
||||
for (const Variant &K : keys) {
|
||||
String key = K;
|
||||
|
||||
for (int i = 0; i < BUILD_OPTION_MAX; i++) {
|
||||
String f = build_option_identifiers[i];
|
||||
if (f == key) {
|
||||
build_options_disabled[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.has("force_detect_classes")) {
|
||||
force_detect_classes = data["force_detect_classes"];
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void EditorBuildProfile::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_disable_class", "class_name", "disable"), &EditorBuildProfile::set_disable_class);
|
||||
ClassDB::bind_method(D_METHOD("is_class_disabled", "class_name"), &EditorBuildProfile::is_class_disabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_disable_build_option", "build_option", "disable"), &EditorBuildProfile::set_disable_build_option);
|
||||
ClassDB::bind_method(D_METHOD("is_build_option_disabled", "build_option"), &EditorBuildProfile::is_build_option_disabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_build_option_name", "build_option"), &EditorBuildProfile::_get_build_option_name);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("save_to_file", "path"), &EditorBuildProfile::save_to_file);
|
||||
ClassDB::bind_method(D_METHOD("load_from_file", "path"), &EditorBuildProfile::load_from_file);
|
||||
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_3D);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_PHYSICS_2D);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_PHYSICS_3D);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_NAVIGATION);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_XR);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_RENDERING_DEVICE);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_OPENGL);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_VULKAN);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_MAX);
|
||||
}
|
||||
|
||||
EditorBuildProfile::EditorBuildProfile() {}
|
||||
|
||||
//////////////////////////
|
||||
|
||||
void EditorBuildProfileManager::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_READY: {
|
||||
String last_file = EditorSettings::get_singleton()->get_project_metadata("build_profile", "last_file_path", "");
|
||||
if (!last_file.is_empty()) {
|
||||
_import_profile(last_file);
|
||||
}
|
||||
if (edited.is_null()) {
|
||||
edited.instantiate();
|
||||
_update_edited_profile();
|
||||
}
|
||||
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_profile_action(int p_action) {
|
||||
last_action = Action(p_action);
|
||||
|
||||
switch (p_action) {
|
||||
case ACTION_RESET: {
|
||||
confirm_dialog->set_text("Reset the edited profile?");
|
||||
confirm_dialog->popup_centered();
|
||||
} break;
|
||||
case ACTION_LOAD: {
|
||||
import_profile->popup_file_dialog();
|
||||
} break;
|
||||
case ACTION_SAVE: {
|
||||
if (!profile_path->get_text().is_empty()) {
|
||||
Error err = edited->save_to_file(profile_path->get_text());
|
||||
if (err != OK) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("File saving failed."));
|
||||
}
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
case ACTION_SAVE_AS: {
|
||||
export_profile->popup_file_dialog();
|
||||
export_profile->set_current_file(profile_path->get_text());
|
||||
} break;
|
||||
case ACTION_NEW: {
|
||||
confirm_dialog->set_text("Create a new profile?");
|
||||
confirm_dialog->popup_centered();
|
||||
} break;
|
||||
case ACTION_DETECT: {
|
||||
confirm_dialog->set_text("This will scan all files in the current project to detect used classes.");
|
||||
confirm_dialog->popup_centered();
|
||||
} break;
|
||||
case ACTION_MAX: {
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_find_files(EditorFileSystemDirectory *p_dir, const HashMap<String, DetectedFile> &p_cache, HashMap<String, DetectedFile> &r_detected) {
|
||||
if (p_dir == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_dir->get_file_count(); i++) {
|
||||
String p = p_dir->get_file_path(i);
|
||||
|
||||
uint64_t timestamp = 0;
|
||||
String md5;
|
||||
|
||||
if (p_cache.has(p)) {
|
||||
const DetectedFile &cache = p_cache[p];
|
||||
// Check if timestamp and MD5 match.
|
||||
timestamp = FileAccess::get_modified_time(p);
|
||||
bool cache_valid = true;
|
||||
if (cache.timestamp != timestamp) {
|
||||
md5 = FileAccess::get_md5(p);
|
||||
if (md5 != cache.md5) {
|
||||
cache_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (cache_valid) {
|
||||
r_detected.insert(p, cache);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Not cached, or cache invalid.
|
||||
|
||||
DetectedFile cache;
|
||||
|
||||
HashSet<StringName> classes;
|
||||
ResourceLoader::get_classes_used(p, &classes);
|
||||
|
||||
for (const StringName &E : classes) {
|
||||
cache.classes.push_back(E);
|
||||
}
|
||||
|
||||
if (md5.is_empty()) {
|
||||
cache.timestamp = FileAccess::get_modified_time(p);
|
||||
cache.md5 = FileAccess::get_md5(p);
|
||||
} else {
|
||||
cache.timestamp = timestamp;
|
||||
cache.md5 = md5;
|
||||
}
|
||||
|
||||
r_detected.insert(p, cache);
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_dir->get_subdir_count(); i++) {
|
||||
_find_files(p_dir->get_subdir(i), p_cache, r_detected);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_detect_classes() {
|
||||
HashMap<String, DetectedFile> previous_file_cache;
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open("res://.godot/editor/used_class_cache", FileAccess::READ);
|
||||
if (f.is_valid()) {
|
||||
while (!f->eof_reached()) {
|
||||
String l = f->get_line();
|
||||
Vector<String> fields = l.split("::");
|
||||
if (fields.size() == 4) {
|
||||
String path = fields[0];
|
||||
DetectedFile df;
|
||||
df.timestamp = fields[1].to_int();
|
||||
df.md5 = fields[2];
|
||||
df.classes = fields[3].split(",");
|
||||
previous_file_cache.insert(path, df);
|
||||
}
|
||||
}
|
||||
f.unref();
|
||||
}
|
||||
|
||||
HashMap<String, DetectedFile> updated_file_cache;
|
||||
|
||||
_find_files(EditorFileSystem::get_singleton()->get_filesystem(), previous_file_cache, updated_file_cache);
|
||||
|
||||
HashSet<StringName> used_classes;
|
||||
|
||||
// Find classes and update the disk cache in the process.
|
||||
f = FileAccess::open("res://.godot/editor/used_class_cache", FileAccess::WRITE);
|
||||
|
||||
for (const KeyValue<String, DetectedFile> &E : updated_file_cache) {
|
||||
String l = E.key + "::" + itos(E.value.timestamp) + "::" + E.value.md5 + "::";
|
||||
for (int i = 0; i < E.value.classes.size(); i++) {
|
||||
String c = E.value.classes[i];
|
||||
if (i > 0) {
|
||||
l += ",";
|
||||
}
|
||||
l += c;
|
||||
used_classes.insert(c);
|
||||
}
|
||||
f->store_line(l);
|
||||
}
|
||||
|
||||
f.unref();
|
||||
|
||||
// Add forced ones.
|
||||
|
||||
Vector<String> force_detect = edited->get_force_detect_classes().split(",");
|
||||
for (int i = 0; i < force_detect.size(); i++) {
|
||||
String c = force_detect[i].strip_edges();
|
||||
if (c.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
used_classes.insert(c);
|
||||
}
|
||||
|
||||
// Filter all classes to discard inherited ones.
|
||||
|
||||
HashSet<StringName> all_used_classes;
|
||||
|
||||
for (const StringName &E : used_classes) {
|
||||
StringName c = E;
|
||||
if (!ClassDB::class_exists(c)) {
|
||||
// Maybe this is an old class that got replaced? try getting compat class.
|
||||
c = ClassDB::get_compatibility_class(c);
|
||||
if (!c) {
|
||||
// No luck, skip.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
while (c) {
|
||||
all_used_classes.insert(c);
|
||||
c = ClassDB::get_parent_class(c);
|
||||
}
|
||||
}
|
||||
|
||||
edited->clear_disabled_classes();
|
||||
|
||||
List<StringName> all_classes;
|
||||
ClassDB::get_class_list(&all_classes);
|
||||
|
||||
for (const StringName &E : all_classes) {
|
||||
if (all_used_classes.has(E)) {
|
||||
// This class is valid, do nothing.
|
||||
continue;
|
||||
}
|
||||
|
||||
StringName p = ClassDB::get_parent_class(E);
|
||||
if (!p || all_used_classes.has(p)) {
|
||||
// If no parent, or if the parent is enabled, then add to disabled classes.
|
||||
// This way we avoid disabling redundant classes.
|
||||
edited->set_disable_class(E, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_action_confirm() {
|
||||
switch (last_action) {
|
||||
case ACTION_RESET: {
|
||||
edited.instantiate();
|
||||
_update_edited_profile();
|
||||
} break;
|
||||
case ACTION_LOAD: {
|
||||
} break;
|
||||
case ACTION_SAVE: {
|
||||
} break;
|
||||
case ACTION_SAVE_AS: {
|
||||
} break;
|
||||
case ACTION_NEW: {
|
||||
profile_path->set_text("");
|
||||
edited.instantiate();
|
||||
_update_edited_profile();
|
||||
} break;
|
||||
case ACTION_DETECT: {
|
||||
_detect_classes();
|
||||
_update_edited_profile();
|
||||
} break;
|
||||
case ACTION_MAX: {
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected) {
|
||||
TreeItem *class_item = class_list->create_item(p_parent);
|
||||
class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
class_item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_class, "Node"));
|
||||
String text = p_class;
|
||||
|
||||
bool disabled = edited->is_class_disabled(p_class);
|
||||
if (disabled) {
|
||||
class_item->set_custom_color(0, class_list->get_theme_color(SNAME("disabled_font_color"), SNAME("Editor")));
|
||||
}
|
||||
|
||||
class_item->set_text(0, text);
|
||||
class_item->set_editable(0, true);
|
||||
class_item->set_selectable(0, true);
|
||||
class_item->set_metadata(0, p_class);
|
||||
|
||||
bool collapsed = edited->is_item_collapsed(p_class);
|
||||
class_item->set_collapsed(collapsed);
|
||||
|
||||
if (p_class == p_selected) {
|
||||
class_item->select(0);
|
||||
}
|
||||
if (disabled) {
|
||||
// Class disabled, do nothing else (do not show further).
|
||||
return;
|
||||
}
|
||||
|
||||
class_item->set_checked(0, true); // If it's not disabled, its checked.
|
||||
|
||||
List<StringName> child_classes;
|
||||
ClassDB::get_direct_inheriters_from_class(p_class, &child_classes);
|
||||
child_classes.sort_custom<StringName::AlphCompare>();
|
||||
|
||||
for (const StringName &name : child_classes) {
|
||||
if (String(name).begins_with("Editor") || ClassDB::get_api_type(name) != ClassDB::API_CORE) {
|
||||
continue;
|
||||
}
|
||||
_fill_classes_from(class_item, name, p_selected);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_class_list_item_selected() {
|
||||
if (updating_build_options) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *item = class_list->get_selected();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
Variant md = item->get_metadata(0);
|
||||
if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
|
||||
String class_name = md;
|
||||
String class_description;
|
||||
|
||||
DocTools *dd = EditorHelp::get_doc_data();
|
||||
HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_name);
|
||||
if (E) {
|
||||
class_description = DTR(E->value.brief_description);
|
||||
}
|
||||
|
||||
description_bit->set_text(class_description);
|
||||
} else if (md.get_type() == Variant::INT) {
|
||||
int build_option_id = md;
|
||||
String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption(build_option_id));
|
||||
|
||||
description_bit->set_text(TTRGET(build_option_description));
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_class_list_item_edited() {
|
||||
if (updating_build_options) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *item = class_list->get_edited();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool checked = item->is_checked(0);
|
||||
|
||||
Variant md = item->get_metadata(0);
|
||||
if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
|
||||
String class_selected = md;
|
||||
edited->set_disable_class(class_selected, !checked);
|
||||
_update_edited_profile();
|
||||
} else if (md.get_type() == Variant::INT) {
|
||||
int build_option_selected = md;
|
||||
edited->set_disable_build_option(EditorBuildProfile::BuildOption(build_option_selected), !checked);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_class_list_item_collapsed(Object *p_item) {
|
||||
if (updating_build_options) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *item = Object::cast_to<TreeItem>(p_item);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
Variant md = item->get_metadata(0);
|
||||
if (md.get_type() != Variant::STRING && md.get_type() != Variant::STRING_NAME) {
|
||||
return;
|
||||
}
|
||||
|
||||
String class_name = md;
|
||||
bool collapsed = item->is_collapsed();
|
||||
edited->set_item_collapsed(class_name, collapsed);
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_update_edited_profile() {
|
||||
String class_selected;
|
||||
int build_option_selected = -1;
|
||||
|
||||
if (class_list->get_selected()) {
|
||||
Variant md = class_list->get_selected()->get_metadata(0);
|
||||
if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
|
||||
class_selected = md;
|
||||
} else if (md.get_type() == Variant::INT) {
|
||||
build_option_selected = md;
|
||||
}
|
||||
}
|
||||
|
||||
class_list->clear();
|
||||
|
||||
updating_build_options = true;
|
||||
|
||||
TreeItem *root = class_list->create_item();
|
||||
|
||||
TreeItem *build_options = class_list->create_item(root);
|
||||
build_options->set_text(0, TTR("General Features:"));
|
||||
for (int i = 0; i < EditorBuildProfile::BUILD_OPTION_MAX; i++) {
|
||||
TreeItem *build_option;
|
||||
build_option = class_list->create_item(build_options);
|
||||
|
||||
build_option->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
build_option->set_text(0, EditorBuildProfile::get_build_option_name(EditorBuildProfile::BuildOption(i)));
|
||||
build_option->set_selectable(0, true);
|
||||
build_option->set_editable(0, true);
|
||||
build_option->set_metadata(0, i);
|
||||
if (!edited->is_build_option_disabled(EditorBuildProfile::BuildOption(i))) {
|
||||
build_option->set_checked(0, true);
|
||||
}
|
||||
|
||||
if (i == build_option_selected) {
|
||||
build_option->select(0);
|
||||
}
|
||||
}
|
||||
|
||||
TreeItem *classes = class_list->create_item(root);
|
||||
classes->set_text(0, TTR("Nodes and Classes:"));
|
||||
|
||||
_fill_classes_from(classes, "Node", class_selected);
|
||||
_fill_classes_from(classes, "Resource", class_selected);
|
||||
|
||||
force_detect_classes->set_text(edited->get_force_detect_classes());
|
||||
|
||||
updating_build_options = false;
|
||||
|
||||
_class_list_item_selected();
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_force_detect_classes_changed(const String &p_text) {
|
||||
if (updating_build_options) {
|
||||
return;
|
||||
}
|
||||
edited->set_force_detect_classes(force_detect_classes->get_text());
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_import_profile(const String &p_path) {
|
||||
Ref<EditorBuildProfile> profile;
|
||||
profile.instantiate();
|
||||
Error err = profile->load_from_file(p_path);
|
||||
String basefile = p_path.get_file();
|
||||
if (err != OK) {
|
||||
EditorNode::get_singleton()->show_warning(vformat(TTR("File '%s' format is invalid, import aborted."), basefile));
|
||||
return;
|
||||
}
|
||||
|
||||
profile_path->set_text(p_path);
|
||||
EditorSettings::get_singleton()->set_project_metadata("build_profile", "last_file_path", p_path);
|
||||
|
||||
edited = profile;
|
||||
_update_edited_profile();
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_export_profile(const String &p_path) {
|
||||
ERR_FAIL_COND(edited.is_null());
|
||||
Error err = edited->save_to_file(p_path);
|
||||
if (err != OK) {
|
||||
EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving profile to path: '%s'."), p_path));
|
||||
} else {
|
||||
profile_path->set_text(p_path);
|
||||
EditorSettings::get_singleton()->set_project_metadata("build_profile", "last_file_path", p_path);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<EditorBuildProfile> EditorBuildProfileManager::get_current_profile() {
|
||||
return edited;
|
||||
}
|
||||
|
||||
EditorBuildProfileManager *EditorBuildProfileManager::singleton = nullptr;
|
||||
|
||||
void EditorBuildProfileManager::_bind_methods() {
|
||||
ClassDB::bind_method("_update_selected_profile", &EditorBuildProfileManager::_update_edited_profile);
|
||||
}
|
||||
|
||||
EditorBuildProfileManager::EditorBuildProfileManager() {
|
||||
VBoxContainer *main_vbc = memnew(VBoxContainer);
|
||||
add_child(main_vbc);
|
||||
|
||||
HBoxContainer *path_hbc = memnew(HBoxContainer);
|
||||
profile_path = memnew(LineEdit);
|
||||
path_hbc->add_child(profile_path);
|
||||
profile_path->set_editable(true);
|
||||
profile_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
|
||||
profile_actions[ACTION_NEW] = memnew(Button(TTR("New")));
|
||||
path_hbc->add_child(profile_actions[ACTION_NEW]);
|
||||
profile_actions[ACTION_NEW]->connect("pressed", callable_mp(this, &EditorBuildProfileManager::_profile_action), varray(ACTION_NEW));
|
||||
|
||||
profile_actions[ACTION_LOAD] = memnew(Button(TTR("Load")));
|
||||
path_hbc->add_child(profile_actions[ACTION_LOAD]);
|
||||
profile_actions[ACTION_LOAD]->connect("pressed", callable_mp(this, &EditorBuildProfileManager::_profile_action), varray(ACTION_LOAD));
|
||||
|
||||
profile_actions[ACTION_SAVE] = memnew(Button(TTR("Save")));
|
||||
path_hbc->add_child(profile_actions[ACTION_SAVE]);
|
||||
profile_actions[ACTION_SAVE]->connect("pressed", callable_mp(this, &EditorBuildProfileManager::_profile_action), varray(ACTION_SAVE));
|
||||
|
||||
profile_actions[ACTION_SAVE_AS] = memnew(Button(TTR("Save As")));
|
||||
path_hbc->add_child(profile_actions[ACTION_SAVE_AS]);
|
||||
profile_actions[ACTION_SAVE_AS]->connect("pressed", callable_mp(this, &EditorBuildProfileManager::_profile_action), varray(ACTION_SAVE_AS));
|
||||
|
||||
main_vbc->add_margin_child(TTR("Profile:"), path_hbc);
|
||||
|
||||
main_vbc->add_child(memnew(HSeparator));
|
||||
|
||||
HBoxContainer *profiles_hbc = memnew(HBoxContainer);
|
||||
|
||||
profile_actions[ACTION_RESET] = memnew(Button(TTR("Reset to Defaults")));
|
||||
profiles_hbc->add_child(profile_actions[ACTION_RESET]);
|
||||
profile_actions[ACTION_RESET]->connect("pressed", callable_mp(this, &EditorBuildProfileManager::_profile_action), varray(ACTION_RESET));
|
||||
|
||||
profile_actions[ACTION_DETECT] = memnew(Button(TTR("Detect from Project")));
|
||||
profiles_hbc->add_child(profile_actions[ACTION_DETECT]);
|
||||
profile_actions[ACTION_DETECT]->connect("pressed", callable_mp(this, &EditorBuildProfileManager::_profile_action), varray(ACTION_DETECT));
|
||||
|
||||
main_vbc->add_margin_child(TTR("Actions:"), profiles_hbc);
|
||||
|
||||
class_list = memnew(Tree);
|
||||
class_list->set_hide_root(true);
|
||||
class_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);
|
||||
class_list->connect("cell_selected", callable_mp(this, &EditorBuildProfileManager::_class_list_item_selected));
|
||||
class_list->connect("item_edited", callable_mp(this, &EditorBuildProfileManager::_class_list_item_edited), varray(), CONNECT_DEFERRED);
|
||||
class_list->connect("item_collapsed", callable_mp(this, &EditorBuildProfileManager::_class_list_item_collapsed));
|
||||
// It will be displayed once the user creates or chooses a profile.
|
||||
main_vbc->add_margin_child(TTR("Configure Engine Build Profile:"), class_list, true);
|
||||
|
||||
description_bit = memnew(EditorHelpBit);
|
||||
description_bit->set_custom_minimum_size(Size2(0, 80) * EDSCALE);
|
||||
main_vbc->add_margin_child(TTR("Description:"), description_bit, false);
|
||||
|
||||
confirm_dialog = memnew(ConfirmationDialog);
|
||||
add_child(confirm_dialog);
|
||||
confirm_dialog->set_title(TTR("Please Confirm:"));
|
||||
confirm_dialog->connect("confirmed", callable_mp(this, &EditorBuildProfileManager::_action_confirm));
|
||||
|
||||
import_profile = memnew(EditorFileDialog);
|
||||
add_child(import_profile);
|
||||
import_profile->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
|
||||
import_profile->add_filter("*.build", TTR("Egine Build Profile"));
|
||||
import_profile->connect("files_selected", callable_mp(this, &EditorBuildProfileManager::_import_profile));
|
||||
import_profile->set_title(TTR("Load Profile"));
|
||||
import_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
|
||||
|
||||
export_profile = memnew(EditorFileDialog);
|
||||
add_child(export_profile);
|
||||
export_profile->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
|
||||
export_profile->add_filter("*.build", TTR("Egine Build Profile"));
|
||||
export_profile->connect("file_selected", callable_mp(this, &EditorBuildProfileManager::_export_profile));
|
||||
export_profile->set_title(TTR("Export Profile"));
|
||||
export_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
|
||||
|
||||
force_detect_classes = memnew(LineEdit);
|
||||
main_vbc->add_margin_child(TTR("Forced classes on detect:"), force_detect_classes);
|
||||
force_detect_classes->connect("text_changed", callable_mp(this, &EditorBuildProfileManager::_force_detect_classes_changed));
|
||||
|
||||
set_title(TTR("Edit Build Configuration Profile"));
|
||||
|
||||
singleton = this;
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/*************************************************************************/
|
||||
/* editor_build_profile.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef EDITOR_BUILD_PROFILE_H
|
||||
#define EDITOR_BUILD_PROFILE_H
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "editor/editor_help.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/option_button.h"
|
||||
#include "scene/gui/separator.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class EditorBuildProfile : public RefCounted {
|
||||
GDCLASS(EditorBuildProfile, RefCounted);
|
||||
|
||||
public:
|
||||
enum BuildOption {
|
||||
BUILD_OPTION_3D,
|
||||
BUILD_OPTION_PHYSICS_2D,
|
||||
BUILD_OPTION_PHYSICS_3D,
|
||||
BUILD_OPTION_NAVIGATION,
|
||||
BUILD_OPTION_XR,
|
||||
BUILD_OPTION_RENDERING_DEVICE,
|
||||
BUILD_OPTION_OPENGL,
|
||||
BUILD_OPTION_VULKAN,
|
||||
BUILD_OPTION_MAX
|
||||
};
|
||||
|
||||
private:
|
||||
HashSet<StringName> disabled_classes;
|
||||
|
||||
HashSet<StringName> collapsed_classes;
|
||||
|
||||
String force_detect_classes;
|
||||
|
||||
bool build_options_disabled[BUILD_OPTION_MAX] = {};
|
||||
static const char *build_option_identifiers[BUILD_OPTION_MAX];
|
||||
static const bool build_option_disable_values[BUILD_OPTION_MAX];
|
||||
|
||||
String _get_build_option_name(BuildOption p_build_option) { return get_build_option_name(p_build_option); }
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_disable_class(const StringName &p_class, bool p_disabled);
|
||||
bool is_class_disabled(const StringName &p_class) const;
|
||||
|
||||
void set_item_collapsed(const StringName &p_class, bool p_collapsed);
|
||||
bool is_item_collapsed(const StringName &p_class) const;
|
||||
|
||||
void set_disable_build_option(BuildOption p_build_option, bool p_disable);
|
||||
bool is_build_option_disabled(BuildOption p_build_option) const;
|
||||
|
||||
void set_force_detect_classes(const String &p_classes);
|
||||
String get_force_detect_classes() const;
|
||||
|
||||
void clear_disabled_classes();
|
||||
|
||||
Error save_to_file(const String &p_path);
|
||||
Error load_from_file(const String &p_path);
|
||||
|
||||
static String get_build_option_name(BuildOption p_build_option);
|
||||
static String get_build_option_description(BuildOption p_build_option);
|
||||
static bool get_build_option_disable_value(BuildOption p_build_option);
|
||||
|
||||
EditorBuildProfile();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(EditorBuildProfile::BuildOption)
|
||||
|
||||
class EditorFileSystemDirectory;
|
||||
|
||||
class EditorBuildProfileManager : public AcceptDialog {
|
||||
GDCLASS(EditorBuildProfileManager, AcceptDialog);
|
||||
|
||||
enum Action {
|
||||
ACTION_NEW,
|
||||
ACTION_RESET,
|
||||
ACTION_LOAD,
|
||||
ACTION_SAVE,
|
||||
ACTION_SAVE_AS,
|
||||
ACTION_DETECT,
|
||||
ACTION_MAX
|
||||
};
|
||||
|
||||
Action last_action = ACTION_NEW;
|
||||
|
||||
ConfirmationDialog *confirm_dialog = nullptr;
|
||||
Button *profile_actions[ACTION_MAX];
|
||||
|
||||
Tree *class_list = nullptr;
|
||||
EditorHelpBit *description_bit = nullptr;
|
||||
|
||||
EditorFileDialog *import_profile = nullptr;
|
||||
EditorFileDialog *export_profile = nullptr;
|
||||
|
||||
LineEdit *profile_path = nullptr;
|
||||
|
||||
LineEdit *force_detect_classes = nullptr;
|
||||
|
||||
void _profile_action(int p_action);
|
||||
void _action_confirm();
|
||||
|
||||
void _update_edited_profile();
|
||||
void _fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected);
|
||||
|
||||
Ref<EditorBuildProfile> edited;
|
||||
|
||||
void _import_profile(const String &p_path);
|
||||
void _export_profile(const String &p_path);
|
||||
|
||||
bool updating_build_options = false;
|
||||
|
||||
void _class_list_item_selected();
|
||||
void _class_list_item_edited();
|
||||
void _class_list_item_collapsed(Object *p_item);
|
||||
void _detect_classes();
|
||||
|
||||
void _force_detect_classes_changed(const String &p_text);
|
||||
|
||||
struct DetectedFile {
|
||||
uint32_t timestamp = 0;
|
||||
String md5;
|
||||
Vector<String> classes;
|
||||
};
|
||||
|
||||
void _find_files(EditorFileSystemDirectory *p_dir, const HashMap<String, DetectedFile> &p_cache, HashMap<String, DetectedFile> &r_detected);
|
||||
|
||||
static EditorBuildProfileManager *singleton;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
Ref<EditorBuildProfile> get_current_profile();
|
||||
|
||||
static EditorBuildProfileManager *get_singleton() { return singleton; }
|
||||
EditorBuildProfileManager();
|
||||
};
|
||||
|
||||
#endif // EDITOR_BUILD_PROFILE_H
|
|
@ -75,6 +75,7 @@
|
|||
#include "editor/dependency_editor.h"
|
||||
#include "editor/editor_about.h"
|
||||
#include "editor/editor_audio_buses.h"
|
||||
#include "editor/editor_build_profile.h"
|
||||
#include "editor/editor_command_palette.h"
|
||||
#include "editor/editor_data.h"
|
||||
#include "editor/editor_export.h"
|
||||
|
@ -2805,6 +2806,9 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
|
|||
}
|
||||
}
|
||||
} break;
|
||||
case TOOLS_BUILD_PROFILE_MANAGER: {
|
||||
build_profile_manager->popup_centered_clamped(Size2(700, 800) * EDSCALE, 0.8);
|
||||
} break;
|
||||
case RUN_USER_DATA_FOLDER: {
|
||||
// Ensure_user_data_dir() to prevent the edge case: "Open User Data Folder" won't work after the project was renamed in ProjectSettingsEditor unless the project is saved.
|
||||
OS::get_singleton()->ensure_user_data_dir();
|
||||
|
@ -6451,6 +6455,10 @@ EditorNode::EditorNode() {
|
|||
|
||||
feature_profile_manager = memnew(EditorFeatureProfileManager);
|
||||
gui_base->add_child(feature_profile_manager);
|
||||
|
||||
build_profile_manager = memnew(EditorBuildProfileManager);
|
||||
gui_base->add_child(build_profile_manager);
|
||||
|
||||
about = memnew(EditorAbout);
|
||||
gui_base->add_child(about);
|
||||
feature_profile_manager->connect("current_feature_profile_changed", callable_mp(this, &EditorNode::_feature_profile_changed));
|
||||
|
@ -6543,6 +6551,10 @@ EditorNode::EditorNode() {
|
|||
p->add_item(TTR("Install Android Build Template..."), FILE_INSTALL_ANDROID_SOURCE);
|
||||
p->add_item(TTR("Open User Data Folder"), RUN_USER_DATA_FOLDER);
|
||||
|
||||
p->add_separator();
|
||||
p->add_item(TTR("Customize Engine Build Configuration..."), TOOLS_BUILD_PROFILE_MANAGER);
|
||||
p->add_separator();
|
||||
|
||||
plugin_config_dialog = memnew(PluginConfigDialog);
|
||||
plugin_config_dialog->connect("plugin_ready", callable_mp(this, &EditorNode::_on_plugin_ready));
|
||||
gui_base->add_child(plugin_config_dialog);
|
||||
|
@ -7187,6 +7199,7 @@ EditorNode::EditorNode() {
|
|||
vshader_convert.instantiate();
|
||||
resource_conversion_plugins.push_back(vshader_convert);
|
||||
}
|
||||
|
||||
update_spinner_step_msec = OS::get_singleton()->get_ticks_msec();
|
||||
update_spinner_step_frame = Engine::get_singleton()->get_frames_drawn();
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ class TabContainer;
|
|||
class TextureProgressBar;
|
||||
class VSplitContainer;
|
||||
class Window;
|
||||
class EditorBuildProfileManager;
|
||||
|
||||
class EditorNode : public Node {
|
||||
GDCLASS(EditorNode, Node);
|
||||
|
@ -163,6 +164,7 @@ private:
|
|||
EDIT_REDO,
|
||||
EDIT_RELOAD_SAVED_SCENE,
|
||||
TOOLS_ORPHAN_RESOURCES,
|
||||
TOOLS_BUILD_PROFILE_MANAGER,
|
||||
TOOLS_CUSTOM,
|
||||
RESOURCE_SAVE,
|
||||
RESOURCE_SAVE_AS,
|
||||
|
@ -377,6 +379,7 @@ private:
|
|||
EditorFileDialog *file = nullptr;
|
||||
ExportTemplateManager *export_template_manager = nullptr;
|
||||
EditorFeatureProfileManager *feature_profile_manager = nullptr;
|
||||
EditorBuildProfileManager *build_profile_manager = nullptr;
|
||||
EditorFileDialog *file_templates = nullptr;
|
||||
EditorFileDialog *file_export_lib = nullptr;
|
||||
EditorFileDialog *file_script = nullptr;
|
||||
|
|
|
@ -65,15 +65,19 @@ Error ResourceLoaderText::_parse_sub_resource_dummy(DummyReadData *p_data, Varia
|
|||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
String unique_id = token.value;
|
||||
if (p_data->no_placeholders) {
|
||||
r_res.unref();
|
||||
} else {
|
||||
String unique_id = token.value;
|
||||
|
||||
if (!p_data->resource_map.has(unique_id)) {
|
||||
r_err_str = "Found unique_id reference before mapping, sub-resources stored out of order in resource file";
|
||||
return ERR_PARSE_ERROR;
|
||||
if (!p_data->resource_map.has(unique_id)) {
|
||||
r_err_str = "Found unique_id reference before mapping, sub-resources stored out of order in resource file";
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
r_res = p_data->resource_map[unique_id];
|
||||
}
|
||||
|
||||
r_res = p_data->resource_map[unique_id];
|
||||
|
||||
VariantParser::get_token(p_stream, token, line, r_err_str);
|
||||
if (token.type != VariantParser::TK_PARENTHESIS_CLOSE) {
|
||||
r_err_str = "Expected ')'";
|
||||
|
@ -91,11 +95,15 @@ Error ResourceLoaderText::_parse_ext_resource_dummy(DummyReadData *p_data, Varia
|
|||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
String id = token.value;
|
||||
if (p_data->no_placeholders) {
|
||||
r_res.unref();
|
||||
} else {
|
||||
String id = token.value;
|
||||
|
||||
ERR_FAIL_COND_V(!p_data->rev_external_resources.has(id), ERR_PARSE_ERROR);
|
||||
ERR_FAIL_COND_V(!p_data->rev_external_resources.has(id), ERR_PARSE_ERROR);
|
||||
|
||||
r_res = p_data->rev_external_resources[id];
|
||||
r_res = p_data->rev_external_resources[id];
|
||||
}
|
||||
|
||||
VariantParser::get_token(p_stream, token, line, r_err_str);
|
||||
if (token.type != VariantParser::TK_PARENTHESIS_CLOSE) {
|
||||
|
@ -1066,7 +1074,7 @@ static void bs_save_unicode_string(Ref<FileAccess> p_f, const String &p_string,
|
|||
p_f->store_buffer((const uint8_t *)utf8.get_data(), utf8.length() + 1);
|
||||
}
|
||||
|
||||
Error ResourceLoaderText::save_as_binary(Ref<FileAccess> p_f, const String &p_path) {
|
||||
Error ResourceLoaderText::save_as_binary(const String &p_path) {
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
@ -1271,7 +1279,7 @@ Error ResourceLoaderText::save_as_binary(Ref<FileAccess> p_f, const String &p_pa
|
|||
}
|
||||
|
||||
if (next_tag.name == "node") {
|
||||
//this is a node, must save one more!
|
||||
// This is a node, must save one more!
|
||||
|
||||
if (!is_scene) {
|
||||
error_text += "found the 'node' tag on a resource file!";
|
||||
|
@ -1346,6 +1354,126 @@ Error ResourceLoaderText::save_as_binary(Ref<FileAccess> p_f, const String &p_pa
|
|||
return OK;
|
||||
}
|
||||
|
||||
Error ResourceLoaderText::get_classes_used(HashSet<StringName> *r_classes) {
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
ignore_resource_parsing = true;
|
||||
|
||||
DummyReadData dummy_read;
|
||||
dummy_read.no_placeholders = true;
|
||||
VariantParser::ResourceParser rp;
|
||||
rp.ext_func = _parse_ext_resource_dummys;
|
||||
rp.sub_func = _parse_sub_resource_dummys;
|
||||
rp.userdata = &dummy_read;
|
||||
|
||||
while (next_tag.name == "ext_resource") {
|
||||
error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp);
|
||||
|
||||
if (error) {
|
||||
_printerr();
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
while (next_tag.name == "sub_resource" || next_tag.name == "resource") {
|
||||
if (next_tag.name == "sub_resource") {
|
||||
if (!next_tag.fields.has("type")) {
|
||||
error = ERR_FILE_CORRUPT;
|
||||
error_text = "Missing 'type' in external resource tag";
|
||||
_printerr();
|
||||
return error;
|
||||
}
|
||||
|
||||
r_classes->insert(next_tag.fields["type"]);
|
||||
|
||||
} else {
|
||||
r_classes->insert(next_tag.fields["res_type"]);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
String assign;
|
||||
Variant value;
|
||||
|
||||
error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &rp);
|
||||
|
||||
if (error) {
|
||||
if (error == ERR_FILE_EOF) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
_printerr();
|
||||
return error;
|
||||
}
|
||||
|
||||
if (!assign.is_empty()) {
|
||||
continue;
|
||||
} else if (!next_tag.name.is_empty()) {
|
||||
error = OK;
|
||||
break;
|
||||
} else {
|
||||
error = ERR_FILE_CORRUPT;
|
||||
error_text = "Premature end of file while parsing [sub_resource]";
|
||||
_printerr();
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (next_tag.name == "node") {
|
||||
// This is a node, must save one more!
|
||||
|
||||
if (!is_scene) {
|
||||
error_text += "found the 'node' tag on a resource file!";
|
||||
_printerr();
|
||||
error = ERR_FILE_CORRUPT;
|
||||
return error;
|
||||
}
|
||||
|
||||
if (!next_tag.fields.has("type")) {
|
||||
error = ERR_FILE_CORRUPT;
|
||||
error_text = "Missing 'type' in external resource tag";
|
||||
_printerr();
|
||||
return error;
|
||||
}
|
||||
|
||||
r_classes->insert(next_tag.fields["type"]);
|
||||
|
||||
while (true) {
|
||||
String assign;
|
||||
Variant value;
|
||||
|
||||
error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &rp);
|
||||
|
||||
if (error) {
|
||||
if (error == ERR_FILE_MISSING_DEPENDENCIES) {
|
||||
// Resource loading error, just skip it.
|
||||
} else if (error != ERR_FILE_EOF) {
|
||||
_printerr();
|
||||
return error;
|
||||
} else {
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (!assign.is_empty()) {
|
||||
continue;
|
||||
} else if (!next_tag.name.is_empty()) {
|
||||
error = OK;
|
||||
break;
|
||||
} else {
|
||||
error = ERR_FILE_CORRUPT;
|
||||
error_text = "Premature end of file while parsing [sub_resource]";
|
||||
_printerr();
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
String ResourceLoaderText::recognize(Ref<FileAccess> p_f) {
|
||||
error = OK;
|
||||
|
||||
|
@ -1473,6 +1601,26 @@ bool ResourceFormatLoaderText::handles_type(const String &p_type) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
void ResourceFormatLoaderText::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
|
||||
String ext = p_path.get_extension().to_lower();
|
||||
if (ext == "tscn") {
|
||||
r_classes->insert("PackedScene");
|
||||
}
|
||||
|
||||
// ...for anything else must test...
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
|
||||
if (f.is_null()) {
|
||||
return; // Could not read.
|
||||
}
|
||||
|
||||
ResourceLoaderText loader;
|
||||
loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
|
||||
loader.res_path = loader.local_path;
|
||||
loader.open(f);
|
||||
loader.get_classes_used(r_classes);
|
||||
}
|
||||
|
||||
String ResourceFormatLoaderText::get_resource_type(const String &p_path) const {
|
||||
String ext = p_path.get_extension().to_lower();
|
||||
if (ext == "tscn") {
|
||||
|
@ -1561,7 +1709,7 @@ Error ResourceFormatLoaderText::convert_file_to_binary(const String &p_src_path,
|
|||
loader.local_path = ProjectSettings::get_singleton()->localize_path(path);
|
||||
loader.res_path = loader.local_path;
|
||||
loader.open(f);
|
||||
return loader.save_as_binary(f, p_dst_path);
|
||||
return loader.save_as_binary(p_dst_path);
|
||||
}
|
||||
|
||||
/*****************************************************************************************************/
|
||||
|
|
|
@ -90,6 +90,7 @@ class ResourceLoaderText {
|
|||
};
|
||||
|
||||
struct DummyReadData {
|
||||
bool no_placeholders = false;
|
||||
HashMap<Ref<Resource>, int> external_resources;
|
||||
HashMap<String, Ref<Resource>> rev_external_resources;
|
||||
HashMap<Ref<Resource>, int> resource_index_map;
|
||||
|
@ -125,8 +126,9 @@ public:
|
|||
ResourceUID::ID get_uid(Ref<FileAccess> p_f);
|
||||
void get_dependencies(Ref<FileAccess> p_f, List<String> *p_dependencies, bool p_add_types);
|
||||
Error rename_dependencies(Ref<FileAccess> p_f, const String &p_path, const HashMap<String, String> &p_map);
|
||||
Error get_classes_used(HashSet<StringName> *r_classes);
|
||||
|
||||
Error save_as_binary(Ref<FileAccess> p_f, const String &p_path);
|
||||
Error save_as_binary(const String &p_path);
|
||||
ResourceLoaderText();
|
||||
};
|
||||
|
||||
|
@ -137,6 +139,8 @@ public:
|
|||
virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const;
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const;
|
||||
virtual bool handles_type(const String &p_type) const;
|
||||
virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes);
|
||||
|
||||
virtual String get_resource_type(const String &p_path) const;
|
||||
virtual ResourceUID::ID get_resource_uid(const String &p_path) const;
|
||||
virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false);
|
||||
|
|
Loading…
Reference in New Issue