mirror of
https://github.com/godotengine/godot.git
synced 2024-11-25 05:33:11 +00:00
Add support for scene/resource customization in export plugins
EditorExportPlugin adds a set of callbacks to allow customizing scenes, resources or subresources in all files exported: * Can take scene files, resource files and subresources in all of them. * Uses a cache for the converted files if nothing changes, so this work only happens if a file is modified. * Uses hashing to differentiate export configuration caches. * Removed the previous conversion code to binary, as this one uses existing stuff. This API is useful in several scenarios: * Needed by the "server" export platform to get rid of textures, meshes, audio, etc. * Needed by text to binary converters. * Needed by eventual optimizations such as shader precompiling on export, mesh merging and optimization, etc. This is a draft, feedback is very welcome.
This commit is contained in:
parent
c40855f818
commit
ef17c4668a
@ -32,6 +32,7 @@
|
||||
|
||||
#include "core/io/file_access_encrypted.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "core/string/string_builder.h"
|
||||
#include "core/variant/variant_parser.h"
|
||||
|
||||
PackedStringArray ConfigFile::_get_sections() const {
|
||||
@ -130,6 +131,28 @@ void ConfigFile::erase_section_key(const String &p_section, const String &p_key)
|
||||
}
|
||||
}
|
||||
|
||||
String ConfigFile::encode_to_text() const {
|
||||
StringBuilder sb;
|
||||
bool first = true;
|
||||
for (const KeyValue<String, HashMap<String, Variant>> &E : values) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
sb.append("\n");
|
||||
}
|
||||
if (!E.key.is_empty()) {
|
||||
sb.append("[" + E.key + "]\n\n");
|
||||
}
|
||||
|
||||
for (const KeyValue<String, Variant> &F : E.value) {
|
||||
String vstr;
|
||||
VariantWriter::write_to_string(F.value, vstr);
|
||||
sb.append(F.key.property_name_encode() + "=" + vstr + "\n");
|
||||
}
|
||||
}
|
||||
return sb.as_string();
|
||||
}
|
||||
|
||||
Error ConfigFile::save(const String &p_path) {
|
||||
Error err;
|
||||
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err);
|
||||
@ -295,6 +318,7 @@ Error ConfigFile::_parse(const String &p_path, VariantParser::Stream *p_stream)
|
||||
void ConfigFile::clear() {
|
||||
values.clear();
|
||||
}
|
||||
|
||||
void ConfigFile::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_value", "section", "key", "value"), &ConfigFile::set_value);
|
||||
ClassDB::bind_method(D_METHOD("get_value", "section", "key", "default"), &ConfigFile::get_value, DEFVAL(Variant()));
|
||||
@ -312,6 +336,8 @@ void ConfigFile::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("parse", "data"), &ConfigFile::parse);
|
||||
ClassDB::bind_method(D_METHOD("save", "path"), &ConfigFile::save);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("encode_to_text"), &ConfigFile::encode_to_text);
|
||||
|
||||
BIND_METHOD_ERR_RETURN_DOC("load", ERR_FILE_CANT_OPEN);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("load_encrypted", "path", "key"), &ConfigFile::load_encrypted);
|
||||
|
@ -68,6 +68,8 @@ public:
|
||||
Error load(const String &p_path);
|
||||
Error parse(const String &p_data);
|
||||
|
||||
String encode_to_text() const; // used by exporter
|
||||
|
||||
void clear();
|
||||
|
||||
Error load_encrypted(const String &p_path, const Vector<uint8_t> &p_key);
|
||||
|
@ -98,6 +98,12 @@
|
||||
Removes the entire contents of the config.
|
||||
</description>
|
||||
</method>
|
||||
<method name="encode_to_text" qualifiers="const">
|
||||
<return type="String" />
|
||||
<description>
|
||||
Obtain the text version of this config file (the same text that would be written to a file).
|
||||
</description>
|
||||
</method>
|
||||
<method name="erase_section">
|
||||
<return type="void" />
|
||||
<param index="0" name="section" type="String" />
|
||||
|
9
doc/classes/EditorExportPlatform.xml
Normal file
9
doc/classes/EditorExportPlatform.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="EditorExportPlatform" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
|
||||
<brief_description>
|
||||
</brief_description>
|
||||
<description>
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
</class>
|
@ -10,6 +10,51 @@
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="_begin_customize_resources" qualifiers="virtual const">
|
||||
<return type="bool" />
|
||||
<param index="0" name="platform" type="EditorExportPlatform" />
|
||||
<param index="1" name="features" type="PackedStringArray" />
|
||||
<description>
|
||||
Return true if this plugin will customize resources based on the platform and features used.
|
||||
</description>
|
||||
</method>
|
||||
<method name="_begin_customize_scenes" qualifiers="virtual const">
|
||||
<return type="bool" />
|
||||
<param index="0" name="platform" type="EditorExportPlatform" />
|
||||
<param index="1" name="features" type="PackedStringArray" />
|
||||
<description>
|
||||
Return true if this plugin will customize scenes based on the platform and features used.
|
||||
</description>
|
||||
</method>
|
||||
<method name="_customize_resource" qualifiers="virtual">
|
||||
<return type="Resource" />
|
||||
<param index="0" name="resource" type="Resource" />
|
||||
<param index="1" name="path" type="String" />
|
||||
<description>
|
||||
Customize a resource. If changes are made to it, return the same or a new resource. Otherwise, return [code]null[/code].
|
||||
The [i]path[/i] argument is only used when customizing an actual file, otherwise this means that this resource is part of another one and it will be empty.
|
||||
</description>
|
||||
</method>
|
||||
<method name="_customize_scene" qualifiers="virtual">
|
||||
<return type="Node" />
|
||||
<param index="0" name="scene" type="Node" />
|
||||
<param index="1" name="path" type="String" />
|
||||
<description>
|
||||
Customize a scene. If changes are made to it, return the same or a new scene. Otherwise, return [code]null[/code]. If a new scene is returned, it is up to you to dispose of the old one.
|
||||
</description>
|
||||
</method>
|
||||
<method name="_end_customize_resources" qualifiers="virtual">
|
||||
<return type="void" />
|
||||
<description>
|
||||
This is called when the customization process for resources ends.
|
||||
</description>
|
||||
</method>
|
||||
<method name="_end_customize_scenes" qualifiers="virtual">
|
||||
<return type="void" />
|
||||
<description>
|
||||
This is called when the customization process for scenes ends.
|
||||
</description>
|
||||
</method>
|
||||
<method name="_export_begin" qualifiers="virtual">
|
||||
<return type="void" />
|
||||
<param index="0" name="features" type="PackedStringArray" />
|
||||
@ -36,6 +81,18 @@
|
||||
Calling [method skip] inside this callback will make the file not included in the export.
|
||||
</description>
|
||||
</method>
|
||||
<method name="_get_customization_configuration_hash" qualifiers="virtual const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Return a hash based on the configuration passed (for both scenes and resources). This helps keep separate caches for separate export configurations.
|
||||
</description>
|
||||
</method>
|
||||
<method name="_get_name" qualifiers="virtual const">
|
||||
<return type="String" />
|
||||
<description>
|
||||
Return the name identifier of this plugin (for future identification by the exporter).
|
||||
</description>
|
||||
</method>
|
||||
<method name="add_file">
|
||||
<return type="void" />
|
||||
<param index="0" name="path" type="String" />
|
||||
|
@ -4107,6 +4107,7 @@ void EditorNode::register_editor_types() {
|
||||
GDREGISTER_CLASS(EditorSyntaxHighlighter);
|
||||
GDREGISTER_ABSTRACT_CLASS(EditorInterface);
|
||||
GDREGISTER_CLASS(EditorExportPlugin);
|
||||
GDREGISTER_ABSTRACT_CLASS(EditorExportPlatform);
|
||||
GDREGISTER_CLASS(EditorResourceConversionPlugin);
|
||||
GDREGISTER_CLASS(EditorSceneFormatImporter);
|
||||
GDREGISTER_CLASS(EditorScenePostImportPlugin);
|
||||
@ -7418,11 +7419,6 @@ EditorNode::EditorNode() {
|
||||
editor_plugins_force_over = memnew(EditorPluginList);
|
||||
editor_plugins_force_input_forwarding = memnew(EditorPluginList);
|
||||
|
||||
Ref<EditorExportTextSceneToBinaryPlugin> export_text_to_binary_plugin;
|
||||
export_text_to_binary_plugin.instantiate();
|
||||
|
||||
EditorExport::get_singleton()->add_export_plugin(export_text_to_binary_plugin);
|
||||
|
||||
Ref<GDExtensionExportPlugin> gdextension_export_plugin;
|
||||
gdextension_export_plugin.instantiate();
|
||||
|
||||
|
@ -351,6 +351,8 @@ EditorExport::EditorExport() {
|
||||
|
||||
singleton = this;
|
||||
set_process(true);
|
||||
|
||||
GLOBAL_DEF("editor/export/convert_text_resources_to_binary", true);
|
||||
}
|
||||
|
||||
EditorExport::~EditorExport() {
|
||||
|
@ -44,6 +44,7 @@
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/plugins/script_editor_plugin.h"
|
||||
#include "editor_export_plugin.h"
|
||||
#include "scene/resources/packed_scene.h"
|
||||
|
||||
static int _get_pad(int p_alignment, int p_n) {
|
||||
int rest = p_n % p_alignment;
|
||||
@ -488,6 +489,295 @@ EditorExportPlatform::ExportNotifier::~ExportNotifier() {
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorExportPlatform::_export_customize_dictionary(Dictionary &dict, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins) {
|
||||
bool changed = false;
|
||||
|
||||
List<Variant> keys;
|
||||
dict.get_key_list(&keys);
|
||||
for (const Variant &K : keys) {
|
||||
Variant v = dict[K];
|
||||
switch (v.get_type()) {
|
||||
case Variant::OBJECT: {
|
||||
Ref<Resource> res = v;
|
||||
if (res.is_valid()) {
|
||||
for (uint32_t j = 0; j < customize_resources_plugins.size(); j++) {
|
||||
Ref<Resource> new_res = customize_resources_plugins[j]->_customize_resource(res, "");
|
||||
if (new_res.is_valid()) {
|
||||
changed = true;
|
||||
if (new_res != res) {
|
||||
dict[K] = new_res;
|
||||
res = new_res;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If it was not replaced, go through and see if there is something to replace.
|
||||
if (res.is_valid() && !res->get_path().is_resource_file() && _export_customize_object(res.ptr(), customize_resources_plugins), true) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
} break;
|
||||
case Variant::DICTIONARY: {
|
||||
Dictionary d = v;
|
||||
if (_export_customize_dictionary(d, customize_resources_plugins)) {
|
||||
changed = true;
|
||||
}
|
||||
} break;
|
||||
case Variant::ARRAY: {
|
||||
Array a = v;
|
||||
if (_export_customize_array(a, customize_resources_plugins)) {
|
||||
changed = true;
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool EditorExportPlatform::_export_customize_array(Array &arr, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins) {
|
||||
bool changed = false;
|
||||
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
Variant v = arr.get(i);
|
||||
switch (v.get_type()) {
|
||||
case Variant::OBJECT: {
|
||||
Ref<Resource> res = v;
|
||||
if (res.is_valid()) {
|
||||
for (uint32_t j = 0; j < customize_resources_plugins.size(); j++) {
|
||||
Ref<Resource> new_res = customize_resources_plugins[j]->_customize_resource(res, "");
|
||||
if (new_res.is_valid()) {
|
||||
changed = true;
|
||||
if (new_res != res) {
|
||||
arr.set(i, new_res);
|
||||
res = new_res;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If it was not replaced, go through and see if there is something to replace.
|
||||
if (res.is_valid() && !res->get_path().is_resource_file() && _export_customize_object(res.ptr(), customize_resources_plugins), true) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case Variant::DICTIONARY: {
|
||||
Dictionary d = v;
|
||||
if (_export_customize_dictionary(d, customize_resources_plugins)) {
|
||||
changed = true;
|
||||
}
|
||||
} break;
|
||||
case Variant::ARRAY: {
|
||||
Array a = v;
|
||||
if (_export_customize_array(a, customize_resources_plugins)) {
|
||||
changed = true;
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool EditorExportPlatform::_export_customize_object(Object *p_object, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins) {
|
||||
bool changed = false;
|
||||
|
||||
List<PropertyInfo> props;
|
||||
p_object->get_property_list(&props);
|
||||
for (const PropertyInfo &E : props) {
|
||||
switch (E.type) {
|
||||
case Variant::OBJECT: {
|
||||
Ref<Resource> res = p_object->get(E.name);
|
||||
if (res.is_valid()) {
|
||||
for (uint32_t j = 0; j < customize_resources_plugins.size(); j++) {
|
||||
Ref<Resource> new_res = customize_resources_plugins[j]->_customize_resource(res, "");
|
||||
if (new_res.is_valid()) {
|
||||
changed = true;
|
||||
if (new_res != res) {
|
||||
p_object->set(E.name, new_res);
|
||||
res = new_res;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If it was not replaced, go through and see if there is something to replace.
|
||||
if (res.is_valid() && !res->get_path().is_resource_file() && _export_customize_object(res.ptr(), customize_resources_plugins), true) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
} break;
|
||||
case Variant::DICTIONARY: {
|
||||
Dictionary d = p_object->get(E.name);
|
||||
if (_export_customize_dictionary(d, customize_resources_plugins)) {
|
||||
// May have been generated, so set back just in case
|
||||
p_object->set(E.name, d);
|
||||
changed = true;
|
||||
}
|
||||
} break;
|
||||
case Variant::ARRAY: {
|
||||
Array a = p_object->get(E.name);
|
||||
if (_export_customize_array(a, customize_resources_plugins)) {
|
||||
// May have been generated, so set back just in case
|
||||
p_object->set(E.name, a);
|
||||
changed = true;
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool EditorExportPlatform::_export_customize_scene_resources(Node *p_root, Node *p_node, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins) {
|
||||
bool changed = false;
|
||||
|
||||
if (p_node == p_root || p_node->get_owner() == p_root) {
|
||||
if (_export_customize_object(p_node, customize_resources_plugins)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_node->get_child_count(); i++) {
|
||||
if (_export_customize_scene_resources(p_root, p_node->get_child(i), customize_resources_plugins)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
String EditorExportPlatform::_export_customize(const String &p_path, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins, LocalVector<Ref<EditorExportPlugin>> &customize_scenes_plugins, HashMap<String, FileExportCache> &export_cache, const String &export_base_path, bool p_force_save) {
|
||||
if (!p_force_save && customize_resources_plugins.is_empty() && customize_scenes_plugins.is_empty()) {
|
||||
return p_path; // do none
|
||||
}
|
||||
|
||||
// Check if a cache exists
|
||||
if (export_cache.has(p_path)) {
|
||||
FileExportCache &fec = export_cache[p_path];
|
||||
|
||||
if (fec.saved_path.is_empty() || FileAccess::exists(fec.saved_path)) {
|
||||
// Destination file exists (was not erased) or not needed
|
||||
|
||||
uint64_t mod_time = FileAccess::get_modified_time(p_path);
|
||||
if (fec.source_modified_time == mod_time) {
|
||||
// Cached (modified time matches).
|
||||
fec.used = true;
|
||||
return fec.saved_path.is_empty() ? p_path : fec.saved_path;
|
||||
}
|
||||
|
||||
String md5 = FileAccess::get_md5(p_path);
|
||||
if (FileAccess::exists(p_path + ".import")) {
|
||||
// Also consider the import file in the string
|
||||
md5 += FileAccess::get_md5(p_path + ".import");
|
||||
}
|
||||
if (fec.source_md5 == md5) {
|
||||
// Cached (md5 matches).
|
||||
fec.source_modified_time = mod_time;
|
||||
fec.used = true;
|
||||
return fec.saved_path.is_empty() ? p_path : fec.saved_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileExportCache fec;
|
||||
fec.used = true;
|
||||
fec.source_modified_time = FileAccess::get_modified_time(p_path);
|
||||
|
||||
String md5 = FileAccess::get_md5(p_path);
|
||||
if (FileAccess::exists(p_path + ".import")) {
|
||||
// Also consider the import file in the string
|
||||
md5 += FileAccess::get_md5(p_path + ".import");
|
||||
}
|
||||
|
||||
fec.source_md5 = md5;
|
||||
|
||||
// Check if it should convert
|
||||
|
||||
String type = ResourceLoader::get_resource_type(p_path);
|
||||
|
||||
bool modified = false;
|
||||
|
||||
String save_path;
|
||||
|
||||
if (type == "PackedScene") { // Its a scene.
|
||||
Ref<PackedScene> ps = ResourceLoader::load(p_path, "PackedScene", ResourceFormatLoader::CACHE_MODE_IGNORE);
|
||||
ERR_FAIL_COND_V(ps.is_null(), p_path);
|
||||
Node *node = ps->instantiate();
|
||||
ERR_FAIL_COND_V(node == nullptr, p_path);
|
||||
if (customize_scenes_plugins.size()) {
|
||||
for (uint32_t i = 0; i < customize_scenes_plugins.size(); i++) {
|
||||
Node *customized = customize_scenes_plugins[i]->_customize_scene(node, p_path);
|
||||
if (customized != nullptr) {
|
||||
node = customized;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (customize_resources_plugins.size()) {
|
||||
if (_export_customize_scene_resources(node, node, customize_resources_plugins)) {
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (modified || p_force_save) {
|
||||
// If modified, save it again. This is also used for TSCN -> SCN conversion on export.
|
||||
|
||||
String base_file = p_path.get_file().get_basename() + ".scn"; // use SCN for saving (binary) and repack (If conversting, TSCN PackedScene representation is inefficient, so repacking is also desired).
|
||||
save_path = export_base_path.path_join("export-" + p_path.md5_text() + "-" + base_file);
|
||||
|
||||
Ref<PackedScene> s;
|
||||
s.instantiate();
|
||||
s->pack(node);
|
||||
Error err = ResourceSaver::save(s, save_path);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, p_path, "Unable to save export scene file to: " + save_path);
|
||||
}
|
||||
} else {
|
||||
Ref<Resource> res = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE);
|
||||
ERR_FAIL_COND_V(res.is_null(), p_path);
|
||||
|
||||
if (customize_resources_plugins.size()) {
|
||||
for (uint32_t i = 0; i < customize_resources_plugins.size(); i++) {
|
||||
Ref<Resource> new_res = customize_resources_plugins[i]->_customize_resource(res, p_path);
|
||||
if (new_res.is_valid()) {
|
||||
modified = true;
|
||||
if (new_res != res) {
|
||||
res = new_res;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_export_customize_object(res.ptr(), customize_resources_plugins)) {
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (modified || p_force_save) {
|
||||
// If modified, save it again. This is also used for TRES -> RES conversion on export.
|
||||
|
||||
String base_file = p_path.get_file().get_basename() + ".res"; // use RES for saving (binary)
|
||||
save_path = export_base_path.path_join("export-" + p_path.md5_text() + "-" + base_file);
|
||||
|
||||
Error err = ResourceSaver::save(res, save_path);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, p_path, "Unable to save export resource file to: " + save_path);
|
||||
}
|
||||
}
|
||||
|
||||
fec.saved_path = save_path;
|
||||
|
||||
export_cache[p_path] = fec;
|
||||
|
||||
return save_path.is_empty() ? p_path : save_path;
|
||||
}
|
||||
|
||||
Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) {
|
||||
//figure out paths of files that will be exported
|
||||
HashSet<String> paths;
|
||||
@ -601,6 +891,15 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
|
||||
Error err = OK;
|
||||
Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
|
||||
|
||||
struct SortByName {
|
||||
bool operator()(const Ref<EditorExportPlugin> &left, const Ref<EditorExportPlugin> &right) const {
|
||||
return left->_get_name() < right->_get_name();
|
||||
}
|
||||
};
|
||||
|
||||
// Always sort by name, to so if for some reason theya are re-arranged, it still works.
|
||||
export_plugins.sort_custom<SortByName>();
|
||||
|
||||
for (int i = 0; i < export_plugins.size(); i++) {
|
||||
export_plugins.write[i]->set_export_preset(p_preset);
|
||||
|
||||
@ -623,6 +922,65 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
|
||||
}
|
||||
|
||||
HashSet<String> features = get_features(p_preset, p_debug);
|
||||
PackedStringArray features_psa;
|
||||
for (const String &feature : features) {
|
||||
features_psa.push_back(feature);
|
||||
}
|
||||
|
||||
// Check if custom processing is needed
|
||||
uint32_t custom_resources_hash = HASH_MURMUR3_SEED;
|
||||
uint32_t custom_scene_hash = HASH_MURMUR3_SEED;
|
||||
|
||||
LocalVector<Ref<EditorExportPlugin>> customize_resources_plugins;
|
||||
LocalVector<Ref<EditorExportPlugin>> customize_scenes_plugins;
|
||||
|
||||
for (int i = 0; i < export_plugins.size(); i++) {
|
||||
if (export_plugins[i]->_begin_customize_resources(Ref<EditorExportPlatform>(this), features_psa)) {
|
||||
customize_resources_plugins.push_back(export_plugins[i]);
|
||||
|
||||
custom_resources_hash = hash_murmur3_one_64(export_plugins[i]->_get_name().hash64(), custom_resources_hash);
|
||||
uint64_t hash = export_plugins[i]->_get_customization_configuration_hash();
|
||||
custom_resources_hash = hash_murmur3_one_64(hash, custom_resources_hash);
|
||||
}
|
||||
if (export_plugins[i]->_begin_customize_scenes(Ref<EditorExportPlatform>(this), features_psa)) {
|
||||
customize_scenes_plugins.push_back(export_plugins[i]);
|
||||
|
||||
custom_resources_hash = hash_murmur3_one_64(export_plugins[i]->_get_name().hash64(), custom_resources_hash);
|
||||
uint64_t hash = export_plugins[i]->_get_customization_configuration_hash();
|
||||
custom_scene_hash = hash_murmur3_one_64(hash, custom_scene_hash);
|
||||
}
|
||||
}
|
||||
|
||||
HashMap<String, FileExportCache> export_cache;
|
||||
String export_base_path = ProjectSettings::get_singleton()->get_project_data_path().path_join("exported/") + itos(custom_resources_hash);
|
||||
|
||||
bool convert_text_to_binary = GLOBAL_GET("editor/export/convert_text_resources_to_binary");
|
||||
|
||||
if (convert_text_to_binary || customize_resources_plugins.size() || customize_scenes_plugins.size()) {
|
||||
// See if we have something to open
|
||||
Ref<FileAccess> f = FileAccess::open(export_base_path.path_join("file_cache"), FileAccess::READ);
|
||||
if (f.is_valid()) {
|
||||
String l = f->get_line();
|
||||
while (l != String()) {
|
||||
Vector<String> fields = l.split("::");
|
||||
if (fields.size() == 4) {
|
||||
FileExportCache fec;
|
||||
String path = fields[0];
|
||||
fec.source_md5 = fields[1].strip_edges();
|
||||
fec.source_modified_time = fields[2].strip_edges().to_int();
|
||||
fec.saved_path = fields[3];
|
||||
fec.used = false; // Assume unused until used.
|
||||
export_cache[path] = fec;
|
||||
}
|
||||
l = f->get_line();
|
||||
}
|
||||
} else {
|
||||
// create the path
|
||||
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
|
||||
d->change_dir(ProjectSettings::get_singleton()->get_project_data_path());
|
||||
d->make_dir_recursive("exported/" + itos(custom_resources_hash));
|
||||
}
|
||||
}
|
||||
|
||||
//store everything in the export medium
|
||||
int idx = 0;
|
||||
@ -633,85 +991,133 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
|
||||
String type = ResourceLoader::get_resource_type(path);
|
||||
|
||||
if (FileAccess::exists(path + ".import")) {
|
||||
//file is imported, replace by what it imports
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
err = config->load(path + ".import");
|
||||
if (err != OK) {
|
||||
ERR_PRINT("Could not parse: '" + path + "', not exported.");
|
||||
continue;
|
||||
}
|
||||
// Before doing this, try to see if it can be customized
|
||||
|
||||
String importer_type = config->get_value("remap", "importer");
|
||||
String export_path = _export_customize(path, customize_resources_plugins, customize_scenes_plugins, export_cache, export_base_path, false);
|
||||
|
||||
if (importer_type == "keep") {
|
||||
//just keep file as-is
|
||||
Vector<uint8_t> array = FileAccess::get_file_as_array(path);
|
||||
err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key);
|
||||
if (export_path != path) {
|
||||
// It was actually customized..
|
||||
// Since the original file is likely not recognized, just use the import system
|
||||
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
err = config->load(path + ".import");
|
||||
if (err != OK) {
|
||||
ERR_PRINT("Could not parse: '" + path + "', not exported.");
|
||||
continue;
|
||||
}
|
||||
config->set_value("remap", "type", ResourceLoader::get_resource_type(export_path));
|
||||
|
||||
// Erase all PAths
|
||||
List<String> keys;
|
||||
config->get_section_keys("remap", &keys);
|
||||
for (const String &K : keys) {
|
||||
if (E.begins_with("path")) {
|
||||
config->erase_section_key("remap", K);
|
||||
}
|
||||
}
|
||||
// Set actual converted path.
|
||||
config->set_value("remap", "path", export_path);
|
||||
|
||||
// erase useless sections
|
||||
config->erase_section("deps");
|
||||
config->erase_section("params");
|
||||
|
||||
String import_text = config->encode_to_text();
|
||||
CharString cs = import_text.utf8();
|
||||
Vector<uint8_t> sarr;
|
||||
sarr.resize(cs.size());
|
||||
memcpy(sarr.ptrw(), cs.ptr(), sarr.size());
|
||||
|
||||
err = p_func(p_udata, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, key);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
// Now actual remapped file:
|
||||
sarr = FileAccess::get_file_as_array(export_path);
|
||||
err = p_func(p_udata, export_path, sarr, idx, total, enc_in_filters, enc_ex_filters, key);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
// file is imported and not customized, replace by what it imports
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
err = config->load(path + ".import");
|
||||
if (err != OK) {
|
||||
ERR_PRINT("Could not parse: '" + path + "', not exported.");
|
||||
continue;
|
||||
}
|
||||
|
||||
String importer_type = config->get_value("remap", "importer");
|
||||
|
||||
if (importer_type == "keep") {
|
||||
//just keep file as-is
|
||||
Vector<uint8_t> array = FileAccess::get_file_as_array(path);
|
||||
err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key);
|
||||
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> remaps;
|
||||
config->get_section_keys("remap", &remaps);
|
||||
|
||||
HashSet<String> remap_features;
|
||||
|
||||
for (const String &F : remaps) {
|
||||
String remap = F;
|
||||
String feature = remap.get_slice(".", 1);
|
||||
if (features.has(feature)) {
|
||||
remap_features.insert(feature);
|
||||
}
|
||||
}
|
||||
|
||||
if (remap_features.size() > 1) {
|
||||
this->resolve_platform_feature_priorities(p_preset, remap_features);
|
||||
}
|
||||
|
||||
err = OK;
|
||||
|
||||
for (const String &F : remaps) {
|
||||
String remap = F;
|
||||
if (remap == "path") {
|
||||
String remapped_path = config->get_value("remap", remap);
|
||||
Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path);
|
||||
err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key);
|
||||
} else if (remap.begins_with("path.")) {
|
||||
String feature = remap.get_slice(".", 1);
|
||||
|
||||
if (remap_features.has(feature)) {
|
||||
String remapped_path = config->get_value("remap", remap);
|
||||
Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path);
|
||||
err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
//also save the .import file
|
||||
Vector<uint8_t> array = FileAccess::get_file_as_array(path + ".import");
|
||||
err = p_func(p_udata, path + ".import", array, idx, total, enc_in_filters, enc_ex_filters, key);
|
||||
|
||||
List<String> remaps;
|
||||
config->get_section_keys("remap", &remaps);
|
||||
|
||||
HashSet<String> remap_features;
|
||||
|
||||
for (const String &F : remaps) {
|
||||
String remap = F;
|
||||
String feature = remap.get_slice(".", 1);
|
||||
if (features.has(feature)) {
|
||||
remap_features.insert(feature);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (remap_features.size() > 1) {
|
||||
this->resolve_platform_feature_priorities(p_preset, remap_features);
|
||||
}
|
||||
|
||||
err = OK;
|
||||
|
||||
for (const String &F : remaps) {
|
||||
String remap = F;
|
||||
if (remap == "path") {
|
||||
String remapped_path = config->get_value("remap", remap);
|
||||
Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path);
|
||||
err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key);
|
||||
} else if (remap.begins_with("path.")) {
|
||||
String feature = remap.get_slice(".", 1);
|
||||
|
||||
if (remap_features.has(feature)) {
|
||||
String remapped_path = config->get_value("remap", remap);
|
||||
Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path);
|
||||
err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
//also save the .import file
|
||||
Vector<uint8_t> array = FileAccess::get_file_as_array(path + ".import");
|
||||
err = p_func(p_udata, path + ".import", array, idx, total, enc_in_filters, enc_ex_filters, key);
|
||||
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Customize
|
||||
|
||||
bool do_export = true;
|
||||
for (int i = 0; i < export_plugins.size(); i++) {
|
||||
if (export_plugins[i]->get_script_instance()) { //script based
|
||||
PackedStringArray features_psa;
|
||||
for (const String &feature : features) {
|
||||
features_psa.push_back(feature);
|
||||
}
|
||||
export_plugins.write[i]->_export_file_script(path, type, features_psa);
|
||||
} else {
|
||||
export_plugins.write[i]->_export_file(path, type, features);
|
||||
@ -748,8 +1154,18 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
|
||||
}
|
||||
//just store it as it comes
|
||||
if (do_export) {
|
||||
Vector<uint8_t> array = FileAccess::get_file_as_array(path);
|
||||
err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key);
|
||||
// Customization only happens if plugins did not take care of it before
|
||||
bool force_binary = convert_text_to_binary && (path.get_extension().to_lower() == "tres" || path.get_extension().to_lower() == "tscn");
|
||||
String export_path = _export_customize(path, customize_resources_plugins, customize_scenes_plugins, export_cache, export_base_path, force_binary);
|
||||
|
||||
if (export_path != path) {
|
||||
// Add a remap entry
|
||||
path_remaps.push_back(path);
|
||||
path_remaps.push_back(export_path);
|
||||
}
|
||||
|
||||
Vector<uint8_t> array = FileAccess::get_file_as_array(export_path);
|
||||
err = p_func(p_udata, export_path, array, idx, total, enc_in_filters, enc_ex_filters, key);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
@ -759,6 +1175,31 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (convert_text_to_binary || customize_resources_plugins.size() || customize_scenes_plugins.size()) {
|
||||
// End scene customization
|
||||
|
||||
String fcache = export_base_path.path_join("file_cache");
|
||||
Ref<FileAccess> f = FileAccess::open(fcache, FileAccess::WRITE);
|
||||
|
||||
if (f.is_valid()) {
|
||||
for (const KeyValue<String, FileExportCache> &E : export_cache) {
|
||||
if (E.value.used) { // May be old, unused
|
||||
String l = E.key + "::" + E.value.source_md5 + "::" + itos(E.value.source_modified_time) + "::" + E.value.saved_path;
|
||||
f->store_line(l);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ERR_PRINT("Error opening export file cache: " + fcache);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < customize_resources_plugins.size(); i++) {
|
||||
customize_resources_plugins[i]->_end_customize_resources();
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < customize_scenes_plugins.size(); i++) {
|
||||
customize_scenes_plugins[i]->_end_customize_scenes();
|
||||
}
|
||||
}
|
||||
//save config!
|
||||
|
||||
Vector<String> custom_list;
|
||||
|
@ -40,6 +40,8 @@ struct EditorProgress;
|
||||
#include "scene/gui/rich_text_label.h"
|
||||
#include "scene/main/node.h"
|
||||
|
||||
class EditorExportPlugin;
|
||||
|
||||
class EditorExportPlatform : public RefCounted {
|
||||
GDCLASS(EditorExportPlatform, RefCounted);
|
||||
|
||||
@ -99,6 +101,20 @@ private:
|
||||
|
||||
static Error _add_shared_object(void *p_userdata, const SharedObject &p_so);
|
||||
|
||||
struct FileExportCache {
|
||||
uint64_t source_modified_time = 0;
|
||||
String source_md5;
|
||||
String saved_path;
|
||||
bool used = false;
|
||||
};
|
||||
|
||||
bool _export_customize_dictionary(Dictionary &dict, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins);
|
||||
bool _export_customize_array(Array &array, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins);
|
||||
bool _export_customize_object(Object *p_object, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins);
|
||||
bool _export_customize_scene_resources(Node *p_root, Node *p_node, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins);
|
||||
|
||||
String _export_customize(const String &p_path, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins, LocalVector<Ref<EditorExportPlugin>> &customize_scenes_plugins, HashMap<String, FileExportCache> &export_cache, const String &export_base_path, bool p_force_save);
|
||||
|
||||
protected:
|
||||
struct ExportNotifier {
|
||||
ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags);
|
||||
|
@ -138,6 +138,64 @@ void EditorExportPlugin::_export_end_script() {
|
||||
GDVIRTUAL_CALL(_export_end);
|
||||
}
|
||||
|
||||
// Customization
|
||||
|
||||
bool EditorExportPlugin::_begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const {
|
||||
bool ret = false;
|
||||
if (GDVIRTUAL_CALL(_begin_customize_resources, p_platform, p_features, ret)) {
|
||||
return ret;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Ref<Resource> EditorExportPlugin::_customize_resource(const Ref<Resource> &p_resource, const String &p_path) {
|
||||
Ref<Resource> ret;
|
||||
if (GDVIRTUAL_REQUIRED_CALL(_customize_resource, p_resource, p_path, ret)) {
|
||||
return ret;
|
||||
}
|
||||
return Ref<Resource>();
|
||||
}
|
||||
|
||||
bool EditorExportPlugin::_begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const {
|
||||
bool ret = false;
|
||||
if (GDVIRTUAL_CALL(_begin_customize_scenes, p_platform, p_features, ret)) {
|
||||
return ret;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Node *EditorExportPlugin::_customize_scene(Node *p_root, const String &p_path) {
|
||||
Node *ret = nullptr;
|
||||
if (GDVIRTUAL_REQUIRED_CALL(_customize_scene, p_root, p_path, ret)) {
|
||||
return ret;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint64_t EditorExportPlugin::_get_customization_configuration_hash() const {
|
||||
uint64_t ret = 0;
|
||||
if (GDVIRTUAL_REQUIRED_CALL(_get_customization_configuration_hash, ret)) {
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void EditorExportPlugin::_end_customize_scenes() {
|
||||
GDVIRTUAL_CALL(_end_customize_scenes);
|
||||
}
|
||||
|
||||
void EditorExportPlugin::_end_customize_resources() {
|
||||
GDVIRTUAL_CALL(_end_customize_resources);
|
||||
}
|
||||
|
||||
String EditorExportPlugin::_get_name() const {
|
||||
String ret;
|
||||
if (GDVIRTUAL_REQUIRED_CALL(_get_name, ret)) {
|
||||
return ret;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void EditorExportPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) {
|
||||
}
|
||||
|
||||
@ -164,38 +222,20 @@ void EditorExportPlugin::_bind_methods() {
|
||||
GDVIRTUAL_BIND(_export_file, "path", "type", "features");
|
||||
GDVIRTUAL_BIND(_export_begin, "features", "is_debug", "path", "flags");
|
||||
GDVIRTUAL_BIND(_export_end);
|
||||
|
||||
GDVIRTUAL_BIND(_begin_customize_resources, "platform", "features");
|
||||
GDVIRTUAL_BIND(_customize_resource, "resource", "path");
|
||||
|
||||
GDVIRTUAL_BIND(_begin_customize_scenes, "platform", "features");
|
||||
GDVIRTUAL_BIND(_customize_scene, "scene", "path");
|
||||
|
||||
GDVIRTUAL_BIND(_get_customization_configuration_hash);
|
||||
|
||||
GDVIRTUAL_BIND(_end_customize_scenes);
|
||||
GDVIRTUAL_BIND(_end_customize_resources);
|
||||
|
||||
GDVIRTUAL_BIND(_get_name);
|
||||
}
|
||||
|
||||
EditorExportPlugin::EditorExportPlugin() {
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
|
||||
void EditorExportTextSceneToBinaryPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) {
|
||||
String extension = p_path.get_extension().to_lower();
|
||||
if (extension != "tres" && extension != "tscn") {
|
||||
return;
|
||||
}
|
||||
|
||||
bool convert = GLOBAL_GET("editor/export/convert_text_resources_to_binary");
|
||||
if (!convert) {
|
||||
return;
|
||||
}
|
||||
String tmp_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpfile.res");
|
||||
Error err = ResourceFormatLoaderText::convert_file_to_binary(p_path, tmp_path);
|
||||
if (err != OK) {
|
||||
DirAccess::remove_file_or_error(tmp_path);
|
||||
ERR_FAIL();
|
||||
}
|
||||
Vector<uint8_t> data = FileAccess::get_file_as_array(tmp_path);
|
||||
if (data.size() == 0) {
|
||||
DirAccess::remove_file_or_error(tmp_path);
|
||||
ERR_FAIL();
|
||||
}
|
||||
DirAccess::remove_file_or_error(tmp_path);
|
||||
add_file(p_path + ".converted.res", data, true);
|
||||
}
|
||||
|
||||
EditorExportTextSceneToBinaryPlugin::EditorExportTextSceneToBinaryPlugin() {
|
||||
GLOBAL_DEF("editor/export/convert_text_resources_to_binary", false);
|
||||
}
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "core/extension/native_extension.h"
|
||||
#include "editor_export_preset.h"
|
||||
#include "editor_export_shared_object.h"
|
||||
#include "scene/main/node.h"
|
||||
|
||||
class EditorExportPlugin : public RefCounted {
|
||||
GDCLASS(EditorExportPlugin, RefCounted);
|
||||
@ -77,6 +78,7 @@ class EditorExportPlugin : public RefCounted {
|
||||
macos_plugin_files.clear();
|
||||
}
|
||||
|
||||
// Export
|
||||
void _export_file_script(const String &p_path, const String &p_type, const Vector<String> &p_features);
|
||||
void _export_begin_script(const Vector<String> &p_features, bool p_debug, const String &p_path, int p_flags);
|
||||
void _export_end_script();
|
||||
@ -108,6 +110,31 @@ protected:
|
||||
GDVIRTUAL4(_export_begin, Vector<String>, bool, String, uint32_t)
|
||||
GDVIRTUAL0(_export_end)
|
||||
|
||||
GDVIRTUAL2RC(bool, _begin_customize_resources, const Ref<EditorExportPlatform> &, const Vector<String> &)
|
||||
GDVIRTUAL2R(Ref<Resource>, _customize_resource, const Ref<Resource> &, String)
|
||||
|
||||
GDVIRTUAL2RC(bool, _begin_customize_scenes, const Ref<EditorExportPlatform> &, const Vector<String> &)
|
||||
GDVIRTUAL2R(Node *, _customize_scene, Node *, String)
|
||||
GDVIRTUAL0RC(uint64_t, _get_customization_configuration_hash)
|
||||
|
||||
GDVIRTUAL0(_end_customize_scenes)
|
||||
GDVIRTUAL0(_end_customize_resources)
|
||||
|
||||
GDVIRTUAL0RC(String, _get_name)
|
||||
|
||||
bool _begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const; // Return true if this plugin does property export customization
|
||||
Ref<Resource> _customize_resource(const Ref<Resource> &p_resource, const String &p_path); // If nothing is returned, it means do not touch (nothing changed). If something is returned (either the same or a different resource) it means changes are made.
|
||||
|
||||
bool _begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const; // Return true if this plugin does property export customization
|
||||
Node *_customize_scene(Node *p_root, const String &p_path); // Return true if a change was made
|
||||
|
||||
uint64_t _get_customization_configuration_hash() const; // Hash used for caching customized resources and scenes.
|
||||
|
||||
void _end_customize_scenes();
|
||||
void _end_customize_resources();
|
||||
|
||||
virtual String _get_name() const;
|
||||
|
||||
public:
|
||||
Vector<String> get_ios_frameworks() const;
|
||||
Vector<String> get_ios_embedded_frameworks() const;
|
||||
@ -121,12 +148,4 @@ public:
|
||||
EditorExportPlugin();
|
||||
};
|
||||
|
||||
class EditorExportTextSceneToBinaryPlugin : public EditorExportPlugin {
|
||||
GDCLASS(EditorExportTextSceneToBinaryPlugin, EditorExportPlugin);
|
||||
|
||||
public:
|
||||
virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) override;
|
||||
EditorExportTextSceneToBinaryPlugin();
|
||||
};
|
||||
|
||||
#endif // EDITOR_EXPORT_PLUGIN_H
|
||||
|
@ -36,6 +36,7 @@
|
||||
class GDExtensionExportPlugin : public EditorExportPlugin {
|
||||
protected:
|
||||
virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features);
|
||||
virtual String _get_name() const { return "GDExtension"; }
|
||||
};
|
||||
|
||||
void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) {
|
||||
|
@ -88,6 +88,8 @@ public:
|
||||
// TODO: Re-add compiled GDScript on export.
|
||||
return;
|
||||
}
|
||||
|
||||
virtual String _get_name() const override { return "GDScript"; }
|
||||
};
|
||||
|
||||
static void _editor_init() {
|
||||
|
Loading…
Reference in New Issue
Block a user