summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp14
-rw-r--r--modules/gdscript/gdscript_cache.cpp3
-rw-r--r--modules/gdscript/gdscript_compiler.cpp2
-rw-r--r--modules/gdscript/gdscript_editor.cpp26
-rw-r--r--modules/gdscript/gdscript_parser.cpp7
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.cpp1
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml8
-rw-r--r--modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp2
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp7
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.h2
-rw-r--r--modules/gltf/editor/editor_scene_importer_fbx.cpp6
-rw-r--r--modules/gltf/editor/editor_scene_importer_fbx.h2
-rw-r--r--modules/gltf/editor/editor_scene_importer_gltf.cpp6
-rw-r--r--modules/gltf/editor/editor_scene_importer_gltf.h2
-rw-r--r--modules/gltf/gltf_document.cpp110
-rw-r--r--modules/gltf/gltf_document.h14
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.cpp2
-rw-r--r--modules/mono/csharp_script.cpp1
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs89
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs4
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs68
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs10
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs10
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs15
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs718
-rw-r--r--modules/mono/glue/runtime_interop.cpp30
-rw-r--r--modules/noise/editor/noise_editor_plugin.cpp1
-rw-r--r--modules/openxr/extensions/openxr_opengl_extension.cpp2
-rw-r--r--modules/openxr/openxr_api.cpp4
30 files changed, 784 insertions, 386 deletions
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index a6840b54b8..95e577c140 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -494,8 +494,8 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
result = ref->get_parser()->head->get_datatype();
} else {
result.kind = GDScriptParser::DataType::SCRIPT;
- result.native_type = ScriptServer::get_global_class_native_base(first);
result.script_type = ResourceLoader::load(path, "Script");
+ result.native_type = result.script_type->get_instance_base_type();
result.script_path = path;
result.is_constant = true;
result.is_meta_type = false;
@@ -2733,21 +2733,13 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str
return type;
}
- type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- type.kind = GDScriptParser::DataType::CLASS;
- type.builtin_type = Variant::OBJECT;
- type.native_type = ScriptServer::get_global_class_native_base(p_class_name);
- type.class_type = ref->get_parser()->head;
- type.script_path = ref->get_parser()->script_path;
- type.is_constant = true;
- type.is_meta_type = true;
- return type;
+ return ref->get_parser()->head->get_datatype();
} else {
type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
type.kind = GDScriptParser::DataType::SCRIPT;
type.builtin_type = Variant::OBJECT;
- type.native_type = ScriptServer::get_global_class_native_base(p_class_name);
type.script_type = ResourceLoader::load(path, "Script");
+ type.native_type = type.script_type->get_instance_base_type();
type.script_path = path;
type.is_constant = true;
type.is_meta_type = true;
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 2e7263b652..021504f242 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -260,7 +260,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
Ref<GDScript> script;
r_error = OK;
if (singleton->full_gdscript_cache.has(p_path)) {
- script = Ref<GDScript>(singleton->full_gdscript_cache[p_path]);
+ script = singleton->full_gdscript_cache[p_path];
if (!p_update_from_disk) {
return script;
}
@@ -360,7 +360,6 @@ Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_
singleton->packed_scene_cache[p_path] = scene;
singleton->packed_scene_dependencies[p_path].insert(p_owner);
- scene->recreate_state();
scene->reload_from_file();
return scene;
}
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index f0ceb42f89..103eb60da9 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -2284,7 +2284,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
_set_error(vformat(R"(Could not find class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr);
return ERR_COMPILATION_FAILED;
}
- ERR_FAIL_COND_V(!base->is_valid(), ERR_BUG);
+ ERR_FAIL_COND_V(!base->is_valid() && !base->reloading, ERR_BUG);
}
p_script->base = base;
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 7628bffd22..c02ee99a86 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -3265,15 +3265,6 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
}
}
- // Need special checks for assert and preload as they are technically
- // keywords, so are not registered in GDScriptUtilityFunctions.
- if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) {
- r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD;
- r_result.class_name = "@GDScript";
- r_result.class_member = p_symbol;
- return OK;
- }
-
if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = "@GDScript";
@@ -3283,11 +3274,24 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
GDScriptParser parser;
parser.parse(p_code, p_path, true);
- GDScriptAnalyzer analyzer(&parser);
- analyzer.analyze();
GDScriptParser::CompletionContext context = parser.get_completion_context();
+ // Allows class functions with the names like built-ins to be handled properly.
+ if (context.type != GDScriptParser::COMPLETION_ATTRIBUTE) {
+ // Need special checks for assert and preload as they are technically
+ // keywords, so are not registered in GDScriptUtilityFunctions.
+ if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD;
+ r_result.class_name = "@GDScript";
+ r_result.class_member = p_symbol;
+ return OK;
+ }
+ }
+
+ GDScriptAnalyzer analyzer(&parser);
+ analyzer.analyze();
+
if (context.current_class && context.current_class->extends.size() > 0) {
bool success = false;
ClassDB::get_integer_constant(context.current_class->extends[0], p_symbol, &success);
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 84f6fb230c..f8fe015c6b 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -3801,16 +3801,19 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
break;
case GDScriptParser::DataType::SCRIPT: {
StringName class_name;
- if (export_type.script_type != nullptr && export_type.script_type.is_valid()) {
+ StringName native_base;
+ if (export_type.script_type.is_valid()) {
class_name = export_type.script_type->get_language()->get_global_class_name(export_type.script_type->get_path());
+ native_base = export_type.script_type->get_instance_base_type();
}
if (class_name == StringName()) {
Ref<Script> script = ResourceLoader::load(export_type.script_path, SNAME("Script"));
if (script.is_valid()) {
class_name = script->get_language()->get_global_class_name(export_type.script_path);
+ native_base = script->get_instance_base_type();
}
}
- if (class_name != StringName() && ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(class_name), SNAME("Resource"))) {
+ if (class_name != StringName() && native_base != StringName() && ClassDB::is_parent_class(native_base, SNAME("Resource"))) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
variable->export_info.hint_string = class_name;
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp
index 39f4c976a4..551973140d 100644
--- a/modules/gdscript/language_server/gdscript_language_protocol.cpp
+++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "editor/doc_tools.h"
+#include "editor/editor_help.h"
#include "editor/editor_log.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index 588015de62..f313f4b28f 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -14,7 +14,6 @@
<param index="1" name="base_path" type="String" />
<param index="2" name="state" type="GLTFState" />
<param index="3" name="flags" type="int" default="0" />
- <param index="4" name="bake_fps" type="int" default="30" />
<description>
Takes a [PackedByteArray] defining a gLTF and returns a [GLTFState] object through the [param state] parameter.
[b]Note:[/b] The [param base_path] tells [method append_from_buffer] where to find dependencies and can be empty.
@@ -25,8 +24,7 @@
<param index="0" name="path" type="String" />
<param index="1" name="state" type="GLTFState" />
<param index="2" name="flags" type="int" default="0" />
- <param index="3" name="bake_fps" type="int" default="30" />
- <param index="4" name="base_path" type="String" default="&quot;&quot;" />
+ <param index="3" name="base_path" type="String" default="&quot;&quot;" />
<description>
Takes a path to a gLTF file and returns a [GLTFState] object through the [param state] parameter.
[b]Note:[/b] The [param base_path] tells [method append_from_file] where to find dependencies and can be empty.
@@ -37,7 +35,6 @@
<param index="0" name="node" type="Node" />
<param index="1" name="state" type="GLTFState" />
<param index="2" name="flags" type="int" default="0" />
- <param index="3" name="bake_fps" type="int" default="30" />
<description>
Takes a Godot Engine scene node and returns a [GLTFState] object through the [param state] parameter.
</description>
@@ -52,7 +49,8 @@
<method name="generate_scene">
<return type="Node" />
<param index="0" name="state" type="GLTFState" />
- <param index="1" name="bake_fps" type="int" default="30" />
+ <param index="1" name="bake_fps" type="float" default="30" />
+ <param index="2" name="trimming" type="bool" default="false" />
<description>
Takes a [GLTFState] object through the [param state] parameter and returns a Godot Engine scene node.
</description>
diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
index 95db1c0965..0c0b134bd1 100644
--- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
+++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
@@ -85,7 +85,7 @@ void SceneExporterGLTFPlugin::_gltf2_dialog_action(String p_file) {
state.instantiate();
int32_t flags = 0;
flags |= EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS;
- Error err = doc->append_from_scene(root, state, flags, 30.0f);
+ Error err = doc->append_from_scene(root, state, flags);
if (err != OK) {
ERR_PRINT(vformat("glTF2 save scene error %s.", itos(err)));
}
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index 20c9508474..7007ea5d13 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -41,6 +41,7 @@
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "main/main.h"
+#include "scene/gui/line_edit.h"
#include "scene/main/node.h"
#include "scene/resources/animation.h"
@@ -58,7 +59,7 @@ void EditorSceneFormatImporterBlend::get_extensions(List<String> *r_extensions)
}
Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_t p_flags,
- const HashMap<StringName, Variant> &p_options, int p_bake_fps,
+ const HashMap<StringName, Variant> &p_options,
List<String> *r_missing_deps, Error *r_err) {
// Get global paths for source and sink.
@@ -227,14 +228,14 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
if (p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]) {
base_dir = sink.get_base_dir();
}
- Error err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, p_bake_fps, base_dir);
+ Error err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, base_dir);
if (err != OK) {
if (r_err) {
*r_err = FAILED;
}
return nullptr;
}
- return gltf->generate_scene(state, p_bake_fps);
+ return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]);
}
Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h
index a1485ff82e..fe687f19fc 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.h
+++ b/modules/gltf/editor/editor_scene_importer_blend.h
@@ -66,7 +66,7 @@ public:
virtual uint32_t get_import_flags() const override;
virtual void get_extensions(List<String> *r_extensions) const override;
virtual Node *import_scene(const String &p_path, uint32_t p_flags,
- const HashMap<StringName, Variant> &p_options, int p_bake_fps,
+ const HashMap<StringName, Variant> &p_options,
List<String> *r_missing_deps, Error *r_err = nullptr) override;
virtual void get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) override;
diff --git a/modules/gltf/editor/editor_scene_importer_fbx.cpp b/modules/gltf/editor/editor_scene_importer_fbx.cpp
index 017a44cccf..14f2117413 100644
--- a/modules/gltf/editor/editor_scene_importer_fbx.cpp
+++ b/modules/gltf/editor/editor_scene_importer_fbx.cpp
@@ -49,7 +49,7 @@ void EditorSceneFormatImporterFBX::get_extensions(List<String> *r_extensions) co
}
Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t p_flags,
- const HashMap<StringName, Variant> &p_options, int p_bake_fps,
+ const HashMap<StringName, Variant> &p_options,
List<String> *r_missing_deps, Error *r_err) {
// Get global paths for source and sink.
@@ -95,14 +95,14 @@ Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t
Ref<GLTFState> state;
state.instantiate();
print_verbose(vformat("glTF path: %s", sink));
- Error err = gltf->append_from_file(sink, state, p_flags, p_bake_fps);
+ Error err = gltf->append_from_file(sink, state, p_flags);
if (err != OK) {
if (r_err) {
*r_err = FAILED;
}
return nullptr;
}
- return gltf->generate_scene(state, p_bake_fps);
+ return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]);
}
Variant EditorSceneFormatImporterFBX::get_option_visibility(const String &p_path, bool p_for_animation,
diff --git a/modules/gltf/editor/editor_scene_importer_fbx.h b/modules/gltf/editor/editor_scene_importer_fbx.h
index b0039b1c8f..6bf9f3e033 100644
--- a/modules/gltf/editor/editor_scene_importer_fbx.h
+++ b/modules/gltf/editor/editor_scene_importer_fbx.h
@@ -45,7 +45,7 @@ public:
virtual uint32_t get_import_flags() const override;
virtual void get_extensions(List<String> *r_extensions) const override;
virtual Node *import_scene(const String &p_path, uint32_t p_flags,
- const HashMap<StringName, Variant> &p_options, int p_bake_fps,
+ const HashMap<StringName, Variant> &p_options,
List<String> *r_missing_deps, Error *r_err = nullptr) override;
virtual void get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) override;
diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp
index 161808aade..3cf49a3046 100644
--- a/modules/gltf/editor/editor_scene_importer_gltf.cpp
+++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp
@@ -47,13 +47,13 @@ void EditorSceneFormatImporterGLTF::get_extensions(List<String> *r_extensions) c
}
Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t p_flags,
- const HashMap<StringName, Variant> &p_options, int p_bake_fps,
+ const HashMap<StringName, Variant> &p_options,
List<String> *r_missing_deps, Error *r_err) {
Ref<GLTFDocument> doc;
doc.instantiate();
Ref<GLTFState> state;
state.instantiate();
- Error err = doc->append_from_file(p_path, state, p_flags, p_bake_fps);
+ Error err = doc->append_from_file(p_path, state, p_flags);
if (err != OK) {
if (r_err) {
*r_err = err;
@@ -63,7 +63,7 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t
if (p_options.has("animation/import")) {
state->set_create_animations(bool(p_options["animation/import"]));
}
- return doc->generate_scene(state, p_bake_fps);
+ return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]);
}
#endif // TOOLS_ENABLED
diff --git a/modules/gltf/editor/editor_scene_importer_gltf.h b/modules/gltf/editor/editor_scene_importer_gltf.h
index edca038532..c0582b26c1 100644
--- a/modules/gltf/editor/editor_scene_importer_gltf.h
+++ b/modules/gltf/editor/editor_scene_importer_gltf.h
@@ -45,7 +45,7 @@ public:
virtual uint32_t get_import_flags() const override;
virtual void get_extensions(List<String> *r_extensions) const override;
virtual Node *import_scene(const String &p_path, uint32_t p_flags,
- const HashMap<StringName, Variant> &p_options, int p_bake_fps,
+ const HashMap<StringName, Variant> &p_options,
List<String> *r_missing_deps, Error *r_err = nullptr) override;
};
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index eb8f7e5ebc..735e35ac1e 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -5860,7 +5860,7 @@ T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T
ERR_FAIL_V(p_values[0]);
}
-void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps) {
+void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, const GLTFAnimationIndex index, const float bake_fps, const bool trimming) {
Ref<GLTFAnimation> anim = state->animations[index];
String anim_name = anim->get_name();
@@ -5877,7 +5877,8 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
animation->set_loop_mode(Animation::LOOP_LINEAR);
}
- float length = 0.0;
+ double anim_start = trimming ? INFINITY : 0.0;
+ double anim_end = 0.0;
for (const KeyValue<int, GLTFAnimation::Track> &track_i : anim->get_tracks()) {
const GLTFAnimation::Track &track = track_i.value;
@@ -5907,19 +5908,40 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
transform_node_path = node_path;
}
- for (int i = 0; i < track.rotation_track.times.size(); i++) {
- length = MAX(length, track.rotation_track.times[i]);
- }
- for (int i = 0; i < track.position_track.times.size(); i++) {
- length = MAX(length, track.position_track.times[i]);
- }
- for (int i = 0; i < track.scale_track.times.size(); i++) {
- length = MAX(length, track.scale_track.times[i]);
- }
-
- for (int i = 0; i < track.weight_tracks.size(); i++) {
- for (int j = 0; j < track.weight_tracks[i].times.size(); j++) {
- length = MAX(length, track.weight_tracks[i].times[j]);
+ if (trimming) {
+ for (int i = 0; i < track.rotation_track.times.size(); i++) {
+ anim_start = MIN(anim_start, track.rotation_track.times[i]);
+ anim_end = MAX(anim_end, track.rotation_track.times[i]);
+ }
+ for (int i = 0; i < track.position_track.times.size(); i++) {
+ anim_start = MIN(anim_start, track.position_track.times[i]);
+ anim_end = MAX(anim_end, track.position_track.times[i]);
+ }
+ for (int i = 0; i < track.scale_track.times.size(); i++) {
+ anim_start = MIN(anim_start, track.scale_track.times[i]);
+ anim_end = MAX(anim_end, track.scale_track.times[i]);
+ }
+ for (int i = 0; i < track.weight_tracks.size(); i++) {
+ for (int j = 0; j < track.weight_tracks[i].times.size(); j++) {
+ anim_start = MIN(anim_start, track.weight_tracks[i].times[j]);
+ anim_end = MAX(anim_end, track.weight_tracks[i].times[j]);
+ }
+ }
+ } else {
+ // If you don't use trimming and the first key time is not at 0.0, fake keys will be inserted.
+ for (int i = 0; i < track.rotation_track.times.size(); i++) {
+ anim_end = MAX(anim_end, track.rotation_track.times[i]);
+ }
+ for (int i = 0; i < track.position_track.times.size(); i++) {
+ anim_end = MAX(anim_end, track.position_track.times[i]);
+ }
+ for (int i = 0; i < track.scale_track.times.size(); i++) {
+ anim_end = MAX(anim_end, track.scale_track.times[i]);
+ }
+ for (int i = 0; i < track.weight_tracks.size(); i++) {
+ for (int j = 0; j < track.weight_tracks[i].times.size(); j++) {
+ anim_end = MAX(anim_end, track.weight_tracks[i].times[j]);
+ }
}
}
@@ -5988,10 +6010,8 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
}
}
- //first determine animation length
-
const double increment = 1.0 / bake_fps;
- double time = 0.0;
+ double time = anim_start;
Vector3 base_pos;
Quaternion base_rot;
@@ -6017,26 +6037,26 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
if (position_idx >= 0) {
pos = _interpolate_track<Vector3>(track.position_track.times, track.position_track.values, time, track.position_track.interpolation);
- animation->position_track_insert_key(position_idx, time, pos);
+ animation->position_track_insert_key(position_idx, time - anim_start, pos);
}
if (rotation_idx >= 0) {
rot = _interpolate_track<Quaternion>(track.rotation_track.times, track.rotation_track.values, time, track.rotation_track.interpolation);
- animation->rotation_track_insert_key(rotation_idx, time, rot);
+ animation->rotation_track_insert_key(rotation_idx, time - anim_start, rot);
}
if (scale_idx >= 0) {
scale = _interpolate_track<Vector3>(track.scale_track.times, track.scale_track.values, time, track.scale_track.interpolation);
- animation->scale_track_insert_key(scale_idx, time, scale);
+ animation->scale_track_insert_key(scale_idx, time - anim_start, scale);
}
if (last) {
break;
}
time += increment;
- if (time >= length) {
+ if (time >= anim_end) {
last = true;
- time = length;
+ time = anim_end;
}
}
}
@@ -6071,21 +6091,21 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
bool last = false;
while (true) {
real_t blend = _interpolate_track<real_t>(track.weight_tracks[i].times, track.weight_tracks[i].values, time, gltf_interp);
- animation->blend_shape_track_insert_key(track_idx, time, blend);
+ animation->blend_shape_track_insert_key(track_idx, time - anim_start, blend);
if (last) {
break;
}
time += increment;
- if (time >= length) {
+ if (time >= anim_end) {
last = true;
- time = length;
+ time = anim_end;
}
}
}
}
}
- animation->set_length(length);
+ animation->set_length(anim_end - anim_start);
Ref<AnimationLibrary> library;
if (!ap->has_animation_library("")) {
@@ -6585,7 +6605,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
}
}
-Error GLTFDocument::_parse(Ref<GLTFState> state, String p_path, Ref<FileAccess> f, int p_bake_fps) {
+Error GLTFDocument::_parse(Ref<GLTFState> state, String p_path, Ref<FileAccess> f) {
Error err;
if (f.is_null()) {
return FAILED;
@@ -6636,7 +6656,7 @@ Error GLTFDocument::_parse(Ref<GLTFState> state, String p_path, Ref<FileAccess>
}
}
- err = _parse_gltf_state(state, p_path, p_bake_fps);
+ err = _parse_gltf_state(state, p_path);
ERR_FAIL_COND_V(err != OK, err);
return OK;
@@ -6754,14 +6774,14 @@ Error GLTFDocument::_serialize_file(Ref<GLTFState> state, const String p_path) {
}
void GLTFDocument::_bind_methods() {
- ClassDB::bind_method(D_METHOD("append_from_file", "path", "state", "flags", "bake_fps", "base_path"),
- &GLTFDocument::append_from_file, DEFVAL(0), DEFVAL(30), DEFVAL(String()));
- ClassDB::bind_method(D_METHOD("append_from_buffer", "bytes", "base_path", "state", "flags", "bake_fps"),
- &GLTFDocument::append_from_buffer, DEFVAL(0), DEFVAL(30));
- ClassDB::bind_method(D_METHOD("append_from_scene", "node", "state", "flags", "bake_fps"),
- &GLTFDocument::append_from_scene, DEFVAL(0), DEFVAL(30));
- ClassDB::bind_method(D_METHOD("generate_scene", "state", "bake_fps"),
- &GLTFDocument::generate_scene, DEFVAL(30));
+ ClassDB::bind_method(D_METHOD("append_from_file", "path", "state", "flags", "base_path"),
+ &GLTFDocument::append_from_file, DEFVAL(0), DEFVAL(String()));
+ ClassDB::bind_method(D_METHOD("append_from_buffer", "bytes", "base_path", "state", "flags"),
+ &GLTFDocument::append_from_buffer, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("append_from_scene", "node", "state", "flags"),
+ &GLTFDocument::append_from_scene, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("generate_scene", "state", "bake_fps", "trimming"),
+ &GLTFDocument::generate_scene, DEFVAL(30), DEFVAL(false));
ClassDB::bind_method(D_METHOD("generate_buffer", "state"),
&GLTFDocument::generate_buffer);
ClassDB::bind_method(D_METHOD("write_to_filesystem", "state", "path"),
@@ -6870,7 +6890,7 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> state, const String &p_pa
return OK;
}
-Node *GLTFDocument::generate_scene(Ref<GLTFState> state, int32_t p_bake_fps) {
+Node *GLTFDocument::generate_scene(Ref<GLTFState> state, float p_bake_fps, bool p_trimming) {
ERR_FAIL_NULL_V(state, nullptr);
ERR_FAIL_INDEX_V(0, state->root_nodes.size(), nullptr);
Error err = OK;
@@ -6884,7 +6904,7 @@ Node *GLTFDocument::generate_scene(Ref<GLTFState> state, int32_t p_bake_fps) {
root->add_child(ap, true);
ap->set_owner(root);
for (int i = 0; i < state->animations.size(); i++) {
- _import_animation(state, ap, i, p_bake_fps);
+ _import_animation(state, ap, i, p_bake_fps, p_trimming);
}
}
for (KeyValue<GLTFNodeIndex, Node *> E : state->scene_nodes) {
@@ -6910,7 +6930,7 @@ Node *GLTFDocument::generate_scene(Ref<GLTFState> state, int32_t p_bake_fps) {
return root;
}
-Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> state, uint32_t p_flags, int32_t p_bake_fps) {
+Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> state, uint32_t p_flags) {
ERR_FAIL_COND_V(state.is_null(), FAILED);
state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS;
state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS;
@@ -6930,7 +6950,7 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> state, uint32
return OK;
}
-Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> state, uint32_t p_flags, int32_t p_bake_fps) {
+Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> state, uint32_t p_flags) {
ERR_FAIL_COND_V(state.is_null(), FAILED);
// TODO Add missing texture and missing .bin file paths to r_missing_deps 2021-09-10 fire
Error err = FAILED;
@@ -6941,7 +6961,7 @@ Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_pa
file_access.instantiate();
file_access->open_custom(p_bytes.ptr(), p_bytes.size());
state->base_path = p_base_path.get_base_dir();
- err = _parse(state, state->base_path, file_access, p_bake_fps);
+ err = _parse(state, state->base_path, file_access);
ERR_FAIL_COND_V(err != OK, err);
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
@@ -6951,7 +6971,7 @@ Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_pa
return OK;
}
-Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> state, const String &p_search_path, float p_bake_fps) {
+Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> state, const String &p_search_path) {
Error err;
/* PARSE EXTENSIONS */
@@ -7047,7 +7067,7 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> state, const String &p_sear
return OK;
}
-Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags, int32_t p_bake_fps, String p_base_path) {
+Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags, String p_base_path) {
// TODO Add missing texture and missing .bin file paths to r_missing_deps 2021-09-10 fire
if (r_state == Ref<GLTFState>()) {
r_state.instantiate();
@@ -7064,7 +7084,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint
base_path = p_path.get_base_dir();
}
r_state->base_path = base_path;
- err = _parse(r_state, base_path, f, p_bake_fps);
+ err = _parse(r_state, base_path, f);
ERR_FAIL_COND_V(err != OK, err);
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index 5a0e4ff498..6eb38354a2 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -292,17 +292,17 @@ private:
static float get_max_component(const Color &p_color);
public:
- Error append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, int32_t p_bake_fps = 30, String p_base_path = String());
- Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, int32_t p_bake_fps = 30);
- Error append_from_scene(Node *p_node, Ref<GLTFState> r_state, uint32_t p_flags = 0, int32_t p_bake_fps = 30);
+ Error append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, String p_base_path = String());
+ Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> r_state, uint32_t p_flags = 0);
+ Error append_from_scene(Node *p_node, Ref<GLTFState> r_state, uint32_t p_flags = 0);
public:
- Node *generate_scene(Ref<GLTFState> state, int32_t p_bake_fps = 30.0f);
+ Node *generate_scene(Ref<GLTFState> state, float p_bake_fps = 30.0f, bool p_trimming = false);
PackedByteArray generate_buffer(Ref<GLTFState> state);
Error write_to_filesystem(Ref<GLTFState> state, const String &p_path);
public:
- Error _parse_gltf_state(Ref<GLTFState> state, const String &p_search_path, float p_bake_fps);
+ Error _parse_gltf_state(Ref<GLTFState> state, const String &p_search_path);
Error _parse_gltf_extensions(Ref<GLTFState> state);
void _process_mesh_instances(Ref<GLTFState> state, Node *scene_root);
void _generate_scene_node(Ref<GLTFState> state, Node *scene_parent,
@@ -310,7 +310,7 @@ public:
const GLTFNodeIndex node_index);
void _generate_skeleton_bone_node(Ref<GLTFState> state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index);
void _import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
- const GLTFAnimationIndex index, const int bake_fps);
+ const GLTFAnimationIndex index, const float bake_fps, const bool trimming);
void _convert_mesh_instances(Ref<GLTFState> state);
GLTFCameraIndex _convert_camera(Ref<GLTFState> state, Camera3D *p_camera);
void _convert_light_to_gltf(Light3D *light, Ref<GLTFState> state, Ref<GLTFNode> gltf_node);
@@ -368,7 +368,7 @@ public:
void _convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
String p_animation_track_name);
Error _serialize(Ref<GLTFState> state, const String &p_path);
- Error _parse(Ref<GLTFState> state, String p_path, Ref<FileAccess> f, int p_bake_fps);
+ Error _parse(Ref<GLTFState> state, String p_path, Ref<FileAccess> f);
};
#endif // GLTF_DOCUMENT_H
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp
index c8aedc8b92..89ef9ddd90 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp
@@ -40,6 +40,8 @@
#include "editor/editor_undo_redo_manager.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/camera_3d.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/separator.h"
#include "scene/main/window.h"
void GridMapEditor::_node_removed(Node *p_node) {
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index a4bffc1e3c..cfb9a0fbfb 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -46,6 +46,7 @@
#include "editor/editor_internal_calls.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
+#include "editor/inspector_dock.h"
#include "editor/node_dock.h"
#include "editor/script_templates/templates.gen.h"
#endif
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs
index 3020cfbc50..eb83833b40 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs
@@ -12,6 +12,95 @@ namespace Godot.SourceGenerators.Sample
[SuppressMessage("ReSharper", "InconsistentNaming")]
public partial class ExportedProperties : Godot.Object
{
+ // Do not generate default value
+ private String _notGenerate_Property_String = new string("not generate");
+ [Export]
+ public String NotGenerate_Complex_Lamda_Property
+ {
+ get => _notGenerate_Property_String + Convert.ToInt32("1");
+ set => _notGenerate_Property_String = value;
+ }
+
+ [Export]
+ public String NotGenerate_Lamda_NoField_Property
+ {
+ get => new string("not generate");
+ set => _notGenerate_Property_String = value;
+ }
+
+ [Export]
+ public String NotGenerate_Complex_Return_Property
+ {
+ get
+ {
+ return _notGenerate_Property_String + Convert.ToInt32("1");
+ }
+ set
+ {
+ _notGenerate_Property_String = value;
+ }
+ }
+
+ private int _notGenerate_Property_Int = 1;
+ [Export]
+ public string NotGenerate_Returns_Property
+ {
+ get
+ {
+ if (_notGenerate_Property_Int == 1)
+ {
+ return "a";
+ }
+ else
+ {
+ return "b";
+ }
+ }
+ set
+ {
+ _notGenerate_Property_Int = value == "a" ? 1 : 2;
+ }
+ }
+
+ // Full Property
+ private String _fullProperty_String = "FullProperty_String";
+ [Export]
+ public String FullProperty_String
+ {
+ get
+ {
+ return _fullProperty_String;
+ }
+ set
+ {
+ _fullProperty_String = value;
+ }
+ }
+
+ private String _fullProperty_String_Complex = new string("FullProperty_String_Complex") + Convert.ToInt32("1");
+ [Export]
+ public String FullProperty_String_Complex
+ {
+ get
+ {
+ return _fullProperty_String_Complex;
+ }
+ set
+ {
+ _fullProperty_String_Complex = value;
+ }
+ }
+
+ // Lamda Property
+ private String _lamdaProperty_String = "LamdaProperty_String";
+ [Export]
+ public String LamdaProperty_String
+ {
+ get => _lamdaProperty_String;
+ set => _lamdaProperty_String = value;
+ }
+
+ // Auto Property
[Export] private Boolean property_Boolean { get; set; } = true;
[Export] private Char property_Char { get; set; } = 'f';
[Export] private SByte property_SByte { get; set; } = 10;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
index 5dd079bb17..252f162b0c 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
@@ -292,7 +292,7 @@ namespace Godot.SourceGenerators
source.Append("if (name == PropertyName.")
.Append(propertyMemberName)
.Append(") {\n")
- .Append(" ")
+ .Append(" this.")
.Append(propertyMemberName)
.Append(" = ")
.AppendNativeVariantToManagedExpr("value", propertyTypeSymbol, propertyMarshalType)
@@ -317,7 +317,7 @@ namespace Godot.SourceGenerators
.Append(propertyMemberName)
.Append(") {\n")
.Append(" value = ")
- .AppendManagedToNativeVariantExpr(propertyMemberName, propertyMarshalType)
+ .AppendManagedToNativeVariantExpr("this." + propertyMemberName, propertyMarshalType)
.Append(";\n")
.Append(" return true;\n")
.Append(" }\n");
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
index 3d0055759e..3f588a4c90 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
@@ -163,19 +164,68 @@ namespace Godot.SourceGenerators
continue;
}
- // TODO: Detect default value from simple property getters (currently we only detect from initializers)
-
- EqualsValueClauseSyntax? initializer = property.DeclaringSyntaxReferences
- .Select(r => r.GetSyntax() as PropertyDeclarationSyntax)
- .Select(s => s?.Initializer ?? null)
- .FirstOrDefault();
+ var propertyDeclarationSyntax = property.DeclaringSyntaxReferences
+ .Select(r => r.GetSyntax() as PropertyDeclarationSyntax).FirstOrDefault();
// Fully qualify the value to avoid issues with namespaces.
string? value = null;
- if (initializer != null)
+ if (propertyDeclarationSyntax != null)
{
- var sm = context.Compilation.GetSemanticModel(initializer.SyntaxTree);
- value = initializer.Value.FullQualifiedSyntax(sm);
+ if (propertyDeclarationSyntax.Initializer != null)
+ {
+ var sm = context.Compilation.GetSemanticModel(propertyDeclarationSyntax.Initializer.SyntaxTree);
+ value = propertyDeclarationSyntax.Initializer.Value.FullQualifiedSyntax(sm);
+ }
+ else
+ {
+ var propertyGet = propertyDeclarationSyntax.AccessorList?.Accessors.Where(a => a.Keyword.IsKind(SyntaxKind.GetKeyword)).FirstOrDefault();
+ if (propertyGet != null)
+ {
+ if (propertyGet.ExpressionBody != null)
+ {
+ if (propertyGet.ExpressionBody.Expression is IdentifierNameSyntax identifierNameSyntax)
+ {
+ var sm = context.Compilation.GetSemanticModel(identifierNameSyntax.SyntaxTree);
+ var fieldSymbol = sm.GetSymbolInfo(identifierNameSyntax).Symbol as IFieldSymbol;
+ EqualsValueClauseSyntax? initializer = fieldSymbol?.DeclaringSyntaxReferences
+ .Select(r => r.GetSyntax())
+ .OfType<VariableDeclaratorSyntax>()
+ .Select(s => s.Initializer)
+ .FirstOrDefault(i => i != null);
+
+ if (initializer != null)
+ {
+ sm = context.Compilation.GetSemanticModel(initializer.SyntaxTree);
+ value = initializer.Value.FullQualifiedSyntax(sm);
+ }
+ }
+ }
+ else
+ {
+ var returns = propertyGet.DescendantNodes().OfType<ReturnStatementSyntax>();
+ if (returns.Count() == 1)
+ {// Generate only single return
+ var returnStatementSyntax = returns.Single();
+ if (returnStatementSyntax.Expression is IdentifierNameSyntax identifierNameSyntax)
+ {
+ var sm = context.Compilation.GetSemanticModel(identifierNameSyntax.SyntaxTree);
+ var fieldSymbol = sm.GetSymbolInfo(identifierNameSyntax).Symbol as IFieldSymbol;
+ EqualsValueClauseSyntax? initializer = fieldSymbol?.DeclaringSyntaxReferences
+ .Select(r => r.GetSyntax())
+ .OfType<VariableDeclaratorSyntax>()
+ .Select(s => s.Initializer)
+ .FirstOrDefault(i => i != null);
+
+ if (initializer != null)
+ {
+ sm = context.Compilation.GetSemanticModel(initializer.SyntaxTree);
+ value = initializer.Value.FullQualifiedSyntax(sm);
+ }
+ }
+ }
+ }
+ }
+ }
}
exportedMembers.Add(new ExportedPropertyMetadata(
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
index e3b7ac297d..1b9f63c22d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
@@ -474,6 +474,11 @@ namespace Godot.Collections
}
}
+ internal interface IGenericGodotArray
+ {
+ public Array UnderlyingArray { get; }
+ }
+
/// <summary>
/// Typed wrapper around Godot's Array class, an array of Variant
/// typed elements allocated in the engine in C++. Useful when
@@ -487,7 +492,8 @@ namespace Godot.Collections
IList<T>,
IReadOnlyList<T>,
ICollection<T>,
- IEnumerable<T>
+ IEnumerable<T>,
+ IGenericGodotArray
{
private static godot_variant ToVariantFunc(in Array<T> godotArray) =>
VariantUtils.CreateFromArray(godotArray);
@@ -503,6 +509,8 @@ namespace Godot.Collections
private readonly Array _underlyingArray;
+ Array IGenericGodotArray.UnderlyingArray => _underlyingArray;
+
internal ref godot_array.movable NativeValue
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
index f14790a218..cf25e1f0ae 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
@@ -344,6 +344,11 @@ namespace Godot.Collections
}
}
+ internal interface IGenericGodotDictionary
+ {
+ public Dictionary UnderlyingDictionary { get; }
+ }
+
/// <summary>
/// Typed wrapper around Godot's Dictionary class, a dictionary of Variant
/// typed elements allocated in the engine in C++. Useful when
@@ -354,7 +359,8 @@ namespace Godot.Collections
/// <typeparam name="TValue">The type of the dictionary's values.</typeparam>
public class Dictionary<[MustBeVariant] TKey, [MustBeVariant] TValue> :
IDictionary<TKey, TValue>,
- IReadOnlyDictionary<TKey, TValue>
+ IReadOnlyDictionary<TKey, TValue>,
+ IGenericGodotDictionary
{
private static godot_variant ToVariantFunc(in Dictionary<TKey, TValue> godotDictionary) =>
VariantUtils.CreateFromDictionary(godotDictionary);
@@ -370,6 +376,8 @@ namespace Godot.Collections
private readonly Dictionary _underlyingDict;
+ Dictionary IGenericGodotDictionary.UnderlyingDictionary => _underlyingDict;
+
internal ref godot_dictionary.movable NativeValue
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
index 649661ee06..d2a80d0e92 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
@@ -293,6 +293,10 @@ namespace Godot.NativeInterop
return VariantUtils.CreateFromDictionary(godotDictionary);
case Collections.Array godotArray:
return VariantUtils.CreateFromArray(godotArray);
+ case Collections.IGenericGodotDictionary godotDictionary:
+ return VariantUtils.CreateFromDictionary(godotDictionary.UnderlyingDictionary);
+ case Collections.IGenericGodotArray godotArray:
+ return VariantUtils.CreateFromArray(godotArray.UnderlyingArray);
case Variant variant:
return NativeFuncs.godotsharp_variant_new_copy((godot_variant)variant.NativeVar);
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
index b30b6a0752..c7deb6423b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
@@ -414,21 +414,6 @@ namespace Godot.NativeInterop
// StringExtensions
- public static partial void godotsharp_string_md5_buffer(in godot_string p_self,
- out godot_packed_byte_array r_md5_buffer);
-
- public static partial void godotsharp_string_md5_text(in godot_string p_self, out godot_string r_md5_text);
-
- public static partial int godotsharp_string_rfind(in godot_string p_self, in godot_string p_what, int p_from);
-
- public static partial int godotsharp_string_rfindn(in godot_string p_self, in godot_string p_what, int p_from);
-
- public static partial void godotsharp_string_sha256_buffer(in godot_string p_self,
- out godot_packed_byte_array r_sha256_buffer);
-
- public static partial void godotsharp_string_sha256_text(in godot_string p_self,
- out godot_string r_sha256_text);
-
public static partial void godotsharp_string_simplify_path(in godot_string p_self,
out godot_string r_simplified_path);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
index f511233fcc..d4329d78c1 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.IO;
using System.Security;
+using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Godot.NativeInterop;
@@ -67,30 +69,13 @@ namespace Godot
}
/// <summary>
- /// If the string is a path to a file, return the path to the file without the extension.
- /// </summary>
- /// <seealso cref="GetExtension(string)"/>
- /// <seealso cref="GetBaseDir(string)"/>
- /// <seealso cref="GetFile(string)"/>
- /// <param name="instance">The path to a file.</param>
- /// <returns>The path to the file without the extension.</returns>
- public static string GetBaseName(this string instance)
- {
- int index = instance.LastIndexOf('.');
-
- if (index > 0)
- return instance.Substring(0, index);
-
- return instance;
- }
-
- /// <summary>
/// Returns <see langword="true"/> if the strings begins
/// with the given string <paramref name="text"/>.
/// </summary>
/// <param name="instance">The string to check.</param>
/// <param name="text">The beginning string.</param>
/// <returns>If the string begins with the given string.</returns>
+ [Obsolete("Use string.StartsWith instead.")]
public static bool BeginsWith(this string instance, string text)
{
return instance.StartsWith(text);
@@ -144,15 +129,15 @@ namespace Godot
}
/// <summary>
- /// Returns the amount of substrings <paramref name="what"/> in the string.
+ /// Returns the number of occurrences of substring <paramref name="what"/> in the string.
/// </summary>
/// <param name="instance">The string where the substring will be searched.</param>
/// <param name="what">The substring that will be counted.</param>
- /// <param name="caseSensitive">If the search is case sensitive.</param>
/// <param name="from">Index to start searching from.</param>
/// <param name="to">Index to stop searching at.</param>
- /// <returns>Amount of substrings in the string.</returns>
- public static int Count(this string instance, string what, bool caseSensitive = true, int from = 0, int to = 0)
+ /// <param name="caseSensitive">If the search is case sensitive.</param>
+ /// <returns>Number of occurrences of the substring in the string.</returns>
+ public static int Count(this string instance, string what, int from = 0, int to = 0, bool caseSensitive = true)
{
if (what.Length == 0)
{
@@ -211,6 +196,82 @@ namespace Godot
}
/// <summary>
+ /// Returns the number of occurrences of substring <paramref name="what"/> (ignoring case)
+ /// between <paramref name="from"/> and <paramref name="to"/> positions. If <paramref name="from"/>
+ /// and <paramref name="to"/> equals 0 the whole string will be used. If only <paramref name="to"/>
+ /// equals 0 the remained substring will be used.
+ /// </summary>
+ /// <param name="instance">The string where the substring will be searched.</param>
+ /// <param name="what">The substring that will be counted.</param>
+ /// <param name="from">Index to start searching from.</param>
+ /// <param name="to">Index to stop searching at.</param>
+ /// <returns>Number of occurrences of the substring in the string.</returns>
+ public static int CountN(this string instance, string what, int from = 0, int to = 0)
+ {
+ return instance.Count(what, from, to, caseSensitive: false);
+ }
+
+ /// <summary>
+ /// Returns a copy of the string with indentation (leading tabs and spaces) removed.
+ /// See also <see cref="Indent"/> to add indentation.
+ /// </summary>
+ /// <param name="instance">The string to remove the indentation from.</param>
+ /// <returns>The string with the indentation removed.</returns>
+ public static string Dedent(this string instance)
+ {
+ var sb = new StringBuilder();
+ string indent = "";
+ bool hasIndent = false;
+ bool hasText = false;
+ int lineStart = 0;
+ int indentStop = -1;
+
+ for (int i = 0; i < instance.Length; i++)
+ {
+ char c = instance[i];
+ if (c == '\n')
+ {
+ if (hasText)
+ {
+ sb.Append(instance.Substring(indentStop, i - indentStop));
+ }
+ sb.Append('\n');
+ hasText = false;
+ lineStart = i + 1;
+ indentStop = -1;
+ }
+ else if (!hasText)
+ {
+ if (c > 32)
+ {
+ hasText = true;
+ if (!hasIndent)
+ {
+ hasIndent = true;
+ indent = instance.Substring(lineStart, i - lineStart);
+ indentStop = i;
+ }
+ }
+ if (hasIndent && indentStop < 0)
+ {
+ int j = i - lineStart;
+ if (j >= indent.Length || c != indent[j])
+ {
+ indentStop = i;
+ }
+ }
+ }
+ }
+
+ if (hasText)
+ {
+ sb.Append(instance.Substring(indentStop, instance.Length - indentStop));
+ }
+
+ return sb.ToString();
+ }
+
+ /// <summary>
/// Returns a copy of the string with special characters escaped using the C language standard.
/// </summary>
/// <param name="instance">The string to escape.</param>
@@ -443,29 +504,6 @@ namespace Godot
}
/// <summary>
- /// Returns <see langword="true"/> if the strings ends
- /// with the given string <paramref name="text"/>.
- /// </summary>
- /// <param name="instance">The string to check.</param>
- /// <param name="text">The ending string.</param>
- /// <returns>If the string ends with the given string.</returns>
- public static bool EndsWith(this string instance, string text)
- {
- return instance.EndsWith(text);
- }
-
- /// <summary>
- /// Erase <paramref name="chars"/> characters from the string starting from <paramref name="pos"/>.
- /// </summary>
- /// <param name="instance">The string to modify.</param>
- /// <param name="pos">Starting position from which to erase.</param>
- /// <param name="chars">Amount of characters to erase.</param>
- public static void Erase(this StringBuilder instance, int pos, int chars)
- {
- instance.Remove(pos, chars);
- }
-
- /// <summary>
/// Returns the extension without the leading period character (<c>.</c>)
/// if the string is a valid file name or path. If the string does not contain
/// an extension, returns an empty string instead.
@@ -489,7 +527,7 @@ namespace Godot
/// <returns>The extension of the file or an empty string.</returns>
public static string GetExtension(this string instance)
{
- int pos = instance.FindLast(".");
+ int pos = instance.RFind(".");
if (pos < 0)
return instance;
@@ -498,12 +536,16 @@ namespace Godot
}
/// <summary>
- /// Find the first occurrence of a substring. Optionally, the search starting position can be passed.
+ /// Returns the index of the first occurrence of the specified string in this instance,
+ /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing
+ /// to the end of the string.
+ /// Note: If you just want to know whether a string contains a substring, use the
+ /// <see cref="string.Contains(string)"/> method.
/// </summary>
/// <seealso cref="Find(string, char, int, bool)"/>
- /// <seealso cref="FindLast(string, string, bool)"/>
- /// <seealso cref="FindLast(string, string, int, bool)"/>
/// <seealso cref="FindN(string, string, int)"/>
+ /// <seealso cref="RFind(string, string, int, bool)"/>
+ /// <seealso cref="RFindN(string, string, int)"/>
/// <param name="instance">The string that will be searched.</param>
/// <param name="what">The substring to find.</param>
/// <param name="from">The search starting position.</param>
@@ -519,9 +561,9 @@ namespace Godot
/// Find the first occurrence of a char. Optionally, the search starting position can be passed.
/// </summary>
/// <seealso cref="Find(string, string, int, bool)"/>
- /// <seealso cref="FindLast(string, string, bool)"/>
- /// <seealso cref="FindLast(string, string, int, bool)"/>
/// <seealso cref="FindN(string, string, int)"/>
+ /// <seealso cref="RFind(string, string, int, bool)"/>
+ /// <seealso cref="RFindN(string, string, int)"/>
/// <param name="instance">The string that will be searched.</param>
/// <param name="what">The substring to find.</param>
/// <param name="from">The search starting position.</param>
@@ -529,50 +571,21 @@ namespace Godot
/// <returns>The first instance of the char, or -1 if not found.</returns>
public static int Find(this string instance, char what, int from = 0, bool caseSensitive = true)
{
- // TODO: Could be more efficient if we get a char version of `IndexOf`.
- // See https://github.com/dotnet/runtime/issues/44116
- return instance.IndexOf(what.ToString(), from,
- caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
- }
+ if (caseSensitive)
+ return instance.IndexOf(what, from);
- /// <summary>Find the last occurrence of a substring.</summary>
- /// <seealso cref="Find(string, string, int, bool)"/>
- /// <seealso cref="Find(string, char, int, bool)"/>
- /// <seealso cref="FindLast(string, string, int, bool)"/>
- /// <seealso cref="FindN(string, string, int)"/>
- /// <param name="instance">The string that will be searched.</param>
- /// <param name="what">The substring to find.</param>
- /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param>
- /// <returns>The starting position of the substring, or -1 if not found.</returns>
- public static int FindLast(this string instance, string what, bool caseSensitive = true)
- {
- return instance.FindLast(what, instance.Length - 1, caseSensitive);
- }
-
- /// <summary>Find the last occurrence of a substring specifying the search starting position.</summary>
- /// <seealso cref="Find(string, string, int, bool)"/>
- /// <seealso cref="Find(string, char, int, bool)"/>
- /// <seealso cref="FindLast(string, string, bool)"/>
- /// <seealso cref="FindN(string, string, int)"/>
- /// <param name="instance">The string that will be searched.</param>
- /// <param name="what">The substring to find.</param>
- /// <param name="from">The search starting position.</param>
- /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param>
- /// <returns>The starting position of the substring, or -1 if not found.</returns>
- public static int FindLast(this string instance, string what, int from, bool caseSensitive = true)
- {
- return instance.LastIndexOf(what, from,
- caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
+ return CultureInfo.InvariantCulture.CompareInfo.IndexOf(instance, what, from, CompareOptions.OrdinalIgnoreCase);
}
/// <summary>
- /// Find the first occurrence of a substring but search as case-insensitive.
- /// Optionally, the search starting position can be passed.
+ /// Returns the index of the first case-insensitive occurrence of the specified string in this instance,
+ /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing
+ /// to the end of the string.
/// </summary>
/// <seealso cref="Find(string, string, int, bool)"/>
/// <seealso cref="Find(string, char, int, bool)"/>
- /// <seealso cref="FindLast(string, string, bool)"/>
- /// <seealso cref="FindLast(string, string, int, bool)"/>
+ /// <seealso cref="RFind(string, string, int, bool)"/>
+ /// <seealso cref="RFindN(string, string, int)"/>
/// <param name="instance">The string that will be searched.</param>
/// <param name="what">The substring to find.</param>
/// <param name="from">The search starting position.</param>
@@ -616,7 +629,7 @@ namespace Godot
}
}
- int sep = Mathf.Max(rs.FindLast("/"), rs.FindLast("\\"));
+ int sep = Mathf.Max(rs.RFind("/"), rs.RFind("\\"));
if (sep == -1)
return directory;
@@ -625,6 +638,24 @@ namespace Godot
}
/// <summary>
+ /// If the string is a path to a file, return the path to the file without the extension.
+ /// </summary>
+ /// <seealso cref="GetExtension(string)"/>
+ /// <seealso cref="GetBaseDir(string)"/>
+ /// <seealso cref="GetFile(string)"/>
+ /// <param name="instance">The path to a file.</param>
+ /// <returns>The path to the file without the extension.</returns>
+ public static string GetBaseName(this string instance)
+ {
+ int index = instance.RFind(".");
+
+ if (index > 0)
+ return instance.Substring(0, index);
+
+ return instance;
+ }
+
+ /// <summary>
/// If the string is a path to a file, return the file and ignore the base directory.
/// </summary>
/// <seealso cref="GetBaseName(string)"/>
@@ -634,7 +665,7 @@ namespace Godot
/// <returns>The file name.</returns>
public static string GetFile(this string instance)
{
- int sep = Mathf.Max(instance.FindLast("/"), instance.FindLast("\\"));
+ int sep = Mathf.Max(instance.RFind("/"), instance.RFind("\\"));
if (sep == -1)
return instance;
@@ -643,8 +674,8 @@ namespace Godot
}
/// <summary>
- /// Converts the given byte array of ASCII encoded text to a string.
- /// Faster alternative to <see cref="GetStringFromUTF8"/> if the
+ /// Converts ASCII encoded array to string.
+ /// Fast alternative to <see cref="GetStringFromUTF8"/> if the
/// content is ASCII-only. Unlike the UTF-8 function this function
/// maps every byte to a character in the array. Multibyte sequences
/// will not be interpreted correctly. For parsing user input always
@@ -658,13 +689,35 @@ namespace Godot
}
/// <summary>
- /// Converts the given byte array of UTF-8 encoded text to a string.
+ /// Converts UTF-16 encoded array to string using the little endian byte order.
+ /// </summary>
+ /// <param name="bytes">A byte array of UTF-16 characters.</param>
+ /// <returns>A string created from the bytes.</returns>
+ public static string GetStringFromUTF16(this byte[] bytes)
+ {
+ return Encoding.Unicode.GetString(bytes);
+ }
+
+ /// <summary>
+ /// Converts UTF-32 encoded array to string using the little endian byte order.
+ /// </summary>
+ /// <param name="bytes">A byte array of UTF-32 characters.</param>
+ /// <returns>A string created from the bytes.</returns>
+ public static string GetStringFromUTF32(this byte[] bytes)
+ {
+ return Encoding.UTF32.GetString(bytes);
+ }
+
+ /// <summary>
+ /// Converts UTF-8 encoded array to string.
/// Slower than <see cref="GetStringFromASCII"/> but supports UTF-8
/// encoded data. Use this function if you are unsure about the
/// source of the data. For user input this function
/// should always be preferred.
/// </summary>
- /// <param name="bytes">A byte array of UTF-8 characters (a character may take up multiple bytes).</param>
+ /// <param name="bytes">
+ /// A byte array of UTF-8 characters (a character may take up multiple bytes).
+ /// </param>
/// <returns>A string created from the bytes.</returns>
public static string GetStringFromUTF8(this byte[] bytes)
{
@@ -766,18 +819,44 @@ namespace Godot
}
/// <summary>
- /// Inserts a substring at a given position.
+ /// Returns a copy of the string with lines indented with <paramref name="prefix"/>.
+ /// For example, the string can be indented with two tabs using <c>"\t\t"</c>,
+ /// or four spaces using <c>" "</c>. The prefix can be any string so it can
+ /// also be used to comment out strings with e.g. <c>"// </c>.
+ /// See also <see cref="Dedent"/> to remove indentation.
+ /// Note: Empty lines are kept empty.
/// </summary>
- /// <param name="instance">The string to modify.</param>
- /// <param name="pos">Position at which to insert the substring.</param>
- /// <param name="what">Substring to insert.</param>
- /// <returns>
- /// The string with <paramref name="what"/> inserted at the given
- /// position <paramref name="pos"/>.
- /// </returns>
- public static string Insert(this string instance, int pos, string what)
+ /// <param name="instance">The string to add indentation to.</param>
+ /// <param name="prefix">The string to use as indentation.</param>
+ /// <returns>The string with indentation added.</returns>
+ public static string Indent(this string instance, string prefix)
{
- return instance.Insert(pos, what);
+ var sb = new StringBuilder();
+ int lineStart = 0;
+
+ for (int i = 0; i < instance.Length; i++)
+ {
+ char c = instance[i];
+ if (c == '\n')
+ {
+ if (i == lineStart)
+ {
+ sb.Append(c); // Leave empty lines empty.
+ }
+ else
+ {
+ sb.Append(prefix);
+ sb.Append(instance.Substring(lineStart, i - lineStart + 1));
+ }
+ lineStart = i + 1;
+ }
+ }
+ if (lineStart != instance.Length)
+ {
+ sb.Append(prefix);
+ sb.Append(instance.Substring(lineStart));
+ }
+ return sb.ToString();
}
/// <summary>
@@ -873,19 +952,94 @@ namespace Godot
return instance.IsSubsequenceOf(text, caseSensitive: false);
}
+ private static readonly char[] _invalidFileNameCharacters = { ':', '/', '\\', '?', '*', '"', '|', '%', '<', '>' };
+
+ /// <summary>
+ /// Returns <see langword="true"/> if this string is free from characters that
+ /// aren't allowed in file names.
+ /// </summary>
+ /// <param name="instance">The string to check.</param>
+ /// <returns>If the string contains a valid file name.</returns>
+ public static bool IsValidFileName(this string instance)
+ {
+ var stripped = instance.Trim();
+ if (instance != stripped)
+ return false;
+
+ if (string.IsNullOrEmpty(stripped))
+ return false;
+
+ return instance.IndexOfAny(_invalidFileNameCharacters) == -1;
+ }
+
/// <summary>
- /// Check whether the string contains a valid <see langword="float"/>.
+ /// Returns <see langword="true"/> if this string contains a valid <see langword="float"/>.
+ /// This is inclusive of integers, and also supports exponents.
/// </summary>
+ /// <example>
+ /// <code>
+ /// GD.Print("1.7".IsValidFloat()) // Prints "True"
+ /// GD.Print("24".IsValidFloat()) // Prints "True"
+ /// GD.Print("7e3".IsValidFloat()) // Prints "True"
+ /// GD.Print("Hello".IsValidFloat()) // Prints "False"
+ /// </code>
+ /// </example>
/// <param name="instance">The string to check.</param>
/// <returns>If the string contains a valid floating point number.</returns>
public static bool IsValidFloat(this string instance)
{
- float f;
- return float.TryParse(instance, out f);
+ return float.TryParse(instance, out _);
+ }
+
+ /// <summary>
+ /// Returns <see langword="true"/> if this string contains a valid hexadecimal number.
+ /// If <paramref name="withPrefix"/> is <see langword="true"/>, then a validity of the
+ /// hexadecimal number is determined by <c>0x</c> prefix, for instance: <c>0xDEADC0DE</c>.
+ /// </summary>
+ /// <param name="instance">The string to check.</param>
+ /// <param name="withPrefix">If the string must contain the <c>0x</c> prefix to be valid.</param>
+ /// <returns>If the string contains a valid hexadecimal number.</returns>
+ public static bool IsValidHexNumber(this string instance, bool withPrefix = false)
+ {
+ if (string.IsNullOrEmpty(instance))
+ return false;
+
+ int from = 0;
+ if (instance.Length != 1 && instance[0] == '+' || instance[0] == '-')
+ {
+ from++;
+ }
+
+ if (withPrefix)
+ {
+ if (instance.Length < 3)
+ return false;
+ if (instance[from] != '0' || instance[from + 1] != 'x')
+ return false;
+ from += 2;
+ }
+
+ for (int i = from; i < instance.Length; i++)
+ {
+ char c = instance[i];
+ if (IsHexDigit(c))
+ continue;
+
+ return false;
+ }
+
+ return true;
+
+ static bool IsHexDigit(char c)
+ {
+ return char.IsDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
+ }
}
/// <summary>
- /// Check whether the string contains a valid color in HTML notation.
+ /// Returns <see langword="true"/> if this string contains a valid color in hexadecimal
+ /// HTML notation. Other HTML notations such as named colors or <c>hsl()</c> aren't
+ /// considered valid by this method and will return <see langword="false"/>.
/// </summary>
/// <param name="instance">The string to check.</param>
/// <returns>If the string contains a valid HTML color.</returns>
@@ -895,10 +1049,17 @@ namespace Godot
}
/// <summary>
- /// Check whether the string is a valid identifier. As is common in
- /// programming languages, a valid identifier may contain only letters,
- /// digits and underscores (_) and the first character may not be a digit.
+ /// Returns <see langword="true"/> if this string is a valid identifier.
+ /// A valid identifier may contain only letters, digits and underscores (<c>_</c>)
+ /// and the first character may not be a digit.
/// </summary>
+ /// <example>
+ /// <code>
+ /// GD.Print("good_ident_1".IsValidIdentifier()) // Prints "True"
+ /// GD.Print("1st_bad_ident".IsValidIdentifier()) // Prints "False"
+ /// GD.Print("bad_ident_#2".IsValidIdentifier()) // Prints "False"
+ /// </code>
+ /// </example>
/// <param name="instance">The string to check.</param>
/// <returns>If the string contains a valid identifier.</returns>
public static bool IsValidIdentifier(this string instance)
@@ -926,38 +1087,73 @@ namespace Godot
}
/// <summary>
- /// Check whether the string contains a valid integer.
+ /// Returns <see langword="true"/> if this string contains a valid <see langword="int"/>.
/// </summary>
+ /// <example>
+ /// <code>
+ /// GD.Print("7".IsValidInt()) // Prints "True"
+ /// GD.Print("14.6".IsValidInt()) // Prints "False"
+ /// GD.Print("L".IsValidInt()) // Prints "False"
+ /// GD.Print("+3".IsValidInt()) // Prints "True"
+ /// GD.Print("-12".IsValidInt()) // Prints "True"
+ /// </code>
+ /// </example>
/// <param name="instance">The string to check.</param>
/// <returns>If the string contains a valid integer.</returns>
- public static bool IsValidInteger(this string instance)
+ public static bool IsValidInt(this string instance)
{
- int f;
- return int.TryParse(instance, out f);
+ return int.TryParse(instance, out _);
}
/// <summary>
- /// Check whether the string contains a valid IP address.
+ /// Returns <see langword="true"/> if this string contains only a well-formatted
+ /// IPv4 or IPv6 address. This method considers reserved IP addresses such as
+ /// <c>0.0.0.0</c> as valid.
/// </summary>
/// <param name="instance">The string to check.</param>
/// <returns>If the string contains a valid IP address.</returns>
public static bool IsValidIPAddress(this string instance)
{
- // TODO: Support IPv6 addresses
- string[] ip = instance.Split(".");
+ if (instance.Contains(':'))
+ {
+ string[] ip = instance.Split(':');
- if (ip.Length != 4)
- return false;
+ for (int i = 0; i < ip.Length; i++)
+ {
+ string n = ip[i];
+ if (n.Length == 0)
+ continue;
+
+ if (n.IsValidHexNumber(withPrefix: false))
+ {
+ long nint = n.HexToInt();
+ if (nint < 0 || nint > 0xffff)
+ return false;
- for (int i = 0; i < ip.Length; i++)
+ continue;
+ }
+
+ if (!n.IsValidIPAddress())
+ return false;
+ }
+ }
+ else
{
- string n = ip[i];
- if (!n.IsValidInteger())
- return false;
+ string[] ip = instance.Split('.');
- int val = n.ToInt();
- if (val < 0 || val > 255)
+ if (ip.Length != 4)
return false;
+
+ for (int i = 0; i < ip.Length; i++)
+ {
+ string n = ip[i];
+ if (!n.IsValidInt())
+ return false;
+
+ int val = n.ToInt();
+ if (val < 0 || val > 255)
+ return false;
+ }
}
return true;
@@ -1003,41 +1199,20 @@ namespace Godot
}
/// <summary>
- /// Returns the length of the string in characters.
- /// </summary>
- /// <param name="instance">The string to check.</param>
- /// <returns>The length of the string.</returns>
- public static int Length(this string instance)
- {
- return instance.Length;
- }
-
- /// <summary>
/// Returns a copy of the string with characters removed from the left.
+ /// The <paramref name="chars"/> argument is a string specifying the set of characters
+ /// to be removed.
+ /// Note: The <paramref name="chars"/> is not a prefix. See <see cref="TrimPrefix"/>
+ /// method that will remove a single prefix string rather than a set of characters.
/// </summary>
/// <seealso cref="RStrip(string, string)"/>
/// <param name="instance">The string to remove characters from.</param>
/// <param name="chars">The characters to be removed.</param>
/// <returns>A copy of the string with characters removed from the left.</returns>
+ [Obsolete("Use string.TrimStart instead.")]
public static string LStrip(this string instance, string chars)
{
- int len = instance.Length;
- int beg;
-
- for (beg = 0; beg < len; beg++)
- {
- if (chars.Find(instance[beg]) == -1)
- {
- break;
- }
- }
-
- if (beg == 0)
- {
- return instance;
- }
-
- return instance.Substr(beg, len - beg);
+ return instance.TrimStart(chars.ToCharArray());
}
/// <summary>
@@ -1117,10 +1292,9 @@ namespace Godot
/// <returns>The MD5 hash of the string.</returns>
public static byte[] MD5Buffer(this string instance)
{
- using godot_string instanceStr = Marshaling.ConvertStringToNative(instance);
- NativeFuncs.godotsharp_string_md5_buffer(instanceStr, out var md5Buffer);
- using (md5Buffer)
- return Marshaling.ConvertNativePackedByteArrayToSystemArray(md5Buffer);
+#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms
+ return MD5.HashData(Encoding.UTF8.GetBytes(instance));
+#pragma warning restore CA5351
}
/// <summary>
@@ -1131,10 +1305,7 @@ namespace Godot
/// <returns>The MD5 hash of the string.</returns>
public static string MD5Text(this string instance)
{
- using godot_string instanceStr = Marshaling.ConvertStringToNative(instance);
- NativeFuncs.godotsharp_string_md5_text(instanceStr, out var md5Text);
- using (md5Text)
- return Marshaling.ConvertStringToManaged(md5Text);
+ return instance.MD5Buffer().HexEncode();
}
/// <summary>
@@ -1151,17 +1322,6 @@ namespace Godot
}
/// <summary>
- /// Returns the character code at position <paramref name="at"/>.
- /// </summary>
- /// <param name="instance">The string to check.</param>
- /// <param name="at">The position int the string for the character to check.</param>
- /// <returns>The character code.</returns>
- public static int OrdAt(this string instance, int at)
- {
- return instance[at];
- }
-
- /// <summary>
/// Format a number to have an exact number of <paramref name="digits"/>
/// after the decimal point.
/// </summary>
@@ -1282,34 +1442,47 @@ namespace Godot
}
/// <summary>
- /// Perform a search for a substring, but start from the end of the string instead of the beginning.
+ /// Returns the index of the last occurrence of the specified string in this instance,
+ /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing to
+ /// the beginning of the string.
/// </summary>
+ /// <seealso cref="Find(string, string, int, bool)"/>
+ /// <seealso cref="Find(string, char, int, bool)"/>
+ /// <seealso cref="FindN(string, string, int)"/>
/// <seealso cref="RFindN(string, string, int)"/>
/// <param name="instance">The string that will be searched.</param>
/// <param name="what">The substring to search in the string.</param>
/// <param name="from">The position at which to start searching.</param>
+ /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param>
/// <returns>The position at which the substring was found, or -1 if not found.</returns>
- public static int RFind(this string instance, string what, int from = -1)
+ public static int RFind(this string instance, string what, int from = -1, bool caseSensitive = true)
{
- using godot_string instanceStr = Marshaling.ConvertStringToNative(instance);
- using godot_string whatStr = Marshaling.ConvertStringToNative(instance);
- return NativeFuncs.godotsharp_string_rfind(instanceStr, whatStr, from);
+ if (from == -1)
+ from = instance.Length - 1;
+
+ return instance.LastIndexOf(what, from,
+ caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
}
/// <summary>
- /// Perform a search for a substring, but start from the end of the string instead of the beginning.
- /// Also search case-insensitive.
+ /// Returns the index of the last case-insensitive occurrence of the specified string in this instance,
+ /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing to
+ /// the beginning of the string.
/// </summary>
- /// <seealso cref="RFind(string, string, int)"/>
+ /// <seealso cref="Find(string, string, int, bool)"/>
+ /// <seealso cref="Find(string, char, int, bool)"/>
+ /// <seealso cref="FindN(string, string, int)"/>
+ /// <seealso cref="RFind(string, string, int, bool)"/>
/// <param name="instance">The string that will be searched.</param>
/// <param name="what">The substring to search in the string.</param>
/// <param name="from">The position at which to start searching.</param>
/// <returns>The position at which the substring was found, or -1 if not found.</returns>
public static int RFindN(this string instance, string what, int from = -1)
{
- using godot_string instanceStr = Marshaling.ConvertStringToNative(instance);
- using godot_string whatStr = Marshaling.ConvertStringToNative(instance);
- return NativeFuncs.godotsharp_string_rfindn(instanceStr, whatStr, from);
+ if (from == -1)
+ from = instance.Length - 1;
+
+ return instance.LastIndexOf(what, from, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
@@ -1332,30 +1505,43 @@ namespace Godot
/// <summary>
/// Returns a copy of the string with characters removed from the right.
+ /// The <paramref name="chars"/> argument is a string specifying the set of characters
+ /// to be removed.
+ /// Note: The <paramref name="chars"/> is not a suffix. See <see cref="TrimSuffix"/>
+ /// method that will remove a single suffix string rather than a set of characters.
/// </summary>
/// <seealso cref="LStrip(string, string)"/>
/// <param name="instance">The string to remove characters from.</param>
/// <param name="chars">The characters to be removed.</param>
/// <returns>A copy of the string with characters removed from the right.</returns>
+ [Obsolete("Use string.TrimEnd instead.")]
public static string RStrip(this string instance, string chars)
{
- int len = instance.Length;
- int end;
-
- for (end = len - 1; end >= 0; end--)
- {
- if (chars.Find(instance[end]) == -1)
- {
- break;
- }
- }
+ return instance.TrimEnd(chars.ToCharArray());
+ }
- if (end == len - 1)
- {
- return instance;
- }
+ /// <summary>
+ /// Returns the SHA-1 hash of the string as an array of bytes.
+ /// </summary>
+ /// <seealso cref="SHA1Text(string)"/>
+ /// <param name="instance">The string to hash.</param>
+ /// <returns>The SHA-1 hash of the string.</returns>
+ public static byte[] SHA1Buffer(this string instance)
+ {
+#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms
+ return SHA1.HashData(Encoding.UTF8.GetBytes(instance));
+#pragma warning restore CA5350
+ }
- return instance.Substr(0, end + 1);
+ /// <summary>
+ /// Returns the SHA-1 hash of the string as a string.
+ /// </summary>
+ /// <seealso cref="SHA1Buffer(string)"/>
+ /// <param name="instance">The string to hash.</param>
+ /// <returns>The SHA-1 hash of the string.</returns>
+ public static string SHA1Text(this string instance)
+ {
+ return instance.SHA1Buffer().HexEncode();
}
/// <summary>
@@ -1366,10 +1552,7 @@ namespace Godot
/// <returns>The SHA-256 hash of the string.</returns>
public static byte[] SHA256Buffer(this string instance)
{
- using godot_string instanceStr = Marshaling.ConvertStringToNative(instance);
- NativeFuncs.godotsharp_string_sha256_buffer(instanceStr, out var sha256Buffer);
- using (sha256Buffer)
- return Marshaling.ConvertNativePackedByteArrayToSystemArray(sha256Buffer);
+ return SHA256.HashData(Encoding.UTF8.GetBytes(instance));
}
/// <summary>
@@ -1380,10 +1563,7 @@ namespace Godot
/// <returns>The SHA-256 hash of the string.</returns>
public static string SHA256Text(this string instance)
{
- using godot_string instanceStr = Marshaling.ConvertStringToNative(instance);
- NativeFuncs.godotsharp_string_sha256_text(instanceStr, out var sha256Text);
- using (sha256Text)
- return Marshaling.ConvertStringToManaged(sha256Text);
+ return instance.SHA256Buffer().HexEncode();
}
/// <summary>
@@ -1455,7 +1635,7 @@ namespace Godot
/// <returns>The array of strings split from the string.</returns>
public static string[] Split(this string instance, string divisor, bool allowEmpty = true)
{
- return instance.Split(new[] { divisor },
+ return instance.Split(divisor,
allowEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries);
}
@@ -1503,8 +1683,10 @@ namespace Godot
};
/// <summary>
- /// Returns a copy of the string stripped of any non-printable character at the beginning and the end.
- /// The optional arguments are used to toggle stripping on the left and right edges respectively.
+ /// Returns a copy of the string stripped of any non-printable character
+ /// (including tabulations, spaces and line breaks) at the beginning and the end.
+ /// The optional arguments are used to toggle stripping on the left and right
+ /// edges respectively.
/// </summary>
/// <param name="instance">The string to strip.</param>
/// <param name="left">If the left side should be stripped.</param>
@@ -1522,6 +1704,30 @@ namespace Godot
return instance.TrimEnd(_nonPrintable);
}
+
+ /// <summary>
+ /// Returns a copy of the string stripped of any escape character.
+ /// These include all non-printable control characters of the first page
+ /// of the ASCII table (&lt; 32), such as tabulation (<c>\t</c>) and
+ /// newline (<c>\n</c> and <c>\r</c>) characters, but not spaces.
+ /// </summary>
+ /// <param name="instance">The string to strip.</param>
+ /// <returns>The string stripped of any escape characters.</returns>
+ public static string StripEscapes(this string instance)
+ {
+ var sb = new StringBuilder();
+ for (int i = 0; i < instance.Length; i++)
+ {
+ // Escape characters on first page of the ASCII table, before 32 (Space).
+ if (instance[i] < 32)
+ continue;
+
+ sb.Append(instance[i]);
+ }
+
+ return sb.ToString();
+ }
+
/// <summary>
/// Returns part of the string from the position <paramref name="from"/>, with length <paramref name="len"/>.
/// </summary>
@@ -1539,13 +1745,15 @@ namespace Godot
/// <summary>
/// Converts the String (which is a character array) to PackedByteArray (which is an array of bytes).
- /// The conversion is speeded up in comparison to <see cref="ToUTF8(string)"/> with the assumption
- /// that all the characters the String contains are only ASCII characters.
+ /// The conversion is faster compared to <see cref="ToUTF8Buffer(string)"/>,
+ /// as this method assumes that all the characters in the String are ASCII characters.
/// </summary>
- /// <seealso cref="ToUTF8(string)"/>
+ /// <seealso cref="ToUTF8Buffer(string)"/>
+ /// <seealso cref="ToUTF16Buffer(string)"/>
+ /// <seealso cref="ToUTF32Buffer(string)"/>
/// <param name="instance">The string to convert.</param>
/// <returns>The string as ASCII encoded bytes.</returns>
- public static byte[] ToAscii(this string instance)
+ public static byte[] ToASCIIBuffer(this string instance)
{
return Encoding.ASCII.GetBytes(instance);
}
@@ -1573,41 +1781,76 @@ namespace Godot
}
/// <summary>
- /// Returns the string converted to lowercase.
+ /// Converts the string (which is an array of characters) to an UTF-16 encoded array of bytes.
/// </summary>
- /// <seealso cref="ToUpper(string)"/>
+ /// <seealso cref="ToASCIIBuffer(string)"/>
+ /// <seealso cref="ToUTF32Buffer(string)"/>
+ /// <seealso cref="ToUTF8Buffer(string)"/>
/// <param name="instance">The string to convert.</param>
- /// <returns>The string converted to lowercase.</returns>
- public static string ToLower(this string instance)
+ /// <returns>The string as UTF-16 encoded bytes.</returns>
+ public static byte[] ToUTF16Buffer(this string instance)
{
- return instance.ToLower();
+ return Encoding.Unicode.GetBytes(instance);
}
/// <summary>
- /// Returns the string converted to uppercase.
+ /// Converts the string (which is an array of characters) to an UTF-32 encoded array of bytes.
/// </summary>
- /// <seealso cref="ToLower(string)"/>
+ /// <seealso cref="ToASCIIBuffer(string)"/>
+ /// <seealso cref="ToUTF16Buffer(string)"/>
+ /// <seealso cref="ToUTF8Buffer(string)"/>
/// <param name="instance">The string to convert.</param>
- /// <returns>The string converted to uppercase.</returns>
- public static string ToUpper(this string instance)
+ /// <returns>The string as UTF-32 encoded bytes.</returns>
+ public static byte[] ToUTF32Buffer(this string instance)
{
- return instance.ToUpper();
+ return Encoding.UTF32.GetBytes(instance);
}
/// <summary>
- /// Converts the String (which is an array of characters) to PackedByteArray (which is an array of bytes).
- /// The conversion is a bit slower than <see cref="ToAscii(string)"/>, but supports all UTF-8 characters.
- /// Therefore, you should prefer this function over <see cref="ToAscii(string)"/>.
+ /// Converts the string (which is an array of characters) to an UTF-8 encoded array of bytes.
+ /// The conversion is a bit slower than <see cref="ToASCIIBuffer(string)"/>,
+ /// but supports all UTF-8 characters. Therefore, you should prefer this function
+ /// over <see cref="ToASCIIBuffer(string)"/>.
/// </summary>
- /// <seealso cref="ToAscii(string)"/>
+ /// <seealso cref="ToASCIIBuffer(string)"/>
+ /// <seealso cref="ToUTF16Buffer(string)"/>
+ /// <seealso cref="ToUTF32Buffer(string)"/>
/// <param name="instance">The string to convert.</param>
/// <returns>The string as UTF-8 encoded bytes.</returns>
- public static byte[] ToUTF8(this string instance)
+ public static byte[] ToUTF8Buffer(this string instance)
{
return Encoding.UTF8.GetBytes(instance);
}
/// <summary>
+ /// Removes a given string from the start if it starts with it or leaves the string unchanged.
+ /// </summary>
+ /// <param name="instance">The string to remove the prefix from.</param>
+ /// <param name="prefix">The string to remove from the start.</param>
+ /// <returns>A copy of the string with the prefix string removed from the start.</returns>
+ public static string TrimPrefix(this string instance, string prefix)
+ {
+ if (instance.StartsWith(prefix))
+ return instance.Substring(prefix.Length);
+
+ return instance;
+ }
+
+ /// <summary>
+ /// Removes a given string from the end if it ends with it or leaves the string unchanged.
+ /// </summary>
+ /// <param name="instance">The string to remove the suffix from.</param>
+ /// <param name="suffix">The string to remove from the end.</param>
+ /// <returns>A copy of the string with the suffix string removed from the end.</returns>
+ public static string TrimSuffix(this string instance, string suffix)
+ {
+ if (instance.EndsWith(suffix))
+ return instance.Substring(0, instance.Length - suffix.Length);
+
+ return instance;
+ }
+
+ /// <summary>
/// Decodes a string in URL encoded format. This is meant to
/// decode parameters in a URL when receiving an HTTP request.
/// This mostly wraps around <see cref="Uri.UnescapeDataString"/>,
@@ -1634,6 +1877,25 @@ namespace Godot
return Uri.EscapeDataString(instance);
}
+ private const string _uniqueNodePrefix = "%";
+ private static readonly string[] _invalidNodeNameCharacters = { ".", ":", "@", "/", "\"", _uniqueNodePrefix };
+
+ /// <summary>
+ /// Removes any characters from the string that are prohibited in
+ /// <see cref="Node"/> names (<c>.</c> <c>:</c> <c>@</c> <c>/</c> <c>"</c>).
+ /// </summary>
+ /// <param name="instance">The string to sanitize.</param>
+ /// <returns>The string sanitized as a valid node name.</returns>
+ public static string ValidateNodeName(this string instance)
+ {
+ string name = instance.Replace(_invalidNodeNameCharacters[0], "");
+ for (int i = 1; i < _invalidNodeNameCharacters.Length; i++)
+ {
+ name = name.Replace(_invalidNodeNameCharacters[i], "");
+ }
+ return name;
+ }
+
/// <summary>
/// Returns a copy of the string with special characters escaped using the XML standard.
/// </summary>
diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp
index e20a88076a..338e5a0147 100644
--- a/modules/mono/glue/runtime_interop.cpp
+++ b/modules/mono/glue/runtime_interop.cpp
@@ -1067,30 +1067,6 @@ void godotsharp_dictionary_to_string(const Dictionary *p_self, String *r_str) {
*r_str = Variant(*p_self).operator String();
}
-void godotsharp_string_md5_buffer(const String *p_self, PackedByteArray *r_md5_buffer) {
- memnew_placement(r_md5_buffer, PackedByteArray(p_self->md5_buffer()));
-}
-
-void godotsharp_string_md5_text(const String *p_self, String *r_md5_text) {
- memnew_placement(r_md5_text, String(p_self->md5_text()));
-}
-
-int32_t godotsharp_string_rfind(const String *p_self, const String *p_what, int32_t p_from) {
- return p_self->rfind(*p_what, p_from);
-}
-
-int32_t godotsharp_string_rfindn(const String *p_self, const String *p_what, int32_t p_from) {
- return p_self->rfindn(*p_what, p_from);
-}
-
-void godotsharp_string_sha256_buffer(const String *p_self, PackedByteArray *r_sha256_buffer) {
- memnew_placement(r_sha256_buffer, PackedByteArray(p_self->sha256_buffer()));
-}
-
-void godotsharp_string_sha256_text(const String *p_self, String *r_sha256_text) {
- memnew_placement(r_sha256_text, String(p_self->sha256_text()));
-}
-
void godotsharp_string_simplify_path(const String *p_self, String *r_simplified_path) {
memnew_placement(r_simplified_path, String(p_self->simplify_path()));
}
@@ -1473,12 +1449,6 @@ static const void *unmanaged_callbacks[]{
(void *)godotsharp_dictionary_duplicate,
(void *)godotsharp_dictionary_remove_key,
(void *)godotsharp_dictionary_to_string,
- (void *)godotsharp_string_md5_buffer,
- (void *)godotsharp_string_md5_text,
- (void *)godotsharp_string_rfind,
- (void *)godotsharp_string_rfindn,
- (void *)godotsharp_string_sha256_buffer,
- (void *)godotsharp_string_sha256_text,
(void *)godotsharp_string_simplify_path,
(void *)godotsharp_string_to_camel_case,
(void *)godotsharp_string_to_pascal_case,
diff --git a/modules/noise/editor/noise_editor_plugin.cpp b/modules/noise/editor/noise_editor_plugin.cpp
index 47f5f8f819..a8e376e142 100644
--- a/modules/noise/editor/noise_editor_plugin.cpp
+++ b/modules/noise/editor/noise_editor_plugin.cpp
@@ -34,6 +34,7 @@
#include "editor/editor_inspector.h"
#include "editor/editor_scale.h"
+#include "scene/gui/button.h"
#include "scene/gui/texture_rect.h"
#include "modules/noise/noise.h"
diff --git a/modules/openxr/extensions/openxr_opengl_extension.cpp b/modules/openxr/extensions/openxr_opengl_extension.cpp
index 569030cc11..234c5f8391 100644
--- a/modules/openxr/extensions/openxr_opengl_extension.cpp
+++ b/modules/openxr/extensions/openxr_opengl_extension.cpp
@@ -138,7 +138,7 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex
graphics_binding_gl.display = eglGetCurrentDisplay();
graphics_binding_gl.config = (EGLConfig)0; // https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/master/src/tests/hello_xr/graphicsplugin_opengles.cpp#L122
- graphics_binding_gl.context = eglGetCurrentContext();
+ graphics_binding_gl.context = (void *)display_server->window_get_native_handle(DisplayServer::OPENGL_CONTEXT);
#else
graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR;
graphics_binding_gl.next = p_next_pointer;
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index d6580ebfa6..59d3865acd 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -51,7 +51,7 @@
#define XR_USE_GRAPHICS_API_VULKAN
#endif
#ifdef GLES3_ENABLED
-#ifdef ANDROID
+#ifdef ANDROID_ENABLED
#define XR_USE_GRAPHICS_API_OPENGL_ES
#include <EGL/egl.h>
#include <EGL/eglext.h>
@@ -59,7 +59,7 @@
#include <GLES3/gl3ext.h>
#else
#define XR_USE_GRAPHICS_API_OPENGL
-#endif // ANDROID
+#endif // ANDROID_ENABLED
#ifdef X11_ENABLED
#include OPENGL_INCLUDE_H
#define GL_GLEXT_PROTOTYPES 1